计算两个集合(Java)之间交叉点的最有效方法是什么?

时间:2016-04-18 03:33:36

标签: java performance count set intersection

问题背景

我正在比较两个(一次,实际上很多)文本文件,我想确定它们有多相似。为此,我从每个文件创建了小的,重叠的文本组。我现在想要确定一个文件中那些来自另一个文件的组的数量。

我更喜欢只使用没有外部库的Java 8。

尝试

这是我最快的两种方法。第一个包含一堆逻辑,如果其余元素无法满足阈值,则允许它停止(这总共节省了一些时间,但当然执行额外的逻辑也需要时间)。第二个是慢。它没有那些优化,实际上决定了交集,而不是仅仅计算它,并使用一个流,这对我来说是一个新的。
我有一个整数阈值和dblThreshold(相同的值转换为double),这是必须共享的较小文件的最小百分比。另外,从我的有限测试来看,似乎写入任何一个集合的所有逻辑都比使用反向参数再次调用该方法更快。

public int numberShared(Set<String> sOne, Set<String> sTwo) {
    int numFound = 0;
    if (sOne.size() > sTwo.size()) {
        int smallSize = sTwo.size();
        int left = smallSize;
        for (String item: sTwo) {
            if (numFound < threshold && ((double)numFound + left < (dblThreshold) * smallSize)) {
                break;
            }
            if (sOne.contains(item)) {
                numFound++;
            }
            left--;
        }
    } else {
        int smallSize = sOne.size();
        int left = smallSize;
        for (String item: sOne) {
            if (numFound < threshold && ((double)numFound + left < (dblThreshold) * smallSize)) {
                break;
            }
            if (sTwo.contains(item)) {
                numFound++;
            }
            left--;
        }
    }
    return numFound;
}

第二种方法:

public int numberShared(Set<String> sOne, Set<String> sTwo) {
    if (sOne.size() < sTwo.size()) {
        long numFound = sOne.parallelStream()
                            .filter(segment -> sTwo.contains(segment))
                            .collect(Collectors.counting());
        return (int)numFound;
    } else {
        long numFound = sTwo.parallelStream()
                            .filter(segment -> sOne.contains(segment))
                            .collect(Collectors.counting());
        return (int)numFound;
    }
}

非常感谢任何改进这些方法的建议,或者对这个问题采取新颖的想法和方法!

编辑:我刚刚意识到我的阈值检查的第一部分(在某些情况下,试图消除第二次检查双打的需要)是不正确的。我会尽快修改它。

1 个答案:

答案 0 :(得分:0)

如果我理解正确,您已经确定哪些方法最快,但在使用Java 8流时不确定如何实施阈值检查。这是您可以做到的一种方式 - 但请注意,如果没有适当的数据并且知道您感兴趣的阈值,我很难进行大量测试,因此请采用这种简化的测试用例用一粒盐(并根据需要调整)。

public class Sets {
    private static final int NOT_ENOUGH_MATCHES = -1;
    private static final String[] arrayOne = { "1", "2", "4", "9" };
    private static final String[] arrayTwo = { "2", "3", "5", "7", "9" };
    private static final Set<String> setOne = new HashSet<>();
    private static final Set<String> setTwo = new HashSet<>();

    public static void main(String[] ignoredArguments) {
        setOne.addAll(Arrays.asList(arrayOne));
        setTwo.addAll(Arrays.asList(arrayTwo));
        boolean isFirstSmaller = setOne.size() < setTwo.size();
        System.out.println("Number shared: " + (isFirstSmaller ?
                numberShared(setOne, setTwo) : numberShared(setTwo, setOne)));
    }

    private static long numberShared(Set<String> smallerSet, Set<String> largerSet) {
        SimpleBag bag = new SimpleBag(3, 0.5d, largerSet, smallerSet.size());
        try {
            smallerSet.forEach(eachItem -> bag.add(eachItem));
            return bag.duplicateCount;
        } catch (IllegalStateException exception) {
            return NOT_ENOUGH_MATCHES;
        }
    }

    public static class SimpleBag {
        private Map<String, Boolean> items;
        private int threshold;
        private double fraction;
        protected int duplicateCount = 0;
        private int smallerSize;
        private int numberLeft;

        public SimpleBag(int aThreshold, double aFraction, Set<String> someStrings,
                int otherSetSize) {
            threshold = aThreshold;
            fraction = aFraction;
            items = new HashMap<>();
            someStrings.forEach(eachString -> items.put(eachString, false));
            smallerSize = otherSetSize;
            numberLeft = otherSetSize;
        }

        public void add(String aString) {
            Boolean value = items.get(aString);
            boolean alreadyExists = value != null;
            if (alreadyExists) {
                duplicateCount++;
            }
            items.put(aString, alreadyExists);
            numberLeft--;
            if (cannotMeetThreshold()) {
                throw new IllegalStateException("Can't meet threshold; stopping at "
                        + duplicateCount + " duplicates");
            }
        }

        public boolean cannotMeetThreshold() {
            return duplicateCount < threshold
                    && (duplicateCount + numberLeft < fraction * smallerSize);
        }
    }
}

所以我做了一个简化的&#34; Bag-like&#34;实现以较大集合的内容开始,映射为false值的键(因为我们知道每个中只有一个)。然后我们迭代较小的集合,将每个项目添加到包中,如果它重复,则将值切换为true并跟踪重复计数(我最初做了{{1}在.count()的末尾,但这对你的特殊情况来说已经足够了)。添加每个项目后,我们会检查是否能达到阈值,在这种情况下我们会抛出异常(可能不是退出.stream().allMatch()的最漂亮方式,但在此如果它 是非法的状态)。最后,我们返回重复计数,如果遇到异常则返回.forEach()。在我的小测试中,将-1更改为0.5d以查看差异。