1. Java Stream-API

The Stream API enables the step-by-step processing of data. After a source emits data, different steps follow that filter and transform data and reduce it to a result.

Although the term Streams can be ambiguous and may be mistaken for input/output streams, it is an essential feature of Java 8 that leverages other innovations in the Java SE library, including predefined functional interfaces and Optional. By combining Streams with lambda expressions and method references, developers can write concise code and configure processing steps declaratively in a novel way.

The first task in this assignment block makes use of the heroes we already met in the chapter about the class library. All the major terminal and intermediate operations are used for this collection of heroes. Different tasks follow, the solution of which shows the elegance of the Stream API.

Prerequisites

  • be able to build a stream

  • be able to use terminal and intermediate operations

  • be able to handle primitive streams

  • be able to use lambda expressions practically

Data types used in this chapter:

1.1. Regular streams with their terminal and intermediate operations

For each stream, there are two mandatory steps and any number of optional steps in between:

  1. construction of the stream from a data source.

  2. optional processing steps called intermediate operations.

  3. final operation called terminal operation.

1.1.1. Hero Epic: Meet Stream API ⭐

In the chapter "The Java Class Library" the class Heroes with heroes was introduced. This assignment is based on it.

Stream construction:

  • For the following task items, always build a new Stream with the heroes, and then apply the terminal and intermediate operations according to the following pattern:

    Heroes.ALL.stream().intermediate1(…).intermediate2(…).terminal()

Terminal operations:

  1. Output all information about heroes in CSV format on the screen.

  2. Ask if all heroes were introduced after 1900.

  3. Ask if any female hero was introduced after 1950 (inclusive).

  4. Which hero appears first?

  5. Which hero is closest in the year of publication to 1960? Only one terminal operation should be used on the stream.

  6. A StringBuilder is to be created that contains all years' comma separated. The result is to be created with a single terminal Stream method, with no intermediate operation in between. The order of the years in the string is not relevant.

  7. Split the male and female heroes into two groups. The result shall be of type Map<Sex, List<Hero>>.

  8. Form two partitions, with heroes introduced before and after 1970. The result shall be of type Map<Boolean, List<Hero>>.

Intermediate (non-terminal) operations:

  1. How many female heroes are there in total?

  2. Sort all heroes by release date, then output all heroes.

  3. Go through the following steps:

    a) Create a comma-separated string with the names of all female heroes.
    b) In the Hero there is no setter because the Hero is immutable. But with the constructor, we can build new heroes. Convert the heroes to a list of anonymous heroes, where the plain name in parentheses is removed along with the parentheses themselves.
    c) Create an int[] with all years when heroes were introduced — without duplicate entries.

  4. Go through UNIVERSES and not ALL to output the names of all the heroes.

1.1.2. Get the beloved captain from a list ⭐

At the end of the year, the ship’s crew votes on which candidate for captain should pave their way to rich plunder in the future. The winner is the person with the most nominations.

Task:

  • Given is an array of strings with names. Which name was mentioned how often? The names are not case-sensitive.

  • Many people just call Captain CiaoCiao simply CiaoCiao, this should be equivalent to Captain CiaoCiao.

Example:

{"Anne", "Captain CiaoCiao", "Balico", "Charles", "Anne", "CiaoCiao", "CiaoCiao", "Drake", "Anne", "Balico", "CiaoCiao" }{charles=1, anne=3, drake=1, ciaociao=4, balico=2}

1.1.3. Frame pictures ⭐

Captain CiaoCiao has been chosen as the best captain, so the joy is great. He would like to have his picture framed.

Given is a multiline string, such as

     ______
