Java流:按功能合并映射键

时间:2018-08-20 07:54:45

标签: java java-stream

我有一张地图Map<String, List<String>>。如果一个键是另一个键的功能,我想合并键,例如:

如果函数是“前缀”,我想在地图中给出这些值:

{"123", ["a"]]}
{"85", ["a","b"]]}
{"8591", ["c"]}

获取具有这些值的新地图:

{"123", ["a"]}
{"85", ["a","b","c"]}

此映射“减少”是用户请求的一部分,因此必须快速。我知道我可以做O(n^2),但是我正在寻找更好的方法,如果可能的话,应该平行进行。

以下是通过调用getMatchingKey函数为每个键找到超级键的代码:

    Map<String, Set<String>> result= new HashMap<>();
    for (Map.Entry<String, List<String>> entry : input.entrySet()){
        String x = getMatchingKey(entry.getKey(), input.keySet());
        if (!resultt.containsKey(x)){
            resultt.put(x, new HashSet<String>());
        }
        resultt.get(x).addAll((input.get(x)));
        resultt.get(x).addAll((entry.getValue()));
    }

编辑

我遇到的全部问题是这样的: 给定实体名称与其足迹Map<String, Footprint>的映射,我想从包含在不同实体中的Subnet中删除Footprint

Footprint对象包含Subent的列表。

所以我虽然将映射反转为Map<Subnet, List<String>>,然后将所有子网映射到它们的实体名称,而不是合并所有子网,最后从原始Map中过滤出子网。像这样:

    public Map<String, Footprint> clearOverlaps(Map<String, Footprint> footprintsMap) {

    Map<Subnet, List<String>> subnetsToGroupNameMap =
            footprintsMap.entrySet()
                    .parallelStream()
                    .flatMap(e -> e.getValue().getSubnets().stream().map(i -> new AbstractMap.SimpleEntry<>(i, e.getKey())))
                    .collect(groupingBy(e->e.getKey(), mapping(e->e.getValue(), toList())));

    Map<Subnet, Set<String>> subnetsToGroupNameFiltered = new HashMap<>();
    for (Map.Entry<Subnet, List<String>> entry : subnetsToGroupNameMap.entrySet()){
        Subnet x = findSubnetBiggerOrEqualToMe(entry.getKey(), subnetsToGroupNameMap.keySet());

        if (!subnetsToGroupNameFiltered .containsKey(x)){
            subnetsToGroupNameFiltered .put(x, new HashSet<String>());
        }
        subnetsToGroupNameFiltered .get(x).addAll((subnetsToGroupNameMap.get(x)));
        subnetsToGroupNameFiltered .get(x).addAll((entry.getValue()));

    }
    footprintsMap.entrySet().stream().forEach(entry->entry.getValue().getSubnets().stream().filter(x->!subnetsToGroupNameFiltered .containsKey(x)));
    return footprintsMap;
}

函数findSubnetBiggerOrEqualToMe在所有子网中找到包含子网实例的最大子网。

但是由于此功能应在用户请求下运行,并且Map包含数以万计的实体以及数以万计的子网,因此我需要快速的功能(内存是免费的:))

1 个答案:

答案 0 :(得分:0)

我尝试了一种方法,该方法首先按字典顺序对子网进行排序。这样可以将您对findSubnetBiggerOrEqualToMe的调用从n ^ 2减少到排序算法的复杂度(通常为〜nlog(n))。我将假设您可以订购子网,因为其逻辑应该与findSubnetBiggerOrEqualToMe中的逻辑类似。

理想地,如果所有子网的超网都是相同集合的前缀,那么这将是线性时间的简单减少。示例[1, 2, 22, 222, 3]

for (int i = 0; i < sortedEntries.size() - 1; i++)
{
    Entry<Subnet, Set<String>> subnet = sortedEntries.get(i);
    Entry<Subnet, Set<String>> potentialSupernet = sortedEntries.get(i + 1);

    if (subnet.getKey().isPrefix(potentialSupernet.getKey()))
    {
        potentialSupernet.getValue().addAll(subnet.getValue());
        sortedEntries.remove(i);
        i--;
    }
}

但是一旦遇到[1, 2, 22, 23]之类的情况(22和23不是同一网络的前缀),它就不再是简单的简化了,因为您不仅需要查找下一个条目,还需要进一步查找确保您找到了所有超级网(2个必须同时合并到22个 23个中):

for (int i = 0; i < sortedEntries.size(); i++)
{
    Entry<Subnet, Set<String>> subnet = sortedEntries.get(i);

    for (int j = i + 1; j < sortedEntries.size(); j++)
    {
        Entry<Subnet, Set<String>> nextNet = sortedEntries.get(j);
        if (!subnet.getKey().isPrefix(nextNet.getKey()))
        {
            break;
        }

        Entry<Subnet, Set<String>> nextNextNet = j < sortedEntries.size() - 1 ? sortedEntries.get(j + 1) : null;
        if (nextNextNet == null || !subnet.getKey().isPrefix(nextNextNet.getKey()))
        {
            // biggest, and last superset found
            nextNet.getValue().addAll(subnet.getValue());
            sortedEntries.remove(i);
            i--;
        }
        else if (!nextNet.getKey().isPrefix(nextNextNet.getKey()))
        {
            // biggest superset found, but not last
            nextNet.getValue().addAll(subnet.getValue());
        }
    }
}

此方法减少n ^ 2的效果取决于独立网络的数量。具有相同前缀的集合越小,运行时的平方就应该越小。

最后,我认为这种方法的行为与前缀树方法非常相似。在那里,您将构建树,然后迭代叶子(即最大的超集),然后将其祖先的所有项目合并到它们的集合中。