repo: https://github.com/kenchen879/kata-potter
需求設計
曾幾何時,有一個 5 本書系列,講述了一位名叫哈利的非常英國的英雄。 (至少在這個 Kata 被發明的時候,只有 5 個。從那以後他們成倍增加)全世界的孩子都認為他很棒,當然,出版商也一樣。因此,為了對人類慷慨解囊,(並為了增加銷售額),他們建立了以下定價模型,以利用哈利吸引人的優點。
這五本書中的任何一本都需要 8 歐元。但是,如果您從該系列中購買了兩本不同的書,則可以在這兩本書上獲得 5% 的折扣。
如果您購買 3 種不同的書籍,您將獲得 10% 的折扣。購買 4 種不同的書籍,您將獲得 20% 的折扣。如果您全部購買 5 個,您將獲得 25% 的巨大折扣。
請注意,例如,如果您購買了四本書,其中 3 本書是不同的書名,那麼您可以在其中的 3 本書上獲得 10% 的折扣,但第四本書的價格仍然是 8 歐元。
波特狂熱正在席捲全國,各地青少年的父母都在排隊,購物籃裡裝滿了波特的書籍。你的任務是編寫一段代碼來計算任何可以想像到的購物籃的價格,並提供盡可能大的折扣。
舉例來說,這個購物籃會花費多少錢?
- 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
1 | (4 * 8) - 20% [first book, second book, third book, fourth book] |
開始 Red–Green–Refactor cycle
第一個紅燈 (Red)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// potter.spec.ts
test('testBasics: buy one book', () => {
const potter = new Potter();
potter.addToBasket([]);
expect(potter.price).toBe(0);
potter.addToBasket([1]);
expect(potter.price).toBe(8);
potter.addToBasket([2]);
expect(potter.price).toBe(8);
potter.addToBasket([3]);
expect(potter.price).toBe(8);
potter.addToBasket([4]);
expect(potter.price).toBe(8);
potter.addToBasket([1, 1, 1]);
expect(potter.price).toBe(8 * 3);
});1
2
3
4
5
6
7// potter.ts
export class Potter {
addToBasket(pins: number) {}
get price() {
return -1;
}
}第一個綠燈 (Green)
將 score method 回傳值改為 0,形成第一個綠燈。
1
2
3
4
5
6
7// potter.ts
export class Potter {
addToBasket(pins: number) {}
get price() {
return 0;
}
}第一次重構
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28// potter.spec.ts
import { Potter } from './potter';
describe('Kata - Harry Potter Book', () => {
let potter: Potter;
beforeEach(() => {
potter = new Potter();
});
it('should create an instance', () => {
expect(potter).toBeTruthy();
});
test('testBasics: buy one book', () => {
potter.addToBasket([]);
expect(potter.price).toBe(0);
potter.addToBasket([1]);
expect(potter.price).toBe(8);
potter.addToBasket([2]);
expect(potter.price).toBe(8);
potter.addToBasket([3]);
expect(potter.price).toBe(8);
potter.addToBasket([4]);
expect(potter.price).toBe(8);
potter.addToBasket([1, 1, 1]);
expect(potter.price).toBe(8 * 3);
});
});第二個紅燈
1
2
3
4
5
6
7
8
9
10
11// potter.spec.ts
test('testBasics: buy one book', () => {
potter.addToBasket([0, 1]);
expect(potter.price).toBe(8 * 2 * 0.95);
potter.addToBasket([0, 2, 4]);
expect(potter.price).toBe(8 * 3 * 0.9);
potter.addToBasket([0, 1, 2, 4]);
expect(potter.price).toBe(8 * 4 * 0.8);
potter.addToBasket([0, 1, 2, 3, 4]);
expect(potter.price).toBe(8 * 5 * 0.75);
});第二個綠燈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41export class Potter {
private _basket: number[] = [];
addToBasket(book: number[]) {
this._basket = [];
for (let i = 0; i < book.length; i++)
this._basket.push(book[i]);
}
get price() {
let price = 0;
let totalBookNumber = 0;
let distunctBookNumber = 0;
totalBookNumber = this._basket.length;
const distinctBasket = this._basket.filter((ele , pos) => {
return this._basket.indexOf(ele) == pos;
});
distunctBookNumber = distinctBasket.length;
if (distunctBookNumber == 1) {
price = 8 * totalBookNumber;
} else {
switch (totalBookNumber) {
case 2:
price = 8 * 2 * 0.95;
break;
case 3:
price = 8 * 3 * 0.9;
break;
case 4:
price = 8 * 4 * 0.8;
break;
case 5:
price = 8 * 5 * 0.75;
break;
default:
break;
}
}
return price;
}
}第二次重構
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49// potter.ts
export class Potter {
private _basket: number[] = [];
addToBasket(book: number[]) {
this._basket = [];
for (let i = 0; i < book.length; i++)
this._basket.push(book[i]);
}
get price() {
let price = 8;
let discount = [1, 1, 0.95, 0.9, 0.8, 0.75];
let totalBookNumber = 0;
let distinctBookNumber = 0;
totalBookNumber = this._basket.length;
// 取得不重複的購物籃
const distinctBasket = this.distinctBasket;
// 取得不重複購物籃的數量
distinctBookNumber = distinctBasket.length;
if (distinctBookNumber == 1) {
price *= totalBookNumber;
} else {
switch (totalBookNumber) {
case 2:
price *= 2 * discount[2];
break;
case 3:
price *= 3 * discount[3];
break;
case 4:
price *= 4 * discount[4];
break;
case 5:
price *= 5 * discount[5];
break;
default:
price = 0;
}
}
return price;
}
get distinctBasket () {
const distinctBasket = [...(new Set(this._basket))];
return distinctBasket;
}
}第三個紅燈
1
2
3
4
5
6
7
8
9
10
11// potter.spec.ts
test('testSeveralDiscounts', () => {
potter.addToBasket([0, 0, 1]);
expect(potter.price).toBe(8 + (8 * 2 * 0.95));
potter.addToBasket([0, 0, 1, 1]);
expect(potter.price).toBe(2 * (8 * 2 * 0.95));
potter.addToBasket([0, 0, 1, 2, 2, 3]);
expect(potter.price).toBe((8 * 4 * 0.8) + (8 * 2 * 0.95));
potter.addToBasket([0, 1, 1, 2, 3, 4]);
expect(potter.price).toBe(8 + (8 * 5 * 0.75));
});第三個綠燈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55// potter.ts
export class Potter {
private _basket: number[] = [];
addToBasket(book: number[]) {
this._basket = [];
for (let i = 0; i < book.length; i++)
this._basket.push(book[i]);
}
get price() {
let price = 0;
const discount = [1, 1, 0.95, 0.9, 0.8, 0.75];
let totalBookNumber = 0;
// 取得不重複的購物籃
let distinctBasket = [];
let distinctBookNumber = 0;
let index = [];
totalBookNumber = this._basket.length;
while (totalBookNumber > 0) {
// 取得不重複的購物籃
distinctBasket = this.distinctBasket;
// 取得不重複購物籃的數量
distinctBookNumber = distinctBasket.length;
switch (distinctBookNumber) {
case 1:
price += 8 * distinctBookNumber;
break;
case 2:
price += 8 * 2 * discount[2];
break;
case 3:
price += 8 * 3 * discount[3];
break;
case 4:
price += 8 * 4 * discount[4];
break;
case 5:
price += 8 * 5 * discount[5];
break;
default:
price = 0;
}
index = distinctBasket.filter(e => this._basket.indexOf(e));
index.forEach(e => this._basket.splice(e, distinctBookNumber));
totalBookNumber -= distinctBookNumber;
}
return price;
}
get distinctBasket () {
const distinctBasket = [...(new Set(this._basket))];
return distinctBasket;
}
}第三次重構
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54// potter.spec.ts
export class Potter {
private _basket: number[] = [];
addToBasket(book: number[]) {
this._basket = [];
book.forEach(e => this._basket.push(e));
}
get price() {
let price = 0;
const discount = [1, 1, 0.95, 0.9, 0.8, 0.75];
let totalBookNumber = 0;
// 取得不重複的購物籃
let distinctBasket = [];
let distinctBookNumber = 0;
let index = [];
totalBookNumber = this._basket.length;
while (totalBookNumber > 0) {
// 取得不重複的購物籃
distinctBasket = this.distinctBasket;
// 取得不重複購物籃的數量
distinctBookNumber = distinctBasket.length;
switch (distinctBookNumber) {
case 1:
price += 8 * distinctBookNumber;
break;
case 2:
price += 8 * 2 * discount[2];
break;
case 3:
price += 8 * 3 * discount[3];
break;
case 4:
price += 8 * 4 * discount[4];
break;
case 5:
price += 8 * 5 * discount[5];
break;
default:
price = 0;
}
index = distinctBasket.filter(e => this._basket.indexOf(e));
index.forEach(e => this._basket.splice(e, distinctBookNumber));
totalBookNumber -= distinctBookNumber;
}
return price;
}
get distinctBasket () {
const distinctBasket = [...(new Set(this._basket))];
return distinctBasket;
}
}第四個紅燈
1
2
3
4
5
6
7
8
9
10
11// potter.spec.ts
test('testEdgeCases', () => {
potter.addToBasket([0, 0, 1, 1, 2, 2, 3, 4]);
expect(potter.price).toBe(2 * (8 * 4 * 0.8));
potter.addToBasket([0, 0, 0, 0, 0,
1, 1, 1, 1, 1,
2, 2, 2, 2,
3, 3, 3, 3, 3,
4, 4, 4, 4]);
expect(potter.price).toBe(3 * (8 * 5 * 0.75) + 2 * (8 * 4 * 0.8));
});第四個綠燈以及第四次重構
要取得價格最佳解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71// potter.ts
export class Potter {
private _basket: number[] = [];
private totalBookNumber = 0;
constructor () {
this._basket = [];
this.totalBookNumber = 0;
}
addToBasket(book: number[]) {
book.forEach(e => this._basket.push(e));
this.totalBookNumber = book.length;
}
get price() {
let price = 0;
const discount = [1, 1, 0.95, 0.9, 0.8, 0.75];
// 取得不重複的購物籃
let distinctBasket:number[] = [];
// 取得不重複購物籃的數量
let distinctBookNumber = 0;
while (this.totalBookNumber > 0) {
if (this.checkOptimalPrice()) {
price += 2 * 8 * 4 * discount[4];
} else {
distinctBasket = this.createDistinctBasket();
distinctBookNumber = distinctBasket.length;
price += 8 * distinctBookNumber * discount[distinctBookNumber];
this.removeBook(distinctBasket, distinctBookNumber);
}
}
return price;
}
checkOptimalPrice () {
if (this.totalBookNumber < 8) return false;
let distinctBasket1: number[] = [];
let distinctBasket2: number[] = [];
distinctBasket1 = this.createDistinctBasket();
this.removeBook(distinctBasket1, distinctBasket1.length);
distinctBasket2 = this.createDistinctBasket();
this.removeBook(distinctBasket2, distinctBasket2.length);
if (distinctBasket1.length == 5 && distinctBasket2.length == 3) {
return true;
} else {
distinctBasket1.forEach(e => this._basket.push(e));
this.totalBookNumber += distinctBasket1.length;
distinctBasket2.forEach(e => this._basket.push(e));
this.totalBookNumber += distinctBasket2.length;
return false;
}
}
removeBook (distinct: number[], num: number) {
for (let i = 0; i < num; i++) {
this._basket.splice(this._basket.indexOf(distinct[i]), 1);
}
this.totalBookNumber -= num;
}
createDistinctBasket () {
// 取得不重複的購物籃
const distinctBook = [...(new Set(this._basket))];
// 取得不重複購物籃的數量
return distinctBook;
}
}