在列表中查找唯一值的快速方法

时间:2012-09-25 14:36:13

标签: java performance collections

给定一个KeyValuePairs列表,其中每对都有getValue()方法,获取List(或Set)唯一值的最快方法是什么?

以下所有产生可接受的结果。 u1似乎是预期列表大小(约1000-2000 KVP)

的最快速度

我们可以做得更好(更快)吗?

private static Set<String> u1(List<_KVPair> pairs) {
    Set<String> undefined = new HashSet<String>();

    for (_KVPair pair : pairs) {
        undefined.add(pair.getValue());
    }

    if (undefined.size() == 1) {
        return new HashSet<String>();
    }
    return undefined;
}

private static List<String> u2(List<_KVPair> pairs) {

    List<String> undefined = new ArrayList<String>();
    for (_KVPair pair : pairs) {
        if (!undefined.contains(pair.getValue())) {
            undefined.add(pair.getValue());
        }
    }

    return undefined;
}

private static List<String> u3(List<_KVPair> pairs) {

    List<String> undefined = new LinkedList<String>();

    Iterator<_KVPair> it = pairs.iterator();
    while (it.hasNext()) {
        String value = it.next().getValue();
        if (!undefined.contains(value)) {
            undefined.add(value);
        }
    }
    return undefined;
}

大约3600对,'u3'获胜。大约1500对,'u1'获胜

6 个答案:

答案 0 :(得分:7)

第一个选项应该更快。您可以通过在使用之前调整集合大小来使其更快。通常情况下,如果您预计会有少量重复项:

Set<String> undefined = new HashSet<String>(pairs.size(), 1);

请注意,我使用1作为加载因子来防止任何调整大小。

出于好奇,我进行了测试(下面的代码) - 结果是(编译后):

测试1 (注意:预热需要几分钟)

  

原始清单的大小= 3,000,没有重复:
  设置:8
  arraylist:668
  链表:1166

测试2

  

原始列表的大小= 30,000 - 所有字符串相同:
  设置:25
  arraylist:11
  linkelist:13

这是有道理的:

  • 当有很多重复项时,List#contains将运行得相当快,因为​​可以更快地找到重复项并且分配大量集合的成本+哈希算法正在处罚
  • 当没有或很少有重复时,该集合大幅获胜。
public class TestPerf {

    private static int NUM_RUN;
    private static Random r = new Random(System.currentTimeMillis());
    private static boolean random = false; //toggle to false for no duplicates in original list


    public static void main(String[] args) {

        List<String> list = new ArrayList<>();

        for (int i = 0; i < 30_000; i++) {
            list.add(getRandomString());
        }

        //warm up
        for (int i = 0; i < 10_000; i++) {
            method1(list);
            method2(list);
            method3(list);
        }

        NUM_RUN = 100;
        long sum = 0;
        long start = System.nanoTime();
        for (int i = 0; i < NUM_RUN; i++) {
            sum += method1(list);
        }
        long end = System.nanoTime();
        System.out.println("set: " + (end - start) / 1000000);

        sum = 0;
        start = System.nanoTime();
        for (int i = 0; i < NUM_RUN; i++) {
            sum += method2(list);
        }
        end = System.nanoTime();
        System.out.println("arraylist: " + (end - start) / 1000000);

        sum = 0;
        start = System.nanoTime();
        for (int i = 0; i < NUM_RUN; i++) {
            sum += method3(list);
        }
        end = System.nanoTime();
        System.out.println("linkelist: " + (end - start) / 1000000);

        System.out.println(sum);
    }

    private static int method1(final List<String> list) {
        Set<String> set = new HashSet<>(list.size(), 1);
        for (String s : list) {
            set.add(s);
        }
        return set.size();
    }

    private static int method2(final List<String> list) {
        List<String> undefined = new ArrayList<>();
        for (String s : list) {
            if (!undefined.contains(s)) {
                undefined.add(s);
            }
        }
        return undefined.size();
    }

    private static int method3(final List<String> list) {
        List<String> undefined = new LinkedList<>();

        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            String value = it.next();
            if (!undefined.contains(value)) {
                undefined.add(value);
            }
        }
        return undefined.size();
    }

    private static String getRandomString() {
        if (!random) {
            return "skdjhflkjrglajhsdkhkjqwhkdjahkshd";
        }
        int size = r.nextInt(100);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < size; i++) {
            char c = (char) ('a' + r.nextInt(27));
            sb.append(c);
        }
        System.out.println(sb);
        return sb.toString();
    }
}

答案 1 :(得分:2)

更新:请参阅下面的编辑

当您可以执行

时,迭代列表是没有意义的
return new HashSet<_KVPair>(pairs)

最差的选项是u2和u3,您将第一个列表中的项添加到第二个列表,并在循环的每次迭代中调用List.contains(item)。此操作接近O(n^2) - List.contains(item)需要将项目与可能的整个列表进行比较。避免算法需要迭代列表调用一些也遍历列表的操作。

如果您想要独特的商品,请使用Set。如果您按排序顺序需要这些项目,请使用TreeSet,否则请在99%的时间内使用HashSet

修改:我错过了你想获得一组pair.getValue();但建议是相同的 - 使用Set,不要在循环中使用List.contains()

答案 2 :(得分:2)

您可以通过将第一行更改为:

来加快u1
Set<String> undefined = new HashSet<String>(pairs.size());

除此之外,当您添加值时,该组内部必须调整大量。

答案 3 :(得分:1)

我敢说选项1是最快且最干净的。在检查是否已经包含值的方面很难击败哈希集。

基于列表的解决方案无法按照之前的答案中所述进行扩展

答案 4 :(得分:1)

另一种方法可能是Sort list然后在一个循环中你可以通过保持引用最后一个元素的引用来消除重复,如果引用相等则不添加到新列表中其他明智的添加

Collections.sort(pairs)//O(n log n)

Loop
if(!lastAdded.equals(pairs.get(i)))
 {
   //Add to list 
   //change lastAdded
 }

答案 5 :(得分:-1)

没有给出的答案从最终结果中删除重复项,它们只是删除了重复项。因此,如果一个字符串存在两次,它仍然会出现在最终结果中,但只是一次。如果那不是必需的,那么我只是浪费了五分钟......

 public Map<String, String> countOccurences(List<String> source){
       Map<String, Integer> result =   new HashMap<>(source.size());
        int temp =0;
        for (String value : source) {
            if(result.containsKey(value)){
                temp = result.get(value);
                temp++;
                result.put(value, temp);
                temp = 0;
            }
            else {
                result.put(value, 1);
            }
        }
    }
    public List<String> sublistSingles(Map<String, Integer> results){
        List<String> duplicatesRemoved = new ArrayList<>(results.size());
        for(Map.Entry<String, Integer> result:results.entrySet()){
            if(result.getValue().equals(1)){
              duplicatesRemoved.add(result.getKey());
            }
        }
        return duplicatesRemoved;
    }