From ebfb3ed8371fda27b4a2bc644dbe160f8c2cc5d5 Mon Sep 17 00:00:00 2001 From: Sander Ploegsma Date: Thu, 4 Apr 2024 06:27:12 +0200 Subject: [PATCH] Add `sets` concept (#2769) --- concepts/sets/.meta/config.json | 5 + concepts/sets/about.md | 52 +++++ concepts/sets/introduction.md | 52 +++++ concepts/sets/links.json | 10 + config.json | 18 ++ .../gotta-snatch-em-all/.docs/hints.md | 32 +++ .../gotta-snatch-em-all/.docs/instructions.md | 80 +++++++ .../gotta-snatch-em-all/.docs/introduction.md | 54 +++++ .../.docs/introduction.md.tpl | 3 + .../gotta-snatch-em-all/.meta/config.json | 27 +++ .../gotta-snatch-em-all/.meta/design.md | 74 ++++++ .../src/reference/java/GottaSnatchEmAll.java | 40 ++++ .../concept/gotta-snatch-em-all/build.gradle | 23 ++ .../src/main/java/GottaSnatchEmAll.java | 25 ++ .../src/test/java/GottaSnatchEmAllTest.java | 215 ++++++++++++++++++ exercises/settings.gradle | 1 + 16 files changed, 711 insertions(+) create mode 100644 concepts/sets/.meta/config.json create mode 100644 concepts/sets/about.md create mode 100644 concepts/sets/introduction.md create mode 100644 concepts/sets/links.json create mode 100644 exercises/concept/gotta-snatch-em-all/.docs/hints.md create mode 100644 exercises/concept/gotta-snatch-em-all/.docs/instructions.md create mode 100644 exercises/concept/gotta-snatch-em-all/.docs/introduction.md create mode 100644 exercises/concept/gotta-snatch-em-all/.docs/introduction.md.tpl create mode 100644 exercises/concept/gotta-snatch-em-all/.meta/config.json create mode 100644 exercises/concept/gotta-snatch-em-all/.meta/design.md create mode 100644 exercises/concept/gotta-snatch-em-all/.meta/src/reference/java/GottaSnatchEmAll.java create mode 100644 exercises/concept/gotta-snatch-em-all/build.gradle create mode 100644 exercises/concept/gotta-snatch-em-all/src/main/java/GottaSnatchEmAll.java create mode 100644 exercises/concept/gotta-snatch-em-all/src/test/java/GottaSnatchEmAllTest.java diff --git a/concepts/sets/.meta/config.json b/concepts/sets/.meta/config.json new file mode 100644 index 000000000..069ef6c01 --- /dev/null +++ b/concepts/sets/.meta/config.json @@ -0,0 +1,5 @@ +{ + "blurb": "Sets are unordered collections that do not allow duplicates.", + "authors": ["sanderploegsma"], + "contributors": [] +} diff --git a/concepts/sets/about.md b/concepts/sets/about.md new file mode 100644 index 000000000..79607f271 --- /dev/null +++ b/concepts/sets/about.md @@ -0,0 +1,52 @@ +# About + +A [`Set`][set-docs] is an unordered collection that (unlike `List`) is guaranteed to not contain any duplicate values. + +The generic type parameter of the `Set` interface denotes the type of the elements contained in the `Set`: + +```java +Set ints = Set.of(1, 2, 3); +Set strings = Set.of("alpha", "beta", "gamma"); +Set mixed = Set.of(1, false, "foo"); +``` + +Note that the `Set.of()` method creates an [_unmodifiable_][unmodifiable-set-docs] `Set` instance. +Trying to call methods like `add` and `remove` on this instance will result in an exception at run-time. + +To create a modifiable `Set`, you need to instantiate a class that implements the `Set` interface. +The most-used built-in class that implements this interface is the [`HashSet`][hashset-docs] class. + +```java +Set ints = new HashSet<>(); +``` + +The `Set` interface extends from the [`Collection`][collection-docs] and [`Iterable`][iterable-docs] interfaces, and therefore shares a lot of methods with other types of collections. +A notable difference to the `Collection` interface, however, is that methods like `add` and `remove` return a `boolean` (instead of `void`) which indicates whether the item was contained in the set when that method was called: + +```java +Set set = new HashSet<>(); +set.add(1); +// => true +set.add(2); +// => true +set.add(1); +// => false +set.size(); +// => 2 +set.contains(1); +// => true +set.contains(3); +// => false +set.remove(3); +// => false +set.remove(2); +// => true +set.size(); +// => 1 +``` + +[collection-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Collection.html +[hashset-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashSet.html +[iterable-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Iterable.html +[set-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html +[unmodifiable-set-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html#unmodifiable diff --git a/concepts/sets/introduction.md b/concepts/sets/introduction.md new file mode 100644 index 000000000..f25255098 --- /dev/null +++ b/concepts/sets/introduction.md @@ -0,0 +1,52 @@ +# Introduction + +A [`Set`][set-docs] is an unordered collection that (unlike `List`) is guaranteed to not contain any duplicate values. + +The generic type parameter of the `Set` interface denotes the type of the elements contained in the `Set`: + +```java +Set ints = Set.of(1, 2, 3); +Set strings = Set.of("alpha", "beta", "gamma"); +Set mixed = Set.of(1, false, "foo"); +``` + +Note that the `Set.of()` method creates an [_unmodifiable_][unmodifiable-set-docs] `Set` instance. +Trying to call methods like `add` and `remove` on this instance will result in an exception at run-time. + +To create a modifiable `Set`, you need to instantiate a class that implements the `Set` interface. +The most-used built-in class that implements this interface is the [`HashSet`][hashset-docs] class. + +```java +Set ints = new HashSet<>(); +``` + +The `Set` interface extends from the [`Collection`][collection-docs] and [`Iterable`][iterable-docs] interfaces, and therefore shares a lot of methods with other types of collections. +A notable difference to the `Collection` interface, however, is that methods like `add` and `remove` return a `boolean` (instead of `void`) which indicates whether the item was contained in the set when that method was called: + +```java +Set set = new HashSet<>(); +set.add(1); +// => true +set.add(2); +// => true +set.add(1); +// => false +set.size(); +// => 2 +set.contains(1); +// => true +set.contains(3); +// => false +set.remove(3); +// => false +set.remove(2); +// => true +set.size(); +// => 1 +``` + +[collection-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Collection.html +[hashset-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashSet.html +[iterable-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Iterable.html +[set-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html +[unmodifiable-set-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html#unmodifiable diff --git a/concepts/sets/links.json b/concepts/sets/links.json new file mode 100644 index 000000000..1ad0bceab --- /dev/null +++ b/concepts/sets/links.json @@ -0,0 +1,10 @@ +[ + { + "url": "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html", + "description": "java.util.Set API documentation" + }, + { + "url": "https://www.baeldung.com/java-set-operations", + "description": "Implementing set operations in Java" + } +] diff --git a/config.json b/config.json index 77f419ad5..65507f432 100644 --- a/config.json +++ b/config.json @@ -295,6 +295,19 @@ "prerequisites": [ "numbers" ] + }, + { + "slug": "gotta-snatch-em-all", + "name": "gotta-snatch-em-all", + "uuid": "a7938215-4597-4c51-8ceb-6514d5654485", + "concepts": [ + "sets" + ], + "prerequisites": [ + "lists", + "generic-types" + ], + "status": "beta" } ], "practice": [ @@ -1890,6 +1903,11 @@ "slug": "randomness", "name": "Randomness" }, + { + "uuid": "775572da-46f5-4d8a-b154-f35ae344ea40", + "slug": "sets", + "name": "Sets" + }, { "uuid": "8a468b14-724a-4036-8edb-d19a02809840", "slug": "strings", diff --git a/exercises/concept/gotta-snatch-em-all/.docs/hints.md b/exercises/concept/gotta-snatch-em-all/.docs/hints.md new file mode 100644 index 000000000..48627ff3b --- /dev/null +++ b/exercises/concept/gotta-snatch-em-all/.docs/hints.md @@ -0,0 +1,32 @@ +# Hints + +## General + +- The [`Set` API documentation][set-docs] contains a list of methods available on the `Set` interface. + +## 1. Start a collection + +- The [`HashSet` class][hashset-docs] has a constructor that takes a `Collection`. + +## 2. Grow the collection + +- Check out the signature of the [`add` method][set-add-docs] defined on the `Set` interface. + +## 3. Start trading + +- You can create a copy of a `Set` by wrapping it in a `new HashSet<>()`. +- The [`Set` interface][set-docs] has a method that removes all items contained in another `Collection`. + +## 4. Identify common cards + +- You can create a copy of a `Set` by wrapping it in a `new HashSet<>()`. +- The [`Set` interface][set-docs] has a method that retains all items contained in another `Collection`. + +## 5. All of the cards + +- You can create a copy of a `Set` by wrapping it in a `new HashSet<>()`. +- The [`Set` interface][set-docs] has a method that adds all items contained in another `Collection`. + +[hashset-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashSet.html +[set-add-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html#add(E) +[set-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html diff --git a/exercises/concept/gotta-snatch-em-all/.docs/instructions.md b/exercises/concept/gotta-snatch-em-all/.docs/instructions.md new file mode 100644 index 000000000..283e236da --- /dev/null +++ b/exercises/concept/gotta-snatch-em-all/.docs/instructions.md @@ -0,0 +1,80 @@ +# Instructions + +Your nostalgia for Blorkemon™️ cards is showing no sign of slowing down, you even started collecting them again, and you +are getting your friends to join you. + +In this exercise, you will use the `Set` interface to help you manage your collection, since duplicate cards are not +important when your goal is to get all existing cards. + +## 1. Start a collection + +You just found your old stash of Blorkemon™️ cards! so it's time to start a new collection! +The stash contains a bunch of duplicate cards, so it's time to start a new collection by removing the duplicates. + +You really want your friends to join your Blorkemon™️ madness, and the best way is to kickstart their collection by +giving them one card. + +Implement the `newCollection` method, which transforms a list of cards into a `Set` representing your new collection. + +```java +GottaSnatchEmAll.newCollection(List.of("Newthree", "Newthree", "Newthree")); +// => {"Newthree"} +``` + +## 2. Grow the collection + +Once you have a collection, it takes a life of its own and must grow. + +Implement the `addCard` method, which takes a new card and your current set of collected cards. +The method should add the new card to the collection if it isn't already present, and should return a `boolean` +indicating whether the collection was updated. + +```java +Set collection = GottaSnatchEmAll.newCollection("Newthree"); +GottaSnatchEmAll.addCard("Scientuna",collection); +// => true + +collection.contains("Scientuna"); +// => true +``` + +## 3. Start trading + +You really want your friends to join your Blorkemon™️ madness, so it's time to start trading! + +Not every trade is worth doing, or can be done at all. +You cannot trade a card you don't have, and you shouldn't trade a card for one that you already have. + +Implement the `canTrade` method, that takes your current collection and the collection of one of your friends. +It should return a `boolean` indicating whether a trade is possible, following the rules above. + +```java +Set myCollection = Set.of("Newthree"); +Set theirCollection = Set.of("Scientuna"); +GottaSnatchEmAll.canTrade(myCollection, theirCollection); +// => true +``` + +## 4. Identify common cards + +You and your Blorkemon™️ enthusiast friends gather and wonder which cards are the most common. + +Implement the `commonCards` method, which takes a list of collections and returns a collection of cards that all collections +have. + +```java +GottaSnatchEmAll.commonCards(List.of(Set.of("Scientuna"), Set.of("Newthree","Scientuna"))); +// => {"Scientuna"} +``` + +## 5. All of the cards + +Do you and your friends collectively own all of the Blorkemon™️ cards? + +Implement the `allCards` method, which takes a list of collections and returns a collection of all different cards in +all the collections combined. + +```java +GottaSnatchEmAll.allCards(List.of(Set.of("Scientuna"), Set.of("Newthree","Scientuna"))); +// => {"Newthree", "Scientuna"} +``` diff --git a/exercises/concept/gotta-snatch-em-all/.docs/introduction.md b/exercises/concept/gotta-snatch-em-all/.docs/introduction.md new file mode 100644 index 000000000..921174753 --- /dev/null +++ b/exercises/concept/gotta-snatch-em-all/.docs/introduction.md @@ -0,0 +1,54 @@ +# Introduction + +## Sets + +A [`Set`][set-docs] is an unordered collection that (unlike `List`) is guaranteed to not contain any duplicate values. + +The generic type parameter of the `Set` interface denotes the type of the elements contained in the `Set`: + +```java +Set ints = Set.of(1, 2, 3); +Set strings = Set.of("alpha", "beta", "gamma"); +Set mixed = Set.of(1, false, "foo"); +``` + +Note that the `Set.of()` method creates an [_unmodifiable_][unmodifiable-set-docs] `Set` instance. +Trying to call methods like `add` and `remove` on this instance will result in an exception at run-time. + +To create a modifiable `Set`, you need to instantiate a class that implements the `Set` interface. +The most-used built-in class that implements this interface is the [`HashSet`][hashset-docs] class. + +```java +Set ints = new HashSet<>(); +``` + +The `Set` interface extends from the [`Collection`][collection-docs] and [`Iterable`][iterable-docs] interfaces, and therefore shares a lot of methods with other types of collections. +A notable difference to the `Collection` interface, however, is that methods like `add` and `remove` return a `boolean` (instead of `void`) which indicates whether the item was contained in the set when that method was called: + +```java +Set set = new HashSet<>(); +set.add(1); +// => true +set.add(2); +// => true +set.add(1); +// => false +set.size(); +// => 2 +set.contains(1); +// => true +set.contains(3); +// => false +set.remove(3); +// => false +set.remove(2); +// => true +set.size(); +// => 1 +``` + +[collection-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Collection.html +[hashset-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashSet.html +[iterable-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Iterable.html +[set-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html +[unmodifiable-set-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html#unmodifiable diff --git a/exercises/concept/gotta-snatch-em-all/.docs/introduction.md.tpl b/exercises/concept/gotta-snatch-em-all/.docs/introduction.md.tpl new file mode 100644 index 000000000..b92fafb7a --- /dev/null +++ b/exercises/concept/gotta-snatch-em-all/.docs/introduction.md.tpl @@ -0,0 +1,3 @@ +# Introduction + +%{concept:sets} diff --git a/exercises/concept/gotta-snatch-em-all/.meta/config.json b/exercises/concept/gotta-snatch-em-all/.meta/config.json new file mode 100644 index 000000000..e5ff03150 --- /dev/null +++ b/exercises/concept/gotta-snatch-em-all/.meta/config.json @@ -0,0 +1,27 @@ +{ + "authors": [ + "sanderploegsma" + ], + "contributors": [ + "kahgoh" + ], + "files": { + "solution": [ + "src/main/java/GottaSnatchEmAll.java" + ], + "test": [ + "src/test/java/GottaSnatchEmAllTest.java" + ], + "exemplar": [ + ".meta/src/reference/java/GottaSnatchEmAll.java" + ], + "invalidator": [ + "build.gradle" + ] + }, + "forked_from": [ + "elm/gotta-snatch-em-all" + ], + "icon": "character-study", + "blurb": "Learn to use sets by playing a vintage trading card game" +} diff --git a/exercises/concept/gotta-snatch-em-all/.meta/design.md b/exercises/concept/gotta-snatch-em-all/.meta/design.md new file mode 100644 index 000000000..7fb0c0312 --- /dev/null +++ b/exercises/concept/gotta-snatch-em-all/.meta/design.md @@ -0,0 +1,74 @@ +# Design + +## Learning objectives + +- Get to know the `Set` interface. +- Know about the `HashSet` class. +- Know how the `add` method works. +- Know how to perform set operations like asymmetric difference, intersection and union. + +## Out of scope + +- More specific subtypes of `Set`, such as `OrderedSet` and `SequencedSet`. +- More advanced classes implementing the `Set` interface, such as `TreeSet` and `LinkedHashSet`. + +## Concepts + +- `sets` + +## Prerequisites + +This exercise's prerequisites Concepts are: + +- `lists` +- `generic-types` + +## Analyzer + +This exercise could benefit from the following rules in the [analyzer]. +If the solution does not receive any of the listed feedback, it must be exemplar. +Leave a `celebratory` comment to celebrate the success! + +### Task 1 + +The idea behind this task is that the student knows how to convert from a `List` to a `Set`. +Ideally we want the student to use the `HashSet(Collection)` constructor. +If the solution instead creates a new empty set and manually `add` or `addAll` the items in the list, leave an `actionable` comment pointing to the `HashSet` constructor instead. + +### Task 2 + +The solution should invoke and return the result of the `add` method. +If the solution manually checks for the existence of the item using `contains` and/or an `if` statement, leave an `essential` comment pointing to the signature of the `add` method. + +### Task 3 + +The goal of this task is to calculate the [relative complement][set-relative-complement] or difference of two sets using the `removeAll` method. +If the solution manually loops through the contents of either set, leave an `essential` comment pointing to the `removeAll` method. + +Since the sets passed to the `canTrade` method are mutable on purpose, the solution may call `removeAll` on passed sets directly. +If this is the case, leave an `informative` comment explaining that it's better not to mutate the sets directly. +Instead, they should create a copy by wrapping each set in a `new HashSet()`. + +The solution may use Java Streams to find the difference between the sets. +This is also an acceptable solution and requires no feedback. + +### Task 4 + +The goal of this task is to find the [intersection][set-intersection] of a list of sets using the `retainAll` method. +If the solution manually loops through the contents of any set, leave an `essential` comment pointing to the `retainAll` method. + +The solution may use Java Streams to find the intersection of the sets. +This is also an acceptable solution and requires no feedback. + +### Task 5 + +The goal of this task is to find the [union][set-union] of a list of sets using the `addAll` method. +If the solution manually loops through the contents of any set, leave an `essential` comment pointing to the `addAll` method. + +The solution may use Java Streams to find the union of the sets. +This is also an acceptable solution and requires no feedback. + +[analyzer]: https://github.com/exercism/java-analyzer +[set-relative-complement]: https://www.baeldung.com/java-set-operations#4-the-relative-complement-of-sets +[set-intersection]: https://www.baeldung.com/java-set-operations#2-the-intersection-of-sets +[set-union]: https://www.baeldung.com/java-set-operations#3-the-union-of-sets diff --git a/exercises/concept/gotta-snatch-em-all/.meta/src/reference/java/GottaSnatchEmAll.java b/exercises/concept/gotta-snatch-em-all/.meta/src/reference/java/GottaSnatchEmAll.java new file mode 100644 index 000000000..7e9429bae --- /dev/null +++ b/exercises/concept/gotta-snatch-em-all/.meta/src/reference/java/GottaSnatchEmAll.java @@ -0,0 +1,40 @@ +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +class GottaSnatchEmAll { + + static Set newCollection(List cards) { + return new HashSet<>(cards); + } + + static boolean addCard(String card, Set collection) { + return collection.add(card); + } + + static boolean canTrade(Set myCollection, Set theirCollection) { + Set cardsWorthTradingFor = new HashSet<>(theirCollection); + cardsWorthTradingFor.removeAll(myCollection); + return !myCollection.isEmpty() && !cardsWorthTradingFor.isEmpty(); + } + + static Set commonCards(List> collections) { + if (collections.isEmpty()) { + return Set.of(); + } + + Set commonCards = new HashSet<>(collections.get(0)); + for (int i = 1; i < collections.size(); i++) { + commonCards.retainAll(collections.get(i)); + } + return commonCards; + } + + static Set allCards(List> collections) { + Set allCards = new HashSet<>(); + for (Set collection : collections) { + allCards.addAll(collection); + } + return allCards; + } +} diff --git a/exercises/concept/gotta-snatch-em-all/build.gradle b/exercises/concept/gotta-snatch-em-all/build.gradle new file mode 100644 index 000000000..d2eca9ec7 --- /dev/null +++ b/exercises/concept/gotta-snatch-em-all/build.gradle @@ -0,0 +1,23 @@ +plugins { + id "java" +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation platform("org.junit:junit-bom:5.10.0") + testImplementation "org.junit.jupiter:junit-jupiter" + testImplementation "org.assertj:assertj-core:3.25.1" +} + +test { + useJUnitPlatform() + + testLogging { + exceptionFormat = "full" + showStandardStreams = true + events = ["passed", "failed", "skipped"] + } +} diff --git a/exercises/concept/gotta-snatch-em-all/src/main/java/GottaSnatchEmAll.java b/exercises/concept/gotta-snatch-em-all/src/main/java/GottaSnatchEmAll.java new file mode 100644 index 000000000..56d2aa63f --- /dev/null +++ b/exercises/concept/gotta-snatch-em-all/src/main/java/GottaSnatchEmAll.java @@ -0,0 +1,25 @@ +import java.util.List; +import java.util.Set; + +class GottaSnatchEmAll { + + static Set newCollection(List cards) { + throw new UnsupportedOperationException("Please implement the (static) GottaSnatchEmAll.newCollection() method"); + } + + static boolean addCard(String card, Set collection) { + throw new UnsupportedOperationException("Please implement the (static) GottaSnatchEmAll.addCard() method"); + } + + static boolean canTrade(Set myCollection, Set theirCollection) { + throw new UnsupportedOperationException("Please implement the (static) GottaSnatchEmAll.canTrade() method"); + } + + static Set commonCards(List> collections) { + throw new UnsupportedOperationException("Please implement the (static) GottaSnatchEmAll.commonCards() method"); + } + + static Set allCards(List> collections) { + throw new UnsupportedOperationException("Please implement the (static) GottaSnatchEmAll.allCards() method"); + } +} diff --git a/exercises/concept/gotta-snatch-em-all/src/test/java/GottaSnatchEmAllTest.java b/exercises/concept/gotta-snatch-em-all/src/test/java/GottaSnatchEmAllTest.java new file mode 100644 index 000000000..100bbae2f --- /dev/null +++ b/exercises/concept/gotta-snatch-em-all/src/test/java/GottaSnatchEmAllTest.java @@ -0,0 +1,215 @@ +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +class GottaSnatchEmAllTest { + + @Test + @Tag("task:1") + @DisplayName("newCollection returns an empty set when given an empty list") + void testNewCollectionEmptyList() { + assertThat(GottaSnatchEmAll.newCollection(List.of())).isEmpty(); + } + + @Test + @Tag("task:1") + @DisplayName("newCollection returns a set with one card when given a list with one card") + void testNewCollectionSingletonList() { + List cards = List.of("Bleakachu"); + Set expected = Set.of("Bleakachu"); + assertThat(GottaSnatchEmAll.newCollection(cards)).isEqualTo(expected); + } + + @Test + @Tag("task:1") + @DisplayName("newCollection returns a set with one card when given a list with one repeated card") + void testNewCollectionListWithDuplicates() { + List cards = List.of("Bleakachu", "Bleakachu"); + Set expected = Set.of("Bleakachu"); + assertThat(GottaSnatchEmAll.newCollection(cards)).isEqualTo(expected); + } + + @Test + @Tag("task:1") + @DisplayName("newCollection returns a set with two cards when given a list with two unique cards") + void testNewCollectionListWithoutDuplicates() { + List cards = List.of("Bleakachu", "Newthree"); + Set expected = Set.of("Bleakachu", "Newthree"); + assertThat(GottaSnatchEmAll.newCollection(cards)).isEqualTo(expected); + } + + @Test + @Tag("task:2") + @DisplayName("addCard returns true when the collection does not yet contain the new card") + void testAddCardReturnsTrueWhenCardNotInCollection() { + Set collection = new HashSet<>(); + assertThat(GottaSnatchEmAll.addCard("Veevee", collection)).isTrue(); + } + + @Test + @Tag("task:2") + @DisplayName("addCard returns false when the collection already contains the new card") + void testAddCardReturnsFalseWhenCardAlreadyInCollection() { + Set collection = new HashSet<>(Set.of("Veevee")); + assertThat(GottaSnatchEmAll.addCard("Veevee", collection)).isFalse(); + } + + @Test + @Tag("task:2") + @DisplayName("addCard adds the card to the collection when it is a new card") + void testAddCardShouldAddNewCardToCollection() { + Set collection = new HashSet<>(); + Set expected = Set.of("Veevee"); + GottaSnatchEmAll.addCard("Veevee", collection); + assertThat(collection).isEqualTo(expected); + } + + @Test + @Tag("task:2") + @DisplayName("addCard doesn't add the card to the collection when it already contains the new card") + void testAddCardShouldNotAddExistingCardToCollection() { + Set collection = new HashSet<>(Set.of("Veevee")); + Set expected = Set.of("Veevee"); + GottaSnatchEmAll.addCard("Veevee", collection); + assertThat(collection).isEqualTo(expected); + } + + @Test + @Tag("task:3") + @DisplayName("canTrade returns false when both collections are empty") + void testCanTradeBothCollectionsEmpty() { + Set myCollection = new HashSet<>(); + Set theirCollection = new HashSet<>(); + assertThat(GottaSnatchEmAll.canTrade(myCollection, theirCollection)).isFalse(); + } + + @Test + @Tag("task:3") + @DisplayName("canTrade returns false when my collections is empty") + void testCanTradeMyCollectionsEmpty() { + Set myCollection = new HashSet<>(); + Set theirCollection = new HashSet<>(Set.of("Bleakachu")); + assertThat(GottaSnatchEmAll.canTrade(myCollection, theirCollection)).isFalse(); + } + + @Test + @Tag("task:3") + @DisplayName("canTrade returns false when their collections is empty") + void testCanTradeTheirCollectionsEmpty() { + Set myCollection = new HashSet<>(Set.of("Bleakachu")); + Set theirCollection = new HashSet<>(); + assertThat(GottaSnatchEmAll.canTrade(myCollection, theirCollection)).isFalse(); + } + + @Test + @Tag("task:3") + @DisplayName("canTrade returns false when both collections have the same cards") + void testCanTradeBothCollectionsHaveSameCards() { + Set myCollection = new HashSet<>(Set.of("Gyros", "Garilord")); + Set theirCollection = new HashSet<>(Set.of("Garilord", "Gyros")); + assertThat(GottaSnatchEmAll.canTrade(myCollection, theirCollection)).isFalse(); + } + + @Test + @Tag("task:3") + @DisplayName("canTrade returns true when both collections have unique cards") + void testCanTradeBothCollectionsHaveUniqueCards() { + Set myCollection = new HashSet<>(Set.of("Gyros")); + Set theirCollection = new HashSet<>(Set.of("Garilord")); + assertThat(GottaSnatchEmAll.canTrade(myCollection, theirCollection)).isTrue(); + } + + @Test + @Tag("task:3") + @DisplayName("canTrade returns true when both collections have at least one card the other doesn't have") + void testCanTradeBothCollectionsMixedCards() { + Set myCollection = new HashSet<>(Set.of("Gyros", "Garilord", "Bleakachu")); + Set theirCollection = new HashSet<>(Set.of("Garilord", "Veevee", "Gyros")); + assertThat(GottaSnatchEmAll.canTrade(myCollection, theirCollection)).isTrue(); + } + + @Test + @Tag("task:3") + @DisplayName("canTrade returns true when my collection is a non-empty subset of their collection") + void testCanTradeMyCollectionSubsetOfTheirCollection() { + Set myCollection = new HashSet<>(Set.of("Gyros", "Garilord")); + Set theirCollection = new HashSet<>(Set.of("Garilord", "Veevee", "Gyros")); + assertThat(GottaSnatchEmAll.canTrade(myCollection, theirCollection)).isTrue(); + } + + @Test + @Tag("task:3") + @DisplayName("canTrade returns false when their collection is a non-empty subset of my collection") + void testCanTradeTheirCollectionSubsetOfMyCollection() { + Set myCollection = new HashSet<>(Set.of("Garilord", "Veevee", "Gyros")); + Set theirCollection = new HashSet<>(Set.of("Gyros", "Garilord")); + assertThat(GottaSnatchEmAll.canTrade(myCollection, theirCollection)).isFalse(); + } + + @Test + @Tag("task:4") + @DisplayName("commonCards returns an empty set when all cards are different") + void testCommonCardsAllCardsDifferent() { + List> collections = List.of( + Set.of("Veevee"), + Set.of("Bleakachu"), + Set.of("Wigglycream") + ); + Set expected = Set.of(); + assertThat(GottaSnatchEmAll.commonCards(collections)).isEqualTo(expected); + } + + @Test + @Tag("task:4") + @DisplayName("commonCards returns a set with all cards when given a single collection") + void testCommonCardsSingleCollection() { + List> collections = List.of( + Set.of("Veevee", "Wigglycream", "Mayofried") + ); + Set expected = Set.of("Veevee", "Wigglycream", "Mayofried"); + assertThat(GottaSnatchEmAll.commonCards(collections)).isEqualTo(expected); + } + + @Test + @Tag("task:4") + @DisplayName("commonCards returns a set with cards present in all given collections") + void testCommonCardsMultipleCollections() { + List> collections = List.of( + Set.of("Veevee", "Wigglycream", "Mayofried"), + Set.of("Gyros", "Wigglycream", "Shazam"), + Set.of("Cooltentbro", "Mayofried", "Wigglycream") + ); + Set expected = Set.of("Wigglycream"); + assertThat(GottaSnatchEmAll.commonCards(collections)).isEqualTo(expected); + } + + @Test + @Tag("task:5") + @DisplayName("allCards returns a set with all cards when given a single collection") + void testAllCardsSingleCollection() { + List> collections = List.of( + Set.of("Veevee", "Wigglycream", "Mayofried") + ); + Set expected = Set.of("Veevee", "Wigglycream", "Mayofried"); + assertThat(GottaSnatchEmAll.allCards(collections)).isEqualTo(expected); + } + + @Test + @Tag("task:5") + @DisplayName("allCards returns a set with all cards when given multiple collections") + void testAllCardsMultipleCollections() { + List> collections = List.of( + Set.of("Veevee", "Wigglycream", "Mayofried"), + Set.of("Gyros", "Wigglycream", "Shazam"), + Set.of("Cooltentbro", "Mayofried", "Wigglycream") + ); + Set expected = Set.of("Cooltentbro", "Gyros", "Mayofried", "Shazam", "Veevee", "Wigglycream"); + assertThat(GottaSnatchEmAll.allCards(collections)).isEqualTo(expected); + } +} diff --git a/exercises/settings.gradle b/exercises/settings.gradle index 6ee23501a..916d7dd7f 100644 --- a/exercises/settings.gradle +++ b/exercises/settings.gradle @@ -10,6 +10,7 @@ include 'concept:captains-log' include 'concept:cars-assemble' include 'concept:elons-toy-car' include 'concept:football-match-reports' +include 'concept:gotta-snatch-em-all' include 'concept:karls-languages' include 'concept:lasagna' include 'concept:log-levels'