【问题标题】:A TreeSet or TreeMap that allow duplicates允许重复的 TreeSet 或 TreeMap
【发布时间】:2014-04-10 16:48:42
【问题描述】:

我需要一个 Collection 来对元素进行排序,但不会删除重复项。

我选择了TreeSet,因为TreeSet 实际上将值添加到支持的TreeMap

public boolean add(E e) {
    return m.put(e, PRESENT)==null;
}

TreeMap 使用 Comparators compare 逻辑删除重复项

我写了一个Comparator,它在元素相等的情况下返回 1 而不是 0。因此,在元素相等的情况下,TreeSet 与此 Comparator 不会覆盖重复项,只会对其进行排序。

我已经针对简单的String 对象对其进行了测试,但我需要一组自定义对象。

public static void main(String[] args)
{       
        List<String> strList = Arrays.asList( new String[]{"d","b","c","z","s","b","d","a"} );      
        Set<String> strSet = new TreeSet<String>(new StringComparator());       
        strSet.addAll(strList);     
        System.out.println(strSet); 
}

class StringComparator implements Comparator<String>
{
    @Override
    public int compare(String s1, String s2)
    {
        if(s1.compareTo(s2) == 0){
            return 1;
        }
        else{
            return s1.compareTo(s2);
        }
    }
}

这种方法很好还是有更好的方法来实现这一点?

编辑

实际上我有以下类的 ArrayList:

class Fund 
{
    String fundCode;
    BigDecimal fundValue;
    .....

    public boolean equals(Object obj) {
    // uses fundCode for equality
    }
}

我需要所有fundCode 最高的fundValue

