Java 唯一元素的筛选器列表

Java 唯一元素的筛选器列表,java,algorithm,list,unique,Java,Algorithm,List,Unique,我正在寻找一种优雅的方法来过滤列表中唯一的元素。例如: [1, 2, 2, 3, 1, 4] -> [3, 4] // 1 and 2 occur more than once 我找到的大多数解决方案都是手动计算所有元素的出现次数,然后按只出现一次的元素进行过滤 对我来说,这听起来不太优雅,也许有更好的解决方案、最佳实践或数据结构的名称已经解决了这个问题?我也在考虑利用流,但我不知道如何使用 请注意,我不是要求删除重复项,即[1,2,3,4],而是要求只保留唯一的元素,因此[3,

我正在寻找一种优雅的方法来过滤列表中唯一的元素。例如:

   [1, 2, 2, 3, 1, 4]
-> [3, 4] // 1 and 2 occur more than once
我找到的大多数解决方案都是手动计算所有元素的出现次数,然后按只出现一次的元素进行过滤

对我来说,这听起来不太优雅,也许有更好的解决方案、最佳实践或数据结构的名称已经解决了这个问题?我也在考虑利用流,但我不知道如何使用


请注意,我不是要求删除重复项,即
[1,2,3,4]
,而是要求只保留唯一的元素,因此
[3,4]

结果列表的顺序或收集的类型对我来说并不重要。

您可以使用来计算出现次数(
getCount(1)
表示唯一)

Bag是一个集合,允许存储多个项目及其重复计数:

public void whenAdded_thenCountIsKept() {
   Bag<Integer> bag = new HashBag<>(
   Arrays.asList(1, 2, 3, 3, 3, 1, 4));         
   assertThat(2, equalTo(bag.getCount(1)));
}
返回包中的一组唯一元素


我怀疑有没有比对只出现过一次的数据进行计数和过滤更好的方法。至少,我能想到的所有方法都将使用类似于引擎盖下的方法

此外,还不清楚您所说的优雅、可读性或性能是什么意思?因此,我将放弃一些方法


计数 下面是一个流变量,它计算出现次数(
Map
),然后过滤只出现一次的元素。它基本上与您前面描述的相同,或者与
Bag
s在发动机罩下的操作相同:

List<E> result = elements.stream() // Stream<E>
    .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())) // Map<E, Long>
    .entries() // Set<Entry<E, Long>>
    .stream()  // Stream<Entry<E, Long>>
    .filter(entry -> entry.getValue() == 1)
    .map(Entry::getKey)
    .collect(Collectors.toList());
如您所见,这只需要对
元素进行一次迭代,而不是多次迭代


从某种意义上说,这种方法是优雅的,它不会计算不必要的信息。对于您想要的,计算元素出现的频率是完全不相关的。我们只关心“它是否出现过一次或多次?”而不关心它是否出现过5次或11次。

首先需要收集所有元素,然后删除超过1个元素的组

Map<String, Long> map = Stream.of("a", "b", "a", "a", "c", "d", "c")
            .collect(Collectors.groupingBy(Function.identity(), 
                     Collectors.counting()));
map.entrySet()
    .stream()
    .filter(e -> e.getValue() == 1L)
    .map(e -> e.getKey())
    .forEach(System.out::println);

使用映射累积频率计数的想法听起来不错:它在大致线性(O(n))时间内运行,只需要O(n)额外空间

这里有一个算法需要额外的空间,代价是运行时间为O(n^2):

公共静态无效保留单体(列表)
{
int i=0;
而(i

这个想法很简单:用一个缓慢的指针,
i
,在列表上移动,直到它结束;对于
i
的每个值,从
i+1
运行一个快速指针
j
,直到列表结束,删除与
list[i]
重复的任何
list[j]
;在
j
用完后,如果发现并删除了
list[i]
的任何重复项,也可以删除
list[i]

,以下操作将使用:

使用
IntList
无需将
int
值和
Integer
对象框起来


注意:我是Eclipse集合的提交者。

您的结果列表可以保持其顺序吗?@user753642,没有。如果您不必使用O(n)额外空间,那么这是一个优雅的解决方案。如果不能使用额外的O(n)空间,则可以对列表进行排序,然后遍历并删除与当前节点具有重复邻居节点的节点。由于排序的原因,这将是O(nlogn)解决方案,但是的,在空间和时间之间始终存在权衡,您需要选择一个或另一个。
Bag
的可能副本是否有方法获取出现X次的
Bag
中的所有元素?我不仅仅需要计数-我需要元素的集合。@Johnalison您可以使用计数为1的流元素和过滤器元素当
在几行中解决这个问题时,它本质上就是OP所描述的(一个
映射
对出现次数进行计数,然后只对出现一次的元素进行过滤)。正如OP.@或use
uniqueSet
的注释一样,请参见@Zabuza
Bag
是Apache收集接口,它与Java的
Map
不同,您的第二个解决方案是我应该发布的
Set<E> result = new HashSet<>();
Set<E> appeared = new HashSet<>();

for (E element : elements) {
    if (result.contains(element)) { // 2nd occurrence
        result.remove(element);
        appeared.add(element);
        continue;
    }
    if (appeared.contains(element)) { // >2nd occurrence
        continue;
    }

    result.add(element); // 1st occurrence
}
Map<String, Long> map = Stream.of("a", "b", "a", "a", "c", "d", "c")
            .collect(Collectors.groupingBy(Function.identity(), 
                     Collectors.counting()));
map.entrySet()
    .stream()
    .filter(e -> e.getValue() == 1L)
    .map(e -> e.getKey())
    .forEach(System.out::println);
        Stream.of("a", "b", "a", "a", "c", "d", "c")
                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
                .entrySet()
                .stream()
                .filter(e -> e.getValue() == 1L)
                .map(e -> e.getKey())
                .forEach(System.out::println);
public static <T> void retainSingletons(List<T> list)
{
    int i = 0;
    while (i < list.size()) {
        boolean foundDup = false;
        int j = i + 1;
        while (j < list.size()) {
            if (list.get(i).equals(list.get(j))) {
                list.remove(j);
                foundDup = true;
            } else {
                ++j;
            }
        }
        if (foundDup) {
            list.remove(i);
        } else {
            ++i;
        }
    }
}
IntList list = IntLists.mutable.with(1, 2, 2, 3, 1, 4);
IntSet unique = list.toBag().selectUnique();
System.out.println(unique);