📜 ⬆️ ⬇️

Solving the Einstein's Puzzle by Haskell

Prelude

Albert_einstein
Not so long ago I read an article on Habré that reminded me of an interesting puzzle, which is called the “ Einstein 's Mystery ” or the “Zebra puzzle”. Probably many of you solved this problem on a piece of paper and were proud to be part of a few percent of the world's population who are capable of it.

After reading the article, I thought about the software solution of this problem. The approach given in the article was interesting and fully justified the name of the blog, but it seemed to me not quite clear. At the moment I'm interested in the Haskell programming language, which is perfect for warming up brains by itself, but solving the puzzle on it seemed like a great challenge.


Algorithm

In general, the task looks like this:
It is necessary to restore the values ​​of the attributes of all objects (or some of them, which are otherwise the same), which would not contradict the restrictions.
')
The first thought was a complete enumeration of options with checking the fulfillment of the conditions of the task, but simple calculations show that the number of combinations is equal
5! 5 = 24.883.200.000 5! 5 = 24.883.200.000 , which is quite a lot.

In the course of some reflections, the following approach was born: there is an admissible solution space, constraints describe subsets of solutions, correct solutions lie at the intersection of such subsets (that is, in the area where all conditions are fulfilled).

The next step was the decision to describe the subsets of solutions in the form of a set of patterns: decisions in which some attributes of some objects are fixed, and the rest can take any valid values. And having such a description you only need to learn how to intersect the sets described in this way.

Decision

When compiling and rewriting a solution in the Haskell language, I was guided not only by the desire to find a solution to the problem, but also by the desire to write an understandable program that would easily demonstrate both the algorithm and the excellent Haskell language.

Therefore, I tried to minimally use approaches that prevent unprepared people from reading the program. The solution does not claim complete universality, however, some tools for solving this class of problems have been described.

However, read for yourself - I left a lot of comments for you.
But your comments, I will be glad.
  1. import data . List ( lookup , nub)
  2. import data . Maybe (fromMaybe , catMaybes)
  3. -----------------------------------------------
  4. - The Mystery of Einstein -
  5. - by Atamur -
  6. -----------------------------------------------
  7. ---------------------------
  8. - General description of the task -
  9. ---------------------------
  10. - The solution is a sequence of objects
  11. type Solution = [Object]
  12. - Each object is described by a set of attribute pairs and their values.
  13. type Object = [( String , String )]
  14. - indicating the attribute-value pair with the = sign:
  15. attr = : value = (attr , value)
  16. attributes = [ "nationality" , "house" , "pet" , "drink" , "smoke" ]
  17. size = 5
  18. - We will solve the problem of finding a solution that satisfies some
  19. - restrictions
  20. - We will do this starting from the set of all solutions and gradually refining
  21. - this is a set until it decreases to one solution, which will be
  22. - true.
  23. - Therefore, an object can immediately describe a set of real objects
  24. - which have some attributes set, and the rest (not set)
  25. - can be any. Therefore, an empty object is a template of all possible
  26. - objects:
  27. anyObject = [] :: Object
  28. - A set of empty objects - the set of all solutions for a given
  29. - dimensions:
  30. anySolution = [anyObject | n [1 .. size]]
  31. - Each solution describes a compatible set of solutions, for example, a solution
  32. - [["nationality" =: "Englishman"], anyObject, anyObject]
  33. - describes all three objects, the first of which has an attribute
  34. - "nationality is English"
  35. “But when refining solutions, we will need to have sets of incompatible
  36. - decisions (for example, “Englishman or first or second but not at the same time”):
  37. type Solutions = [Solution]
  38. empty = [] :: Solutions
  39. - To solve the problem, we need to impose restrictions on the solution
  40. - Each constraint will transform a variety of solutions.
  41. - As a result, there may be several unrelated sets or
  42. - empty solution set
  43. type Restriction = Solution Solutions
  44. - applying a constraint to a set of solutions
  45. - when transforming a set of solutions, we will check for
  46. - uniqueness of attribute values:
  47. apply :: Restriction Solutions Solutions
  48. apply restrict sols =
  49. concat [ filter ( not . duplicates) (restrict solution) | solution sols]
  50. where
  51. duplicates sols = any duplicateValues ​​( map (values ​​sols) attributes)
  52. values ​​sols attr = map ( lookup attr) sols
  53. duplicateValues ​​vals' =
  54. let vals = catMaybes vals'
  55. in (vals nub vals)
  56. - the intersection of two patterns describing objects
  57. - can be a template containing attributes from both source templates,
  58. - or it may be incompatible if the source templates contain different
  59. - values ​​of the same attributes
  60. both :: Object Object Maybe Object
  61. both obj obj ' = foldl join (Just []) attributes - combine by attributes
  62. where
  63. - if we already have an empty set then the result is also empty
  64. join Nothing _ = Nothing
  65. - otherwise compare values ​​of attributes by attributes.
  66. join (Just rest) attr =
  67. case ( lookup attr obj , lookup attr obj ') of
  68. (Nothing , Nothing) Just rest
  69. (Just value , Nothing) Just ((attr = : value): rest)
  70. (Nothing , Just value) Just ((attr = : value): rest)
  71. (Just value , Just value ')
  72. if value == value '
  73. then Just ((attr = : value): rest)
  74. else Nothing
  75. - basic restriction - the object in some position must coincide with
  76. - set by template
  77. objectAt :: Int Object Restriction
  78. objectAt n obj solution =
  79. case both obj (solution !! (n - 1)) of
  80. Nothing empty - if the template is not compatible with the one already there
  81. Just res [replace n res solution]
  82. where
  83. replace nx xs = take (n - 1) xs ++ [x] ++ drop n xs
  84. - Operations on constraints ---------------------------------------------
  85. - intersection - both restrictions are true
  86. ( < & > ) rs rs 'solution = apply rs (rs' solution)
  87. r _ all = foldl1 ( < & > ) - all restrictions from the set
  88. - association - one or the other is true
  89. ( <|> ) rs rs 'solution = rs solution ++ rs' solution
  90. r _ any = foldl1 ( <|> ) - one of the limitations
  91. - Derived constraints ---------------------------------------------- -
  92. - there is some object (either in the first position, or in the second, etc.)
  93. exists obj = r _ any [objectAt n obj | n [1 .. 5]]
  94. - one object always follows another
  95. before obj obj ' = r _ any [
  96. objectAt n obj < & > objectAt (n + 1) obj ' | n [1 .. size - 1]]
  97. - near (or one before the second, or second before the first)
  98. near obj obj ' = (obj `before` obj') <|> (obj 'before` obj)
  99. ------------------------------------------------
  100. - Description of the specific problem
  101. ------------------------------------------------
  102. restrictions =
  103. objectAt 1 [ "nationality" = : "Norwegian" ] < & >
  104. exists [ "nationality" = : "Englishman" , "house" = : "Red" ] < & >
  105. ([ "house" = : "Green" ] `before` [ " house " = : " White " ]) < & >
  106. exists [ "nationality" = : "Dane" , "drink" = : "Tea" ] < & >
  107. ([ "smoke" = : "Malboro" ] `near` [ " pet " = : " Cat " ]) < & >
  108. exists [ "smoke" = : "Dunhill" , "house" = : "Yellow" ] < & >
  109. exists [ "nationality" = : "German" , "smoke" = : "Rothmans" ] < & >
  110. objectAt 3 [ "drink" = : "Milk" ] < & >
  111. ([ "smoke" = : "Malboro" ] `near` [ " drink " = : " Water " ]) < & >
  112. exists [ "smoke" = : "Pallmall" , "pet" = : "Bird" ] < & >
  113. exists [ "nationality" = : "Swede" , "pet" = : "Dog" ] < & >
  114. ([ "nationality" = : "Norwegian" ] `near` [ " house " = : " Blue " ]) < & >
  115. exists [ "pet" = : "Horse" , "house" = : "Blue" ] < & >
  116. exists [ "smoke" = : "Winfield" , "drink" = : "Beer" ] < & >
  117. exists [ "house" = : "Green" , "drink" = : "Coffee" ] < & >
  118. exists [ "pet" = : "Fish" ]
  119. - the fish does not appear anywhere, you need to ask what it is
  120. main = let solutions = apply restrictions [anySolution]
  121. in if length solutions > 1
  122. then putStrLn ( "Total solution sets:" ++ show ( length solutions))
  123. else putStrLn $ descrSolution $ head solutions
  124. -----------------------------
  125. - String views -
  126. -----------------------------
  127. - a string representation of a person:
  128. descrMan man = descr "nationality" ++ "lives in" ++
  129. descr "house" ++ "house, owns" ++
  130. descr "pet" ++ ", drinks" ++
  131. descr "drink" ++ "and smokes" ++
  132. descr "smoke"
  133. where descr attr = fromMaybe "?" ( lookup attr man)
  134. - string representation of the solution:
  135. descrSolution sol = concat [descrMan man ++ " \ n " | man sol]

Source: https://habr.com/ru/post/122123/


All Articles