_.-':::::::`.
\::::::::::::`.-._
 \:::''   `::::`-.`.
  \         `:::::`.\
   \          `-::::`:
    \______       `:::`.
    .|_.-'__`._     `:::\
   ,'`|:::|  )/`.     \:::
  /. -.`--'  : /.\     ::|
  `-,-'  _,'/| \|\\    |:|
   ,'`::.    |/>`;'\   |:|
   (_\ \:.:.:`((_));`. ;:|
   \.:\ ::_:_:_`-','  `-:|
    `:\\|     SSt:
       )`__...---'

This is to be placed in a picture frame:

+------------------------------+
|                              |
|       ______                 |
|  _.-':::::::`.               |
|  \::::::::::::`.-._          |
|   \:::''   `::::`-.`.        |
|    \         `:::::`.\       |
|     \          `-::::`:      |
|      \______       `:::`.    |
|      .|_.-'__`._     `:::\   |
|     ,'`|:::|  )/`.     \:::  |
|    /. -.`--'  : /.\     ::|  |
|    `-,-'  _,'/| \|\\    |:|  |
|     ,'`::.    |/>`;'\   |:|  |
|     (_\ \:.:.:`((_));`. ;:|  |
|     \.:\ ::_:_:_`-','  `-:|  |
|      `:\\|     SSt:          |
|         )`__...---'          |
|                              |
+------------------------------+

Task:

  • Write a frame(String) method that frames a multi-line string. Use the String methods lines() and repeat(…​).

    • The horizontal lines consist of -.

    • The vertical lines consist of |.

    • In the corners, there are plus signs +.

    • The spacing to the right and left of the frame is 2 spaces.

    • The inner space at the top and bottom is a blank line.

    • Line breaks are \n, but they should be relatively easy to change in the program.

1.1.4. Look and say ⭐⭐

Captain CiaoCiao is daydreaming and writing on a piece of paper:

1

He sees the 1 and says to himself, "Oh, 1 times the 1!" He writes that down:

1 1

Now he sees two ones and can read it out like this:

2 1

"Arrr! That’s a two and a one!" He writes it down:

1 2 1 1

He reads the numbers again and says:

1 1 1 2 2 1

Now the one even appears three times:

3 1 2 2 1 1

Captain CiaoCiao finds that the numbers get big quickly, though. He is curious to see if after a few passes only 1, 2, and 3 occur as digits.

Look and say

Task:

  • Create an infinite stream of look-and-say numbers using a Stream.iterate(…​).

  • Limit the stream to 20 elements

  • Output the numbers to the console; they can also be output compactly as a string like 111221.

The task can be solved with a clever regular expression with a back-reference. However, this solution variant is sophisticated, and those who want to take this route can find more details at https://regular-expressions.mobi/backref.html.

What is asked here is the look-and-say sequence, which https://oeis.org/A005150 explains in more detail with many references.

1.1.5. Remove duplicate islands of rare earth metals ⭐⭐⭐

The business with rare earth metals is particularly attractive to Bonny Brain. Their crew compiles a list of islands that are home to certain earth metals. The result goes into a text file that looks like this:

Balancar
Erbium
Benecia
Yttrium
Luria
Thulium
Kelva
Neodymium
Mudd
Europium
Tamaal
Erbium
Varala
Gadolinium
Luria
Thulium

One line contains the island, the next line contains the rare earth metals. However, different crew members may enter the same pairs in the text file. In the example, it is the pair Luria and Thulium.

Task:

  • Write a program that deletes all duplicate line pairs from the text.

  • The program must be flexible enough that the input can come from a String, File, InputStream or Path.

  • The lines are always separated only with a \n. Moreover, the last line ends with a \n.

For the solution, the types Pattern, Scanner and MatchResult as well as the Scanner method findAll(..) and further Stream methods are helpful.

1.1.6. Where are the sails? ⭐⭐

Bonny Brain needs a new high-performance sail for the ship. The clerks from materials management prepare a list of coordinates of suitable cloth manufacturers:

Point.Double[] targets = { // Latitude, Longitude
   new Point.Double( 44.7226698,  1.6716612 ),
   new Point.Double( 50.4677807, -1.5833018 ),
   new Point.Double( 44.7226698,  1.6716612 )
};

Task:

  • In the list some coordinates occur twice, these can be ignored.

  • At the end there should be a Map<Point.Double, Integer> with the coordinate and the distance in kilometers to the current location of Bonny Brain (40.2390577, 3.7138939).

An example output might look like this:

{Point2D.Double[50.4677807, -1.5833018]=1209, Point2D.Double[44.7226698, 1.6716612]=525}

The distance in kilometers is calculated using the Haversine formula like this:

private static int distance( double lat1, double lng1,
                             double lat2, double lng2 ) {
  double earthRadius = 6371; // km
  double dLat = Math.toRadians( lat2 - lat1 );
  double dLng = Math.toRadians( lng2 - lng1 );
  double a = Math.sin( dLat / 2 ) * Math.sin( dLat / 2 ) +
      Math.cos( Math.toRadians( lat1 ) ) * Math.cos( Math.toRadians( lat2 ) ) *
          Math.sin( dLng / 2 ) * Math.sin( dLng / 2 );
  double d = 2 * Math.atan2( Math.sqrt( a ), Math.sqrt( 1 - a ) );
  return (int) (earthRadius * d);
}

Captain CiaoCiao needs to increase his fleet, so he asks the crew what armored cars are recommended. He gets an array of model names of the following type:

String[] cars = {
  "Gurkha RPV", "Mercedes-Benz G 63 AMG", "BMW 750", "Toyota Land Cruiser",
  "Mercedes-Benz G 63 AMG", "Volkswagen T5", "BMW 750", "Gurkha RPV", "Dartz Prombron",
  "Marauder", "Gurkha RPV" };

Task:

  • Write a program that processes an array of model names and produces a Map<String, Long> at the end that associates the model names with the number of occurrences. This part of the task can be solved well with the Stream API.

  • There should be no models named only once; only models named twice or more should appear in the data structure. For this part of the task, we can better do without the Stream API and use another variant.

An example output could look like this:

{Mercedes-Benz G 63 AMG=2, BMW 750=2, Gurkha RPV=3}

Modify the query so that all models are in a map, but the names are associated with false if there are fewer than two mentions. An output might look like this:

{Marauder=false, Dartz Prombron=false, Mercedes-Benz G 63 AMG=true, Toyota Land Cruiser=false, Volkswagen T5=false, BMW 750=true, Gurkha RPV=true}

1.2. Primitive streams

In addition to streams for objects, the Java standard library provides three special streams for primitive data types: IntStream, LongStream and DoubleStream. Many methods are similar, important differences are ranges and special reductions, for example to sum or average.

1.2.1. Detect NaN in an array ⭐

Java supports three special values for the floating-point type double: Double.NaN, Double.NEGATIVE_INFINITY, and Double.POSITIVE_INFINITY; corresponding constants exist for float in Float. For mathematical operations, it must be checked whether the result is valid and not a NaN. By the arithmetic operations like addition, subtraction, multiplication, division NaN cannot be achieved, unless an operand is NaN, but various methods from the class Math return NaN as result in case of invalid input. For example, in the methods log(double a) or sqrt(double a), if the argument a is less than zero, the result is NaN.

Task:

  • Write a method containsNan(double[]) that returns true if the array contains a NaN, otherwise false.

  • A single expression should suffice in the body of the method.

Example:

double[] numbers1 = { Math.sqrt( 2 ), Math.sqrt( 4 ) };
System.out.println( containsNan( numbers1 ) );           // false

double[] numbers2 = { Math.sqrt( 2 ), Math.sqrt( -4 ) };
System.out.println( containsNan( numbers2 ) );           // true

1.2.2. Generate decades ⭐

A decade always represents a period of ten years, regardless of its start and end dates. Decades are typically grouped by their common tens' digit. The decade from 1990 to 1999 is known as the 0-to-9 decade, starting on January 1, 1990, and ending on December 31, 1999. Another interpretation is the 1-to-0 decade, where the counting of decades begins with a 1 in the one place. In this case, the 1990s would start on January 1, 1991, and end on December 31, 2000.

Task:

  • Write a method int[] decades(int start, int end) that returns all decades from a start year to an end year as an array.

  • The 0-to-9 decade is to be used.

Examples:

  • Arrays.toString( decades( 1890, 1920 ) )[1890, 1900, 1910, 1920]

  • Arrays.toString( decades( 0, 10 ) )[0, 10]

  • Arrays.toString( decades( 10, 10 ) )[10]

  • Arrays.toString( decades( 10, -10 ) )[]

1.2.3. Generate array with constant content via stream ⭐

Task:

  • Write a method fillNewArray(int size, int value).

Example:

  • Arrays.toString( fillNewArray( 3, -1 ) )[-1, -1, -1]

1.2.4. Draw pyramids ⭐

Task:

  • Create pyramids of the following shape from a clever combination of range(…​), mapToObj(…​) and forEach(…​):

        /\
       /\/\
      /\/\/\
     /\/\/\/\
    /\/\/\/\/\
  • Try to solve the task in just one statement, from building the pyramid to console output.

  • The height should be configurable. The pyramid in the example is five lines high.

1.2.5. Teddies labeled with letters ⭐

In the aftermath of their latest raid, the pirates found themselves with a ton of loot that needed to be smuggled out of the country in inconspicuous crates. Their solution? Slap cute teddy bear designs on the boxes to distract anyone who might be snooping around. But to avoid any mix-ups, they decided to add a subtle letter identifier to each bear. Now, they needed to develop a program that could generate a series of printable teddy bear designs with the identifiers included.

Task:

  • Given the following template for the teddy with a wildcard character #.

    String teddy = """
           _     _   \s
          (c).-.(c)  \s
           / o_o \\   \s
         __\\( Y )/__ \s
        (_.-/'-'\\-._)\s
           || # ||   \s
         _.' `-' '._ \s
        (.-./`-'\\.-.)\s
         `-'     `-' \s""";
  • Write a program, which converts a string into a sequence of consecutive teddies.

Example:

  • For the input "MME" the following output should appear on the screen:

       _     _       _     _       _     _
      (c).-.(c)     (c).-.(c)     (c).-.(c)
       / o_o \       / o_o \       / o_o \
     __\( Y )/__   __\( Y )/__   __\( Y )/__
    (_.-/'-'\-._) (_.-/'-'\-._) (_.-/'-'\-._)
       || M ||       || M ||       || E ||
     _.' `-' '._   _.' `-' '._   _.' `-' '._
    (.-./`-'\.-.) (.-./`-'\.-.) (.-./`-'\.-.)
     `-'     `-'   `-'     `-'   `-'     `-'

1.2.6. Get the letter frequency of a string ⭐

One requirement for compression is to represent frequently occurring strings as short as possible. If, for example, 0 0 0 1 1 occurs in a file, then the following is stored later: four times a 0, then three times a 1. A compression algorithm tries to express the information about the numbers 0 and 1 in very few bits. It is helpful if it is known how often a symbol or a sequence occurs altogether, to be able to estimate whether compression of this sequence is worthwhile at all. A loop could be run over the input beforehand and count frequencies.

Task:

  • The input is a string. Using clever stream concatenation, generate a new string containing each letter of the source string followed by the frequency of that letter in the given string.

  • The pairs of letters and frequencies should be separated by a slash in the result string.

  • Performance does not play a central role.

Examples:

  • "eclectic""e2/c3/l1/e2/c3/t1/i1/c3"

  • "cccc"c4/c4/c4

  • """"

1.2.7. From 1 to 0, from 10 to 9 ⭐⭐

Bonny Brain wants to buy a new boat and sends Elaine to the marina to evaluate boats. Elaine writes her ratings from 1 to 10 in a row on a piece of paper, something like this:

102341024

The Bonny Brain gets the sequence of numbers, but is not happy with the order and the numbers. First, the numbers should be separated by a comma, and second, they should start at 0, not 1.

Task:

  • Write a method String decrementNumbers(Reader) that reads a string of digits from an input source and converts it to a comma-separated string; all numbers should be decremented by 1. Anything that is not a digit should not be included in the result.

Examples:

  • 102341024"9, 1, 2, 3, 9, 1, 3"

  • -1"0"

  • abc123xyz456"0, 1, 2, 3, 4, 5"

1.2.8. The annual octopus contest ⭐⭐

The pirates around Bonny Brain and Captain CiaoCiao organized a contest to choose the most beautiful and intelligent octopus. Each octopus is assigned a number and is judged by the pirates. In the end, the octopus with the most mentions wins. Arrr, talk about a beauty pageant for tentacles!

Task:

  • Given an int array with the identifiers of the octopuses. Write a program that determines the number from the array that occurs most often.

Example

  • In the array

    int[] values = { 1, 1, 2, 3, 4, 2, 3, 2, 2, 1, 7, 3, 2, 2, 1 };

    2 is the winning octopus, since it was named most frequently.

1.2.9. Merge three int arrays ⭐

Shortly before the evaluation of the octopus competition, it turned out that the voting results for the smartest octopuses were not written down on one sheet of paper, but on three. Chaos erupts among the pirates and octopuses. The three lists must be merged into one list.

Task:

  • A method is sought that merges three int arrays.

  • There should be two overloaded methods:

    • static int[] join(int[] numbers1, int[] numbers2, int[] numbers3) and

    • static int[] join(int[] numbers1, int[] numbers2, int[] numbers3, long maxSize)

      The optional fourth parameter can be used to reduce the maximum number of elements in the result.

Examples:

int[] numbers1 = { 1, 2, 3 };
int[] numbers2 = { 1, 1, 2 };
int[] numbers3 = { 4, 3, 1, 2 };
int[] result1 = join( numbers1, numbers2, numbers3 );
int[] result2 = join( numbers1, numbers2, numbers3, 5 );
System.out.println( Arrays.toString( result1 ) ); // [1, 2, 3, 1, 1, 2, 4, 3, 1, 2]
System.out.println( Arrays.toString( result2 ) ); // [1, 2, 3, 1, 1]

1.2.10. Determine winning combinations ⭐⭐

Bonny Brain plans the next party and prepares a ring toss game. The first thing she does is set up different objects, such as these two:

▨ ▧

Then she puts two rings in the players' hands and lets them throw. If the ring goes over an object, that counts as a win. How many ways are there to win, and what are the possibilities? Taking the two objects ▨ ▧, it could be that a player "hits" ▨ or ▧, or both — ▨ and ▧ — no-hit is no win.

Task:

  • Given a string of arbitrary characters from the Basic Multilingual Plane (BMP), i.e., U+0000 to U+D7FF and U+E000 to U+FFFF.

  • Create a list of all the ways a player can win.

Example:

  • ▨▧[▧, ▨, ▨▧], but not [▧, ▨, ▨▧, ▧▨].

  • ▣▢▲[▣▢▲, ▲, ▢, ▣, ▢▲, ▣▲, ▣▢]

  • MOON[OO, MN, MO, MOON, MOO, MON, M, N, OON, ON, O]

1.3. Statistics

The IntStream, LongStream and DoubleStream streams have terminating methods such as average(), count(), max(), min() and sum(). However, if not only one of these statistical information is interesting, but several, various information can be collected in an IntSummaryStatistics, LongSummaryStatistics or DoubleSummaryStatistics.

1.3.1. The fastest and slowest paddlers ⭐

Bonny Brain hosts the annual "Venomous Paddle Open" paddling competition on party island X Æ A-12. In the end, the best, worst, and average times should be given out. The results of the paddlers are represented by the following data type:

record Result( String name, double time ) { }

Task:

  • Create a Stream of Result objects. Pre-assign some Result objects with selected values for testing.

  • Output a small statistic of times in the end.

Example:

From the following stream …​

Stream<Result> stream = Stream.of(
      new Result( "Bareil Antos", 124.123 ), new Result( "Kimara Cretak", 434.22 ),
      new Result( "Keyla Detmer", 321.34 ), new Result( "Amanda Grayson", 143.99 ),
      new Result( "Mora Pol", 122.22 ), new Result( "Gen Rhys", 377.23 ) );

... the output may look like this:

count:   6
min:     122,22
max:     434,22
average: 253,85

1.3.2. Calculate median ⭐⭐

The *SummaryStatistics types return the arithmetic mean with getAverage(). The arithmetic mean is calculated by dividing the sum of the given values by the number of values. There are many other mean values, such as the geometric mean or the harmonic mean.

Means are often used in statistics, but they have the problem of being more prone to outliers. Statistics frequently work with the median. The median is the central value, that is, the value that is "in the middle" of the sorted list. Numbers that are too small or too large are at the edge and are outliers and are not included in the median.

If the number of values is odd, then there is a natural middle.

  • Example 1: In the list 9, 11, 11, 11, 12, the median is 11. If the number of values is even, the median can be defined from the arithmetic mean of the two middle numbers.

  • Example 2: In the list 10, 10, 12, 12, the median is the arithmetic mean of the values 10 and 12, i.e., 11.

Task:

  • Given is a double[] with measured values. Write a method double median(double... values) that calculates the median of the array values with even and odd numbers.

  • Use a DoubleStream for the solution and consider whether limit(…​) and skip(…​) help.

1.3.3. Calculate temperature statistics and draw charts ⭐⭐⭐

Bonny Brain is skilled in numerical analysis, but her preference is for charts. Graphical representation of data is much simpler for her to comprehend compared to text-based formats.

She is given a chart with temperature data and would like to see at a glance when it is warmest and a vacation with the family is well possible.

Task:

  • We are looking for a program that can process and display temperatures. More precisely:

    • Generate a list of random numbers that in the best-case follow the temperature curve of the year, say in the form of a sine curve from 0 to π.

    • Generate random temperature values for several years, and store the years with the values in associative memory. Use the Year data type as a key for a Map sorted by years. Bonus: The number of days corresponds to the number of days in the year, so 365 or 366.

    • Write an ASCII table with the temperatures of all years to the console.

    • Output the highest and lowest annual temperature of a year.

    • Output the highest, lowest, and average temperature for a month of a year.

    • Generate a file that aggregates and visualizes the twelve average temperatures of a month from one year. Take the following HTML document as a basis and fill the data array accordingly.

<!DOCTYPE html><html>
<head><meta charset="UTF-8"></head>
<body>
<canvas></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.3.2/dist/chart.min.js"></script>
<script>
  const cfg = {
    type: "bar",
    data: {
     labels:"Jan. Feb. Mar. Apr. May June July Aug. Sept. Oct. Nov. Dec.".split(" "),
     datasets: [{
      label: "Average temperature",
      data: [11, 17, 21, 25, 27, 29, 29, 27, 25.6, 21.6, 17.5, 12.5],
     }]
    }
  };
  window.onload = () => new Chart(document.querySelector("canvas").getContext("2d"), cfg);
</script>
</body></html>