今更油売り算

前に書いたのは再帰深さ優先探索だったけど、今度は深さ優先と幅優先を切りかえられるように書いた。スタックとキュー。

しかし、たまたま問題の10, 7, 3の場合は深さ優先と幅優先で同じ結果になってしまったので、他の例をば。

$ ./abura2.scm depth 12 7 3
init
((12 . 12) (7 . 0) (3 . 0))
((12 . 5) (7 . 7) (3 . 0))
((12 . 5) (7 . 4) (3 . 3))
((12 . 8) (7 . 4) (3 . 0))
((12 . 8) (7 . 1) (3 . 3))
((12 . 11) (7 . 1) (3 . 0))
((12 . 11) (7 . 0) (3 . 1))
((12 . 4) (7 . 7) (3 . 1))
((12 . 4) (7 . 5) (3 . 3))
((12 . 7) (7 . 5) (3 . 0))
((12 . 7) (7 . 2) (3 . 3))
((12 . 10) (7 . 2) (3 . 0))
((12 . 10) (7 . 0) (3 . 2))
((12 . 3) (7 . 7) (3 . 2))
((12 . 3) (7 . 6) (3 . 3))
$ ./abura2.scm breadth 12 7 3
init
((12 . 12) (7 . 0) (3 . 0))
((12 . 9) (7 . 0) (3 . 3))
((12 . 9) (7 . 3) (3 . 0))
((12 . 6) (7 . 3) (3 . 3))

pretty printじゃないのは御愛嬌。

#!/usr/bin/env gosh
;; -*- coding: utf-8; -*-

(use srfi-1)
(use util.queue)
(use util.combinations)

;; <state> ::= (<unit> ...)
;; <unit> ::= (limit . value)
;; <search-state> ::= (<state> . <step>) = (<state> from<int> to<int>)
;; <step> ::= (from<int> to<int>)
;;
;; so, initial state is '((10 . 10) (7 . 0) (3 . 0))
;; and search state '(((10 . 10) (7 . 0) (3 . 0)) . (0 . 1)) means
;;     next state is '((10 . 3) (7 . 7) (3 . 0))

(define (main args)
  (pretty-print-result (apply search-abura (string->symbol (cadr args)) (map x->integer (cddr args))))
  0)

(define nil ())

;; key, value := <state>, <state>
;; 'value' represents the state before 'key'
;; so you can trace the flow of states from the last state.
(define *transition-pairs* #f)
(define (init-states!)
  (set! *transition-pairs* (make-hash-table 'equal?))
  #t)
(init-states!)

(define (register-states before after)
  (hash-table-put! *transition-pairs* after before))

(define (state-before state)
  (hash-table-get *transition-pairs* state #f))

(define appeared? state-before)

(define (state-flow last-state)
  (let loop((ret ()) (state last-state))
    (let1 before (state-before state)
      (if before
	  (loop (cons state ret) before)
	  (cons state ret)))))

(define (container-of flag)
  (cond ((eq? flag 'depth) ())
	(else ; 'breadth
	 (make-queue))))

(define (value unit)
  (cdr unit))

(define (limit unit)
  (car unit))

;;; if unit is (10 . 3)
;;; then room-of is 7
(define (room-of unit)
  (- (car unit) (cdr unit)))

(define (moving-amount state from to)
  (min (room-of (ref state to)) (value (ref state from))))

(define (finished? state)
  (let1 half (/ (car (ref state 0)) 2)
    (call/cc
     (lambda (cc)
       (for-each
	(lambda (m)
	  (for-each
	   (lambda (combi)
	     (if
	      (= half
		 (apply
		  + 0 
		  (map
		   (lambda (index)
		     (cdr (ref state index)))
		   combi)))
		 (cc #t) #f))
	   (combinations (iota (length state)) m)))
	(iota (- (length state) 1) 1))
       (cc #f)))))

(define (move state from to)
  (let ((to-unit (ref state to)) (from-unit (ref state from))
	(arg-state state))
    (let loop((i 0) (ret ()) (state state))
      (if (null? state) (reverse ret)
	  (loop
	   (+ i 1)
	   (let1 unit (car state)
	     (cons (cons
		    (limit unit)
		    (cond ((= i from)
			   (- (value from-unit) (moving-amount arg-state from to)))
			  ((= i to) 
			   (+ (value to-unit) (moving-amount arg-state from to)))
			  (else (value unit))))
		   ret))
	   (cdr state))))))

(define (init-units . args)
  (let1 args (sort args >)
    (let1 initial-unit (cons (car args) (car args))
      (let loop((args (cdr args)) (ret `(,initial-unit)))
	(if (null? args)
	    (reverse ret)
	    (loop (cdr args) (cons (cons (car args) 0) ret)))))))

(define (search-abura method . args)
  (let1 ret (search-by method (apply init-units args))
    (state-flow ret)))

;; TODO: remove duplication
(define (gen-next-states state)
  (map (cut apply move state <>)
       (let1 len (length state)
	 (let loop((i 0) (j 0) (ret ()))
	   (cond
	    ((>= i len) ret)
	    ((>= j len) (loop (+ i 1) 0 ret))
	    (else (loop i (+ j 1)
			(if (= i j)
			    ret
			    (cons `(,i ,j) ret)))))))))

(define-macro (!push! container value)
  `(if (eq? method 'depth)
       (push! ,container ,value)
       (enqueue! ,container ,value)))
(define-macro (!pop! container)
  `(if (eq? method 'depth)
       (pop! ,container)
       (dequeue! ,container)))

(define (search-by method init-state)
  (let ((container (container-of method))
	(empty? (if (eq? method 'depth) null?
		    (lambda (x) (and (null? (cdr x)) (null? (car x)))))))
    (init-states!)
    (!push! container init-state)
    (register-states 'init init-state)
    (let loop()
      (let1 iter-ret
	  (if (empty? container) #f
	      (let1 state (!pop! container)
		(if (finished? state)
		    state		; found solution!
		    (begin (for-each
			    (lambda (next-state) 
			      (if (appeared? next-state) #f
				  (begin
				    (register-states state next-state)
				    (!push! container next-state))))
			    (gen-next-states state))
			   #f))))
	(if iter-ret iter-ret
	    (loop))))))

(define (head ls len)
  (let loop((ls ls) (ret ()) (len len))
    (if (or (null? ls) (= len 0))
	(reverse ret)
	(loop (cdr ls) (cons (car ls) ret) (- len 1)))))

(define (pretty-print-result states)
  (for-each print states))