การสรุปรายการระดับความซ้อนโดยพลการใน F #


10

ฉันกำลังพยายามสร้างฟังก์ชัน F # ที่จะส่งคืนผลรวมของรายการintของการซ้อนกันโดยพลการ กล่าวคือ มันจะทำงานให้list<int>เป็นและlist<list<int>>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 ที่ซึ่งผู้แปลรวบรวมพจนานุกรมที่เหมาะสมสำหรับคุณ เอฟ # รหัสในกรณีดังกล่าวจะเป็นสิ่งที่ต้องการgetSum (dictList (dictList (..... (dictList dictInt)))) nestedListซึ่งมีจำนวนของdictListการแข่งขันจำนวนในรูปแบบของ[] nestedList
Chi

คุณสามารถทำให้รหัส haskell นี้สามารถเรียกใช้บน REPL ได้หรือไม่?
Filipe Carvalho

ที่นี่คุณไป ... repl.it/repls/BlondCoolParallelport
karakfa

F # ไม่มีคลาสประเภท ( github.com/fsharp/fslang-suggestions/issues/243 ) ฉันลองใช้ตัวจัดการการโอเวอร์โหลดเคล็ดลับที่ในทางทฤษฎีสามารถใช้งานได้ แต่ฉันเพิ่งจัดการเพื่อคอมไพเลอร์ แต่บางทีคุณสามารถทำบางอย่างของเคล็ดลับ: stackoverflow.com/a/8376001/418488
metaprogrammer อีก

2
ฉันไม่สามารถจินตนาการถึง F # codebase ที่เป็นจริงที่คุณต้องการได้ คุณมีแรงจูงใจในการทำสิ่งนี้อย่างไร ฉันอาจจะเปลี่ยนการออกแบบเพื่อที่คุณจะไม่ตกอยู่ในสถานการณ์เช่นนี้ - มันอาจจะทำให้โค้ด F # ของคุณดีขึ้นต่อไป
Tomas Petricek

คำตอบ:


4

UPDATE

ฉันพบรุ่นที่ง่ายกว่าโดยใช้โอเปอเรเตอร์($)แทนการเป็นสมาชิก แรงบันดาลใจจาก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

นี้จะขึ้นอยู่กับการใช้ SRTPs มีข้อ จำกัด สมาชิก: static member Sumข้อ จำกัด ต้องเป็นประเภทที่จะได้เป็นสมาชิกเรียกว่าส่งกลับSum intเมื่อใช้ฟังก์ชั่น SRTPs 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 นั้นก็เร็วขึ้น 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

มาตรฐาน

|    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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.