Підсумовування списків довільних рівнів вкладеності у F #


10

Я намагаюся створити функцію F #, яка поверне суму списку ints довільної вкладеності. Тобто він буде працювати для 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/isissue/243 ). Я спробував операцію з перевантажувальним трюком, який теоретично міг би працювати, але мені просто вдалося зірвати компілятор, але, можливо, ви можете зробити щось із цього фокусу: stackoverflow.com/a/8376001/418488
Ще один метапрограмер

2
Я не уявляю жодної реалістичної бази даних F #, де вам це знадобиться. Якою була ваша мотивація до цього? Я б, напевно, змінив дизайн, щоб ви не потрапляли в подібну ситуацію - це, мабуть, полегшить ваш F # код все одно.
Томаш Петричек

Відповіді:


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.

  • getSum0оголошує Sumобмеження члена і викликає його.
  • getSum передається SumOperationsяк параметр першого типу до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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.