diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7918e00f712f06efda46edec128e46c1e1ff09c9..6ba016365a0c19353050f156b68c0bdeb24a6801 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -15,3 +15,4 @@ include: - local: "/string-calculator-2/.gitlab-ci.yml" - local: "/trip-service-kata/.gitlab-ci.yml" - local: "/tcr-roman-numerals/.gitlab-ci.yml" + - local: "/java-h2g2/.gitlab-ci.yml" diff --git a/README.md b/README.md index 1604f63c0436cfc525eafea9a2f8566f9ee23a60..7604eb69cd8c5cd5510df2930b946346299a0ed2 100644 --- a/README.md +++ b/README.md @@ -37,3 +37,4 @@ Ce dépôt Git a pour but de partager les différents ateliers pouvant être ré | [Proxifier son localhost avec un nginx dockerisé avec Edouard CATTEZ](https://www.youtube.com/watch?v=qWMfRb3zK7k) | Talk | Facile | | [Dev pour des data scientists, un sale boulot](https://www.youtube.com/watch?v=QK3OJGAresE) | Discussion | | | [TCR - Roman Numerals](/tcr-roman-numerals) | Kata | Moyenne | +| [H2G2 en Java](/java-h2g2) | Kata | Moyenne | diff --git a/java-h2g2/.gitlab-ci.yml b/java-h2g2/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..c518f3687485f028ef893255199790d33e3cba9d --- /dev/null +++ b/java-h2g2/.gitlab-ci.yml @@ -0,0 +1,11 @@ +package-java-h2g2: + variables: + PROJECT_FOLDER: "java-h2g2" + extends: .java + only: + refs: + - master + - merge_requests + changes: + - ".gitlab-common-ci.yml" + - "java-h2g2/**/*" diff --git a/java-h2g2/README.md b/java-h2g2/README.md new file mode 100644 index 0000000000000000000000000000000000000000..53fd5db54b7262a563882e71d396107c25c846d4 --- /dev/null +++ b/java-h2g2/README.md @@ -0,0 +1,9 @@ +# Java h2g2 + +Résolution en TDD et en Java du kata [Potter](https://codingdojo.org/kata/Potter/) renommé h2g2 pour l'occasion. + +- **Auteurs** : Anthony REY et Colin DAMON +- **Date** : 27/11/2020 +- **Langage** : Java +- **Niveau** : Moyen +- **Replay** : TODO diff --git a/java-h2g2/pom.xml b/java-h2g2/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..b9bbe10929cec2b176cfdd2229a80e3e1aebdfea --- /dev/null +++ b/java-h2g2/pom.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <version>1.0.0</version> + <groupId>fr.ippon.kata</groupId> + <artifactId>java-parent</artifactId> + <relativePath>../java-parent</relativePath> + </parent> + + <version>1.0.0-SNAPSHOT</version> + <artifactId>java-h2g2</artifactId> + + <name>JavaH2G2</name> + + <developers> + <developer> + <email>arey@ippon.fr</email> + <name>Anthony REY</name> + </developer> + <developer> + <email>cdamon@ippon.fr</email> + <name>Colin DAMON</name> + </developer> + </developers> +</project> diff --git a/java-h2g2/src/main/java/fr/ippon/kata/h2g2/Book.java b/java-h2g2/src/main/java/fr/ippon/kata/h2g2/Book.java new file mode 100644 index 0000000000000000000000000000000000000000..4dd9649bb6c67d6cb690d56f7e7f64523f61db8a --- /dev/null +++ b/java-h2g2/src/main/java/fr/ippon/kata/h2g2/Book.java @@ -0,0 +1,9 @@ +package fr.ippon.kata.h2g2; + +public enum Book { + FIRST, + SECOND, + THIRD, + FORTH, + FIFTH +} diff --git a/java-h2g2/src/main/java/fr/ippon/kata/h2g2/Shop.java b/java-h2g2/src/main/java/fr/ippon/kata/h2g2/Shop.java new file mode 100644 index 0000000000000000000000000000000000000000..38cb3d290318dfa4c38352a141a6d5346a6e330f --- /dev/null +++ b/java-h2g2/src/main/java/fr/ippon/kata/h2g2/Shop.java @@ -0,0 +1,117 @@ +package fr.ippon.kata.h2g2; + +import static java.util.stream.Collectors.*; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class Shop { + private static final Map<Integer, Float> FACTORS = buildFactors(); + private static final int BOOK_PRICE = 8; + + private static Map<Integer, Float> buildFactors() { + Map<Integer, Float> factors = new HashMap<>(); + + factors.put(5, 0.75f); + factors.put(4, 0.80f); + factors.put(3, 0.90f); + factors.put(2, 0.95f); + factors.put(1, 1f); + factors.put(0, 1f); + + return factors; + } + + public static float buy(Book... books) { + Basket basket = new Basket(books); + + return (float) basket.getPacks().stream().map(packPrice()).reduce(BigDecimal.ZERO, BigDecimal::add).floatValue(); + } + + private static Function<Integer, BigDecimal> packPrice() { + return count -> BigDecimal.valueOf(BOOK_PRICE * count * FACTORS.get(count)); + } + + private static final class Basket { + private final Collection<Integer> packs; + + public Basket(Book... books) { + packs = buildPacks(books); + } + + private Collection<Integer> buildPacks(Book[] books) { + Map<Book, Integer> groupedBooks = groupBooks(books); + + List<Integer> packs = firstPackLevel(groupedBooks); + + return optimizePacks(packs); + } + + private Collection<Integer> optimizePacks(List<Integer> packs) { + int threePack = countPackBySize(packs, 3); + int fivePack = countPackBySize(packs, 5); + int commonPacks = Math.min(fivePack, threePack); + + List<Integer> result = new ArrayList<>(); + + result.addAll(repeatPack(1, countPackBySize(packs, 1))); + result.addAll(repeatPack(2, countPackBySize(packs, 2))); + result.addAll(repeatPack(3, threePack - commonPacks)); + result.addAll(repeatPack(4, countPackBySize(packs, 4) + commonPacks * 2)); + result.addAll(repeatPack(5, fivePack - commonPacks)); + + return result; + } + + private List<Integer> repeatPack(int packSize, int packCount) { + return IntStream.range(0, packCount).mapToObj(key -> Integer.valueOf(packSize)).collect(Collectors.toList()); + } + + private int countPackBySize(List<Integer> packs, int size) { + return (int) packs.stream().filter(value -> value == size).count(); + } + + private List<Integer> firstPackLevel(Map<Book, Integer> groupedBooks) { + List<Integer> packs = new ArrayList<>(); + + while (remainingBooks(groupedBooks)) { + packs.add(groupedBooks.size()); + + groupedBooks = + groupedBooks.entrySet().stream().filter(booksRemaining()).collect(Collectors.toMap(Entry::getKey, entry -> entry.getValue() - 1)); + } + return packs; + } + + private boolean remainingBooks(Map<Book, Integer> groupedBooks) { + return !groupedBooks.isEmpty(); + } + + private Predicate<Entry<Book, Integer>> booksRemaining() { + return entry -> entry.getValue() > 1; + } + + private static Map<Book, Integer> groupBooks(Book... books) { + return Arrays + .stream(books) + .collect(groupingBy(Function.identity())) + .entrySet() + .stream() + .collect(toMap(Entry::getKey, entry -> entry.getValue().size())); + } + + private Collection<Integer> getPacks() { + return packs; + } + } +} diff --git a/java-h2g2/src/test/java/fr/ippon/kata/h2g2/H2G2Test.java b/java-h2g2/src/test/java/fr/ippon/kata/h2g2/H2G2Test.java new file mode 100644 index 0000000000000000000000000000000000000000..7595bc7ac4c11759dafc667eea4b1433d5786970 --- /dev/null +++ b/java-h2g2/src/test/java/fr/ippon/kata/h2g2/H2G2Test.java @@ -0,0 +1,54 @@ +package fr.ippon.kata.h2g2; + +import static fr.ippon.kata.h2g2.Book.*; +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class H2G2Test { + + @Test + void shouldBeFreeWithoutBooks() { + assertThat(Shop.buy()).isEqualTo(0); + } + + @Test + void shouldPayFullPriceForOneBook() { + assertThat(Shop.buy(FIRST)).isEqualTo(8); + } + + @Test + void shouldPayFullPriceForTwoFirstBooks() { + assertThat(Shop.buy(FIRST, FIRST)).isEqualTo(8 * 2); + } + + @Test + void shouldGetFivePercentOffWithTwoBooksFromSeries() { + assertThat(Shop.buy(FIRST, SECOND)).isEqualTo(8 * 2 * 0.95f); + } + + @Test + void shouldGetTenPercentOffWithThreeBooksFromSeries() { + assertThat(Shop.buy(THIRD, FORTH, FIFTH)).isEqualTo(8 * 3 * 0.90f); + } + + @Test + void shouldGetTwentyPercentOffWithFourBooksFromSeries() { + assertThat(Shop.buy(SECOND, THIRD, FORTH, FIFTH)).isEqualTo(8 * 4 * 0.80f); + } + + @Test + void shouldGetTwentyFivePercentOffWithFiveBooksFromSeries() { + assertThat(Shop.buy(FIRST, SECOND, THIRD, FORTH, FIFTH)).isEqualTo(8 * 5 * 0.75f); + } + + @Test + void shouldGetBestPossibleReductionForFirstFirstSecond() { + assertThat(Shop.buy(FIRST, FIRST, SECOND)).isEqualTo(8 + (8 * 2 * 0.95f)); + } + + @Test + void shouldGetBestPossibleReductionForBasketOfHell() { + assertThat(Shop.buy(FIRST, FIRST, SECOND, SECOND, THIRD, THIRD, FORTH, FIFTH)).isEqualTo(2 * 8 * 4 * 0.8f); + } +}