Record of experiments, readings, links, videos and other things that I find on the long road.
Registro de experimentos, lecturas, links, vídeos y otras cosas que voy encontrando en el largo camino.
Tuesday, September 29, 2015
Interesting Talk: "Deconstructing the Database"
I've just watched this wonderful talk by Rich Hickey:
Monday, September 28, 2015
Kata: Integer Ranges in Clojure
I just did the Integer Ranges kata in Clojure.
These are the tests using Midje:
and this is the resulting code:
As usual I used a mix of TDD and REPL-driven development committing after each green and each refactoring.
I also committed the REPL history.
See all the commits here if you want to follow the process.
You can find all the code on GitHub.
These are the tests using Midje:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns integer-ranges.core-test | |
(:use midje.sweet) | |
(:use [integer-ranges.core])) | |
(facts | |
"about integer-ranges" | |
(fact | |
"it knows which numbers an interval includes" | |
(includes? "[2, 5]" "{2,3,4,5}") => true | |
(includes? "[2, 5]" "{2,-1}") => false | |
(includes? "[2, 5)" "{5}") => false | |
(includes? "(2, 5]" "{2}") => false | |
(includes? "(2, 5)" "{2}") => false | |
(includes? "(2, 5)" "{5}") => false) | |
(fact | |
"it tells all numbers an interval includes" | |
(all-numbers "[2,5]") => [2 3 4 5] | |
(all-numbers "[1,5]") => [1 2 3 4 5] | |
(all-numbers "(1,5]") => [2 3 4 5] | |
(all-numbers "(1,5)") => [2 3 4]) | |
(fact | |
"it knows when an interval contains another interval" | |
(contains-range? "[2,10)" "[2,5]") => true | |
(contains-range? "(2,10]" "[2,5]") => false | |
(contains-range? "[2,4]" "[2,5]") => false | |
(contains-range? "[2,4]" "[2,5)") => true) | |
(fact | |
"it knows an interval's end points" | |
(end-points "[3,8]") => [3 8]) | |
(fact | |
"it knows when two intervals overlap" | |
(overlaps? "[2,10)" "[9,10)") => true | |
(overlaps? "[2,10)" "[1,2)") => false | |
(overlaps? "[2,10)" "[10,12)") => false | |
(overlaps? "[2,10]" "[10,12)") => true | |
(overlaps? "[2,10]" "[3,5)") => true | |
(overlaps? "[3,5)" "[2,10]") => true | |
(overlaps? "[9,10)" "[2,10)") => true | |
(overlaps? "[1,2)" "[2,10)") => false | |
(overlaps? "[1,2)" "[5,10)") => false | |
(overlaps? "[2,10]" "(10,20)") => false | |
(overlaps? "[2,10]" "[10,20)") => true | |
(overlaps? "[2,10)" "[10,20)") => false) | |
(fact | |
"it knows if two intervals are equal or not | |
(being equal means that they include the same numbers)" | |
(equals? "[2,10)" "[9,10)") => false | |
(equals? "[5,8]" "[5,8]") => true | |
(equals? "[5,8]" "[5,9)") => true | |
(equals? "[4,8]" "(3,9)") => true | |
(equals? "(4,8]" "[5,9)") => true)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns integer-ranges.core | |
(:require [clojure.string :as string])) | |
(defn- remove-spaces [s] | |
(string/replace s #" " "")) | |
(defn- remove-brackets [descriptor] | |
(apply str (drop-last (drop 1 descriptor)))) | |
(defn- numbers-descriptors [descriptor] | |
(-> descriptor | |
remove-spaces | |
remove-brackets | |
(string/split #","))) | |
(defn- numbers [numbers-list-descriptor] | |
(map #(Integer/parseInt (str %)) | |
(numbers-descriptors numbers-list-descriptor))) | |
(defn- brackets [descriptor] | |
(let [stripped_descriptor (string/replace descriptor #" " "")] | |
[(first stripped_descriptor) (last stripped_descriptor)])) | |
(defn- closed-open-interval [descriptor] | |
(let [[lower upper] (numbers descriptor) | |
[opening-bracket closing-bracket] (brackets descriptor)] | |
[(if (= opening-bracket \[) lower (inc lower)) | |
(if (= closing-bracket \]) (inc upper) upper)])) | |
(defn- includes-number? [[lower upper] number] | |
(<= lower number (dec upper))) | |
(defn includes? [interval-descriptor numbers-descriptor] | |
(every? | |
#(includes-number? (closed-open-interval interval-descriptor) %) | |
(numbers numbers-descriptor))) | |
(defn all-numbers [descriptor] | |
(apply range (closed-open-interval descriptor))) | |
(defn contains-range? [descriptor other-descriptor] | |
(let [[lower upper] (closed-open-interval descriptor) | |
[other-lower other-upper] (closed-open-interval other-descriptor)] | |
(<= lower other-lower other-upper upper))) | |
(def end-points numbers) | |
(defn overlaps? [descriptor other-descriptor] | |
(let [[lower upper] (closed-open-interval descriptor) | |
[other-lower other-upper] (closed-open-interval other-descriptor)] | |
(and (< lower other-upper) (> upper other-lower)))) | |
(defn equals? [descriptor other-descriptor] | |
(= (closed-open-interval descriptor) | |
(closed-open-interval other-descriptor))) |
See all the commits here if you want to follow the process.
You can find all the code on GitHub.
Saturday, September 26, 2015
Interesting Talk: "Maintaining Balance While Reducing Duplication"
I've just watched this great talk by David Chelimsky:
Thursday, September 24, 2015
Kata: Password validation in Python
Yesterday I did the Password validation kata in Python.
I used TDD to write the code.
These are the tests using Pytest:
and this is the resulting code:
If you want to follow the process step by step, have a look at the commits.
You can find all the code in GitHub.
I used TDD to write the code.
These are the tests using Pytest:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pytest | |
from password_validator import PasswordValidator | |
@pytest.fixture | |
def validator(): | |
return PasswordValidator(8) | |
def test_a_strong_password(validator): | |
assert validator.is_strong_password("#Ab3cccc") is True | |
def test_that_only_passwords_with_the_minimum_length_are_strong(validator): | |
assert validator.is_strong_password("#Ab3ccc") is False | |
def test_that_only_passwords_including_numbers_are_strong(validator): | |
assert validator.is_strong_password("#Abccccc") is False | |
def test_that_only_passwords_including_upper_case_letters_are_strong(validator): | |
assert validator.is_strong_password("#ab3cccc") is False | |
def test_that_only_passwords_including_lower_case_letters_are_strong(validator): | |
assert validator.is_strong_password("#AB3CCCC") is False | |
def test_that_only_passwords_including_special_characters_are_strong(validator): | |
assert validator.is_strong_password("cAb3cccc") is False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
class PasswordValidator(object): | |
def __init__(self, minimum_length): | |
self.minimum_length = minimum_length | |
def is_strong_password(self, password): | |
includes_special_characters = self._includes_any( | |
self.REQUIRED_SPECIAL_CHARACTERS, password | |
) | |
includes_lower_case_letters = self._includes_any( | |
self.LOWER_CASE_LETTERS, password | |
) | |
includes_upper_case_letters = self._includes_any( | |
self.UPPER_CASE_LETTERS, password | |
) | |
includes_numbers = self._includes_any(self.NUMBERS, password) | |
has_minimum_length = len(password) >= self.minimum_length | |
return \ | |
has_minimum_length and \ | |
includes_numbers and \ | |
includes_upper_case_letters and \ | |
includes_lower_case_letters and \ | |
includes_special_characters | |
@staticmethod | |
def _includes_any(pattern, password): | |
return re.search(pattern, password) is not None | |
REQUIRED_SPECIAL_CHARACTERS = '[%#]' | |
UPPER_CASE_LETTERS = '[A-Z]' | |
LOWER_CASE_LETTERS = '[a-z]' | |
NUMBERS = '[0-9]' |
If you want to follow the process step by step, have a look at the commits.
You can find all the code in GitHub.
Wednesday, September 23, 2015
Kata: Word wrap in Clojure
Last night I did the Word Wrap kata in Clojure.
It was proposed in the last Barcelona Software Craftsmanship coding dojo but I couldn't attend, so I did it at home.
These are the tests using Midje:
and this is the resulting code:
As usual I used a mix of TDD and REPL-driven development committing after each green and each refactoring.
I also committed the REPL history. See all the commits here to follow the process.
Once I got to a tail recursive solution, I tried to make it more readable by extracting some explanatory helpers and working in the naming of bindings, functions and function arguments.
You can find all the code in GitHub.
It was proposed in the last Barcelona Software Craftsmanship coding dojo but I couldn't attend, so I did it at home.
These are the tests using Midje:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns word-wrap.core-test | |
(:use midje.sweet) | |
(:use [word-wrap.core])) | |
(facts | |
"about wrapping words" | |
(fact | |
"a text that fits in the given columns number are not wrapped" | |
(wrap "koko koko" 9) => "koko koko" | |
(wrap "ko" 9) => "ko" | |
(wrap "" 9) => "") | |
(fact | |
"a text without spaces that doesn't fit in the given columns number are wrapped" | |
(wrap "kokokoko" 4) => "koko\nkoko") | |
(fact | |
"a text with spaces that doesn't fit in the given columns number are wrapped | |
at the space that is closest to the maximum column" | |
(wrap "koko koko" 7) => "koko\nkoko" | |
(wrap "koko koko koko" 12) => "koko koko\nkoko" | |
(wrap "koko koko koko koko koko koko" 12) => "koko koko\nkoko koko\nkoko koko" | |
(wrap | |
"This koko should be easy unless there are hidden, or not so hidden, obstacles. Let's start!" | |
12) => "This koko\nshould be\neasy unless\nthere are\nhidden, or\nnot so\nhidden,\nobstacles.\nLet's start!") | |
(fact | |
"a text already splitted in lines gets each of its lines re-wrapped" | |
(wrap | |
"kokokoko\nkaka koko\nkoko koko" | |
6) => "kokoko\nko\nkaka\nkoko\nkoko\nkoko")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns word-wrap.core | |
(:require [clojure.string :as string])) | |
(def ^:private to-trimmed-string | |
(comp string/trim (partial apply str))) | |
(def ^:private rest-of-line | |
(comp to-trimmed-string (partial drop))) | |
(defn- wrap-line-at [index line] | |
(str (to-trimmed-string (take index line)) \newline)) | |
(defn- index-of-last-fitting-space [max-columns line] | |
(.lastIndexOf (take max-columns line) \space)) | |
(def ^:private valid-index? pos?) | |
(defn- compute-wrapping-index [line max-columns] | |
(let [index (index-of-last-fitting-space max-columns line)] | |
(if (valid-index? index) | |
index | |
max-columns))) | |
(defn- fits? [line max-columns] | |
(<= (count line) max-columns)) | |
(defn- line->wrapped-lines [wrapped-lines line max-columns] | |
(if (fits? line max-columns) | |
(conj wrapped-lines line) | |
(let [index (compute-wrapping-index line max-columns)] | |
(recur (conj wrapped-lines (wrap-line-at index line)) | |
(rest-of-line index line) | |
max-columns)))) | |
(defn- wrap-line [line max-columns] | |
(apply str (line->wrapped-lines [] line max-columns))) | |
(defn- extract-lines [text] | |
(string/split text #"\n")) | |
(def ^:private join-lines (partial string/join \newline)) | |
(defn wrap [text max-columns] | |
(->> text | |
extract-lines | |
(map #(wrap-line % max-columns)) | |
join-lines)) |
Once I got to a tail recursive solution, I tried to make it more readable by extracting some explanatory helpers and working in the naming of bindings, functions and function arguments.
You can find all the code in GitHub.
Monday, September 21, 2015
Interesting Podcast: "When to use modules"
I've just listened to this great Ruby Rogues podcast about Ruby modules:
Sunday, September 20, 2015
Kata: Yatzi refactoring kata in Ruby
Last night I did the Yatzy refactoring kata in Ruby.
I mainly used it to practice with Enumerable functions.
This is the original code:
and this is the refactored one:
I committed after every small refactoring (the commits step by step).
You can find the code in this repository in GitHub.
I mainly used it to practice with Enumerable functions.
This is the original code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Yatzy | |
def self.chance(d1, d2, d3, d4, d5) | |
total = 0 | |
total += d1 | |
total += d2 | |
total += d3 | |
total += d4 | |
total += d5 | |
return total | |
end | |
def self.yatzy(dice) | |
counts = [0]*(dice.length+1) | |
for die in dice do | |
counts[die-1] += 1 | |
end | |
for i in 0..counts.size do | |
if counts[i] == 5 | |
return 50 | |
end | |
end | |
return 0 | |
end | |
def self.ones( d1, d2, d3, d4, d5) | |
sum = 0 | |
if (d1 == 1) | |
sum += 1 | |
end | |
if (d2 == 1) | |
sum += 1 | |
end | |
if (d3 == 1) | |
sum += 1 | |
end | |
if (d4 == 1) | |
sum += 1 | |
end | |
if (d5 == 1) | |
sum += 1 | |
end | |
sum | |
end | |
def self.twos( d1, d2, d3, d4, d5) | |
sum = 0 | |
if (d1 == 2) | |
sum += 2 | |
end | |
if (d2 == 2) | |
sum += 2 | |
end | |
if (d3 == 2) | |
sum += 2 | |
end | |
if (d4 == 2) | |
sum += 2 | |
end | |
if (d5 == 2) | |
sum += 2 | |
end | |
return sum | |
end | |
def self.threes( d1, d2, d3, d4, d5) | |
s = 0 | |
if (d1 == 3) | |
s += 3 | |
end | |
if (d2 == 3) | |
s += 3 | |
end | |
if (d3 == 3) | |
s += 3 | |
end | |
if (d4 == 3) | |
s += 3 | |
end | |
if (d5 == 3) | |
s += 3 | |
end | |
return s | |
end | |
def initialize(d1, d2, d3, d4, _5) | |
@dice = [0]*5 | |
@dice[0] = d1 | |
@dice[1] = d2 | |
@dice[2] = d3 | |
@dice[3] = d4 | |
@dice[4] = _5 | |
end | |
def fours | |
sum = 0 | |
for at in Array 0..4 | |
if (@dice[at] == 4) | |
sum += 4 | |
end | |
end | |
return sum | |
end | |
def fives() | |
s = 0 | |
i = 0 | |
for i in (Range.new(0, @dice.size)) | |
if (@dice[i] == 5) | |
s = s + 5 | |
end | |
end | |
s | |
end | |
def sixes | |
sum = 0 | |
for at in 0..@dice.length | |
if (@dice[at] == 6) | |
sum = sum + 6 | |
end | |
end | |
return sum | |
end | |
def self.score_pair( d1, d2, d3, d4, d5) | |
counts = [0]*6 | |
counts[d1-1] += 1 | |
counts[d2-1] += 1 | |
counts[d3-1] += 1 | |
counts[d4-1] += 1 | |
counts[d5-1] += 1 | |
at = 0 | |
(0...6).each do |at| | |
if (counts[6-at-1] >= 2) | |
return (6-at)*2 | |
end | |
end | |
return 0 | |
end | |
def self.two_pair( d1, d2, d3, d4, d5) | |
counts = [0]*6 | |
counts[d1-1] += 1 | |
counts[d2-1] += 1 | |
counts[d3-1] += 1 | |
counts[d4-1] += 1 | |
counts[d5-1] += 1 | |
n = 0 | |
score = 0 | |
for i in Array 0..5 | |
if (counts[6-i-1] >= 2) | |
n = n+1 | |
score += (6-i) | |
end | |
end | |
if (n == 2) | |
return score * 2 | |
else | |
return 0 | |
end | |
end | |
def self.four_of_a_kind( _1, _2, d3, d4, d5) | |
tallies = [0]*6 | |
tallies[_1-1] += 1 | |
tallies[_2-1] += 1 | |
tallies[d3-1] += 1 | |
tallies[d4-1] += 1 | |
tallies[d5-1] += 1 | |
for i in (0..6) | |
if (tallies[i] >= 4) | |
return (i+1) * 4 | |
end | |
end | |
return 0 | |
end | |
def self.three_of_a_kind( d1, d2, d3, d4, d5) | |
t = [0]*6 | |
t[d1-1] += 1 | |
t[d2-1] += 1 | |
t[d3-1] += 1 | |
t[d4-1] += 1 | |
t[d5-1] += 1 | |
for i in [0,1,2,3,4,5] | |
if (t[i] >= 3) | |
return (i+1) * 3 | |
end | |
end | |
0 | |
end | |
def self.smallStraight( d1, d2, d3, d4, d5) | |
tallies = [0]*6 | |
tallies[d1-1] += 1 | |
tallies[d2-1] += 1 | |
tallies[d3-1] += 1 | |
tallies[d4-1] += 1 | |
tallies[d5-1] += 1 | |
(tallies[0] == 1 and | |
tallies[1] == 1 and | |
tallies[2] == 1 and | |
tallies[3] == 1 and | |
tallies[4] == 1) ? 15 : 0 | |
end | |
def self.largeStraight( d1, d2, d3, d4, d5) | |
tallies = [0]*6 | |
tallies[d1-1] += 1 | |
tallies[d2-1] += 1 | |
tallies[d3-1] += 1 | |
tallies[d4-1] += 1 | |
tallies[d5-1] += 1 | |
if (tallies[1] == 1 and tallies[2] == 1 and tallies[3] == 1 and tallies[4] == 1 and tallies[5] == 1) | |
return 20 | |
end | |
return 0 | |
end | |
def self.fullHouse( d1, d2, d3, d4, d5) | |
tallies = [] | |
_2 = false | |
i = 0 | |
_2_at = 0 | |
_3 = false | |
_3_at = 0 | |
tallies = [0]*6 | |
tallies[d1-1] += 1 | |
tallies[d2-1] += 1 | |
tallies[d3-1] += 1 | |
tallies[d4-1] += 1 | |
tallies[d5-1] += 1 | |
for i in Array 0..5 | |
if (tallies[i] == 2) | |
_2 = true | |
_2_at = i+1 | |
end | |
end | |
for i in Array 0..5 | |
if (tallies[i] == 3) | |
_3 = true | |
_3_at = i+1 | |
end | |
end | |
if (_2 and _3) | |
return _2_at * 2 + _3_at * 3 | |
else | |
return 0 | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Yatzy | |
def self.chance *dies | |
dies.reduce(:+) | |
end | |
def self.yatzy *dies | |
return 50 if all_equal?(dies) | |
return 0 | |
end | |
def self.ones *dies | |
compute_score(dies, 1) | |
end | |
def self.twos *dies | |
compute_score(dies, 2) | |
end | |
def self.threes *dies | |
compute_score(dies, 3) | |
end | |
def self.fours *dies | |
compute_score(dies, 4) | |
end | |
def self.fives *dies | |
compute_score(dies, 5) | |
end | |
def self.sixes *dies | |
compute_score(dies, 6) | |
end | |
def self.one_pair *dies | |
compute_group_of_a_kind_score(dies, 2) | |
end | |
def self.two_pairs *dies | |
pairs = extract_groups_with_equal_or_larger_size(dies, 2) | |
pairs.keys.reduce(:+) * 2 | |
end | |
def self.four_of_a_kind *dies | |
compute_group_of_a_kind_score(dies, 4) | |
end | |
def self.three_of_a_kind *dies | |
compute_group_of_a_kind_score(dies, 3) | |
end | |
def self.small_straight *dies | |
return 15 if small_straight?(dies) | |
return 0 | |
end | |
def self.large_straight *dies | |
return 20 if large_straight?(dies) | |
return 0 | |
end | |
def self.full_house *dies | |
return 0 unless full_house?(dies) | |
compute_full_house_score(dies) | |
end | |
private | |
def self.compute_score dies, die_value | |
dies_with_value = dies.select {|die| die == die_value} | |
die_value * dies_with_value.size | |
end | |
def self.compute_frequencies dies | |
dies.inject({}) do |frequencies_so_far, die| | |
if frequencies_so_far.include?(die) | |
frequencies_so_far[die] += 1 | |
else | |
frequencies_so_far.merge!({die => 1}) | |
end | |
frequencies_so_far | |
end | |
end | |
def self.extract_groups_with_equal_or_larger_size dies, size | |
extract_groups( | |
dies, | |
lambda { |frequency| frequency >= size } | |
) | |
end | |
def self.compute_group_of_a_kind_score dies, group_size | |
group = extract_groups_with_equal_or_larger_size(dies, group_size) | |
compute_group_score(group, group_size) | |
end | |
def self.compute_group_score group, group_size | |
(group.keys.max || 0) * group_size | |
end | |
def self.small_straight? dies | |
frequencies = compute_frequencies(dies) | |
frequencies.all? do |die, frequency| | |
frequency == 1 && die != 6 | |
end | |
end | |
def self.large_straight? dies | |
frequencies = compute_frequencies(dies) | |
frequencies.all? do |die, frequency| | |
frequency == 1 && die != 1 | |
end | |
end | |
def self.all_equal? dies | |
dies.uniq.size == 1 | |
end | |
def self.compute_full_house_score dies | |
pairs_score = compute_full_house_group(dies, 2) | |
triplets_score = compute_full_house_group(dies, 3) | |
pairs_score + triplets_score | |
end | |
def self.full_house? dies | |
triplets = extract_groups_with_equal_size(dies, 3) | |
pairs = extract_groups_with_equal_size(dies, 2) | |
only_one_pair = pairs.size == 1 | |
only_one_triplet = triplets.size == 1 | |
only_one_pair && only_one_triplet | |
end | |
def self.extract_groups_with_equal_size dies, size | |
extract_groups( | |
dies, | |
lambda { |frequency| frequency == size } | |
) | |
end | |
def self.compute_full_house_group dies, group_size | |
group = extract_groups_with_equal_size(dies, group_size) | |
compute_group_score(group, group_size) | |
end | |
def self.extract_groups dies, predicate | |
compute_frequencies(dies).select do |_, frequency| | |
predicate.call(frequency) | |
end | |
end | |
end |
You can find the code in this repository in GitHub.
Friday, September 18, 2015
Friday, September 11, 2015
Last Pet Project: Glowworm Swarm Optimization in Clojure
Lately I've been working bit by bit in an implementation of the Glowworm Swarm Optimization algorithm (GSO) in Clojure.
Some time ago I implemented the GSO algorithm in C++ to practice TDD.
I decided to implement it it again in Clojure to practice with something larger that Exercism exercises or katas an yet small and familiar enough to finish it in spare bits of time.
This is the code of the resulting Clojure GSO on GitHub.
This GSO Clojure version is much shorter than its C++ version.
Aside from practicing Clojure, I've learned many other things while doing it.
I'll try to enumerate them here:
I'd like to thank Álvaro García, Natxo Cabré and Francesc Guillén from the Clojure Developers Barcelona meetup for their feedback and Brian Jiménez for rubbing elbows with me in the first C++ version.
Some time ago I implemented the GSO algorithm in C++ to practice TDD.
I decided to implement it it again in Clojure to practice with something larger that Exercism exercises or katas an yet small and familiar enough to finish it in spare bits of time.
This is the code of the resulting Clojure GSO on GitHub.
This GSO Clojure version is much shorter than its C++ version.
Aside from practicing Clojure, I've learned many other things while doing it.
I'll try to enumerate them here:
- Mixing Java and Clojure code in order to use a Sean Luke's Mersenne Twister Java implementation.
- I learned more about Midje's checkers.
- dire library.
- Decomposing a bigger Clojure application in several name spaces according to roles and responsibilities.
- Using maps to model the data.
- Struggling to make the code more readable and keeping functions at the same level of abstraction.
- Using only higher-order functions to compose the algorithm (I set myself the constraint to use no global configuration maps).
- Taking advantage of those higher-order functions to test the different parts of the algorithm in isolation.
- Separating the code that builds the graph of functions that compose the algorithm from the algorithm itself.
- Where would I apply a library like component instead of relying only n higher-order functions.
- Many other little Clojure things.
I'd like to thank Álvaro García, Natxo Cabré and Francesc Guillén from the Clojure Developers Barcelona meetup for their feedback and Brian Jiménez for rubbing elbows with me in the first C++ version.
Wednesday, September 9, 2015
Kata: "Sieve of Eratosthenes" test-driven and explained step by step
Yesterday we practiced doing the Sieve of Eratosthenes kata at a Barcelona Software Craftsmanship event.
My partner Fernando Mora and I used TDD in Scala to write the code.
Today I did it once again in Clojure.
I'd like to explain here how I did it step by step in order to share it with the Barcelona Software Craftsmanship members.
I started by writing this test:
which I quickly got to green by just hard-coding the response:
In this first test I used wishful thinking to get the function and signature I wished.
Then I wrote the following test:
which drove me to generalize the code substituting the hard-coded list by a code that generated a list that was valid for both the first and the second test:
The next test was the first one that drove me to start eliminating multiples of a number, in this case the multiples of 2:
I made it quickly pass by using filter to only keep those that are not multiples of 2 and 2 itself:
Alternatively, I could have taken a smaller step by just eliminating 4, then go on triangulating to also eliminate 6 and finally refactor out the duplication by eliminating all the multiples of 2 that are different from 2.
Since the implementation was obvious and I could rely on filter, I decided not to follow that path and took a larger step.
I've noticed that my TDD baby steps tend to be larger in functional languages. I think the reason is, on one hand, the REPL which complements TDD by providing a faster feedback loop for triangulation and trying things out and, on the other hand, the power of sequence functions in those languages (in the case of this kata the Scala and Clojure ones).
Once the test was passing I started to refactor that ugly one-liner and got to this more readable version:
in which I extracted two helpers and used remove instead of filter to better express the idea of sieving.
My next goal was to write a test to drive me to generalize the code a bit more by eliminating the hard-coded number 2 in line 10 of the code.
To do it I just needed a test that forced the code to also eliminate just the multiples of 3:
Again I could have triangulated to first eliminate 9 and then 12 before refactoring but there was an easier way at this point: to introduce recursion.
But before doing that, I quickly went to green by calling the sieve function twice to eliminate the multiples of 2 and 3:
With this tiny step I both highlighted the recursive pattern and provided a safe place from which to start using refactoring to introduce recursion.
In this case I think that having tried to directly introduce recursion to make the test pass would have been too large a step to take, so I played safe.
Once in green again I safely refactored the code to introduce recursion:
Notice that this version of the code is the first one that solves the kata.
From this point on I just refactored the code trying to make it a bit more readable until I got to this version:
Finally, I wrote a more thorough test that made the stepping-stone tests that helped me drive the solution redundant, so I deleted them:
If you want to have a closer look at the process I followed, please check the commits list where I've also included the REPL history. You can also see all the code in this GitHub repository.
That's all.
I'd like to thank Barcelona Software Craftsmanship members for practicing together every two weeks, especially Álvaro García for facilitating this last kata, and eBay España for kindly having us yesterday (and on many previous events) in their Barcelona office.
My partner Fernando Mora and I used TDD in Scala to write the code.
Today I did it once again in Clojure.
I'd like to explain here how I did it step by step in order to share it with the Barcelona Software Craftsmanship members.
I started by writing this test:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core-test | |
(:use midje.sweet) | |
(:use [eratosthenes-sieve.core])) | |
(facts | |
"about Eratosthenes sieve" | |
(fact | |
"it returns all the primes up to a given number" | |
(primes-up-to 2) => [2])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core) | |
(defn primes-up-to [n] | |
[2]) |
Then I wrote the following test:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core-test | |
(:use midje.sweet) | |
(:use [eratosthenes-sieve.core])) | |
(facts | |
"about Eratosthenes sieve" | |
(fact | |
"it returns all the primes up to a given number" | |
(primes-up-to 2) => [2] | |
(primes-up-to 3) => [2 3])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core) | |
(defn primes-up-to [n] | |
(range 2 (inc n))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core-test | |
(:use midje.sweet) | |
(:use [eratosthenes-sieve.core])) | |
(facts | |
"about Eratosthenes sieve" | |
(fact | |
"it returns all the primes up to a given number" | |
(primes-up-to 2) => [2] | |
(primes-up-to 3) => [2 3] | |
(primes-up-to 5) => [2 3 5])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core) | |
(defn primes-up-to [n] | |
(filter #(or (not= 0 (mod % 2)) (= % 2)) | |
(range 2 (inc n)))) |
Since the implementation was obvious and I could rely on filter, I decided not to follow that path and took a larger step.
I've noticed that my TDD baby steps tend to be larger in functional languages. I think the reason is, on one hand, the REPL which complements TDD by providing a faster feedback loop for triangulation and trying things out and, on the other hand, the power of sequence functions in those languages (in the case of this kata the Scala and Clojure ones).
Once the test was passing I started to refactor that ugly one-liner and got to this more readable version:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core) | |
(defn- integers-up-to [n] | |
(range 2 (inc n))) | |
(defn- multiple-of? [n p] | |
(and (zero? (mod n p)) (not= n p))) | |
(defn primes-up-to [n] | |
(remove #(multiple-of? % 2) (integers-up-to n))) |
My next goal was to write a test to drive me to generalize the code a bit more by eliminating the hard-coded number 2 in line 10 of the code.
To do it I just needed a test that forced the code to also eliminate just the multiples of 3:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core-test | |
(:use midje.sweet) | |
(:use [eratosthenes-sieve.core])) | |
(facts | |
"about Eratosthenes sieve" | |
(fact | |
"it returns all the primes up to a given number" | |
(primes-up-to 2) => [2] | |
(primes-up-to 3) => [2 3] | |
(primes-up-to 5) => [2 3 5] | |
(primes-up-to 11) => [2 3 5 7 11])) |
But before doing that, I quickly went to green by calling the sieve function twice to eliminate the multiples of 2 and 3:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core) | |
(defn- integers-up-to [n] | |
(range 2 (inc n))) | |
(defn- multiple-of? [n p] | |
(and (zero? (mod n p)) (not= n p))) | |
(defn- sieve [numbers prime] | |
(remove #(multiple-of? % prime) numbers)) | |
(defn primes-up-to [n] | |
(sieve (sieve (integers-up-to n) 2) 3)) |
In this case I think that having tried to directly introduce recursion to make the test pass would have been too large a step to take, so I played safe.
Once in green again I safely refactored the code to introduce recursion:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core) | |
(defn- integers-up-to [n] | |
(range 2 (inc n))) | |
(defn- multiple-of? [n p] | |
(and (zero? (mod n p)) (not= n p))) | |
(defn- sieve [numbers prime] | |
(let [sieved (remove #(multiple-of? % prime) numbers) | |
next-prime (first (drop-while #(<= % prime) sieved))] | |
(if (nil? next-prime) | |
sieved | |
(recur sieved next-prime)))) | |
(defn primes-up-to [n] | |
(sieve (integers-up-to n) 2)) |
From this point on I just refactored the code trying to make it a bit more readable until I got to this version:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core) | |
(defn- integers-up-to [n] | |
(range 2 (inc n))) | |
(defn- multiple-of? [n p] | |
(and (zero? (mod n p)) (not= n p))) | |
(defn- next-prime [prime numbers] | |
(first (drop-while #(<= % prime) numbers))) | |
(defn- sieve-multiples-of [prime numbers] | |
(remove #(multiple-of? % prime) numbers)) | |
(defn- sieve [numbers] | |
(loop [primes numbers prime 2] | |
(let [sieved (sieve-multiples-of prime primes)] | |
(if-let [prime (next-prime prime sieved)] | |
(recur sieved prime) | |
primes)))) | |
(defn primes-up-to [n] | |
(sieve (integers-up-to n))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core-test | |
(:use midje.sweet) | |
(:use [eratosthenes-sieve.core])) | |
(facts | |
"about Eratosthenes sieve" | |
(fact | |
"it returns all the primes up to a given number" | |
(primes-up-to 100) => [2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97])) |
That's all.
I'd like to thank Barcelona Software Craftsmanship members for practicing together every two weeks, especially Álvaro García for facilitating this last kata, and eBay España for kindly having us yesterday (and on many previous events) in their Barcelona office.
Saturday, September 5, 2015
Interesting Podcast: "Object Oriented Programming in Rails with Jim Weirich"
I've just listened to this great Ruby Rogues podcast with Jim Weirich talking about Object Oriented Programming in Rails:
Interesting Talk: "Writing Testable JavaScript"
I've just watched this great talk by Rebecca Murphey:
These are the slides.
There is also a great article about the techniques and the example shown in the talk.
And here is the final version of the example code.
These are the slides.
There is also a great article about the techniques and the example shown in the talk.
And here is the final version of the example code.
Subscribe to:
Posts (Atom)