需要将F#命令式代码转换为功能性的想法

时间:2012-07-01 21:20:01

标签: f# functional-programming imperative

我有一个函数女巫以强制性的风格编写,无法理解如何将其转换为更强大的功能方法。

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

示例:

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

Ouput = {(“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
    }

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

3 个答案:

答案 0 :(得分:6)

我不完全理解你想要的行为 - 生成你想要配对的索引的算法是什么?无论如何,一个很好的功能解决方案是将你想要分开的元素分开,然后使用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))

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

编辑还有一个想法 - 是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)

答案 1 :(得分: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. 扭转名单。

  4. 解释

    第一行是最难的一行。

    让我们定义状态。它将是序列号的三元string option包含字符串## 2,7等,以及一旦我们遇到元素就添加的“外部”(string*string) list# #5,10等

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

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

    初始状态是MSDN(0, None, []). More info on List.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

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

答案 2 :(得分:0)

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

首先,我会从输入中生成两个列表:

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

应该产生列表

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

然后压缩它们,我。即通过

之类的东西制作一对配对列表
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