【问题标题】:Limited SortedSet有限排序集
【发布时间】:2011-12-05 08:12:44
【问题描述】:

我正在寻找元素数量有限的 SortedSet 实现。因此,如果添加了更多元素,则比较器将决定是否添加该项目并从 Set 中删除最后一个元素。

SortedSet<Integer> t1 = new LimitedSet<Integer>(3);
t1.add(5);
t1.add(3);
t1.add(1);
// [1,3,5]
t1.add(2);
// [1,2,3]
t1.add(9);
// [1,2,3]
t1.add(0);
// [0,1,2]

标准 API 中是否有一种优雅的方式来实现这一点?

我写了一个 JUnit 测试来检查实现:

@Test
public void testLimitedSortedSet() {
final LimitedSortedSet<Integer> t1 = new LimitedSortedSet<Integer>(3);
t1.add(5);
t1.add(3);
t1.add(1);
System.out.println(t1);
// [1,3,5]
t1.add(2);
System.out.println(t1);
// [1,2,3]
t1.add(9);
System.out.println(t1);
// [1,2,3]
t1.add(0);
System.out.println(t1);
// [0,1,2]
Assert.assertTrue(3 == t1.size());
Assert.assertEquals(Integer.valueOf(0), t1.first());
}

【问题讨论】:

    标签: java api sortedset


    【解决方案1】:

    使用标准 API,您必须自己完成,即扩展排序集类之一并将您想要的逻辑添加到 add()addAll() 方法。应该不会太难。

    顺便说一句,我不完全理解你的例子:

    t1.add(9);
    // [1,2,3]
    

    集合之后不应该包含[1,2,9]吗?

    编辑:我想现在我明白了:您只想保留添加到集合中的最小的 3 个元素,对吗?

    编辑 2:示例实现(未优化)可能如下所示:

    class LimitedSortedSet<E> extends TreeSet<E> {
    
      private int maxSize;
    
      LimitedSortedSet( int maxSize ) {
        this.maxSize = maxSize;
      }
    
      @Override
      public boolean addAll( Collection<? extends E> c ) {
        boolean added = super.addAll( c );        
        if( size() > maxSize ) {
          E firstToRemove = (E)toArray( )[maxSize];
          removeAll( tailSet( firstToRemove ) );
        }   
        return added;
      }
    
      @Override
      public boolean add( E o ) {    
        boolean added =  super.add( o );
        if( size() > maxSize ) {
          E firstToRemove = (E)toArray( )[maxSize];
          removeAll( tailSet( firstToRemove ) );
        }
        return added;
      }
    }
    

    请注意,tailSet() 返回包含参数的子集(如果在集合中)。这意味着如果您无法计算下一个更高的值(不需要在集合中),您将不得不读取该元素。这是在上面的代码中完成的。

    如果您可以计算下一个值,例如如果你有一组整数,做一些tailSet( lastElement + 1 ) 就足够了,你不必读取最后一个元素。

    或者,您可以自己迭代集合并删除您要保留的最后一个元素之后的所有元素。

    另一种选择,虽然这可能更有效,但在插入元素之前检查大小并相应地删除。

    更新:正如 msandiford 正确指出的那样,应该删除的第一个元素是索引 maxSize 处的元素。因此,无需读取(重新添加?)最后一个想要的元素。

    重要提示: 正如@DieterDP 正确指出的那样,上面的实现违反了Collection#add() api 合同,该合同规定,如果一个集合由于任何原因拒绝添加一个元素,而不是它是重复的,则必须抛出异常

    在上面的示例中,元素是首先添加的,但由于大小限制可能会再次删除,或者可能会删除其他元素,因此这违反了合同。

    要解决这个问题,您可能需要更改 add()addAll() 以在这些情况下抛出异常(或者可能在任何情况下使它们无法使用)并提供替代方法来添加不违反任何规则的元素现有的 api 合约。

    在任何情况下,应谨慎使用上述示例,因为将其与不知道违规的代码一起使用可能会导致不需要的且难以调试的错误。

    【讨论】:

    • 你是对的。如果排序顺序无关紧要,我可以为任务使用队列。
    • 谢谢你的例子!我写了一个单元测试但它失败了: void testLimitedSortedSet() { final LimitedSortedSet t1 = new LimitedSortedSet(3); t1.add(5); t1.add(3); t1.add(1); System.out.println(t1); // [1,3,5] t1.add(2); System.out.println(t1); // [1,2,3] t1.add(9); System.out.println(t1); // [1,2,3] t1.add(0); System.out.println(t1); // [0,1,2] Assert.assertTrue(3 == t1.size()); Assert.assertEquals(Integer.valueOf(0), t1.first()); }
    • 谢谢你的例子!我写了一个单元测试,它失败了,输出如下:[1,3,5]。就像我在@Kowser 的回答中提到的那样,我会尝试使用 NavigableSet。当我完成后,我在这里发布代码。为了您的努力,我将您的代码标记为解决方案。
    • @Andreas 请注意,我修复了代码中的一些错误(我有 headSet() 而不是 tailSet() 并且需要“读取”最后一个元素)。另请注意,这只是供您构建的示例,您仍然需要检查错误并修复它们。
    • 我知道您的代码的示例状态。我不想让你做我的“功课”:-) 我已经在调试过程中,也许我用装饰器模式接受了 Sean Patrick Floyd 的想法。
    【解决方案2】:

    我会说这是装饰器模式的典型应用,类似于Collections类暴露的装饰器集合:unmodifiableXXX、synchronizedXXX、singletonXXX等。我会以Guava的ForwardingSortedSet为基类,写一个类用您需要的功能装饰现有的SortedSet,如下所示:

    public final class SortedSets {
    
        public <T> SortedSet<T> maximumSize(
            final SortedSet<T> original, final int maximumSize){
    
            return new ForwardingSortedSet<T>() {
    
                @Override
                protected SortedSet<T> delegate() {
                    return original;
                }
    
                @Override
                public boolean add(final T e) {
                    if(original.size()<maximumSize){
                        return original.add(e);
                    }else return false;
                }
    
                // implement other methods accordingly
            };
        }
    
    }
    

    【讨论】:

    • 好主意!当我必须实现多个“特殊”集时,我会使用它。
    • 您当前的代码违反了 Collection#add 合同,该合同明确规定您在拒绝元素时不能简单地返回 false。最好定义一个 offer 方法。
    • @DieterDP 我不知道。我会争辩说,“如果一个集合拒绝添加一个特定元素,除了它已经包含该元素之外”子句与这个用例足够相似。我当然会避免引入不受接口支持的方法。因此,也许还可以编写一个 LimitedSortedSet 接口,该接口使用此方法扩展 SortedSet。但鉴于 SO 答案的范围,我选择不走完整个循环
    【解决方案3】:

    不,使用现有的 Java 库没有类似的东西。

    但是,是的,您可以使用组合构建如下所示的。我相信这会很容易。

    public class LimitedSet implements SortedSet {
    
        private TreeSet treeSet = new TreeSet();
    
        public boolean add(E e) {
            boolean result = treeSet.add(e);
            if(treeSet.size() >= expectedSize) {
                // remove the one you like ;)
            }
            return result;
        }
    
        // all other methods delegate to the "treeSet"
    
    }
    

    更新 看完你的评论

    因为您需要始终删除最后一个元素:

    • 您可以考虑在内部维护堆栈
    • 它会增加 O(n) 的内存复杂度
    • 但可以用 O(1)... 恒定时间检索最后一个元素

    我相信它应该可以解决问题

    【讨论】:

    • 我想到了这个。但我希望那里有一个有效的实现:-)。删除 TreeSet 中的最后一个元素可能代价高昂,因为必须遍历整个列表。对于 Java 1.6,我可以使用 NavigableSet,有一个 pollLast() 方法应该很快。
    • 因为您需要始终删除最后一个元素,您可以考虑在内部维护一个堆栈。它会增加 O(n) 的内存复杂度,但可以仅用 O(1)... 恒定时间检索最后一个元素。我相信它应该可以解决问题。
    • 虽然你已经选择了一个答案,但我很好奇你的想法。
    • 我不确定你的内部堆栈是什么意思。我认为 TreeSet 有一个用于存储元素并记住第一个和最后一个元素的双链表。所以 pollLast() 方法根本不应该遍历列表,因为最后一个元素是已知的。为了优化 add() 操作,可以进行预检查,是否应该添加元素。
    猜你喜欢
    • 2020-05-11
    • 1970-01-01
    • 2018-11-08
    • 1970-01-01
    • 1970-01-01
    • 2016-02-04
    • 1970-01-01
    • 2010-11-08
    • 2019-09-16
    相关资源
    最近更新 更多