1. Data Structures and Algorithms
Data structures store essential information in the application. They are organized via lists, sets, queues, and associative maps. In Java, the interfaces and classes around data structures are called Collection-API. Since there are so many types to choose from, the purpose of this chapter is to bring order to the confusion and to illustrate the use of the corresponding collections through the exercises.
Prerequisites
be able to distinguish data structures lists, sets, associative memory.
know data types
List
,ArrayList
andLinkedList
.know data types
Set
,HashSet
andTreeSet
.know
Map
,HashMap
andTreeMap
data types.know the difference between queue and deque.
know how to create an order with
Comparator
.know how to use and implement iterators.
be able to use data structures in a thread-safe way.
optional: Interest in the open-source library Guava for further data structures.
Data types used in this chapter:
1.1. The types of the Collection API
The list of user types is long this time. However, the design follows a basic principle, so it is not so complicated after all:
Interfaces describe the functionality of a data structure, "what is provided".
Classes use different strategies to implement the specification from the interfaces; they represent the "how it is implemented".
As developers, we need to know interfaces and implementations, and to review, let’s look again at the central types we’ll encounter more often in this chapter:
To note:
Iterable
is the most general interface, representing what can be traversed;Iterable
providesIterator
instances. Not only data structures areIterable
.Collection
is the top interface that really represents data structures. It specifies methods for adding elements to the collection or for deleting them.Under
Collection
are the actual abstractions, whether it is a list, set, or queue. Below are the implementations.Some operations are not with the data types themselves, but are outsourced to a class
Collections
. Similar applies to arrays, where there is also a utility classArrays
.
We want to build a decision tree for the classes and interfaces java.util.Set
, java.util.List
, java.util.Map
, java.util.HashSet
, java.util.TreeSet
, java.util.Hashtable
, java.util.HashMap
and java.util.TreeMap
. The following considerations must be made in the selection process:
Access via key
duplicates allowed
fast access
sorted iteration
thread safe
If access is from a key to a value, this is generally an associative map, that is, an implementation of the Map
interface. Implementations of Map
are HashMap
, TreeMap
, and the outdated class Hashtable
. However, lists are also special associative stores, where the index is an integer starting at 0 and ascending. Lists work quite well whenever the key is a small integer and there are few spaces. The association of arbitrary integers to objects does not work well with a list.
Duplicates are allowed in lists, but not in sets and associative stores. There are indeed requirements that a set should note how often an element occurs, but this must be implemented itself with an associative memory that associates the element with a counter.
All data structures allow fast access. The only question is what to ask for. A list cannot quickly answer whether an element is present or not because the list must be traversed from front to back to do so. With an associative store or set, this query is much faster because of the internal organization of the data. This test of existence can be answered even somewhat faster for data structures that use hashing internally than for data structures that keep elements sorted.
Lists can be sorted, and a traversal returns the elements in the sorted order. A TreeSet
and a TreeMap
are also sorted by criteria. The data structures with the hashing method have no user-defined order of sorting.
Data structures can be divided into three groups: Data structures since Java 1.0, data structures since Java 1.2, and data structures since Java 5. In the first Java versions the data structures Vector
, Hashtable
, Dictionary
and Stack
were introduced. These data structures are all thread-safe, but they are no longer used today. In Java 1.2 the Collection API was introduced, all data structures are not thread safe. In Java 5 the new package java.util.concurrent
has been introduced, all data structures in it are safe against concurrent changes.
1.2. Lists
For the exercises, let’s start with the simplest data structure, lists. Lists are sequences of information where the order is maintained when appending new elements, and elements can occur multiple times. Even null
is allowed as an element.
1.2.1. Singing and cooking: Traverse lists and check properties ⭐
Captain CiaoCiao is putting together a new crew. Everyone in the crew has a name and a profession:
record CrewMember( String name, Profession profession ) {
enum Profession { CAPTAIN, NAVIGATOR, CARPENTER, COOK, MUSICIAN, DOCTOR }
}
For each crew, Captain CiaoCiao makes sure that there are as many cooks as musicians.
Exercise:
Write a method
areSameNumberOfCooksAndMusicians(List<CrewMember>)
that returnstrue
if there are the same number of cooks as musicians,false
otherwise.
Example:
CrewMember captain = new CrewMember( "CiaoCiao", CrewMember.Profession.CAPTAIN );
CrewMember cook1 = new CrewMember( "Remy", CrewMember.Profession.COOK );
CrewMember cook2 = new CrewMember( "The Witch Cook", CrewMember.Profession.COOK );
CrewMember musician1 = new CrewMember( "Mahna Mahna", CrewMember.Profession.MUSICIAN );
CrewMember musician2 = new CrewMember( "Rowlf", CrewMember.Profession.MUSICIAN );
List<CrewMember> crew1 = List.of( cook1, musician1 );
System.out.println( areSameNumberOfCooksAndMusicians( crew1 ) ); // true
List<CrewMember> crew2 = List.of( cook1, musician1, musician2, captain );
System.out.println( areSameNumberOfCooksAndMusicians( crew2 ) ); // false
List<CrewMember> crew3 = List.of( cook1, musician1, musician2, captain, cook2 );
System.out.println( areSameNumberOfCooksAndMusicians( crew3 ) ); // true
1.2.2. Filter comments from lists ⭐
Bonny Brain reads an old logbook of Captain Dipturus Dimwit, which repeatedly contains four entries:
Magnetic Compass Course [1].
Speed of water current
Weather
Comments and general observations
The Bonny Brain is searching for a specific comment in a list of strings. To do so, they need to delete the first, second, and third entries so that only the fourth entry, which contains the desired comment, remains.
Exercise:
Implement a method
void reduceToComments(List<String> lines)
that deletes each of the first, second, and third entries in the passed list, keeping only the fourth.
Examples:
"A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4", "C1", "C2", "C3", "C4"
→"A4", "B4", "C4"
empty list → nothing happens
"A1"
→ exceptionIllegal size 1 of list, must be divisible by 4
1.2.3. Shorten lists because there is no downturn ⭐
For Captain CiaoCiao, things should only ever go up; when he reads sequences of numbers, they should only ever go up.
Exercise:
Write a method
trimNonGrowingNumbers(List<Double> numbers)
that truncates the list when the next number is no longer greater than or equal to the previous one.Remember: the passed list must be mutable so that elements can be deleted.
Examples:
If the list contains the numbers 1, 2, 3, 4, 5, the list stays that way.
If the list contains the numbers 1, 2, 3, 2, 1, the sequence is shortened to 1, 2, 3.
1.2.4. Eating with friends: Compare elements, find commonalities ⭐
Captain CiaoCiao is planning a party on the mainland, and in a large circle, there should always be two guests sitting next to each other who have at least one thing in common. Guests are declared by the following type:
public record Guest(
boolean likesToShoot,
boolean likesToGamble,
boolean likesBlackmail
) { }
Exercise:
Write a method
int allGuestsHaveSimilarInterests(List<Guest> guests)
that returns-1
if all guests have a neighbor that matches in at least one property. Otherwise, the result is>= 0
, and the index on exactly the first guest that sits incorrectly, that is, has nothing in common with any of its neighbors.Guest
can be extended in any way
1.2.5. Check lists for same order of elements ⭐
Fat Donny Bone Spurs and Ally Al Lyons sneak into the Anonymous Buccaneers support group and are told to report to Captain CiaoCiao who is sitting to the right of whom in the conversation circle. Both try to remember. They do not necessarily start with the same person in their enumerations.
Task:
Write a method
boolean isSameCircle(List<String> names1, List<String> names2)
that tests whether the names in the two lists immediately follow each other one and the same order. Consider that the people are sitting in a circle, and the last person in the list "sits" next to the first person in the list. Names of individuals may appear more than once.
Examples:
List 1:
Alexandre, Charles, Anne, Henry
→ same. List 2:
Alexandre, Charles, Anne, Henry`List 1:
Anne, Henry, Alexandre, Charles
, List 2:Alexandre, Charles, Anne, Henry
→ sameList 1:
Alexandre, Charles, Anne, Henry
. List 2:Alexandre, Charles, Henry, Anne
→ not the sameList 1:
Anne, Henry, Alexandre, Charles
, List 2:Alexandre, William, Anne, Henry
→ not the same
1.2.6. And now the weather: Find repeated elements ⭐
Napoleon Nose chats with Bonny Brain about the weather, "We’ve had so many rainy days for the last few months, it’s been bad for capering." Bonny Brain replies, "We haven’t had that many rainy days in a row!". Who is right?
Given a list of weather data:
Rain, sun, rain, rain, hail, snow, storm, sun, sun, rain, rain, sun.
In the list, sun
occurs three times in a row. That is what we want to know. Although rain
occurs more often in the list overall, that is not relevant to the solution.
Task:
Create a new record
WeatherOccurrence
for weather information:record WeatherOccurrence( String weather, int occurrences, int startIndex ) { }
Implement a method
WeatherOccurrence longestSequenceOfSameWeather(List<String> weather)
that reveals,what weather
how many times it appears in the list directly after each other and
where the longest list starts.
If weather occurs the same number of times in a row, the method is free to decide what it returns. Elements may be
null
.
1.2.7. Generate receipt output ⭐
A receipt contains entries and information such as quantity, product name, price, total.
Program a receipt in this task.
Task:
Create a new class
Receipt
for the receipt.A receipt consists of items of type
Item
. Create the classItem
as a nested type inReceipt
.Each
Item
has a name and a (gross) price stored in cents.Receipt
should overridetoString()
and return a formatted receipt:Output all products and the total.
Entries can appear more than once on the receipt and should be summarized. For example, there are not
nuts
,nuts
in the list, but2x nuts
. The entries must have the same name and price for equivalence.Use a Locale you like in
NumberFormat.getCurrencyInstance(locale)
to format the currency.
Example:
With the structure
Receipt receipt = new Receipt(); receipt.addItem( new Receipt.Item( "Peanuts", 222 ) ); receipt.addItem( new Receipt.Item( "Lightsaber", 19999 ) ); receipt.addItem( new Receipt.Item( "Peanuts", 222 ) ); receipt.addItem( new Receipt.Item( "Log book", 1000 ) ); receipt.addItem( new Receipt.Item( "Peanuts", 222 ) ); System.out.println( receipt );
is the output (the comma is used as a decimal separator):
3x Peanuts 2,22 € 6,66 € 1x Lightsaber 199,99 € 199,99 € 1x Logbook 10,00 € 10,00 € Sum: 216,65 €
1.2.8. Everything tastes better with cheese: Insert elements into lists ⭐
Captain CiaoCiao likes veggies, but when he does, he has to add lots of cheese.
Task:
Write a method
insertCheeseAroundVegetable(List)
that gets a list of recipe ingredients and whenever vegetables appear in the list, adds the ingredient "cheese" right before or after it.The list must be modifiable.
Examples:
gnocchi, zucchini, peppers, cream, broth, milk, butter, onion, tomato, salt, bell pepper → gnocchi, zucchini, cheese, peppers, cheese, cream, broth, milk, butter, onion, cheese, tomato, cheese, salt, bell pepper.
Cheese → Cheese
Use a fixed set of vegetables.
1.2.9. Search elements with the iterator and find Covid Cough ⭐⭐
Bonny Brain runs to the port and looks for Covid Cough, who is stashing disinfectant in his ship. Each ship contains a list of passenger names. Ships are declared by the following small class:
class Ship {
private List<String> persons = new ArrayList<>();
void addName( String name ) { persons.add( name ); }
boolean contains( String name ) { return persons.contains( name ); }
@Override public String toString() {
return "" + persons;
}
}
There are 100 ships at the port, stored in a LinkedList<Ship>
. Covid Cough is hiding in an unknown ship, let’s simulate that:
final int NUMBER_OF_SHIPS = 100;
List<Ship> ships = new LinkedList<>();
for ( int i = 0; i < NUMBER_OF_SHIPS; i++ )
ships.add( new Ship() );
ships.get( new Random().nextInt( ships.size() ) ).addName( "Covid Cough" );
Bonny Brain arrives at one of the many entrances to the harbor, and there are ships to her right and left:
int index = new Random().nextInt( ships.size() );
ListIterator<Ship> iterator = ships.listIterator( index );
The only access to the ships is given by the ListIterator
. Keep in mind that the ListIterator
can only be used to go forward and backward, there is no random access!
Task:
Visit the ships with the
ListIterator
, and find Covid Cough.Is there a strategy how to find the person most efficiently? It is known how many ships there are in total, 100. Since the index is known where Bonny Brain enters the harbor, we also know how many ships are to the left and right of the entrance.
1.2.10. Move elements, play musical chairs ⭐
At a birthday party, the guests are playing musical chairs. People sit on chairs, and when the music starts playing, they get up and walk around the chairs. The names of the guests are in a list.
Task A:
Create a new class
MusicalChairs
.Create a constructor
MusicalChairs(String... names)
that stores the names internally.Implement the method
toString()
which returns the names comma separated.Write a method
rotate(int distance)
that shifts the names in the list by the positiondistance
to the right. The elements falling out to the right are pushed back into the left. The operation is in place, so the (internal) list itself is changed, and the method does not return anything.
Use a suitable method from |
Example:
MusicalChairs musicalChairs =
new MusicalChairs( "Laser", "Milka", "Popo", "Despot" );
musicalChairs.rotate( 2 );
System.out.println( musicalChairs ); // Popo, Despot, Laser, Milka
Task B:
Write another method
void rotateAndRemoveLast(int distance)
, which first moves the list bydistance
positions to the right and then deletes the last element.Add a method
String play()
that callsrotateAndRemoveLast(…)
in a loop until there is only one element left in the list; then the winner is determined, and it is returned as a string. The distance is random on each pass.
Consider the case where the list might be empty in the solution.
1.2.11. Programming a question game with planets ⭐⭐
Captain CiaoCiao is accepting recruits, and to test their knowledge, he asks them the diameter of the planets in the solar system. He wants an interactive application for this, where a planet is randomly selected, and the recruits must know the diameter.
The planets are predefined as an enumeration type:
enum Planet {
JUPITER( "Jupiter", 139_822 ), SATURN( "Saturn", 116_464 ),
URANUS( "Uranus", 50_724 ), NEPTUNE( "Neptune", 49_248 ),
EARTH( "Earth", 12_756 ), VENUS( "Venus,", 12_104 ),
MARS( "Mars", 6_780 ), MERCURY( "Mercury", 4_780 ),
PLUTO( "Pluto", 2_400 );
public final String name;
public final int diameter; // km
Planet( String name, int diameter ) {
this.name = name;
this.diameter = diameter;
}
}
Task:
Program a console application that builds a random sequence of all planets in the first step. Consider how we can use the
shuffle(…)
method fromjava.util.Collections
for this.Iterate over this random sequence of planets, and generate a console output that asks for the diameter of those planets. As a choice, the recruit should be shown four diameters in kilometers, where one diameter is the correct one and three diameters are from different planets.
If the recruit enters the correct diameter, a message appears on the screen; if the wrong diameter is entered, the console output reports the correct diameter.
Example:
What is the diameter of planet Uranus (in km)? 49248 km 50724 km 12756 km 139822 km 50724 Correct! What is the diameter of planet Pluto (in km)? 12104 km 4780 km 2400 km 12756 km 11111 Wrong! The diameter of Pluto is 2400 km. What is the diameter of planet Jupiter (in km)? 139822 km 6780 km 2400 km 49248 km …
1.2.12. Understanding the implementation of the class java.util.ArrayList ⭐⭐
A java.util.ArrayList
is an important data structure. It is in competing with an array. Two questions come to mind: how is the ArrayList
actually implemented, and how big might the speed difference be?
See https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.base/share/classes/java/util/ArrayList.java for implementation sources. Get an overview.
Internally, the implementation uses the array
elementData
for the data. How does the implementation ofget(int)
andset(…)
work? What are the `costs?Where is the array object
elementData
created?An
ArrayList
is usually always a bit larger than the number of current elements, as a kind of buffer. The size is called capacity. If you build anArrayList
object with the standard constructor, how many elements is there room for in the array?If on insertion, the array is too small,
ensureCapacity(…)
will increase the size of the array. How does this method work? What is the strategy behind it?Build an
ArrayList
with the standard constructor and append 1,000 elements. How many copy operations are necessary?
1.3. Sets
Sets contain their elements only once. They can be unsorted or sorted. Java provides the Set
interface for abstraction, two important implementations are HashSet
and TreeSet
.
A whole series of questions arise with sets:
Is the set empty?
Which elements are in the set?
Is an asked element in the set, yes or no?
Given two sets, do they both contain the same elements?
What does a new set look like when two sets are united?
Does the set completely contain another set, so is one set a subset of another?
What is the intersection of two sets, that is, what are the elements that occur in both sets?
What is the difference set, that is, if you delete elements from one set that are present in another set?
Some of these operations can be answered directly using the Set data type. For example, there are the methods isEmpty()
or contains(…)
. Set operations, in particular, are not very well-supported, and programmers sometimes have to take workarounds for them. For subsets, for example, there is the Collections
method disjoint(Collection<?>, Collection<?>)
, but it returns a boolean
that says whether the two collections have no common element.
Let’s answer some questions with tasks.
1.3.1. Form subsets, find common elements ⭐
Bonny Brain’s daughter is dating Cora Corona, and they want to find out if they are compatible. So, they both write down what they like. It looks like this:
Set<String> hobbies1 = Set.of(
"Candy making", "Camping", "Billiards", "Fishkeeping", "Eating",
"Action figures", "Birdwatching", "Axe throwing" );
Set<String> hobbies2 = Set.of( "Axe throwing", "Candy making", "Camping",
"Action figures", "Case modding", "Skiing", "Satellite watching" );
Task:
What is the percentage of similarity between the two? Also, what are some methods we could use to answer this question?
Look up methods in |
1.3.2. Get all words contained in a word ⭐⭐
Captain CiaoCiao intercepts a secret message, and the text consists of seemingly unrelated words. After some pondering, it occurs to him that there are other words in the words.
A program is to find out what valid words a given word contains. To find out what is a valid word, you can use a word list from the Internet:
Task:
Write a program with a static method
Collection<String> wordList(String string, Collection<String> words)
that generates all substring contained instring
and returns exactly those words in aCollection
that are valid words in the dictionarywords
and are at least three characters long.
Example for the English dictionary:
wordList( "wristwatches", words )
→[wrist, wristwatch, wristwatches, rist, is, twa, twat, wat, watch, watches, tch, tche, che, hes]
abibliophobia
→[abib, bib, bibl, bibliophobia, pho, phobia, hob, obi, obia]
A file with words can be transformed into a data structure like this:
|
1.3.3. Exclude duplicate elements with a UniqueIterator ⭐⭐
In other data structures, for example, lists, elements can occur more than once. Write a UniqueIterator
that returns elements of a Collection
only once. null
never occurs as an element.
The generic types are declared as follows:
public class UniqueIterator<E> implements Iterator<E> {
public UniqueIterator( Iterator<? extends E> iterator ) {
// …
}
// etc.
}
The constructor indicates that the new iterator gets an existing iterator as a parameter. So, a call could look like this:
List<String> names = …;
Iterator<String> UniqueIterator = new UniqueIterator( names.iterator( ) );
1.4. Map keys to values
A hash table associates keys with values. In other programming languages, they are also called dictionaries. Java uses the Map
interface to specify the operations for all implementations. Two important implementations are HashMap
and TreeMap
.
1.4.1. Convert two-dimensional arrays to map ⭐
Data types that inherit from Collection
are relatively flexible in accepting data; for example, the elements of a List
can be copied into a Set
via addAll(Collection)
. Arrays can also be used directly as Collection
with Arrays.asList(…)
.
The Map
data type is less flexible, it is not possible to simply transfer arrays or other Collection
collections into a Map
.
Task:
Write a method
Map<String, String> convertToMap(String[][])
that converts a two-dimensional array into ajava.util.Map
.The first entry in the array should be the key, the second the value.
The keys correctly implement
hashCode()
andequals(…)
.If later in the array the same key occurs again, the method will overwrite the earlier pair.
Keys and values must not be
null
and must lead to an exception.
Example:
String[][] array = {
{ "red", "#FF0000" },
{ "green", "#00FF00" },
{ "blue", "#0000FF" }
};
Map<String, String> colorMap = convertToMap( array );
System.out.println( colorMap ); // {red=#FF0000, green=#00FF00, blue=#0000FF}
1.4.2. Convert text to Morse code and vice versa ⭐
Captain CiaoCiao needs to send a message to a distant island using Morse code. A Morse code message consists of short and long symbols, indicated by the characters .
and -
.
Copy the following definition into a new class Morse
:
// A .- N -. 0 ----- // B -... O --- 1 .---- // C -.-. P .--. 2 ..--- // D -.. Q --.- 3 ...-- // E . R .-. 4 ....- // F ..-. S ... 5 ..... // G --. T - 6 -.... // H .... U ..- 7 --... // I .. V ...- 8 ---.. // J .--- W .-- 9 ----. // K -.- X -..- // L .-.. Y -.-- // M -- Z --..
Task:
Create a new class
Morse
.Write two methods:
String encode(String string)
. It accepts a string and converts it to Morse code. Each character of the string is to be output in the corresponding Morse code. Each block is to be separated by a space in the output. Unknown characters are skipped. Lowercase letters shall be evaluated like uppercase letters. There are two spaces between words.String decode(String string)
. Turns Morse code back into the original strings. The two spaces for word separators become single spaces again.
1.4.3. Remember word frequency with associative memory ⭐⭐
Girly Gossip listens in on the deck of a group, so she can tell Captain CiaoCiao later what is being discussed. What is important is what is often mentioned as a word or phrase.
Task:
Write a method
List<String> importantGossip(String... words)
that returns, from a vararg of strings, exactly the five strings in a list that occur most frequently in the array passed.
Example:
String[] words = {
"Baby Shark", "Corona", "Baby Yoda", "Corona", "Baby Yoda", "Tiger King",
"David Bowie", "Kylie Jenner", "Kardashian", "Love Island", "Bachelorette",
"Baby Yoda", "Tiger King", "Billie Eilish", "Corona"
};
System.out.println( importantGossip( words ) );
outputs
[Baby Yoda, Corona, Tiger King, Baby Shark, Bachelorette.]
Keep in mind that it is not about the individual words like Baby
or Yoda
, but always about the whole string, for example, Baby Yoda
or Baby Shark
.
1.4.4. Read in and read out colors ⭐⭐
Bonny Brain gets a new design for their flags, but the designers only speak gibberish:
For the background, let's use #89cff0 or #bcd4e6, and for the text, maybe #fffaf0 or #f8f8ff.
She finds out that a specification like #RRGGBB
stands for the red, green, blue part of a color, coded in hexadecimal. Fortunately, there are "translation tables" like https://tutego.de/download/colors.csv, which contains lines like
amber,"Amber",#ffbf00,255,191,0 aqua,"Aqua",#0ff,0,255,255 blush,"Blush",#de5d83,222,93,131 wine,"Wine",#722f37,114,47,55
Occasionally, the file contains the color values only with three symbols, like in the example aqua
with 0ff
. In this case, the individual color values are doubled, so #RGB
then becomes #RRGGBB
.
Task:
Create a new class
Color
for the representation of colors. Each color has a name (String name
) and an RGB value (int rgb
). Write — or generate via the IDE — the methodtoString()
. Add further methods if that is useful.Create a new class
ColorNames
.Give the class an instance variable
HashMap<Integer, Color> colorMap
, so thatColorNames
can internally remember allColor
objects in aMap
; the keys of theMap
are the RGB values as integer, and the associated value is the correspondingColor
object.Copy the file https://tutego.de/download/colors.csv locally to disk.
Create a constructor that reads the file. We can use
Scanner
for this, or read the file completely withFiles.readAllLines(Paths.get("colors.csv"))
, which returns aList<String>
.Split each line of the CSV source, extract the color name (2nd column) and RGB value (3rd column). Tip: The color value can be converted to an integer using a Java method:
Integer.decode("#722f37")
returns7483191
. Remember that color names can be in the form#RGB
and#RRGGBB
.Transfer the color name and integer value to
Color
objects, and place them in theMap
.Add a method
decode(int rgb)
that returns the associatedColor
object for an RGB value.
Example:
mapper.decode( 7483191 )
→Optional['Wine' is RGB #722F37]
mapper.decode( 7 )
→Optional.empty
1.4.5. Read in names and manage lengths ⭐
Bonny Brain likes to play name crossword puzzles where each entry is a name. Often she can’t think of a name with a certain length — software should help!
Task:
The file http://tutego.de/download/family-names.txt contains last names. Save the file on your file system.
To read the file, we can utilize either the
Scanner
class or theFiles
methodreadAllLines(Path)
.Sort the names into a
TreeMap<Integer, List<String>>
: The key is the length of the name, the list contains all names with the same length.On the command line, list all names in ascending order of length.
Ask from the command line for a length and output all names of that length as long as the length is not 0 or negative.
1.4.6. Find missing characters ⭐⭐
Captain CiaoCiao has scammed the Dead Sea Scrolls (Cumexhopp scrolls) and no one has been able to decipher the text yet. He wants to make it! However, many characters are unreadable, while other characters are easily readable. It is also easy to see how many characters the word has.
Task:
Create a new class with the
main(…)
method and copy the two lists into the program:List<String> words = Arrays.asList( "house", "mouse", "horn", "cannon" ); List<String> missingLettersWords = Arrays.asList( "_ouse", "ho__", "ca__on", "gun", "__e__", "_____" );
Match each word from
missingLettersWords
with all possible words from the dictionarywords
where the underscore symbolizes unknown characters.The length of the suggested words from the dictionary must be equal to the word length of the
unreadable
word.At least one character must be given.
Example:
Possible screen output from the given lists:
_ouse -> [mouse, house] ho__ -> [horn] ca__on -> [cannon] gun -> No results __e__ -> No results _____ -> No results
1.4.7. Calculate number of paths to the three-headed monkey ⭐⭐
After a night of drinking in Manhattan, Captain CiaoCiao misses his three-headed monkey. He must have lost the stuffed animal somewhere along the way! But where could it be? His team has to walk all the streets from start to finish. The only thing Captain CiaoCiao can remember is that he didn’t get across the diagonal.
The image shows 4 × 4 street blocks and there are 14 possibilities. After some time of looking for, the crew finds the stuffed animal, they are lucky!
Captain CiaoCiao ponders: What if there were 5 or 10 blocks — wouldn’t the number of paths then be much too large to search?
The answer to the question is provided by mathematics. What is being searched for here is a monotonic path for a square with n × n cells. The number of possible paths provides the Catalan number, which is calculated as follows:
Cn = (2n)! / (n+1)! n!
Task:
Convert the formula by the method
BigInteger catalan(BigInteger n)
. Use your own internal methodBigInteger factorial(BigInteger n)
for the factorial calculation.Three factorials must be calculated in the formula: n!, (n+1)! and (2n)!. It is (n+1)! nothing else than n! × (n+1) is, so n! has to be calculated twice; also on the way to the calculation of (2n)! the intermediate result (n+1)! arises. So many multiplications have to be done twice, so the products should be cached: resort to the data type
WeakHashMap
for this.Compare the times when we call the
catalan(…)
method twice with the same parameters. Use the following code as a template:long start = System.nanoTime(); BigInteger catalan1000 = catalan( BigInteger.valueOf( 1000 ) ); long end = System.nanoTime(); System.out.println( catalan1000 ); System.out.println( TimeUnit.NANOSECONDS.toMillis( end - start ) );
1.4.8. Manage holidays in a sorted associative store ⭐
Elements in a TreeMap
are automatically sorted. TreeMap
implements java.util.NavigableMap
, which HashMap
does not. The order is either determined by an external Comparator
, or the elements have a natural order.
From the API documentation, we see that firstEntry()
and lastEntry()
return the smallest and largest element, respectively. The return type is Map.Entry<K,V>
.
Given a key key
, the following methods return relative to that key:
ceiling*(K key)
: Returns a result that is greater than or equal to this key.floor*(K key)
: Returns a result that is less than or equal to the given key.lower*(K key)
: Returns a result that is strictly less than the given key.higher*(K key)
: Returns a result that is strictly greater than the given key..
All methods return null
if there is no answer to the question.
For subsets, the following methods are useful:
SortedMap<K,V> subMap(K fromKey, K toKey)
NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive)
In the first case, fromKey
is inclusive and toKey
is exclusive; this corresponds to the usual convention of Java. The second method allows more precise control over whether the start or end element belongs.
Task:
Build a sorted associative memory:
NavigableMap<LocalDate, String> dates = new TreeMap<>( Map.of( LocalDate.of( 2024, Month.JANUARY, 1 ), "New Year's Day", … ) );
The type
<LocalDate, String>
means that a temporal typeLocalDate
should be associated with a string.The class
LocalDate
implementsComparable
, which means that the elements have a natural order.Fill the data structure with some pairs for real or fictitious holidays.
Answer the following questions using the appropriate methods of
NavigableMap
:According to the data structure, what is the earliest and last holiday?
The Christmas vacation end on the 6th of January. What is the first holiday after the Christmas vacation?
Which date values are in the Christmas vacation from December 23 – January 6 (date values inclusive)?
Delete all registered holidays that fall within the Christmas vacations from the data structure.
1.4.9. Determine commonality: Party set and souvenir ⭐
Bonny Brain plans a party, and all families contribute:
Set<String> gombonoGifts = new HashSet<>();
Collections.addAll( gombonoGifts, "Vodka", "BBQ Grill", "kneading soap" );
Set<String> banannaGifts = new HashSet<>();
Collections.addAll( banannaGifts, "Vodka", "drinking helmet" );
Set<String> cilimbiGifts = new HashSet<>();
Collections.addAll( cilimbiGifts,
"drinking helmet", "Money box", "Vodka", "water pistol" );
List<Set<String>> families =
Arrays.asList( gombonoGifts, banannaGifts, cilimbiGifts );
Since Bonny Brain is a perfection strategist, she wants to know if things are brought multiple times.
Task:
Write a method
printMultipleGifts(List<Set<String>> families)
that outputs which things are brought how many times.What was brought more than once?
Example:
Possible output for the upper assignments:
{drinking helmet=2, kneading soap=1, water pistol=1, Money box=1, BBQ Grill=1, Vodka=3} drinking helmet Vodka
1.5. Properties
The class Properties
is a special associative memory that associates only string with strings. The class not only represents a data structure, but can also read and write files called property files. These are text files that are usually used for configuration. Key-value pairs are separated in the file by =
. It is also possible to read and write the values in an XML format, but this is uncommon.
1.5.1. Develop convenient properties decorator ⭐⭐
The Properties
class contains key-value pairs, where the keys are always just strings. Possible conversions have to be done by developers themselves, which is inconvenient.
Task:
Write a class PropertiesConfiguration
that decorates a Properties
object. The most general method returns an Optional
that is either filled or empty if the key does not exist:
Optional<String> getString(String key)
.
The advantage with the Optional
is that alternatives for default values can easily be determined: conf.getProperty("rank").orElse("Captain")
.
Other methods of PropertiesConfiguration
are to perform conversions:
Optional<Boolean> getBoolean(String key)
OptionalLong getLong(String key)
OptionalDouble getDouble(String key)
Optional<BigInteger> getBigInteger(String key)
If there was no associated value to the key, the container is empty. If the conversion to an error fails, that also results in an empty container.
Example for the API:
Properties root = new Properties();
root.setProperty( "likes-rum", "true" );
root.setProperty( "age", "55" );
root.setProperty( "income", "123456789012" );
root.setProperty( "hobbies", "drinking, gambling\\, games, swearing competitions" );
root.setProperty( "weakness_of_character", "" );
PropertiesConfiguration conf = new PropertiesConfiguration( root );
Optional<Boolean> maybeLikesRum = conf.getBoolean( "likes-rum" );
OptionalLong maybeAge = conf.getLong( "age" );
Optional<BigInteger> maybeIncome = conf.getBigInteger( "income" );
System.out.println( maybeLikesRum ); // Optional[true]
System.out.println( maybeAge ); // OptionalLong[55]
System.out.println( maybeIncome ); // Optional[123456789012]
Optional addition: query lists.
Advanced developers can implement the following method:
List<String> getList(String key)
. Returns a list of comma-separated strings. The comma itself can be masked out by\,
.
Example:
List<String> hobbies = conf.getList( "hobbies" );
List<String> weaknessOfCharacter = conf.getList( "weakness_of_character" );
System.out.println( hobbies ); // [drinking, gambling, games, swearing competitions]
System.out.println( hobbies.size() ); // 3
System.out.println( weaknessOfCharacter ); // []
*Optional addition: store binary values
A java.util.HashMap
can associate arbitrary types, for a Properties
only strings can be associated with strings. If other data types, such as byte
arrays, are to be stored, they must be converted to a string. A byte[]
can be converted to an ASCII string in various ways, such as using BASE64 encoding; Java can do this using the class Base64
.
Since Properties
are read rather than written, the get*(…)
methods were sufficient for us so far. With the following addition, two new methods are to be written, one for setting and one for querying:
void putBinary(String key, byte[] bytes)
Optional<byte[]> getBinary(String key)
An example of use:
conf.putBinary( "binary", new byte[]{ 0, 1, 127, (byte) 254, (byte) 255 } );
System.out.println( conf.getString( "binary" ) ); // Optional[AAF//v8=]
conf.getBinary( "binary" ).ifPresent( binary ->
System.out.printf( "%d %d %d %d %d",
binary[0], binary[1], binary[2], binary[3], binary[4] )
);
1.6. Stack and queues
A general list in Java lets us access the elements by an index, we call such access also random access because we have the choice to ask for an element at any position. There are data structures that are much more restricted and can, for example, only insert or delete elements at the beginning or end. These include:
stack
queues
With a stack, you can only insert elements at one end and must remove the elements again at this end. The principle is also called LIFO: last in, first out. In contrast to this is the queue. With it, that is read out first, which was added also as first. The principle is called FIFO: first in, first out.
Pure stacks and queues do not exist in Java, only interfaces implemented by lists.
1.6.1. Program RPN pocket calculator ⭐
We usually write mathematical expressions in infix notation, where the operators are placed between the operands, such as 47 + 11
. In principle, however, the operator can also stand in front of the operands, as in + 47 11
, or behind them, as in 47 11 +
.
The Hewlett-Packard pocket calculators had established a special input in the 1980s, the Reverse Polish Notation (RPN). This is a postfix notation where the operators are placed after the values. The advantage for computers is that the precedence — point before line calculation — has already been resolved by users, simplifying program logic in the calculator. PostScript also uses this representation because mathematical expressions can be easily evaluated via a stack.[2]
We want to program a RPN calculator.
Task:
Write a program that first tokenizes a string like
"12 34 23 + *"
. Hint: For splitting, you can usesplit(…)
ofString
or aScanner
.After splitting, the result is to be evaluated. Start with a fixed string for testing.
Read in a string from the command line so that we have a real RPN calculator.
What errors and problems need to be handled and caught? How should we handle errors?
1.7. BitSet
The class BitSet
is a space-saving and performant alternative to boolean
arrays. The data structure is useful when you need a mapping of an integer to a truth value. The data structure can quickly answer whether an index (a positive integer) is associated with true
or false
. If the number of bits becomes too large, or if there are large gaps, https://github.com/brettwooldridge/SparseBitSet is a good alternative.
1.7.1. Forget no ship ⭐
Once a year there is an exercise where 13 ships have to be boarded. Each of the ship is uniquely identified by a number from 10 to 22. Once the adventure be over, our trusty Bonny Brain receives a list of the ships that their crew boarded. It could be something like this:
{10, 20, 21, 15, 16, 17, 18, 19, 11, 12, 13, 14, 22 }
Sometimes she gets lists where numbers are missing or appear twice:
{10, 20, 21, 15, 16, 17, 18, 22 }
{10, 20, 21, 10, 15, 16, 10 }
Such lists show Bonny Brain that ships were forgotten or repeatedly raided during the exercise. Since manual checking is inconvenient, software should do the job.
Task:
Create a new class with a new static method
checkForCompletedCompetition(int... shipIds)
. Pass the IDs of the ships.Report which ships were raided multiple times. The amount is not significant.
Report if a ship was not raided and which one it was.
Write the program in such a way that no two nested loops are used. In other words, the runtime should not be quadratic.
1.7.2. Find duplicate entries, and solve the animal chaos ⭐
Captain CiaoCiao feeds the animals of his private zoo before going to bed. But being a bit tipsy from the rum, he forgets to close the gate. The next morning, Finian Fishbone and Fred Fritte notice that animals are missing. They quickly run to Captain CiaoCiao: "Some animals have escaped!" — "Scurvy baboon! Which ones?" asks the captain. The two ponder and record (writing is not their strong suit):
🐩🐏🐴🦋🐙
🐴🐏🐧🐸🦋🐌
Captain CiaoCiao notes that both have poor memories, and only wants to have animals searched for that are named by both.
Task:
Write a method
String sameSymbols(String, String)
that returns a string containing the common symbols. The order is not significant, all Unicode characters are possible.Since we have to iterate over all characters of the
string
and it contains "higher" Unicode characters, which are moved out by twochar
, the solution is to usestring.codePoints().forEach(consumer)
. This statement iterates over all characters of the Stringstring
and calls the passedIntConsumer
for each character. This is an application of the Stream API, which we will look at in more detail in the next chapter.
Examples:
sameSymbols("🐩🐏🐴🦋🐙", "🐴🐏🐧🐸🦋🐌")
→"🐏🐴🦋"
sameSymbols("abcy", "bcd")
→"bc"
sameSymbols("abc", "def")
→""
Since the result is urgent, implement the method so that the runtime is linear with the length of the strings, in computer science jargon: O(N+M) if N and M are the lengths of the strings. All Unicode characters are allowed.
1.8. Thread-safe data structures
For the previous tasks around the data structures ArrayList
, LinkedList
, HashSet
, TreeSet
etc. we never needed more than the main thread. In the following tasks, more threads and concurrent accesses come into play, and then we need to use thread-safe data structures. For the solutions, we want to use the data types from the package java.util.concurrent
. The package contains different thread-safe data structures, which work correctly even with an arbitrary number of parallel accesses and have an excellent performance.
1.8.1. Understanding the difference between HashMap, Synchronized-Wrapper, ConcurrentHashMap.
The data structures from the Collection API are not thread-safe. In the package java.util.concurrent
there are data structures that can be accessed by many threads.
Create a
HashMap
and write with many threads concurrently into the data structure; it will break, how can you notice that?Use the synchronized wrapper
Collections.synchronizedMap()
and try again. What is the result?Use a
ConcurrentHashMap
. What happens here when you access it with multiple threads? What advantage does it have over the synchronized wrapper?There is also
Hashtable
, where does this class come from and, do we still use it?
1.8.2. Loading ship ⭐⭐
Captain CiaoCiao and the crew are getting ready for the next big adventure on Gazorpazorp Island. 5 employees put crates and barrels on the loading ramp, and 10 employees stow the goods in the ship. There can be no more than 5 objects on the loading dock at a time.
Task:
Create an
ArrayBlockingQueue<String>
of capacity 5 for the ramp.Create two different
Runnable
implementationsLoader
andUnloader
which get a reference to theArrayBlockingQueue
.The
Loader
puts strings on the ramp (random strings from a set of product names).The
Unloader
shall take strings from the ramp and output them to the screen.It takes
Loader
andUnloader
randomly between 1 and 2 seconds for their work.
5 threads shall be
Unloader
and 10 threadsLoader
.
There are different methods for adding and removing. The difference is important. Otherwise, program errors may occur:
You can’t deduce the semantics from the method names, you have to learn the difference. For our task, only one column, and these methods come into question. |
1.8.3. Handle important messages first ⭐⭐
A PriorityQueue
has an internal sorting so that the items with a higher priority move to the front. The priority comes from either the natural ordering of the elements implementing Comparable
or an external Comparator
. "Small" elements have a higher priority and move to the front of the PriorityQueue
. At the end of the queue are the elements with the lowest priority. (This is the same as with vaccinations: Prioritization group 1 gets the stuff first).
Captain CiaoCiao gets work orders from various parties, but everything from Bonny Brain has top priority. Each work order from Bonny Brain contains the coseword "Little Canon," which lets the captain know that it is time to spring into action immediately.
Task:
Put the following record into the project:
record Message( String message, LocalDateTime timestamp ) { Message( String message ) { this( Objects.requireNonNull( message ), LocalDateTime.now() ); } @Override public String toString() { return "'%s', %s".formatted( message, timestamp.format( DateTimeFormatter.ofPattern("mm:ss.SSSSSSS") ) ); } }
toString()
encloses the message in single quotes and displays only the minutes and seconds of the timestamp, because that is enough for a quick visual comparison of which message is younger or older.For
Message
implement aComparator
which creates an order so that messages with the term of endearment are »smaller« than messages without the coseword, so that this can be evaluated as a higher priority later. If both messages contain a term of endearment or both messages do not contain a term of endearment, they are equally important.Extend the
Comparator
by another comparison logic, so that the timestamp is considered, and an earlier message is also processed earlier.Initialize the
PriorityQueue
with messages, and watch how messages with the term of endearment move forward in the queue.
Example:
Assuming that PriorityQueue<Message> tasks
is a correctly initialized data structure, the following program will produce the output shown:
tasks.add( new Message( "Treasure Hunt" ) );
System.out.println( "= " + tasks );
tasks.add( new Message( "Little Canon, Family Movie Night!" ) );
System.out.println( "= " + tasks );
tasks.add( new Message( "Build a pirate ship" ) );
System.out.println( "= " + tasks );
System.out.println( tasks.remove() + "\n" + "= " + tasks );
System.out.println( tasks.remove() + "\n" + "= " + tasks );
tasks.add( new Message( "Capture the Flag" ) );
System.out.println( "= " + tasks );
tasks.add( new Message( "Bury the treasure, Little Canon" ) );
System.out.println( "= " + tasks );
tasks.add( new Message( "Little Canon, make a treasure map" ) );
System.out.println( "= " + tasks );
for ( int i = 0; i < 4; i++ )
System.out.println( tasks.remove() + "\n" + "= " + tasks );
The output is:
= ['Treasure Hunt', 44:20.8500129] = ['Little Canon, Family Movie Night!', 44:20.8580242, 'Treasure Hunt', 44:20.8500129] = ['Little Canon, Family Movie Night!', 44:20.8580242, 'Treasure Hunt', 44:20.8500129, 'Build a pirate ship', 44:20.8590231] 'Little Canon, Family Movie Night!', 44:20.8580242 = ['Treasure Hunt', 44:20.8500129, 'Build a pirate ship', 44:20.8590231] 'Treasure Hunt', 44:20.8500129 = ['Build a pirate ship', 44:20.8590231] = ['Build a pirate ship', 44:20.8590231, 'Capture the Flag', 44:20.8665477] = ['Bury the treasure, Little Canon', 44:20.8665477, 'Capture the Flag', 44:20.8665477, 'Build a pirate ship', 44:20.8590231] = ['Bury the treasure, Little Canon', 44:20.8665477, 'Little Canon, make a treasure map', 44:20.8665477, 'Build a pirate ship', 44:20.8590231, 'Capture the Flag', 44:20.8665477] 'Bury the treasure, Little Canon', 44:20.8665477 = ['Little Canon, make a treasure map', 44:20.8665477, 'Capture the Flag', 44:20.8665477, 'Build a pirate ship', 44:20.8590231] 'Little Canon, make a treasure map', 44:20.8665477 = ['Build a pirate ship', 44:20.8590231, 'Capture the Flag', 44:20.8665477] 'Build a pirate ship', 44:20.8590231 = ['Capture the Flag', 44:20.8665477] 'Capture the Flag', 44:20.8665477 = []
1.8.4. If used up, create a new one ⭐⭐⭐
The expression new BigInteger(1024, new SecureRandom())
generates a large random number of type BigInteger
.
Task:
Write a custom class
SecureRandomBigIntegerIterator
that implementsIterator
and can return an infinite number ofBigIntegers
.Whenever the number is queried and
used up
, a background thread should automatically compute a new random number.