Tổng hợp danh sách các mức lồng nhau tùy ý trong F #


10

Tôi đang cố gắng tạo một hàm F # sẽ trả về tổng của một danh sách intcác hàm lồng nhau tùy ý. I E. nó sẽ làm việc cho a list<int>, a list<list<int>>và a list<list<list<list<list<list<int>>>>>>.

Trong Haskell tôi sẽ viết một cái gì đó như:

class HasSum a where
    getSum :: a -> Integer

instance HasSum Integer where
    getSum = id

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

Điều đó sẽ cho tôi làm:

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

Làm thế nào tôi có thể đạt được điều này trong F #?


1
Tôi không biết F # đủ - Tôi không biết nếu nó hỗ trợ một cái gì đó như kiểu chữ của Haskell. Trong trường hợp xấu nhất, bạn sẽ có thể chuyển từ điển rõ ràng ngay cả khi nó không thuận tiện như trong Haskell nơi trình biên dịch đưa ra từ điển phù hợp với bạn. Mã F # trong trường hợp như vậy sẽ giống như getSum (dictList (dictList (..... (dictList dictInt)))) nestedListsố lượng dictListkhớp với số lượng []trong loại nestedList.
chi

Bạn có thể làm cho mã haskell này có thể chạy được trên REPL không?
Filipe Carvalho


F # không có các lớp loại ( github.com/fsharp/fslang-suggestions/issues/243 ). Tôi đã thử thủ thuật nạp chồng toán tử mà trên lý thuyết có thể hoạt động nhưng tôi chỉ tìm cách làm sập trình biên dịch nhưng có lẽ bạn có thể tạo ra một mẹo nào đó: stackoverflow.com/a/8376001/418488
Chỉ là một trình siêu dữ liệu khác

2
Tôi không thể tưởng tượng bất kỳ cơ sở mã F # thực tế nào mà bạn sẽ cần điều này. Động lực của bạn để làm điều này là gì? Tôi có thể sẽ thay đổi thiết kế để bạn không gặp phải tình huống như thế này - nó có thể sẽ làm cho mã F # của bạn tốt hơn.
Tomas Petricek

Câu trả lời:


4

CẬP NHẬT

Tôi tìm thấy một phiên bản đơn giản hơn bằng cách sử dụng một toán tử ($)thay vì một thành viên. Lấy cảm hứng từ 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

Phần còn lại của lời giải thích vẫn được áp dụng và nó rất hữu ích ...

Tôi tìm thấy một cách để làm cho nó có thể:

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

Chạy ví dụ của bạn:

let list v = List.replicate 6 v

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

Điều này dựa trên việc sử dụng SRTP với các ràng buộc thành viên : static member Sum, ràng buộc này yêu cầu loại phải có một thành viên được gọi Sum trả về một int. Khi sử dụng các chức năng chung SRTP cần phải có inline.

Đó không phải là phần khó khăn. Phần khó là "thêm" Sumthành viên vào loại hiện có intListkhông được phép. Nhưng, chúng ta có thể thêm nó vào một loại mới SumOperationsvà đưa vào chế (^t or ^a) nơi ^tsẽ luôn luôn được SumOperations.

  • getSum0tuyên bố các Sumràng buộc thành viên và gọi nó.
  • getSum chuyển SumOperationslàm tham số loại đầu tiên chogetSum0

Dòng static member inline Sum(x : float ) = int xđược thêm vào để thuyết phục trình biên dịch sử dụng lệnh gọi hàm động chung và không chỉ mặc định static member inline Sum(x : int )khi gọiList.sumBy

Như bạn có thể thấy là một chút phức tạp, cú pháp rất phức tạp và cần phải làm việc xung quanh một số quirks trên trình biên dịch nhưng cuối cùng thì điều đó là có thể.

Phương pháp này có thể được mở rộng để làm việc với Mảng, bộ dữ liệu, tùy chọn, v.v. hoặc bất kỳ sự kết hợp nào của chúng bằng cách thêm nhiều định nghĩa vào 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


đây là một giải pháp tuyệt vời nhưng tại sao không chỉ đệ quy hoặc gấp?
s952163

4
Đệ quy và gấp không thể xử lý các loại khác nhau. Khi một hàm đệ quy chung được khởi tạo, nó sẽ sửa loại tham số. Trong trường hợp này mỗi cuộc gọi để Sumđược thực hiện với một kiểu đơn giản: Sum<int list list list>, Sum<int list list>, Sum<int list>, Sum<int>.
Sáng

2

Đây là phiên bản thời gian chạy, sẽ hoạt động với tất cả các bộ sưu tập .net. Tuy nhiên, trao đổi lỗi trình biên dịch trong câu trả lời của AMieres cho ngoại lệ thời gian chạy và AMieres 'cũng nhanh hơn 36 lần.

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

Điểm chuẩn

|    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
Nó hoạt động tốt, mặc dù chậm hơn rõ rệt: chạy 10 lần mất 56 giây so với 1 giây với giải pháp khác.
Sáng

Điểm chuẩn đáng kinh ngạc! bạn đã dùng gì
Sáng

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.