【问题标题】:Avoiding code duplication in F#避免 F# 中的代码重复
【发布时间】:2010-02-14 02:07:10
【问题描述】:

我有两个尝试将浮点列表转换为 Vector3 或 Vector2 列表的 sn-ps 代码。这个想法是一次从列表中取出 2/3 个元素并将它们组合为一个向量。最终结果是一个向量序列。

    let rec vec3Seq floatList =
        seq {
            match floatList with
            | x::y::z::tail -> yield Vector3(x,y,z)
                               yield! vec3Seq tail
            | [] -> ()
            | _ -> failwith "float array not multiple of 3?"
            }

    let rec vec2Seq floatList =
        seq {
            match floatList with
            | x::y::tail -> yield Vector2(x,y)
                            yield! vec2Seq tail
            | [] -> ()
            | _ -> failwith "float array not multiple of 2?"
            }

代码看起来非常相似,但似乎无法提取公共部分。有什么想法吗?

【问题讨论】:

  • 对我来说看起来很干净,我认为您不会遇到可维护性问题
  • 您可以编写可以抓取 N 个项目的通用代码,然后只使用 match 来选择 Vector3Vector2(根据需要),但为什么呢?开销会比你在这里的更复杂。现在,如果你一直到 12 岁,那就是另一回事了....

标签: f# tail-recursion code-duplication


【解决方案1】:

这是一种方法。我不确定这到底有多简单,但它确实抽象出了一些重复的逻辑。

let rec mkSeq (|P|_|) x =
  seq {
    match x with
    | P(p,tail) -> 
        yield p
        yield! mkSeq (|P|_|) tail
    | [] -> ()
    | _ -> failwith "List length mismatch" }

let vec3Seq =
  mkSeq (function
  | x::y::z::tail -> Some(Vector3(x,y,z), tail)
  | _ -> None)

【讨论】:

  • 越看越喜欢。
  • 很好地使用了部分活动模式。
  • 这非常优雅。我从没想过将活动模式传递给函数。惊人的。谢谢你。
【解决方案2】:

正如 Rex 所评论的,如果您只希望在两种情况下这样做,那么如果您保留代码原样,您可能不会遇到任何问题。但是,如果您想提取一个通用模式,那么您可以编写一个函数,将列表拆分为指定长度(2 或 3 或任何其他数字)的子列表。一旦你这样做了,你将只使用map 将每个指定长度的列表变成Vector

F# 库中没有拆分列表的功能(据我所知),因此您必须自己实现它。大致可以这样完成:

let divideList n list = 
  // 'acc' - accumulates the resulting sub-lists (reversed order)
  // 'tmp' - stores values of the current sub-list (reversed order)
  // 'c'   - the length of 'tmp' so far
  // 'list' - the remaining elements to process
  let rec divideListAux acc tmp c list = 
    match list with
    | x::xs when c = n - 1 -> 
      // we're adding last element to 'tmp', 
      // so we reverse it and add it to accumulator
      divideListAux ((List.rev (x::tmp))::acc) [] 0 xs
    | x::xs ->
      // add one more value to 'tmp'
      divideListAux acc (x::tmp) (c+1) xs
    | [] when c = 0 ->  List.rev acc // no more elements and empty 'tmp'
    | _ -> failwithf "not multiple of %d" n // non-empty 'tmp'
  divideListAux [] [] 0 list      

现在,您可以使用此函数来实现您的两个转换,如下所示:

seq { for [x; y] in floatList |> divideList 2 -> Vector2(x,y) }
seq { for [x; y; z] in floatList |> divideList 3 -> Vector3(x,y,z) }

这将发出警告,因为我们使用了不完整的模式,期望返回的列表的长度分别为 2 或 3,但这是正确的期望,因此代码可以正常工作。我还使用了序列表达式的简短版本,->do yield 做同样的事情,但它只能用于像这样的简单情况。

【讨论】:

    【解决方案3】:

    这类似于 kvb 的解决方案,但不使用部分活动模式。

    let rec listToSeq convert (list:list<_>) =
        seq {
            if not(List.isEmpty list) then
                let list, vec = convert list
                yield vec
                yield! listToSeq convert list
            }
    
    let vec2Seq = listToSeq (function
        | x::y::tail -> tail, Vector2(x,y)
        | _ -> failwith "float array not multiple of 2?")
    
    let vec3Seq = listToSeq (function
        | x::y::z::tail -> tail, Vector3(x,y,z)
        | _ -> failwith "float array not multiple of 3?")
    

    【讨论】:

      【解决方案4】:

      老实说,你所拥有的已经是最好的了,虽然你可以用这个来做的更紧凑:

      // take 3 [1 .. 5] returns ([1; 2; 3], [4; 5])
      let rec take count l =
          match count, l with
          | 0, xs -> [], xs
          | n, x::xs -> let res, xs' = take (count - 1) xs in x::res, xs'
          | n, [] -> failwith "Index out of range"
      
      // split 3 [1 .. 6] returns [[1;2;3]; [4;5;6]]
      let rec split count l =
          seq { match take count l with
                | xs, ys -> yield xs; if ys <> [] then yield! split count ys }
      
      let vec3Seq l = split 3 l |> Seq.map (fun [x;y;z] -> Vector3(x, y, z))
      let vec2Seq l = split 2 l |> Seq.map (fun [x;y] -> Vector2(x, y))
      

      现在,分解列表的过程被转移到它自己的通用“take”和“split”函数中,更容易将其映射到您想要的类型。

      【讨论】:

        猜你喜欢
        • 2014-03-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多