diff --git a/.github/workflows/java.yml b/.github/workflows/java.yml index c125970af..1a4953152 100644 --- a/.github/workflows/java.yml +++ b/.github/workflows/java.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Set up JDK 1.17 - uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 with: java-version: 17 distribution: "temurin" @@ -31,7 +31,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Set up JDK 1.17 - uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 with: java-version: 17 distribution: "temurin" @@ -47,7 +47,7 @@ jobs: - name: Test all exercises using java-test-runner run: bin/test-with-test-runner - name: Archive test results - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 with: name: test-results path: exercises/**/build/results.json diff --git a/.github/workflows/markdown.yml b/.github/workflows/markdown.yml index 5ab372415..abe9683a5 100644 --- a/.github/workflows/markdown.yml +++ b/.github/workflows/markdown.yml @@ -19,4 +19,4 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Lint markdown - uses: DavidAnson/markdownlint-cli2-action@a23dae216ce3fee4db69da41fed90d2a4af801cf + uses: DavidAnson/markdownlint-cli2-action@992badcdf24e3b8eb7e87ff9287fe931bcb00c6e diff --git a/.github/workflows/run-configlet-sync.yml b/.github/workflows/run-configlet-sync.yml new file mode 100644 index 000000000..b49cbffe8 --- /dev/null +++ b/.github/workflows/run-configlet-sync.yml @@ -0,0 +1,10 @@ +name: Run Configlet Sync + +on: + workflow_dispatch: + schedule: + - cron: '0 0 15 * *' + +jobs: + call-gha-workflow: + uses: exercism/github-actions/.github/workflows/configlet-sync.yml@main diff --git a/POLICIES.md b/POLICIES.md index fa0b5132b..09d803f04 100644 --- a/POLICIES.md +++ b/POLICIES.md @@ -51,7 +51,7 @@ References: [[1](https://github.com/exercism/java/issues/177#issuecomment-261291 > throw new UnsupportedOperationException("Delete this statement and write your own implementation."); > ``` > -> - Exercises of difficulty 5 or higher: copy the StubTemplate.java file (provided [here](https://github.com/exercism/java/tree/main/_template/src/main/java)) and rename it to fit the exercise. For example, for the exercise linked-list the file could be named LinkedList.java. Then either (1) add hints to the hints.md file (which gets merged into the README.md for the exercise) or (2) provide stubs as above for exercises that demand complicated method signatures. +> - Exercises of difficulty 5 or higher: copy the StubTemplate.java file (provided in [this template file](https://github.com/exercism/java/blob/main/resources/exercise-template/src/main/java/ExerciseName.java)) and rename it to fit the exercise. For example, for the exercise linked-list the file could be named LinkedList.java. Then either (1) add hints to the hints.md file (which gets merged into the README.md for the exercise) or (2) provide stubs as above for exercises that demand complicated method signatures. References: [[1](https://github.com/exercism/java/issues/178)], [[2](https://github.com/exercism/java/pull/683#discussion_r125506930)], [[3](https://github.com/exercism/java/issues/977)], [[4](https://github.com/exercism/java/issues/1721)] @@ -108,7 +108,7 @@ References: [[1](https://github.com/exercism/java/issues/365#issuecomment-292533 ### Good first issues -> Aim to keep 10-20 small and straightforward issues open at any given time. Identify any such issues by applying the "good first issue" label. You can view the current list of these issues [here](https://github.com/exercism/java/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). +> Aim to keep 10-20 small and straightforward issues open at any given time. Identify any such issues by applying the "good first issue" label. You can view the current list of labeled issues [on GitHub](https://github.com/exercism/java/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). References: [[1](https://github.com/exercism/java/issues/220#issue-196447088)], [[2](https://github.com/exercism/java/issues/1669)] diff --git a/concepts/classes/introduction.md b/concepts/classes/introduction.md index 7a488babe..08c04a5aa 100644 --- a/concepts/classes/introduction.md +++ b/concepts/classes/introduction.md @@ -33,7 +33,7 @@ class Car { ``` One can optionally assign an initial value to a field. -If a field does _not_ specify an initial value, it wll be set to its type's default value. +If a field does _not_ specify an initial value, it will be set to its type's default value. An instance's field values can be accessed and updated using dot-notation. ```java diff --git a/concepts/datetime/about.md b/concepts/datetime/about.md index 2724d5cdb..8ab1fbad6 100644 --- a/concepts/datetime/about.md +++ b/concepts/datetime/about.md @@ -47,13 +47,13 @@ These methods return a _new_ `LocalDate` instance and do not update the existing ```java LocalDate date = LocalDate.of(2007, 12, 3); -date.addDays(3); +date.plusDays(3); // => 2007-12-06 -date.addMonths(1); +date.plusMonths(1); // => 2008-01-03 -date.addYears(1); +date.plusYears(1); // => 2008-12-03 ``` diff --git a/concepts/datetime/introduction.md b/concepts/datetime/introduction.md index afb15b114..2b9513189 100644 --- a/concepts/datetime/introduction.md +++ b/concepts/datetime/introduction.md @@ -41,7 +41,7 @@ These methods return a _new_ `LocalDate` instance and do not update the existing ```java LocalDate date = LocalDate.of(2007, 12, 3); -date.addDays(3); +date.plusDays(3); // => 2007-12-06 ``` diff --git a/concepts/exceptions/.meta/config.json b/concepts/exceptions/.meta/config.json index 1c6bc8bb3..ef26a0098 100644 --- a/concepts/exceptions/.meta/config.json +++ b/concepts/exceptions/.meta/config.json @@ -1,5 +1,5 @@ { "blurb": "Exceptions are thrown when an error that needs special handling occurs.", "authors": ["sanderploegsma"], - "contributors": [] + "contributors": ["BahaaMohamed98"] } diff --git a/concepts/exceptions/about.md b/concepts/exceptions/about.md index 96e2bc4a5..d3a4f5946 100644 --- a/concepts/exceptions/about.md +++ b/concepts/exceptions/about.md @@ -8,11 +8,12 @@ An exception is an event that occurs during the execution of a program that disr Exceptions are raised explicitly in Java, and the act of raising an exception is called _throwing an exception_. The act of handling an exception is called _catching an exception_. -Java distinguishes three types of exceptions: +In Java, all exceptions are subclasses of the `Exception` class, which itself is a subclass of `Throwable`. + +Java distinguishes two types of exceptions: 1. Checked exceptions 2. Unchecked exceptions -3. Errors ### Checked exceptions @@ -21,7 +22,7 @@ An example of a checked exception is the `FileNotFoundException` which occurs wh This type of exception is checked at compile-time: methods that throw checked exceptions should specify this in their method signature, and code calling a method that might throw a checked exception is required to handle it or the code will not compile. -All exceptions in Java that do not inherit from `RuntimeException` or `Error` are considered checked exceptions. +All checked exceptions are subclasses of `Exception` that do not extend `RuntimeException`. ### Unchecked exceptions @@ -30,17 +31,7 @@ An example of an unchecked exception is the `NullPointerException` which occurs This type of exception is not checked at compile-time: methods that throw unchecked exceptions are not required to specify this in their method signature, and code calling a method that might throw an unchecked exception is not required to handle it. -All exceptions in Java that inherit from `RuntimeException` are considered unchecked exceptions. - -### Errors - -_Errors_ are exceptional conditions that are external to an application. -An example of an error is the `OutOfMemoryError` which occurs when an application is trying to use more memory than is available on the system. - -Like unchecked exceptions, errors are not checked at compile-time. -They are not usually thrown from application code. - -All exceptions in Java that inherit from `Error` are considered errors. +All unchecked exceptions inherit from `RuntimeException`, which itself is an extension of `Exception`. ## Throwing exceptions @@ -135,6 +126,17 @@ Withdrawal failed: Cannot withdraw a negative amount Current balance: 5.0 ``` +## Errors + +Java also has a separate category called _Errors_ which are serious problems that are external to an application. +An example of an error is the `OutOfMemoryError` which occurs when an application is trying to use more memory than is available on the system. + +Like unchecked exceptions, errors are not checked at compile-time. +The difference is that they represent system level problems and are generally thrown by the Java Virtual machine or environment instead of the application. +Applications should generally not attempt to catch or handle them. + +All errors in Java inherit from the `Error` class. + ## When not to use exceptions As stated previously, exceptions are events that disrupt the normal flow of instructions, and are used to handle _exceptional events_. diff --git a/concepts/exceptions/introduction.md b/concepts/exceptions/introduction.md index 586f6f1ea..c50113113 100644 --- a/concepts/exceptions/introduction.md +++ b/concepts/exceptions/introduction.md @@ -8,11 +8,12 @@ An exception is an event that occurs during the execution of a program that disr Exceptions are raised explicitly in Java, and the act of raising an exception is called _throwing an exception_. The act of handling an exception is called _catching an exception_. -Java distinguishes three types of exceptions: +In Java, all exceptions are subclasses of the `Exception` class, which itself is a subclass of `Throwable`. + +Java distinguishes two types of exceptions: 1. Checked exceptions 2. Unchecked exceptions -3. Errors ### Checked exceptions @@ -21,7 +22,7 @@ An example of a checked exception is the `FileNotFoundException` which occurs wh This type of exception is checked at compile-time: methods that throw checked exceptions should specify this in their method signature, and code calling a method that might throw a checked exception is required to handle it or the code will not compile. -All exceptions in Java that do not inherit from `RuntimeException` or `Error` are considered checked exceptions. +All checked exceptions are subclasses of `Exception` that do not extend `RuntimeException`. ### Unchecked exceptions @@ -30,17 +31,7 @@ An example of an unchecked exception is the `NullPointerException` which occurs This type of exception is not checked at compile-time: methods that throw unchecked exceptions are not required to specify this in their method signature, and code calling a method that might throw an unchecked exception is not required to handle it. -All exceptions in Java that inherit from `RuntimeException` are considered unchecked exceptions. - -### Errors - -_Errors_ are exceptional conditions that are external to an application. -An example of an error is the `OutOfMemoryError` which occurs when an application is trying to use more memory than is available on the system. - -Like unchecked exceptions, errors are not checked at compile-time. -They are not usually thrown from application code. - -All exceptions in Java that inherit from `Error` are considered errors. +All unchecked exceptions inherit from `RuntimeException`, which itself is an extension of `Exception`. ## Throwing exceptions @@ -134,3 +125,14 @@ Withdrawing -10.0 Withdrawal failed: Cannot withdraw a negative amount Current balance: 5.0 ``` + +## Errors + +Java also has a separate category called _Errors_ which are serious problems that are external to an application. +An example of an error is the `OutOfMemoryError` which occurs when an application is trying to use more memory than is available on the system. + +Like unchecked exceptions, errors are not checked at compile-time. +The difference is that they represent system level problems and are generally thrown by the Java Virtual machine or environment instead of the application. +Applications should generally not attempt to catch or handle them. + +All errors in Java inherit from the `Error` class. diff --git a/concepts/inheritance/about.md b/concepts/inheritance/about.md index 4d1698bb8..bd6dd590e 100644 --- a/concepts/inheritance/about.md +++ b/concepts/inheritance/about.md @@ -18,7 +18,7 @@ There are four access modifiers: - `protected` - default (No keyword required) -You can read more about them [here][access-modifiers] +You can read more about them [in this article][access-modifiers] ## Inheritance vs Composition diff --git a/concepts/maps/.meta/config.json b/concepts/maps/.meta/config.json new file mode 100644 index 000000000..98c459cdc --- /dev/null +++ b/concepts/maps/.meta/config.json @@ -0,0 +1,7 @@ +{ + "blurb": "Maps are a collection of key value pairs.", + "authors": [ + "kahgoh" + ], + "contributors": [] +} diff --git a/concepts/maps/about.md b/concepts/maps/about.md new file mode 100644 index 000000000..86a820cf1 --- /dev/null +++ b/concepts/maps/about.md @@ -0,0 +1,173 @@ +# About Maps + +A **Map** is a data structure for storing key value pairs. +It is similar to dictionaries in other programming languages. +The [Map][map-javadoc] interface defines the operations you can make with a map. + +## HashMap + +Java has a number of different Map implementations. +[HashMap][hashmap-javadoc] is a commonly used one. + +```java +// Make an instance +Map fruitPrices = new HashMap<>(); +``` + +~~~~exercism/note +When defining a `Map` variable, it is recommended to define the variable as a `Map` type rather than the specific type, as in the above example. +This practice makes it easy to change the `Map` implementation later. +~~~~ + +`HashMap` also has a copy constructor. + +```java +// Make a copy of a map +Map copy = new HashMap<>(fruitPrices); +``` + +Add entries to the map using [put][map-put-javadoc]. + +```java +fruitPrices.put("apple", 100); +fruitPrices.put("pear", 80); +// => { "apple" => 100, "pear" => 80 } +``` + +Only one value can be associated with each key. +Calling `put` with the same key will update the key's value. + +```java +fruitPrices.put("pear", 40); +// => { "apple" => 100, "pear" => 40 } +``` + +Use [get][map-get-javadoc] to get the value for a key. + +```java +fruitPrices.get("apple"); // => 100 +``` + +Use [containsKey][map-containskey-javadoc] to see if the map contains a particular key. + +```java +fruitPrices.containsKey("apple"); // => true +fruitPrices.containsKey("orange"); // => false +``` + +Remove entries with [remove][map-remove-javadoc]. + +```java +fruitPrices.put("plum", 90); // Add plum to map +fruitPrices.remove("plum"); // Removes plum from map +``` + +The [size][map-size-javadoc] method returns the number of entries. + +```java +fruitPrices.size(); // Returns 2 +``` + +You can use the [keySet][map-keyset-javadoc] or [values][map-values-javadoc] methods to obtain the keys or the values in a Map as a Set or collection respectively. + +```java +fruitPrices.keySet(); // Returns "apple" and "pear" in a set +fruitPrices.values(); // Returns 100 and 80, in a Collection +``` + +## HashMap uses `hashCode` and `equals` + +HashMaps uses the object's [hashCode][object-hashcode-javadoc] and [equals][object-equals-javadoc] method to work out where to store and how to retrieve the values for a key. +For this reason, it is important that their return values do not change between storing and getting them, otherwise the HashMap may not be able to find the value. + +For example, lets say we have the following class that will be used as the key to a map: + +```java +public class Stock { + private String name; + + public void setName(String name) { + this.name = name; + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (Objects.equals(Stock.class, obj.getClass()) && obj instanceof Stock other) { + return Objects.equals(name, other.name); + } + return false; + } +} +``` + +The `hashCode` and `equals` depend on the `name` field, which can be changed via `setName`. +Altering the `hashCode` can produce surprising results: + +```java +Stock stock = new Stock(); +stock.setName("Beanies"); + +Map stockCount = new HashMap<>(); +stockCount.put(stock, 80); + +stockCount.get(stock); // Returns 80 + +Stock other = new Stock(); +other.setName("Beanies"); + +stockCount.get(other); // Returns 80 because "other" and "stock" are equal + +stock.setName("Choccies"); +stockCount.get(stock); // Returns null because hashCode value has changed + +stockCount.get(other); // Also returns null because "other" and "stock" are not equal + +stock.setName("Beanies"); +stockCount.get(stock); // HashCode restored, so returns 80 again + +stockCount.get(other); // Also returns 80 again because "other" and "stock" are back to equal +``` + +## Map.of and Map.copyOf + +Another common way to create maps is to use [Map.of][map-of-javadoc] or [Map.ofEntries][map-ofentries-javadoc]. + +```java +// Using Map.of +Map temperatures = Map.of("Mon", 30, "Tue", 28, "Wed", 32); + +// or using Map.ofEntries +Map temperatures2 = Map.ofEntries(Map.entry("Mon", 30, "Tue", 28, "Wed", 32)); +``` + +Unlike `HashMap`, they populate the map upfront and become read-only once created. +[Map.copyOf][map-copyof-javadoc] makes a read-only copy of a map. + +```java +Map readOnlyFruitPrices = Map.copyOf(fruitPrices); +``` + +Calling methods like `put`, `remove` or `clear` results in an `UnsupportedOperationException`. + +[map-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html +[hashmap-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashMap.html +[map-put-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#put(K,V) +[map-get-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#get(java.lang.Object) +[map-containskey-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#containsKey(java.lang.Object) +[map-remove-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#remove(java.lang.Object) +[map-size-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#size() +[map-of-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#of() +[map-ofentries-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#ofEntries(java.util.Map.Entry...) +[map-copyof-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#copyOf(java.util.Map) +[object-hashcode-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Object.html#hashCode() +[object-equals-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Object.html#equals(java.lang.Object) +[map-keyset-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#keySet() +[map-values-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#values() diff --git a/concepts/maps/introduction.md b/concepts/maps/introduction.md new file mode 100644 index 000000000..60bfbe7e3 --- /dev/null +++ b/concepts/maps/introduction.md @@ -0,0 +1,72 @@ +# Introduction + +A **Map** is a data structure for storing key value pairs. +It is similar to dictionaries in other programming languages. +The [Map][map-javadoc] interface defines operations on a map. + +Java has a number of different Map implementations. +[HashMap][hashmap-javadoc] is a commonly used one. + +```java +// Make an instance +Map fruitPrices = new HashMap<>(); +``` + +Add entries to the map using [put][map-put-javadoc]. + +```java +fruitPrices.put("apple", 100); +fruitPrices.put("pear", 80); +// => { "apple" => 100, "pear" => 80 } +``` + +Only one value can be associated with each key. +Calling `put` with the same key will update the key's value. + +```java +fruitPrices.put("pear", 40); +// => { "apple" => 100, "pear" => 40 } +``` + +Use [get][map-get-javadoc] to get the value for a key. + +```java +fruitPrices.get("apple"); // => 100 +``` + +Use [containsKey][map-containskey-javadoc] to see if the map contains a particular key. + +```java +fruitPrices.containsKey("apple"); // => true +fruitPrices.containsKey("orange"); // => false +``` + +Remove entries with [remove][map-remove-javadoc]. + +```java +fruitPrices.put("plum", 90); // Add plum to map +fruitPrices.remove("plum"); // Removes plum from map +``` + +The [size][map-size-javadoc] method returns the number of entries. + +```java +fruitPrices.size(); // Returns 2 +``` + +You can use the [keySet][map-keyset-javadoc] or [values][map-values-javadoc] methods to obtain the keys or the values in a Map as a Set or collection respectively. + +```java +fruitPrices.keySet(); // Returns "apple" and "pear" in a set +fruitPrices.values(); // Returns 100 and 80, in a Collection +``` + +[map-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html +[hashmap-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashMap.html +[map-put-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#put(K,V) +[map-get-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#get(java.lang.Object) +[map-containskey-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#containsKey(java.lang.Object) +[map-remove-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#remove(java.lang.Object) +[map-size-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#size() +[map-keyset-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#keySet() +[map-values-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#values() diff --git a/concepts/maps/links.json b/concepts/maps/links.json new file mode 100644 index 000000000..22258ff94 --- /dev/null +++ b/concepts/maps/links.json @@ -0,0 +1,10 @@ +[ + { + "url": "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html", + "description": "Interface Map documentation" + }, + { + "url": "https://dev.java/learn/api/collections-framework/maps/", + "description": "Using Maps to Store Key Value Pairs" + } + ] diff --git a/concepts/switch-statement/about.md b/concepts/switch-statement/about.md index 9d53f54cb..2c89c5c04 100644 --- a/concepts/switch-statement/about.md +++ b/concepts/switch-statement/about.md @@ -145,7 +145,7 @@ Starting with Java 14 (available as a preview before in Java 12 and 13) it is po However if you use the new `->` notation it must be followed by either: a single statement/expression, a `throw` statement or a `{}` block. No more confusion! -You can find more information on enhanced switch [here][switch1], [here][switch2] and on the [oracle documentation][oracle-doc]. +You can find more information on enhanced switch in [this article][switch1] and [this one][switch2], along with the official [Oracle documentation][oracle-doc]. In addition, a feature called `Guarded Patterns` was added in Java 21, which allows you to do checks in the case label itself. @@ -160,7 +160,7 @@ return switch (day) { }; ``` -You can find more information on the switch expression on Java 21 [here][switch-on-Java-21] +You can find more information on the switch expression on Java 21 in [this blog][switch-on-Java-21] [yield-keyword]: https://www.codejava.net/java-core/the-java-language/yield-keyword-in-java [switch1]: https://www.vojtechruzicka.com/java-enhanced-switch/ diff --git a/config.json b/config.json index 86038a6b9..1356cbf81 100644 --- a/config.json +++ b/config.json @@ -110,8 +110,8 @@ "status": "active" }, { - "slug": "elons-toy-car", - "name": "Elon's Toy Car", + "slug": "jedliks-toy-car", + "name": "Jedlik's Toy Car", "uuid": "2ae791e9-eb7a-4344-841d-0c4797e5106c", "concepts": [ "classes" @@ -308,6 +308,19 @@ "generic-types" ], "status": "beta" + }, + { + "slug": "international-calling-connoisseur", + "name": "International Calling Connoisseur", + "uuid": "03506c5a-601a-42cd-b037-c310208de84d", + "concepts": [ + "maps" + ], + "prerequisites": [ + "classes", + "foreach-loops", + "generic-types" + ] } ], "practice": [ @@ -841,6 +854,18 @@ ], "difficulty": 4 }, + { + "slug": "swift-scheduling", + "name": "Swift Scheduling", + "uuid": "7f5388dc-ce0e-40d4-98d1-7a00aeae018d", + "practices": [], + "prerequisites": [ + "if-else-statements", + "datetime", + "strings" + ], + "difficulty": 4 + }, { "slug": "triangle", "name": "Triangle", @@ -989,7 +1014,8 @@ "practices": [], "prerequisites": [ "if-else-statements", - "for-loops" + "for-loops", + "maps" ], "difficulty": 5 }, @@ -1040,6 +1066,19 @@ ], "difficulty": 5 }, + { + "slug": "relative-distance", + "name": "Relative Distance", + "uuid": "a3cf95fd-c7c1-4199-a253-7bae8d1aba9a", + "practices": [ + "maps" + ], + "prerequisites": [ + "lists", + "maps" + ], + "difficulty": 5 + }, { "slug": "robot-name", "name": "Robot Name", @@ -1092,7 +1131,8 @@ "practices": [], "prerequisites": [ "for-loops", - "arrays" + "arrays", + "maps" ], "difficulty": 5 }, @@ -1144,7 +1184,8 @@ "practices": [], "prerequisites": [ "chars", - "exceptions" + "exceptions", + "maps" ], "difficulty": 6 }, @@ -1205,6 +1246,19 @@ "practices": [], "prerequisites": [ "foreach-loops", + "maps", + "strings" + ], + "difficulty": 6 + }, + { + "slug": "flower-field", + "name": "Flower Field", + "uuid": "bddd180a-d634-454a-af03-4d625f77e1e2", + "practices": [], + "prerequisites": [ + "constructors", + "lists", "strings" ], "difficulty": 6 @@ -1259,12 +1313,9 @@ "name": "Minesweeper", "uuid": "416a1489-12af-4593-8540-0f55285c96b4", "practices": [], - "prerequisites": [ - "constructors", - "lists", - "strings" - ], - "difficulty": 6 + "prerequisites": [], + "difficulty": 6, + "status": "deprecated" }, { "slug": "parallel-letter-frequency", @@ -1272,10 +1323,23 @@ "uuid": "38a405e8-619d-400f-b53c-2f06461fdf9d", "practices": [], "prerequisites": [ + "maps", "strings" ], "difficulty": 6 }, + { + "slug": "piecing-it-together", + "name": "Piecing It Together", + "uuid": "be303729-ad8a-4f4c-a235-6828a6734f05", + "practices": [], + "prerequisites": [ + "exceptions", + "for-loops", + "if-else-statements" + ], + "difficulty": 6 + }, { "slug": "queen-attack", "name": "Queen Attack", @@ -1447,7 +1511,8 @@ "uuid": "2e760ae2-fadd-4d31-9639-c4554e2826e9", "practices": [], "prerequisites": [ - "enums" + "enums", + "maps" ], "difficulty": 7 }, @@ -1495,7 +1560,8 @@ "chars", "if-else-statements", "lists", - "for-loops" + "for-loops", + "maps" ], "difficulty": 7 }, @@ -1556,7 +1622,8 @@ "prerequisites": [ "arrays", "strings", - "if-else-statements" + "if-else-statements", + "maps" ], "difficulty": 7 }, @@ -1703,6 +1770,7 @@ "exceptions", "for-loops", "if-else-statements", + "maps", "numbers" ], "difficulty": 8 @@ -1905,6 +1973,11 @@ "slug": "lists", "name": "Lists" }, + { + "uuid": "2f6fdedb-a0ac-4bab-92d6-3be61520b9bc", + "slug": "maps", + "name": "Maps" + }, { "uuid": "54118389-9c01-431b-a850-f47da498f845", "slug": "method-overloading", diff --git a/exercises/concept/annalyns-infiltration/build.gradle b/exercises/concept/annalyns-infiltration/build.gradle index 1344305f7..dd3862eb9 100644 --- a/exercises/concept/annalyns-infiltration/build.gradle +++ b/exercises/concept/annalyns-infiltration/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/bird-watcher/build.gradle b/exercises/concept/bird-watcher/build.gradle index 1344305f7..dd3862eb9 100644 --- a/exercises/concept/bird-watcher/build.gradle +++ b/exercises/concept/bird-watcher/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/bird-watcher/src/test/java/BirdWatcherTest.java b/exercises/concept/bird-watcher/src/test/java/BirdWatcherTest.java index 80ff5f8e2..5b1746082 100644 --- a/exercises/concept/bird-watcher/src/test/java/BirdWatcherTest.java +++ b/exercises/concept/bird-watcher/src/test/java/BirdWatcherTest.java @@ -16,7 +16,7 @@ public class BirdWatcherTest { private static final int TODAY = 4; private BirdWatcher birdWatcher; - private int lastWeek[] = {DAY1, DAY2, DAY3, DAY4, DAY5, DAY6, TODAY}; + private final int[] lastWeek = {DAY1, DAY2, DAY3, DAY4, DAY5, DAY6, TODAY}; @BeforeEach public void setUp() { @@ -61,6 +61,13 @@ public void itShouldNotHaveDaysWithoutBirds() { assertThat(birdWatcher.hasDayWithoutBirds()).isFalse(); } + @Test + @Tag("task:4") + @DisplayName("The hasDayWithoutBirds method returns true if the last day has zero visits") + public void itHasLastDayWithoutBirds() { + birdWatcher = new BirdWatcher(new int[]{1, 2, 5, 3, 7, 8, 0}); + assertThat(birdWatcher.hasDayWithoutBirds()).isTrue(); + } @Test @Tag("task:5") diff --git a/exercises/concept/blackjack/build.gradle b/exercises/concept/blackjack/build.gradle index 1344305f7..dd3862eb9 100644 --- a/exercises/concept/blackjack/build.gradle +++ b/exercises/concept/blackjack/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/booking-up-for-beauty/.docs/introduction.md b/exercises/concept/booking-up-for-beauty/.docs/introduction.md index 2baa59e0f..ed132331c 100644 --- a/exercises/concept/booking-up-for-beauty/.docs/introduction.md +++ b/exercises/concept/booking-up-for-beauty/.docs/introduction.md @@ -43,7 +43,7 @@ These methods return a _new_ `LocalDate` instance and do not update the existing ```java LocalDate date = LocalDate.of(2007, 12, 3); -date.addDays(3); +date.plusDays(3); // => 2007-12-06 ``` diff --git a/exercises/concept/booking-up-for-beauty/build.gradle b/exercises/concept/booking-up-for-beauty/build.gradle index 1344305f7..dd3862eb9 100644 --- a/exercises/concept/booking-up-for-beauty/build.gradle +++ b/exercises/concept/booking-up-for-beauty/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/calculator-conundrum/.docs/introduction.md b/exercises/concept/calculator-conundrum/.docs/introduction.md index e9b19e65f..49103a297 100644 --- a/exercises/concept/calculator-conundrum/.docs/introduction.md +++ b/exercises/concept/calculator-conundrum/.docs/introduction.md @@ -10,11 +10,12 @@ An exception is an event that occurs during the execution of a program that disr Exceptions are raised explicitly in Java, and the act of raising an exception is called _throwing an exception_. The act of handling an exception is called _catching an exception_. -Java distinguishes three types of exceptions: +In Java, all exceptions are subclasses of the `Exception` class, which itself is a subclass of `Throwable`. + +Java distinguishes two types of exceptions: 1. Checked exceptions 2. Unchecked exceptions -3. Errors #### Checked exceptions @@ -23,7 +24,7 @@ An example of a checked exception is the `FileNotFoundException` which occurs wh This type of exception is checked at compile-time: methods that throw checked exceptions should specify this in their method signature, and code calling a method that might throw a checked exception is required to handle it or the code will not compile. -All exceptions in Java that do not inherit from `RuntimeException` or `Error` are considered checked exceptions. +All checked exceptions are subclasses of `Exception` that do not extend `RuntimeException`. #### Unchecked exceptions @@ -32,17 +33,7 @@ An example of an unchecked exception is the `NullPointerException` which occurs This type of exception is not checked at compile-time: methods that throw unchecked exceptions are not required to specify this in their method signature, and code calling a method that might throw an unchecked exception is not required to handle it. -All exceptions in Java that inherit from `RuntimeException` are considered unchecked exceptions. - -#### Errors - -_Errors_ are exceptional conditions that are external to an application. -An example of an error is the `OutOfMemoryError` which occurs when an application is trying to use more memory than is available on the system. - -Like unchecked exceptions, errors are not checked at compile-time. -They are not usually thrown from application code. - -All exceptions in Java that inherit from `Error` are considered errors. +All unchecked exceptions inherit from `RuntimeException`, which itself is an extension of `Exception`. ### Throwing exceptions @@ -136,3 +127,14 @@ Withdrawing -10.0 Withdrawal failed: Cannot withdraw a negative amount Current balance: 5.0 ``` + +### Errors + +Java also has a separate category called _Errors_ which are serious problems that are external to an application. +An example of an error is the `OutOfMemoryError` which occurs when an application is trying to use more memory than is available on the system. + +Like unchecked exceptions, errors are not checked at compile-time. +The difference is that they represent system level problems and are generally thrown by the Java Virtual machine or environment instead of the application. +Applications should generally not attempt to catch or handle them. + +All errors in Java inherit from the `Error` class. diff --git a/exercises/concept/calculator-conundrum/build.gradle b/exercises/concept/calculator-conundrum/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/concept/calculator-conundrum/build.gradle +++ b/exercises/concept/calculator-conundrum/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/captains-log/build.gradle b/exercises/concept/captains-log/build.gradle index 1344305f7..dd3862eb9 100644 --- a/exercises/concept/captains-log/build.gradle +++ b/exercises/concept/captains-log/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/cars-assemble/build.gradle b/exercises/concept/cars-assemble/build.gradle index 1344305f7..dd3862eb9 100644 --- a/exercises/concept/cars-assemble/build.gradle +++ b/exercises/concept/cars-assemble/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/football-match-reports/build.gradle b/exercises/concept/football-match-reports/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/concept/football-match-reports/build.gradle +++ b/exercises/concept/football-match-reports/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/gotta-snatch-em-all/build.gradle b/exercises/concept/gotta-snatch-em-all/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/concept/gotta-snatch-em-all/build.gradle +++ b/exercises/concept/gotta-snatch-em-all/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/international-calling-connoisseur/.docs/hints.md b/exercises/concept/international-calling-connoisseur/.docs/hints.md new file mode 100644 index 000000000..8bedc370b --- /dev/null +++ b/exercises/concept/international-calling-connoisseur/.docs/hints.md @@ -0,0 +1,39 @@ +# Hints + +## General + +- The [`Map` API documentation][map-docs] contains a list of methods available on the `Map` interface. + +## 1. Return the codes in a map + +- You will need to define a `Map` in [such a way][declaring-members] that you can use it in the class methods. + +## 2. Add entries to the dictionary + +- Maps have a [method][map-put-docs] to add and update the value for a key. + +## 3. Lookup a dialing code's country + +- Maps have a [method][map-get-docs] to get the key's value. + +## 4. Don't allow duplicates + +- There is a way to check if the map has a [key][map-contains-key-docs] or a [value][map-contains-value-docs]. + +## 5. Find a country's dialing code + +- There is a [way][map-entry-set-docs] to get an iterable collection of entries in a map, which allows you to go through the key-value pairs. + +## 6. Update the country's dialing code + +- Do not forget about the country's previous dialing code will be in the map. +- There is a [method][map-remove-docs] to remove an entry from the map. + +[declaring-members]: https://dev.java/learn/classes-objects/creating-classes/#declaring-members +[map-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html +[map-put-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#put(K,V) +[map-get-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#get(java.lang.Object) +[map-contains-key-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#containsKey(java.lang.Object) +[map-contains-value-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#containsValue(java.lang.Object) +[map-entry-set-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#entrySet() +[map-remove-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#remove(java.lang.Object) diff --git a/exercises/concept/international-calling-connoisseur/.docs/instructions.md b/exercises/concept/international-calling-connoisseur/.docs/instructions.md new file mode 100644 index 000000000..e8f728f0f --- /dev/null +++ b/exercises/concept/international-calling-connoisseur/.docs/instructions.md @@ -0,0 +1,95 @@ +# Instructions + +In this exercise you'll be writing code to manage a dictionary of international dialing codes using a `Map`. + +The dictionary allows looking up the name of a country (the map's value, as a `String`) by the international dialing code (the map's key, as an `Integer`), + +## 1. Return the codes in a map + +Implement the method `getCodes` that takes no parameters and returns a map of the dialing codes to country currently in the dictionary. + +```java +DialingCodes dialingCodes = new DialingCodes(); +dialingCodes.getCodes(); +// => empty map +``` + +## 2. Add entries to the dictionary + +The dictionary needs to be populated. +Implement the `setDialingCode` method that takes a dialing code and the corresponding country and adds the dialing code and country. +If the dialing code is already in the map, update the map with the provided code and country. + +```java +DialingCodes dialingCodes = new DialingCodes(); +dialingCodes.setDialingCode(679, "Unknown"); +// => { 679 => "Unknown" } + +dialingCodes.setDialingCode(679, "Fiji"); +// => { 679 => "Fiji" } +``` + +## 3. Lookup a dialing code's country + +Implement the `getCountry` method that takes a map of dialing codes and a dialing code and returns the country name with the dialing code. + +```java +DialingCodes dialingCodes = new DialingCodes(); +dialingCodes.setDialingCode(55, "Brazil"); +dialingCodes.getCountry(55); +// => "Brazil" +``` + +## 4. Don't allow duplicates + +When adding a dialing code, care needs to be taken to prevent a code or country to be added twice. +In situations where this happens, it can be assumed that the first entry is the right entry. +Implement the `addNewDialingCode` method that adds an entry for the given dialing code and country. +However, unlike `setDialingCode`, it does nothing if the dialing code or the country is already in the map. + +```java +DialingCodes dialingCodes = new DialingCodes(); +dialingCodes.addNewDialingCode(32, "Belgium"); +dialingCodes.addNewDialingCode(379, "Vatican City"); +// => { 39 => "Italy", 379 => "Vatican City" } + + +dialingCodes.addNewDialingCode(32, "Other"); +dialingCodes.addNewDialingCode(39, "Vatican City"); +// => { 32 => "Belgium", 379 => "Vatican City" } +``` + +## 5. Find a country's dialing code + +Its rare, but mistakes can be made. +To correct the mistake, we will need to know what dialing code the country is currently mapped to. +To find which dialing code needs to be corrected, implement the `findDialingCode` method that takes in a map of dialing codes and a country and returns the country's dialing code. +Return `null` if the country is _not_ in the map. + +```java +DialingCodes dialingCodes = new DialingCodes(); +dialingCodes.addDialingCode(44, "UK"); +dialingCodes.findDialingCode("UK"); +// => 44 + +dialingCodes.findDialingCode("Unlisted"); +// => null +``` + +## 6. Update the country's dialing code + +Now that we know which dialing code needs to be corrected, we proceed to update the code. +Implement the `updateCountryDialingCode` method that takes the country's new dialing code and the country's name and updates accordingly. +Do nothing if the country is _not_ in the map (as this method is meant to only help correct mistakes). + +```java +DialingCodes dialingCodes = new DialingCodes(); +dialingCodes.addDialingCode(88, "Japan"); +// => { 88 => "Japan" } + +dialingCodes.updateCountryDialingCode(81, "Japan"); +// => { 81 => "Japan" } + +dialingCodes.updateCountryDialingCode(32, "Mars"); +// => { 81 => "Japan"} +``` diff --git a/exercises/concept/international-calling-connoisseur/.docs/introduction.md b/exercises/concept/international-calling-connoisseur/.docs/introduction.md new file mode 100644 index 000000000..c407eea39 --- /dev/null +++ b/exercises/concept/international-calling-connoisseur/.docs/introduction.md @@ -0,0 +1,74 @@ +# Introduction + +## Maps + +A **Map** is a data structure for storing key value pairs. +It is similar to dictionaries in other programming languages. +The [Map][map-javadoc] interface defines operations on a map. + +Java has a number of different Map implementations. +[HashMap][hashmap-javadoc] is a commonly used one. + +```java +// Make an instance +Map fruitPrices = new HashMap<>(); +``` + +Add entries to the map using [put][map-put-javadoc]. + +```java +fruitPrices.put("apple", 100); +fruitPrices.put("pear", 80); +// => { "apple" => 100, "pear" => 80 } +``` + +Only one value can be associated with each key. +Calling `put` with the same key will update the key's value. + +```java +fruitPrices.put("pear", 40); +// => { "apple" => 100, "pear" => 40 } +``` + +Use [get][map-get-javadoc] to get the value for a key. + +```java +fruitPrices.get("apple"); // => 100 +``` + +Use [containsKey][map-containskey-javadoc] to see if the map contains a particular key. + +```java +fruitPrices.containsKey("apple"); // => true +fruitPrices.containsKey("orange"); // => false +``` + +Remove entries with [remove][map-remove-javadoc]. + +```java +fruitPrices.put("plum", 90); // Add plum to map +fruitPrices.remove("plum"); // Removes plum from map +``` + +The [size][map-size-javadoc] method returns the number of entries. + +```java +fruitPrices.size(); // Returns 2 +``` + +You can use the [keySet][map-keyset-javadoc] or [values][map-values-javadoc] methods to obtain the keys or the values in a Map as a Set or collection respectively. + +```java +fruitPrices.keySet(); // Returns "apple" and "pear" in a set +fruitPrices.values(); // Returns 100 and 80, in a Collection +``` + +[map-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashMap.html +[hashmap-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashMap.html +[map-put-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#put(K,V) +[map-get-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#get(java.lang.Object) +[map-containskey-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#containsKey(java.lang.Object) +[map-remove-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#remove(java.lang.Object) +[map-size-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#size() +[map-keyset-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#keySet() +[map-values-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#values() diff --git a/exercises/concept/international-calling-connoisseur/.docs/introduction.md.tpl b/exercises/concept/international-calling-connoisseur/.docs/introduction.md.tpl new file mode 100644 index 000000000..f1ea541d1 --- /dev/null +++ b/exercises/concept/international-calling-connoisseur/.docs/introduction.md.tpl @@ -0,0 +1,3 @@ +# Introduction + +%{concept:maps} diff --git a/exercises/concept/international-calling-connoisseur/.meta/config.json b/exercises/concept/international-calling-connoisseur/.meta/config.json new file mode 100644 index 000000000..e65e319f0 --- /dev/null +++ b/exercises/concept/international-calling-connoisseur/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "kahgoh" + ], + "files": { + "solution": [ + "src/main/java/DialingCodes.java" + ], + "test": [ + "src/test/java/DialingCodesTest.java" + ], + "exemplar": [ + ".meta/src/reference/java/DialingCodes.java" + ], + "invalidator": [ + "build.gradle" + ] + }, + "blurb": "Learn about maps while managing international calling codes." +} diff --git a/exercises/concept/international-calling-connoisseur/.meta/design.md b/exercises/concept/international-calling-connoisseur/.meta/design.md new file mode 100644 index 000000000..a173c4900 --- /dev/null +++ b/exercises/concept/international-calling-connoisseur/.meta/design.md @@ -0,0 +1,38 @@ +# Design + +## Learning objectives + +- Know about the `Map` interface. +- Know about `HashMap`. +- Know how to put, remove and retrieve items from a `Map`. +- Know how to find the keys of the map. + +## Out of scope + +- Map equality. +- The importance of the key object's `hashCode` and `equals` implementation and how `HashMap` uses them. +- More advanced or specific types of `Map`, such as `WeakHashMap` or `TreeMap`. +- Handling concurrency. + +## Concepts + +- `map` + +## Prerequisites + +This exercise's prerequisites Concepts are: + +- `classes` +- `foreach-loops` +- `generic-types` + +## Analyzer + +This exercise could benefit from the following rules in the [analyzer]: + +- `actionable`: If the user directly returns the class field, recommend returning a copy to the student. + +If the solution does not receive any of the above feedback, it must be exemplar. +Leave a `celebratory` comment to celebrate the success! + +[analyzer]: https://github.com/exercism/java-analyzer diff --git a/exercises/concept/international-calling-connoisseur/.meta/src/reference/java/DialingCodes.java b/exercises/concept/international-calling-connoisseur/.meta/src/reference/java/DialingCodes.java new file mode 100644 index 000000000..d3ecb0a36 --- /dev/null +++ b/exercises/concept/international-calling-connoisseur/.meta/src/reference/java/DialingCodes.java @@ -0,0 +1,43 @@ +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class DialingCodes { + + private final Map countryByDialingCode = new HashMap<>(); + + public Map getCodes() { + return Map.copyOf(countryByDialingCode); + } + + public void setDialingCode(Integer code, String country) { + countryByDialingCode.put(code, country); + } + + public String getCountry(Integer code) { + return countryByDialingCode.get(code); + } + + public void addNewDialingCode(Integer code, String country) { + if (!countryByDialingCode.containsValue(country)) { + countryByDialingCode.putIfAbsent(code, country); + } + } + + public Integer findDialingCode(String country) { + for (Map.Entry entry : countryByDialingCode.entrySet()) { + if (Objects.equals(entry.getValue(), country)) { + return entry.getKey(); + } + } + return null; + } + + public void updateCountryDialingCode(Integer code, String country) { + Integer existingCode = findDialingCode(country); + if (existingCode != null) { + countryByDialingCode.remove(existingCode); + countryByDialingCode.put(code, country); + } + } +} diff --git a/exercises/concept/international-calling-connoisseur/build.gradle b/exercises/concept/international-calling-connoisseur/build.gradle new file mode 100644 index 000000000..d28f35dee --- /dev/null +++ b/exercises/concept/international-calling-connoisseur/build.gradle @@ -0,0 +1,25 @@ +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" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" +} + +test { + useJUnitPlatform() + + testLogging { + exceptionFormat = "full" + showStandardStreams = true + events = ["passed", "failed", "skipped"] + } +} diff --git a/exercises/concept/elons-toy-car/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/international-calling-connoisseur/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from exercises/concept/elons-toy-car/gradle/wrapper/gradle-wrapper.jar rename to exercises/concept/international-calling-connoisseur/gradle/wrapper/gradle-wrapper.jar diff --git a/exercises/concept/elons-toy-car/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/international-calling-connoisseur/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from exercises/concept/elons-toy-car/gradle/wrapper/gradle-wrapper.properties rename to exercises/concept/international-calling-connoisseur/gradle/wrapper/gradle-wrapper.properties diff --git a/exercises/concept/elons-toy-car/gradlew b/exercises/concept/international-calling-connoisseur/gradlew similarity index 100% rename from exercises/concept/elons-toy-car/gradlew rename to exercises/concept/international-calling-connoisseur/gradlew diff --git a/exercises/concept/elons-toy-car/gradlew.bat b/exercises/concept/international-calling-connoisseur/gradlew.bat similarity index 100% rename from exercises/concept/elons-toy-car/gradlew.bat rename to exercises/concept/international-calling-connoisseur/gradlew.bat diff --git a/exercises/concept/international-calling-connoisseur/src/main/java/DialingCodes.java b/exercises/concept/international-calling-connoisseur/src/main/java/DialingCodes.java new file mode 100644 index 000000000..0c40e1835 --- /dev/null +++ b/exercises/concept/international-calling-connoisseur/src/main/java/DialingCodes.java @@ -0,0 +1,34 @@ +import java.util.Map; + +public class DialingCodes { + + public Map getCodes() { + throw new UnsupportedOperationException( + "Delete this statement and write your own implementation."); + } + + public void setDialingCode(Integer code, String country) { + throw new UnsupportedOperationException( + "Delete this statement and write your own implementation."); + } + + public String getCountry(Integer code) { + throw new UnsupportedOperationException( + "Delete this statement and write your own implementation."); + } + + public void addNewDialingCode(Integer code, String country) { + throw new UnsupportedOperationException( + "Delete this statement and write your own implementation."); + } + + public Integer findDialingCode(String country) { + throw new UnsupportedOperationException( + "Delete this statement and write your own implementation."); + } + + public void updateCountryDialingCode(Integer code, String country) { + throw new UnsupportedOperationException( + "Delete this statement and write your own implementation."); + } +} diff --git a/exercises/concept/international-calling-connoisseur/src/test/java/DialingCodesTest.java b/exercises/concept/international-calling-connoisseur/src/test/java/DialingCodesTest.java new file mode 100644 index 000000000..e88e7379f --- /dev/null +++ b/exercises/concept/international-calling-connoisseur/src/test/java/DialingCodesTest.java @@ -0,0 +1,141 @@ +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +public class DialingCodesTest { + + @Test + @Tag("task:1") + @DisplayName("getCodes initially returns an empty map") + public void testGetCodesReturnsMap() { + DialingCodes dialingCodes = new DialingCodes(); + + assertThat(dialingCodes.getCodes()).isEmpty(); + } + + @Test + @Tag("task:2") + @DisplayName("setDialingCode adds new entry") + public void testSetDialingCodeAddsEntry() { + DialingCodes dialingCodes = new DialingCodes(); + dialingCodes.setDialingCode(679, "Fiji"); + + assertThat(dialingCodes.getCodes()).containsOnly(entry(679, "Fiji")); + } + + @Test + @Tag("task:2") + @DisplayName("setDialingCode updates existing entry") + public void testSetDialingCodeUpdatesEntry() { + DialingCodes dialingCodes = new DialingCodes(); + dialingCodes.setDialingCode(679, "Unknown"); + dialingCodes.setDialingCode(679, "Fiji"); + + assertThat(dialingCodes.getCodes()).containsOnly(entry(679, "Fiji")); + } + + @Test + @Tag("task:2") + @DisplayName("setDialingCode with multiple entries") + public void testSetDialingCodeWithMultipleEntries() { + DialingCodes dialingCodes = new DialingCodes(); + dialingCodes.setDialingCode(60, "Malaysia"); + dialingCodes.setDialingCode(233, "Retrieving..."); + dialingCodes.setDialingCode(56, "Chile"); + dialingCodes.setDialingCode(233, "Ghana"); + + assertThat(dialingCodes.getCodes()).containsOnly(entry(60, "Malaysia"), entry(233, "Ghana"), + entry(56, "Chile")); + } + + @Test + @Tag("task:3") + @DisplayName("getCountry returns a code's country") + public void testGetCountryForCode() { + DialingCodes dialingCodes = new DialingCodes(); + dialingCodes.setDialingCode(55, "Brazil"); + + assertThat(dialingCodes.getCountry(55)).isEqualTo("Brazil"); + } + + @Test + @Tag("task:3") + @DisplayName("getCountry returns updated country") + public void testGetCountryForUpdatedCode() { + DialingCodes dialingCodes = new DialingCodes(); + dialingCodes.setDialingCode(962, "Retrieving..."); + dialingCodes.setDialingCode(962, "Jordan"); + + assertThat(dialingCodes.getCountry(962)).isEqualTo("Jordan"); + } + + @Test + @Tag("task:4") + @DisplayName("addNewDialingCode adds new codes") + public void testAddNewDialingCodeAddsNewCodes() { + DialingCodes dialingCodes = new DialingCodes(); + dialingCodes.addNewDialingCode(32, "Belgium"); + dialingCodes.addNewDialingCode(379, "Vatican City"); + + assertThat(dialingCodes.getCodes()).containsOnly(entry(32, "Belgium"), + entry(379, "Vatican City")); + } + + @Test + @Tag("task:4") + @DisplayName("addNewDialingCode leaves already added code") + public void testAddNewDialingCodeLeavesExistingCode() { + DialingCodes dialingCodes = new DialingCodes(); + dialingCodes.addNewDialingCode(32, "Belgium"); + dialingCodes.addNewDialingCode(379, "Vatican City"); + dialingCodes.addNewDialingCode(32, "Other"); + + assertThat(dialingCodes.getCodes()).containsOnly(entry(32, "Belgium"), + entry(379, "Vatican City")); + } + + @Test + @Tag("task:4") + @DisplayName("addNewDialingCode leaves already added country") + public void testAddNewDialingCodeLeavesExistingCountry() { + DialingCodes dialingCodes = new DialingCodes(); + dialingCodes.addNewDialingCode(61, "Australia"); + dialingCodes.addNewDialingCode(1000, "Australia"); + + assertThat(dialingCodes.getCodes()).containsOnly(entry(61, "Australia")); + } + + @Test + @Tag("task:5") + @DisplayName("findDialingCode returns a country's dialing code") + public void testFindDialingCode() { + DialingCodes dialingCodes = new DialingCodes(); + dialingCodes.addNewDialingCode(44, "UK"); + + assertThat(dialingCodes.findDialingCode("UK")).isEqualTo(44); + } + + @Test + @Tag("task:5") + @DisplayName("findDialingCode returns null for country not yet added") + public void testFindDialingCodeWithUnlistedCountry() { + DialingCodes dialingCodes = new DialingCodes(); + dialingCodes.addNewDialingCode(44, "UK"); + + assertThat(dialingCodes.findDialingCode("Unlisted")).isNull(); + } + + @Test + @Tag("task:6") + @DisplayName("updateDialingCode updates the map") + public void testUpdateDialingCode() { + DialingCodes dialingCodes = new DialingCodes(); + dialingCodes.addNewDialingCode(88, "Japan"); + dialingCodes.updateCountryDialingCode(81, "Japan"); + + assertThat(dialingCodes.getCodes()).containsOnly(entry(81, "Japan")); + } +} diff --git a/exercises/concept/elons-toy-car/.docs/hints.md b/exercises/concept/jedliks-toy-car/.docs/hints.md similarity index 100% rename from exercises/concept/elons-toy-car/.docs/hints.md rename to exercises/concept/jedliks-toy-car/.docs/hints.md diff --git a/exercises/concept/elons-toy-car/.docs/instructions.md b/exercises/concept/jedliks-toy-car/.docs/instructions.md similarity index 61% rename from exercises/concept/elons-toy-car/.docs/instructions.md rename to exercises/concept/jedliks-toy-car/.docs/instructions.md index a24720618..cb3f66561 100644 --- a/exercises/concept/elons-toy-car/.docs/instructions.md +++ b/exercises/concept/jedliks-toy-car/.docs/instructions.md @@ -15,38 +15,38 @@ You have six tasks, each of which will work with remote controlled car instances ## 1. Buy a brand-new remote controlled car -Implement the (_static_) `ElonsToyCar.buy()` method to return a brand-new remote controlled car instance: +Implement the (_static_) `JedliksToyCar.buy()` method to return a brand-new remote controlled car instance: ```java -ElonsToyCar car = ElonsToyCar.buy(); +JedliksToyCar car = JedliksToyCar.buy(); ``` ## 2. Display the distance driven -Implement the `ElonsToyCar.distanceDisplay()` method to return the distance as displayed on the LED display: +Implement the `JedliksToyCar.distanceDisplay()` method to return the distance as displayed on the LED display: ```java -ElonsToyCar car = ElonsToyCar.buy(); +JedliksToyCar car = JedliksToyCar.buy(); car.distanceDisplay(); // => "Driven 0 meters" ``` ## 3. Display the battery percentage -Implement the `ElonsToyCar.batteryDisplay()` method to return the battery percentage as displayed on the LED display: +Implement the `JedliksToyCar.batteryDisplay()` method to return the battery percentage as displayed on the LED display: ```java -ElonsToyCar car = ElonsToyCar.buy(); +JedliksToyCar car = JedliksToyCar.buy(); car.batteryDisplay(); // => "Battery at 100%" ``` ## 4. Update the number of meters driven when driving -Implement the `ElonsToyCar.drive()` method that updates the number of meters driven: +Implement the `JedliksToyCar.drive()` method that updates the number of meters driven: ```java -ElonsToyCar car = ElonsToyCar.buy(); +JedliksToyCar car = JedliksToyCar.buy(); car.drive(); car.drive(); car.distanceDisplay(); @@ -55,10 +55,10 @@ car.distanceDisplay(); ## 5. Update the battery percentage when driving -Update the `ElonsToyCar.drive()` method to update the battery percentage: +Update the `JedliksToyCar.drive()` method to update the battery percentage: ```java -ElonsToyCar car = ElonsToyCar.buy(); +JedliksToyCar car = JedliksToyCar.buy(); car.drive(); car.drive(); car.batteryDisplay(); @@ -67,10 +67,10 @@ car.batteryDisplay(); ## 6. Prevent driving when the battery is drained -Update the `ElonsToyCar.drive()` method to not increase the distance driven nor decrease the battery percentage when the battery is drained (at 0%): +Update the `JedliksToyCar.drive()` method to not increase the distance driven nor decrease the battery percentage when the battery is drained (at 0%): ```java -ElonsToyCar car = ElonsToyCar.buy(); +JedliksToyCar car = JedliksToyCar.buy(); // Drain the battery // ... diff --git a/exercises/concept/elons-toy-car/.docs/introduction.md b/exercises/concept/jedliks-toy-car/.docs/introduction.md similarity index 100% rename from exercises/concept/elons-toy-car/.docs/introduction.md rename to exercises/concept/jedliks-toy-car/.docs/introduction.md diff --git a/exercises/concept/elons-toy-car/.docs/introduction.md.tpl b/exercises/concept/jedliks-toy-car/.docs/introduction.md.tpl similarity index 100% rename from exercises/concept/elons-toy-car/.docs/introduction.md.tpl rename to exercises/concept/jedliks-toy-car/.docs/introduction.md.tpl diff --git a/exercises/concept/elons-toy-car/.meta/config.json b/exercises/concept/jedliks-toy-car/.meta/config.json similarity index 64% rename from exercises/concept/elons-toy-car/.meta/config.json rename to exercises/concept/jedliks-toy-car/.meta/config.json index e55a4b908..300ca8c16 100644 --- a/exercises/concept/elons-toy-car/.meta/config.json +++ b/exercises/concept/jedliks-toy-car/.meta/config.json @@ -7,18 +7,18 @@ ], "files": { "solution": [ - "src/main/java/ElonsToyCar.java" + "src/main/java/JedliksToyCar.java" ], "test": [ - "src/test/java/ElonsToyCarTest.java" + "src/test/java/JedliksToyCarTest.java" ], "exemplar": [ - ".meta/src/reference/java/ElonsToyCar.java" + ".meta/src/reference/java/JedliksToyCar.java" ], "invalidator": [ "build.gradle" ] }, - "icon": "elons-toys", + "icon": "jedliks-toys", "blurb": "Learn about classes by working on a remote controlled car." } diff --git a/exercises/concept/elons-toy-car/.meta/design.md b/exercises/concept/jedliks-toy-car/.meta/design.md similarity index 100% rename from exercises/concept/elons-toy-car/.meta/design.md rename to exercises/concept/jedliks-toy-car/.meta/design.md diff --git a/exercises/concept/elons-toy-car/.meta/src/reference/java/ElonsToyCar.java b/exercises/concept/jedliks-toy-car/.meta/src/reference/java/JedliksToyCar.java similarity index 84% rename from exercises/concept/elons-toy-car/.meta/src/reference/java/ElonsToyCar.java rename to exercises/concept/jedliks-toy-car/.meta/src/reference/java/JedliksToyCar.java index 683e4bb32..96ef6955c 100644 --- a/exercises/concept/elons-toy-car/.meta/src/reference/java/ElonsToyCar.java +++ b/exercises/concept/jedliks-toy-car/.meta/src/reference/java/JedliksToyCar.java @@ -1,4 +1,4 @@ -class ElonsToyCar { +class JedliksToyCar { private int batteryPercentage = 100; private int distanceDrivenInMeters = 0; @@ -21,7 +21,7 @@ public String batteryDisplay() { return "Battery at " + batteryPercentage + "%"; } - public static ElonsToyCar buy() { - return new ElonsToyCar(); + public static JedliksToyCar buy() { + return new JedliksToyCar(); } } diff --git a/exercises/concept/elons-toy-car/build.gradle b/exercises/concept/jedliks-toy-car/build.gradle similarity index 86% rename from exercises/concept/elons-toy-car/build.gradle rename to exercises/concept/jedliks-toy-car/build.gradle index 1344305f7..dd3862eb9 100644 --- a/exercises/concept/elons-toy-car/build.gradle +++ b/exercises/concept/jedliks-toy-car/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/jedliks-toy-car/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/jedliks-toy-car/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..e6441136f Binary files /dev/null and b/exercises/concept/jedliks-toy-car/gradle/wrapper/gradle-wrapper.jar differ diff --git a/exercises/concept/jedliks-toy-car/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/jedliks-toy-car/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..2deab89d5 --- /dev/null +++ b/exercises/concept/jedliks-toy-car/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/exercises/concept/jedliks-toy-car/gradlew b/exercises/concept/jedliks-toy-car/gradlew new file mode 100644 index 000000000..1aa94a426 --- /dev/null +++ b/exercises/concept/jedliks-toy-car/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/exercises/concept/jedliks-toy-car/gradlew.bat b/exercises/concept/jedliks-toy-car/gradlew.bat new file mode 100644 index 000000000..25da30dbd --- /dev/null +++ b/exercises/concept/jedliks-toy-car/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/exercises/concept/elons-toy-car/src/main/java/ElonsToyCar.java b/exercises/concept/jedliks-toy-car/src/main/java/JedliksToyCar.java similarity index 60% rename from exercises/concept/elons-toy-car/src/main/java/ElonsToyCar.java rename to exercises/concept/jedliks-toy-car/src/main/java/JedliksToyCar.java index f3efa7259..d9906a7b8 100644 --- a/exercises/concept/elons-toy-car/src/main/java/ElonsToyCar.java +++ b/exercises/concept/jedliks-toy-car/src/main/java/JedliksToyCar.java @@ -1,17 +1,17 @@ -public class ElonsToyCar { - public static ElonsToyCar buy() { - throw new UnsupportedOperationException("Please implement the (static) ElonsToyCar.buy() method"); +public class JedliksToyCar { + public static JedliksToyCar buy() { + throw new UnsupportedOperationException("Please implement the (static) JedliksToyCar.buy() method"); } public String distanceDisplay() { - throw new UnsupportedOperationException("Please implement the ElonsToyCar.distanceDisplay() method"); + throw new UnsupportedOperationException("Please implement the JedliksToyCar.distanceDisplay() method"); } public String batteryDisplay() { - throw new UnsupportedOperationException("Please implement the ElonsToyCar.batteryDisplay() method"); + throw new UnsupportedOperationException("Please implement the JedliksToyCar.batteryDisplay() method"); } public void drive() { - throw new UnsupportedOperationException("Please implement the ElonsToyCar.drive() method"); + throw new UnsupportedOperationException("Please implement the JedliksToyCar.drive() method"); } } diff --git a/exercises/concept/elons-toy-car/src/test/java/ElonsToyCarTest.java b/exercises/concept/jedliks-toy-car/src/test/java/JedliksToyCarTest.java similarity index 85% rename from exercises/concept/elons-toy-car/src/test/java/ElonsToyCarTest.java rename to exercises/concept/jedliks-toy-car/src/test/java/JedliksToyCarTest.java index f97c2a798..19db64391 100644 --- a/exercises/concept/elons-toy-car/src/test/java/ElonsToyCarTest.java +++ b/exercises/concept/jedliks-toy-car/src/test/java/JedliksToyCarTest.java @@ -4,12 +4,12 @@ import static org.assertj.core.api.Assertions.assertThat; -public class ElonsToyCarTest { +public class JedliksToyCarTest { @Test @Tag("task:1") @DisplayName("The static buy method returns a new remote controlled car instance") public void buy_new_car_returns_instance() { - ElonsToyCar car = ElonsToyCar.buy(); + JedliksToyCar car = JedliksToyCar.buy(); assertThat(car).isNotNull(); } @@ -17,8 +17,8 @@ public void buy_new_car_returns_instance() { @Tag("task:1") @DisplayName("The static buy method returns each time a new remote controlled car instance") public void buy_new_car_returns_new_car_each_time() { - ElonsToyCar car1 = ElonsToyCar.buy(); - ElonsToyCar car2 = ElonsToyCar.buy(); + JedliksToyCar car1 = JedliksToyCar.buy(); + JedliksToyCar car2 = JedliksToyCar.buy(); assertThat(car1).isNotEqualTo(car2); } @@ -26,7 +26,7 @@ public void buy_new_car_returns_new_car_each_time() { @Tag("task:2") @DisplayName("The distanceDisplay method shows 0 meters message on a new car") public void new_car_distance_display() { - ElonsToyCar car = new ElonsToyCar(); + JedliksToyCar car = new JedliksToyCar(); assertThat(car.distanceDisplay()).isEqualTo("Driven 0 meters"); } @@ -34,7 +34,7 @@ public void new_car_distance_display() { @Tag("task:3") @DisplayName("The batteryDisplay method shows full battery message on a new car") public void new_car_battery_display() { - ElonsToyCar car = new ElonsToyCar(); + JedliksToyCar car = new JedliksToyCar(); assertThat(car.batteryDisplay()).isEqualTo("Battery at 100%"); } @@ -42,7 +42,7 @@ public void new_car_battery_display() { @Tag("task:4") @DisplayName("The distanceDisplay method shows the correct message after driving once") public void distance_display_after_driving_once() { - ElonsToyCar car = new ElonsToyCar(); + JedliksToyCar car = new JedliksToyCar(); car.drive(); assertThat(car.distanceDisplay()).isEqualTo("Driven 20 meters"); } @@ -51,7 +51,7 @@ public void distance_display_after_driving_once() { @Tag("task:4") @DisplayName("The distanceDisplay method shows the correct message after driving multiple times") public void distance_display_after_driving_multiple_times() { - ElonsToyCar car = new ElonsToyCar(); + JedliksToyCar car = new JedliksToyCar(); for (int i = 0; i < 17; i++) { car.drive(); @@ -64,7 +64,7 @@ public void distance_display_after_driving_multiple_times() { @Tag("task:5") @DisplayName("The batteryDisplay method shows the correct message after driving once") public void battery_display_after_driving_once() { - ElonsToyCar car = new ElonsToyCar(); + JedliksToyCar car = new JedliksToyCar(); car.drive(); assertThat(car.batteryDisplay()).isEqualTo("Battery at 99%"); @@ -74,7 +74,7 @@ public void battery_display_after_driving_once() { @Tag("task:5") @DisplayName("The batteryDisplay method shows the correct battery percentage after driving multiple times") public void battery_display_after_driving_multiple_times() { - ElonsToyCar car = new ElonsToyCar(); + JedliksToyCar car = new JedliksToyCar(); for (int i = 0; i < 23; i++) { car.drive(); @@ -87,7 +87,7 @@ public void battery_display_after_driving_multiple_times() { @Tag("task:5") @DisplayName("The batteryDisplay method shows battery empty after draining all battery") public void battery_display_when_battery_empty() { - ElonsToyCar car = new ElonsToyCar(); + JedliksToyCar car = new JedliksToyCar(); // Drain the battery for (int i = 0; i < 100; i++) { @@ -104,7 +104,7 @@ public void battery_display_when_battery_empty() { @Tag("task:6") @DisplayName("The distanceDisplay method shows the correct message after driving and draining all battery") public void distance_display_when_battery_empty() { - ElonsToyCar car = new ElonsToyCar(); + JedliksToyCar car = new JedliksToyCar(); // Drain the battery for (int i = 0; i < 100; i++) { diff --git a/exercises/concept/karls-languages/build.gradle b/exercises/concept/karls-languages/build.gradle index 1344305f7..dd3862eb9 100644 --- a/exercises/concept/karls-languages/build.gradle +++ b/exercises/concept/karls-languages/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/lasagna/build.gradle b/exercises/concept/lasagna/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/concept/lasagna/build.gradle +++ b/exercises/concept/lasagna/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/log-levels/.docs/hints.md b/exercises/concept/log-levels/.docs/hints.md index 8453667c5..2e5bce849 100644 --- a/exercises/concept/log-levels/.docs/hints.md +++ b/exercises/concept/log-levels/.docs/hints.md @@ -7,12 +7,12 @@ ## 1. Get message from a log line - Different options to search for text in a string are explored in [this tutorial][tutorial-search-text-in-string]. -- How to split strings can be seen [here][tutorial-split-strings] +- How to split strings can be seen in [this tutorial][tutorial-split-strings] - Removing white space is [built-in][tutorial-trim-white-space]. ## 2. Get log level from a log line -- Changing a `String`'s casing is explored [here][tutorial-changing-case-upper] and [here][tutorial-changing-case-lower]. +- Changing a `String`'s casing is explored in [this changing to upper case][tutorial-changing-case-upper] and [this changing to lower case][tutorial-changing-case-lower] tutorial. ## 3. Reformat a log line diff --git a/exercises/concept/log-levels/build.gradle b/exercises/concept/log-levels/build.gradle index 1344305f7..dd3862eb9 100644 --- a/exercises/concept/log-levels/build.gradle +++ b/exercises/concept/log-levels/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/logs-logs-logs/build.gradle b/exercises/concept/logs-logs-logs/build.gradle index 1344305f7..dd3862eb9 100644 --- a/exercises/concept/logs-logs-logs/build.gradle +++ b/exercises/concept/logs-logs-logs/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/need-for-speed/build.gradle b/exercises/concept/need-for-speed/build.gradle index 1344305f7..dd3862eb9 100644 --- a/exercises/concept/need-for-speed/build.gradle +++ b/exercises/concept/need-for-speed/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/remote-control-competition/.docs/hints.md b/exercises/concept/remote-control-competition/.docs/hints.md index 1b48b5515..fbd4ebfa7 100644 --- a/exercises/concept/remote-control-competition/.docs/hints.md +++ b/exercises/concept/remote-control-competition/.docs/hints.md @@ -15,7 +15,7 @@ ## 3. Allow the production cars to be ranked - See [this discussion][sort] of sorting. -- See [here][comparable] for default comparison of objects. +- See [Comparable's Javadocs][comparable] for default comparison of objects. [interfaces]: https://docs.oracle.com/javase/tutorial/java/concepts/interface.html [sort]: https://docs.oracle.com/javase/7/docs/api/java/util/Collections.html diff --git a/exercises/concept/remote-control-competition/build.gradle b/exercises/concept/remote-control-competition/build.gradle index 1344305f7..dd3862eb9 100644 --- a/exercises/concept/remote-control-competition/build.gradle +++ b/exercises/concept/remote-control-competition/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/salary-calculator/.docs/hints.md b/exercises/concept/salary-calculator/.docs/hints.md index 1c74c7964..68df6d809 100644 --- a/exercises/concept/salary-calculator/.docs/hints.md +++ b/exercises/concept/salary-calculator/.docs/hints.md @@ -2,7 +2,7 @@ ## General -- Refer to examples [here][ternary-operator-first] and [here][ternary-operator-second] for guidance on using _ternary operators_. +- Refer to examples in [this article][ternary-operator-first] and [this one][ternary-operator-second] for guidance on using _ternary operators_. ## 1. Determine the salary multiplier diff --git a/exercises/concept/salary-calculator/build.gradle b/exercises/concept/salary-calculator/build.gradle index 1344305f7..dd3862eb9 100644 --- a/exercises/concept/salary-calculator/build.gradle +++ b/exercises/concept/salary-calculator/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/secrets/build.gradle b/exercises/concept/secrets/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/concept/secrets/build.gradle +++ b/exercises/concept/secrets/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/squeaky-clean/build.gradle b/exercises/concept/squeaky-clean/build.gradle index 1344305f7..dd3862eb9 100644 --- a/exercises/concept/squeaky-clean/build.gradle +++ b/exercises/concept/squeaky-clean/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/tim-from-marketing/build.gradle b/exercises/concept/tim-from-marketing/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/concept/tim-from-marketing/build.gradle +++ b/exercises/concept/tim-from-marketing/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/wizards-and-warriors-2/build.gradle b/exercises/concept/wizards-and-warriors-2/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/concept/wizards-and-warriors-2/build.gradle +++ b/exercises/concept/wizards-and-warriors-2/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/concept/wizards-and-warriors/.docs/hints.md b/exercises/concept/wizards-and-warriors/.docs/hints.md index ab6871b2f..d9d52fc58 100644 --- a/exercises/concept/wizards-and-warriors/.docs/hints.md +++ b/exercises/concept/wizards-and-warriors/.docs/hints.md @@ -13,7 +13,7 @@ The whole inheritance concept has a lot to do with the concepts around [overridi ## 2. Describe a Warrior - In Java, the `toString()` method is actually present inside the `Object` class (which is a superclass to all the classes in Java). - You can read more about it [here][object-class-java]. + You can read more about it at the official [Oracle documentation][object-class-java]. - To override this method inside your implementation class, you should have a method with same name i.e. `toString()` and same return type i.e. `String`. - The `toString()` method must be `public`. @@ -34,7 +34,7 @@ The whole inheritance concept has a lot to do with the concepts around [overridi ## 6. Describe a Wizard - In Java, the `toString()` method is actually present inside the `Object` class (which is a superclass to all the classes in Java). - You can read more about it [here][object-class-java]. + You can read more about it at the official [Oracle documentation][object-class-java]. - To override this method inside your implementation class, you should have a method with same name i.e. `toString()` and same return type i.e. `String`. - The `toString()` method must be `public`. diff --git a/exercises/concept/wizards-and-warriors/build.gradle b/exercises/concept/wizards-and-warriors/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/concept/wizards-and-warriors/build.gradle +++ b/exercises/concept/wizards-and-warriors/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/acronym/build.gradle b/exercises/practice/acronym/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/acronym/build.gradle +++ b/exercises/practice/acronym/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/affine-cipher/.docs/instructions.md b/exercises/practice/affine-cipher/.docs/instructions.md index f6329db93..1603dbbce 100644 --- a/exercises/practice/affine-cipher/.docs/instructions.md +++ b/exercises/practice/affine-cipher/.docs/instructions.md @@ -20,7 +20,7 @@ Where: - `i` is the letter's index from `0` to the length of the alphabet - 1. - `m` is the length of the alphabet. - For the Roman alphabet `m` is `26`. + For the Latin alphabet `m` is `26`. - `a` and `b` are integers which make up the encryption key. Values `a` and `m` must be _coprime_ (or, _relatively prime_) for automatic decryption to succeed, i.e., they have number `1` as their only common factor (more information can be found in the [Wikipedia article about coprime integers][coprime-integers]). diff --git a/exercises/practice/affine-cipher/build.gradle b/exercises/practice/affine-cipher/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/affine-cipher/build.gradle +++ b/exercises/practice/affine-cipher/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/all-your-base/build.gradle b/exercises/practice/all-your-base/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/all-your-base/build.gradle +++ b/exercises/practice/all-your-base/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/allergies/build.gradle b/exercises/practice/allergies/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/allergies/build.gradle +++ b/exercises/practice/allergies/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/alphametics/build.gradle b/exercises/practice/alphametics/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/alphametics/build.gradle +++ b/exercises/practice/alphametics/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/anagram/.docs/instructions.md b/exercises/practice/anagram/.docs/instructions.md index a7298485b..dca24f526 100644 --- a/exercises/practice/anagram/.docs/instructions.md +++ b/exercises/practice/anagram/.docs/instructions.md @@ -1,13 +1,12 @@ # Instructions -Your task is to, given a target word and a set of candidate words, to find the subset of the candidates that are anagrams of the target. +Given a target word and one or more candidate words, your task is to find the candidates that are anagrams of the target. An anagram is a rearrangement of letters to form a new word: for example `"owns"` is an anagram of `"snow"`. A word is _not_ its own anagram: for example, `"stop"` is not an anagram of `"stop"`. -The target and candidates are words of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`). -Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `StoP` is not an anagram of `sTOp`. -The anagram set is the subset of the candidate set that are anagrams of the target (in any order). -Words in the anagram set should have the same letter case as in the candidate set. +The target word and candidate words are made up of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`). +Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `"StoP"` is not an anagram of `"sTOp"`. +The words you need to find should be taken from the candidate words, using the same letter case. -Given the target `"stone"` and candidates `"stone"`, `"tones"`, `"banana"`, `"tons"`, `"notes"`, `"Seton"`, the anagram set is `"tones"`, `"notes"`, `"Seton"`. +Given the target `"stone"` and the candidate words `"stone"`, `"tones"`, `"banana"`, `"tons"`, `"notes"`, and `"Seton"`, the anagram words you need to find are `"tones"`, `"notes"`, and `"Seton"`. diff --git a/exercises/practice/anagram/build.gradle b/exercises/practice/anagram/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/anagram/build.gradle +++ b/exercises/practice/anagram/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/armstrong-numbers/build.gradle b/exercises/practice/armstrong-numbers/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/armstrong-numbers/build.gradle +++ b/exercises/practice/armstrong-numbers/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/atbash-cipher/build.gradle b/exercises/practice/atbash-cipher/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/atbash-cipher/build.gradle +++ b/exercises/practice/atbash-cipher/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/bank-account/build.gradle b/exercises/practice/bank-account/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/bank-account/build.gradle +++ b/exercises/practice/bank-account/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/binary-search-tree/.docs/instructions.md b/exercises/practice/binary-search-tree/.docs/instructions.md index c9bbba5b9..7625220e9 100644 --- a/exercises/practice/binary-search-tree/.docs/instructions.md +++ b/exercises/practice/binary-search-tree/.docs/instructions.md @@ -19,29 +19,52 @@ All data in the left subtree is less than or equal to the current node's data, a For example, if we had a node containing the data 4, and we added the data 2, our tree would look like this: +![A graph with root node 4 and a single child node 2.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2.svg) + +```text 4 / 2 +``` If we then added 6, it would look like this: +![A graph with root node 4 and two child nodes 2 and 6.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2-6.svg) + +```text 4 / \ 2 6 +``` If we then added 3, it would look like this +![A graph with root node 4, two child nodes 2 and 6, and a grandchild node 3.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2-6-3.svg) + +```text 4 / \ 2 6 \ 3 +``` And if we then added 1, 5, and 7, it would look like this +![A graph with root node 4, two child nodes 2 and 6, and four grandchild nodes 1, 3, 5 and 7.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2-6-1-3-5-7.svg) + +```text 4 / \ / \ 2 6 / \ / \ 1 3 5 7 +``` + +## Credit + +The images were created by [habere-et-dispertire][habere-et-dispertire] using [PGF/TikZ][pgf-tikz] by Till Tantau. + +[habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire +[pgf-tikz]: https://en.wikipedia.org/wiki/PGF/TikZ diff --git a/exercises/practice/binary-search-tree/build.gradle b/exercises/practice/binary-search-tree/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/binary-search-tree/build.gradle +++ b/exercises/practice/binary-search-tree/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/binary-search/build.gradle b/exercises/practice/binary-search/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/binary-search/build.gradle +++ b/exercises/practice/binary-search/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/bob/.approaches/config.json b/exercises/practice/bob/.approaches/config.json index d7a8ba83b..14e2d8764 100644 --- a/exercises/practice/bob/.approaches/config.json +++ b/exercises/practice/bob/.approaches/config.json @@ -2,13 +2,30 @@ "introduction": { "authors": [ "bobahop" + ], + "contributors": [ + "jagdish-15", + "kahgoh" ] }, "approaches": [ + { + "uuid": "6ca5c7c0-f8f1-49b2-b137-951fa39f89eb", + "slug": "method-based-if-statements", + "title": "method-based if statements", + "blurb": "Use if statements to return the answer with the help of methods.", + "authors": [ + "jagdish-15" + ], + "contributors": [ + "BenjaminGale", + "kahgoh" + ] + }, { "uuid": "323eb230-7f27-4301-88ea-19c39d3eb5b6", - "slug": "if-statements", - "title": "if statements", + "slug": "variable-based-if-statements", + "title": "variable-based if statements", "blurb": "Use if statements to return the answer.", "authors": [ "bobahop" diff --git a/exercises/practice/bob/.approaches/introduction.md b/exercises/practice/bob/.approaches/introduction.md index f7b7389b2..96575876e 100644 --- a/exercises/practice/bob/.approaches/introduction.md +++ b/exercises/practice/bob/.approaches/introduction.md @@ -1,30 +1,65 @@ # Introduction -There are various idiomatic approaches to solve Bob. -A basic approach can use a series of `if` statements to test the conditions. -An array can contain answers from which the right response is selected by an index calculated from scores given to the conditions. +In this exercise, we’re working on a program to determine Bob’s responses based on the tone and style of given messages. +Bob responds differently depending on whether a message is a question, a shout, both, or silence. +Various approaches can be used to implement this logic efficiently and cleanly, ensuring the code remains readable and easy to maintain. ## General guidance -Regardless of the approach used, some things you could look out for include +When implementing your solution, consider the following tips to keep your code optimized and idiomatic: -- If the input is trimmed, [`trim()`][trim] only once. +- **Trim the Input Once**: Use [`trim()`][trim] only once at the start to remove any unnecessary whitespace. +- **Use Built-in Methods**: For checking if a message is a question, prefer [`endsWith("?")`][endswith] instead of manually checking the last character. +- **Single Determinations**: Use variables for `questioning` and `shouting` rather than calling these checks multiple times to improve efficiency. +- **DRY Code**: Avoid duplicating code by combining the logic for determining a shout and a question when handling shouted questions. Following the [DRY][dry] principle helps maintain clear and maintainable code. +- **Return Statements**: An early return in an `if` statement eliminates the need for additional `else` blocks, making the code more readable. +- **Curly Braces**: While optional for single-line statements, some teams may require them for readability and consistency. -- Use the [`endsWith()`][endswith] `String` method instead of checking the last character by index for `?`. +## Approach: method-based `if` statements -- Don't copy/paste the logic for determining a shout and for determining a question into determining a shouted question. - Combine the two determinations instead of copying them. - Not duplicating the code will keep the code [DRY][dry]. +```java +class Bob { + String hey(String input) { + var inputTrimmed = input.trim(); + + if (isSilent(inputTrimmed)) { + return "Fine. Be that way!"; + } + if (isShouting(inputTrimmed) && isQuestioning(inputTrimmed)) { + return "Calm down, I know what I'm doing!"; + } + if (isShouting(inputTrimmed)) { + return "Whoa, chill out!"; + } + if (isQuestioning(inputTrimmed)) { + return "Sure."; + } + + return "Whatever."; + } -- Perhaps consider making `questioning` and `shouting` values set once instead of functions that are possibly called twice. + private boolean isShouting(String input) { + return input.chars() + .anyMatch(Character::isLetter) && + input.chars() + .filter(Character::isLetter) + .allMatch(Character::isUpperCase); + } -- If an `if` statement can return, then an `else if` or `else` is not needed. - Execution will either return or will continue to the next statement anyway. + private boolean isQuestioning(String input) { + return input.endsWith("?"); + } -- If the body of an `if` statement is only one line, curly braces aren't needed. - Some teams may still require them in their style guidelines, though. + private boolean isSilent(String input) { + return input.length() == 0; + } +} +``` + +This approach defines helper methods for each type of message—silent, shouting, and questioning—to keep each condition clean and easily testable. +For more details, refer to the [method-based `if` Statements Approach][approach-method-if]. -## Approach: `if` statements +## Approach: variable-based `if` statements ```java import java.util.function.Predicate; @@ -56,7 +91,8 @@ class Bob { } ``` -For more information, check the [`if` statements approach][approach-if]. +This approach uses variables to avoid rechecking whether Bob is silent, shouting or questioning. +For more details, refer to the [variable-based `if` Statements Approach][approach-variable-if]. ## Approach: answer array @@ -86,16 +122,22 @@ class Bob { } ``` -For more information, check the [Answer array approach][approach-answer-array]. +This approach uses an array of answers and calculates the appropriate index based on flags for shouting and questioning. +For more details, refer to the [Answer Array Approach][approach-answer-array]. + +## Which Approach to Use? + +The choice between the **Method-Based `if` Statements Approach**, **Variable-Based `if` Statements Approach**, and the **Answer Array Approach** depends on readability, maintainability, and efficiency: -## Which approach to use? +- **Method-Based `if` Statements Approach**: This is clear and easy to follow but checks conditions multiple times, potentially affecting performance. Storing results in variables like `questioning` and `shouting` can improve efficiency but may reduce clarity slightly. +- **Variable-Based `if` Statements Approach**: This approach can be more efficient by avoiding redundant checks, but its nested structure can reduce readability and maintainability. +- **Answer Array Approach**: Efficient and compact, this method uses an array of responses based on flags for questioning and shouting. However, it may be less intuitive and harder to modify if more responses are needed. -Since benchmarking with the [Java Microbenchmark Harness][jmh] is currently outside the scope of this document, -the choice between `if` statements and answers array can be made by perceived readability. +Each approach offers a balance between readability and performance, with trade-offs in flexibility and clarity. [trim]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#trim() [endswith]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#endsWith(java.lang.String) [dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself -[approach-if]: https://exercism.org/tracks/java/exercises/bob/approaches/if-statements +[approach-method-if]: https://exercism.org/tracks/java/exercises/bob/approaches/method-based-if-statements +[approach-variable-if]: https://exercism.org/tracks/java/exercises/bob/approaches/variable-based-if-statements [approach-answer-array]: https://exercism.org/tracks/java/exercises/bob/approaches/answer-array -[jmh]: https://github.com/openjdk/jmh diff --git a/exercises/practice/bob/.approaches/method-based-if-statements/content.md b/exercises/practice/bob/.approaches/method-based-if-statements/content.md new file mode 100644 index 000000000..93c95160b --- /dev/null +++ b/exercises/practice/bob/.approaches/method-based-if-statements/content.md @@ -0,0 +1,99 @@ +# Method-Based `if` statements + +```java +class Bob { + String hey(String input) { + var inputTrimmed = input.trim(); + + if (isSilent(inputTrimmed)) { + return "Fine. Be that way!"; + } + if (isShouting(inputTrimmed) && isQuestioning(inputTrimmed)) { + return "Calm down, I know what I'm doing!"; + } + if (isShouting(inputTrimmed)) { + return "Whoa, chill out!"; + } + if (isQuestioning(inputTrimmed)) { + return "Sure."; + } + + return "Whatever."; + } + + private boolean isShouting(String input) { + return input.chars() + .anyMatch(Character::isLetter) && + input.chars() + .filter(Character::isLetter) + .allMatch(Character::isUpperCase); + } + + private boolean isQuestioning(String input) { + return input.endsWith("?"); + } + + private boolean isSilent(String input) { + return input.length() == 0; + } +} +``` + +In this approach, the different conditions for Bob’s responses are separated into dedicated private methods within the `Bob` class. +This method-based approach improves readability and modularity by organizing each condition check into its own method, making the main response method easier to understand and maintain. + +## Explanation + +This approach simplifies the main method `hey` by breaking down each response condition into helper methods: + +### Trimming the Input + +The `input` is trimmed using the `String` [`trim()`][trim] method to remove any leading or trailing whitespace. +This helps to accurately detect if the input is empty and should prompt a `"Fine. Be that way!"` response. + +~~~~exercism/caution +Note that a `null` `string` would be different from a `String` of all whitespace. +A `null` `String` would throw a `NullPointerException` if `trim()` were applied to it. +~~~~ + +### Delegating to Helper Methods + +Each condition is evaluated using the following helper methods: + +1. **`isSilent`**: Checks if the trimmed input has no characters. +2. **`isShouting`**: Checks if the input is all uppercase and contains at least one alphabetic character, indicating shouting. +3. **`isQuestioning`**: Verifies if the trimmed input ends with a question mark. + +This modular approach keeps each condition encapsulated, enhancing code clarity. + +### Order of Checks + +The order of checks within `hey` is important: + +1. Silence is evaluated first, as it requires an immediate response. +2. Shouted questions take precedence over individual checks for shouting and questioning. +3. Shouting comes next, requiring its response if not combined with a question. +4. Questioning (a non-shouted question) is checked afterward. + +This ordering ensures that Bob’s response matches the expected behavior without redundancy. + +## Shortening + +When the body of an `if` statement is a single line, both the test expression and the body _could_ be put on the same line, like so: + +```java +if (isSilent(inputTrimmed)) return "Fine. Be that way!"; +``` + +or the body _could_ be put on a separate line without curly braces: + +```java +if (isSilent(inputTrimmed)) + return "Fine. Be that way!"; +``` + +However, the [Java Coding Conventions][coding-conventions] advise always using curly braces for `if` statements, which helps to avoid errors. +Your team may choose to overrule them at its own risk. + +[trim]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#trim() +[coding-conventions]: https://www.oracle.com/java/technologies/javase/codeconventions-statements.html#449 diff --git a/exercises/practice/bob/.approaches/method-based-if-statements/snippet.txt b/exercises/practice/bob/.approaches/method-based-if-statements/snippet.txt new file mode 100644 index 000000000..4abcfa820 --- /dev/null +++ b/exercises/practice/bob/.approaches/method-based-if-statements/snippet.txt @@ -0,0 +1,8 @@ +if (isSilent(inputTrimmed)) + return "Fine. Be that way!"; +if (isShouting(inputTrimmed) && isQuestioning(inputTrimmed)) + return "Calm down, I know what I'm doing!"; +if (isShouting(inputTrimmed)) + return "Whoa, chill out!"; +if (isQuestioning(inputTrimmed)) + return "Sure."; \ No newline at end of file diff --git a/exercises/practice/bob/.approaches/if-statements/content.md b/exercises/practice/bob/.approaches/variable-based-if-statements/content.md similarity index 99% rename from exercises/practice/bob/.approaches/if-statements/content.md rename to exercises/practice/bob/.approaches/variable-based-if-statements/content.md index 4c3791fc1..d20648458 100644 --- a/exercises/practice/bob/.approaches/if-statements/content.md +++ b/exercises/practice/bob/.approaches/variable-based-if-statements/content.md @@ -1,4 +1,4 @@ -# `if` statements +# Variable-Based `if` statements ```java import java.util.function.Predicate; diff --git a/exercises/practice/bob/.approaches/if-statements/snippet.txt b/exercises/practice/bob/.approaches/variable-based-if-statements/snippet.txt similarity index 100% rename from exercises/practice/bob/.approaches/if-statements/snippet.txt rename to exercises/practice/bob/.approaches/variable-based-if-statements/snippet.txt diff --git a/exercises/practice/bob/build.gradle b/exercises/practice/bob/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/bob/build.gradle +++ b/exercises/practice/bob/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/book-store/build.gradle b/exercises/practice/book-store/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/book-store/build.gradle +++ b/exercises/practice/book-store/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/bottle-song/build.gradle b/exercises/practice/bottle-song/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/bottle-song/build.gradle +++ b/exercises/practice/bottle-song/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/bowling/build.gradle b/exercises/practice/bowling/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/bowling/build.gradle +++ b/exercises/practice/bowling/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/change/build.gradle b/exercises/practice/change/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/change/build.gradle +++ b/exercises/practice/change/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/circular-buffer/build.gradle b/exercises/practice/circular-buffer/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/circular-buffer/build.gradle +++ b/exercises/practice/circular-buffer/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/clock/build.gradle b/exercises/practice/clock/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/clock/build.gradle +++ b/exercises/practice/clock/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/collatz-conjecture/build.gradle b/exercises/practice/collatz-conjecture/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/collatz-conjecture/build.gradle +++ b/exercises/practice/collatz-conjecture/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/complex-numbers/build.gradle b/exercises/practice/complex-numbers/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/complex-numbers/build.gradle +++ b/exercises/practice/complex-numbers/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/connect/build.gradle b/exercises/practice/connect/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/connect/build.gradle +++ b/exercises/practice/connect/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/crypto-square/build.gradle b/exercises/practice/crypto-square/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/crypto-square/build.gradle +++ b/exercises/practice/crypto-square/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/custom-set/build.gradle b/exercises/practice/custom-set/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/custom-set/build.gradle +++ b/exercises/practice/custom-set/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/darts/build.gradle b/exercises/practice/darts/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/darts/build.gradle +++ b/exercises/practice/darts/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/diamond/build.gradle b/exercises/practice/diamond/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/diamond/build.gradle +++ b/exercises/practice/diamond/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/difference-of-squares/build.gradle b/exercises/practice/difference-of-squares/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/difference-of-squares/build.gradle +++ b/exercises/practice/difference-of-squares/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/dnd-character/build.gradle b/exercises/practice/dnd-character/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/dnd-character/build.gradle +++ b/exercises/practice/dnd-character/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/dominoes/build.gradle b/exercises/practice/dominoes/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/dominoes/build.gradle +++ b/exercises/practice/dominoes/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/dot-dsl/.docs/instructions.md b/exercises/practice/dot-dsl/.docs/instructions.md index b3a63996d..5e65ebef9 100644 --- a/exercises/practice/dot-dsl/.docs/instructions.md +++ b/exercises/practice/dot-dsl/.docs/instructions.md @@ -22,7 +22,7 @@ Write a Domain Specific Language similar to the Graphviz dot language. Our DSL is similar to the Graphviz dot language in that our DSL will be used to create graph data structures. However, unlike the DOT Language, our DSL will be an internal DSL for use only in our language. -More information about the difference between internal and external DSLs can be found [here][fowler-dsl]. +[Learn more about the difference between internal and external DSLs][fowler-dsl]. [dsl]: https://en.wikipedia.org/wiki/Domain-specific_language [dot-language]: https://en.wikipedia.org/wiki/DOT_(graph_description_language) diff --git a/exercises/practice/dot-dsl/build.gradle b/exercises/practice/dot-dsl/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/dot-dsl/build.gradle +++ b/exercises/practice/dot-dsl/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/eliuds-eggs/.docs/introduction.md b/exercises/practice/eliuds-eggs/.docs/introduction.md index 819897480..2b2e5c43d 100644 --- a/exercises/practice/eliuds-eggs/.docs/introduction.md +++ b/exercises/practice/eliuds-eggs/.docs/introduction.md @@ -58,7 +58,7 @@ The position information encoding is calculated as follows: ### Decimal number on the display -16 +8 ### Actual eggs in the coop diff --git a/exercises/practice/eliuds-eggs/build.gradle b/exercises/practice/eliuds-eggs/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/eliuds-eggs/build.gradle +++ b/exercises/practice/eliuds-eggs/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/error-handling/build.gradle b/exercises/practice/error-handling/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/error-handling/build.gradle +++ b/exercises/practice/error-handling/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/etl/build.gradle b/exercises/practice/etl/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/etl/build.gradle +++ b/exercises/practice/etl/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/flatten-array/.docs/instructions.append.md b/exercises/practice/flatten-array/.docs/instructions.append.md new file mode 100644 index 000000000..c5bf8cb0c --- /dev/null +++ b/exercises/practice/flatten-array/.docs/instructions.append.md @@ -0,0 +1,3 @@ +# Instructions append + +For the Java track, the input will be provided as a `List` instead of an array, and the output should also be a `List`. diff --git a/exercises/practice/flatten-array/.docs/instructions.md b/exercises/practice/flatten-array/.docs/instructions.md index 89dacfa32..b5b82713d 100644 --- a/exercises/practice/flatten-array/.docs/instructions.md +++ b/exercises/practice/flatten-array/.docs/instructions.md @@ -1,11 +1,16 @@ # Instructions -Take a nested list and return a single flattened list with all values except nil/null. +Take a nested array of any depth and return a fully flattened array. -The challenge is to take an arbitrarily-deep nested list-like structure and produce a flattened structure without any nil/null values. +Note that some language tracks may include null-like values in the input array, and the way these values are represented varies by track. +Such values should be excluded from the flattened array. -For example: +Additionally, the input may be of a different data type and contain different types, depending on the track. -input: [1,[2,3,null,4],[null],5] +Check the test suite for details. -output: [1,2,3,4,5] +## Example + +input: `[1, [2, 6, null], [[null, [4]], 5]]` + +output: `[1, 2, 6, 4, 5]` diff --git a/exercises/practice/flatten-array/.docs/introduction.md b/exercises/practice/flatten-array/.docs/introduction.md new file mode 100644 index 000000000..a31485746 --- /dev/null +++ b/exercises/practice/flatten-array/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +A shipment of emergency supplies has arrived, but there's a problem. +To protect from damage, the items — flashlights, first-aid kits, blankets — are packed inside boxes, and some of those boxes are nested several layers deep inside other boxes! + +To be prepared for an emergency, everything must be easily accessible in one box. +Can you unpack all the supplies and place them into a single box, so they're ready when needed most? diff --git a/exercises/practice/flatten-array/.meta/tests.toml b/exercises/practice/flatten-array/.meta/tests.toml index 6300219d7..44acf175d 100644 --- a/exercises/practice/flatten-array/.meta/tests.toml +++ b/exercises/practice/flatten-array/.meta/tests.toml @@ -32,12 +32,32 @@ description = "null values are omitted from the final result" [c6cf26de-8ccd-4410-84bd-b9efd88fd2bc] description = "consecutive null values at the front of the list are omitted from the final result" +include = false + +[bc72da10-5f55-4ada-baf3-50e4da02ec8e] +description = "consecutive null values at the front of the array are omitted from the final result" +reimplements = "c6cf26de-8ccd-4410-84bd-b9efd88fd2bc" [382c5242-587e-4577-b8ce-a5fb51e385a1] description = "consecutive null values in the middle of the list are omitted from the final result" +include = false + +[6991836d-0d9b-4703-80a0-3f1f23eb5981] +description = "consecutive null values in the middle of the array are omitted from the final result" +reimplements = "382c5242-587e-4577-b8ce-a5fb51e385a1" [ef1d4790-1b1e-4939-a179-51ace0829dbd] description = "6 level nest list with null values" +include = false + +[dc90a09c-5376-449c-a7b3-c2d20d540069] +description = "6 level nested array with null values" +reimplements = "ef1d4790-1b1e-4939-a179-51ace0829dbd" [85721643-705a-4150-93ab-7ae398e2942d] description = "all values in nested list are null" +include = false + +[51f5d9af-8f7f-4fb5-a156-69e8282cb275] +description = "all values in nested array are null" +reimplements = "85721643-705a-4150-93ab-7ae398e2942d" diff --git a/exercises/practice/flatten-array/build.gradle b/exercises/practice/flatten-array/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/flatten-array/build.gradle +++ b/exercises/practice/flatten-array/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/flower-field/.docs/instructions.md b/exercises/practice/flower-field/.docs/instructions.md new file mode 100644 index 000000000..bbdae0c2c --- /dev/null +++ b/exercises/practice/flower-field/.docs/instructions.md @@ -0,0 +1,26 @@ +# Instructions + +Your task is to add flower counts to empty squares in a completed Flower Field garden. +The garden itself is a rectangle board composed of squares that are either empty (`' '`) or a flower (`'*'`). + +For each empty square, count the number of flowers adjacent to it (horizontally, vertically, diagonally). +If the empty square has no adjacent flowers, leave it empty. +Otherwise replace it with the count of adjacent flowers. + +For example, you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): + +```text +·*·*· +··*·· +··*·· +····· +``` + +Which your code should transform into this: + +```text +1*3*1 +13*31 +·2*2· +·111· +``` diff --git a/exercises/practice/flower-field/.docs/introduction.md b/exercises/practice/flower-field/.docs/introduction.md new file mode 100644 index 000000000..af9b61536 --- /dev/null +++ b/exercises/practice/flower-field/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +[Flower Field][history] is a compassionate reimagining of the popular game Minesweeper. +The object of the game is to find all the flowers in the garden using numeric hints that indicate how many flowers are directly adjacent (horizontally, vertically, diagonally) to a square. +"Flower Field" shipped in regional versions of Microsoft Windows in Italy, Germany, South Korea, Japan and Taiwan. + +[history]: https://web.archive.org/web/20020409051321fw_/http://rcm.usr.dsi.unimi.it/rcmweb/fnm/ diff --git a/exercises/practice/flower-field/.meta/config.json b/exercises/practice/flower-field/.meta/config.json new file mode 100644 index 000000000..54dc83896 --- /dev/null +++ b/exercises/practice/flower-field/.meta/config.json @@ -0,0 +1,41 @@ +{ + "authors": [ + "stkent" + ], + "contributors": [ + "aadityakulkarni", + "BNAndras", + "FridaTveit", + "hgvanpariya", + "jmrunkle", + "jsertel", + "kytrinyx", + "lemoncurry", + "matthewmorgan", + "matthewstyler", + "morrme", + "msomji", + "muzimuzhi", + "redshirt4", + "SleeplessByte", + "Smarticles101", + "sshine", + "vivshaw", + "Zaldrick" + ], + "files": { + "solution": [ + "src/main/java/FlowerFieldBoard.java" + ], + "test": [ + "src/test/java/FlowerFieldBoardTest.java" + ], + "example": [ + ".meta/src/reference/java/FlowerFieldBoard.java" + ], + "invalidator": [ + "build.gradle" + ] + }, + "blurb": "Mark all the flowers in a garden." +} diff --git a/exercises/practice/flower-field/.meta/src/reference/java/FlowerFieldBoard.java b/exercises/practice/flower-field/.meta/src/reference/java/FlowerFieldBoard.java new file mode 100644 index 000000000..0fa97e128 --- /dev/null +++ b/exercises/practice/flower-field/.meta/src/reference/java/FlowerFieldBoard.java @@ -0,0 +1,75 @@ +import java.util.ArrayList; +import java.util.List; + +final class FlowerFieldBoard { + + private static final char FLOWER_CHAR = '*'; + + private static final char SPACE_CHAR = ' '; + + private final List rawRepresentation; + + private final int numberOfRows; + + private final int numberOfColumns; + + FlowerFieldBoard(final List rawRepresentation) { + this.rawRepresentation = rawRepresentation; + this.numberOfRows = rawRepresentation.size(); + this.numberOfColumns = rawRepresentation.isEmpty() ? 0 : rawRepresentation.get(0).length(); + } + + List withNumbers() { + final List result = new ArrayList<>(); + + for (int rowNumber = 0; rowNumber < numberOfRows; rowNumber++) { + result.add(getRowWithNumbers(rowNumber)); + } + + return result; + } + + private String getRowWithNumbers(final int rowNumber) { + StringBuilder result = new StringBuilder(numberOfColumns); + + for (int columnNumber = 0; columnNumber < numberOfColumns; columnNumber++) { + result.append(getCellNumber(rowNumber, columnNumber)); + } + + return result.toString(); + } + + private char getCellNumber(final int rowNumber, final int columnNumber) { + // If (rowNumber, columnNumber) is a flower, we're done. + if (rawRepresentation.get(rowNumber).charAt(columnNumber) == FLOWER_CHAR) { + return FLOWER_CHAR; + } + + final int flowerCount = computeFlowerCountAround(rowNumber, columnNumber); + + // If computed count is positive, add it to the annotated row. Otherwise, add a blank space. + return flowerCount > 0 ? Character.forDigit(flowerCount, 10) : SPACE_CHAR; + } + + private int computeFlowerCountAround(final int rowNumber, final int columnNumber) { + int result = 0; + + // Compute row and column ranges to inspect (respecting board edges). + final int minRowToInspect = Math.max(rowNumber - 1, 0); + final int maxRowToInspect = Math.min(rowNumber + 1, numberOfRows - 1); + final int minColToInspect = Math.max(columnNumber - 1, 0); + final int maxColToInspect = Math.min(columnNumber + 1, numberOfColumns - 1); + + // Count flowers in the cells surrounding (row, col). + for (int rowToInspect = minRowToInspect; rowToInspect <= maxRowToInspect; rowToInspect++) { + for (int colToInspect = minColToInspect; colToInspect <= maxColToInspect; colToInspect++) { + if (rawRepresentation.get(rowToInspect).charAt(colToInspect) == FLOWER_CHAR) { + result += 1; + } + } + } + + return result; + } + +} diff --git a/exercises/practice/flower-field/.meta/tests.toml b/exercises/practice/flower-field/.meta/tests.toml new file mode 100644 index 000000000..c2b24fdaf --- /dev/null +++ b/exercises/practice/flower-field/.meta/tests.toml @@ -0,0 +1,46 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[237ff487-467a-47e1-9b01-8a891844f86c] +description = "no rows" + +[4b4134ec-e20f-439c-a295-664c38950ba1] +description = "no columns" + +[d774d054-bbad-4867-88ae-069cbd1c4f92] +description = "no flowers" + +[225176a0-725e-43cd-aa13-9dced501f16e] +description = "garden full of flowers" + +[3f345495-f1a5-4132-8411-74bd7ca08c49] +description = "flower surrounded by spaces" + +[6cb04070-4199-4ef7-a6fa-92f68c660fca] +description = "space surrounded by flowers" + +[272d2306-9f62-44fe-8ab5-6b0f43a26338] +description = "horizontal line" + +[c6f0a4b2-58d0-4bf6-ad8d-ccf4144f1f8e] +description = "horizontal line, flowers at edges" + +[a54e84b7-3b25-44a8-b8cf-1753c8bb4cf5] +description = "vertical line" + +[b40f42f5-dec5-4abc-b167-3f08195189c1] +description = "vertical line, flowers at edges" + +[58674965-7b42-4818-b930-0215062d543c] +description = "cross" + +[dd9d4ca8-9e68-4f78-a677-a2a70fd7a7b8] +description = "large garden" diff --git a/exercises/practice/flower-field/build.gradle b/exercises/practice/flower-field/build.gradle new file mode 100644 index 000000000..d28f35dee --- /dev/null +++ b/exercises/practice/flower-field/build.gradle @@ -0,0 +1,25 @@ +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" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" +} + +test { + useJUnitPlatform() + + testLogging { + exceptionFormat = "full" + showStandardStreams = true + events = ["passed", "failed", "skipped"] + } +} diff --git a/exercises/practice/flower-field/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/flower-field/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..e6441136f Binary files /dev/null and b/exercises/practice/flower-field/gradle/wrapper/gradle-wrapper.jar differ diff --git a/exercises/practice/flower-field/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/flower-field/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..2deab89d5 --- /dev/null +++ b/exercises/practice/flower-field/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/exercises/practice/flower-field/gradlew b/exercises/practice/flower-field/gradlew new file mode 100755 index 000000000..1aa94a426 --- /dev/null +++ b/exercises/practice/flower-field/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/exercises/practice/flower-field/gradlew.bat b/exercises/practice/flower-field/gradlew.bat new file mode 100644 index 000000000..25da30dbd --- /dev/null +++ b/exercises/practice/flower-field/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/exercises/practice/flower-field/src/main/java/FlowerFieldBoard.java b/exercises/practice/flower-field/src/main/java/FlowerFieldBoard.java new file mode 100644 index 000000000..db059db34 --- /dev/null +++ b/exercises/practice/flower-field/src/main/java/FlowerFieldBoard.java @@ -0,0 +1,13 @@ +import java.util.List; + +class FlowerFieldBoard { + + FlowerFieldBoard(List boardRows) { + throw new UnsupportedOperationException("Delete this statement and write your own implementation."); + } + + List withNumbers() { + throw new UnsupportedOperationException("Delete this statement and write your own implementation."); + } + +} \ No newline at end of file diff --git a/exercises/practice/flower-field/src/test/java/FlowerFieldBoardTest.java b/exercises/practice/flower-field/src/test/java/FlowerFieldBoardTest.java new file mode 100644 index 000000000..518b6344b --- /dev/null +++ b/exercises/practice/flower-field/src/test/java/FlowerFieldBoardTest.java @@ -0,0 +1,241 @@ +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class FlowerFieldBoardTest { + + @Test + public void testInputBoardWithNoRowsAndNoColumns() { + List inputBoard = Collections.emptyList(); + List expectedNumberedBoard = Collections.emptyList(); + List actualNumberedBoard = new FlowerFieldBoard(inputBoard).withNumbers(); + + assertThat(actualNumberedBoard).isEqualTo(expectedNumberedBoard); + } + + @Disabled("Remove to run test") + @Test + public void testInputBoardWithOneRowAndNoColumns() { + List inputBoard = Collections.singletonList(""); + List expectedNumberedBoard = Collections.singletonList(""); + List actualNumberedBoard = new FlowerFieldBoard(inputBoard).withNumbers(); + + assertThat(actualNumberedBoard).isEqualTo(expectedNumberedBoard); + } + + @Disabled("Remove to run test") + @Test + public void testInputBoardWithNoFlowers() { + List inputBoard = Arrays.asList( + " ", + " ", + " " + ); + + List expectedNumberedBoard = Arrays.asList( + " ", + " ", + " " + ); + + List actualNumberedBoard = new FlowerFieldBoard(inputBoard).withNumbers(); + + assertThat(actualNumberedBoard).isEqualTo(expectedNumberedBoard); + } + + @Disabled("Remove to run test") + @Test + public void testInputBoardWithOnlyFlowers() { + List inputBoard = Arrays.asList( + "***", + "***", + "***" + ); + + List expectedNumberedBoard = Arrays.asList( + "***", + "***", + "***" + ); + + List actualNumberedBoard = new FlowerFieldBoard(inputBoard).withNumbers(); + + assertThat(actualNumberedBoard).isEqualTo(expectedNumberedBoard); + } + + @Disabled("Remove to run test") + @Test + public void testInputBoardWithSingleFlowerAtCenter() { + List inputBoard = Arrays.asList( + " ", + " * ", + " " + ); + + List expectedNumberedBoard = Arrays.asList( + "111", + "1*1", + "111" + ); + + List actualNumberedBoard = new FlowerFieldBoard(inputBoard).withNumbers(); + + assertThat(actualNumberedBoard).isEqualTo(expectedNumberedBoard); + } + + @Disabled("Remove to run test") + @Test + public void testInputBoardWithFlowersAroundPerimeter() { + List inputBoard = Arrays.asList( + "***", + "* *", + "***" + ); + + List expectedNumberedBoard = Arrays.asList( + "***", + "*8*", + "***" + ); + + List actualNumberedBoard = new FlowerFieldBoard(inputBoard).withNumbers(); + + assertThat(actualNumberedBoard).isEqualTo(expectedNumberedBoard); + } + + @Disabled("Remove to run test") + @Test + public void testInputBoardWithSingleRowAndTwoFlowers() { + List inputBoard = Collections.singletonList( + " * * " + ); + + List expectedNumberedBoard = Collections.singletonList( + "1*2*1" + ); + + List actualNumberedBoard = new FlowerFieldBoard(inputBoard).withNumbers(); + + assertThat(actualNumberedBoard).isEqualTo(expectedNumberedBoard); + } + + @Disabled("Remove to run test") + @Test + public void testInputBoardWithSingleRowAndTwoFlowersAtEdges() { + List inputBoard = Collections.singletonList( + "* *" + ); + + List expectedNumberedBoard = Collections.singletonList( + "*1 1*" + ); + + List actualNumberedBoard = new FlowerFieldBoard(inputBoard).withNumbers(); + + assertThat(actualNumberedBoard).isEqualTo(expectedNumberedBoard); + } + + @Disabled("Remove to run test") + @Test + public void testInputBoardWithSingleColumnAndTwoFlowers() { + List inputBoard = Arrays.asList( + " ", + "*", + " ", + "*", + " " + ); + + List expectedNumberedBoard = Arrays.asList( + "1", + "*", + "2", + "*", + "1" + ); + + List actualNumberedBoard = new FlowerFieldBoard(inputBoard).withNumbers(); + + assertThat(actualNumberedBoard).isEqualTo(expectedNumberedBoard); + } + + @Disabled("Remove to run test") + @Test + public void testInputBoardWithSingleColumnAndTwoFlowersAtEdges() { + List inputBoard = Arrays.asList( + "*", + " ", + " ", + " ", + "*" + ); + + List expectedNumberedBoard = Arrays.asList( + "*", + "1", + " ", + "1", + "*" + ); + + List actualNumberedBoard = new FlowerFieldBoard(inputBoard).withNumbers(); + + assertThat(actualNumberedBoard).isEqualTo(expectedNumberedBoard); + } + + @Disabled("Remove to run test") + @Test + public void testInputBoardWithFlowersInCross() { + List inputBoard = Arrays.asList( + " * ", + " * ", + "*****", + " * ", + " * " + ); + + List expectedNumberedBoard = Arrays.asList( + " 2*2 ", + "25*52", + "*****", + "25*52", + " 2*2 " + ); + + List actualNumberedBoard = new FlowerFieldBoard(inputBoard).withNumbers(); + + assertThat(actualNumberedBoard).isEqualTo(expectedNumberedBoard); + } + + @Disabled("Remove to run test") + @Test + public void testLargeInputBoard() { + List inputBoard = Arrays.asList( + " * * ", + " * ", + " * ", + " * *", + " * * ", + " " + ); + + List expectedNumberedBoard = Arrays.asList( + "1*22*1", + "12*322", + " 123*2", + "112*4*", + "1*22*2", + "111111" + ); + + List actualNumberedBoard = new FlowerFieldBoard(inputBoard).withNumbers(); + + assertThat(actualNumberedBoard).isEqualTo(expectedNumberedBoard); + } + +} diff --git a/exercises/practice/food-chain/build.gradle b/exercises/practice/food-chain/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/food-chain/build.gradle +++ b/exercises/practice/food-chain/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/forth/build.gradle b/exercises/practice/forth/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/forth/build.gradle +++ b/exercises/practice/forth/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/game-of-life/build.gradle b/exercises/practice/game-of-life/build.gradle index 1344305f7..dd3862eb9 100644 --- a/exercises/practice/game-of-life/build.gradle +++ b/exercises/practice/game-of-life/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/gigasecond/build.gradle b/exercises/practice/gigasecond/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/gigasecond/build.gradle +++ b/exercises/practice/gigasecond/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/go-counting/build.gradle b/exercises/practice/go-counting/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/go-counting/build.gradle +++ b/exercises/practice/go-counting/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/grade-school/build.gradle b/exercises/practice/grade-school/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/grade-school/build.gradle +++ b/exercises/practice/grade-school/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/grains/.docs/instructions.md b/exercises/practice/grains/.docs/instructions.md index df479fc0a..f5b752a81 100644 --- a/exercises/practice/grains/.docs/instructions.md +++ b/exercises/practice/grains/.docs/instructions.md @@ -1,15 +1,11 @@ # Instructions -Calculate the number of grains of wheat on a chessboard given that the number on each square doubles. +Calculate the number of grains of wheat on a chessboard. -There once was a wise servant who saved the life of a prince. -The king promised to pay whatever the servant could dream up. -Knowing that the king loved chess, the servant told the king he would like to have grains of wheat. -One grain on the first square of a chess board, with the number of grains doubling on each successive square. +A chessboard has 64 squares. +Square 1 has one grain, square 2 has two grains, square 3 has four grains, and so on, doubling each time. -There are 64 squares on a chessboard (where square 1 has one grain, square 2 has two grains, and so on). +Write code that calculates: -Write code that shows: - -- how many grains were on a given square, and +- the number of grains on a given square - the total number of grains on the chessboard diff --git a/exercises/practice/grains/.docs/introduction.md b/exercises/practice/grains/.docs/introduction.md new file mode 100644 index 000000000..0df4f46f7 --- /dev/null +++ b/exercises/practice/grains/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +There once was a wise servant who saved the life of a prince. +The king promised to pay whatever the servant could dream up. +Knowing that the king loved chess, the servant told the king he would like to have grains of wheat. +One grain on the first square of a chessboard, with the number of grains doubling on each successive square. diff --git a/exercises/practice/grains/.meta/config.json b/exercises/practice/grains/.meta/config.json index 611ddb233..671ae4999 100644 --- a/exercises/practice/grains/.meta/config.json +++ b/exercises/practice/grains/.meta/config.json @@ -26,5 +26,5 @@ }, "blurb": "Calculate the number of grains of wheat on a chessboard given that the number on each square doubles.", "source": "The CodeRanch Cattle Drive, Assignment 6", - "source_url": "https://coderanch.com/wiki/718824/Grains" + "source_url": "https://web.archive.org/web/20240908084142/https://coderanch.com/wiki/718824/Grains" } diff --git a/exercises/practice/grains/build.gradle b/exercises/practice/grains/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/grains/build.gradle +++ b/exercises/practice/grains/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/grep/build.gradle b/exercises/practice/grep/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/grep/build.gradle +++ b/exercises/practice/grep/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/hamming/build.gradle b/exercises/practice/hamming/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/hamming/build.gradle +++ b/exercises/practice/hamming/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/hello-world/.docs/instructions.append.md b/exercises/practice/hello-world/.docs/instructions.append.md index f70c88daa..f1580cb34 100644 --- a/exercises/practice/hello-world/.docs/instructions.append.md +++ b/exercises/practice/hello-world/.docs/instructions.append.md @@ -112,13 +112,13 @@ Seeing both kinds can be instructive and interesting. Mentors review submitted solutions for the track they've chosen to mentor to help users learn as much as possible. -To read more about mentoring, go to the following [link][Mentoring]. +To read more about mentoring, go to the [Exercism docs][Mentoring]. ### Contribute to Exercism The entire of Exercism is Open Source and is a labor of love for over 100 maintainers and many more contributors. -A starting point to jumping in and becoming a contributor can be found [here][Contributing]. +A starting point to jumping in and becoming a contributor can be found in the [Exercism contributing guide][Contributing]. [Tutorial]: #tutorial [Introduction]: #introduction diff --git a/exercises/practice/hello-world/build.gradle b/exercises/practice/hello-world/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/hello-world/build.gradle +++ b/exercises/practice/hello-world/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/high-scores/build.gradle b/exercises/practice/high-scores/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/high-scores/build.gradle +++ b/exercises/practice/high-scores/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/house/build.gradle b/exercises/practice/house/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/house/build.gradle +++ b/exercises/practice/house/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/isbn-verifier/build.gradle b/exercises/practice/isbn-verifier/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/isbn-verifier/build.gradle +++ b/exercises/practice/isbn-verifier/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/isogram/build.gradle b/exercises/practice/isogram/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/isogram/build.gradle +++ b/exercises/practice/isogram/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/killer-sudoku-helper/build.gradle b/exercises/practice/killer-sudoku-helper/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/killer-sudoku-helper/build.gradle +++ b/exercises/practice/killer-sudoku-helper/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/kindergarten-garden/build.gradle b/exercises/practice/kindergarten-garden/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/kindergarten-garden/build.gradle +++ b/exercises/practice/kindergarten-garden/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/knapsack/.docs/instructions.append.md b/exercises/practice/knapsack/.docs/instructions.append.md deleted file mode 100644 index 67e2260d7..000000000 --- a/exercises/practice/knapsack/.docs/instructions.append.md +++ /dev/null @@ -1,4 +0,0 @@ -# Instructions append - -- Use recursion -- Check if there are any overlapping subproblems whose results can be cached diff --git a/exercises/practice/knapsack/build.gradle b/exercises/practice/knapsack/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/knapsack/build.gradle +++ b/exercises/practice/knapsack/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/largest-series-product/build.gradle b/exercises/practice/largest-series-product/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/largest-series-product/build.gradle +++ b/exercises/practice/largest-series-product/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/leap/.meta/config.json b/exercises/practice/leap/.meta/config.json index 234e5a6b5..25541b165 100644 --- a/exercises/practice/leap/.meta/config.json +++ b/exercises/practice/leap/.meta/config.json @@ -25,5 +25,5 @@ }, "blurb": "Determine whether a given year is a leap year.", "source": "CodeRanch Cattle Drive, Assignment 3", - "source_url": "https://coderanch.com/t/718816/Leap" + "source_url": "https://web.archive.org/web/20240907033714/https://coderanch.com/t/718816/Leap" } diff --git a/exercises/practice/leap/.meta/design.md b/exercises/practice/leap/.meta/design.md index ba1f404b6..6ef49b284 100644 --- a/exercises/practice/leap/.meta/design.md +++ b/exercises/practice/leap/.meta/design.md @@ -6,7 +6,8 @@ This exercise could benefit from the following rules in the [analyzer]: - `essential`: Verify that the solution does not use `java.time.Year.isLeap(int)` or `new java.util.GregorianCalendar().isLeapYear(int)`. - `essential`: Verify that the solution does not contain hard-coded years used in the tests. -- `actionable`: If the solution uses conditional statements like `if/else` or ternary expressions, instruct the student to use simple boolean logic instead. +- `actionable`: If the solution uses `if/else` statements, instruct the student to use simple boolean logic or a single ternary operator instead. +- `actionable`: If the solution uses more than 1 ternary operator, instruct the student that their solution can be simplified. - `actionable`: If the solution contains more than 3 checks, instruct the student that their solution can be simplified. [analyzer]: https://github.com/exercism/java-analyzer diff --git a/exercises/practice/leap/build.gradle b/exercises/practice/leap/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/leap/build.gradle +++ b/exercises/practice/leap/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/ledger/build.gradle b/exercises/practice/ledger/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/ledger/build.gradle +++ b/exercises/practice/ledger/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/linked-list/build.gradle b/exercises/practice/linked-list/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/linked-list/build.gradle +++ b/exercises/practice/linked-list/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/list-ops/build.gradle b/exercises/practice/list-ops/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/list-ops/build.gradle +++ b/exercises/practice/list-ops/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md index 5bbf007b0..df2e304a3 100644 --- a/exercises/practice/luhn/.docs/instructions.md +++ b/exercises/practice/luhn/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Determine whether a credit card number is valid according to the [Luhn formula][luhn]. +Determine whether a number is valid according to the [Luhn formula][luhn]. The number will be provided as a string. @@ -10,54 +10,59 @@ Strings of length 1 or less are not valid. Spaces are allowed in the input, but they should be stripped before checking. All other non-digit characters are disallowed. -### Example 1: valid credit card number +## Examples -```text -4539 3195 0343 6467 -``` +### Valid credit card number -The first step of the Luhn algorithm is to double every second digit, starting from the right. -We will be doubling +The number to be checked is `4539 3195 0343 6467`. + +The first step of the Luhn algorithm is to start at the end of the number and double every second digit, beginning with the second digit from the right and moving left. ```text 4539 3195 0343 6467 ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ (double these) ``` -If doubling the number results in a number greater than 9 then subtract 9 from the product. -The results of our doubling: +If the result of doubling a digit is greater than 9, we subtract 9 from that result. +We end up with: ```text 8569 6195 0383 3437 ``` -Then sum all of the digits: +Finally, we sum all digits. +If the sum is evenly divisible by 10, the original number is valid. ```text -8+5+6+9+6+1+9+5+0+3+8+3+3+4+3+7 = 80 +8 + 5 + 6 + 9 + 6 + 1 + 9 + 5 + 0 + 3 + 8 + 3 + 3 + 4 + 3 + 7 = 80 ``` -If the sum is evenly divisible by 10, then the number is valid. -This number is valid! +80 is evenly divisible by 10, so number `4539 3195 0343 6467` is valid! + +### Invalid Canadian SIN + +The number to be checked is `066 123 468`. -### Example 2: invalid credit card number +We start at the end of the number and double every second digit, beginning with the second digit from the right and moving left. ```text -8273 1232 7352 0569 +066 123 478 + ↑ ↑ ↑ ↑ (double these) ``` -Double the second digits, starting from the right +If the result of doubling a digit is greater than 9, we subtract 9 from that result. +We end up with: ```text -7253 2262 5312 0539 +036 226 458 ``` -Sum the digits +We sum the digits: ```text -7+2+5+3+2+2+6+2+5+3+1+2+0+5+3+9 = 57 +0 + 3 + 6 + 2 + 2 + 6 + 4 + 5 + 8 = 36 ``` -57 is not evenly divisible by 10, so this number is not valid. +36 is not evenly divisible by 10, so number `066 123 478` is not valid! [luhn]: https://en.wikipedia.org/wiki/Luhn_algorithm diff --git a/exercises/practice/luhn/.docs/introduction.md b/exercises/practice/luhn/.docs/introduction.md index ec2bd709d..dee48006e 100644 --- a/exercises/practice/luhn/.docs/introduction.md +++ b/exercises/practice/luhn/.docs/introduction.md @@ -2,10 +2,10 @@ At the Global Verification Authority, you've just been entrusted with a critical assignment. Across the city, from online purchases to secure logins, countless operations rely on the accuracy of numerical identifiers like credit card numbers, bank account numbers, transaction codes, and tracking IDs. -The Luhn algorithm is a simple checksum formula used to ensure these numbers are valid and error-free. +The Luhn algorithm is a simple checksum formula used to help identify mistyped numbers. A batch of identifiers has just arrived on your desk. All of them must pass the Luhn test to ensure they're legitimate. -If any fail, they'll be flagged as invalid, preventing errors or fraud, such as incorrect transactions or unauthorized access. +If any fail, they'll be flagged as invalid, preventing mistakes such as incorrect transactions or failed account verifications. Can you ensure this is done right? The integrity of many services depends on you. diff --git a/exercises/practice/luhn/build.gradle b/exercises/practice/luhn/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/luhn/build.gradle +++ b/exercises/practice/luhn/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/markdown/build.gradle b/exercises/practice/markdown/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/markdown/build.gradle +++ b/exercises/practice/markdown/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/matching-brackets/build.gradle b/exercises/practice/matching-brackets/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/matching-brackets/build.gradle +++ b/exercises/practice/matching-brackets/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/matrix/build.gradle b/exercises/practice/matrix/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/matrix/build.gradle +++ b/exercises/practice/matrix/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/mazy-mice/build.gradle b/exercises/practice/mazy-mice/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/mazy-mice/build.gradle +++ b/exercises/practice/mazy-mice/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/meetup/.docs/instructions.md b/exercises/practice/meetup/.docs/instructions.md index 000de2fd1..8b1bda5eb 100644 --- a/exercises/practice/meetup/.docs/instructions.md +++ b/exercises/practice/meetup/.docs/instructions.md @@ -2,7 +2,7 @@ Your task is to find the exact date of a meetup, given a month, year, weekday and week. -There are five week values to consider: `first`, `second`, `third`, `fourth`, `last`, `teenth`. +There are six week values to consider: `first`, `second`, `third`, `fourth`, `last`, `teenth`. For example, you might be asked to find the date for the meetup on the first Monday in January 2018 (January 1, 2018). diff --git a/exercises/practice/meetup/build.gradle b/exercises/practice/meetup/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/meetup/build.gradle +++ b/exercises/practice/meetup/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/micro-blog/build.gradle b/exercises/practice/micro-blog/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/micro-blog/build.gradle +++ b/exercises/practice/micro-blog/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/minesweeper/build.gradle b/exercises/practice/minesweeper/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/minesweeper/build.gradle +++ b/exercises/practice/minesweeper/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/nth-prime/build.gradle b/exercises/practice/nth-prime/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/nth-prime/build.gradle +++ b/exercises/practice/nth-prime/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/nucleotide-count/build.gradle b/exercises/practice/nucleotide-count/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/nucleotide-count/build.gradle +++ b/exercises/practice/nucleotide-count/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/ocr-numbers/build.gradle b/exercises/practice/ocr-numbers/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/ocr-numbers/build.gradle +++ b/exercises/practice/ocr-numbers/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/palindrome-products/build.gradle b/exercises/practice/palindrome-products/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/palindrome-products/build.gradle +++ b/exercises/practice/palindrome-products/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/pangram/build.gradle b/exercises/practice/pangram/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/pangram/build.gradle +++ b/exercises/practice/pangram/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/parallel-letter-frequency/build.gradle b/exercises/practice/parallel-letter-frequency/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/parallel-letter-frequency/build.gradle +++ b/exercises/practice/parallel-letter-frequency/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/pascals-triangle/build.gradle b/exercises/practice/pascals-triangle/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/pascals-triangle/build.gradle +++ b/exercises/practice/pascals-triangle/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/perfect-numbers/build.gradle b/exercises/practice/perfect-numbers/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/perfect-numbers/build.gradle +++ b/exercises/practice/perfect-numbers/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/phone-number/.docs/instructions.md b/exercises/practice/phone-number/.docs/instructions.md index 62ba48e96..5d4d3739f 100644 --- a/exercises/practice/phone-number/.docs/instructions.md +++ b/exercises/practice/phone-number/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Clean up user-entered phone numbers so that they can be sent SMS messages. +Clean up phone numbers so that they can be sent SMS messages. The **North American Numbering Plan (NANP)** is a telephone numbering system used by many countries in North America like the United States, Canada or Bermuda. All NANP-countries share the same international country code: `1`. diff --git a/exercises/practice/phone-number/build.gradle b/exercises/practice/phone-number/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/phone-number/build.gradle +++ b/exercises/practice/phone-number/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/piecing-it-together/.docs/instructions.md b/exercises/practice/piecing-it-together/.docs/instructions.md new file mode 100644 index 000000000..c0c966592 --- /dev/null +++ b/exercises/practice/piecing-it-together/.docs/instructions.md @@ -0,0 +1,41 @@ +# Instructions + +Given partial information about a jigsaw puzzle, add the missing pieces. + +If the information is insufficient to complete the details, or if given parts are in contradiction, the user should be notified. + +The full information about the jigsaw puzzle contains the following parts: + +- `pieces`: Total number of pieces +- `border`: Number of border pieces +- `inside`: Number of inside (non-border) pieces +- `rows`: Number of rows +- `columns`: Number of columns +- `aspectRatio`: Aspect ratio of columns to rows +- `format`: Puzzle format, which can be `portrait`, `square`, or `landscape` + +For this exercise, you may assume square pieces, so that the format can be derived from the aspect ratio: + +- If the aspect ratio is less than 1, it's `portrait` +- If it is equal to 1, it's `square` +- If it is greater than 1, it's `landscape` + +## Three examples + +### Portrait + +A portrait jigsaw puzzle with 6 pieces, all of which are border pieces and none are inside pieces. It has 3 rows and 2 columns. The aspect ratio is 1.5 (3/2). + +![A 2 by 3 jigsaw puzzle](https://assets.exercism.org/images/exercises/piecing-it-together/jigsaw-puzzle-2x3.svg) + +### Square + +A square jigsaw puzzle with 9 pieces, all of which are border pieces except for the one in the center, which is an inside piece. It has 3 rows and 3 columns. The aspect ratio is 1 (3/3). + +![A 3 by 3 jigsaw puzzle](https://assets.exercism.org/images/exercises/piecing-it-together/jigsaw-puzzle-3x3.svg) + +### Landscape + +A landscape jigsaw puzzle with 12 pieces, 10 of which are border pieces and 2 are inside pieces. It has 3 rows and 4 columns. The aspect ratio is 1.333333... (4/3). + +![A 4 by 3 jigsaw puzzle](https://assets.exercism.org/images/exercises/piecing-it-together/jigsaw-puzzle-4x3.svg) diff --git a/exercises/practice/piecing-it-together/.docs/introduction.md b/exercises/practice/piecing-it-together/.docs/introduction.md new file mode 100644 index 000000000..2fa20f6c5 --- /dev/null +++ b/exercises/practice/piecing-it-together/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +Your best friend has started collecting jigsaw puzzles and wants to build a detailed catalog of their collection — recording information such as the total number of pieces, the number of rows and columns, the number of border and inside pieces, aspect ratio, and puzzle format. +Even with your powers combined, it takes multiple hours to solve a single jigsaw puzzle with one thousand pieces — and then you still need to count the rows and columns and calculate the remaining numbers manually. +The even larger puzzles with thousands of pieces look quite daunting now. +"There has to be a better way!" you exclaim and sit down in front of your computer to solve the problem. diff --git a/exercises/practice/piecing-it-together/.meta/config.json b/exercises/practice/piecing-it-together/.meta/config.json new file mode 100644 index 000000000..76e6e6cbf --- /dev/null +++ b/exercises/practice/piecing-it-together/.meta/config.json @@ -0,0 +1,22 @@ +{ + "authors": [ + "zamora-carlos" + ], + "files": { + "solution": [ + "src/main/java/PiecingItTogether.java" + ], + "test": [ + "src/test/java/PiecingItTogetherTest.java" + ], + "example": [ + ".meta/src/reference/java/PiecingItTogether.java" + ], + "editor": [ + "src/main/java/JigsawInfo.java" + ] + }, + "blurb": "Fill in missing jigsaw puzzle details from partial data", + "source": "atk just started another 1000-pieces jigsaw puzzle when this idea hit him", + "source_url": "https://github.com/exercism/problem-specifications/pull/2554" +} diff --git a/exercises/practice/piecing-it-together/.meta/src/reference/java/JigsawInfo.java b/exercises/practice/piecing-it-together/.meta/src/reference/java/JigsawInfo.java new file mode 100644 index 000000000..e08eb8909 --- /dev/null +++ b/exercises/practice/piecing-it-together/.meta/src/reference/java/JigsawInfo.java @@ -0,0 +1,140 @@ +import java.util.Objects; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; + +/** + * Represents partial or complete information about a jigsaw puzzle, + * + * NOTE: There is no need to change this file and is treated as read only by the Exercism test runners. + */ +public class JigsawInfo { + private final OptionalInt pieces; + private final OptionalInt border; + private final OptionalInt inside; + private final OptionalInt rows; + private final OptionalInt columns; + private final OptionalDouble aspectRatio; + private final Optional format; + + private JigsawInfo(Builder builder) { + this.pieces = builder.pieces; + this.border = builder.border; + this.inside = builder.inside; + this.rows = builder.rows; + this.columns = builder.columns; + this.aspectRatio = builder.aspectRatio; + this.format = builder.format; + } + + public static class Builder { + private OptionalInt pieces = OptionalInt.empty(); + private OptionalInt border = OptionalInt.empty(); + private OptionalInt inside = OptionalInt.empty(); + private OptionalInt rows = OptionalInt.empty(); + private OptionalInt columns = OptionalInt.empty(); + private OptionalDouble aspectRatio = OptionalDouble.empty(); + private Optional format = Optional.empty(); + + public Builder pieces(int pieces) { + this.pieces = OptionalInt.of(pieces); + return this; + } + + public Builder border(int border) { + this.border = OptionalInt.of(border); + return this; + } + + public Builder inside(int inside) { + this.inside = OptionalInt.of(inside); + return this; + } + + public Builder rows(int rows) { + this.rows = OptionalInt.of(rows); + return this; + } + + public Builder columns(int columns) { + this.columns = OptionalInt.of(columns); + return this; + } + + public Builder aspectRatio(double aspectRatio) { + this.aspectRatio = OptionalDouble.of(aspectRatio); + return this; + } + + public Builder format(String format) { + this.format = Optional.of(format); + return this; + } + + public JigsawInfo build() { + return new JigsawInfo(this); + } + } + + public OptionalInt getPieces() { + return pieces; + } + + public OptionalInt getBorder() { + return border; + } + + public OptionalInt getInside() { + return inside; + } + + public OptionalInt getRows() { + return rows; + } + + public OptionalInt getColumns() { + return columns; + } + + public OptionalDouble getAspectRatio() { + return aspectRatio; + } + + public Optional getFormat() { + return format; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + + JigsawInfo that = (JigsawInfo) o; + return Objects.equals(pieces, that.pieces) + && Objects.equals(border, that.border) + && Objects.equals(inside, that.inside) + && Objects.equals(rows, that.rows) + && Objects.equals(columns, that.columns) + && Objects.equals(aspectRatio, that.aspectRatio) + && Objects.equals(format, that.format); + } + + @Override + public int hashCode() { + return Objects.hash(pieces, border, inside, rows, columns, aspectRatio, format); + } + + @Override + public String toString() { + return "JigsawInfo{" + + "pieces=" + pieces + + ", border=" + border + + ", inside=" + inside + + ", rows=" + rows + + ", columns=" + columns + + ", aspectRatio=" + aspectRatio + + ", format=" + format + + '}'; + } +} diff --git a/exercises/practice/piecing-it-together/.meta/src/reference/java/PiecingItTogether.java b/exercises/practice/piecing-it-together/.meta/src/reference/java/PiecingItTogether.java new file mode 100644 index 000000000..ddbb91dc8 --- /dev/null +++ b/exercises/practice/piecing-it-together/.meta/src/reference/java/PiecingItTogether.java @@ -0,0 +1,135 @@ +import java.util.*; + +public class PiecingItTogether { + private static final double DOUBLE_EQUALITY_TOLERANCE = 1e-9; + private static final int MAX_DIMENSION = 1000; + + public static JigsawInfo getCompleteInformation(JigsawInfo input) { + List validGuesses = new ArrayList<>(); + + if (input.getPieces().isPresent()) { + // If pieces is known, we only test divisors of pieces + int pieces = input.getPieces().getAsInt(); + for (int rows = 1; rows <= pieces; rows++) { + if (pieces % rows != 0) { + continue; + } + int columns = pieces / rows; + createValidJigsaw(rows, columns, input).ifPresent(validGuesses::add); + } + } else if (input.getInside().isPresent() && input.getInside().getAsInt() > 0) { + // If inside pieces is non-zero, we only test divisors of inside + int inside = input.getInside().getAsInt(); + for (int innerRows = 1; innerRows <= inside; innerRows++) { + if (inside % innerRows != 0) { + continue; + } + int innerColumns = inside / innerRows; + createValidJigsaw(innerRows + 2, innerColumns + 2, input).ifPresent(validGuesses::add); + } + } else { + // Brute force using border constraint if available + int maxDimension = input.getBorder().isPresent() + ? Math.min(input.getBorder().getAsInt(), MAX_DIMENSION) + : MAX_DIMENSION; + + for (int rows = 1; rows <= maxDimension; rows++) { + for (int columns = 1; columns <= maxDimension; columns++) { + createValidJigsaw(rows, columns, input).ifPresent(validGuesses::add); + } + } + } + + if (validGuesses.size() == 1) { + return validGuesses.get(0); + } else if (validGuesses.size() > 1) { + throw new IllegalArgumentException("Insufficient data"); + } else { + throw new IllegalArgumentException("Contradictory data"); + } + } + + private static String getFormatFromAspect(double aspectRatio) { + if (Math.abs(aspectRatio - 1.0) < DOUBLE_EQUALITY_TOLERANCE) { + return "square"; + } else if (aspectRatio < 1.0) { + return "portrait"; + } else { + return "landscape"; + } + } + + private static JigsawInfo fromRowsAndCols(int rows, int columns) { + int pieces = rows * columns; + int border = (rows == 1 || columns == 1) ? pieces : 2 * (rows + columns - 2); + int inside = pieces - border; + double aspectRatio = (double) columns / rows; + String format = getFormatFromAspect(aspectRatio); + + return new JigsawInfo.Builder() + .pieces(pieces) + .border(border) + .inside(inside) + .rows(rows) + .columns(columns) + .aspectRatio(aspectRatio) + .format(format) + .build(); + } + + /** + * Verifies that all known values in the input match those in the computed result. + * Returns false if any known value conflicts, true if all values are consistent. + * + * @param computed the fully inferred jigsaw information + * @param input the original partial input with possibly empty values + * @return true if all values are consistent, false if any conflict exists + */ + private static boolean isConsistent(JigsawInfo computed, JigsawInfo input) { + return valuesMatch(computed.getPieces(), input.getPieces()) && + valuesMatch(computed.getBorder(), input.getBorder()) && + valuesMatch(computed.getInside(), input.getInside()) && + valuesMatch(computed.getRows(), input.getRows()) && + valuesMatch(computed.getColumns(), input.getColumns()) && + valuesMatch(computed.getAspectRatio(), input.getAspectRatio()) && + valuesMatch(computed.getFormat(), input.getFormat()); + } + + /** + * Attempts to construct a valid jigsaw configuration using the specified number of rows and columns. + * Returns a valid result only if the configuration is consistent with the input. + * + * @param rows number of rows to try + * @param columns number of columns to try + * @param input the original input to check for consistency + * @return an Optional containing a valid configuration, or empty if inconsistent + */ + private static Optional createValidJigsaw(int rows, int columns, JigsawInfo input) { + JigsawInfo candidate = fromRowsAndCols(rows, columns); + return isConsistent(candidate, input) ? Optional.of(candidate) : Optional.empty(); + } + + private static boolean valuesMatch(Optional a, Optional b) { + if (a.isPresent() && b.isPresent()) { + return Objects.equals(a.get(), b.get()); + } + + return true; + } + + private static boolean valuesMatch(OptionalInt a, OptionalInt b) { + if (a.isPresent() && b.isPresent()) { + return a.getAsInt() == b.getAsInt(); + } + + return true; + } + + private static boolean valuesMatch(OptionalDouble a, OptionalDouble b) { + if (a.isPresent() && b.isPresent()) { + return Double.compare(a.getAsDouble(), b.getAsDouble()) == 0; + } + + return true; + } +} diff --git a/exercises/practice/piecing-it-together/.meta/tests.toml b/exercises/practice/piecing-it-together/.meta/tests.toml new file mode 100644 index 000000000..f462c15a3 --- /dev/null +++ b/exercises/practice/piecing-it-together/.meta/tests.toml @@ -0,0 +1,31 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[ad626f23-09a2-4f5f-ba22-eec0671fa2a9] +description = "1000 pieces puzzle with 1.6 aspect ratio" + +[3e0c5919-3561-42f5-b9ed-26d70c20214e] +description = "square puzzle with 32 rows" + +[1126f160-b094-4dc2-bf37-13e36e394867] +description = "400 pieces square puzzle with only inside pieces and aspect ratio" + +[a9743178-5642-4cc0-8fdb-00d6b031c3a0] +description = "1500 pieces landscape puzzle with 30 rows and 1.6 aspect ratio" + +[f6378369-989c-497f-a6e2-f30b1fa76cba] +description = "300 pieces portrait puzzle with 70 border pieces" + +[f53f82ba-5663-4c7e-9e86-57fdbb3e53d2] +description = "puzzle with insufficient data" + +[a3d5c31a-cc74-44bf-b4fc-9e4d65f1ac1a] +description = "puzzle with contradictory data" diff --git a/exercises/practice/piecing-it-together/build.gradle b/exercises/practice/piecing-it-together/build.gradle new file mode 100644 index 000000000..dd3862eb9 --- /dev/null +++ b/exercises/practice/piecing-it-together/build.gradle @@ -0,0 +1,25 @@ +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" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" +} + +test { + useJUnitPlatform() + + testLogging { + exceptionFormat = "full" + showStandardStreams = true + events = ["passed", "failed", "skipped"] + } +} diff --git a/exercises/practice/piecing-it-together/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/piecing-it-together/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..e6441136f Binary files /dev/null and b/exercises/practice/piecing-it-together/gradle/wrapper/gradle-wrapper.jar differ diff --git a/exercises/practice/piecing-it-together/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/piecing-it-together/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..2deab89d5 --- /dev/null +++ b/exercises/practice/piecing-it-together/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/exercises/practice/piecing-it-together/gradlew b/exercises/practice/piecing-it-together/gradlew new file mode 100755 index 000000000..1aa94a426 --- /dev/null +++ b/exercises/practice/piecing-it-together/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/exercises/practice/piecing-it-together/gradlew.bat b/exercises/practice/piecing-it-together/gradlew.bat new file mode 100644 index 000000000..93e3f59f1 --- /dev/null +++ b/exercises/practice/piecing-it-together/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/exercises/practice/piecing-it-together/src/main/java/JigsawInfo.java b/exercises/practice/piecing-it-together/src/main/java/JigsawInfo.java new file mode 100644 index 000000000..e08eb8909 --- /dev/null +++ b/exercises/practice/piecing-it-together/src/main/java/JigsawInfo.java @@ -0,0 +1,140 @@ +import java.util.Objects; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; + +/** + * Represents partial or complete information about a jigsaw puzzle, + * + * NOTE: There is no need to change this file and is treated as read only by the Exercism test runners. + */ +public class JigsawInfo { + private final OptionalInt pieces; + private final OptionalInt border; + private final OptionalInt inside; + private final OptionalInt rows; + private final OptionalInt columns; + private final OptionalDouble aspectRatio; + private final Optional format; + + private JigsawInfo(Builder builder) { + this.pieces = builder.pieces; + this.border = builder.border; + this.inside = builder.inside; + this.rows = builder.rows; + this.columns = builder.columns; + this.aspectRatio = builder.aspectRatio; + this.format = builder.format; + } + + public static class Builder { + private OptionalInt pieces = OptionalInt.empty(); + private OptionalInt border = OptionalInt.empty(); + private OptionalInt inside = OptionalInt.empty(); + private OptionalInt rows = OptionalInt.empty(); + private OptionalInt columns = OptionalInt.empty(); + private OptionalDouble aspectRatio = OptionalDouble.empty(); + private Optional format = Optional.empty(); + + public Builder pieces(int pieces) { + this.pieces = OptionalInt.of(pieces); + return this; + } + + public Builder border(int border) { + this.border = OptionalInt.of(border); + return this; + } + + public Builder inside(int inside) { + this.inside = OptionalInt.of(inside); + return this; + } + + public Builder rows(int rows) { + this.rows = OptionalInt.of(rows); + return this; + } + + public Builder columns(int columns) { + this.columns = OptionalInt.of(columns); + return this; + } + + public Builder aspectRatio(double aspectRatio) { + this.aspectRatio = OptionalDouble.of(aspectRatio); + return this; + } + + public Builder format(String format) { + this.format = Optional.of(format); + return this; + } + + public JigsawInfo build() { + return new JigsawInfo(this); + } + } + + public OptionalInt getPieces() { + return pieces; + } + + public OptionalInt getBorder() { + return border; + } + + public OptionalInt getInside() { + return inside; + } + + public OptionalInt getRows() { + return rows; + } + + public OptionalInt getColumns() { + return columns; + } + + public OptionalDouble getAspectRatio() { + return aspectRatio; + } + + public Optional getFormat() { + return format; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + + JigsawInfo that = (JigsawInfo) o; + return Objects.equals(pieces, that.pieces) + && Objects.equals(border, that.border) + && Objects.equals(inside, that.inside) + && Objects.equals(rows, that.rows) + && Objects.equals(columns, that.columns) + && Objects.equals(aspectRatio, that.aspectRatio) + && Objects.equals(format, that.format); + } + + @Override + public int hashCode() { + return Objects.hash(pieces, border, inside, rows, columns, aspectRatio, format); + } + + @Override + public String toString() { + return "JigsawInfo{" + + "pieces=" + pieces + + ", border=" + border + + ", inside=" + inside + + ", rows=" + rows + + ", columns=" + columns + + ", aspectRatio=" + aspectRatio + + ", format=" + format + + '}'; + } +} diff --git a/exercises/practice/piecing-it-together/src/main/java/PiecingItTogether.java b/exercises/practice/piecing-it-together/src/main/java/PiecingItTogether.java new file mode 100644 index 000000000..d0ba9fa81 --- /dev/null +++ b/exercises/practice/piecing-it-together/src/main/java/PiecingItTogether.java @@ -0,0 +1,5 @@ +public class PiecingItTogether { + public static JigsawInfo getCompleteInformation(JigsawInfo input) { + throw new UnsupportedOperationException("Delete this statement and write your own implementation."); + } +} diff --git a/exercises/practice/piecing-it-together/src/test/java/PiecingItTogetherTest.java b/exercises/practice/piecing-it-together/src/test/java/PiecingItTogetherTest.java new file mode 100644 index 000000000..b07648c0a --- /dev/null +++ b/exercises/practice/piecing-it-together/src/test/java/PiecingItTogetherTest.java @@ -0,0 +1,167 @@ +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class PiecingItTogetherTest { + private static final double DOUBLE_EQUALITY_TOLERANCE = 1e-9; + + @Test + public void test1000PiecesWithAspectRatio() { + JigsawInfo input = new JigsawInfo.Builder() + .pieces(1000) + .aspectRatio(1.6) + .build(); + + JigsawInfo expected = new JigsawInfo.Builder() + .pieces(1000) + .border(126) + .inside(874) + .rows(25) + .columns(40) + .aspectRatio(1.6) + .format("landscape") + .build(); + + JigsawInfo actual = PiecingItTogether.getCompleteInformation(input); + assertJigsawInfoEquals(actual, expected); + } + + @Disabled("Remove to run test") + @Test + public void testSquarePuzzleWith32Rows() { + JigsawInfo input = new JigsawInfo.Builder() + .rows(32) + .format("square") + .build(); + + JigsawInfo expected = new JigsawInfo.Builder() + .pieces(1024) + .border(124) + .inside(900) + .rows(32) + .columns(32) + .aspectRatio(1.0) + .format("square") + .build(); + + JigsawInfo actual = PiecingItTogether.getCompleteInformation(input); + assertJigsawInfoEquals(actual, expected); + } + + @Disabled("Remove to run test") + @Test + public void testInsideAndAspectRatioOnly() { + JigsawInfo input = new JigsawInfo.Builder() + .inside(324) + .aspectRatio(1.0) + .build(); + + JigsawInfo expected = new JigsawInfo.Builder() + .pieces(400) + .border(76) + .inside(324) + .rows(20) + .columns(20) + .aspectRatio(1.0) + .format("square") + .build(); + + JigsawInfo actual = PiecingItTogether.getCompleteInformation(input); + assertJigsawInfoEquals(actual, expected); + } + + @Disabled("Remove to run test") + @Test + public void testLandscape1500WithRowsAndAspect() { + JigsawInfo input = new JigsawInfo.Builder() + .rows(30) + .aspectRatio(1.6666666666666667) + .build(); + + JigsawInfo expected = new JigsawInfo.Builder() + .pieces(1500) + .border(156) + .inside(1344) + .rows(30) + .columns(50) + .aspectRatio(1.6666666666666667) + .format("landscape") + .build(); + + JigsawInfo actual = PiecingItTogether.getCompleteInformation(input); + assertJigsawInfoEquals(actual, expected); + } + + @Disabled("Remove to run test") + @Test + public void test300PiecesPortraitWithBorder() { + JigsawInfo input = new JigsawInfo.Builder() + .pieces(300) + .border(70) + .format("portrait") + .build(); + + JigsawInfo expected = new JigsawInfo.Builder() + .pieces(300) + .border(70) + .inside(230) + .rows(25) + .columns(12) + .aspectRatio(0.48) + .format("portrait") + .build(); + + JigsawInfo actual = PiecingItTogether.getCompleteInformation(input); + assertJigsawInfoEquals(actual, expected); + } + + @Disabled("Remove to run test") + @Test + public void testInsufficientData() { + JigsawInfo input = new JigsawInfo.Builder() + .pieces(1500) + .format("landscape") + .build(); + + assertThatThrownBy(() -> PiecingItTogether.getCompleteInformation(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Insufficient data"); + } + + @Disabled("Remove to run test") + @Test + public void testContradictoryData() { + JigsawInfo input = new JigsawInfo.Builder() + .rows(100) + .columns(1000) + .format("square") + .build(); + + assertThatThrownBy(() -> PiecingItTogether.getCompleteInformation(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Contradictory data"); + } + + /** + * Helper to compare two JigsawInfo objects, + * allowing a small tolerance when comparing aspect ratio doubles. + */ + private void assertJigsawInfoEquals(JigsawInfo actual, JigsawInfo expected) { + assertThat(actual.getPieces()).isEqualTo(expected.getPieces()); + assertThat(actual.getBorder()).isEqualTo(expected.getBorder()); + assertThat(actual.getInside()).isEqualTo(expected.getInside()); + assertThat(actual.getRows()).isEqualTo(expected.getRows()); + assertThat(actual.getColumns()).isEqualTo(expected.getColumns()); + assertThat(actual.getFormat()).isEqualTo(expected.getFormat()); + + Double actualAspect = actual.getAspectRatio() + .orElseThrow(() -> new AssertionError("Missing aspect ratio in actual result")); + Double expectedAspect = expected.getAspectRatio() + .orElseThrow(() -> new AssertionError("Missing aspect ratio in expected result")); + + assertThat(actualAspect).isCloseTo(expectedAspect, + org.assertj.core.api.Assertions.within(DOUBLE_EQUALITY_TOLERANCE)); + } +} diff --git a/exercises/practice/pig-latin/build.gradle b/exercises/practice/pig-latin/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/pig-latin/build.gradle +++ b/exercises/practice/pig-latin/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/poker/build.gradle b/exercises/practice/poker/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/poker/build.gradle +++ b/exercises/practice/poker/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/pov/build.gradle b/exercises/practice/pov/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/pov/build.gradle +++ b/exercises/practice/pov/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/prime-factors/build.gradle b/exercises/practice/prime-factors/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/prime-factors/build.gradle +++ b/exercises/practice/prime-factors/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/protein-translation/.docs/instructions.md b/exercises/practice/protein-translation/.docs/instructions.md index 44880802c..35c953b11 100644 --- a/exercises/practice/protein-translation/.docs/instructions.md +++ b/exercises/practice/protein-translation/.docs/instructions.md @@ -1,36 +1,17 @@ # Instructions -Translate RNA sequences into proteins. +Your job is to translate RNA sequences into proteins. -RNA can be broken into three-nucleotide sequences called codons, and then translated to a protein like so: +RNA strands are made up of three-nucleotide sequences called **codons**. +Each codon translates to an **amino acid**. +When joined together, those amino acids make a protein. -RNA: `"AUGUUUUCU"` => translates to - -Codons: `"AUG", "UUU", "UCU"` -=> which become a protein with the following sequence => - -Protein: `"Methionine", "Phenylalanine", "Serine"` - -There are 64 codons which in turn correspond to 20 amino acids; however, all of the codon sequences and resulting amino acids are not important in this exercise. -If it works for one codon, the program should work for all of them. -However, feel free to expand the list in the test suite to include them all. - -There are also three terminating codons (also known as 'STOP' codons); if any of these codons are encountered (by the ribosome), all translation ends and the protein is terminated. - -All subsequent codons after are ignored, like this: - -RNA: `"AUGUUUUCUUAAAUG"` => - -Codons: `"AUG", "UUU", "UCU", "UAA", "AUG"` => - -Protein: `"Methionine", "Phenylalanine", "Serine"` - -Note the stop codon `"UAA"` terminates the translation and the final methionine is not translated into the protein sequence. - -Below are the codons and resulting amino acids needed for the exercise. +In the real world, there are 64 codons, which in turn correspond to 20 amino acids. +However, for this exercise, you’ll only use a few of the possible 64. +They are listed below: | Codon | Amino Acid | -| :----------------- | :------------ | +| ------------------ | ------------- | | AUG | Methionine | | UUU, UUC | Phenylalanine | | UUA, UUG | Leucine | @@ -40,6 +21,18 @@ Below are the codons and resulting amino acids needed for the exercise. | UGG | Tryptophan | | UAA, UAG, UGA | STOP | +For example, the RNA string “AUGUUUUCU” has three codons: “AUG”, “UUU” and “UCU”. +These map to Methionine, Phenylalanine, and Serine. + +## “STOP” Codons + +You’ll note from the table above that there are three **“STOP” codons**. +If you encounter any of these codons, ignore the rest of the sequence — the protein is complete. + +For example, “AUGUUUUCUUAAAUG” contains a STOP codon (“UAA”). +Once we reach that point, we stop processing. +We therefore only consider the part before it (i.e. “AUGUUUUCU”), not any further codons after it (i.e. “AUG”). + Learn more about [protein translation on Wikipedia][protein-translation]. [protein-translation]: https://en.wikipedia.org/wiki/Translation_(biology) diff --git a/exercises/practice/protein-translation/build.gradle b/exercises/practice/protein-translation/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/protein-translation/build.gradle +++ b/exercises/practice/protein-translation/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/proverb/build.gradle b/exercises/practice/proverb/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/proverb/build.gradle +++ b/exercises/practice/proverb/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/pythagorean-triplet/.approaches/config.json b/exercises/practice/pythagorean-triplet/.approaches/config.json index d359d60a4..826288047 100644 --- a/exercises/practice/pythagorean-triplet/.approaches/config.json +++ b/exercises/practice/pythagorean-triplet/.approaches/config.json @@ -2,6 +2,10 @@ "introduction": { "authors": [ "bobahop" + ], + "contributors": [ + "BNAndras", + "jagdish-15" ] }, "approaches": [ diff --git a/exercises/practice/pythagorean-triplet/.approaches/introduction.md b/exercises/practice/pythagorean-triplet/.approaches/introduction.md index e3a5351a2..d67297e1a 100644 --- a/exercises/practice/pythagorean-triplet/.approaches/introduction.md +++ b/exercises/practice/pythagorean-triplet/.approaches/introduction.md @@ -171,6 +171,6 @@ So if the nested `for` loops approach is fast enough, it may be preferred for re [parallel]: https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html#parallel-- [flatmap]: https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html#flatMap-java.util.function.IntFunction- [filter]: https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html#filter-java.util.function.IntPredicate- -[approach-for-loops]: https://exercism.org/tracks/java/exercises/pythagorean-triplets/approaches/for-loops -[approach-intstream-parallel-flatmap-filter]: https://exercism.org/tracks/java/exercises/pythagorean-triplets/approaches/if-intstream-parallel-flatmap-filter +[approach-for-loops]: https://exercism.org/tracks/java/exercises/pythagorean-triplet/approaches/for-loops +[approach-intstream-parallel-flatmap-filter]: https://exercism.org/tracks/java/exercises/pythagorean-triplet/approaches/intstream-parallel-flatmap-filter [jmh]: https://github.com/openjdk/jmh diff --git a/exercises/practice/pythagorean-triplet/build.gradle b/exercises/practice/pythagorean-triplet/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/pythagorean-triplet/build.gradle +++ b/exercises/practice/pythagorean-triplet/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/queen-attack/build.gradle b/exercises/practice/queen-attack/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/queen-attack/build.gradle +++ b/exercises/practice/queen-attack/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/rail-fence-cipher/build.gradle b/exercises/practice/rail-fence-cipher/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/rail-fence-cipher/build.gradle +++ b/exercises/practice/rail-fence-cipher/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/raindrops/build.gradle b/exercises/practice/raindrops/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/raindrops/build.gradle +++ b/exercises/practice/raindrops/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/rational-numbers/build.gradle b/exercises/practice/rational-numbers/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/rational-numbers/build.gradle +++ b/exercises/practice/rational-numbers/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/react/build.gradle b/exercises/practice/react/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/react/build.gradle +++ b/exercises/practice/react/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/rectangles/build.gradle b/exercises/practice/rectangles/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/rectangles/build.gradle +++ b/exercises/practice/rectangles/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/relative-distance/.docs/instructions.md b/exercises/practice/relative-distance/.docs/instructions.md new file mode 100644 index 000000000..9046aee7c --- /dev/null +++ b/exercises/practice/relative-distance/.docs/instructions.md @@ -0,0 +1,39 @@ +# Instructions + +Your task is to determine the degree of separation between two individuals in a family tree. +This is similar to the pop culture idea that every Hollywood actor is [within six degrees of Kevin Bacon][six-bacons]. + +- You will be given an input, with all parent names and their children. +- Each name is unique, a child _can_ have one or two parents. +- The degree of separation is defined as the shortest number of connections from one person to another. +- If two individuals are not connected, return a value that represents "no known relationship." + Please see the test cases for the actual implementation. + +## Example + +Given the following family tree: + +```text + ┌──────────┐ ┌──────────┐ ┌───────────┐ + │ Helena │ │ Erdős ├─────┤ Shusaku │ + └───┬───┬──┘ └─────┬────┘ └────┬──────┘ + ┌───┘ └───────┐ └───────┬───────┘ +┌─────┴────┐ ┌────┴───┐ ┌─────┴────┐ +│ Isla ├─────┤ Tariq │ │ Kevin │ +└────┬─────┘ └────┬───┘ └──────────┘ + │ │ +┌────┴────┐ ┌────┴───┐ +│ Uma │ │ Morphy │ +└─────────┘ └────────┘ +``` + +The degree of separation between Tariq and Uma is 2 (Tariq → Isla → Uma). +There's no known relationship between Isla and Kevin, as there is no connection in the given data. +The degree of separation between Uma and Isla is 1. + +~~~~exercism/note +Isla and Tariq are siblings and have a separation of 1. +Similarly, this implementation would report a separation of 2 from you to your father's brother. +~~~~ + +[six-bacons]: https://en.m.wikipedia.org/wiki/Six_Degrees_of_Kevin_Bacon diff --git a/exercises/practice/relative-distance/.docs/introduction.md b/exercises/practice/relative-distance/.docs/introduction.md new file mode 100644 index 000000000..cb9fee6c7 --- /dev/null +++ b/exercises/practice/relative-distance/.docs/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +You've been hired to develop **Noble Knots**, the hottest new dating app for nobility! +With centuries of royal intermarriage, things have gotten… _complicated_. +To avoid any _oops-we're-twins_ situations, your job is to build a system that checks how closely two people are related. + +Noble Knots is inspired by Iceland's "[Islendinga-App][islendiga-app]," which is backed up by a database that traces all known family connections between Icelanders from the time of the settlement of Iceland. +Your algorithm will determine the **degree of separation** between two individuals in the royal family tree. + +Will your app help crown a perfect match? + +[islendiga-app]: http://www.islendingaapp.is/information-in-english/ diff --git a/exercises/practice/relative-distance/.meta/config.json b/exercises/practice/relative-distance/.meta/config.json new file mode 100644 index 000000000..c6b2cc422 --- /dev/null +++ b/exercises/practice/relative-distance/.meta/config.json @@ -0,0 +1,22 @@ +{ + "authors": [ + "BNAndras" + ], + "files": { + "solution": [ + "src/main/java/RelativeDistance.java" + ], + "test": [ + "src/test/java/RelativeDistanceTest.java" + ], + "example": [ + ".meta/src/reference/java/RelativeDistance.java" + ], + "invalidator": [ + "build.gradle" + ] + }, + "blurb": "Given a family tree, calculate the degree of separation.", + "source": "vaeng", + "source_url": "https://github.com/exercism/problem-specifications/pull/2537" +} diff --git a/exercises/practice/relative-distance/.meta/src/reference/java/RelativeDistance.java b/exercises/practice/relative-distance/.meta/src/reference/java/RelativeDistance.java new file mode 100644 index 000000000..3d170879b --- /dev/null +++ b/exercises/practice/relative-distance/.meta/src/reference/java/RelativeDistance.java @@ -0,0 +1,68 @@ +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; + +class RelativeDistance { + + private final Map> graph; + + RelativeDistance(Map> familyTree) { + final HashMap> connections = new HashMap<>(); + + for (Map.Entry> entry : familyTree.entrySet()) { + String parent = entry.getKey(); + List children = entry.getValue(); + + connections.putIfAbsent(parent, new HashSet<>()); + + for (String child : children) { + connections.putIfAbsent(child, new HashSet<>()); + + connections.get(parent).add(child); + connections.get(child).add(parent); + + for (String sibling : children) { + if (!sibling.equals(child)) { + connections.get(child).add(sibling); + } + } + } + } + + graph = connections; + } + + int degreeOfSeparation(String personA, String personB) { + if (!graph.containsKey(personA) || !graph.containsKey(personB)) { + return -1; + } + + Queue queue = new LinkedList<>(); + Map distances = new HashMap<>() { + { + put(personA, 0); + } + }; + queue.add(personA); + + while (!queue.isEmpty()) { + String current = queue.poll(); + int currentDistance = distances.get(current); + + for (String relative : graph.get(current)) { + if (!distances.containsKey(relative)) { + if (relative.equals(personB)) { + return currentDistance + 1; + } + distances.put(relative, currentDistance + 1); + queue.add(relative); + } + } + } + + return -1; + } +} diff --git a/exercises/practice/relative-distance/.meta/tests.toml b/exercises/practice/relative-distance/.meta/tests.toml new file mode 100644 index 000000000..66c91ba09 --- /dev/null +++ b/exercises/practice/relative-distance/.meta/tests.toml @@ -0,0 +1,31 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[4a1ded74-5d32-47fb-8ae5-321f51d06b5b] +description = "Direct parent-child relation" + +[30d17269-83e9-4f82-a0d7-8ef9656d8dce] +description = "Sibling relationship" + +[8dffa27d-a8ab-496d-80b3-2f21c77648b5] +description = "Two degrees of separation, grandchild" + +[34e56ec1-d528-4a42-908e-020a4606ee60] +description = "Unrelated individuals" + +[93ffe989-bad2-48c4-878f-3acb1ce2611b] +description = "Complex graph, cousins" + +[2cc2e76b-013a-433c-9486-1dbe29bf06e5] +description = "Complex graph, no shortcut, far removed nephew" + +[46c9fbcb-e464-455f-a718-049ea3c7400a] +description = "Complex graph, some shortcuts, cross-down and cross-up, cousins several times removed, with unrelated family tree" diff --git a/exercises/practice/relative-distance/build.gradle b/exercises/practice/relative-distance/build.gradle new file mode 100644 index 000000000..d28f35dee --- /dev/null +++ b/exercises/practice/relative-distance/build.gradle @@ -0,0 +1,25 @@ +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" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" +} + +test { + useJUnitPlatform() + + testLogging { + exceptionFormat = "full" + showStandardStreams = true + events = ["passed", "failed", "skipped"] + } +} diff --git a/exercises/practice/relative-distance/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/relative-distance/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..e6441136f Binary files /dev/null and b/exercises/practice/relative-distance/gradle/wrapper/gradle-wrapper.jar differ diff --git a/exercises/practice/relative-distance/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/relative-distance/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..2deab89d5 --- /dev/null +++ b/exercises/practice/relative-distance/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/exercises/practice/relative-distance/gradlew b/exercises/practice/relative-distance/gradlew new file mode 100755 index 000000000..1aa94a426 --- /dev/null +++ b/exercises/practice/relative-distance/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/exercises/practice/relative-distance/gradlew.bat b/exercises/practice/relative-distance/gradlew.bat new file mode 100644 index 000000000..25da30dbd --- /dev/null +++ b/exercises/practice/relative-distance/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/exercises/practice/relative-distance/src/main/java/RelativeDistance.java b/exercises/practice/relative-distance/src/main/java/RelativeDistance.java new file mode 100644 index 000000000..6b74ce091 --- /dev/null +++ b/exercises/practice/relative-distance/src/main/java/RelativeDistance.java @@ -0,0 +1,13 @@ +import java.util.List; +import java.util.Map; + +class RelativeDistance { + + RelativeDistance(Map> familyTree) { + throw new UnsupportedOperationException("Delete this statement and write your own implementation."); + } + + int degreeOfSeparation(String personA, String personB) { + throw new UnsupportedOperationException("Delete this statement and write your own implementation."); + } +} diff --git a/exercises/practice/relative-distance/src/test/java/RelativeDistanceTest.java b/exercises/practice/relative-distance/src/test/java/RelativeDistanceTest.java new file mode 100644 index 000000000..3002b1a91 --- /dev/null +++ b/exercises/practice/relative-distance/src/test/java/RelativeDistanceTest.java @@ -0,0 +1,256 @@ +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class RelativeDistanceTest { + + @Test + public void testDirectParentChildRelation() { + Map> familyTree = new HashMap<>() { + { + put("Vera", List.of("Tomoko")); + put("Tomoko", List.of("Aditi")); + } + }; + + RelativeDistance rd = new RelativeDistance(familyTree); + assertThat(rd.degreeOfSeparation("Vera", "Tomoko")).isEqualTo(1); + } + + @Disabled("Remove to run test") + @Test + public void testSiblingRelationship() { + Map> familyTree = new HashMap<>() { + { + put("Dalia", List.of("Olga", "Yassin")); + } + }; + + RelativeDistance rd = new RelativeDistance(familyTree); + assertThat(rd.degreeOfSeparation("Olga", "Yassin")).isEqualTo(1); + } + + @Disabled("Remove to run test") + @Test + public void testTwoDegreesOfSeparationGrandchild() { + Map> familyTree = new HashMap<>() { + { + put("Khadija", List.of("Mateo")); + put("Mateo", List.of("Rami")); + } + }; + + RelativeDistance rd = new RelativeDistance(familyTree); + assertThat(rd.degreeOfSeparation("Khadija", "Rami")).isEqualTo(2); + } + + @Disabled("Remove to run test") + @Test + public void testUnrelatedIndividuals() { + Map> familyTree = new HashMap<>() { + { + put("Priya", List.of("Rami")); + put("Kaito", List.of("Elif")); + } + }; + + RelativeDistance rd = new RelativeDistance(familyTree); + assertThat(rd.degreeOfSeparation("Priya", "Kaito")).isEqualTo(-1); + } + + @Disabled("Remove to run test") + @Test + public void testComplexGraphCousins() { + Map> familyTree = new HashMap<>() { + { + put("Aiko", List.of("Bao", "Carlos")); + put("Bao", List.of("Dalia", "Elias")); + put("Carlos", List.of("Fatima", "Gustavo")); + put("Dalia", List.of("Hassan", "Isla")); + put("Elias", List.of("Javier")); + put("Fatima", List.of("Khadija", "Liam")); + put("Gustavo", List.of("Mina")); + put("Hassan", List.of("Noah", "Olga")); + put("Isla", List.of("Pedro")); + put("Javier", List.of("Quynh", "Ravi")); + put("Khadija", List.of("Sofia")); + put("Liam", List.of("Tariq", "Uma")); + put("Mina", List.of("Viktor", "Wang")); + put("Noah", List.of("Xiomara")); + put("Olga", List.of("Yuki")); + put("Pedro", List.of("Zane", "Aditi")); + put("Quynh", List.of("Boris")); + put("Ravi", List.of("Celine")); + put("Sofia", List.of("Diego", "Elif")); + put("Tariq", List.of("Farah")); + put("Uma", List.of("Giorgio")); + put("Viktor", List.of("Hana", "Ian")); + put("Wang", List.of("Jing")); + put("Xiomara", List.of("Kaito")); + put("Yuki", List.of("Leila")); + put("Zane", List.of("Mateo")); + put("Aditi", List.of("Nia")); + put("Boris", List.of("Oscar")); + put("Celine", List.of("Priya")); + put("Diego", List.of("Qi")); + put("Elif", List.of("Rami")); + put("Farah", List.of("Sven")); + put("Giorgio", List.of("Tomoko")); + put("Hana", List.of("Umar")); + put("Ian", List.of("Vera")); + put("Jing", List.of("Wyatt")); + put("Kaito", List.of("Xia")); + put("Leila", List.of("Yassin")); + put("Mateo", List.of("Zara")); + put("Nia", List.of("Antonio")); + put("Oscar", List.of("Bianca")); + put("Priya", List.of("Cai")); + put("Qi", List.of("Dimitri")); + put("Rami", List.of("Ewa")); + put("Sven", List.of("Fabio")); + put("Tomoko", List.of("Gabriela")); + put("Umar", List.of("Helena")); + put("Vera", List.of("Igor")); + put("Wyatt", List.of("Jun")); + put("Xia", List.of("Kim")); + put("Yassin", List.of("Lucia")); + put("Zara", List.of("Mohammed")); + } + }; + + RelativeDistance rd = new RelativeDistance(familyTree); + assertThat(rd.degreeOfSeparation("Dimitri", "Fabio")).isEqualTo(9); + } + + @Disabled("Remove to run test") + @Test + public void testComplexGraphNoShortcutFarRemovedNephew() { + Map> familyTree = new HashMap<>() { + { + put("Aiko", List.of("Bao", "Carlos")); + put("Bao", List.of("Dalia", "Elias")); + put("Carlos", List.of("Fatima", "Gustavo")); + put("Dalia", List.of("Hassan", "Isla")); + put("Elias", List.of("Javier")); + put("Fatima", List.of("Khadija", "Liam")); + put("Gustavo", List.of("Mina")); + put("Hassan", List.of("Noah", "Olga")); + put("Isla", List.of("Pedro")); + put("Javier", List.of("Quynh", "Ravi")); + put("Khadija", List.of("Sofia")); + put("Liam", List.of("Tariq", "Uma")); + put("Mina", List.of("Viktor", "Wang")); + put("Noah", List.of("Xiomara")); + put("Olga", List.of("Yuki")); + put("Pedro", List.of("Zane", "Aditi")); + put("Quynh", List.of("Boris")); + put("Ravi", List.of("Celine")); + put("Sofia", List.of("Diego", "Elif")); + put("Tariq", List.of("Farah")); + put("Uma", List.of("Giorgio")); + put("Viktor", List.of("Hana", "Ian")); + put("Wang", List.of("Jing")); + put("Xiomara", List.of("Kaito")); + put("Yuki", List.of("Leila")); + put("Zane", List.of("Mateo")); + put("Aditi", List.of("Nia")); + put("Boris", List.of("Oscar")); + put("Celine", List.of("Priya")); + put("Diego", List.of("Qi")); + put("Elif", List.of("Rami")); + put("Farah", List.of("Sven")); + put("Giorgio", List.of("Tomoko")); + put("Hana", List.of("Umar")); + put("Ian", List.of("Vera")); + put("Jing", List.of("Wyatt")); + put("Kaito", List.of("Xia")); + put("Leila", List.of("Yassin")); + put("Mateo", List.of("Zara")); + put("Nia", List.of("Antonio")); + put("Oscar", List.of("Bianca")); + put("Priya", List.of("Cai")); + put("Qi", List.of("Dimitri")); + put("Rami", List.of("Ewa")); + put("Sven", List.of("Fabio")); + put("Tomoko", List.of("Gabriela")); + put("Umar", List.of("Helena")); + put("Vera", List.of("Igor")); + put("Wyatt", List.of("Jun")); + put("Xia", List.of("Kim")); + put("Yassin", List.of("Lucia")); + put("Zara", List.of("Mohammed")); + } + }; + RelativeDistance rd = new RelativeDistance(familyTree); + assertThat(rd.degreeOfSeparation("Lucia", "Jun")).isEqualTo(14); + } + + @Disabled("Remove to run test") + @Test + public void testComplexGraphSomeShortcutsCrossDownAndCrossUpCousinsSeveralTimesRemovedWithUnrelatedFamilyTree() { + Map> familyTree = new HashMap<>() { + { + put("Aiko", List.of("Bao", "Carlos")); + put("Bao", List.of("Dalia")); + put("Carlos", List.of("Fatima", "Gustavo")); + put("Dalia", List.of("Hassan", "Isla")); + put("Fatima", List.of("Khadija", "Liam")); + put("Gustavo", List.of("Mina")); + put("Hassan", List.of("Noah", "Olga")); + put("Isla", List.of("Pedro")); + put("Javier", List.of("Quynh", "Ravi")); + put("Khadija", List.of("Sofia")); + put("Liam", List.of("Tariq", "Uma")); + put("Mina", List.of("Viktor", "Wang")); + put("Noah", List.of("Xiomara")); + put("Olga", List.of("Yuki")); + put("Pedro", List.of("Zane", "Aditi")); + put("Quynh", List.of("Boris")); + put("Ravi", List.of("Celine")); + put("Sofia", List.of("Diego", "Elif")); + put("Tariq", List.of("Farah")); + put("Uma", List.of("Giorgio")); + put("Viktor", List.of("Hana", "Ian")); + put("Wang", List.of("Jing")); + put("Xiomara", List.of("Kaito")); + put("Yuki", List.of("Leila")); + put("Zane", List.of("Mateo")); + put("Aditi", List.of("Nia")); + put("Boris", List.of("Oscar")); + put("Celine", List.of("Priya")); + put("Diego", List.of("Qi")); + put("Elif", List.of("Rami")); + put("Farah", List.of("Sven")); + put("Giorgio", List.of("Tomoko")); + put("Hana", List.of("Umar")); + put("Ian", List.of("Vera")); + put("Jing", List.of("Wyatt")); + put("Kaito", List.of("Xia")); + put("Leila", List.of("Yassin")); + put("Mateo", List.of("Zara")); + put("Nia", List.of("Antonio")); + put("Oscar", List.of("Bianca")); + put("Priya", List.of("Cai")); + put("Qi", List.of("Dimitri")); + put("Rami", List.of("Ewa")); + put("Sven", List.of("Fabio")); + put("Tomoko", List.of("Gabriela")); + put("Umar", List.of("Helena")); + put("Vera", List.of("Igor")); + put("Wyatt", List.of("Jun")); + put("Xia", List.of("Kim")); + put("Yassin", List.of("Lucia")); + put("Zara", List.of("Mohammed")); + } + }; + RelativeDistance rd = new RelativeDistance(familyTree); + assertThat(rd.degreeOfSeparation("Wyatt", "Xia")).isEqualTo(12); + } +} diff --git a/exercises/practice/resistor-color-duo/build.gradle b/exercises/practice/resistor-color-duo/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/resistor-color-duo/build.gradle +++ b/exercises/practice/resistor-color-duo/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/resistor-color-trio/build.gradle b/exercises/practice/resistor-color-trio/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/resistor-color-trio/build.gradle +++ b/exercises/practice/resistor-color-trio/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/resistor-color/build.gradle b/exercises/practice/resistor-color/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/resistor-color/build.gradle +++ b/exercises/practice/resistor-color/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/reverse-string/build.gradle b/exercises/practice/reverse-string/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/reverse-string/build.gradle +++ b/exercises/practice/reverse-string/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/rna-transcription/.meta/config.json b/exercises/practice/rna-transcription/.meta/config.json index 3b7e9fecf..f750ce6a6 100644 --- a/exercises/practice/rna-transcription/.meta/config.json +++ b/exercises/practice/rna-transcription/.meta/config.json @@ -38,7 +38,7 @@ "build.gradle" ] }, - "blurb": "Given a DNA strand, return its RNA Complement Transcription.", + "blurb": "Given a DNA strand, return its RNA complement.", "source": "Hyperphysics", "source_url": "https://web.archive.org/web/20220408112140/http://hyperphysics.phy-astr.gsu.edu/hbase/Organic/transcription.html" } diff --git a/exercises/practice/rna-transcription/build.gradle b/exercises/practice/rna-transcription/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/rna-transcription/build.gradle +++ b/exercises/practice/rna-transcription/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/robot-name/build.gradle b/exercises/practice/robot-name/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/robot-name/build.gradle +++ b/exercises/practice/robot-name/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/robot-simulator/build.gradle b/exercises/practice/robot-simulator/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/robot-simulator/build.gradle +++ b/exercises/practice/robot-simulator/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/roman-numerals/build.gradle b/exercises/practice/roman-numerals/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/roman-numerals/build.gradle +++ b/exercises/practice/roman-numerals/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/rotational-cipher/build.gradle b/exercises/practice/rotational-cipher/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/rotational-cipher/build.gradle +++ b/exercises/practice/rotational-cipher/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/run-length-encoding/build.gradle b/exercises/practice/run-length-encoding/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/run-length-encoding/build.gradle +++ b/exercises/practice/run-length-encoding/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/saddle-points/.docs/instructions.md b/exercises/practice/saddle-points/.docs/instructions.md index c585568b4..f69cdab95 100644 --- a/exercises/practice/saddle-points/.docs/instructions.md +++ b/exercises/practice/saddle-points/.docs/instructions.md @@ -13,11 +13,12 @@ Or it might have one, or even several. Here is a grid that has exactly one candidate tree. ```text - 1 2 3 4 - |----------- -1 | 9 8 7 8 -2 | 5 3 2 4 <--- potential tree house at row 2, column 1, for tree with height 5 -3 | 6 6 7 1 + ↓ + 1 2 3 4 + |----------- + 1 | 9 8 7 8 +→ 2 |[5] 3 2 4 + 3 | 6 6 7 1 ``` - Row 2 has values 5, 3, 2, and 4. The largest value is 5. diff --git a/exercises/practice/saddle-points/build.gradle b/exercises/practice/saddle-points/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/saddle-points/build.gradle +++ b/exercises/practice/saddle-points/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/satellite/build.gradle b/exercises/practice/satellite/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/satellite/build.gradle +++ b/exercises/practice/satellite/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/say/.meta/config.json b/exercises/practice/say/.meta/config.json index 4ffa776c6..8df32cdda 100644 --- a/exercises/practice/say/.meta/config.json +++ b/exercises/practice/say/.meta/config.json @@ -21,5 +21,5 @@ }, "blurb": "Given a number from 0 to 999,999,999,999, spell out that number in English.", "source": "A variation on the JavaRanch CattleDrive, Assignment 4", - "source_url": "https://coderanch.com/wiki/718804" + "source_url": "https://web.archive.org/web/20240907035912/https://coderanch.com/wiki/718804" } diff --git a/exercises/practice/say/build.gradle b/exercises/practice/say/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/say/build.gradle +++ b/exercises/practice/say/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/scrabble-score/build.gradle b/exercises/practice/scrabble-score/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/scrabble-score/build.gradle +++ b/exercises/practice/scrabble-score/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/secret-handshake/build.gradle b/exercises/practice/secret-handshake/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/secret-handshake/build.gradle +++ b/exercises/practice/secret-handshake/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/series/build.gradle b/exercises/practice/series/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/series/build.gradle +++ b/exercises/practice/series/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/sgf-parsing/build.gradle b/exercises/practice/sgf-parsing/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/sgf-parsing/build.gradle +++ b/exercises/practice/sgf-parsing/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md index 085c0a57d..71292e178 100644 --- a/exercises/practice/sieve/.docs/instructions.md +++ b/exercises/practice/sieve/.docs/instructions.md @@ -6,37 +6,96 @@ A prime number is a number larger than 1 that is only divisible by 1 and itself. For example, 2, 3, 5, 7, 11, and 13 are prime numbers. By contrast, 6 is _not_ a prime number as it not only divisible by 1 and itself, but also by 2 and 3. -To use the Sieve of Eratosthenes, you first create a list of all the numbers between 2 and your given number. -Then you repeat the following steps: +To use the Sieve of Eratosthenes, first, write out all the numbers from 2 up to and including your given number. +Then, follow these steps: -1. Find the next unmarked number in your list (skipping over marked numbers). +1. Find the next unmarked number (skipping over marked numbers). This is a prime number. 2. Mark all the multiples of that prime number as **not** prime. -You keep repeating these steps until you've gone through every number in your list. +Repeat the steps until you've gone through every number. At the end, all the unmarked numbers are prime. ~~~~exercism/note -The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes. -To check you are implementing the Sieve correctly, a good first test is to check that you do not use division or remainder operations. +The Sieve of Eratosthenes marks off multiples of each prime using addition (repeatedly adding the prime) or multiplication (directly computing its multiples), rather than checking each number for divisibility. + +The tests don't check that you've implemented the algorithm, only that you've come up with the correct primes. ~~~~ ## Example Let's say you're finding the primes less than or equal to 10. -- List out 2, 3, 4, 5, 6, 7, 8, 9, 10, leaving them all unmarked. +- Write out 2, 3, 4, 5, 6, 7, 8, 9, 10, leaving them all unmarked. + + ```text + 2 3 4 5 6 7 8 9 10 + ``` + - 2 is unmarked and is therefore a prime. Mark 4, 6, 8 and 10 as "not prime". + + ```text + 2 3 [4] 5 [6] 7 [8] 9 [10] + ↑ + ``` + - 3 is unmarked and is therefore a prime. Mark 6 and 9 as not prime _(marking 6 is optional - as it's already been marked)_. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 4 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 5 is unmarked and is therefore a prime. Mark 10 as not prime _(optional - as it's already been marked)_. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 6 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 7 is unmarked and is therefore a prime. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 8 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 9 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 10 is marked as "not prime", so we stop as there are no more numbers to check. -You've examined all numbers and found 2, 3, 5, and 7 are still unmarked, which means they're the primes less than or equal to 10. + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +You've examined all the numbers and found that 2, 3, 5, and 7 are still unmarked, meaning they're the primes less than or equal to 10. diff --git a/exercises/practice/sieve/build.gradle b/exercises/practice/sieve/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/sieve/build.gradle +++ b/exercises/practice/sieve/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/simple-cipher/.docs/instructions.md b/exercises/practice/simple-cipher/.docs/instructions.md index 475af6182..afd0b57da 100644 --- a/exercises/practice/simple-cipher/.docs/instructions.md +++ b/exercises/practice/simple-cipher/.docs/instructions.md @@ -1,66 +1,40 @@ # Instructions -Implement a simple shift cipher like Caesar and a more secure substitution cipher. +Create an implementation of the [Vigenère cipher][wiki]. +The Vigenère cipher is a simple substitution cipher. -## Step 1 +## Cipher terminology -"If he had anything confidential to say, he wrote it in cipher, that is, by so changing the order of the letters of the alphabet, that not a word could be made out. -If anyone wishes to decipher these, and get at their meaning, he must substitute the fourth letter of the alphabet, namely D, for A, and so with the others." -—Suetonius, Life of Julius Caesar +A cipher is an algorithm used to encrypt, or encode, a string. +The unencrypted string is called the _plaintext_ and the encrypted string is called the _ciphertext_. +Converting plaintext to ciphertext is called _encoding_ while the reverse is called _decoding_. -Ciphers are very straight-forward algorithms that allow us to render text less readable while still allowing easy deciphering. -They are vulnerable to many forms of cryptanalysis, but Caesar was lucky that his enemies were not cryptanalysts. +In a _substitution cipher_, each plaintext letter is replaced with a ciphertext letter which is computed with the help of a _key_. +(Note, it is possible for replacement letter to be the same as the original letter.) -The Caesar Cipher was used for some messages from Julius Caesar that were sent afield. -Now Caesar knew that the cipher wasn't very good, but he had one ally in that respect: almost nobody could read well. -So even being a couple letters off was sufficient so that people couldn't recognize the few words that they did know. +## Encoding details -Your task is to create a simple shift cipher like the Caesar Cipher. -This image is a great example of the Caesar Cipher: +In this cipher, the key is a series of lowercase letters, such as `"abcd"`. +Each letter of the plaintext is _shifted_ or _rotated_ by a distance based on a corresponding letter in the key. +An `"a"` in the key means a shift of 0 (that is, no shift). +A `"b"` in the key means a shift of 1. +A `"c"` in the key means a shift of 2, and so on. -![Caesar Cipher][img-caesar-cipher] +The first letter of the plaintext uses the first letter of the key, the second letter of the plaintext uses the second letter of the key and so on. +If you run out of letters in the key before you run out of letters in the plaintext, start over from the start of the key again. -For example: +If the key only contains one letter, such as `"dddddd"`, then all letters of the plaintext are shifted by the same amount (three in this example), which would make this the same as a rotational cipher or shift cipher (sometimes called a Caesar cipher). +For example, the plaintext `"iamapandabear"` would become `"ldpdsdqgdehdu"`. -Giving "iamapandabear" as input to the encode function returns the cipher "ldpdsdqgdehdu". -Obscure enough to keep our message secret in transit. +If the key only contains the letter `"a"` (one or more times), the shift distance is zero and the ciphertext is the same as the plaintext. -When "ldpdsdqgdehdu" is put into the decode function it would return the original "iamapandabear" letting your friend read your original message. +Usually the key is more complicated than that, though! +If the key is `"abcd"` then letters of the plaintext would be shifted by a distance of 0, 1, 2, and 3. +If the plaintext is `"hello"`, we need 5 shifts so the key would wrap around, giving shift distances of 0, 1, 2, 3, and 0. +Applying those shifts to the letters of `"hello"` we get `"hfnoo"`. -## Step 2 +## Random keys -Shift ciphers quickly cease to be useful when the opposition commander figures them out. -So instead, let's try using a substitution cipher. -Try amending the code to allow us to specify a key and use that for the shift distance. +If no key is provided, generate a key which consists of at least 100 random lowercase letters from the Latin alphabet. -Here's an example: - -Given the key "aaaaaaaaaaaaaaaaaa", encoding the string "iamapandabear" -would return the original "iamapandabear". - -Given the key "ddddddddddddddddd", encoding our string "iamapandabear" -would return the obscured "ldpdsdqgdehdu" - -In the example above, we've set a = 0 for the key value. -So when the plaintext is added to the key, we end up with the same message coming out. -So "aaaa" is not an ideal key. -But if we set the key to "dddd", we would get the same thing as the Caesar Cipher. - -## Step 3 - -The weakest link in any cipher is the human being. -Let's make your substitution cipher a little more fault tolerant by providing a source of randomness and ensuring that the key contains only lowercase letters. - -If someone doesn't submit a key at all, generate a truly random key of at least 100 lowercase characters in length. - -## Extensions - -Shift ciphers work by making the text slightly odd, but are vulnerable to frequency analysis. -Substitution ciphers help that, but are still very vulnerable when the key is short or if spaces are preserved. -Later on you'll see one solution to this problem in the exercise "crypto-square". - -If you want to go farther in this field, the questions begin to be about how we can exchange keys in a secure way. -Take a look at [Diffie-Hellman on Wikipedia][dh] for one of the first implementations of this scheme. - -[img-caesar-cipher]: https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Caesar_cipher_left_shift_of_3.svg/320px-Caesar_cipher_left_shift_of_3.svg.png -[dh]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange +[wiki]: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher diff --git a/exercises/practice/simple-cipher/.meta/config.json b/exercises/practice/simple-cipher/.meta/config.json index 6f7ac09b1..de6b37f39 100644 --- a/exercises/practice/simple-cipher/.meta/config.json +++ b/exercises/practice/simple-cipher/.meta/config.json @@ -34,7 +34,7 @@ "build.gradle" ] }, - "blurb": "Implement a simple shift cipher like Caesar and a more secure substitution cipher.", + "blurb": "Implement the Vigenère cipher, a simple substitution cipher.", "source": "Substitution Cipher at Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Substitution_cipher" } diff --git a/exercises/practice/simple-cipher/build.gradle b/exercises/practice/simple-cipher/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/simple-cipher/build.gradle +++ b/exercises/practice/simple-cipher/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/simple-linked-list/build.gradle b/exercises/practice/simple-linked-list/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/simple-linked-list/build.gradle +++ b/exercises/practice/simple-linked-list/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/space-age/build.gradle b/exercises/practice/space-age/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/space-age/build.gradle +++ b/exercises/practice/space-age/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/spiral-matrix/build.gradle b/exercises/practice/spiral-matrix/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/spiral-matrix/build.gradle +++ b/exercises/practice/spiral-matrix/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/square-root/build.gradle b/exercises/practice/square-root/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/square-root/build.gradle +++ b/exercises/practice/square-root/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/state-of-tic-tac-toe/build.gradle b/exercises/practice/state-of-tic-tac-toe/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/state-of-tic-tac-toe/build.gradle +++ b/exercises/practice/state-of-tic-tac-toe/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/sublist/build.gradle b/exercises/practice/sublist/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/sublist/build.gradle +++ b/exercises/practice/sublist/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/sum-of-multiples/build.gradle b/exercises/practice/sum-of-multiples/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/sum-of-multiples/build.gradle +++ b/exercises/practice/sum-of-multiples/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/swift-scheduling/.docs/instructions.md b/exercises/practice/swift-scheduling/.docs/instructions.md new file mode 100644 index 000000000..6423a1066 --- /dev/null +++ b/exercises/practice/swift-scheduling/.docs/instructions.md @@ -0,0 +1,50 @@ +# Instructions + +Your task is to convert delivery date descriptions to _actual_ delivery dates, based on when the meeting started. + +There are two types of delivery date descriptions: + +1. Fixed: a predefined set of words. +2. Variable: words that have a variable component, but follow a predefined set of patterns. + +## Fixed delivery date descriptions + +There are three fixed delivery date descriptions: + +- `"NOW"` +- `"ASAP"` (As Soon As Possible) +- `"EOW"` (End Of Week) + +The following table shows how to translate them: + +| Description | Meeting start | Delivery date | +| ----------- | ----------------------------- | ----------------------------------- | +| `"NOW"` | - | Two hours after the meeting started | +| `"ASAP"` | Before 13:00 | Today at 17:00 | +| `"ASAP"` | After or at 13:00 | Tomorrow at 13:00 | +| `"EOW"` | Monday, Tuesday, or Wednesday | Friday at 17:00 | +| `"EOW"` | Thursday or Friday | Sunday at 20:00 | + +## Variable delivery date descriptions + +There are two variable delivery date description patterns: + +- `"M"` (N-th month) +- `"Q"` (N-th quarter) + +| Description | Meeting start | Delivery date | +| ----------- | ------------------------- | --------------------------------------------------------- | +| `"M"` | Before N-th month | At 8:00 on the _first_ workday of this year's N-th month | +| `"M"` | After or in N-th month | At 8:00 on the _first_ workday of next year's N-th month | +| `"Q"` | Before or in N-th quarter | At 8:00 on the _last_ workday of this year's N-th quarter | +| `"Q"` | After N-th quarter | At 8:00 on the _last_ workday of next year's N-th quarter | + +~~~~exercism/note +A workday is a Monday, Tuesday, Wednesday, Thursday, or Friday. + +A year has four quarters, each with three months: +1. January/February/March +2. April/May/June +3. July/August/September +4. October/November/December. +~~~~ diff --git a/exercises/practice/swift-scheduling/.docs/introduction.md b/exercises/practice/swift-scheduling/.docs/introduction.md new file mode 100644 index 000000000..2322f813f --- /dev/null +++ b/exercises/practice/swift-scheduling/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +This week, it is your turn to take notes in the department's planning meeting. +In this meeting, your boss will set delivery dates for all open work items. +Annoyingly, instead of specifying the _actual_ delivery dates, your boss will only _describe them_ in an abbreviated format. +As many of your colleagues won't be familiar with this corporate lingo, you'll need to convert these delivery date descriptions to actual delivery dates. diff --git a/exercises/practice/swift-scheduling/.meta/config.json b/exercises/practice/swift-scheduling/.meta/config.json new file mode 100644 index 000000000..d555a97e2 --- /dev/null +++ b/exercises/practice/swift-scheduling/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "zamora-carlos" + ], + "files": { + "solution": [ + "src/main/java/SwiftScheduling.java" + ], + "test": [ + "src/test/java/SwiftSchedulingTest.java" + ], + "example": [ + ".meta/src/reference/java/SwiftScheduling.java" + ] + }, + "blurb": "Convert delivery date descriptions to actual delivery dates.", + "source": "Erik Schierboom", + "source_url": "https://github.com/exercism/problem-specifications/pull/2536" +} diff --git a/exercises/practice/swift-scheduling/.meta/src/reference/java/SwiftScheduling.java b/exercises/practice/swift-scheduling/.meta/src/reference/java/SwiftScheduling.java new file mode 100644 index 000000000..8bff6e3d3 --- /dev/null +++ b/exercises/practice/swift-scheduling/.meta/src/reference/java/SwiftScheduling.java @@ -0,0 +1,97 @@ +import java.time.DayOfWeek; +import java.time.LocalDateTime; + +import static java.time.DayOfWeek.*; + +public class SwiftScheduling { + public static LocalDateTime convertToDeliveryDate(LocalDateTime meetingStart, String description) { + if ("NOW".equals(description)) { + return meetingStart.plusHours(2); + } + + if ("ASAP".equals(description)) { + LocalDateTime sameDayAt1pm = toStartOfDay(meetingStart).withHour(13); + + if (meetingStart.isBefore(sameDayAt1pm)) { + return toStartOfDay(meetingStart).withHour(17); + } else { + return toStartOfDay(meetingStart).plusDays(1).withHour(13); + } + } + + if ("EOW".equals(description)) { + DayOfWeek day = meetingStart.getDayOfWeek(); + LocalDateTime deliveryDate = toStartOfDay(meetingStart); + + if (day == MONDAY || day == TUESDAY || day == WEDNESDAY) { + deliveryDate = deliveryDate.withHour(17); + while (deliveryDate.getDayOfWeek() != FRIDAY) { + deliveryDate = deliveryDate.plusDays(1); + } + } else if (day == THURSDAY || day == FRIDAY) { + deliveryDate = deliveryDate.withHour(20); + while (deliveryDate.getDayOfWeek() != SUNDAY) { + deliveryDate = deliveryDate.plusDays(1); + } + } else { + throw new IllegalArgumentException("Invalid day of week"); + } + + return deliveryDate; + } + + if (description.matches("\\d+M")) { + int month = Integer.parseInt(description.substring(0, description.length() - 1)); + LocalDateTime targetMonth = toStartOfDay(meetingStart) + .withMonth(month) + .withDayOfMonth(1); + + if (!meetingStart.isBefore(targetMonth)) { + targetMonth = targetMonth.plusYears(1); + } + + LocalDateTime deliveryDate = targetMonth.withHour(8); + while (isWeekend(deliveryDate)) { + deliveryDate = deliveryDate.plusDays(1); + } + + return deliveryDate; + } + + if (description.matches("Q\\d")) { + int quarter = Integer.parseInt(description.substring(1)); + LocalDateTime lastDayOfQuarter = getLastDayOfQuarter(meetingStart, quarter); + + if (!meetingStart.isBefore(lastDayOfQuarter.plusDays(1))) { + lastDayOfQuarter = lastDayOfQuarter.plusYears(1); + } + + LocalDateTime deliveryDate = lastDayOfQuarter.withHour(8); + while (isWeekend(deliveryDate)) { + deliveryDate = deliveryDate.minusDays(1); + } + + return deliveryDate; + } + + throw new IllegalArgumentException("Invalid description"); + } + + private static LocalDateTime toStartOfDay(LocalDateTime dateTime) { + return dateTime.toLocalDate().atStartOfDay(); + } + + private static LocalDateTime getLastDayOfQuarter(LocalDateTime dateTime, int quarter) { + int lastMonthOfQuarter = quarter * 3; + return toStartOfDay(dateTime) + .withMonth(lastMonthOfQuarter) + .withDayOfMonth(1) + .plusMonths(1) + .minusDays(1); + } + + private static boolean isWeekend(LocalDateTime date) { + DayOfWeek dayOfWeek = date.getDayOfWeek(); + return dayOfWeek == SATURDAY || dayOfWeek == SUNDAY; + } +} diff --git a/exercises/practice/swift-scheduling/.meta/tests.toml b/exercises/practice/swift-scheduling/.meta/tests.toml new file mode 100644 index 000000000..7cc3e4158 --- /dev/null +++ b/exercises/practice/swift-scheduling/.meta/tests.toml @@ -0,0 +1,58 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[1d0e6e72-f370-408c-bc64-5dafa9c6da73] +description = "NOW translates to two hours later" + +[93325e7b-677d-4d96-b017-2582af879dc2] +description = "ASAP before one in the afternoon translates to today at five in the afternoon" + +[cb4252a3-c4c1-41f6-8b8c-e7269733cef8] +description = "ASAP at one in the afternoon translates to tomorrow at one in the afternoon" + +[6fddc1ea-2fe9-4c60-81f7-9220d2f45537] +description = "ASAP after one in the afternoon translates to tomorrow at one in the afternoon" + +[25f46bf9-6d2a-4e95-8edd-f62dd6bc8a6e] +description = "EOW on Monday translates to Friday at five in the afternoon" + +[0b375df5-d198-489e-acee-fd538a768616] +description = "EOW on Tuesday translates to Friday at five in the afternoon" + +[4afbb881-0b5c-46be-94e1-992cdc2a8ca4] +description = "EOW on Wednesday translates to Friday at five in the afternoon" + +[e1341c2b-5e1b-4702-a95c-a01e8e96e510] +description = "EOW on Thursday translates to Sunday at eight in the evening" + +[bbffccf7-97f7-4244-888d-bdd64348fa2e] +description = "EOW on Friday translates to Sunday at eight in the evening" + +[d651fcf4-290e-407c-8107-36b9076f39b2] +description = "EOW translates to leap day" + +[439bf09f-3a0e-44e7-bad5-b7b6d0c4505a] +description = "2M before the second month of this year translates to the first workday of the second month of this year" + +[86d82e83-c481-4fb4-9264-625de7521340] +description = "11M in the eleventh month translates to the first workday of the eleventh month of next year" + +[0d0b8f6a-1915-46f5-a630-1ff06af9da08] +description = "4M in the ninth month translates to the first workday of the fourth month of next year" + +[06d401e3-8461-438f-afae-8d26aa0289e0] +description = "Q1 in the first quarter translates to the last workday of the first quarter of this year" + +[eebd5f32-b16d-4ecd-91a0-584b0364b7ed] +description = "Q4 in the second quarter translates to the last workday of the fourth quarter of this year" + +[c920886c-44ad-4d34-a156-dc4176186581] +description = "Q3 in the fourth quarter translates to the last workday of the third quarter of next year" diff --git a/exercises/practice/swift-scheduling/build.gradle b/exercises/practice/swift-scheduling/build.gradle new file mode 100644 index 000000000..dd3862eb9 --- /dev/null +++ b/exercises/practice/swift-scheduling/build.gradle @@ -0,0 +1,25 @@ +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" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" +} + +test { + useJUnitPlatform() + + testLogging { + exceptionFormat = "full" + showStandardStreams = true + events = ["passed", "failed", "skipped"] + } +} diff --git a/exercises/practice/swift-scheduling/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/swift-scheduling/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..e6441136f Binary files /dev/null and b/exercises/practice/swift-scheduling/gradle/wrapper/gradle-wrapper.jar differ diff --git a/exercises/practice/swift-scheduling/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/swift-scheduling/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..2deab89d5 --- /dev/null +++ b/exercises/practice/swift-scheduling/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/exercises/practice/swift-scheduling/gradlew b/exercises/practice/swift-scheduling/gradlew new file mode 100755 index 000000000..1aa94a426 --- /dev/null +++ b/exercises/practice/swift-scheduling/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/exercises/practice/swift-scheduling/gradlew.bat b/exercises/practice/swift-scheduling/gradlew.bat new file mode 100644 index 000000000..93e3f59f1 --- /dev/null +++ b/exercises/practice/swift-scheduling/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/exercises/practice/swift-scheduling/src/main/java/SwiftScheduling.java b/exercises/practice/swift-scheduling/src/main/java/SwiftScheduling.java new file mode 100644 index 000000000..e5b85bec9 --- /dev/null +++ b/exercises/practice/swift-scheduling/src/main/java/SwiftScheduling.java @@ -0,0 +1,7 @@ +import java.time.LocalDateTime; + +public class SwiftScheduling { + public static LocalDateTime convertToDeliveryDate(LocalDateTime meetingStart, String description) { + throw new UnsupportedOperationException("Delete this statement and write your own implementation."); + } +} diff --git a/exercises/practice/swift-scheduling/src/test/java/SwiftSchedulingTest.java b/exercises/practice/swift-scheduling/src/test/java/SwiftSchedulingTest.java new file mode 100644 index 000000000..03f6c6c9c --- /dev/null +++ b/exercises/practice/swift-scheduling/src/test/java/SwiftSchedulingTest.java @@ -0,0 +1,200 @@ +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; + +class SwiftSchedulingTest { + @Test + @DisplayName("NOW at 9 AM") + void testNowAtNineAm() { + LocalDateTime meetingStart = LocalDateTime.parse("2012-02-13T09:00:00"); + LocalDateTime expected = LocalDateTime.parse("2012-02-13T11:00:00"); + + LocalDateTime actual = SwiftScheduling.convertToDeliveryDate(meetingStart, "NOW"); + + assertThat(actual).isEqualTo(expected); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("ASAP before 1 PM") + void testAsapBeforeOnePm() { + LocalDateTime meetingStart = LocalDateTime.parse("1999-06-03T09:45:00"); + LocalDateTime expected = LocalDateTime.parse("1999-06-03T17:00:00"); + + LocalDateTime actual = SwiftScheduling.convertToDeliveryDate(meetingStart, "ASAP"); + + assertThat(actual).isEqualTo(expected); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("ASAP at 1 PM") + void testAsapAtOnePm() { + LocalDateTime meetingStart = LocalDateTime.parse("2008-12-21T13:00:00"); + LocalDateTime expected = LocalDateTime.parse("2008-12-22T13:00:00"); + + LocalDateTime actual = SwiftScheduling.convertToDeliveryDate(meetingStart, "ASAP"); + + assertThat(actual).isEqualTo(expected); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("ASAP after 1 PM") + void testAsapAfterOnePm() { + LocalDateTime meetingStart = LocalDateTime.parse("2008-12-21T14:50:00"); + LocalDateTime expected = LocalDateTime.parse("2008-12-22T13:00:00"); + + LocalDateTime actual = SwiftScheduling.convertToDeliveryDate(meetingStart, "ASAP"); + + assertThat(actual).isEqualTo(expected); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("EOW on Monday") + void testEowOnMonday() { + LocalDateTime meetingStart = LocalDateTime.parse("2025-02-03T16:00:00"); + LocalDateTime expected = LocalDateTime.parse("2025-02-07T17:00:00"); + + LocalDateTime actual = SwiftScheduling.convertToDeliveryDate(meetingStart, "EOW"); + + assertThat(actual).isEqualTo(expected); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("EOW on Tuesday") + void testEowOnTuesday() { + LocalDateTime meetingStart = LocalDateTime.parse("1997-04-29T10:50:00"); + LocalDateTime expected = LocalDateTime.parse("1997-05-02T17:00:00"); + + LocalDateTime actual = SwiftScheduling.convertToDeliveryDate(meetingStart, "EOW"); + + assertThat(actual).isEqualTo(expected); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("EOW on Wednesday") + void testEowOnWednesday() { + LocalDateTime meetingStart = LocalDateTime.parse("2005-09-14T11:00:00"); + LocalDateTime expected = LocalDateTime.parse("2005-09-16T17:00:00"); + + LocalDateTime actual = SwiftScheduling.convertToDeliveryDate(meetingStart, "EOW"); + + assertThat(actual).isEqualTo(expected); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("EOW on Thursday") + void testEowOnThursday() { + LocalDateTime meetingStart = LocalDateTime.parse("2011-05-19T08:30:00"); + LocalDateTime expected = LocalDateTime.parse("2011-05-22T20:00:00"); + + LocalDateTime actual = SwiftScheduling.convertToDeliveryDate(meetingStart, "EOW"); + + assertThat(actual).isEqualTo(expected); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("EOW on Friday") + void testEowOnFriday() { + LocalDateTime meetingStart = LocalDateTime.parse("2022-08-05T14:00:00"); + LocalDateTime expected = LocalDateTime.parse("2022-08-07T20:00:00"); + + LocalDateTime actual = SwiftScheduling.convertToDeliveryDate(meetingStart, "EOW"); + + assertThat(actual).isEqualTo(expected); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("EOW in leap year") + void testEowDuringLeapYear() { + LocalDateTime meetingStart = LocalDateTime.parse("2008-02-25T10:30:00"); + LocalDateTime expected = LocalDateTime.parse("2008-02-29T17:00:00"); + + LocalDateTime actual = SwiftScheduling.convertToDeliveryDate(meetingStart, "EOW"); + + assertThat(actual).isEqualTo(expected); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("2M in January") + void test2MInJanuary() { + LocalDateTime meetingStart = LocalDateTime.parse("2007-01-02T14:15:00"); + LocalDateTime expected = LocalDateTime.parse("2007-02-01T08:00:00"); + + LocalDateTime actual = SwiftScheduling.convertToDeliveryDate(meetingStart, "2M"); + + assertThat(actual).isEqualTo(expected); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("11M in November") + void test11MInNovember() { + LocalDateTime meetingStart = LocalDateTime.parse("2013-11-21T15:30:00"); + LocalDateTime expected = LocalDateTime.parse("2014-11-03T08:00:00"); + + LocalDateTime actual = SwiftScheduling.convertToDeliveryDate(meetingStart, "11M"); + + assertThat(actual).isEqualTo(expected); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("4M in November") + void test4MInNovember() { + LocalDateTime meetingStart = LocalDateTime.parse("2019-11-18T15:15:00"); + LocalDateTime expected = LocalDateTime.parse("2020-04-01T08:00:00"); + + LocalDateTime actual = SwiftScheduling.convertToDeliveryDate(meetingStart, "4M"); + + assertThat(actual).isEqualTo(expected); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("Q1 in Q1") + void testQ1InQ1() { + LocalDateTime meetingStart = LocalDateTime.parse("2003-01-01T10:45:00"); + LocalDateTime expected = LocalDateTime.parse("2003-03-31T08:00:00"); + + LocalDateTime actual = SwiftScheduling.convertToDeliveryDate(meetingStart, "Q1"); + + assertThat(actual).isEqualTo(expected); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("Q4 in Q2") + void testQ4InQ2() { + LocalDateTime meetingStart = LocalDateTime.parse("2001-04-09T09:00:00"); + LocalDateTime expected = LocalDateTime.parse("2001-12-31T08:00:00"); + + LocalDateTime actual = SwiftScheduling.convertToDeliveryDate(meetingStart, "Q4"); + + assertThat(actual).isEqualTo(expected); + } + + @Disabled("Remove to run test") + @Test + @DisplayName("Q3 in Q4") + void testQ3InQ4() { + LocalDateTime meetingStart = LocalDateTime.parse("2022-10-06T11:00:00"); + LocalDateTime expected = LocalDateTime.parse("2023-09-29T08:00:00"); + + LocalDateTime actual = SwiftScheduling.convertToDeliveryDate(meetingStart, "Q3"); + + assertThat(actual).isEqualTo(expected); + } +} diff --git a/exercises/practice/tournament/build.gradle b/exercises/practice/tournament/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/tournament/build.gradle +++ b/exercises/practice/tournament/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/transpose/build.gradle b/exercises/practice/transpose/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/transpose/build.gradle +++ b/exercises/practice/transpose/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/tree-building/build.gradle b/exercises/practice/tree-building/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/tree-building/build.gradle +++ b/exercises/practice/tree-building/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/triangle/build.gradle b/exercises/practice/triangle/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/triangle/build.gradle +++ b/exercises/practice/triangle/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/twelve-days/build.gradle b/exercises/practice/twelve-days/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/twelve-days/build.gradle +++ b/exercises/practice/twelve-days/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/two-bucket/build.gradle b/exercises/practice/two-bucket/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/two-bucket/build.gradle +++ b/exercises/practice/two-bucket/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/two-fer/build.gradle b/exercises/practice/two-fer/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/two-fer/build.gradle +++ b/exercises/practice/two-fer/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/variable-length-quantity/build.gradle b/exercises/practice/variable-length-quantity/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/variable-length-quantity/build.gradle +++ b/exercises/practice/variable-length-quantity/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/word-count/build.gradle b/exercises/practice/word-count/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/word-count/build.gradle +++ b/exercises/practice/word-count/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/word-search/build.gradle b/exercises/practice/word-search/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/word-search/build.gradle +++ b/exercises/practice/word-search/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/wordy/build.gradle b/exercises/practice/wordy/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/wordy/build.gradle +++ b/exercises/practice/wordy/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/yacht/build.gradle b/exercises/practice/yacht/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/yacht/build.gradle +++ b/exercises/practice/yacht/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/zebra-puzzle/build.gradle b/exercises/practice/zebra-puzzle/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/zebra-puzzle/build.gradle +++ b/exercises/practice/zebra-puzzle/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/practice/zipper/build.gradle b/exercises/practice/zipper/build.gradle index d2eca9ec7..d28f35dee 100644 --- a/exercises/practice/zipper/build.gradle +++ b/exercises/practice/zipper/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { diff --git a/exercises/settings.gradle b/exercises/settings.gradle index a6686a473..2955a15dc 100644 --- a/exercises/settings.gradle +++ b/exercises/settings.gradle @@ -8,9 +8,10 @@ include 'concept:booking-up-for-beauty' include 'concept:calculator-conundrum' include 'concept:captains-log' include 'concept:cars-assemble' -include 'concept:elons-toy-car' +include 'concept:jedliks-toy-car' include 'concept:football-match-reports' include 'concept:gotta-snatch-em-all' +include 'concept:international-calling-connoisseur' include 'concept:karls-languages' include 'concept:lasagna' include 'concept:log-levels' @@ -61,6 +62,7 @@ include 'practice:dot-dsl' include 'practice:error-handling' include 'practice:etl' include 'practice:flatten-array' +include 'practice:flower-field' include 'practice:food-chain' include 'practice:forth' include 'practice:game-of-life' @@ -92,7 +94,7 @@ include 'practice:matrix' include 'practice:mazy-mice' include 'practice:meetup' include 'practice:micro-blog' -include 'practice:minesweeper' +// include 'practice:minesweeper' // deprecated include 'practice:nth-prime' include 'practice:nucleotide-count' include 'practice:ocr-numbers' @@ -103,6 +105,7 @@ include 'practice:parallel-letter-frequency' include 'practice:pascals-triangle' include 'practice:perfect-numbers' include 'practice:phone-number' +include 'practice:piecing-it-together' include 'practice:pig-latin' include 'practice:poker' include 'practice:eliuds-eggs' @@ -117,6 +120,7 @@ include 'practice:raindrops' include 'practice:rational-numbers' include 'practice:react' include 'practice:rectangles' +include 'practice:relative-distance' include 'practice:resistor-color' include 'practice:resistor-color-duo' include 'practice:resistor-color-trio' @@ -145,6 +149,7 @@ include 'practice:state-of-tic-tac-toe' // include 'practice:strain' // deprecated include 'practice:sublist' include 'practice:sum-of-multiples' +include 'practice:swift-scheduling' include 'practice:tournament' include 'practice:transpose' include 'practice:tree-building' diff --git a/reference/contributing-to-practice-exercises.md b/reference/contributing-to-practice-exercises.md index 327c68635..75b25ddd5 100644 --- a/reference/contributing-to-practice-exercises.md +++ b/reference/contributing-to-practice-exercises.md @@ -19,7 +19,7 @@ This can just be a Pull Request with an empty commit that states which new exerc The Java specific details you need to know about adding an exercise are: - Please add an entry to the `exercises` array in `config.json`. - You can find details about what should be in that entry [here][docs-building-config-json]. + You can find details about what should be in that entry at the [Exercism docs][docs-building-config-json]. You can also look at other entries in `config.json` as examples and try to mimic them. - Please add an entry for your exercise to `settings.gradle`. diff --git a/resources/exercise-template/build.gradle b/resources/exercise-template/build.gradle index 1344305f7..dd3862eb9 100644 --- a/resources/exercise-template/build.gradle +++ b/resources/exercise-template/build.gradle @@ -10,6 +10,8 @@ dependencies { testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test {