A few months ago I was introduced to the Dojo XP France. A french and parisian occurrence of a programming dojo concentrating on practicing and discussing TDD.
While "every monday 18:30" is a bit too much for me to follow (not to mention go to as I have almost an hour of transit to go to the usual dojo place) I have been working on some of the Katas.
The last I played with was the Mastermind kata, more precisely a Ioke implementation of the kata.
I have been able to find a very pleasing solution to the kata (hopefully a valid solution too :) ) which I present below. I tried to separate several stages of the code, I hope this will make the progression easier to follow. Obviously the best way to see the code evolve is to come to the Dojo.
Stage 1 : find the correct and well placed pegs
First the tests ...
describe("evalm",
;stage 1
it("should return [0,0] when all pegs are the wrong color",
evalm([]("M","M","M","M"), []("B","B","B","B")) should == [](0,0)
)
it("should return [1,0] when the first peg of both secret and guess is the same",
evalm([]("B","M","M","M"), []("B","B","B","B")) should == [](1,0)
)
it("should return [1,0] when the second peg of both secret and guess is the same",
evalm([]("M","B","M","M"), []("B","B","B","B")) should == [](1,0)
)
it("should return [2,0] when the first 2 peg of both secret and guess is the same",
evalm([]("B","B","M","M"), []("B","B","B","B")) should == [](2,0)
)
)
Then the code ...
;stage 1 evalm=method(guess, secret, if( guess[0]==secret[0], if( guess[1]==secret[1], [](2,0), [](1,0) ), if( guess[1]==secret[1], [](1,0), [](0,0) ) ) )
Stage 2 : Refactor the code to DRY
Same tests as we refactor, thus the code ...
;stage2 == stage 1 refactored evalm=method(guess, secret, good=guess zip(secret) filter(inject(==)) count [](good, 0) )
Stage 3 : Introduce the notion of misplaced pegs for the "R"ed color
By adding More tests ..
describe("evalm",
;stage 1
it("should return [0,0] when all pegs are the wrong color",
evalm([]("M","M","M","M"), []("B","B","B","B")) should == [](0,0)
)
it("should return [1,0] when the first peg of both secret and guess is the same",
evalm([]("B","M","M","M"), []("B","B","B","B")) should == [](1,0)
)
it("should return [1,0] when the second peg of both secret and guess is the same",
evalm([]("M","B","M","M"), []("B","B","B","B")) should == [](1,0)
)
it("should return [2,0] when the first 2 peg of both secret and guess is the same",
evalm([]("B","B","M","M"), []("B","B","B","B")) should == [](2,0)
)
;stage 2 we refactor the good to use zip
;stage 3 introduce the misplaced
it("should return [0,1] when one red peg is misplaced",
evalm([]("M","R","M","M"), []("R","B","B","B")) should == [](0,1)
)
it("should return [0,2] when two red pegs are misplaced",
evalm([]("M","R","M","R"), []("R","B","R","B")) should == [](0,2)
)
)
And writing more code ...
;stage 3 evalm=method(guess, secret, good=guess zip(secret) filter(inject(==)) count bad=guess zip(secret) filter(inject(!=)) bad=bad filter([1]=="R") count [](good, bad) )
Stage 4 : Generalize to all the colors
And yet more tests :) ...
describe("evalm",
;stage 1
it("should return [0,0] when all pegs are the wrong color",
evalm([]("M","M","M","M"), []("B","B","B","B")) should == [](0,0)
)
it("should return [1,0] when the first peg of both secret and guess is the same",
evalm([]("B","M","M","M"), []("B","B","B","B")) should == [](1,0)
)
it("should return [1,0] when the second peg of both secret and guess is the same",
evalm([]("M","B","M","M"), []("B","B","B","B")) should == [](1,0)
)
it("should return [2,0] when the first 2 peg of both secret and guess is the same",
evalm([]("B","B","M","M"), []("B","B","B","B")) should == [](2,0)
)
;stage 2 we refactor the good to use zip
;stage 3 introduce the misplaced
it("should return [0,1] when one red peg is misplaced",
evalm([]("M","R","M","M"), []("R","B","B","B")) should == [](0,1)
)
it("should return [0,2] when two red pegs are misplaced",
evalm([]("M","R","M","R"), []("R","B","R","B")) should == [](0,2)
)
;stage 4 generalize to all colors
it("should return [0,2] when a red peg and a yellow peg are misplaced",
evalm([]("M","R","M","Y"), []("R","B","Y","B")) should == [](0,2)
)
)
For more code ...
;stage 4 evalm=method(guess, secret, good=guess zip(secret) filter(inject(==)) count bad=guess zip(secret) filter(inject(!=)) colors=bad map:set(x,x[0]) bad=colors map(x,bad filter([1]==x) count) sum [](good, bad) )
Stage 5 : Refactor for DRY
Same tests, different code ...
;stage 5 evalm=method(guess, secret, sorted = guess zip(secret) groupBy(inject(==)) good = (sorted[true]||[]) count badpairs = sorted[false]||[] colors = badpairs map:set(x,x[0]) bad = colors map(x,badpairs filter([1]==x) count) sum || 0 [](good, bad) )
While this code is longer, it uses groupBy instead of zipping and filtering the list twice. I would like to improve on this by ensuring that groupBy always returns an empty list [] whether there are values or not. However I haven't found an elegant way to do this ... yet.
Stage 6: the complete solution with acceptance tests
The tests
describe("evalm",
;stage 1
it("should return [0,0] when all pegs are the wrong color",
evalm([]("M","M","M","M"), []("B","B","B","B")) should == [](0,0)
)
it("should return [1,0] when the first peg of both secret and guess is the same",
evalm([]("B","M","M","M"), []("B","B","B","B")) should == [](1,0)
)
it("should return [1,0] when the second peg of both secret and guess is the same",
evalm([]("M","B","M","M"), []("B","B","B","B")) should == [](1,0)
)
it("should return [2,0] when the first 2 peg of both secret and guess is the same",
evalm([]("B","B","M","M"), []("B","B","B","B")) should == [](2,0)
)
;stage 2 we refactor the good to use zip
;stage 3 introduce the misplaced
it("should return [0,1] when one red peg is misplaced",
evalm([]("M","R","M","M"), []("R","B","B","B")) should == [](0,1)
)
it("should return [0,2] when two red pegs are misplaced",
evalm([]("M","R","M","R"), []("R","B","R","B")) should == [](0,2)
)
;stage 4 generalize to all colors
it("should return [0,2] when a red peg and a yellow peg are misplaced",
evalm([]("M","R","M","Y"), []("R","B","Y","B")) should == [](0,2)
)
)
;stage 5 refactor to use groupBy
;stage 6 acceptance tests
describe("recette",
it("should return [0,0] for guess(M,M,M,M) secret(B,B,B,B)",
evalm([]("M","M","M","M"), []("B","B","B","B")) should == [](0,0)
)
it("should return [4,0] for guess(B,B,B,B) secret(B,B,B,B)",
evalm([]("B","B","B","B"), []("B","B","B","B")) should == [](4,0)
)
it("should return [0,4] for guess(A,B,C,D) secret(D,A,B,C)",
evalm([]("A","B","C","D"), []("D","A","B","C")) should == [](0,4)
)
it("should return [2,2] for guess(A,B,C,B) secret(C,B,A,B)",
evalm([]("A","B","C","B"), []("C","B","A","B")) should == [](2,2)
)
)
And the code
;stage 5 evalm=method(guess, secret, sorted = guess zip(secret) groupBy(inject(==)) good = (sorted[true]||[]) count badpairs = sorted[false]||[] colors = badpairs map:set(x,x[0]) bad = colors map(x,badpairs filter([1]==x) count) sum || 0 [](good, bad) )

0 commentaires:
Enregistrer un commentaire