【问题标题】:Equivalent of repeated takeWhile calls: does this function have a "standard" name?等同于重复的 takeWhile 调用:这个函数有一个“标准”名称吗?
【发布时间】:2017-01-19 13:07:36
【问题描述】:

我有一个场景,标准的 List.groupBy 函数不是我想要的,但我不知道该函数的正确名称,因此很难搜索。

我有一个'T 类型的项目列表和一个'T -> 'k 密钥生成函数。这些项目已经在列表中“分组”在一起,因此当您通过 key 函数映射列表时,其结果往往会在同一行中多次出现相同的键,例如[1; 1; 1; 2; 2; 1; 1; 3; 3; 3; 1; 1]。我想要的是获得一个列表列表,其中内部列表包含键生成函数返回相同值的所有项目 - 但它不应该将不同的 1 序列组合在一起。

换句话说,假设我的数据是一个字符串列表,生成密钥的函数是String.length。所以输入是:

["a"; "e"; "i"; "to"; "of"; "o"; "u"; "and"; "for"; "the"; "I"; "O"]

我正在寻找的输出是:

[["a"; "e"; "i"]; ["to"; "of"]; ["o"; "u"]; ["and"; "for"; "the"]; ["I"; "O"]]

换个角度想:这就像获取列表的第一项并存储调用键函数的结果。然后您将使用takeWhile (fun x -> keyFun x = origKeyFunResult) 生成第一段。然后,当 takeWhile 停止返回值时,您记录它停止的时间,并在第一个没有返回原始结果的值上记录 keyFun x 的值——然后从那里继续。 (除了那将是 O(N*M) ,其中 M 是序列的数量,并且在许多情况下会演变为 O(N^2) - 而应该可以在 O(N) 时间内实现此功能)。

现在,我可以很容易地编写那个函数了。那不是问题。我想知道这个函数是否有一个标准的name。因为我认为它会被称为groupBy,但那是另一回事。 (List.groupBy String.length 将返回 [(1, ["a"; "e"; "i"; "o"; "u"; "I"; "O"]); (2, ["to"; "of"]), (3, ["and"; "for"; "the"])],但在这种情况下,我希望“a/e/i”、“o/u”和“I/O”列表保持分离,但我不这样做希望键生成返回的值在输出数据中)。

也许这个函数没有标准名称。但如果有,那是什么?

【问题讨论】:

  • 我认为没有标准名称。你应该继续写这个;您已经说过它很容易编写,并且您为该函数编写的代码将用于记录它的作用。
  • 另见groupAdjBy,它类似于您的函数,但通用,我的意思是它适用于数组、列表和序列。
  • GroupAdjacent in MoreLINQ 也是如此。
  • 把它命名为chunkchunkBy,因为与List.chunkBySize 相似。两者都创建一个列表列表,保留原始列表的顺序,另一个按大小分块,另一个按谓词分块。

标签: f# functional-programming


【解决方案1】:

我来晚了,看来您已经找到了解决方案,而且似乎 F# 中不存在可以处理该问题的单个函数。

为了应对挑战,我试图找到一些可用的解决方案并提出以下建议(它们是否有效由读者决定):

open System

module List = 
  /// <summary>
  /// Generic List Extension:
  /// Given a comparer function the list will be chunked into sub lists
  /// starting when ever comparer finds a difference.
  /// </summary>
  let chunkByPredicate (comparer : 'T -> 'T -> bool) list = 
    let rec func (i : int, lst : 'T list) : 'T list list =
      if i >= lst.Length then
        List.empty
      else
        let first = lst.[i]
        let chunk = lst |> List.skip(i) |> List.takeWhile (fun s -> comparer first s)
        List.append [chunk] (func((i + chunk.Length), lst))

    func (0, list) |> List.where (fun lst -> not (List.isEmpty lst))

// 1. Using List.fold to chunk by string length
let usingListFold (data : string list) = 
  printfn "1. Using List.fold: "
  data 
  |> List.fold (fun (acc : string list list) s -> 
                  if acc.Length > 0 then
                    let last = acc.[acc.Length - 1]
                    let lastLength = last.[0].Length
                    if lastLength = s.Length then
                      List.append (acc |> List.take (acc.Length - 1)) [(last |> List.append [s])]
                    else
                      List.append acc  [[s]]
                  else
                    [[s]]) ([])

  |> List.iter (printfn "%A")
  printfn ""

// 2. Using List.chunkByPredicate
let usingListChunkByPredicate<'a> (predicate : 'a -> 'a -> bool, data : 'a list) = 
  printfn "2. Using List.chunkByPredicate: "
  data
  |> List.chunkByPredicate predicate
  |> List.iter (printfn "%A")
  printfn ""

[<EntryPoint>]
let main argv = 
  let data = ["a"; "e"; "i"; "to"; "of"; "o"; "u"; "and"; "for"; "the"; "I"; "O"]

  usingListFold data
  usingListChunkByPredicate<string>((fun first s -> first.Length = s.Length), data)

  let intData = [0..50]
  usingListChunkByPredicate<int>((fun first n -> first / 10 = n / 10), intData)

  Console.ReadLine() |> ignore
  0

【讨论】:

    猜你喜欢
    • 2010-12-05
    • 2015-11-01
    • 1970-01-01
    • 2013-11-03
    • 2021-09-15
    • 2015-11-09
    • 1970-01-01
    • 1970-01-01
    • 2021-04-26
    相关资源
    最近更新 更多