简体   繁体   English

Java 8 Streams按长度过滤字符串

[英]Java 8 Streams filter Strings by length

Can I use stream to check, which 2 consecutive strings have the biggest sum of their lengths? 我可以使用流来检查,哪两个连续的字符串具有最大的长度总和?

For example I have 5 valid usernames and I should print only Johnny and Frank : 例如,我有5个有效的用户名,我应该只打印JohnnyFrank

String line = "James Jack Johnny Frank Bob";
String regexForValidUserName = "[a-zA-Z][a-zA-Z0-9_]{2,24}";
Pattern patternForUserName = Pattern.compile(regexForValidUserName);
Matcher matcherForUserName = patternForUserName.matcher(line);
List<String> listOfUsers = new LinkedList<>();

if (matcherForUserName.find()) {
listOfUsers.add(matcherForUserName.group());
}

listOfUsers.stream().map((a,b,c,d) -> a.length + b.length > c.length + d.length).foreach(System.out::println);

Here's a stream based solution. 这是一个基于流的解决方案。 I'm not sure I would use it over a for loop, but it does what you want with streams, and is relatively easy to understand. 我不确定我是否会在for循环中使用它,但它可以用流做你想要的,并且相对容易理解。

As I said in the comment, the trick is to have a stream of pairs of names, instead of a stream of names. 正如我在评论中所说,诀窍是拥有一对名称流,而不是名称流。

    List<String> userNames = Arrays.asList("James", "Jack", "Johnny", "Frank", "Bob");
    List<String> longestPair =
        IntStream.range(0, userNames.size() - 1)
                 .mapToObj(i -> Arrays.asList(userNames.get(i), userNames.get(i + 1)))
                 .max(Comparator.comparing(pair -> pair.get(0).length() + pair.get(1).length()))
                 .orElseThrow(() -> new IllegalStateException("the list should have at least 2 elements"));

    System.out.println("longestPair = " + longestPair);

Please don't do that with a LinkedList, because random access to a linked list is very inefficient. 请不要使用LinkedList,因为对链表的随机访问效率非常低。 But you should almost never use a linked list anyway. 但是你几乎不应该使用链表。 Prefer ArrayList. 首选ArrayList。 It's more efficient for basically all realistic use-cases. 对于基本上所有真实的用例,它更有效。

You could also create a Pair class to make that more readable, instead of using a list of two elements. 您还可以创建一个Pair类,使其更具可读性,而不是使用两个元素的列表。

To do that work you must break the original List<String> into List<Pair> chunks, then the work is very easy. 要完成这项工作,您必须将原始List<String>分解为List<Pair>块,然后工作非常简单。 and chunk2 is lazily just like as stream intermediate operations . chunk2一样懒惰 ,就像流中间操作一样 for example: 例如:

Comparator<List<String>> length = comparing(pair -> { 
       return pair.get(0).length() + pair.get(1).length();
});

List<String> longest = chunk2(asList(line.split(" "))).max(length).get();
//           ^--- ["Johnny", "Frank"]

import java.util.Spliterators.AbstractSpliterator;
import static java.util.stream.StreamSupport.stream;
import static java.util.Spliterator.*;

<T> Stream<List<T>> chunk2(List<T> list) {
    int characteristics = ORDERED & SIZED & IMMUTABLE ;
    int size = list.size() - 1;
    return stream(new AbstractSpliterator<List<T>>(size, characteristics) {
        private int pos;

        @Override
        public boolean tryAdvance(Consumer<? super List<T>> action) {
            if (pos >= size) return false;

            action.accept(list.subList(pos, ++pos + 1));
            return true;
        }

    }, false);
}

Supporting such kind of operation for arbitrary streams (ie not having a source with random access that allows streaming over indices), requires a custom collector: 支持任意流的这种操作(即,没有允许通过索引进行流式传输的随机访问源),需要一个自定义收集器:

String line = "James Jack Johnny Frank Bob";
String regexForValidUserName = "[a-zA-Z][a-zA-Z0-9_]{2,24}";
Pattern patternForUserName = Pattern.compile(regexForValidUserName);
Matcher matcherForUserName = patternForUserName.matcher(line);
Stream.Builder<String> builder = Stream.builder();
while(matcherForUserName.find()) builder.add(matcherForUserName.group());
class State {
    String first, last, pair1, pair2;
    int currLength=-1;
    void add(String next) {
        if(first==null) first=next;
        else {
            int nextLength=last.length()+next.length();
                if(nextLength>currLength) {
                pair1=last;
                pair2=next;
                currLength=nextLength;
            }
        }
        last=next;
    }
    void merge(State next) {
        add(next.first);
        if(currLength<next.currLength) {
            pair1=next.pair1;
            pair2=next.pair2;
            currLength=next.currLength;
        }
        last=next.last;
    }
    String[] pair() {
        return currLength>=0? new String[]{ pair1, pair2 }: null;
    }
}
String[] str = builder.build()
       .collect(State::new, State::add, State::merge).pair();
System.out.println(Arrays.toString(str));

A Collector can have a mutable data structure which allows holding state like the previous element. 收集器可以具有可变数据结构,该结构允许保持状态,如前一个元素。 To support merging of two such state objects, it also needs to track the first element, as the last element of one State object may form a pair with the first element of the next State object, if there is one. 为了支持合并两个这样的状态对象,它还需要跟踪第一个元素,因为一个State对象的最后一个元素可以与下一个State对象的第一个元素形成一对(如果有的话)。

So a loop would be simpler to program while the collector supports parallel processing, which only pays off if you have a really large number of elements. 因此,当收集器支持并行处理时,循环将更容易编程,只有当您拥有非常多的元素时才会得到回报。

The stream creation itself would be more straight-forward if we had Java 9's factory method already: 如果我们已经拥有Java 9的工厂方法,那么流创建本身会更直接:

String line = "James Jack Johnny Frank Bob";
String regexForValidUserName = "[a-zA-Z][a-zA-Z0-9_]{2,24}";
Pattern patternForUserName = Pattern.compile(regexForValidUserName);
String[] str = patternForUserName.matcher(line).results()
    .map(MatchResult::group)
    .collect(State::new, State::add, State::merge).pair();
System.out.println(Arrays.toString(str));

(The State class wouldn't change) State级不会改变)

You could iterate over the stream with .forEach , using a custom mutable data structure to track the longest pairs, for example: 您可以使用.forEach迭代流,使用自定义可变数据结构来跟踪最长的对,例如:

class Tracker {
  List<String> pairs = Collections.emptyList();
  String prev = "";
  int longest = 0;

  public void check(String name) {
    int length = prev.length() + name.length();
    if (length > longest) {
      longest = length;
      pairs = Arrays.asList(prev, name);
    }
    prev = name;
  }

  public List<String> pairs() {
    return pairs;
  }
}

String line = "James Jack Johnny Frank Bob";

Tracker tracker = new Tracker();
Stream.of(line.split(" ")).forEach(tracker::check);

System.out.println(tracker.pairs());

This will print [Johnny, Frank] . 这将打印[Johnny, Frank]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM