6
\$\begingroup\$

Having read Can you do the maths puzzle for Vietnamese eight-year-olds that has stumped parents and teachers?, which presented the problem of filling in numbers 1 to 9 to satisfy

$$ a + 13 \frac{b}{c} + d + 12 e - f - 11 + \frac{g \cdot h}{i} - 10 = 66 $$

… I thought I'd try a Common Lisp implementation as a learning exercise.

(defun solve-puzzle (permutation)
  (destructuring-bind (a b c d e f g h i) permutation
    (let ((result (infpre:!! a + 13 * b / c + d + 12 * e - f - 11 + g * h / i - 10)))
      (when (= result 66) (print permutation)))))

(alexandria:map-permutations #'solve-puzzle '(1 2 3 4 5 6 7 8 9) :length 9)

This works; when executed, it produces a list of 136 valid combinations in around 300ms on my laptop. I have a couple of questions regarding the code though:

  • How's my idiom? My background is C, C#, Ruby so I'm probably carrying over habits from those languages.
  • Are there any obvious performance gotchas in the code - obvious to experienced Lispers, that is?
\$\endgroup\$
5
  • 1
    \$\begingroup\$ This is a common form of a Constraint Satisfaction Problem. They're common, and there are libraries for solving more complex problems that show up. It's worth taking a look at implementing simple examples like this with CSP solvers, too, to get a much better feel for how to use them for more difficult problems, too. For example, you can use the Labix python-constraint library. \$\endgroup\$ Commented May 22, 2015 at 2:02
  • \$\begingroup\$ As per the wording of the Vietnamese problem in other sources, the math ought to be performed as you progress through the "snake". Therefore, the equivalent mathematical equation would be a bit much cumbersome. ( ( ( ( ( ( (a+13)*b)/c + d + 12 ) * e) - f - 11 + g ) * h ) / i ) - 10 = 66 \$\endgroup\$
    – user74226
    Commented May 23, 2015 at 3:00
  • \$\begingroup\$ Not all sources agree; the Guardian says that you follow the order of operations. \$\endgroup\$
    – Snowbody
    Commented May 23, 2015 at 18:59
  • \$\begingroup\$ I have closed this question as "unclear". The comments indicate that the order-of-precedence of the "puzzle" is not specified, and is not a "standard" one. Additionally, this question was flagged, and in the "review" system, and I see enough uncertainty to close. Unfortunately, the current answer(s) make changing the question impractical too. \$\endgroup\$
    – rolfl
    Commented May 27, 2015 at 18:22
  • \$\begingroup\$ @rolfl in what way are the questions "how's my idiom?" and "are there any obvious performance gotchas in the code?" unclear? I'm not trying to be confrontational here, I just don't understand how I could have expressed my concerns going into a review any more clearly. Those are exactly the questions I'd have asked of this code if submitting it for review by the people with whom I work. \$\endgroup\$ Commented Jun 11, 2015 at 4:45

1 Answer 1

2
\$\begingroup\$

I'm not familiar with either the alexandria or infpre packages; I wrote my solution in Common Lisp as well, but using only language primitives. Assuming that the infpre evaluator follows standard arithmetic order-of-evaluation rules, your code looks perfectly fine (and more compact and readable than mine) but of course requires having those packages available. More importantly, your code should produce the same answers as my (different) approach.

I used my approach to make sure that the variables were local and that the compiled code manipulated them efficiently. (I ran mine in Clozure Common Lisp which compiles all expressions to machine code before running them).

;; Transcription of expression from puzzle picture
(defvar *expression* '(+ a (/ (* 13 b) c) d (* 12 e) (* -1 f) -11 (/ (* g h) i) -10))

;; Macro to generate nested loops trying all combinations
(defmacro nested-loops (variables variable-initializer body)
  (let ((internal-variable (gensym)))
    `(let ((answers))
       ,(nested-loops-helper t variables variable-initializer body internal-variable)
       answers)))

;; Helper function for macro to generate loop guts for each level
(defun nested-loops-helper (first-time variables variable-initializer body internal-variable)
  (cond ;; no more variables, generate body
    ((null variables)
     `(,@body))
    ;; otherwise generate a loop setting this variable to all possible values except ones already used
    (t (let* ((this-var (pop variables))
              (extra (when first-time `(for ,internal-variable = nil))))
         `(loop for ,this-var ,@variable-initializer ,@extra 
            unless (member ,this-var ,internal-variable)
            do (push ,this-var ,internal-variable)
            (,@(nested-loops-helper nil variables variable-initializer body internal-variable))
            (pop ,internal-variable))))))

;; Invocation of macro to solve puzzle (prints all answers and saves them in solutions variable)
(length (setq solutions
          (nested-loops (a b c d e f g h i) (from 1 to 9) 
                (let ((value #.*expression*))
                  (when (eql value 66)
                (print (list a b c d e f g h i))
                (push (list a b c d e f g h i) answers))))))

(format t "~&There are ~d solution~:p saved in the variable solutions.~%" (length solutions))
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.