【问题标题】:Transposing nested lists in F#在 F# 中转置嵌套列表
【发布时间】:2019-10-03 12:19:14
【问题描述】:

我想在 F# 中转置一个嵌套列表 ('a list list)。问题是,我不想使用递归。

但是,我发现为了不使用递归,我必须使用可变列表。在我看来,这有点问题。尽管如此,我还是尝试用一个 for 循环和两个可变列表来实现它:

let transpose (llst : 'a list list) : 'a list list =
   let mutable lst = llst
   let mutable result = [List.map List.head lst]
   for i = 1 to lst.Length do
      lst <- List.map List.tail lst
      result <- List.append result [List.map List.head lst]
   result

此外,我已经尝试避免它。我知道数组的可变性,所以我尝试通过将'a list list 转换为 2darray ([,]) 来解决问题,但是,当尝试将结果转换回嵌套列表时,它失败了,因为List.ofArray仅适用于一维数组:

let transpose (llst : 'a list list) : 'a list list = [List.ofArray (Array2D.init (llst.ToArray.GetLength 1) (llst.ToArray.GetLength 0) (fun x y -> llst.ToArray.[y,x]))]

第一个代码有效,但我想要一个更简单的实现,没有 for 循环或可变列表。 第二个代码不起作用,因为函数List.ofArray 仅适用于[],不适用于[,]

我也试过

List.init (llst.[0].Length) (List.forall (fun SOMETHING) llst)

SOMETHING 将包含一个函数,该函数获取两个子列表的第一列,然后删除第一列。 (List.map List.head 和 List.map List.tail)。也许使用List.map

【问题讨论】:

    标签: list f# nested transpose


    【解决方案1】:

    这样做的一种方法是采用递归定义并将其转换为非递归。例如,以下递归定义改编自 this answer,并对其使用列表推导进行了调整:

    let rec transpose xs = [
      match xs with
      | [] -> failwith "cannot transpose a 0-by-n matrix"
      | []::xs -> () 
      | xs -> 
          yield List.map List.head xs 
          yield! transpose (List.map List.tail xs) ]
    

    这是尾递归的,因此很容易转换为循环。您只需将参数 xs 设为可变并将递归调用替换为突变。我们还需要添加一个标志来终止循环:

    let rec transpose xs = [
      let mutable xs = xs
      let mutable finished = false
      while not finished do
        match xs with
        | [] -> failwith "cannot transpose a 0-by-n matrix"
        | []::_ -> finished <- true
        | _ -> 
            yield List.map List.head xs 
            xs <- List.map List.tail xs ]
    

    如果您想要一个没有序列推导的可变版本,您可以轻松做到 - 推导只是收集您使用 yield 添加的各个子列表。您可以将它们添加到可变列表中(然后反转列表以按正确顺序获取结果):

    let rec transpose xs = 
      let mutable result = []
      let mutable xs = xs
      let mutable finished = false
      while not finished do
        match xs with
        | [] -> failwith "cannot transpose a 0-by-n matrix"
        | []::_ -> finished <- true
        | _ -> 
            result <- (List.map List.head xs) :: result
            xs <- List.map List.tail xs 
      List.rev result     
    

    【讨论】:

    • 非常感谢。但是,我宁愿不使用任何可变值。也许我可以使用 List.init 来制作一个嵌套列表。有点像f# let transpose (llst : 'a list list) : 'a list list = List.init (llst.[0].Length) (List.init (fun (TRANSPOSE)) 我认为这将是一个非常好的极简实现,但我不知道是否可能。
    【解决方案2】:

    所以,我很感谢您的回复,但我真正追求的是如何制作最简单,最紧凑的函数来转置列表列表。我现在想出了一个有点奇怪的实现。虽然紧凑,但我认为它相当复杂:

    let transpose (llst: 'a list list) : 'a list list = 
    List.map (List.map List.head) (List.init llst.Head.Length (fun x -> List.fold (fun x f -> f x) llst (List.init x (fun x -> (List.map List.tail)))))
    

    可以通过一些子函数变得更简单,但我很有趣这样一个奇怪的实现是如何工作的。谢谢。

    【讨论】:

    • 这真的没有什么简单的。它只是紧凑的,我几乎称它为混淆。它也很脆弱,而且可能是错误的。例如,它将在空列表上崩溃。玩弄这样的想法当然会很有趣,但请不要在别人可能需要理解的地方编写这种代码。我知道您可能从未打算这样做,但有些人实际上认为这样的代码很好,所以这是针对他们的警告。如果你这样写代码,我会找到你的,我会非常严厉地朝你的方向看!
    • $glennsl 我完全同意你所写的一切,显然,在 F# 中转置列表列表的最佳方法是递归映射。此代码并非在所有情况下都有效,也不是可读/可理解的,但它确实适用于最简单和正常的情况。鉴于我想要一个没有可变(变量)的非递归函数,我认为这是最紧凑的解决方案。
    猜你喜欢
    • 1970-01-01
    • 2014-02-22
    • 2018-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-02
    • 1970-01-01
    相关资源
    最近更新 更多