両替プログラム

2ちゃんの某 Prolog スレで、「両替の問題を解いてください.prolog始めて3週間で出た学校の課題です><; 」なんてのを見かけたので、Prolog の勉強を兼ねて書いてみることに。なんとかそれっぽくうごくコードにはできました。

factorize([F | Fs], N, As) :- factorize1(F, Fs, N, [0 | As]).
factorize1(F, [], N, [0 | As]) :-
  M is N mod F, M = 0, A is N // F,
  reverse([A | As], Result), write(Result), nl, !, fail.
factorize1(F, Fs, N, [A | As]) :-
  N >= F, N1 is N - F, A1 is A + 1, 
  factorize1(F, Fs, N1, [A1 | As]), !.
factorize1(_, Fs, N, As) :- factorize(Fs, N, As).
?- factorize([10, 5, 1], 30, []).
[3, 0, 0]
[2, 2, 0]
[2, 1, 5]
[2, 0, 10]
[1, 4, 0]
[1, 3, 5]
[1, 2, 10]
[1, 1, 15]
[1, 0, 20]
[0, 6, 0]
[0, 5, 5]
[0, 4, 10]
[0, 3, 15]
[0, 2, 20]
[0, 1, 25]
[0, 0, 30]

No

で。問題は、この動きを Smalltalk で表現すること。継続渡しを使ったバックトラッキングっていうんでしょうかね。とりあえず、普通に再帰で書いてみると、

Integer >> factorizedBy: factors do: aBlock
  "3 factorizedBy: #(3 2 1) do: [: each | Transcript cr; show: each]"
  ^ self factorizedBy: factors startingWith: #() do: aBlock

Integer >> factorizedBy: factors startingWith: amplitudes do: aBlock
  | amps index factor rest |
  amps _ amplitudes.
  index _ amps size + 1.
  factor _ factors at: index.
  rest _ self -
    (index = 1 ifTrue: [0] ifFalse: [(amps * (factors first: amps size)) sum]).
  index = factors size ifTrue: [
    rest \\ factor = 0 ifTrue: [aBlock value: (amps copyWith: rest // factor)].
    ^ self].
  rest // factor to: 0 by: -1 do: [: amp |
    self factorizedBy: factors startingWith: (amps copyWith: amp) do: aBlock]
30 factorizedBy: #(10 5 1) do: [: each | Transcript cr; show: each]

"=> #(3 0 0)
    #(2 2 0)
    #(2 1 5)
    #(2 0 10)
    #(1 4 0)
    #(1 3 5)
    #(1 2 10)
    #(1 1 15)
    #(1 0 20)
    #(0 6 0)
    #(0 5 5)
    #(0 4 10)
    #(0 3 15)
    #(0 2 20)
    #(0 1 25)
    #(0 0 30) "

悪戦苦闘のすえの継続渡しだと、

factors: factors amplitudes: amplitudes ifFail: failBlock
  "27 factors: #(24 11 3) amplitudes: #(0) ifFail: [^ nil]"
  | factor |
  factor _ factors first.

  (factors size = 1 and: [amplitudes last isZero]) ifTrue: [
    self \\ factor = 0 ifTrue: [
      Transcript cr; show: (amplitudes allButLast copyWith: self // factor)].
    ^ failBlock value].

  ^ self factors: factors allButFirst amplitudes: (amplitudes copyWith: 0) ifFail: [
    ^ self >= factors first
      ifTrue: [
        amplitudes at: amplitudes size put: (amplitudes last + 1).
        self - factor factors: factors amplitudes: amplitudes ifFail: failBlock]
      ifFalse: [failBlock value]]
30 factors: #(10 5 1) amplitudes: #(0) ifFail: [^ nil]
"=> #(0 0 30)
    #(0 1 25)
    #(0 2 20)
    #(0 3 15)
    #(0 4 10)
    #(0 5 5)
    #(0 6 0)
    #(1 0 20)
    #(1 1 15)
    #(1 2 10)
    #(1 3 5)
    #(1 4 0)
    #(2 0 10)
    #(2 1 5)
    #(2 2 0)
    #(3 0 0) "

見通しワル…、ってか読みにくっ(^_^;)。

教訓: Prolog っぽいことは、Prolog で。

つづく