总结F#中任意级别的嵌套的列表


10

我正在尝试创建一个F#函数,该函数将返回int任意嵌套的s 列表的总和。就是 但是对于工作list<int>,一list<list<int>>和a list<list<list<list<list<list<int>>>>>>

在Haskell中,我将编写如下内容:

class HasSum a where
    getSum :: a -> Integer

instance HasSum Integer where
    getSum = id

instance HasSum a => HasSum [a] where
    getSum = sum . map getSum

这会让我做:

list :: a -> [a]
list = replicate 6

nestedList :: [[[[[[[[[[Integer]]]]]]]]]]
nestedList =
    list $ list $ list $ list $ list $
    list $ list $ list $ list $ list (1 :: Integer)

sumNestedList :: Integer
sumNestedList = getSum nestedList

如何在F#中实现这一目标?


1
我对F#的了解还不够,我不知道它是否支持Haskell的类型类。在最坏的情况下,即使不如在Haskell中那样方便,编译器会为您推断正确的字典,您也应该能够传递显式字典。在这种情况下,F#代码会是这样的getSum (dictList (dictList (..... (dictList dictInt)))) nestedList,其中的数dictList匹配的数目[]中的类型nestedList

您能否使此haskell代码在REPL上可运行?
Filipe Carvalho


F#没有类型类(github.com/fsharp/fslang-suggestions/issues/243)。我尝试了理论上可以起作用的运算符重载技巧,但我设法使编译器崩溃了,但也许您可以做出一些技巧:stackoverflow.com/a/8376001/418488
只是另一个元编程人员

2
我无法想象任何实际的F#代码库都需要这个。您这样做的动机是什么?我可能会更改设计,以使您不会陷入这种情况-无论如何,这可能会使您的F#代码变得更好。
Tomas Petricek

Answers:


4

更新

我发现使用运算符($)而不是成员的更简单版本。受https://stackoverflow.com/a/7224269/4550898的启发:

type SumOperations = SumOperations 

let inline getSum b = SumOperations $ b // <-- puting this here avoids defaulting to int

type SumOperations with
    static member inline ($) (SumOperations, x  : int     ) = x 
    static member inline ($) (SumOperations, xl : _   list) = xl |> List.sumBy getSum

其余的解释仍然适用,并且很有用。

我找到了一种使之成为可能的方法:

let inline getSum0< ^t, ^a when (^t or ^a) : (static member Sum : ^a -> int)> a : int = 
    ((^t or ^a) : (static member Sum : ^a -> int) a)

type SumOperations =
    static member inline Sum( x : float   ) = int x
    static member inline Sum( x : int     ) =  x 
    static member inline Sum(lx : _   list) = lx |> List.sumBy getSum0<SumOperations, _>

let inline getSum x = getSum0<SumOperations, _> x

2                  |> getSum |> printfn "%d" // = 2
[ 2 ; 1 ]          |> getSum |> printfn "%d" // = 3
[[2; 3] ; [4; 5] ] |> getSum |> printfn "%d" // = 14

运行您的示例:

let list v = List.replicate 6 v

1
|> list |> list |> list |> list |> list
|> list |> list |> list |> list |> list
|> getSum |> printfn "%d" // = 60466176

这是基于将SRTP与成员约束一起使用的:static member Sum该约束要求类型具有一个称为的成员Sum ,该成员返回int。使用SRTP时,需要通用功能inline

那不是困难的部分。困难的部分是将Sum成员“添加” 到现有类型,例如intList这是不允许的。但是,我们可以将其添加到新类型SumOperations中,并将约束始终包含在(^t or ^a) 其中。^tSumOperations

  • getSum0声明Sum成员约束并调用它。
  • getSumSumOperations作为第一个类型参数 传递给getSum0

static member inline Sum(x : float ) = int x添加该行是为了说服编译器使用通用动态函数调用,而不仅仅是static member inline Sum(x : int )调用时的默认值List.sumBy

如您所见,这有点令人费解,语法很复杂,有必要解决编译器上的一些问题,但最终还是可以的。

此方法可以通过添加更多的定义扩展到使用数组,元组,选项等或它们的任何组合工作SumOperations

type SumOperations with
    static member inline ($) (SumOperations, lx : _   []  ) = lx |> Array.sumBy getSum
    static member inline ($) (SumOperations, a  : ^a * ^b ) = match a with a, b -> getSum a + getSum b 
    static member inline ($) (SumOperations, ox : _ option) = ox |> Option.map getSum |> Option.defaultValue 0

(Some 3, [| 2 ; 1 |]) |> getSum |> printfn "%d" // = 6

https://dotnetfiddle.net/03rVWT


这是一个很好的解决方案!但是为什么不仅仅递归或折叠呢?
s952163

4
递归和折叠不能处理各种类型。实例化通用递归函数时,它将修复参数的类型。在这种情况下,每次调用Sum与一个简单的类型来完成的:Sum<int list list list>Sum<int list list>Sum<int list>Sum<int>
AMieres

2

这是运行时版本,可用于所有.net集合。但是,在AMieres的答案中交换编译器错误以获取运行时异常,而AMieres的速度也提高了36倍。

let list v = List.replicate 6 v

let rec getSum (input:IEnumerable) =
    match input with
    | :? IEnumerable<int> as l -> l |> Seq.sum
    | e -> 
        e 
        |> Seq.cast<IEnumerable> // will runtime exception if not nested IEnumerable Types
        |> Seq.sumBy getSum


1 |> list |> list |> list |> list |> list
|> list |> list |> list |> list |> list |> getSum // = 60466176

基准测试

|    Method |        Mean |     Error |    StdDev |
|---------- |------------:|----------:|----------:|
| WeirdSumC |    76.09 ms |  0.398 ms |  0.373 ms |
| WeirdSumR | 2,779.98 ms | 22.849 ms | 21.373 ms |

// * Legends *
  Mean   : Arithmetic mean of all measurements
  Error  : Half of 99.9% confidence interval
  StdDev : Standard deviation of all measurements
  1 ms   : 1 Millisecond (0.001 sec)

1
它运行良好,但速度明显慢:运行10次需要56秒,而使用其他解决方案则需要1秒。
AMieres

刻薄的基准测试!你用了什么?
AMieres

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.