【问题标题】:Need ideas to transform F# imperative code to functional需要将 F# 命令式代码转换为函数式的想法
【发布时间】:2012-07-02 09:31:18
【问题描述】:

我有一个以命令式风格编写的函数,我不知道如何将其转换为更健壮的函数式方法。

该函数接受一个字符串序列并返回一个元组序列,其中每个元组由输入中的 2,7,12,.. 和 5,10,15,.. 项组成。

例子:

输入 = { “Lorem”、“ipsum”、“dolor”、“set”、“amet”、“consectetuer”、“adipiscing”、“elit”、“Aenean”、“commodo”、“ligula”、 “eget”、“dolor”、“Aenean”、“massa”}

输出 = { ("ipsum", "amet"), ("adipiscing", "commodo"), ("eget", "massa") }

let convert (input : seq<string>) : seq<(string * string)> =
    let enum = input.GetEnumerator()
    let index = ref 0
    let first = ref ""
    let second = ref ""

    seq {
        while enum.MoveNext() do
            let modIndex = !index % 5
            index := !index + 1

            if (modIndex % 2 = 0 && !first = "") then first := enum.Current
            if (modIndex % 5 = 0 && !second = "") then second := enum.Current

            if modIndex = 0  then
                let result = (!first, !second)
                first := ""
                second := ""
                yield result
    }

对于起点的任何帮助或提示表示赞赏。