【问题讨论】:

  • 统计每个元素出现的次数对您来说是否足够好? (换句话说,在您的真实代码中,重复项是完全等价的,还是您需要保留一些差异?例如不区分大小写但保留大小写的集合或映射。)
  • 这不是一个集合。您需要一个排序列表或类似的东西。来自 javadoc:A Set is a Collection that cannot contain duplicate elements. 违约不是个好主意。
  • 如果你可以使用第三部分库,那么 Guava 库可能会有所帮助。见docs.guava-libraries.googlecode.com/git/javadoc/com/google/…(更多关于收藏的信息:code.google.com/p/guava-libraries/wiki/…
  • @JonSkeet,实际上我有一个 Fund 类的 ArrayList,并且由fundCode 检查相等性。我需要所有具有最高基金价值的基金对象。我已经相应地更新了我的问题

标签: java collections treemap treeset


【解决方案1】:

您可以使用Collections.sort 对列表进行排序。

鉴于您的Fund

List<Fund> sortMe = new ArrayList(...);
Collections.sort(sortMe, new Comparator<Fund>() {
  @Override
  public int compare(Fund left, Fund right) {
    return left.fundValue.compareTo(right.fundValue);
  }
});
// sortMe is now sorted

【讨论】:

    【解决方案2】:

    我需要所有fundValue最高的fundCode

    如果这是您想要排序的唯一原因,我建议您根本不要排序。排序主要具有 O(n log(n)) 的复杂性。找到最大值只有 O(n) 的复杂度,并且在您的列表中通过简单的迭代实现:

    List<Fund> maxFunds = new ArrayList<Fund>();
    int max = 0;
    for (Fund fund : funds) {
        if (fund.getFundValue() > max) {
            maxFunds.clear();
            max = fund.getFundValue();
    
        }
        if (fund.getFundValue() == max) {
            maxFunds.add(fund);
    
        }
    }
    

    您可以通过使用像Guava 这样的第三级库来避免该代码。见:How to get max() element from List in Guava

    【讨论】:

      【解决方案3】:

      在 TreeSet 的情况下,使用 Comparator 或 Comparable 来比较和存储对象。不调用 Equals,这就是为什么它不能识别重复的原因

      【讨论】:

        【解决方案4】:

        我们可以使用 List 来代替 TreeSet 并实现 Comparable 接口。

        public class Fund implements Comparable<Fund> {
        
            String fundCode;
            int fundValue;
        
            public Fund(String fundCode, int fundValue) {
                super();
                this.fundCode = fundCode;
                this.fundValue = fundValue;
            }
        
            public String getFundCode() {
                return fundCode;
            }
        
            public void setFundCode(String fundCode) {
                this.fundCode = fundCode;
            }
        
            public int getFundValue() {
                return fundValue;
            }
        
            public void setFundValue(int fundValue) {
                this.fundValue = fundValue;
            }
        
            public int compareTo(Fund compareFund) {
        
                int compare = ((Fund) compareFund).getFundValue();
                return compare - this.fundValue;
            }
        
            public static void main(String args[]){
        
                List<Fund> funds = new ArrayList<Fund>();
        
                Fund fund1 = new Fund("a",100);
                Fund fund2 = new Fund("b",20);
                Fund fund3 = new Fund("c",70);
                Fund fund4 = new Fund("a",100);
                funds.add(fund1);
                funds.add(fund2);
                funds.add(fund3);
                funds.add(fund4);
        
                Collections.sort(funds);
        
                for(Fund fund : funds){
                    System.out.println("Fund code: " + fund.getFundCode() +  "  Fund value : " + fund.getFundValue());
                }
            }
        }
        

        【讨论】:

          【解决方案5】:

          将元素添加到数组列表中,然后使用实用程序 Collections.sort 对元素进行排序。然后实现可比较并根据您的密钥编写您自己的 compareTo 方法。

          也不会删除重复项,也可以排序:

          List<Integer> list = new ArrayList<>();
          
          Collections.sort(list,new Comparator<Integer>() 
          {
          
            @Override
          
          
            public int compare(Objectleft, Object right) {
          
          
          **your logic**
          
               return '';
          
            }
          
          }
          
          )
          ;
          

          【讨论】:

            【解决方案6】:

            您可以使用 PriorityQueue。

            PriorityQueue<Integer> pQueue = new PriorityQueue<Integer>(); 
            

            PriorityQueue():创建一个具有默认初始容量 (11) 的 PriorityQueue,它根据元素的自然顺序对其元素进行排序。

            这是文档的链接:https://docs.oracle.com/javase/8/docs/api/java/util/PriorityQueue.html

            【讨论】:

              【解决方案7】:

              我找到了一种让TreeSet 存储重复键的方法。

              当我使用SortedContainers 在 python 中编写一些代码时,问题就出现了。我有一个对象的空间索引,我想在其中找到开始/结束经度之间的所有对象。

              经度可能是重复的,但我仍然需要能够有效地从索引中添加/删除特定对象。不幸的是,我找不到 Python SortedKeyList 的 Java 等效项,它将排序键与存储的类型分开。

              为了说明这一点,假设我们有大量的零售采购清单,我们希望获得成本在特定范围内的所有采购。

              // We are using TreeSet as a SortedList
              TreeSet _index = new TreeSet<PriceBase>()
              
              // populate the index with the purchases. 
              // Note that 2 of these have the same cost
              _index.add(new Purchase("candy", 1.03));
              Purchase _bananas = new Purchase("bananas", 1.45);
              _index.add(new Purchase(_bananas);
              _index.add(new Purchase("celery", 1.45));
              _index.add(new Purchase("chicken", 4.99));
              
              // Range scan. This iterator should return "candy", "bananas", "celery"
              NavigableSet<PriceBase> _iterator = _index.subset(
                  new PriceKey(0.99), new PriceKey(3.99));
              
              // we can also remove specific items from the list and
              // it finds the specific object even through the sort
              // key is the same
              _index.remove(_bananas);
              

              为列表创建了 3 个类

              • PriceBase:返回排序键(价格)的基类。
              • 购买:包含交易数据的子类。
              • PriceKey:用于范围搜索的子类。

              当我最初使用 TreeSet 实现它时,它可以工作,除非价格相同。诀窍是定义 compareTo() 使其具有多态性:

              1. 如果我们将 Purchase 与 PriceKey 进行比较,则只比较价格。
              2. 如果我们要比较 Purchase 和 Purchase,如果价格相同,则比较价格和名称。

              例如,这里是 PriceBase 和 Purchase 类的 compareTo() 函数。

              // in PriceBase
              @Override
              public int compareTo(PriceBase _other) {
                  return Double.compare(this.getPrice(), _other.getPrice());
              }
              
              // in Purchase
              @Override
              public int compareTo(PriceBase _other) {
              
                  // compare by price
                  int _compare = super.compareTo(_other);
              
                  if(_compare != 0) {
                      // prices are not equal
                      return _compare;
                  }
              
                  if(_other instanceof Purchase == false) {
                      throw new RuntimeException("Right compare must be a Purchase");
                  }
              
                  // compare by item name
                  Purchase _otherPurchase = (Purchase)_other;
                  return this.getName().compareTo(_otherChild.getName());
              }
              

              这个技巧允许 TreeSet 按价格对购买进行排序,但在需要唯一标识时仍然进行真正的比较。

              总之,我需要一个对象索引来支持范围扫描,其中键是一个连续值,如双精度值,并且添加/删除是有效的。

              我知道有很多其他方法可以解决这个问题,但我想避免编写自己的树类。我的解决方案似乎是一个 hack,我很惊讶我找不到其他任何东西。如果您知道更好的方法,请发表评论。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-10-26
                • 1970-01-01
                • 1970-01-01
                • 2018-07-19
                • 2021-03-22
                相关资源
                最近更新 更多