簡體   English   中英

遞歸C#函數從for循環內部返回 - 如何轉換為F#?

[英]Recursive C# function returns from inside a for-loop - how to translate to F#?

很長一段時間C#開發人員,學習F#。 我選擇了一個功能相當的C#代碼,我將其作為一個學習練習 - 將其翻譯為F#。 我已經做了很多關於函數式編程的閱讀,並定期使用C#中的函數結構,但我只有幾個小時的學習F#。

此功能是解決類似於“LonPos 101”的難題的程序的一部分,您可以在亞馬遜等上找到該解決方案。解算器中使用的策略基於識別拼圖空間中只有30個有效位置,因此“迄今為止的解決方案”可以用一個整數表示,每個部分的每個有效位置也可以用一個整數表示,一個完整的解決方案是一個包含7個部分中每一個的可能位置的集合,其中“權重” “7件中加入溶液重量(2 ^ 30-1)。 在給定的函數中,“key”是該片段的“主鍵”,wbk是“按鍵加權” - 按鍵索引,包含相應片段的有效位置列表,而“w”是上述“解決方案” -至今”。 返回值是從鍵到選定位置的映射,並且在導致解決方案的成功遞歸的出口路徑上填充。 我發現在開發C#解決方案時,使這個排序列表使解決方案查找器的速度提高了一個數量級,但它與普通列表同樣有效。

這是我遇到問題的C#函數:

int solutionWeight;

Dictionary<int,int> Evaluate(int w, Dictionary<int, SortedSet<int>> wbk, int key)
{
  if (w == solutionWeight)
    return new Dictionary<int, int>();  

  if (key == 8)
    return null;

  foreach (var w2 in wbk[key])
  {
    if ((w & w2) != 0)
      continue;
    var s = Evaluate(w | w2, wbk, key + 1);
    if (s != null)
    {
      s.Add(key, w2);
      return s;
    }
  }

  return null;
}

這是我對F#版本的嘗試 - 它編譯,但它無法正常工作 - 當執行w不是solutionWeight並且key等於8的情況時,它最終會在let ss = ...行中失敗並導致KeyNotFoundException失敗。對我來說,為什么這行代碼甚至在這種情況下被執行是沒有意義的,但......

    let rec Evaluate(w:int, wbk:Dictionary<int, SortedSet<int>>, key:int):Dictionary<int,int> =
    if w = solutionWeight then 
        Dictionary<int,int>()
    else if key = 8 then 
        null
    else
        // ... this is wrong - runs off the end of some collection - fails with key not found exception
        let ws = wbk.[key] |> Seq.filter (fun w2 -> (w2 &&& w) = 0) 
        /// ... for some reason, execution resumes here after the key = 8 clause above
        let ss = ws |> Seq.map (fun w -> (w,Evaluate(w, wbk, key+1))) 
        let sw = ss |> Seq.find (fun sw -> snd sw <> null) 
        let s = snd sw 
        s.Add(key, fst sw)
        s

指出我正確的方向! 我的下一個嘗試是首先以更實用的方式重寫C#代碼,但感覺這個版本即將開始工作(雖然可能仍然遠離慣用的F#)。

更新:

我重新編寫了F#函數,通過使用一對相互遞歸的函數來消除循環。 它確實有效,但它比非互相遞歸的C#版本慢約2倍。

let rec Evaluate(w:int, wbk:Dictionary<int, SortedSet<int>>, key:int):Dictionary<int,int> =
    if w = solutionWeight then 
        Dictionary<int,int>()
    else if key = 8 then 
        null
    else
        EvalHelper(w, wbk, key, wbk.[key].GetEnumerator())

and EvalHelper(w:int, wbk:Dictionary<int, SortedSet<int>>, key:int, ws:IEnumerator<int>):Dictionary<int,int> =
    if ws.MoveNext() then
        let w2 = ws.Current
        if (w &&& w2) = 0 then
            let s = Evaluate(w ||| w2, wbk, key+ 1)
            if s <> null then
                s.Add(key, w2)
                s
            else
                EvalHelper(w, wbk, key, ws)
        else
            EvalHelper(w, wbk, key, ws)
    else
        null

我該如何進一步改進它?

更新:我更多地調整它 - 感覺更多F#ish,但我仍然覺得我應該能夠擺脫更多類型的注釋並更多地使用F#native類型。 這是一項正在進行中的工作。

let rec Evaluate(w, wbk:Dictionary<int, SortedSet<int>>, key):Dictionary<int,int> option =
    let rec EvalHelper(ws) =
        match ws with
        | w2 :: mws ->
            match w &&& w2 with
            | 0 ->
                let s = Evaluate(w ||| w2, wbk, key+ 1)
                match s with
                | None -> EvalHelper(mws)
                | Some s ->
                    s.Add(key, w2)
                    Some(s)
            | _ -> EvalHelper(mws)
        | _ ->
            None

    if w = solutionWeight then 
        Some (Dictionary<int,int>())
    else if key = 8 then 
        None
    else
        EvalHelper(List.ofSeq wbk.[key])

翻譯此函數的關鍵是將for循環轉換為遞歸,如第一次更新中所示。

let rec Evaluate(w:int, wbk:Dictionary<int, SortedSet<int>>, key:int):Dictionary<int,int> =
    if w = solutionWeight then 
        Dictionary<int,int>()
    else if key = 8 then 
        null
    else
        EvalHelper(w, wbk, key, wbk.[key].GetEnumerator())

and EvalHelper(w:int, wbk:Dictionary<int, SortedSet<int>>, key:int, ws:IEnumerator<int>):Dictionary<int,int> =
    if ws.MoveNext() then
        let w2 = ws.Current
        if (w &&& w2) = 0 then
            let s = Evaluate(w ||| w2, wbk, key+ 1)
            if s <> null then
                s.Add(key, w2)
                s
            else
                EvalHelper(w, wbk, key, ws)
        else
            EvalHelper(w, wbk, key, ws)
    else
        null

隨后的更新剛剛清理了一下風格 - 將它推向了慣用的F#。

let rec Evaluate(w, wbk:Dictionary<int, SortedSet<int>>, key):Dictionary<int,int> option =
    let rec EvalHelper(ws) =
        match ws with
        | w2 :: mws ->
            match w &&& w2 with
            | 0 ->
                let s = Evaluate(w ||| w2, wbk, key+ 1)
                match s with
                | None -> EvalHelper(mws)
                | Some s ->
                    s.Add(key, w2)
                    Some(s)
            | _ -> EvalHelper(mws)
        | _ ->
            None

    if w = solutionWeight then 
        Some (Dictionary<int,int>())
    else if key = 8 then 
        None
    else
        EvalHelper(List.ofSeq wbk.[key])

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM