F # 'daki keyfi iç içe geçme düzeylerinin listeleri üzerinde özetleme


10

Ben intrasgele iç içe geçmiş s listesinin toplamını döndürecek bir F # işlevi oluşturmaya çalışıyorum . Yani. a list<int>, a list<list<int>>ve a için çalışacaktır list<list<list<list<list<list<int>>>>>>.

Haskell'de şöyle bir şey yazardım:

class HasSum a where
    getSum :: a -> Integer

instance HasSum Integer where
    getSum = id

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

hangi yapmama izin verir:

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

Bunu F # ile nasıl başarabilirim?


1
F # 'i yeterince bilmiyorum - Haskell'in tipik örnekleri gibi bir şeyi destekleyip desteklemediğini bilmiyorum. En kötü durumda, derleyicinin sizin için doğru sözlükleri ihlal ettiği Haskell'deki kadar uygun olmasa bile açık sözlükleri geçebilmelisiniz. Bu durumda F # kodu , türündeki sayının eşleşme getSum (dictList (dictList (..... (dictList dictInt)))) nestedListsayısının olduğu gibi bir şey olacaktır . dictList[]nestedList
chi

Bu haskell kodunu bir REPL'de çalıştırılabilir yapabilir misiniz?
Filipe Carvalho


F #, tip sınıflarına sahip değildir ( github.com/fsharp/fslang-suggestions/issues/243 ). Teorik olarak çalışabilir operatör aşırı yükleme hile denedim ama sadece derleyici çökmesini başardı ama belki hile bir şey yapabilirsiniz: stackoverflow.com/a/8376001/418488
Sadece başka bir metaprogrammer

2
İhtiyacınız olan herhangi bir gerçekçi F # kod temeli düşünemiyorum. Bunu yapmak için motivasyonunuz neydi? Muhtemelen böyle bir duruma girmemek için tasarımı değiştirirdim - muhtemelen F # kodunuzu daha iyi hale getirecektir.
Tomas Petricek

Yanıtlar:


4

GÜNCELLEME

Üye ($)yerine operatör kullanarak daha basit bir sürüm buldum . Https://stackoverflow.com/a/7224269/4550898 adresinden ilham alındı :

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

Açıklamanın geri kalanı hala geçerlidir ve yararlıdır ...

Bunu mümkün kılmak için bir yol buldum:

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

Örneğinizi çalıştırma:

let list v = List.replicate 6 v

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

Bu, üye kısıtlamaları olan SRTP'lerin kullanılmasına dayanır: static member Sumkısıtlama, türün bir Sum döndüren bir üyeye sahip olmasını gerektirir int. SRTP'leri kullanırken genel işlevlerin olması gerekir inline.

Zor kısmı bu değil. Zor kısım, Sumüye olan intve Listbuna izin verilmeyen mevcut bir türe "ekleme" dir . Ama biz yeni bir türüne ekleyebilirsiniz SumOperationsve kısıtlama dahil hep olacak .(^t or ^a)^tSumOperations

  • getSum0Sumüye kısıtlamasını bildirir ve çağırır.
  • getSumSumOperationsilk tip parametresi olarak geçergetSum0

Hat static member inline Sum(x : float ) = int x, derleyiciyi yalnızca static member inline Sum(x : int )arama sırasında varsayılan olarak değil, genel bir dinamik işlev çağrısı kullanmaya ikna etmek için eklendiList.sumBy

Gördüğünüz gibi biraz kıvrımlı, sözdizimi karmaşık ve derleyici üzerinde bazı tuhaflıklar etrafında çalışmak gerekiyordu ama sonunda mümkün oldu.

Bu yöntem Diziler, tuples, seçenekler vb. Veya bunların herhangi bir kombinasyonu ile çalışmak için genişletilebilir 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


bu harika bir çözüm! ama neden sadece özyineleme veya katlama değil?
s952163

4
Özyineleme ve katlama, çeşitli türleri işleyemez. Genel bir özyinelemeli işlev başlatıldığında, parametrelerin türünü sabitler. Bu durumda her çağrı için Sumdaha basit bir türü ile yapılır: Sum<int list list list>, Sum<int list list>, Sum<int list>, Sum<int>.
AMieres

2

İşte çalışma zamanı sürümü, tüm .net koleksiyonları ile çalışır. Ancak, AMieres'in çalışma zamanı istisnaları ve AMieres yanıtındaki değiş tokuş derleyici hataları da 36 kat daha hızlıdır.

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

Deneyler

|    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
Oldukça yavaş olmasına rağmen iyi çalışıyor: 10 kez çalıştırmak diğer çözümle 1 saniyeye kıyasla 56 saniye sürdü.
AMieres

Etkileyici kıyaslama! ne kullandın
AMieres

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.