Menjumlahkan daftar tingkat kesewenang-wenangan dalam F #

10

Saya mencoba membuat fungsi F # yang akan mengembalikan jumlah daftar dari intnestedness yang berubah-ubah. Yaitu. itu akan bekerja untuk list<int>alist<list<int>> dan a list<list<list<list<list<list<int>>>>>>.

Di Haskell saya akan menulis sesuatu seperti:

class HasSum a where
    getSum :: a -> Integer

instance HasSum Integer where
    getSum = id

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

yang akan membiarkan saya melakukannya:

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

Bagaimana saya bisa mencapai ini di F #?

runeks
sumber
1
Saya tidak cukup tahu F # - Saya tidak tahu apakah ini mendukung sesuatu seperti kacamata Haskell. Dalam kasus terburuk, Anda harus dapat melewati kamus eksplisit bahkan jika itu tidak senyaman di Haskell di mana kompiler menyimpulkan kamus yang tepat untuk Anda. Kode F # dalam kasus seperti itu akan menjadi sesuatu seperti di getSum (dictList (dictList (..... (dictList dictInt)))) nestedListmana jumlah dictListcocok dengan jumlah []dalam tipe nestedList.
chi
Bisakah Anda membuat kode haskell ini bisa dijalankan pada REPL?
Filipe Carvalho
F # tidak memiliki kelas tipe ( github.com/fsharp/fslang-suggestions/issues/243 ). Saya mencoba trik overloading operator yang secara teori dapat bekerja tetapi saya hanya berhasil membuat crash kompiler tetapi mungkin Anda dapat membuat sesuatu dari trik ini: stackoverflow.com/a/8376001/418488
Hanya metaprogrammer lain
2
Saya tidak bisa membayangkan basis kode F # realistis di mana Anda akan membutuhkan ini. Apa motivasi Anda untuk melakukan ini? Saya mungkin akan mengubah desain sehingga Anda tidak masuk ke situasi seperti ini - itu mungkin akan membuat kode F # Anda lebih baik.
Tomas Petricek

Jawaban:

4

MEMPERBARUI

Saya menemukan versi yang lebih sederhana menggunakan operator dan ($)bukan anggota. Terinspirasi oleh 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

Penjelasan lainnya masih berlaku dan ini berguna ...

Saya menemukan cara untuk memungkinkannya:

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

Menjalankan contoh Anda:

let list v = List.replicate 6 v

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

Ini didasarkan pada penggunaan SRTP dengan batasan anggota:, static member Sumbatasan tersebut mengharuskan jenis untuk memiliki anggota yang dipanggil Sum yang mengembalikan sebuah int. Saat menggunakan SRTP, fungsi generik harus inline.

Itu bukan bagian yang sulit. Bagian yang sulit adalah "menambahkan" Sumanggota ke tipe seperti yang ada intdan Listyang tidak diizinkan. Tapi, kita bisa menambahkannya ke tipe baru SumOperationsdan memasukkan dalam batasan di (^t or ^a) mana ^takan selalu ada SumOperations.

  • getSum0mendeklarasikan Sumbatasan anggota dan memintanya.
  • getSum lolos SumOperationssebagai parameter tipe pertama kegetSum0

Baris static member inline Sum(x : float ) = int xditambahkan untuk meyakinkan kompiler untuk menggunakan panggilan fungsi dinamis generik dan bukan hanya default static member inline Sum(x : int )ketika meneleponList.sumBy

Seperti yang Anda lihat sedikit berbelit-belit, sintaksisnya kompleks dan perlu untuk mengatasi beberapa keanehan pada kompiler tetapi pada akhirnya itu mungkin.

Metode ini dapat diperluas untuk bekerja dengan Array, tuple, opsi, dll. Atau kombinasi dari semuanya dengan menambahkan lebih banyak definisi ke 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

AMieres
sumber
ini solusi hebat! tetapi mengapa tidak hanya rekursi atau lipat?
s952163
4
Rekursi dan lipatan tidak dapat menangani berbagai jenis. Ketika fungsi rekursif generik dipakai, itu memperbaiki tipe parameter. Dalam hal ini setiap panggilan untuk Sumdilakukan dengan jenis sederhana: Sum<int list list list>, Sum<int list list>, Sum<int list>, Sum<int>.
AMieres
2

Berikut adalah versi runtime, akan berfungsi dengan semua koleksi .net. Namun, bertukar kesalahan kompiler dalam jawaban AMieres untuk pengecualian runtime dan AMieres 'juga lebih cepat 36x.

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

Tolak ukur

|    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)
jbtule
sumber
1
Ini bekerja dengan baik, meskipun terasa lebih lambat: menjalankannya 10 kali butuh 56 detik dibandingkan dengan 1 detik dengan solusi lain.
AMieres
Pembandingan tanpa batas! apa yang kamu gunakan
AMieres