2017-09-04 40 views
2

我肤浅洞察到Java 8集流媒体功能,所以我不知道,如果以下甚至有可能:我想filter基于整数比较的收集和再利用的映射该值。如何在没有冗余计算的情况下过滤和映射Java 8流中的值?

具体我有一个Collection<String> strings并希望其每个值的Levenshtein距离如果Levenshtein距离小于值levenshteinLimit映射到固定String x

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); 

效果很好,并表达我正在寻找的结果,但需要冗余计算距离。到目前为止这不是问题。没有流的解决方案也是可行的。我试图学习新的东西。

我假定它是更有效的过滤,然后再映射,因为对象的数量是可能更小,这意味着较少的工作。上述

该代码使用Apache公地文本1.1。示例项目可在https://github.com/krichter722/java-filter-and-map-without-redundancy找到。

回答

1

如果你想避免临时对象持有的键和值,你需要一个定制的收藏家,追溯一下内置收藏家,但纳入过滤器直接。顺便说一句,我不认为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数量只有处理单元映射到低于指定限度的数字。也可以概括为使用FunctionPredicate代替。

这可以用于像

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

但必须强调的是,我们无法保证,这比执行刚刚创建临时对象容纳两个值更好;这取决于很多环境因素。一般来说,创建临时对象并不昂贵。使用持有者对象的方法要灵活得多,尤其是当您想要稍后改变流操作时。

-1

走出我的头,我认为这应该工作:

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

但正如我说:这是我的头,所以我不能保证功能或compilability。但这个想法应该很明显。我们不是计算两次,而是计算出计算值,并从那里继续进行过滤和收集。

+0

'val - > val = Function.identity()'以及 – Andrew

+2

我打算回答这样的问题,但是在第一个映射中丢失了字符串。 – daniu

+0

@daniu就我所了解的代码而言,该字符串除了'apply'的重复调用之外没有别的用途,所以这里没有真正的损失。 – Lothar

0

首先,你可以使代码更快通过避免创建无用的LinkedList。

现在,关于你的问题,如果你想使用流来做到这一点,保持,那么解决办法是每个串映射到包含字符串,其距离的物体,然后过滤这些对象,然后收集到地图:

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); 
5

东西有一个元组的中间目标应该工作:

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)); 
+0

那么,没有办法使用昂贵的'新'?它会帮助使用'Map.Entry',因为将它们添加到'Map'会更便宜吗? –

+2

为什么将它们添加到地图会更便宜?是的,有一种方法:使用for循环而不是使用流。但是你正在预先优化,而且它是所有邪恶的根源。在Java中创建短期对象很便宜,几乎没有机会导致性能问题。 –

相关问题