trackler 1.0.4.0 → 1.0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/common/CONTRIBUTING.md +17 -2
  3. data/common/exercises/anagram/canonical-data.json +0 -14
  4. data/common/exercises/pig-latin/canonical-data.json +6 -1
  5. data/common/exercises/raindrops/description.md +4 -4
  6. data/common/exercises/raindrops/metadata.yml +1 -1
  7. data/common/exercises/roman-numerals/metadata.yml +1 -1
  8. data/lib/trackler/version.rb +1 -1
  9. data/tracks/c/config.json +12 -1
  10. data/tracks/c/exercises/roman-numerals/makefile +16 -0
  11. data/tracks/c/exercises/roman-numerals/src/example.c +43 -0
  12. data/tracks/c/exercises/roman-numerals/src/example.h +6 -0
  13. data/tracks/c/exercises/roman-numerals/test/test_roman_numerals.c +126 -0
  14. data/tracks/c/exercises/roman-numerals/test/vendor/unity.c +1300 -0
  15. data/tracks/c/exercises/roman-numerals/test/vendor/unity.h +274 -0
  16. data/tracks/c/exercises/roman-numerals/test/vendor/unity_internals.h +701 -0
  17. data/tracks/clojure/exercises/flatten-array/project.clj +4 -0
  18. data/tracks/csharp/exercises/protein-translation/ProteinTranslationTest.cs +1 -1
  19. data/tracks/elixir/exercises/bowling/bowling_test.exs +157 -62
  20. data/tracks/elixir/exercises/bowling/example.exs +33 -4
  21. data/tracks/erlang/config.json +176 -36
  22. data/tracks/go/.travis.yml +7 -1
  23. data/tracks/go/bin/test-without-stubs +29 -0
  24. data/tracks/go/exercises/robot-simulator/defs.go +1 -1
  25. data/tracks/go/exercises/variable-length-quantity/variable_length_quantity_test.go +31 -25
  26. data/tracks/haskell/.travis.yml +9 -5
  27. data/tracks/haskell/config.json +127 -121
  28. data/tracks/haskell/exercises/leap/src/LeapYear.hs +1 -1
  29. data/tracks/haskell/exercises/space-age/HINTS.md +14 -2
  30. data/tracks/haskell/exercises/space-age/{src/Example.hs → examples/success-double/SpaceAge.hs} +0 -0
  31. data/tracks/haskell/exercises/space-age/examples/success-double/package.yaml +19 -0
  32. data/tracks/haskell/exercises/space-age/examples/success-rational/SpaceAge.hs +24 -0
  33. data/tracks/haskell/exercises/space-age/examples/success-rational/package.yaml +19 -0
  34. data/tracks/haskell/exercises/space-age/src/SpaceAge.hs +4 -1
  35. data/tracks/haskell/exercises/space-age/test/Tests.hs +12 -5
  36. data/tracks/haskell/exercises/word-count/HINTS.md +21 -0
  37. data/tracks/haskell/exercises/word-count/examples/success-newtype/WordCount.hs +31 -0
  38. data/tracks/haskell/exercises/word-count/examples/success-newtype/package.yaml +21 -0
  39. data/tracks/haskell/exercises/word-count/examples/success-simple/WordCount.hs +13 -0
  40. data/tracks/haskell/exercises/word-count/examples/success-simple/package.yaml +20 -0
  41. data/tracks/haskell/exercises/word-count/package.yaml +0 -2
  42. data/tracks/haskell/exercises/word-count/src/WordCount.hs +2 -1
  43. data/tracks/haskell/exercises/word-count/test/Tests.hs +15 -6
  44. data/tracks/haskell/exercises/zebra-puzzle/package.yaml +19 -0
  45. data/tracks/haskell/exercises/zebra-puzzle/src/Example.hs +113 -0
  46. data/tracks/haskell/exercises/zebra-puzzle/src/ZebraPuzzle.hs +12 -0
  47. data/tracks/haskell/exercises/zebra-puzzle/stack.yaml +1 -0
  48. data/tracks/haskell/exercises/zebra-puzzle/test/Tests.hs +13 -0
  49. data/tracks/javascript/exercises/beer-song/beer-song.spec.js +5 -0
  50. data/tracks/lisp/config.json +147 -0
  51. data/tracks/ocaml/config.json +12 -0
  52. data/tracks/ocaml/exercises/atbash-cipher/.merlin +5 -0
  53. data/tracks/ocaml/exercises/atbash-cipher/HINTS.md +17 -0
  54. data/tracks/ocaml/exercises/atbash-cipher/Makefile +11 -0
  55. data/tracks/ocaml/exercises/atbash-cipher/atbash_cipher.mli +5 -0
  56. data/tracks/ocaml/exercises/atbash-cipher/example.ml +47 -0
  57. data/tracks/ocaml/exercises/atbash-cipher/test.ml +37 -0
  58. data/tracks/ocaml/exercises/meetup/.merlin +5 -0
  59. data/tracks/ocaml/exercises/meetup/Makefile +11 -0
  60. data/tracks/ocaml/exercises/meetup/example.ml +63 -0
  61. data/tracks/ocaml/exercises/meetup/meetup.mli +7 -0
  62. data/tracks/ocaml/exercises/meetup/test.ml +786 -0
  63. data/tracks/php/exercises/bowling/bowling_test.php +233 -10
  64. data/tracks/php/exercises/bowling/example.php +49 -6
  65. data/tracks/python/.travis.yml +5 -0
  66. data/tracks/python/config.json +33 -1
  67. data/tracks/python/exercises/flatten-array/example.py +18 -0
  68. data/tracks/python/exercises/flatten-array/flatten_array_test.py +40 -0
  69. data/tracks/python/exercises/robot-name/robot_name_test.py +8 -2
  70. data/tracks/r/config.json +32 -8
  71. data/tracks/ruby/bin/enable-executable +9 -5
  72. data/tracks/ruby/bin/executable-tests-check +7 -3
  73. data/tracks/ruby/bin/local-status-check +4 -4
  74. data/tracks/ruby/config.json +8 -1
  75. data/tracks/ruby/exercises/acronym/acronym_test.rb +0 -1
  76. data/tracks/ruby/exercises/anagram/.version +1 -0
  77. data/tracks/ruby/exercises/anagram/anagram_test.rb +101 -31
  78. data/tracks/ruby/exercises/anagram/example.rb +4 -0
  79. data/tracks/ruby/exercises/anagram/example.tt +19 -0
  80. data/tracks/ruby/exercises/gigasecond/.version +1 -1
  81. data/tracks/ruby/exercises/gigasecond/example.rb +1 -1
  82. data/tracks/ruby/exercises/gigasecond/gigasecond_test.rb +2 -2
  83. data/tracks/ruby/exercises/isogram/.version +1 -0
  84. data/tracks/ruby/exercises/isogram/example.rb +10 -0
  85. data/tracks/ruby/exercises/isogram/example.tt +20 -0
  86. data/tracks/ruby/exercises/isogram/isogram_test.rb +90 -0
  87. data/tracks/ruby/exercises/poker/example.rb +97 -39
  88. data/tracks/ruby/exercises/poker/poker_test.rb +67 -36
  89. data/tracks/ruby/exercises/sieve/.version +1 -0
  90. data/tracks/ruby/exercises/sieve/example.rb +4 -0
  91. data/tracks/ruby/exercises/sieve/example.tt +21 -0
  92. data/tracks/ruby/exercises/sieve/sieve_test.rb +36 -13
  93. data/tracks/ruby/exercises/word-count/example.tt +4 -3
  94. data/tracks/ruby/exercises/word-count/word_count_test.rb +38 -48
  95. data/tracks/ruby/lib/anagram_cases.rb +42 -0
  96. data/tracks/ruby/lib/isogram_cases.rb +24 -0
  97. data/tracks/ruby/lib/sieve_cases.rb +33 -0
  98. data/tracks/ruby/lib/word_count_cases.rb +19 -0
  99. data/tracks/scala/config.json +21 -0
  100. data/tracks/scala/docs/RESOURCES.md +1 -0
  101. data/tracks/scala/exercises/bracket-push/build.sbt +4 -0
  102. data/tracks/scala/exercises/bracket-push/example.scala +23 -0
  103. data/tracks/scala/exercises/bracket-push/src/main/scala/.keep +0 -0
  104. data/tracks/scala/exercises/bracket-push/src/test/scala/BracketsTest.scala +68 -0
  105. data/tracks/scala/exercises/change/src/test/scala/ChangeTest.scala +7 -0
  106. data/tracks/scala/exercises/sgf-parsing/build.sbt +7 -0
  107. data/tracks/scala/exercises/sgf-parsing/example.scala +66 -0
  108. data/tracks/scala/exercises/sgf-parsing/src/main/scala/.keep +0 -0
  109. data/tracks/scala/exercises/sgf-parsing/src/main/scala/Sgf.scala +17 -0
  110. data/tracks/scala/exercises/sgf-parsing/src/test/scala/SgfTest.scala +66 -0
  111. data/tracks/scala/exercises/zebra-puzzle/build.sbt +3 -0
  112. data/tracks/scala/exercises/zebra-puzzle/example.scala +152 -0
  113. data/tracks/scala/exercises/zebra-puzzle/src/main/scala/.keep +0 -0
  114. data/tracks/scala/exercises/zebra-puzzle/src/main/scala/ZebraPuzzle.scala +14 -0
  115. data/tracks/scala/exercises/zebra-puzzle/src/test/scala/ZebraPuzzleTest.scala +11 -0
  116. data/tracks/scala/testgen/src/main/scala/BracketPushTestGenerator.scala +44 -0
  117. metadata +63 -3
  118. data/tracks/haskell/exercises/word-count/src/Example.hs +0 -8
