如何过滤和MapJava8流中的值而不进行冗余计算?

xmd2e60i  于 2021-07-03  发布在  Java
关注(0)|答案(4)|浏览(211)

我对Java8集合流功能有一些肤浅的了解,所以我不确定是否有以下可能:我想 filter 基于整数比较的集合,并将该值重新用于Map。
具体来说,我有一个 Collection<String> strings 并想把它的每一个值Map到levenshtein距离的一个固定值 String x 如果levenshtein距离小于 levenshteinLimit .

String x = "some string";
Collection<String> strings = new LinkedList<>(Arrays.asList("not some string",
        "some other string"));
int levenshteinLimit = 10;
Map<Integer, String> stringsLevenshteinMap = strings.stream()
        .filter(string -> LevenshteinDistance.getDefaultInstance().apply(x, string) < levenshteinLimit)
        .collect(Collectors.toMap(string -> LevenshteinDistance.getDefaultInstance().apply(x, string), Function.identity()));
System.out.println(stringsLevenshteinMap);

工作得很好,表达了我所寻找的结果,但需要对距离进行冗余计算。到目前为止这还不是问题。一个没有流的解决方案也是可能的。我想学点新东西。
我假设先过滤然后Map更有效,因为对象的数量可能更小,这意味着更少的工作。
上面的代码使用ApacheCommons文本1.1。示例项目可以在https://github.com/krichter722/java-filter-and-map-without-redundancy.

42fyovps

42fyovps1#

具有tuple中间对象的对象应该可以工作:

Map<Integer, String> stringsLevenshteinMap = strings.stream()
    .map(s -> new Tuple<>(LD.getInstance().apply(x, s), s)
    .filter(t -> t.getFirst() < maxDistance)
    .collect(Collectors.toMap(Tuple::getFirst, Tuple::getSecond));
k4aesqcs

k4aesqcs2#

如果您想避免一个临时对象持有键和值,您需要一个自定义收集器,它可以追溯内置收集器的功能,但是可以直接合并过滤。顺便说一下,我不这么认为 Collectors.toMap 在这里是合适的,因为不能保证每个距离只有一根弦。因此,我用 Collectors.groupingBy 作为模板:

public static <T> Collector<T,?,Map<Integer,List<T>>>
                  grouping(ToIntFunction<T> f, int limit) {
    return Collector.of(HashMap::new,
        (m,t) -> {
            int v = f.applyAsInt(t);
            if(v < limit) m.computeIfAbsent(v, x -> new ArrayList<>()).add(t);
        },
        (m1,m2) -> {
            m2.forEach((k,v) -> m1.merge(k, v, (l1,l2)->{ l1.addAll(l2); return l1; }));
            return m1;
        });
}

这基本上做了什么 Collectors.groupingBy 是,但将其用法限制为对 int 仅处理Map到低于指定限制的数字的元素。也可以将其概括为使用 Function 和一个 Predicate 相反。
这个可以像这样使用

Map<Integer, List<String>> stringsLevenshteinMap
    = Stream.of("not some string", "some other string")
            .collect(grouping(
                string -> LevenshteinDistance.getDefaultInstance().apply(x, string),
                levenshteinLimit));

但必须强调的是,不能保证这比创建一个包含这两个值的临时对象更好;这取决于许多环境因素。一般来说,创建临时对象并不昂贵。使用holder对象的方法要灵活得多,尤其是在以后要更改流操作时。

1cklez4t

1cklez4t3#

首先,您可以通过避免创建无用的linkedlist来加快代码的速度。
现在,关于您的问题,如果您想继续使用流来实现这一点,那么解决方案是将每个字符串Map到包含字符串及其距离的对象,然后过滤这些对象,然后收集到一个Map:

String x = "some string";
int levenshteinLimit = 10;

List<String> strings = Arrays.asList("not some string", "some other string"));
Map<Integer, String> stringsLevenshteinMap = 
    strings.stream()
           .map(string -> new StringWithDistance(string, LevenshteinDistance.getDefaultInstance().apply(x, string))
           .filter(o -> o.getDistance() < levenshteinLimit)
           .collect(Collectors.toMap(StringWithDistance::getDistance, StringWithDistance.getString));

System.out.println(stringsLevenshteinMap);
ssm49v7z

ssm49v7z4#

在我脑子里,我觉得这应该管用:

Map<Integer, String> stringsLevenshteinMap = strings.stream()
    .map(string -> LevenshteinDistance.getDefaultInstance().apply(x, string))
    .filter(val -> val < levenshteinLimit)
    .collect(Collectors.toMap(val -> val, Function.identity()));

但正如我所说:这是我的想法,所以我不能保证功能性或可编译性。但这个想法应该是显而易见的。我们不需要计算两次,而是Map到计算出的值,然后继续进行过滤和收集。

相关问题