F# 如何以函数方式编写alphabeta搜索函数(无可变变量)?

F# 如何以函数方式编写alphabeta搜索函数(无可变变量)?,f#,functional-programming,alpha-beta-pruning,F#,Functional Programming,Alpha Beta Pruning,这个周末,我编程的乐趣是用F#编写一个300行的reversi程序。可能还需要几个周末才能找到如何使alphabeta搜索并行化的方法,这实际上超出了这个问题的范围 但我发现,我无法想出一些实现alphabeta函数的“纯功能”方法。即没有任何可变状态 有什么好主意吗 我唯一想到的是编写类似于Seq.foldUntil()函数的东西,其中累加器状态用于存储状态中的更改。可以通过传入的lambda函数取消 也许看起来像这样: let transformWhile<'t,'s,'r> (

这个周末,我编程的乐趣是用F#编写一个300行的reversi程序。可能还需要几个周末才能找到如何使alphabeta搜索并行化的方法,这实际上超出了这个问题的范围

但我发现,我无法想出一些实现alphabeta函数的“纯功能”方法。即没有任何可变状态

有什么好主意吗

我唯一想到的是编写类似于Seq.foldUntil()函数的东西,其中累加器状态用于存储状态中的更改。可以通过传入的lambda函数取消

也许看起来像这样:

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."))
        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!")
let transformWhile't->'s*'r*bool)(state:'s)(sequence:'t seq):'r seq
这里是不纯的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."))
            |> 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)
let rec alphabeta-depth alpha-beta-fork(位置:反转)(最大化:bool):(SquareName选项*int)=
匹配深度
|0->(无,snd(评估位置))
| _ -> 
让所有移动=
方阵
|>序列图(有趣的sq->(sq,tryMove(position.ToMove)sq位置))
|>Seq.filter(fun pos->match snd pos with | Some(|)->true | None->false)
|>Seq.map(有趣的opo->将opo与|(sq,一些(p))->(sq,p)|->failwith匹配(“这里只需要一些(位置)”)
|>A.ofSeq数组
设len=allMoves.Length
配上
|0->(无,snd(评估位置))
| _ ->
如果最大化,那么
设可变v=System.Int32.MinValue
设可变v1=0
设可变a=alpha
设b=beta
设可变i=0
让可变bm:SquareName选项=无
让可变bm1:SquareName选项=无
而我呢
设x,y=alphabeta(深度-1)ab假(snd-allMoves.[i])假
bm1将snd pos与| Some(|)->true | None->false匹配
|>Seq.map(有趣的opo->将opo与|(sq,一些(p))->(sq,p)|->failwith匹配(“这里只需要一些(位置)”)
设len=Seq.length allMoves
配上
|0->(无,snd(评估位置))
| _ ->
如果最大化,那么
让结果=sequext.foldWhile
(乐趣(状态:int*int*SquareName选项*int)移动->
让curAlpha、curBeta、curMove、curValue=状态
设x,y=alphabeta(深度-1)curAlpha curBeta false(snd move)false
让我们来看看新闻核心=
如果y>曲线值,则
(部分(fst移动),y)
其他的
(curMove,曲线值)
设newAlpha=max curAlpha newScore
让goOn=curBeta>newAlpha
((newAlpha,curBeta,newBm,newScore),(newBm,newScore),goOn)
)(alpha、beta、None、System.Int32.MinValue)所有移动
匹配结果
|一些(r)->r
|None->failwith(“这是不可能的!输入序列不是空的!”)
其他的
让结果=sequext.foldWhile
(乐趣(状态:int*int*SquareName选项*int)移动->
让curAlpha、curBeta、curMove、curValue=状态
设x,y=alphabeta(深度-1)curAlpha curBeta false(snd move)true
让我们来看看新闻核心=
如果y<曲线值,则
(部分(fst移动),y)
其他的
(curMove,曲线值)
设newBeta=min curBeta newScore
让goOn=newBeta>curAlpha
((库拉尔法,纽贝塔,纽博姆,新闻核心),(纽博姆,新闻核心),傻瓜)
)(alpha、beta、None、System.Int32.MaxValue)所有移动
匹配结果
|一些(r)->r
|None->failwith(“这是不可能的!输入序列不是空的!”)
这看起来像你的函数式编程专家会做的事情吗?或者你会怎么做


虽然我以前使用的蛮力搜索是尾部递归的(没有建立调用堆栈),但这个纯函数版本不再是尾部递归的。有人能找到一种方法使它再次递归吗?

我既不熟悉该算法,也不熟悉F#,因此我将其转换为纯函数变体:

function alphabeta(node, depth, α, β, maximizingPlayer)
  if depth == 0 or node is a terminal node
    return the heuristic value of node
  if maximizingPlayer
    return take_max(children(node), depth, α, β)
  else
    return take_min(children(node), depth, α, β)

function take_max(children, depth, α, β)
  v = max(v, alphabeta(head(children), depth - 1, α, β, FALSE))
  new_α = max(α, v)

  if β ≤ new_α or tail(children) == Nil
    return v
  else
    return take_max(tail(children), depth, α, β))

function take_min(children, depth, α, β)
  v = min(v, alphabeta(head(children), depth - 1, α, β, TRUE))
  new_β = min(β, v)

  if new_β ≤ α or tail(children) == Nil
    return v
  else
    return take_min(tail(children), depth, α, β))
诀窍是将带有
break
foreach
转换为具有适当基本情况的递归。我假设
children(node)
返回一个cons节点列表,可以使用
head
/
tail
解构它,并测试
Nil

显然,我不能测试这个,但我认为它包含了正确的想法(它几乎就是Python…)


此外,也许这是一个记忆的例子——但这取决于领域(我不熟悉)。这种递归可能更难并行化;为此,您可能可以建立一个并行的
v
s和alphas/beta列表(因为对
alphabeta
的调用可能是最昂贵的部分),在这些列表上用
takeWhile
s替换递归

此外,您还可以看一下Rus的实现
function alphabeta(node, depth, α, β, maximizingPlayer)
  if depth == 0 or node is a terminal node
    return the heuristic value of node
  if maximizingPlayer
    return take_max(children(node), depth, α, β)
  else
    return take_min(children(node), depth, α, β)

function take_max(children, depth, α, β)
  v = max(v, alphabeta(head(children), depth - 1, α, β, FALSE))
  new_α = max(α, v)

  if β ≤ new_α or tail(children) == Nil
    return v
  else
    return take_max(tail(children), depth, α, β))

function take_min(children, depth, α, β)
  v = min(v, alphabeta(head(children), depth - 1, α, β, TRUE))
  new_β = min(β, v)

  if new_β ≤ α or tail(children) == Nil
    return v
  else
    return take_min(tail(children), depth, α, β))