Riassumendo elenchi di livelli arbitrari di nidificazione in F #


10

Sto cercando di creare una funzione F # che restituirà la somma di un elenco di ints di nidificazione arbitraria. Vale a dire. funzionerà per a list<int>, a list<list<int>>e a list<list<list<list<list<list<int>>>>>>.

In Haskell scriverei qualcosa del tipo:

class HasSum a where
    getSum :: a -> Integer

instance HasSum Integer where
    getSum = id

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

che mi lascerebbe fare:

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

Come posso raggiungere questo obiettivo in F #?


1
Non conosco abbastanza F # - non so se supporta qualcosa come le macchine da scrivere di Haskell. Nel peggiore dei casi, dovresti essere in grado di passare dizionari espliciti anche se non è conveniente come in Haskell, dove il compilatore fornisce i dizionari giusti per te. Il codice F # in questo caso sarebbe qualcosa di simile a getSum (dictList (dictList (..... (dictList dictInt)))) nestedListdove il numero di dictListcorrisponde al numero di []nel tipo di nestedList.
Chi

Potresti rendere questo codice haskell eseguibile su un REPL?
Filipe Carvalho,


F # non ha classi di tipi ( github.com/fsharp/fslang-suggestions/issues/243 ). Ho provato il trucco di sovraccarico dell'operatore che in teoria potrebbe funzionare, ma sono riuscito a bloccare il compilatore, ma forse puoi fare qualcosa del trucco: stackoverflow.com/a/8376001/418488
Solo un altro metaprogrammatore

2
Non riesco a immaginare alcuna base di codice F # realistica in cui ne avresti bisogno. Qual è stata la tua motivazione per farlo? Probabilmente cambierei il design in modo da non entrare in una situazione come questa - probabilmente migliorerà comunque il tuo codice F #.
Tomas Petricek,

Risposte:


4

AGGIORNARE

Ho trovato una versione più semplice utilizzando un operatore ($)anziché un membro. Ispirato da 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

Il resto della spiegazione è ancora valido ed è utile ...

Ho trovato un modo per renderlo possibile:

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

Eseguendo il tuo esempio:

let list v = List.replicate 6 v

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

Questo si basa sull'uso di SRTP con vincoli di membro: static member Sumil vincolo richiede che il tipo abbia un membro chiamato Sum che restituisce un int. Quando si utilizzano SRTP, è necessario disporre di funzioni generiche inline.

Questa non è la parte difficile. La parte difficile è "aggiungere" un Summembro a un tipo esistente simile inte Listche non è consentito. Ma possiamo aggiungerlo a un nuovo tipoSumOperations e includerlo nel vincolo (^t or ^a) dove ^tsarà sempre SumOperations.

  • getSum0dichiara il Sumvincolo membro e lo invoca.
  • getSum passaggi SumOperations come primo parametro di tipo agetSum0

La linea static member inline Sum(x : float ) = int x stata aggiunta per convincere il compilatore a utilizzare una chiamata di funzione dinamica generica e non solo a quella predefinita static member inline Sum(x : int )durante la chiamataList.sumBy

Come puoi vedere è un po 'contorto, la sintassi è complessa ed era necessario aggirare alcune stranezze sul compilatore ma alla fine era possibile.

Questo metodo può essere esteso per funzionare con array, tuple, opzioni, ecc. O qualsiasi combinazione di essi aggiungendo più definizioni a 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


questa è un'ottima soluzione! ma perché non solo ricorsione o piega?
s952163,

4
La ricorsione e la piega non possono gestire vari tipi. Quando viene istanziata una funzione ricorsiva generica, il tipo di parametri viene riparato. In questo caso ogni chiamata a Sumè fatto con un tipo più semplice: Sum<int list list list>, Sum<int list list>, Sum<int list>, Sum<int>.
AMieres,

2

Ecco la versione runtime, funzionerebbe con tutte le raccolte .net. Tuttavia, scambia gli errori del compilatore nella risposta di AMieres per le eccezioni di runtime e AMieres è anche 36 volte più veloce.

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

Punti di riferimenti

|    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
Funziona bene, anche se è notevolmente più lento: eseguirlo 10 volte ha richiesto 56 secondi rispetto a 1 secondo con l'altra soluzione.
AMieres,

Impressionante benchmarking! che cosa hai usato?
AMieres,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.