Learning through exercises
- space: next page
- down arrow: next page in the current section
- right arrow: next section
- Domain objects are initialized in
PetDomainForKata
- Data is available to you through
this.people
- down: New concepts for Exercise 1
- right: Exercise 1 solutions
- Collect Pattern (a.k.a. map or transform).
- Returns a new collection where each element has been transformed.
- e.g. collect each pet's name.
Function
is the type that takes an object and returns an object of a different type.- a.k.a. Transformer.
List<Pet> pets = someCodeToGetPets();
List<String> petNames = new ArrayList<>();
for (Pet pet : pets)
{
petNames.add(pet.getName());
}
MutableList<Pet> pets = someCodeToGetPets();
Lambda
MutableList<String> petNames = pets.collect(pet -> pet.getName());
Method Reference
MutableList<String> petNames = pets.collect(Pet::getName);
- Select Pattern (a.k.a. filter).
- Returns the elements of collections that satisfy some condition.
- e.g. select only those people who have a pet.
Predicate
is the type that takes an object and returns a boolean.
List<Person> people = someCodeToGetPeople();
List<Person> petPeople = new ArrayList<>();
for (Person person : people)
{
if (person.isPetPerson())
{
petPeople.add(person);
}
}
MutableList<Person> people = someCodeToGetPeople();
Lambda
MutableList<Person> petPeople =
people.select(person -> person.isPetPerson());
Method Reference
MutableList<Person> petPeople = people.select(Person::isPetPerson);
- Fix
Exercise1Test
; there are failing tests. - Figure out how to get the tests to pass using what you have seen so far.
- down: Exercise 1 solutions
- right: Exercise 2
@Test
public void getFirstNamesOfAllPeople()
{
MutableList<String> firstNames =
this.people.collect(Person::getFirstName);
MutableList<String> expectedFirstNames =
Lists.mutable.with("Mary", "Bob", "Ted", "Jake", "Barry", "Terry", "Harry", "John");
Assertions.assertEquals(expectedFirstNames, firstNames);
}
@Test
public void getNamesOfMarySmithsPets()
{
Person person = this.getPersonNamed("Mary Smith");
MutableList<Pet> pets = person.getPets();
MutableList<String> names =
pets.collect(eachPet -> eachPet.getName());
Assertions.assertEquals("Tabby", names.makeString());
}
@Test
public void getPeopleWithCats()
{
MutableList<Person> people = this.people;
MutableList<Person> peopleWithCats =
people.select(person -> person.hasPet(PetType.CAT));
Verify.assertSize(2, peopleWithCats);
}
@Test
public void getPeopleWithoutCats()
{
MutableList<Person> peopleWithoutCats =
this.people.reject(person -> person.hasPet(PetType.CAT));
Verify.assertSize(6, peopleWithoutCats);
}
- down: New concepts for Exercise 2
- right: Exercise 2 solutions
- Eclipse Collections distribution includes
eclipse-collections-testutils.jar
.- Includes helpful utility for writing unit tests.
- Collection specific.
- Implemented as an extension of JUnit.
- Better error messages.
- Most important class is called
Verify
.
Example from previous solution
Verify.assertSize(2, peopleWithCats);
Instead of
Assertions.assertEquals(2, peopleWithCats.size());
flatCollect()
is a special case ofcollect()
- With
collect()
, when theFunction
returns a collection, the result is collection of collections.
collect example
MutableList<Person> people = ...;
Function<Person, Iterable<PetType>> function =
person -> person.getPetTypes();
MutableList<MutableList<PetType>> pets = people.collect(function);
flatCollect()
outputs a single flattened collection instead of a collection of collections.- The signature of
flatCollect()
is similar tocollect()
, except that theFunction
parameter must map to anIterable
type.
Method Signature
flatCollect(Function<? super T, ? extends Iterable<V>> function);
flatCollect example
MutableList<Person> people = ...;
MutableList<PetType> pets =
people.flatCollect(person -> person.getPetTypes());
- Patterns that use
Predicate
.select
returns the elements of a collection that satisfy thePredicate
.reject
returns the elements of a collection that do not satisfy thePredicate
.count
returns the number of elements that satisfy thePredicate
.
- Short-circuit pattern that use
Predicate
.detect
finds the first element that satisfies thePredicate
.anySatisfy
returns true if any element satisfies thePredicate
.allSatisfy
returns true if all elements satisfy thePredicate
.noneSatisfy
returns true if no elements satisfy thePredicate
.
MutableList
extendsList
FastList
is a drop-in replacement forArrayList
MutableSet
extendsSet
UnifiedSet
is a drop-in replacement forHashSet
Review: How can we filter people over a certain age?
MutableList<Person> voters =
people.select(person -> person.getAge() >= 18);
What if we want to find groups of people with different ages?
collect()
,select()
, andreject()
take a code block with a single parameter (Function
andPredicate
).collectWith()
,selectWith()
, andrejectWith()
are alternate forms that take two parameters.- A code block that takes two parameters.
- An object that gets passed as the second argument to the code block.
- Now the two-argument block is reusable.
Predicate2<Person, Integer> olderThan =
(person, age) -> person.getAge() >= age;
MutableList<Person> drivers = people.selectWith(olderThan, 17);
MutableList<Person> voters = people.selectWith(olderThan, 18);
MutableList<Person> drinkers = people.selectWith(olderThan, 21);
MutableList<Person> sunsetRobotSquad = people.selectWith(olderThan, 160);
- Most iteration patterns have a corresponding "With" form.
collectWith()
selectWith()
detectWith()
anySatisfyWith()
- ...etc
- These forms allow you to use method references in more situations.
MutableList<Person> peopleWithCatMethodReference =
this.people.selectWith(Person::hasPet, PetType.CAT);
- This is a made-up syntax with
select()
that does not compile. OnlyselectWith()
allows us to use a method reference here.
// Does NOT compile
MutableList<Person> peopleWithCatsLambda =
this.people.select(Person::hasPet(PetType.CAT));
- Fix
Exercise2Test
; they have failures. - Use the other iteration patterns that take a
Predicate
. - Use the
flatCollect
pattern.
- down: Exercise 2 solutions
- right: Exercise 3
@Test
public void doAnyPeopleHaveCats()
{
Predicate<Person> predicate = person -> person.hasPet(PetType.CAT);
Assertions.assertTrue(this.people.anySatisfy(predicate));
}
@Test
public void doAllPeopleHavePets()
{
Predicate<Person> predicate = person -> person.isPetPerson();
Assertions.assertFalse(this.people.allSatisfy(predicate));
}
@Test
public void howManyPeopleHaveCats()
{
int count = this.people.count(person -> person.hasPet(PetType.CAT));
Assertions.assertEquals(2, count);
}
@Test
public void findMarySmith()
{
Person result = this.people.detectWith(Person::named, "Mary Smith");
Assertions.assertEquals("Mary", result.getFirstName());
Assertions.assertEquals("Smith", result.getLastName());
}
@Test
public void getPeopleWithPets()
{
// Replace with only the pet owners.
MutableList<Person> petPeople = this.people.select(person -> person.isPetPerson());
Verify.assertSize(7, petPeople);
}
@Test
public void getAllPetTypesOfAllPeople()
{
Function<Person, Iterable<PetType>> function = person -> person.getPetTypes();
MutableSet<PetType> petTypes = this.people.flatCollect(function, Sets.mutable.empty());
Assertions.assertEquals(
Sets.mutable.with(PetType.CAT, PetType.DOG, PetType.TURTLE, PetType.HAMSTER, PetType.BIRD, PetType.SNAKE),
petTypes);
}
@Test
public void getFirstNamesOfAllPeople()
{
MutableList<String> firstNames = this.people.collect(Person::getFirstName);
Assertions.assertEquals(
Lists.mutable.with("Mary", "Bob", "Ted", "Jake", "Barry", "Terry", "Harry", "John"),
firstNames);
}
@Test
public void doAnyPeopleHaveCatsRefactor()
{
boolean peopleHaveCatsLambda = this.people.anySatisfy(person -> person.hasPet(PetType.CAT));
Assertions.assertTrue(peopleHaveCatsLambda);
// Use a method reference, NOT a lambda, to solve the problem below.
boolean peopleHaveCatsMethodRef = this.people.anySatisfyWith(Person::hasPet, PetType.CAT);
Assertions.assertTrue(peopleHaveCatsMethodRef);
}
@Test
public void doAllPeopleHaveCatsRefactor()
{
boolean peopleHaveCatsLambda = this.people.allSatisfy(person -> person.hasPet(PetType.CAT));
Assertions.assertFalse(peopleHaveCatsLambda);
// Use a method reference, NOT a lambda, to solve the problem below.
boolean peopleHaveCatsMethodRef = this.people.allSatisfyWith(Person::hasPet, PetType.CAT);
Assertions.assertFalse(peopleHaveCatsMethodRef);
}
@Test
public void getPeopleWithCatsRefator()
{
// Use a method reference, NOT a lambda, to solve the problem below.
MutableList<Person> peopleWithCatsMethodRef = this.people.selectWith(Person::hasPet, PetType.CAT);
Verify.assertSize(2, peopleWithCatsMethodRef);
}
@Test
public void getPeopleWithoutCatsRefactor()
{
// Use a method reference, NOT a lambda, to solve the problem below.
MutableList<Person> peopleWithoutCatsMethodRef = this.people.rejectWith(Person::hasPet, PetType.CAT);
Verify.assertSize(6, peopleWithoutCatsMethodRef);
}
- down: New concepts for Exercise 3
- right: Exercise 3 solutions
- Useful when you would otherwise use
Map<K, Integer>
- For example, to track the count of each PetType.
- a.k.a. Multiset, Histogram
- Using a
Map
, how should we fill in the blank?
MutableList<Person> people = ...;
MutableList<PetType> pets = people.flatCollect(Person::getPetTypes);
MutableMap<PetType, Integer> petTypeCounts = Maps.mutable.empty();
// ???
int cats = petTypeCounts.get(PetType.CAT);
MutableMap<PetType, Integer> petTypeCounts = Maps.mutable.empty();
for (PetType petType : pets)
{
Integer count = petTypeCounts.get(petType);
if (count == null)
{
count = 0;
}
petTypeCounts.put(petType, count + 1);
}
- Lot of boilerplate code to handle uninitialized counts.
Bag
is implemented as a map of key to count.- Like a
List
, but unordered. - Like a
Set
, but allows duplicates.
MutableBag<PetType> petTypeCounts = pets.toBag();
int cat = petTypeCounts.occurrencesOf(PetType.CAT);
Methods | Inherited from |
---|---|
select() , collect() , etc. |
RichIterable |
add() , remove() , iterator() , etc. |
MutableCollection (java.util.Collection ) |
occurencesOf() , forEachWithOccurences() , toMapOfItemToCount() |
Bag |
addOccurences() , removeOccurences() |
MutableBag |
MutableBag<String> bag =
Bags.mutable.with("one", "two", "two", "three", "three", "three");
Assertions.assertEquals(3, bag.occurrencesOf("three"));
bag.add("one");
Assertions.assertEquals(2, bag.occurrencesOf("one"));
bag.addOccurrences("one", 4);
Assertions.assertEquals(6, bag.occurrencesOf("one"));
Multimap
is similar to Map, but associates a key to multiple values.- Useful when you would otherwise use
Map<K, Collection<V>>
- For example, group people with same last name.
MutableList<Person> people = ...;
MutableMap<String, MutableList<Person>> lastNamesToPeople =
Maps.mutable.empty();
// ???
MutableList<Person> smiths = lastNamesToPeople.get("Smith");
- Code to populated the
Map<K, Collection<V>>
- Lot of boilerplate code to handle uninitialized backing collections.
MutableMap<String, MutableList<Person>> lastNamesToPeople =
Maps.mutable.empty();
for (Person person : people)
{
String lastName = person.getLastName();
MutableList<Person> peopleWithLastName =
lastNamesToPeople.get(lastName);
if (peopleWithLastName == null)
{
peopleWithLastName = Lists.mutable.empty();
lastNamesToPeople.put(lastName, peopleWithLastName);
}
peopleWithLastName.add(person);
}
MutableList<Person> smiths = lastNamesToPeople.get("Smith");
- Using
Multimap
for the same example
MutableListMultimap<String, Person> lastNamesToPeople =
people.groupBy(Person::getLastName);
MutableList<Person> smiths = lastNamesToPeople.get("Smith");
- What happens if you add the same key and value twice?
- It depends on which type of multimap.
MutableMultimap<String, Person> multimap = ...;
multimap.put("Smith", person);
multimap.put("Smith", person);
RichIterable<Person> smiths = multimap.get("Smith");
Verify.assertIterableSize(???, smiths);
- When the multimap is a
ListMultimap
, the groups of values are lists, which allow duplicates.
MutableListMultimap<String, Person> multimap =
Multimaps.mutable.list.empty();
multimap.put("Smith", person);
multimap.put("Smith", person);
MutableList<Person> smiths = multimap.get("Smith");
Verify.assertIterableSize(2, smiths);
- When the multimap is a
SetMultimap
, the groups of values are sets, which don't allow duplicates.
MutableSetMultimap<String, Person> multimap =
Multimaps.mutable.set.empty();
multimap.put("Smith", person);
multimap.put("Smith", person);
MutableSet<Person> smiths = multimap.get("Smith");
Verify.assertIterableSize(1, smiths);
groupByEach()
is a special case ofgroupBy()
.- Analogous to the difference between
collect()
andflatCollect()
. - Appropriate when the
Function
returns anIterable
. - The return type is the same as
groupBy()
:Multimap
.
MutableListMultimap<String, Person> lastNamesToPeople =
people.groupBy(Person::getLastName);
MutableListMultimap<PetType, Person> petsToPeople =
people.groupByEach(person -> person.getPets().collect(Pet::getType);
- Let's say we have 3 people: mrSmith, mrsSmith, mrJones.
- The first two share the same address.
- What will get printed by the following code?
MutableSet<Person> people =
Sets.mutable.with(mrSmith, mrsSmith, mrJones);
int numAddresses =
people.collect(Person::getAddress).size();
System.out.println(numAddresses);
select()
,collect()
, etc. are defined with covariant return types:MutableCollection.collect()
returns aMutableCollection
.MutableList.collect()
returns aMutableList
.MutableSet.collect()
returns aMutableSet
.
- Alternate forms take target collections.
- These forms return the same instance passed in as the target.
MutableSet<Person> people =
Sets.mutable.with(mrSmith, mrsSmith, mrJones);
MutableList<Address> targetList = Lists.mutable.empty();
int numAddresses =
people.collect(Person::getAddress, targetList).size();
System.out.println(numAddresses);
- Fix
Exercise3Test
; they have failures. - Use
Bag
,Multimap
.
- down: Exercise 3 solutions
- right: Exercise 4
@Test
public void getCountsByPetType()
{
MutableBag<PetType> counts =
this.people.flatCollect(Person::getPets).countBy(Pet::getType);
Assertions.assertEquals(2, counts.occurrencesOf(PetType.CAT));
Assertions.assertEquals(2, counts.occurrencesOf(PetType.DOG));
Assertions.assertEquals(2, counts.occurrencesOf(PetType.HAMSTER));
Assertions.assertEquals(1, counts.occurrencesOf(PetType.SNAKE));
Assertions.assertEquals(1, counts.occurrencesOf(PetType.TURTLE));
Assertions.assertEquals(1, counts.occurrencesOf(PetType.BIRD));
}
@Test
public void getPeopleByLastName()
{
MutableListMultimap<String, Person> lastNamesToPeople =
this.people.groupBy(Person::getLastName);
Verify.assertIterableSize(3, lastNamesToPeople.get("Smith"));
}
@Test
public void getPeopleByTheirPets()
{
MutableSetMultimap<PetType, Person> peopleByPetType =
this.people.groupByEach(
Person::getPetTypes,
Multimaps.mutable.set.empty());
Verify.assertIterableSize(2, peopleByPetType.get(PetType.CAT));
Verify.assertIterableSize(2, peopleByPetType.get(PetType.DOG));
Verify.assertIterableSize(1, peopleByPetType.get(PetType.HAMSTER));
Verify.assertIterableSize(1, peopleByPetType.get(PetType.TURTLE));
Verify.assertIterableSize(1, peopleByPetType.get(PetType.BIRD));
Verify.assertIterableSize(1, peopleByPetType.get(PetType.SNAKE));
}
- down: New concepts for Exercise 4
- right: Exercise 4 solutions
- Each data structure has an analogous primitive version.
- All primitive types are supported.
All 8 primitive collection interfaces
- Primitive collections have the same rich and fluent API as regular collections.
Code Example
MutableIntList intListOfAges = this.people
.flatCollect(Person::getPets)
.collectInt(Pet::getAge);
MutableIntList selection =
intListOfAges.select(age -> age > 2);
MutableIntSet intSetOfAges = intListOfAges.toSet();
- Fix
Exercise4Test
; they have failures. - Refactor Java 8 streams to Eclipse Collections.
- down: Exercise 4 solutions
- right: Exercise 5
@Test
public void getAgeStatisticsOfPets()
{
// Try to use a MutableIntList here instead
// Hints: flatMap = flatCollect, map = collect, mapToInt = collectInt
MutableIntList petAges = this.people
.flatCollect(Person::getPets)
.collectInt(Pet::getAge);
// Try to use an IntSet here instead
IntSet uniqueAges = petAges.toSet();
// IntSummaryStatistics is a class in JDK 8 - Look at MutableIntList.summaryStatistics().
IntSummaryStatistics stats = petAges.summaryStatistics();
ImmutableIntSet expected = IntSets.immutable.of(1, 2, 3, 4);
// Is a Set<Integer> equal to an IntSet?
// Hint: Try IntSets instead of Sets as the factory
Assertions.assertEquals(expected, uniqueAges);
// Try to leverage min, max, sum, average from the Eclipse Collections Primitive API
Assertions.assertEquals(stats.getMin(), petAges.minIfEmpty(0));
Assertions.assertEquals(stats.getMax(), petAges.maxIfEmpty(0));
Assertions.assertEquals(stats.getSum(), petAges.sum());
Assertions.assertEquals(stats.getAverage(), petAges.averageIfEmpty(0), 0.0);
Assertions.assertEquals(stats.getCount(), petAges.size());
// Hint: Match = Satisfy
Assertions.assertTrue(petAges.allSatisfy(IntPredicates.greaterThan(0)));
Assertions.assertTrue(petAges.allSatisfy(i -> i > 0));
Assertions.assertFalse(petAges.anySatisfy(i -> i == 0));
Assertions.assertTrue(petAges.noneSatisfy(i -> i < 0));
Assertions.assertEquals(2.0d, petAges.median(), 0.0);
}
@Test
public void streamsToECRefactor1()
{
// Find Bob Smith
Person person = this.people.detect(each -> each.named("Bob Smith"));
// Get Bob Smith's pets' names
String names = person.getPets()
.collect(Pet::getName)
.makeString(" & ");
Assertions.assertEquals("Dolly & Spot", names);
}
@Test
public void streamsToECRefactor2()
{
// Hint: Try to replace the Map<PetType, Long> with a Bag<PetType>
MutableBag<PetType> petTypes = this.people
.asUnmodifiable()
.flatCollect(Person::getPets)
.countBy(Pet::getType);
Assertions.assertEquals(2, petTypes.occurrencesOf(PetType.CAT));
Assertions.assertEquals(2, petTypes.occurrencesOf(PetType.DOG));
Assertions.assertEquals(2, petTypes.occurrencesOf(PetType.HAMSTER));
Assertions.assertEquals(1, petTypes.occurrencesOf(PetType.SNAKE));
Assertions.assertEquals(1, petTypes.occurrencesOf(PetType.TURTLE));
Assertions.assertEquals(1, petTypes.occurrencesOf(PetType.BIRD));
}
@Test
public void streamsToECRefactor3()
{
// Hint: The result of groupingBy/counting can almost always be replaced by a Bag
// Hint: Look for the API on Bag that might return the top 3 pet types
MutableList<ObjectIntPair<PetType>> favorites = this.people
.flatCollect(Person::getPets)
.countBy(Pet::getType)
.topOccurrences(3);
Verify.assertSize(3, favorites);
Verify.assertContains(PrimitiveTuples.pair(PetType.CAT, 2), favorites);
Verify.assertContains(PrimitiveTuples.pair(PetType.DOG, 2), favorites);
Verify.assertContains(PrimitiveTuples.pair(PetType.HAMSTER, 2), favorites);
}
@Test
public void getMedianOfPetAges()
{
var petAges = this.people
.flatCollect(Person::getPets)
.collectInt(Pet::getAge);
Assertions.assertEquals(2.0d, petAges.median(), 0.0);
}
- Exercise 5 is extra-credit.
- Use methods on RichIterable.
- Go right: Learn Exercise 5 solutions
@Test
public void partitionPetAndNonPetPeople()
{
PartitionMutableList<Person> partitionMutableList = this.people
.partition(Person::isPetPerson);
Verify.assertSize(7, partitionMutableList.getSelected());
Verify.assertSize(1, partitionMutableList.getRejected());
}
@Test
public void getOldestPet()
{
Pet oldestPet = this.people
.flatCollect(Person::getPets)
.maxBy(pet -> pet.getAge());
Assertions.assertEquals(PetType.DOG, oldestPet.getType());
Assertions.assertEquals(4, oldestPet.getAge());
}
@Test
public void getAveragePetAge()
{
double averagePetAge = this.people
.flatCollect(Person::getPets)
.collectDouble(pet -> pet.getAge())
.average();
Assertions.assertEquals(1.8888888888888888, averagePetAge, 0.00001);
}
@Test
public void addPetAgesToExistingSet()
{
// Hint: Use petAges as a target collection
MutableIntSet petAges = IntSets.mutable.with(5);
this.people.flatCollect(Person::getPets)
.collectInt(pet -> pet.getAge(), petAges);
Assertions.assertEquals(IntSets.mutable.with(1, 2, 3, 4, 5), petAges);
}
@Test
public void refactorToEclipseCollections()
{
// Replace List and ArrayList with Eclipse Collections types
MutableList<Person> people = Lists.mutable.with(
new Person("Mary", "Smith").addPet(PetType.CAT, "Tabby", 2),
new Person("Bob", "Smith")
.addPet(PetType.CAT, "Dolly", 3)
.addPet(PetType.DOG, "Spot", 2),
new Person("Ted", "Smith").addPet(PetType.DOG, "Spike", 4),
new Person("Jake", "Snake").addPet(PetType.SNAKE, "Serpy", 1),
new Person("Barry", "Bird").addPet(PetType.BIRD, "Tweety", 2),
new Person("Terry", "Turtle").addPet(PetType.TURTLE, "Speedy", 1),
new Person("Harry", "Hamster")
.addPet(PetType.HAMSTER, "Fuzzy", 1)
.addPet(PetType.HAMSTER, "Wuzzy", 1),
new Person("John", "Doe")
);
// Replace Set and HashSet with Eclipse Collections types
MutableIntSet petAges = people
.flatCollect(Person::getPets)
.collectInt(pet -> pet.getAge())
.toSet();
// Extra bonus - convert to a primitive collection
Assertions.assertEquals(IntSets.mutable.with(1, 2, 3, 4), petAges);
}
You have completed the Pet Kata!
Enjoy happy Java development with Eclipse Collections!