Commit e268d66a authored by Colin DAMON's avatar Colin DAMON

Merge branch '66-h2g2-kata' into 'master'

Resolve "H2G2 kata"

Closes #66

See merge request !51
parents 3cc26531 07088f35
......@@ -21,3 +21,4 @@ include:
- local: "/puissance-4/.gitlab-ci.yml"
- local: "/trip-service-kata-typescript/.gitlab-ci.yml"
- local: "/roman-calculator/.gitlab-ci.yml"
- local: "/h2g2/.gitlab-ci.yml"
......@@ -45,3 +45,4 @@ Ce dépôt Git a pour but de partager les différents ateliers pouvant être ré
| [Trip service (TypeScript)](/trip-service-kata-typescript) | Kata | Moyenne |
| [Roman calculator](/roman-calculator) | Kata | Moyenne |
| [Atomic Design I and II](https://gitlab.ippon.fr/arey/pattern-library) | Front | Moyenne |
| [H2G2 (TypeScript)](/h2g2) | Kata | Moyenne |
module.exports = {
root: true,
env: {
node: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
],
plugins: ['prettier', '@typescript-eslint'],
rules: {
quotes: ['error', 'single', { avoidEscape: true }],
'comma-dangle': [
'error',
{
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'always-multiline',
exports: 'always-multiline',
functions: 'never',
},
],
'import/order': [
'error',
{
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
'newlines-between': 'always',
},
],
'prettier/prettier': [
'error',
{
singleQuote: true,
trailingComma: 'es5',
printWidth: 140,
},
],
},
settings: {
'import/resolver': {
typescript: {
alwaysTryTypes: true,
},
},
'import/core-modules': ['sinon'],
},
parserOptions: {
parser: '@typescript-eslint/parser',
},
overrides: [
{
files: ['**/__tests__/*.{j,t}s?(x)', '**/*.spec.{j,t}s?(x)'],
env: {
jest: true,
},
},
{
files: ['**/*.ts', '**/*.tsx'],
rules: {
'no-unused-vars': ['off'],
'no-undef': ['off'],
},
},
],
};
/node_modules
/.idea
h2g2:
variables:
PROJECT_FOLDER: "h2g2"
extends: .node14
only:
refs:
- master
- merge_requests
changes:
- ".gitlab-common-ci.yml"
- "h2g2/**/*"
# Potter
If you want to try this Kata for yourself or at your dojo meeting, read
the problem description and see if the Kata appeals to you. The rest is
commentary and helpful clues for if you get stuck solving it. I would
recommend trying the Kata for yourself before reading too much of it.
## Problem Description
Once upon a time there was a series of 5 books about a very English hero
called Harry. (At least when this Kata was invented, there were only 5.
Since then they have multiplied) Children all over the world thought he
was fantastic, and, of course, so did the publisher. So in a gesture of
immense generosity to mankind, (and to increase sales) they set up the
following pricing model to take advantage of Harry's magical powers.
One copy of any of the five books costs 8 EUR. If, however, you buy two
different books from the series, you get a 5% discount on those two
books. If you buy 3 different books, you get a 10% discount. With 4
different books, you get a 20% discount. If you go the whole hog, and
buy all 5, you get a huge 25% discount.
Note that if you buy, say, four books, of which 3 are different titles,
you get a 10% discount on the 3 that form part of a set, but the fourth
book still costs 8 EUR.
Potter mania is sweeping the country and parents of teenagers everywhere
are queueing up with shopping baskets overflowing with Potter books.
Your mission is to write a piece of code to calculate the price of any
conceivable shopping basket, giving as big a discount as possible.
For example, how much does this basket of books cost?
* 2 copies of the first book
* 2 copies of the second book
* 2 copies of the third book
* 1 copy of the fourth book
* 1 copy of the fifth book
answer :
(4 * 8) - 20% [first book, second book, third book, fourth book]
+ (4 * 8) - 20% [first book, second book, third book, fifth book]
= 25.6 * 2
= 51.20
## Clues
You’ll find that this Kata is easy at the start. You can get going with
tests for baskets of 0 books, 1 book, 2 identical books, 2 different
books… and it is not too difficult to work in small steps and gradually
introduce complexity.
However, the twist becomes apparent when you sit down and work out how
much you think the sample basket above should cost. It isn’t
`5 * 8 * 0.75 + 3 *8 * 0.90`. It is in fact `4 * 8 * 0.8 + 4 * 8 * 0.8`. So the trick
with this Kata is not that the acceptance test you’ve been given is
wrong. The trick is that you have to write some code that is intelligent
enough to notice that two sets of four books is cheaper than a set of
five and a set of three.
You will have to introduce a certain amount of clever optimization
algorithm. But not too much! This problem does not require a fully
fledged general purpose optimizer. Try to solve just this problem well
in order to share it for everyone or even in the ??? . Trust that you
can generalize and improve your solution if and when new requirements
come along.
- This application has nice application for
## Suggested Test Cases
(Originally posted at xp-france)
def testBasics
assert_equal(0, price([]))
assert_equal(8, price([1]))
assert_equal(8, price([2]))
assert_equal(8, price([3]))
assert_equal(8, price([4]))
assert_equal(8 * 3, price([1, 1, 1]))
end
def testSimpleDiscounts
assert_equal(8 * 2 * 0.95, price([0, 1]))
assert_equal(8 * 3 * 0.9, price([0, 2, 4]))
assert_equal(8 * 4 * 0.8, price([0, 1, 2, 4]))
assert_equal(8 * 5 * 0.75, price([0, 1, 2, 3, 4]))
end
def testSeveralDiscounts
assert_equal(8 + (8 * 2 * 0.95), price([0, 0, 1]))
assert_equal(2 * (8 * 2 * 0.95), price([0, 0, 1, 1]))
assert_equal((8 * 4 * 0.8) + (8 * 2 * 0.95), price([0, 0, 1, 2, 2, 3]))
assert_equal(8 + (8 * 5 * 0.75), price([0, 1, 1, 2, 3, 4]))
end
def testEdgeCases
assert_equal(2 * (8 * 4 * 0.8), price([0, 0, 1, 1, 2, 2, 3, 4]))
assert_equal(3 * (8 * 5 * 0.75) + 2 * (8 * 4 * 0.8),
price([0, 0, 0, 0, 0,
1, 1, 1, 1, 1,
2, 2, 2, 2,
3, 3, 3, 3, 3,
4, 4, 4, 4]))
end
# H2G2
Kata Potter, mais on l'a renommé H2G2 parce que la série Harry Potter comporte 7 livres et non 5 alors que la trilogie en cinq volumes de Douglas Adams en comporte 5, en même temps c'est ce qui est écrit dans l'intitulé.
- **Auteurs** : Léa COSTON et Anthony REY
- **Date** : 06/10/2020
- **Langage** : TypeScript
- **Niveau** : Moyen
- **Replay** : [H2G2 Kata - Léa et Anthony](https://www.youtube.com/watch?v=XB2c8fj_Aus)
module.exports = {
roots: ['<rootDir>/src'],
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
testEnvironment: 'node',
};
{
"name": "h2g2",
"version": "1.0.0",
"description": "Code retreat",
"main": "src/index.ts",
"dependencies": {
"@types/jest": "^26.0.15",
"@typescript-eslint/eslint-plugin": "^4.6.1",
"@typescript-eslint/parser": "^4.6.1",
"eslint": "^7.13.0",
"eslint-import-resolver-typescript": "^2.3.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-prettier": "^3.1.4",
"jest": "^26.6.3",
"prettier": "^2.1.2",
"ts-jest": "^26.4.3",
"typescript": "^4.0.5"
},
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:watch:all": "jest --watchAll",
"eslint:ci": "eslint './**/*.{ts,js}'",
"eslint": "eslint './**/*.{ts,js}' --fix"
},
"author": "Anthony REY",
"license": "MIT"
}
const BOOK_PRICE = 8;
const DISCOUNT_TWO_BOOKS = 0.95;
const DISCOUNT_THREE_BOOKS = 0.9;
const DISCOUNT_FOUR_BOOKS = 0.8;
const DISCOUNT_FIVE_BOOKS = 0.75;
const DISCOUNT = new Map([
[2, DISCOUNT_TWO_BOOKS],
[3, DISCOUNT_THREE_BOOKS],
[4, DISCOUNT_FOUR_BOOKS],
[5, DISCOUNT_FIVE_BOOKS],
]);
enum Book {
FIRST = 'FIRST',
SECOND = 'SECOND',
THIRD = 'THIRD',
FOURTH = 'FOURTH',
FIFTH = 'FIFTH',
}
const discountFor = (distinct: number): number => DISCOUNT.get(distinct) || 1;
const maxSerie = (occurences: Map<Book, number>) =>
Array.from(occurences)
.map(([, number]) => number)
.filter((number) => number > 0).length;
function decrementOccurence(occurence: number) {
if (occurence === 0) {
return 0;
}
return occurence - 1;
}
const decrementSerie = (occurences: Map<Book, number>): Map<Book, number> =>
new Map(Array.from(occurences).map(([book, occurence]) => [book, decrementOccurence(occurence)]));
const getOccurences = (books: Book[]) =>
new Map(
[Book.FIRST, Book.SECOND, Book.THIRD, Book.FOURTH, Book.FIFTH].map((book) => [
book,
books.filter((currentBook) => currentBook === book).length,
])
);
const fill = (number: number, size: number): number[] => Array(number).fill(size);
const optimizePacks = (packs: number[]): number[] => {
const one = packs.filter((size) => size === 1).length;
const two = packs.filter((size) => size === 2).length;
const three = packs.filter((size) => size === 3).length;
const four = packs.filter((size) => size === 4).length;
const five = packs.filter((size) => size === 5).length;
const difference = Math.min(three, five);
return [
...fill(one, 1),
...fill(two, 2),
...fill(three - difference, 3),
...fill(four + difference * 2, 4),
...fill(five - difference, 5),
];
};
const price = (books: Book[]): number => {
let occurences = getOccurences(books);
const packs: number[] = [];
while (maxSerie(occurences) > 0) {
packs.push(maxSerie(occurences));
occurences = decrementSerie(occurences);
}
return optimizePacks(packs)
.map((size) => size * BOOK_PRICE * discountFor(size))
.reduce((accumulation, current) => accumulation + current, 0);
};
describe('H2G2', () => {
it('Price of no book should be 0', () => {
expect(price([])).toBe(0);
});
it('Price of one book is 8', () => {
expect(price([Book.FIRST])).toBe(8);
});
it('Price of same book is 8 each', () => {
expect(price([Book.SECOND, Book.SECOND, Book.SECOND, Book.SECOND])).toBe(32);
});
it('Price two different books should have 5% off', () => {
expect(price([Book.THIRD, Book.FOURTH])).toBe(8 * 2 * 0.95);
});
it('Price three different books should have 10% off', () => {
expect(price([Book.THIRD, Book.FOURTH, Book.FIFTH])).toBe(8 * 3 * 0.9);
});
it('Price four different books should have 20% off', () => {
expect(price([Book.FIRST, Book.THIRD, Book.FOURTH, Book.FIFTH])).toBe(8 * 4 * 0.8);
});
it('Price five different books should have 25% off', () => {
expect(price([Book.FIRST, Book.SECOND, Book.THIRD, Book.FOURTH, Book.FIFTH])).toBe(8 * 5 * 0.75);
});
it('Should discount of two different series be independent', () => {
expect(price([Book.FIRST, Book.SECOND, Book.THIRD, Book.FOURTH, Book.FIFTH, Book.FIRST, Book.SECOND])).toBe(
8 * 5 * 0.75 + 8 * 2 * 0.95
);
});
it('Should treat price of edge case', () => {
expect(price([Book.FIRST, Book.SECOND, Book.THIRD, Book.FOURTH, Book.FIFTH, Book.FIRST, Book.SECOND, Book.FOURTH])).toBe(
8 * 4 * 2 * 0.8
);
});
});
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"outFile": "dist/main.js",
"sourceMap": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment