この週末のプログラミングの楽しみは、F# で 300 行のリバーシ プログラムを作成することでした。アルファベット検索を並列化する方法を見つけるには、おそらくあと数週間かかるでしょう。実際、これはこの質問の範囲外です。
しかし、私が見つけたのは、alphabeta 関数を実装するための「純粋に機能的な」方法を思いつくことができなかったことです。つまり、可変状態はありません。
そのための良いアイデアはありますか?
私の頭に浮かんだ唯一のアイデアは、状態の変化を格納するためにアキュムレータ状態が使用される Seq.foldUntil() 関数のようなものを書くことです。そして、渡されたラムダ関数によってキャンセルできます。
多分このように見えます:
let transformWhile<'t,'s,'r> (transformer : 's -> 't -> 's * 'r * bool ) (state : 's) (sequence : 't seq) : 'r seq
ここで、不純なアルファベット関数...
let rec alphabeta depth alpha beta fork (position : ReversiPosition) (maximize : bool) : (SquareName option * int) =
match depth with
| 0 -> (None, snd (eval position))
| _ ->
let allMoves =
allSquares
|> Seq.map (fun sq -> (sq,tryMove (position.ToMove) sq position))
|> Seq.filter (fun pos -> match snd pos with | Some(_) -> true | None -> false )
|> Seq.map (fun opos -> match opos with | (sq,Some(p)) -> (sq,p) | _ -> failwith("only Some(position) expected here."))
|> Array.ofSeq
let len = allMoves.Length
match len with
| 0 -> (None, snd (eval position))
| _ ->
if maximize then
let mutable v = System.Int32.MinValue
let mutable v1 = 0
let mutable a = alpha
let b = beta
let mutable i = 0
let mutable bm : SquareName option = None
let mutable bm1 : SquareName option = None
while (i<len) && (b > a) do
let x,y = alphabeta (depth-1) a b false (snd allMoves.[i]) false
bm1 <- Some(fst allMoves.[i])
v1 <- y
if v1 > v then
bm <- bm1
v <- v1
a <- max a v
if b > a then
i <- (i + 1)
(bm,v)
else
let mutable v = System.Int32.MaxValue
let mutable v1 = 0
let a = alpha
let mutable b = beta
let mutable i = 0
let mutable bm : SquareName option = None
let mutable bm1 : SquareName option = None
while (i<len) && (b > a) do
let x,y = alphabeta (depth-1) a b false (snd allMoves.[i]) true
bm1 <- Some(fst allMoves.[i])
v1 <- y
if v1 < v then
bm <- bm1
v <- v1
b <- min b v
if b > a then
i <- (i + 1)
(bm,v)
答えを待っている間に、transformWhile のアイデアを試してみることにしました。
module SeqExt =
let rec foldWhile<'T,'S,'R> (transformer : 'S -> 'T -> 'S * 'R * bool ) (state : 'S) (sequence : seq<'T>) : 'R option =
if (Seq.length sequence) > 0 then
let rest = (Seq.skip 1 sequence)
let newState, resultValue, goOn = transformer state (Seq.head sequence)
if goOn && not (Seq.isEmpty rest) then
foldWhile transformer newState rest
else
Some(resultValue)
else
None
いくつかのインタラクティブなテストで、いくつかの些細なことで機能することが示されたので、新しいバージョンの alphabeta を作成することにしました。現在は次のようになっています。
let rec alphabeta depth alpha beta fork (position : ReversiPosition) (maximize : bool) : (SquareName option * int) =
match depth with
| 0 -> (None, snd (eval position))
| _ ->
let allMoves =
allSquares
|> Seq.map (fun sq -> (sq,tryMove (position.ToMove) sq position))
|> Seq.filter (fun pos -> match snd pos with | Some(_) -> true | None -> false )
|> Seq.map (fun opos -> match opos with | (sq,Some(p)) -> (sq,p) | _ -> failwith("only Some(position) expected here."))
let len = Seq.length allMoves
match len with
| 0 -> (None, snd (eval position))
| _ ->
if maximize then
let result = SeqExt.foldWhile
( fun (state : int * int * SquareName option * int ) move ->
let curAlpha,curBeta,curMove,curValue = state
let x,y = alphabeta (depth-1) curAlpha curBeta false (snd move) false
let newBm,newScore =
if y > curValue then
(Some(fst move), y)
else
(curMove,curValue)
let newAlpha = max curAlpha newScore
let goOn = curBeta > newAlpha
((newAlpha,curBeta,newBm,newScore),(newBm,newScore),goOn)
) (alpha,beta,None,System.Int32.MinValue) allMoves
match result with
| Some(r) -> r
| None -> failwith("This is not possible! Input sequence was not empty!")
else
let result = SeqExt.foldWhile
( fun (state : int * int * SquareName option * int ) move ->
let curAlpha,curBeta,curMove,curValue = state
let x,y = alphabeta (depth-1) curAlpha curBeta false (snd move) true
let newBm,newScore =
if y < curValue then
(Some(fst move), y)
else
(curMove,curValue)
let newBeta = min curBeta newScore
let goOn = newBeta > curAlpha
((curAlpha,newBeta,newBm,newScore),(newBm,newScore),goOn)
) (alpha,beta,None,System.Int32.MaxValue) allMoves
match result with
| Some(r) -> r
| None -> failwith("This is not possible! Input sequence was not empty!")
それは、関数型プログラミングのプロが行うようなものですか? または、あなたは何をしますか?
以前のブルート フォース検索は末尾再帰 (コール スタックが構築されない) でしたが、この純粋な機能バージョンは末尾再帰ではなくなりました。誰かが再び末尾再帰にする方法を見つけることができますか?