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.
- checksums.yaml +4 -4
- data/common/CONTRIBUTING.md +17 -2
- data/common/exercises/anagram/canonical-data.json +0 -14
- data/common/exercises/pig-latin/canonical-data.json +6 -1
- data/common/exercises/raindrops/description.md +4 -4
- data/common/exercises/raindrops/metadata.yml +1 -1
- data/common/exercises/roman-numerals/metadata.yml +1 -1
- data/lib/trackler/version.rb +1 -1
- data/tracks/c/config.json +12 -1
- data/tracks/c/exercises/roman-numerals/makefile +16 -0
- data/tracks/c/exercises/roman-numerals/src/example.c +43 -0
- data/tracks/c/exercises/roman-numerals/src/example.h +6 -0
- data/tracks/c/exercises/roman-numerals/test/test_roman_numerals.c +126 -0
- data/tracks/c/exercises/roman-numerals/test/vendor/unity.c +1300 -0
- data/tracks/c/exercises/roman-numerals/test/vendor/unity.h +274 -0
- data/tracks/c/exercises/roman-numerals/test/vendor/unity_internals.h +701 -0
- data/tracks/clojure/exercises/flatten-array/project.clj +4 -0
- data/tracks/csharp/exercises/protein-translation/ProteinTranslationTest.cs +1 -1
- data/tracks/elixir/exercises/bowling/bowling_test.exs +157 -62
- data/tracks/elixir/exercises/bowling/example.exs +33 -4
- data/tracks/erlang/config.json +176 -36
- data/tracks/go/.travis.yml +7 -1
- data/tracks/go/bin/test-without-stubs +29 -0
- data/tracks/go/exercises/robot-simulator/defs.go +1 -1
- data/tracks/go/exercises/variable-length-quantity/variable_length_quantity_test.go +31 -25
- data/tracks/haskell/.travis.yml +9 -5
- data/tracks/haskell/config.json +127 -121
- data/tracks/haskell/exercises/leap/src/LeapYear.hs +1 -1
- data/tracks/haskell/exercises/space-age/HINTS.md +14 -2
- data/tracks/haskell/exercises/space-age/{src/Example.hs → examples/success-double/SpaceAge.hs} +0 -0
- data/tracks/haskell/exercises/space-age/examples/success-double/package.yaml +19 -0
- data/tracks/haskell/exercises/space-age/examples/success-rational/SpaceAge.hs +24 -0
- data/tracks/haskell/exercises/space-age/examples/success-rational/package.yaml +19 -0
- data/tracks/haskell/exercises/space-age/src/SpaceAge.hs +4 -1
- data/tracks/haskell/exercises/space-age/test/Tests.hs +12 -5
- data/tracks/haskell/exercises/word-count/HINTS.md +21 -0
- data/tracks/haskell/exercises/word-count/examples/success-newtype/WordCount.hs +31 -0
- data/tracks/haskell/exercises/word-count/examples/success-newtype/package.yaml +21 -0
- data/tracks/haskell/exercises/word-count/examples/success-simple/WordCount.hs +13 -0
- data/tracks/haskell/exercises/word-count/examples/success-simple/package.yaml +20 -0
- data/tracks/haskell/exercises/word-count/package.yaml +0 -2
- data/tracks/haskell/exercises/word-count/src/WordCount.hs +2 -1
- data/tracks/haskell/exercises/word-count/test/Tests.hs +15 -6
- data/tracks/haskell/exercises/zebra-puzzle/package.yaml +19 -0
- data/tracks/haskell/exercises/zebra-puzzle/src/Example.hs +113 -0
- data/tracks/haskell/exercises/zebra-puzzle/src/ZebraPuzzle.hs +12 -0
- data/tracks/haskell/exercises/zebra-puzzle/stack.yaml +1 -0
- data/tracks/haskell/exercises/zebra-puzzle/test/Tests.hs +13 -0
- data/tracks/javascript/exercises/beer-song/beer-song.spec.js +5 -0
- data/tracks/lisp/config.json +147 -0
- data/tracks/ocaml/config.json +12 -0
- data/tracks/ocaml/exercises/atbash-cipher/.merlin +5 -0
- data/tracks/ocaml/exercises/atbash-cipher/HINTS.md +17 -0
- data/tracks/ocaml/exercises/atbash-cipher/Makefile +11 -0
- data/tracks/ocaml/exercises/atbash-cipher/atbash_cipher.mli +5 -0
- data/tracks/ocaml/exercises/atbash-cipher/example.ml +47 -0
- data/tracks/ocaml/exercises/atbash-cipher/test.ml +37 -0
- data/tracks/ocaml/exercises/meetup/.merlin +5 -0
- data/tracks/ocaml/exercises/meetup/Makefile +11 -0
- data/tracks/ocaml/exercises/meetup/example.ml +63 -0
- data/tracks/ocaml/exercises/meetup/meetup.mli +7 -0
- data/tracks/ocaml/exercises/meetup/test.ml +786 -0
- data/tracks/php/exercises/bowling/bowling_test.php +233 -10
- data/tracks/php/exercises/bowling/example.php +49 -6
- data/tracks/python/.travis.yml +5 -0
- data/tracks/python/config.json +33 -1
- data/tracks/python/exercises/flatten-array/example.py +18 -0
- data/tracks/python/exercises/flatten-array/flatten_array_test.py +40 -0
- data/tracks/python/exercises/robot-name/robot_name_test.py +8 -2
- data/tracks/r/config.json +32 -8
- data/tracks/ruby/bin/enable-executable +9 -5
- data/tracks/ruby/bin/executable-tests-check +7 -3
- data/tracks/ruby/bin/local-status-check +4 -4
- data/tracks/ruby/config.json +8 -1
- data/tracks/ruby/exercises/acronym/acronym_test.rb +0 -1
- data/tracks/ruby/exercises/anagram/.version +1 -0
- data/tracks/ruby/exercises/anagram/anagram_test.rb +101 -31
- data/tracks/ruby/exercises/anagram/example.rb +4 -0
- data/tracks/ruby/exercises/anagram/example.tt +19 -0
- data/tracks/ruby/exercises/gigasecond/.version +1 -1
- data/tracks/ruby/exercises/gigasecond/example.rb +1 -1
- data/tracks/ruby/exercises/gigasecond/gigasecond_test.rb +2 -2
- data/tracks/ruby/exercises/isogram/.version +1 -0
- data/tracks/ruby/exercises/isogram/example.rb +10 -0
- data/tracks/ruby/exercises/isogram/example.tt +20 -0
- data/tracks/ruby/exercises/isogram/isogram_test.rb +90 -0
- data/tracks/ruby/exercises/poker/example.rb +97 -39
- data/tracks/ruby/exercises/poker/poker_test.rb +67 -36
- data/tracks/ruby/exercises/sieve/.version +1 -0
- data/tracks/ruby/exercises/sieve/example.rb +4 -0
- data/tracks/ruby/exercises/sieve/example.tt +21 -0
- data/tracks/ruby/exercises/sieve/sieve_test.rb +36 -13
- data/tracks/ruby/exercises/word-count/example.tt +4 -3
- data/tracks/ruby/exercises/word-count/word_count_test.rb +38 -48
- data/tracks/ruby/lib/anagram_cases.rb +42 -0
- data/tracks/ruby/lib/isogram_cases.rb +24 -0
- data/tracks/ruby/lib/sieve_cases.rb +33 -0
- data/tracks/ruby/lib/word_count_cases.rb +19 -0
- data/tracks/scala/config.json +21 -0
- data/tracks/scala/docs/RESOURCES.md +1 -0
- data/tracks/scala/exercises/bracket-push/build.sbt +4 -0
- data/tracks/scala/exercises/bracket-push/example.scala +23 -0
- data/tracks/scala/exercises/bracket-push/src/main/scala/.keep +0 -0
- data/tracks/scala/exercises/bracket-push/src/test/scala/BracketsTest.scala +68 -0
- data/tracks/scala/exercises/change/src/test/scala/ChangeTest.scala +7 -0
- data/tracks/scala/exercises/sgf-parsing/build.sbt +7 -0
- data/tracks/scala/exercises/sgf-parsing/example.scala +66 -0
- data/tracks/scala/exercises/sgf-parsing/src/main/scala/.keep +0 -0
- data/tracks/scala/exercises/sgf-parsing/src/main/scala/Sgf.scala +17 -0
- data/tracks/scala/exercises/sgf-parsing/src/test/scala/SgfTest.scala +66 -0
- data/tracks/scala/exercises/zebra-puzzle/build.sbt +3 -0
- data/tracks/scala/exercises/zebra-puzzle/example.scala +152 -0
- data/tracks/scala/exercises/zebra-puzzle/src/main/scala/.keep +0 -0
- data/tracks/scala/exercises/zebra-puzzle/src/main/scala/ZebraPuzzle.scala +14 -0
- data/tracks/scala/exercises/zebra-puzzle/src/test/scala/ZebraPuzzleTest.scala +11 -0
- data/tracks/scala/testgen/src/main/scala/BracketPushTestGenerator.scala +44 -0
- metadata +63 -3
- 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,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
|
+
}
|
File without changes
|
@@ -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,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
|
+
}
|
File without changes
|
@@ -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,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
|
+
|