【问题讨论】:

    标签: f# functional-programming imperative


    【解决方案1】:

    这里有更惯用的方法。实际上,它是单行的。我刚刚对其进行了对齐以提高可读性。

    let Input = [ "Lorem"; "ipsum"; "dolor"; "set"; "amet"; "consectetuer";
                  "adipiscing"; "elit"; "Aenean"; "commodo"; "ligula"; "eget";
                  "dolor"; "Aenean"; "massa" ]
    
    // Short solution that does not support more than two values
    let Output1 =
        Input
        |> List.fold
            (fun (i, l1, l2) x ->
                if i=4 then 0, None, (l1.Value, x)::l2
                elif i=1 then i+1, Some x, l2
                else i+1, l1, l2
            )
            (0, None, [])
        |> fun (_, _, elem) -> elem
        |> List.rev
    

    想法

    总体思路基于三个步骤:

    1. 将列表拆分为 元组List,获取第 2 个和第 5 个字符串。 警告如果原始数据长度不是 5 的倍数,则尾部元素将丢失。
    2. 通过获取第三个元素从 三元组 中过滤掉临时数据,这是我们的主要目标;
    3. 反转列表。

    说明

    第一行是最难的。

    让我们定义我们的状态。这将是一个 三元组 的序列号,一个包含字符串 ##2、7 等的 string option 和一个“外部”(string*string) list,当我们遇到元素 ##5、10 时添加等。

    该函数会将第 2、7 等元素放入“内部”string option,或者,如果i 等于 5、10 等,则形成一个 元组并添加它到“外部”List(为了清楚起见,删除内部值)。

    我们使用List.fold,因此最终列表要颠倒。

    初始状态是MSDN(0, None, []). More info onList.fold 的三元组

    第二行只取三元组中的第三个元素。我已将其设为允许链式绑定的功能。

    由于:: 运算符的性质,第三行颠倒了List

    根据初始列表的长度。如果它找到了“2nd”元素但没有到达“5th”,则 triple 的第二个元素具有该值。您可以通过验证来检测错误情况:

    ...
    |> fun (_, temp, elem) ->
        if temp.IsSome
        then failwith "Data length must be a multiplier of 5"
        else elem
    ...
    

    这里有一段较长的代码,它支持两个以上的元素:

    let Output2 = 
        Input
        |> List.foldBack
            (fun x (i, l1, l2) ->
                if i = 4
                then 0, [], (x::l1)::l2
                else i+1, x::l1, l2
            )
            <| (0, [], [])
        |> fun (_, _, elem) -> elem
        |> List.choose
            (function
            | [_; first; _; _; second] -> Some (first, second)
            | _-> None
            )
    

    请注意,此变体在第一次调用期间不会删除元素,因此您可能会检索两个以上的项目。

    重要提示:列表以相反的顺序处理,因此项目索引从输入的末尾开始计算。您可以将其成本更改为List.fold,或者进一步反转列表,如Output1

    由于List.foldBack 的签名,请注意反向绑定运算符&lt;|

    您可以通过类似的方式检查错误:通过测试“内部”列表是否不为空。

    【讨论】:

    • 我也喜欢您的解决方案,因为您不限于 Zip 或 Zip3 函数,并且可以简单地向结果元组添加更多元素。这里的问题是序列不存在 foldBack。谢谢你的建议!
    • 哦,我刚刚更新了我的答案以获得更短的解决方案,它不支持多个值。让我回滚以保留两者。
    【解决方案2】:

    我来自 haskell 而不是 f#,所以我将给出一个可能无效的 f# 代码想法:

    起初我会从我的输入中生成两个列表:

    let zeromod5 = filter (index == 0 % 5) input
    let twomod5 = filter (index == 2 % 5) input
    

    这应该导致列表

    { "ipsum", "adipiscing","eget"}
    { "amet", "commodo","massa" }
    

    然后压缩它们,i。 e.用

    之类的东西制作一个配对列表
    zip zeromod5 twomod5
    

    编辑:

    Haskell 版本:

    zipWeird :: [String] -> [(String, String)]
    zipWeird ss = zip twoMod5s zeroMod5s
                where zeroMod5s = map fst $ filter (\(_,y) -> y `mod` 5 == 0) eSS
                      twoMod5s = map fst $ filter (\(_,y) -> y `mod` 5 == 2) eSS
                      eSS = zip ss [1..]
    
    zipWeird2 :: [String] -> [(String, String)]
    zipWeird2 ss = map fst $ filter (\(_,y) -> y `mod`5 ==1) ezSS
                 where zSS = zip (tail ss) (drop 4 ss)
                       ezSS = zip zSS [1..]
    
    input :: [String]
    input = words ("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, "++
                  "sed diam nonumy eirmod tempor invidunt ut labore et dolore "++
                  "magna aliquyam erat, sed diam voluptua. At vero eos et "++
                  "accusam et justo duo dolores et ea rebum. Stet clita kasd "++
                  "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit "++
                  "amet.")
    
    main :: IO ()
    main = do 
              print $ zipWeird input
              print $ zipWeird2 input
    

    【讨论】:

    • 正如你所说,这不是有效的 F#(事实上,它也不是有效的 Haskell)。如果您使用的是 Mac 或 Windows,那么您可以使用在浏览器中托管 F# 编译器的tryfsharp.org 轻松学习 F#。如果你在 Linux 上,那么有一个在服务器端运行 F# 的站点 - 它没有那么好的 UI,但你仍然可以使用它来运行基本的 F#:tryfs.net
    【解决方案3】:

    我不完全理解您想要的行为 - 生成您想要配对的索引的算法是什么?无论如何,一个不错的功能性解决方案是将要配对的元素分开,然后使用 Seq.zip 组合它们。

    您可以使用Seq.mapi 为值添加索引,然后使用Seq.choose 获取具有正确索引的值(并跳过所有其他值)。对于硬编码索引,您可以编写如下内容:

    let indexed = input |> Seq.mapi (fun i s -> i, s)
    Seq.zip 
      (indexed |> Seq.choose (fun (i, v) -> if i=1 || i=6 || i=11 then Some v else None))
      (indexed |> Seq.choose (fun (i, v) -> if i=4 || i=9 || i=14 then Some v else None))
    

    我使用你的数字 -1 因为索引是从 0 开始的 - 所以上面给出了你想要的结果。第二个系列看起来像 5 的倍数,所以也许您希望 i%5 = 4 生成第二个元素:

    let indexed = input |> Seq.mapi (fun i s -> i, s)
    Seq.zip 
      (indexed |> Seq.choose (fun (i, v) -> if i=1 || i=6 || i=11 then Some v else None))
      (indexed |> Seq.choose (fun (i, v) -> if i%5 = 4 then Some v else None))
    

    我仍然没有看到生成第一个元素的一般机制!

    EDIT 还有一个想法——第一个序列是由i*5 + 2 生成的,第二个是由i*5 生成的吗?在这种情况下,您的示例是错误的,但您可以这样写:

    let indexed = input |> Seq.mapi (fun i s -> i, s)
    Seq.zip 
      (indexed |> Seq.choose (fun (i, v) -> if i%5 = 2 then Some v else None))
      (indexed |> Seq.choose (fun (i, v) -> if i%5 = 0 then Some v else None))
    

    ...或者如果你想让代码更简洁,你可以重构:

    let filterNthElements div rem = 
      input |> Seq.mapi (fun i s -> i, s)
            |> Seq.choose (fun (i, v) -> if i%div = rem then Some v else None)
    
    Seq.zip (filterNthElements 5 2) (filterNthElements 5 0)
    

    【讨论】:

    • 我必须添加一个以将其更改为 (fun i s -> i + 1, s) 并且它可以工作。我真的更喜欢功能性风格。感谢您的帮助、您的博客以及您为回答 SO 问题所付出的辛勤工作!
    猜你喜欢
    • 2011-09-30
    • 1970-01-01
    • 1970-01-01
    • 2013-06-28
    • 1970-01-01
    • 2013-10-11
    • 2016-01-08
    • 2012-10-20
    • 1970-01-01
    相关资源
    最近更新 更多