@@ -6,6 +6,7 @@ Exercism provides exercises and feedback but can be difficult to jump into for t
6
6
  * [Programming Scala](http://www.oreilly.com/ofps/)
7
7
  * [StackOverflow](http://stackoverflow.com/)
8
8
  * [Scala Exercises](https://www.scala-exercises.org/std_lib)
9
+ * [The Neophyte's Guide to Scala](http://danielwestheide.com/scala/neophytes.html)
9
10
 
10
11
  ## Linting
11
12
 
@@ -0,0 +1,4 @@
1
+ scalaVersion := "2.11.8"
2
+
3
+ libraryDependencies += "org.scalatest" % "scalatest_2.11" % "2.2.5" % "test"
4
+ libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4"
@@ -0,0 +1,23 @@
1
+ import scala.util.parsing.combinator.RegexParsers
2
+
3
+ object Brackets extends RegexParsers {
4
+ lazy val t = "[^\\[\\]\\(\\)\\{\\}]+".r
5
+
6
+ private def paren: Parser[String] =
7
+ ("(" ~ rep1(t | paren) ~ ")" |
8
+ "[" ~ rep1(t | paren) ~ "]" |
9
+ "{" ~ rep1(t | paren) ~ "}" |
10
+ "(" ~ ")" |
11
+ "[" ~ "]" |
12
+ "{" ~ "}" |
13
+ t) ^^ {
14
+ case _ => ""
15
+ }
16
+
17
+ private def all = rep(paren)
18
+
19
+ def areBalanced(s: String) = this.parseAll(all, s) match {
20
+ case NoSuccess(_, _) => false
21
+ case Success(_, _) => true
22
+ }
23
+ }
@@ -0,0 +1,68 @@
1
+ import org.scalatest.{FunSuite, Matchers}
2
+
3
+ class BracketsTest extends FunSuite with Matchers {
4
+ test("paired square brackets") {
5
+ pending
6
+ Brackets.areBalanced("""[]""") should be (true)
7
+ }
8
+
9
+ test("empty string") {
10
+ pending
11
+ Brackets.areBalanced("""""") should be (true)
12
+ }
13
+
14
+ test("unpaired brackets") {
15
+ pending
16
+ Brackets.areBalanced("""[[""") should be (false)
17
+ }
18
+
19
+ test("wrong ordered brackets") {
20
+ pending
21
+ Brackets.areBalanced("""}{""") should be (false)
22
+ }
23
+
24
+ test("paired with whitespace") {
25
+ pending
26
+ Brackets.areBalanced("""{ }""") should be (true)
27
+ }
28
+
29
+ test("simple nested brackets") {
30
+ pending
31
+ Brackets.areBalanced("""{[]}""") should be (true)
32
+ }
33
+
34
+ test("several paired brackets") {
35
+ pending
36
+ Brackets.areBalanced("""{}[]""") should be (true)
37
+ }
38
+
39
+ test("paired and nested brackets") {
40
+ pending
41
+ Brackets.areBalanced("""([{}({}[])])""") should be (true)
42
+ }
43
+
44
+ test("unopened closing brackets") {
45
+ pending
46
+ Brackets.areBalanced("""{[)][]}""") should be (false)
47
+ }
48
+
49
+ test("unpaired and nested brackets") {
50
+ pending
51
+ Brackets.areBalanced("""([{])""") should be (false)
52
+ }
53
+
54
+ test("paired and wrong nested brackets") {
55
+ pending
56
+ Brackets.areBalanced("""[({]})""") should be (false)
57
+ }
58
+
59
+ test("math expression") {
60
+ pending
61
+ Brackets.areBalanced("""(((185 + 223.85) * 15) - 543)/2""") should be (true)
62
+ }
63
+
64
+ test("complex latex expression") {
65
+ pending
66
+ Brackets.areBalanced("""\left(\begin{array}{cc} \frac{1}{3} & x\\ \mathrm{e}^{x} &... x^2 \end{array}\right)""") should be (true)
67
+ }
68
+ }
@@ -6,31 +6,38 @@ class ChangeTest extends FunSuite with Matchers {
6
6
  }
7
7
 
8
8
  test("multiple coin change") {
9
+ pending
9
10
  Change.findFewestCoins(15, List(1, 5, 10, 25, 100)) should be (Some(List(5, 10)))
10
11
  }
11
12
 
12
13
  test("change with Lilliputian Coins") {
14
+ pending
13
15
  Change.findFewestCoins(23, List(1, 4, 15, 20, 50)) should be (Some(List(4, 4, 15)))
14
16
  }
15
17
 
16
18
  test("change with Lower Elbonia Coins") {
19
+ pending
17
20
  Change.findFewestCoins(63, List(1, 5, 10, 21, 25)) should be (Some(List(21, 21, 21)))
18
21
  }
19
22
 
20
23
  test("large target values") {
24
+ pending
21
25
  Change.findFewestCoins(999, List(1, 2, 5, 10, 20, 50, 100)) should
22
26
  be (Some(List(2, 2, 5, 20, 20, 50, 100, 100, 100, 100, 100, 100, 100, 100, 100)))
23
27
  }
24
28
 
25
29
  test("no coins make 0 change") {
30
+ pending
26
31
  Change.findFewestCoins(0, List(1, 5, 10, 21, 25)) should be (Some(List()))
27
32
  }
28
33
 
29
34
  test("error testing for change smaller than the smallest of coins") {
35
+ pending
30
36
  Change.findFewestCoins(3, List(5, 10)) should be (None)
31
37
  }
32
38
 
33
39
  test("cannot find negative change values") {
40
+ pending
34
41
  Change.findFewestCoins(-5, List(1, 2, 5)) should be (None)
35
42
  }
36
43
  }
@@ -0,0 +1,7 @@
1
+ scalaVersion := "2.11.8"
2
+
3
+ libraryDependencies += "org.scalatest" % "scalatest_2.11" % "2.2.5" % "test"
4
+
5
+ libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4"
6
+
7
+
@@ -0,0 +1,66 @@
1
+ import scala.util.parsing.combinator.RegexParsers
2
+
3
+ object Sgf extends RegexParsers {
4
+
5
+ type Tree[A] = Node[A] // to separate the type from the constructor, cf. Haskell's Data.Tree
6
+ type Forest[A] = List[Tree[A]]
7
+ case class Node[A](rootLabel: A, subForest: Forest[A] = List())
8
+
9
+ // A tree of nodes.
10
+ type SgfTree = Tree[SgfNode]
11
+
12
+ // A node is a property list, each key can only occur once.
13
+ // Keys may have multiple values associated with them.
14
+ type SgfNode = Map[String, List[String]]
15
+
16
+ override val skipWhitespace = false
17
+
18
+ private implicit def parseResultToOption[T](parseResult: ParseResult[T]): Option[T] =
19
+ parseResult map (Some(_)) getOrElse None
20
+
21
+ def parseSgf(text: String): Option[SgfTree] =
22
+ parseAll(sgfGameTree, text)
23
+
24
+ private val sgfGameTree: Parser[SgfTree] =
25
+ ("(" ~ rep1(sgfNode) ~ rep(sgfGameTree) ~ ")") ^^ {
26
+ case _ ~ (rootNode::subNodes) ~ subTrees ~ _ =>
27
+ val subNodeForest: List[SgfTree] = subNodes map (Node(_))
28
+ Node(rootNode, subNodeForest ++ subTrees)
29
+ } named "sgfGameTree"
30
+
31
+ private val sgfNode: Parser[SgfNode] =
32
+ ";" ~ (sgfProperty | emptySgfNode) ^^ {
33
+ case _ ~ sgfNode => sgfNode
34
+ } named "sgfNode"
35
+
36
+ private val emptySgfNode: Parser[SgfNode] =
37
+ "" ^^ const(Map())
38
+
39
+ private def sgfProperty: Parser[SgfNode] =
40
+ propIdent ~ propValues ^^ {
41
+ case identifier ~ values => Map(identifier -> values)
42
+ } named "sgfProperty"
43
+
44
+ private val propIdent: Parser[String] = "[A-Z]".r named "propIdent"
45
+
46
+ private val propValues: Parser[List[String]] = rep1(propValue)
47
+
48
+ private val propValue: Parser[String] =
49
+ "[" ~ rep1(propValuePart) ~ "]" ^^ {
50
+ case _ ~ values ~ _ => values mkString
51
+ } named "propValue"
52
+
53
+ private val propValuePart: Parser[String] = {
54
+ implicit class AsStringParser(self: String) { def p: Parser[String] = self }
55
+ val ignore = const("") _
56
+
57
+ val escapedNewline: Parser[String] = "\\\n".p ^^ ignore
58
+ val escapedChar: Parser[String] = """\\.""".r ^^ (_.takeRight(1))
59
+ val whitespace: Parser[String] = """\s""".r ^^ const(" ")
60
+ val ident: Parser[String] = "[^]]".r
61
+
62
+ escapedNewline | escapedChar | whitespace | ident
63
+ }
64
+
65
+ private def const[A](a: A)(ignore: Any) = a
66
+ }
@@ -0,0 +1,17 @@
1
+ import scala.util.parsing.combinator.RegexParsers
2
+
3
+ object Sgf extends RegexParsers {
4
+
5
+ type Tree[A] = Node[A] // to separate the type from the constructor, cf. Haskell's Data.Tree
6
+ type Forest[A] = List[Tree[A]]
7
+ case class Node[A](rootLabel: A, subForest: Forest[A] = List())
8
+
9
+ // A tree of nodes.
10
+ type SgfTree = Tree[SgfNode]
11
+
12
+ // A node is a property list, each key can only occur once.
13
+ // Keys may have multiple values associated with them.
14
+ type SgfNode = Map[String, List[String]]
15
+
16
+ def parseSgf(text: String): Option[SgfTree] = ???
17
+ }
@@ -0,0 +1,66 @@
1
+ import org.scalatest.{FunSuite, Matchers}
2
+ import Sgf._
3
+
4
+ class SgfTest extends FunSuite with Matchers {
5
+ test("parse \"\"") {
6
+ Sgf.parseSgf("") should be (None)
7
+ }
8
+
9
+ test("parse \"()\"") {
10
+ pending
11
+ Sgf.parseSgf("()") should be (None)
12
+ }
13
+
14
+ test("parse \";\"") {
15
+ pending
16
+ Sgf.parseSgf(";") should be (None)
17
+ }
18
+
19
+ test("parse \"(;)\"") {
20
+ pending
21
+ Sgf.parseSgf("(;)") should be (Some(Node(Map())))
22
+ }
23
+
24
+ test("parse \"(;A[B])\"") {
25
+ Sgf.parseSgf("(;A[B])") should be (Some(Node(Map("A" -> List("B")))))
26
+ }
27
+
28
+ test("parse \"(;a)\"") {
29
+ pending
30
+ Sgf.parseSgf("(;a)") should be (None)
31
+ }
32
+
33
+ test("parse \"(;a[b])\"") {
34
+ pending
35
+ Sgf.parseSgf("(;a[b])") should be (None)
36
+ }
37
+
38
+ test("parse \"(;Aa[b])\"") {
39
+ pending
40
+ Sgf.parseSgf("(;Aa[b])") should be (None)
41
+ }
42
+
43
+ test("parse \"(;A[B];B[C])\"") {
44
+ pending
45
+ Sgf.parseSgf("(;A[B];B[C])") should be (
46
+ Some(Node(Map("A" -> List("B")), List(Node(Map("B" -> List("C")))))))
47
+ }
48
+
49
+ test("parse \"(;A[B](;B[C])(;C[D]))\"") {
50
+ pending
51
+ Sgf.parseSgf("(;A[B](;B[C])(;C[D]))") should be (
52
+ Some(Node(Map("A" -> List("B")), List(Node(Map("B" -> List("C"))),
53
+ Node(Map("C" -> List("D")))))))
54
+ }
55
+
56
+ test("parse \"(;A[b][c][d])\"") {
57
+ pending
58
+ Sgf.parseSgf("(;A[b][c][d])") should be (Some(Node(Map("A" -> List("b", "c", "d")))))
59
+ }
60
+
61
+ test("""parse "(;A[\\]b\nc\\\nd\t\te\\\\ \\\n\\]])"""") {
62
+ pending
63
+ Sgf.parseSgf("(;A[\\]b\nc\\\nd\t\te\\\\ \\\n\\]])") should be (
64
+ Some(Node(Map("A" -> List("]b cd e\\ ]")))))
65
+ }
66
+ }
@@ -0,0 +1,3 @@
1
+ scalaVersion := "2.11.8"
2
+
3
+ libraryDependencies += "org.scalatest" % "scalatest_2.11" % "2.2.5" % "test"
@@ -0,0 +1,152 @@
1
+ object ZebraPuzzle {
2
+
3
+ trait HousePart
4
+
5
+ sealed trait Color extends HousePart
6
+ case object Red extends Color
7
+ case object Green extends Color
8
+ case object Yellow extends Color
9
+ case object Blue extends Color
10
+ case object Ivory extends Color
11
+ object Color { val values: List[Color] = List(Red, Green, Yellow, Blue, Ivory) }
12
+
13
+ sealed trait Resident extends HousePart
14
+ case object Englishman extends Resident
15
+ case object Spaniard extends Resident
16
+ case object Ukrainian extends Resident
17
+ case object Norwegian extends Resident
18
+ case object Japanese extends Resident
19
+ object Resident {
20
+ val values: List[Resident] =
21
+ List(Englishman, Spaniard, Ukrainian, Norwegian, Japanese)
22
+ }
23
+
24
+ sealed trait Pet extends HousePart
25
+ case object Dog extends Pet
26
+ case object Snails extends Pet
27
+ case object Fox extends Pet
28
+ case object Horse extends Pet
29
+ case object Zebra extends Pet
30
+ object Pet { val values: List[Pet] = List(Dog, Snails, Fox, Horse, Zebra) }
31
+
32
+ sealed trait Beverage extends HousePart
33
+ case object Coffee extends Beverage
34
+ case object Tea extends Beverage
35
+ case object Milk extends Beverage
36
+ case object OrangeJuice extends Beverage
37
+ case object Water extends Beverage
38
+ object Beverage {
39
+ val values: List[Beverage] = List(Coffee, Tea, Milk, OrangeJuice, Water)
40
+ }
41
+
42
+ sealed trait Cigarette extends HousePart
43
+ case object OldGold extends Cigarette
44
+ case object Kools extends Cigarette
45
+ case object Chesterfields extends Cigarette
46
+ case object LuckyStrike extends Cigarette
47
+ case object Parliaments extends Cigarette
48
+ object Cigarette {
49
+ val values: List[Cigarette] = List(OldGold, Kools, Chesterfields, LuckyStrike, Parliaments)
50
+ }
51
+
52
+ sealed trait Position extends HousePart { def fromEnum: Int }
53
+ case object One extends Position { override val fromEnum = 0 }
54
+ case object Two extends Position { override val fromEnum = 1 }
55
+ case object Three extends Position { override val fromEnum = 2 }
56
+ case object Four extends Position { override val fromEnum = 3 }
57
+ case object Five extends Position { override val fromEnum = 4 }
58
+ object Position { val values: List[Position] = List(One, Two, Three, Four, Five) }
59
+
60
+ case class House(position: Position, color: Color, resident : Resident,
61
+ beverage: Beverage, cigarette: Cigarette, pet: Pet)
62
+ {
63
+ def toTheRight(other: House): Boolean =
64
+ this.position.fromEnum == other.position.fromEnum + 1
65
+
66
+ def nextTo(other: House): Boolean =
67
+ math.abs(this.position.fromEnum - other.position.fromEnum) == 1
68
+ }
69
+
70
+ case class Solution(waterDrinker: Resident, zebraOwner: Resident)
71
+
72
+ lazy val solve: Solution = {
73
+ def residentWith(what: House => HousePart, value: HousePart): Resident =
74
+ houseWith(what, value, fiveHouses).resident
75
+
76
+ val waterDrinker = residentWith((_.beverage), Water)
77
+ val zebraOwner = residentWith((_.pet), Zebra)
78
+
79
+ Solution(waterDrinker = waterDrinker, zebraOwner = zebraOwner)
80
+ }
81
+
82
+ lazy val fiveHouses: List[House] = {
83
+ def housesAtPosition(position: Position): List[House] =
84
+ validHouses filter (_.position == position)
85
+
86
+ val candidates = for {
87
+ one <- housesAtPosition(One)
88
+ two <- housesAtPosition(Two) if uniqueHouses(List(one, two))
89
+ three <- housesAtPosition(Three) if uniqueHouses(List(one, two, three))
90
+ four <- housesAtPosition(Four) if uniqueHouses(List(one, two, three, four))
91
+ five <- housesAtPosition(Five) if uniqueHouses(List(one, two, three, four, five))
92
+ candidates = List(one, two, three, four, five) if validPositions(candidates)
93
+ } yield candidates
94
+
95
+ candidates head
96
+ }
97
+
98
+ private lazy val validHouses: List[House] =
99
+ for {
100
+ position <- Position.values
101
+ color <- Color.values
102
+ resident <- Resident.values
103
+ beverage <- Beverage.values
104
+ cigarette <- Cigarette.values
105
+ pet <- Pet.values
106
+ house = House(position, color, resident, beverage, cigarette, pet) if validHouse(house)
107
+ } yield house
108
+
109
+ def validHouse(house: House): Boolean = {
110
+ val House(position, color, resident, beverage, cigarette, pet) = house
111
+ List(
112
+ (color == Red, resident == Englishman),
113
+ (resident == Spaniard, pet == Dog),
114
+ (color == Green, beverage == Coffee),
115
+ (resident == Ukrainian, beverage == Tea),
116
+ (cigarette == OldGold, pet == Snails),
117
+ (color == Yellow, cigarette == Kools),
118
+ (position == Three, beverage == Milk),
119
+ (position == One, resident == Norwegian),
120
+ (beverage == OrangeJuice, cigarette == LuckyStrike),
121
+ (resident == Japanese, cigarette == Parliaments)) forall (iff _).tupled
122
+ }
123
+
124
+ private def iff(p1: Boolean, p2: Boolean): Boolean =
125
+ (p1, p2) match {
126
+ case (true, true) => true
127
+ case (false, false) => true
128
+ case _ => false
129
+ }
130
+
131
+ private def uniqueHouses(houses: List[House]): Boolean = {
132
+ def unique(what: House => HousePart): Boolean =
133
+ (houses map what distinct).length == houses.length
134
+
135
+ unique (_.color) && unique (_.resident) && unique (_.beverage) &&
136
+ unique (_.cigarette) && unique (_.pet)
137
+ }
138
+
139
+ private def houseWith(what: House => HousePart, value: HousePart, houses: List[House]): House =
140
+ houses find (what(_) == value) getOrElse(throw new Exception(s"could not find house with $value"))
141
+
142
+ private def validPositions(houses: List[House]): Boolean = {
143
+ def _houseWith(what: House => HousePart, value: HousePart): House =
144
+ houseWith(what, value, houses)
145
+
146
+ (_houseWith(_.color, Green) toTheRight _houseWith(_.color, Ivory)) &&
147
+ (_houseWith(_.cigarette, Chesterfields) nextTo _houseWith(_.pet, Fox)) &&
148
+ (_houseWith(_.cigarette, Kools) nextTo _houseWith(_.pet, Horse)) &&
149
+ (_houseWith(_.resident, Norwegian) nextTo _houseWith(_.color, Blue))
150
+ }
151
+ }
152
+