【问题标题】:How to filter a collection of sets by intersection?如何按交集过滤集合?
【发布时间】:2016-12-20 09:21:36
【问题描述】:

我需要通过集合的交集来合并一个集合,并编写一个具有这样签名的函数

Collection<Set<Integer>> filter(Collection<Set<Integer>> collection);

这是一个简单的集合示例

1) {1,2,3}
2) {4}
3) {1,5}
4) {4,7}
5) {3,5}

在此示例中,我们可以看到集合 135 相交。我们可以将其重写为一个新集合{1,2,3,5}。我们也有两组也有交叉点。它们是24,我们可以创建一个新集合{4,7}。输出结果将是两个集合的集合:{1,2,3,5}{4,7}

我不知道从什么时候开始解决这个任务。

【问题讨论】:

  • 您能否更具体地说明最终输出应该是什么?电源组?
  • 当然。它应该是两个集合({1,2,3,5}{4,7})的集合。
  • @ketrox 任何给定集合的幂都可以是随机的。
  • 如果输出包含输入中没有的元素,这并不是真正的“过滤器”。

标签: java algorithm set


【解决方案1】:

这应该可以解决您的用例。它可能以更有效的方式实现,但我想这应该给你一个开始的想法:

private static Collection<Set<Integer>> mergeIntersections(Collection<Set<Integer>> collection) {
    Collection<Set<Integer>> processedCollection = mergeIntersectionsInternal(collection);
    while (!isMergedSuccessfully(processedCollection)) {
        processedCollection = mergeIntersectionsInternal(processedCollection);
    }
    return processedCollection;
}

private static boolean isMergedSuccessfully(Collection<Set<Integer>> processedCollection) {
    if (processedCollection.size() <= 1) {
        return true;
    }
    final Set<Integer> mergedNumbers = new HashSet<>();
    int totalNumbers = 0;
    for (Set<Integer> set : processedCollection) {
        totalNumbers += set.size();
        mergedNumbers.addAll(set);
    }
    if (totalNumbers > mergedNumbers.size()) {
        return false;
    }
    return true;
}

private static Collection<Set<Integer>> mergeIntersectionsInternal(Collection<Set<Integer>> collection) {
    final Collection<Set<Integer>> processedCollection = new ArrayList<>();
    // ITERATE OVER ALL SETS
    for (final Set<Integer> numberSet : collection) {
        for (final Integer number : numberSet) {
            boolean matched = false;
            // ITERATE OVER ALL PROCESSED SETS COLLECTION
            for (final Set<Integer> processedSet : processedCollection) {
                // CHECK OF THERE IS A MATCH
                if (processedSet.contains(number)) {
                    matched = true;
                    // MATCH FOUND, MERGE THE SETS
                    processedSet.addAll(numberSet);
                    // BREAK OUT OF PROCESSED COLLECTION LOOP
                    break;
                }
            }
            // IF NOT MATCHED THEN ADD AS A COLLECTION ITEM
            if (!matched) {
                processedCollection.add(new HashSet<>(numberSet));
            }
        }
    }
    return processedCollection;
}

它是这样执行的:

public static void main(String[] args) {
        final Collection<Set<Integer>> collection = new ArrayList<>();
        final Set<Integer> set1 = new HashSet<>();
        set1.add(1);
        set1.add(2);
        set1.add(3);
        collection.add(set1);

        final Set<Integer> set2 = new HashSet<>();
        set2.add(4);
        collection.add(set2);

        final Set<Integer> set3 = new HashSet<>();
        set3.add(1);
        set3.add(5);
        collection.add(set3);

        final Set<Integer> set4 = new HashSet<>();
        set4.add(4);
        set4.add(7);
        collection.add(set4);

        final Set<Integer> set5 = new HashSet<>();
        set5.add(3);
        set5.add(5);
        collection.add(set5);

        System.out.println(mergeIntersections(collection));

    }

【讨论】:

  • 我怀疑这是否符合要求。如果设置 A 与 B 和 C 相交,我看不到任何阻止 AuBuC BuC 都出现在输出列表中的机制。此外,这看起来像 O(一些大的)运行时。
  • 是的,输出将是三个集合的集合({1,2,3,5}{5,2}{4})。
  • @马克。我根据您提到的示例集值对其进行了测试,效果很好。
  • 哦,对,是的——它改变了输入集。那是作弊:/
  • 感谢奥利弗的反馈。我已经修复了突变。
【解决方案2】:

解决此问题的一种优雅方法是使用无向图,您可以将输入集中的一个元素与同一集中的至少一个其他元素连接起来,然后寻找连接的组件。

所以你的例子的图形表示是:

从中我们可以很容易地推断出连通分量:{1, 2, 3, 5} 和 {4, 7}。

这是我的代码:

Collection<Set<Integer>> filter(Collection<Set<Integer>> collection) {

    // Build the Undirected Graph represented as an adjacency list
    Map<Integer, Set<Integer>> adjacents = new HashMap<>();
    for (Set<Integer> integerSet : collection) {
        if (!integerSet.isEmpty()) {
            Iterator<Integer> it = integerSet.iterator();
            int node1 = it.next();
            while (it.hasNext()) {
                int node2 = it.next();

                if (!adjacents.containsKey(node1)) {
                    adjacents.put(node1, new HashSet<>());
                }
                if (!adjacents.containsKey(node2)) {
                    adjacents.put(node2, new HashSet<>());
                }
                adjacents.get(node1).add(node2);
                adjacents.get(node2).add(node1);
            }
        }
    }

    // Run DFS on each node to collect the Connected Components
    Collection<Set<Integer>> result = new ArrayList<>();
    Set<Integer> visited = new HashSet<>();
    for (int start : adjacents.keySet()) {
        if (!visited.contains(start)) {
            Set<Integer> resultSet = new HashSet<>();
            Deque<Integer> stack = new ArrayDeque<>();
            stack.push(start);
            while (!stack.isEmpty()) {
                int node1 = stack.pop();
                visited.add(node1);
                resultSet.add(node1);
                for (int node2 : adjacents.get(node1)) {
                    if (!visited.contains(node2)) {
                        stack.push(node2);
                    }
                }
            }
            result.add(resultSet);
        }
    }

    return result;
}

【讨论】:

  • 我不知道为什么每个人都在为这个特定问题提供完整的逐字解决方案。这不是 Stack Overflow。
  • @OliverCharlesworth 因为这很有趣
【解决方案3】:

恕我直言,最好的解决方案是Union-Find algorithm

一个实现:

public class UnionFind {

    Set<Integer> all = new HashSet<>();
    Set<Integer> representants = new HashSet<>();
    Map<Integer, Integer> parents = new HashMap<>();

    public void union(int p0, int p1) {
        int cp0 = find(p0);
        int cp1 = find(p1);
        if (cp0 != cp1) {
            int size0 = parents.get(cp0);
            int size1 = parents.get(cp1);
            if (size1 < size0) {
                int swap = cp0;
                cp0 = cp1;
                cp1 = swap;
            }
            parents.put(cp0, size0 + size1);
            parents.put(cp1, cp0);
            representants.remove(cp1);
        }
    }

    public int find(int p) {
        Integer result = parents.get(p);
        if (result == null) {
            all.add(p);
            parents.put(p, -1);
            representants.add(p);
            result = p;
        } else if (result < 0) {
            result = p;
        } else {
            result = find(result);
            parents.put(p, result);
        }
        return result;
    }

    public Collection<Set<Integer>> getGroups() {
        Map<Integer, Set<Integer>> result = new HashMap<>();
        for (Integer representant : representants) {
            result.put(representant, new HashSet<>(-parents.get(representant)));
        }
        for (Integer value : all) {
            result.get(find(value)).add(value);
        }
        return result.values();
    }

    public static Collection<Set<Integer>> filter(Collection<Set<Integer>> collection) {
        UnionFind groups = new UnionFind();
        for (Set<Integer> set : collection) {
            if (!set.isEmpty()) {
                Iterator<Integer> it = set.iterator();
                int first =  groups.find(it.next());
                while (it.hasNext()) {
                    groups.union(first, it.next());
                }
            }
        }
        return groups.getGroups();
    }
}

【讨论】:

    【解决方案4】:

    这是我的出发点。它从输入集合中删除所有集合,这可以通过先复制来轻松解决。它不会修改输入集合中的每个集合。在我的实现中,Ajay 的 main 方法打印出[[1, 2, 3, 5], [4, 7]]

    Collection<Set<Integer>> filter(Collection<Set<Integer>> collection) {
        Collection<Set<Integer>> mergedSets = new ArrayList<>(collection.size());
        // for each set at a time, merge it with all sets that intersect it
        while (! collection.isEmpty()) {
            // take out the first set; make a copy as not to mutate original sets
            Set<Integer> currentSet = new HashSet<>(removeOneElement(collection));
            // find all intersecting sets and merge them into currentSet
            // the trick is to continue merging until we find no intersecting
            boolean mergedAny;
            do {
                mergedAny = false;
                Iterator<Set<Integer>> it = collection.iterator();
                while (it.hasNext()) {
                    Set<Integer> candidate = it.next();
                    if (intersect(currentSet, candidate)) {
                        it.remove();
                        currentSet.addAll(candidate);
                        mergedAny = true;
                    }
                }
            } while (mergedAny);
            mergedSets.add(currentSet);
        }
        return mergedSets;
    }
    
    private static Set<Integer> removeOneElement(Collection<Set<Integer>> collection) {
        Iterator<Set<Integer>> it = collection.iterator();
        Set<Integer> element = it.next();
        it.remove();
        return element;
    }
    
    /** @return true if the sets have at least one element in common */
    private static boolean intersect(Set<Integer> leftSet, Set<Integer> rightSet) {
        // don’t mutate, take a copy
        Set<Integer> copy = new HashSet<>(leftSet);
        copy.retainAll(rightSet);
        return ! copy.isEmpty();
    }
    

    【讨论】:

    • 如果性能很重要并且您的成员范围不太大(例如,1 到 100 或 1 到 1000),您将需要研究 java.util.BitSet 以开发更多高效实施。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-20
    • 2019-01-12
    • 1970-01-01
    • 2018-04-16
    • 1970-01-01
    • 2014-11-27
    相关资源
    最近更新 更多