F#での入れ子の任意のレベルのリストの合計


10

私はint任意の入れ子のsのリストの合計を返すF#関数を作成しようとしています。つまり。a list<int>、a 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で実行可能にできますか?
フィリペカルバーリョ

ここに行きます... repl.it/repls/BlondCoolParallelport
karakfa

F#には型クラスがありません(github.com/fsharp/fslang-suggestions/issues/243)。私は理論的に仕事ができるというトリックを演算子オーバーロードを試みたが、私は、コンパイラがクラッシュするために管理しますが、おそらくあなたは、トリックの何かを作ることができます。stackoverflow.com/a/8376001/418488
ちょうど別のメタプログラマ

2
これが必要になる現実的なF#コードベースは想像できません。これを行う動機は何でしたか?このような状況にならないように、おそらく設計を変更します。おそらく、とにかくF#コードが改善されるでしょう。
Tomas Petricek

回答:


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、既存のタイプにメンバーを「追加」することでintあり、Listこれは許可されていません。しかし、それを新しいタイプに追加して、常に がどこSumOperationsにあるかを制約(^t or ^a)に含める^tことができますSumOperations

  • getSum0Sumメンバー制約を宣言して呼び出します。
  • 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>
アミエール

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秒でした。
アミエール

印象的なベンチマーク!何を使いましたか
アミエール

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.