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.@或useuniqueSet
的注释一样,请参见@ZabuzaBag
是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);