0

我有一个以命令式风格编写的函数女巫,无法弄清楚如何将其转换为更强大的函数式方法。

该函数接受一个字符串序列并返回一个元组序列,其中每个元组由输入中的 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
    }

感谢您对起点的任何帮助或提示。

4

3 回答 3

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)
于 2012-07-01T21:27:06.363 回答
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,取第二个和第五个字符串。警告如果原始数据长度不是 5 的倍数,则尾随元素将丢失。
  2. 通过获取第三个元素从三元组中过滤掉临时数据,这是我们的主要目标;
  3. 反转列表。

解释

第一行是最难的。

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

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

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

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

第二行只是从三元组中获取第三个元素。我已经使它成为允许链绑定的功能。

List由于运营商的性质,第三行颠倒了::

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

...
|> 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.foldin cost 或进一步反转列表,如Output1.

由于. <|_List.foldBack

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

于 2012-07-02T08:22:24.683 回答
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

编辑:

哈斯克尔版本:

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
于 2012-07-01T21:34:08.067 回答