Streams answers

Start with some imports

import static java.lang.Math.min;
import static java.util.Arrays.stream;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.*;

import java.util.Map;
import java.util.stream.*;

Static imports are a feature added in 2004 to make Java slightly less verbose. They allow us to import individual names from classes and interfaces so we don’t have to qualify them: we can use min instead of Math.min.

countPrimes

Count the number of primes in an int[].

public int countPrimes(int[] ns) {
  return (int) stream(ns).filter(this::isPrime).count();
}

Cast to int because that’s the signature in the problem but count() returns a long.

In this case we know there can’t be more than Integer.MAX_VALUE elements in the stream since it came from an int[].

countVowels

Count the number of vowels in a String.

public int countVowels(String s) {
  return (int) s
    .codePoints()
    .mapToObj(Character::toString)
    .filter(this::isVowel)
    .count();
}

indexOfVowel (#1)

Find the index of the first vowel in a String.

public int indexOfVowel(String s) {
  return (int) IntStream.range(0, s.length())
    .filter(i -> isVowel(s.substring(i, i + 1)))
    .findFirst()
    .orElse(-1);
}

This is okay but using an IntStream to produce indexes is a bit ugly.

This is basically a for loop in disguise.

indexOfVowel (#2)

public int indexOfVowel(String s) {
  return s
    .codePoints()
    .mapToObj(Character::toString)
    .filter(this::isVowel)
    .mapToInt(v -> s.indexOf(v))
    .findFirst()
    .orElse(-1);
}

This solution leans heavily into the laziness of streams. It will essentially iterate over the String until it finds the first vowel and then iterate one more time to find out where that vowel is using indexOf.

indexOfVowel (#3)

public int indexOfVowel(String s) {
  int consonants = (int) s
    .codePoints()
    .mapToObj(Character::toString)
    .takeWhile(not(this::isVowel))
    .count();
  return (consonants + 1) % (s.length() + 1) - 1;
}

Remember we imported java.util.function.Predicate.not

not is a higher-order function that takes a function and returns a function.

Remainder trick

(c + 1) % (s.length() + 1) - 1

Assume s.length() ⟹ 3

c c + 1 (c + 1) % (3 + 1) (c + 1) % (3 + 1) - 1
0 1 1 0
1 2 2 1
2 3 3 2
3 4 0 -1

disemvowel

public String disemvowel(String s) {
  return s
    .codePoints()
    .mapToObj(Character::toString)
    .filter(not(this::isVowel))
    .collect(joining());
}

Notice how after statically importing joining from Collectors this is quite readable.

We could make a helper method

While it’s common to chain together stream methods you can always write methods that take or return streams, just like any other type.

public Stream<String> letters(String s) {
  return s.codePoints().mapToObj(Character::toString);
}

As with any code, if we’re doing the same thing more than once, it’s worth thinking about putting it into a method with a meaningful name.

Using the helper method

public int countVowels(String s) {
  return (int) letters(s).filter(this::isVowel).count();
}

public String disemvowel(String s) {
  return letters(s)
    .filter(not(this::isVowel))
    .collect(joining());
}

countStartsWithVowel

public int countStartsWithVowel(String[] ss) {
  return (int) stream(ss)
    .filter(not(String::isEmpty))
    .map(s -> s.substring(0, 1))
    .filter(this::isVowel)
    .count();
}

totalLength (#1)

public int totalLength(String[] ss) {
  return stream(ss).collect(summingInt(String::length));
}

summingInt is a method in Collectors that takes a function that returns an int and “collects” stream elements by adding up all the ints obtained by calling that function on the elements of the stream.

totalLength (#2)

public int totalLength(String[] ss) {
  return stream(ss).mapToInt(String::length).sum();
}

mapToInt turns the Stream<String> into an IntStream which has a sum terminal method.

xs (#1)

public int[] xs(String[] ss) {
  return stream(ss).mapToInt(s -> s.indexOf("x")).toArray();
}

xs (#2)

Write a streamy indexOf:

private int indexOf(String s, int cp) {
  var before = (int) s
    .codePoints()
    .takeWhile(n -> n != cp)
    .count();
  return (before + 1) % (s.length() + 1) - 1;
}

Then use it:

public int[] xs(String[] ss) {
  return stream(ss).mapToInt(s -> indexOf(s, 'x')).toArray();
}

There’s no reason to do this, however.

countByLength

public int countByLength(String[] ss, int len) {
  return (int) stream(ss)
    .mapToInt(String::length)
    .filter(n -> n == len)
    .count();
}

collectFourLetterWords

public String[] collectFourLetterWords(String[] ss) {
  return stream(ss)
    .filter(s -> s.length() == 4)
    .toArray(String[]::new);
}

collectShouting

public String[] collectShouting(String[] ss) {
  return stream(ss)
    .map(String::toUpperCase)
    .toArray(String[]::new);
}

Hailstone helper

 private LongStream hs(int start) {
   return LongStream.concat(
     LongStream.iterate(
       start,
       n -> n != 1,
       n -> n % 2 == 0 ? n / 2 : 3 * n + 1
     ),
     LongStream.of(1)
   );
}

Would be hailstone but that was taken.

If you can prove this is not an infinite stream you can collect your math PhD on the way out.

hailstoneLength

Find the length of the hailstone sequence starting at a given number.

public int hailstoneLength(int start) {
  return (int) hs(start).count();
}

Trival matter of counting the items in the stream.

hailstoneMax

Find the maximum value occuring in the hailstone sequence starting at a given number.

public int hailstoneMax(int start) {
  return (int) hs(start).max().getAsLong();
}

Also trivial. getAsLong necessary to account for the possibility that the stream was empty.

And again, cast to int because the way the problem was defined.

hailstone

Make an array of the values in the hailstone sequence starting at a given number.

public int[] hailstone(int start) {
  return hs(start).mapToInt(n -> (int) n).toArray();
}

Once again, fairly trivial plus a bit of casting. (Which can go wrong in the sense that the numbers in the sequence can go out of the bounds of an int.

lengthHistogram

public int[] lengthHistogram(String[] ss) {
  var h = new int[11];
  stream(ss)
    .mapToInt(String::length)
    .map(n -> min(n, 10))
    .forEach(n -> h[n]++);
  return h;
}

This is okay but not as cool as it could be because it’s not safe with parallel streams.

The forEach is a code smell, bringing us back into non-functional style.

What’s the right way to do this?

lengthHistogram (#1)

public int[] lengthHistogram(String[] ss) {
  return stream(ss)
    .mapToInt(String::length)
    .map(n -> min(n, 10))
    .collect(
      () -> new int[11], // make the thing we'll collect into
      (h, n) -> h[n]++,  // update it with a new element
      (a, b) -> {        // combine two things if needed
        for (int i = 0; i < a.length; i++) {
          a[i] += b[i];
        }
      }
    );
}

The three lambdas passed into collect are used to create an object that actually handles collecting the results.

lengthHistogram (#2)

public int[] lengthHistogram(String[] ss) {
  Map<Integer, Long> m = stream(ss)
    .map(String::length)
    .collect(groupingBy(n -> min(10, n), counting()));

  int[] h = new int[11];
  m.keySet().stream().forEach(i -> {
    h[i] = m.get(i).intValue();
  });
  return h;
}

groupingBy is the move here but it’s annoying that we then need to copy the values into an array.

lengthHistogram (#3)

public int[] lengthHistogram(String[] ss) {
  Map<Integer, Long> m = stream(ss)
    .map(String::length)
    .collect(groupingBy(n -> min(10, n), counting()));

  int[] h = new int[11];
  Arrays.setAll(h, i -> m.getOrDefault(i, 0L).intValue());
  return h;
}

Same as before but using a different technique to copy into the final array.

lengthHistogram (#4)

public int[] lengthHistogram(String[] ss) {
  Map<Integer, Long> m = stream(ss)
    .map(String::length)
    .collect(groupingBy(n -> min(10, n), counting()));

  return IntStream.range(0, 11)
    .map(i -> m.getOrDefault(i, 0L).intValue())
    .toArray();
}

Same as before but generating final array from an IntStream.