【问题标题】:How to remove all duplicated strings from a Java List?如何从 Java 列表中删除所有重复的字符串?
【发布时间】:2017-08-30 04:35:57
【问题描述】:

对于给定的列表,比如说[ "a", "a", "b", "c", "c" ],我需要[ "b" ]只有非重复元素)作为输出。请注意,这与使用 Set 接口进行作业不同...

我用 Java 编写了以下代码:

void unique(List<String> list) {
    Collections.sort(list);
    List<String> dup = new ArrayList<>();
    int i = 0, j = 0;

    for (String e : list) {
        i = list.indexOf(e);
        j = list.lastIndexOf(e);

        if (i != j && !dup.contains(e)) {
            dup.add(e);
        }
    }

    list.removeAll(dup);
}

它有效...但是对于大小为 85320 的列表,几分钟后结束!

【问题讨论】:

  • 我可能是一个很好的算法问题。列表的值是否受到某种限制(例如只有 ascii 字符)?
  • 如果一个元素出现超过 1 次,则必须从列表中删除所有出现的元素。
  • @ThorbjørnRavnAndersen 我不相信 OP 说不允许设置 - 只是所需的输出不仅仅是你从 new HashSet&lt;&gt;(list) 得到的。
  • @AndyTurner 明白了这一点。没错!
  • @AndyTurner 然后问题标题措辞不当。

标签: java arraylist collections set


【解决方案1】:

使用 Java 8 流:

return list.stream()
    .collect(Collectors.groupingBy(e -> e, Collectors.counting()))
    .entrySet()
    .stream()
    .filter(e -> e.getValue() == 1)
    .map(Map.Entry::getKey)
    .collect(Collectors.toList());

【讨论】:

  • @nazar_art 缺少括号(并且缺少 .stream())。现已添加。
  • 我不是 java 流的忠实拥护者(当它们提供更简单的解决方案时我仍然使用它们)的原因是它们无法同时实现简洁和清晰,在 scala 中同样的说法是list groupBy identity collect {case(k,_+:Nil) =&gt; k}
  • @minus 我相信罪魁祸首是.collect(...) 部分。整个 Collectors 场景非常复杂,可能超出了需要,而且 Java 只允许单个返回值也无济于事。
  • 我完全同意,收集器很难使用(也很难记住),并且在流和收集之间来回切换会增加很多样板。
【解决方案2】:

鉴于您可以对列表进行排序,最有效的方法是使用 ListIterator 迭代相邻元素的运行:

List<String> dup = new ArrayList<>();
Collections.sort(list);
ListIterator<String> it = list.listIterator();
while (it.hasNext()) {
  String first = it.next();

  // Count the number of elements equal to first.
  int cnt = 1;
  while (it.hasNext()) {
    String next = it.next();
    if (!first.equals(next)) {
        it.previous();
        break;
    }
    ++cnt;
  }

  // If there are more than 1 elements between i and start
  // it's duplicated. Otherwise, it's a singleton, so add it
  // to the output.
  if (cnt == 1) {
    dup.add(first);
  }
}

return dup;

ListIterator 对于不支持随机访问的列表(如LinkedList)比使用基于索引的访问更有效。

【讨论】:

    【解决方案3】:

    您可以使用streams 以更简单的步骤实现这一点,如下所示,使用内联 cmets:

    //Find out unique elements first
    List<String> unique = list.stream().distinct().collect(Collectors.toList());
    
    //List to collect output list
    List<String> output = new ArrayList<>();
    
    //Iterate over each unique element
    for(String element : unique) {
    
        //if element found only ONCE add to output list
        if(list.stream().filter(e -> e.equals(element)).count() == 1) {
            output.add(element);
        }
    }
    

    【讨论】:

    • 在我的电脑上大约需要 50-55 秒
    【解决方案4】:

    你最好的表现是用set:

        String[] xs = { "a", "a", "b", "c", "c" };
    
        Set<String> singles = new TreeSet<>();
        Set<String> multiples = new TreeSet<>();
    
        for (String x : xs) {
            if(!multiples.contains(x)){
                if(singles.contains(x)){
                    singles.remove(x);
                    multiples.add(x);
                }else{
                    singles.add(x);
                }
            }
        }
    

    这是一个单通道,插入、删除和包含是 log(n)。

    【讨论】:

    • LinkedHashSet 怎么样?
    • 可能更快,这取决于哈希的好坏,可能需要更多内存。
    • 我为每个数据项使用本机 (Java) 字符串。所以,我希望它有一个很好的哈希值。
    • 问题标题中有“未使用Set”。 TreeSetSet...
    • 注意:你可以使用if (singles.remove(x))而不是调用 contains 然后删除。
    【解决方案5】:

    您可以使用地图。执行以下操作

    1. Create a map of following type Map<String, Integer>
    2. for all elements
           check if the string is in hashmap
                 if yes then increment the value of that map entry by 1
           else add <current element , 1>
    3. now your output are those entries of the Map whose values are 1.
    

    【讨论】:

    • 但他不想使用 Set 或类似的数据结构
    • @prasanth 我不确定这就是 OP 所说的关于“未设置”的内容 - 我将其解释为它不仅仅是做一个集合会做的事情,因为所需的输出不仅仅是不同的价值观。
    • 标题说“不使用 Set”。这就是我的意思。
    猜你喜欢
    • 1970-01-01
    • 2019-09-28
    • 2017-04-18
    • 1970-01-01
    • 2014-08-02
    • 2019-06-01
    • 1970-01-01
    • 2011-12-17
    • 2019-12-15
    相关资源
    最近更新 更多