1

I am trying to do following:

1) get elements from collection that holds condition 2) sort it based on length 3) return only elements with max length

So for example

List<String> list = new ArrayList();
list.add("xone");
list.add("two");
list.add("xthree");
list.add("xseven");

Using stream i can create:

list.stream()
    .filter( e -> e.startsWith("x"))
    .sort( e -> e.length() )
    .collect(..)

however this just sorts it.. is there any pretty way how to return only elements with maximum found length? In this case it would be "xthree" and "xseven"

Thanks for help!

Naman
  • 27,789
  • 26
  • 218
  • 353
jejjejd
  • 721
  • 1
  • 8
  • 18

3 Answers3

1

If you want to use streams, I would split this into two parts for clarity and to reduce memory usage:

final int maxLen = list.stream()
    .max(Comparator.comparingInt(String::length))
    .get()
    .length();

List<String> maxSized = list.stream()
    .filter(item -> item.length() == maxLen)
    .collect(Collectors.toList());

You could collect and group by length, and take the maximum length collection. That would use more memory, but iterate fewer times. It depends on what performance characteristics you want.

maxSized = list.stream()
    .collect(Collectors.groupingBy(String::length))
    .entrySet()
    .stream()
    .max(Comparator.comparingInt(e -> e.getKey()))
    .get()
    .getValue();

Without the stream API you could do this:

int maxLen = 0;
for (String s : list) {
    maxLen = Math.max(maxLen, s.length());
}

List<String> maxSized = new ArrayList<>();
for (String s : list) {
    if (s.length() == maxLen) {
        maxSized.add(s);
    }
}

for (String s: maxSized) {
    System.out.println(s);
}

Prints:

xthree
xseven

ᴇʟᴇvᴀтᴇ
  • 12,285
  • 4
  • 43
  • 66
  • i want to practice streams – jejjejd Mar 30 '20 at 14:07
  • @ELEVATE Your first approach is traversing the list twice you can do this in a single traversal thus reducing time-complexity – Shivansh Narayan Mar 30 '20 at 14:25
  • 3
    @SHIVANSHNARAYAN time complexity is unaffected by iterating the list twice. It's still O(n). – Andy Turner Mar 30 '20 at 14:30
  • 1
    @SHIVANSH, There are trade-offs. It might be faster in a single loop, but if the data is a certain way looping twice could actually be faster than looping once. In any case, execution speed is only one consideration. You should also think about how easy it is to read and maintain the code. In some situations you might also care about how much memory is used during the operation (see my comment on Andy's answer). The single loop methods use more memory than two loops. – ᴇʟᴇvᴀтᴇ Mar 30 '20 at 17:42
1

Using streams:

List<String> longest =
    list.stream().filter( e -> e.startsWith("x"))
        .collect(groupingBy(String::length))
        .entrySet()
        .stream()
        .max(comparingInt(e -> e.getKey()))
        .get()
        .getValue();

But personally I would say it's better to do it without streams: even though the code is longer, I find it easier to follow, and it avoids processing all the strings that are shorter than the longest already found:

List<String> longest = new ArrayList<>();
int max = 0;
for (String s : list) {
  if (!s.startsWith("x")) continue;

  // Ignore the string if it is shorter.
  if (s.length() < max) continue;

  if (s.length() > max) {
    // We found a longer string. Discard the current entries.
    longest.clear();
    max = s.length();
  }

  // Add the string to the list of longest strings.
  longest.add(s);
}
Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • 2
    This algorthm means you only iterate once, but could create performance issues. If the list had a million items roughly pre-sorted by length, with lots of variation in length, this algorithm would create a lot of churn along the way. – ᴇʟᴇvᴀтᴇ Mar 30 '20 at 14:31
  • @ᴇʟᴇvᴀтᴇ you're right, it could. – Andy Turner Mar 30 '20 at 14:40
1

A slightly compact way of writing those groupingBy operations could be as follows -

TreeMap<Integer, List<String>> map = new TreeMap<>(list.stream()
        .collect(Collectors.groupingBy(String::length)));
List<String> maxLengthStrings = map.lastEntry().getValue();
Naman
  • 27,789
  • 26
  • 218
  • 353