Listeler Rock
Haskell'deki sıralı veriler için açık ara en kolay veri yapısı Liste
data [a] = a:[a] | []
Listeler size ϴ (1) eksilerini ve kalıp eşleşmesini verir. O başlangıcı önemi için standart kütüphane ve kullanışlı liste fonksiyonlarının dolduğunu gereken çöp kodunuzu ( foldr
, map
, filter
). Listeler kalıcı , yani tamamen işlevsel, çok güzel. Haskell listeleri gerçekten "listeler" değildir, çünkü bunlar koindüktiftir (diğer diller bu akışlara der)
ones :: [Integer]
ones = 1:ones
twos = map (+1) ones
tenTwos = take 10 twos
harika çalışmak. Sonsuz veri yapıları kaya.
Haskell'deki listeler, zorunlu dillerde yineleyiciler gibi bir arayüz sağlar (tembellik nedeniyle). Bu nedenle, yaygın olarak kullanıldıkları mantıklıdır.
Diğer yandan
Listelerle ilgili ilk sorun, dizinleri dizine (!!)
eklemenin ϴ (k) zaman almasıdır, bu da can sıkıcı bir durumdur. Ayrıca, ekler yavaş olabilir ++
, ancak Haskell'in tembel değerlendirme modeli, bunların gerçekleşirse, tamamen amortismana tabi tutulabileceği anlamına gelir.
Listelerle ilgili ikinci sorun, veri konumlarının zayıf olmasıdır. Gerçek işlemciler, bellekteki nesneler yan yana yerleştirilmediğinde yüksek sabitlere maruz kalır. Bu nedenle, C ++ ' std::vector
da bildiğim herhangi bir saf bağlantılı liste veri yapısından daha hızlı "snoc" (nesneleri koyarak) vardır, ancak bu Haskell'in listelerinden çok daha az kalıcı bir veri yapısı olmamasına rağmen.
Listelerle ilgili üçüncü sorun, alan verimliliğinin zayıf olmasıdır. Ekstra işaretçilerden oluşan demet, depolama alanınızı artırır (sabit bir faktörle).
Diziler İşlevseldir
Data.Sequence
dahili olarak parmak ağaçlarına dayanır (biliyorum, bunu bilmek istemezsiniz), bu da bazı güzel özelliklere sahip oldukları anlamına gelir.
- Tamamen işlevsel.
Data.Sequence
tamamen kalıcı bir veri yapısıdır.
- Ağacın başına ve sonuna hızlı erişim. 1 (1) (itfa edilmiş) ilk veya son unsuru almak veya ağaç eklemek için. Listede en hızlı olan şey
Data.Sequence
, en fazla sürekli yavaştır.
- ϴ (log n) dizinin ortasına erişim. Bu, yeni sekanslar yapmak için değerlerin eklenmesini içerir
- Yüksek kaliteli API
Diğer yandan, Data.Sequence
veri yerellik sorunu için fazla bir şey yapmaz ve sadece sonlu koleksiyonlar için çalışır (listelerden daha az tembeldir)
Diziler kalbin zayıflığı için değil
Diziler, CS'deki en önemli veri yapılarından biridir, ancak tembel saf işlevsel dünyaya çok iyi uymazlar. Diziler ϴ (1) koleksiyonun ortasına erişim ve son derece iyi veri konumu / sabit faktörler sağlar. Ancak, Haskell'e çok iyi uymadıkları için, kullanmak için bir acıdırlar. Geçerli standart kitaplıkta aslında çok sayıda farklı dizi türü vardır. Bunlar arasında tamamen kalıcı diziler, IO monad için değişebilir diziler, ST monad için değiştirilebilir diziler ve yukarıdakilerin kutulanmamış versiyonları bulunur. Daha fazla bilgi için haskell wiki'ye göz atın
Vektör "daha iyi" bir dizidir
Data.Vector
Paket daha yüksek bir seviyede ve daha temiz API, dizi iyilik her sağlar. Ne yaptığınızı gerçekten bilmiyorsanız, performansa benzer bir diziye ihtiyacınız varsa bunları kullanmalısınız. Tabii ki, bazı uyarılar hala geçerlidir - veri yapıları gibi değiştirilebilir dizi sadece saf tembel dillerde hoş değil. Yine de, bazen O (1) performansını Data.Vector
istersiniz ve kullanılabilir bir pakette size verir.
Başka seçenekleriniz var
Sonunda verimli bir şekilde ekleme özelliğine sahip listeler istiyorsanız, bir fark listesi kullanabilirsiniz . Performansı kötüleştiren listelerin en iyi örneği [Char]
, başlangıcın takma adı olduğu gibi gelme eğilimindedir String
. Char
listeler convient, ancak C dizeleri 20 kat daha yavaş sırayla çalıştırmak eğilimindedir, bu yüzden kullanmaktan çekinmeyin Data.Text
veya çok hızlı Data.ByteString
. Eminim şu anda düşünmediğim sıralamaya dayalı başka kütüphaneler de var.
Sonuç
Haskell listelerinde sıralı bir koleksiyona ihtiyaç duyduğum zamanın% 90'ı doğru veri yapısı. Listeler yineleyiciler gibidir, listeleri tüketen toList
işlevler birlikte geldikleri işlevleri kullanarak bu diğer veri yapılarından herhangi biriyle kolayca kullanılabilir . Daha iyi bir dünyada, başlangıç, hangi kap türünü kullandığına dair tamamen parametrik olacaktır, ancak şu anda []
standart kütüphaneyi doldurmaktadır. Yani, listeler (neredeyse) her yerde kesinlikle iyidir.
Liste işlevlerinin çoğunun tam parametrik sürümlerini alabilirsiniz (ve bunları kullanmak asildir)
Prelude.map ---> Prelude.fmap (works for every Functor)
Prelude.foldr/foldl/etc ---> Data.Foldable.foldr/foldl/etc
Prelude.sequence ---> Data.Traversable.sequence
etc
Aslında, Data.Traversable
"liste gibi" herhangi bir şeyde az çok evrensel olan bir API tanımlar.
Yine de, iyi olabilmenize ve sadece tam olarak parametrik kod yazabilmenize rağmen, çoğumuz değildir ve her yerde listeyi kullanıyoruz. Öğreniyorsanız, kesinlikle yapmanızı öneririm.
EDIT: Data.Vector
vs kullanarak ne zaman açıklamak asla anladım yorumlarına dayanarak Data.Sequence
. Diziler ve Vektörler, son derece hızlı indeksleme ve dilimleme işlemleri sağlar, ancak temelde geçici (zorunlu) veri yapılarıdır. Saf fonksiyonel veri yapıları gibi Data.Sequence
ve []
verimli bir şekilde üretmek izin yeni eski değerleri değiştirilmiş sanki eski değerlerden değerleri.
newList oldList = 7 : drop 5 oldList
eski listeyi değiştirmez ve kopyalamak zorunda değildir. Bu yüzden oldList
inanılmaz derecede uzun olsa bile , bu "değişiklik" çok hızlı olacaktır. benzer şekilde
newSequence newValue oldSequence = Sequence.update 3000 newValue oldSequence
ile yeni bir dizi üretecek newValue
3000 elemanının yerine for . Yine, eski diziyi yok etmez, sadece yeni bir dizi oluşturur. Ancak, bunu çok verimli bir şekilde yapar, O (log (min (k, kn)) alarak n, dizinin uzunluğu ve k, değiştirdiğiniz dizindir.
Bunu Vectors
ve ile kolayca yapamazsınız Arrays
. Onlar edilebilir değiştirilmiş ama bu gerçek zorunluluk değişikliktir ve bu nedenle düzenli Haskell kodda yapılmış olamaz. Bu, Vector
pakette , tüm vektörü kopyalamak gibi değişiklikler yapmak snoc
ve bunları yapmak zorunda kalan işlemler anlamına gelir, bu cons
nedenle zaman O(n)
ayırın. Bunun tek istisnası Vector.Mutable
, ST
monad (veya IO
) içinde değiştirilebilir versiyonu ( ) kullanabilmeniz ve tıpkı zorunlu bir dilde yaptığınız gibi tüm değişikliklerinizi yapabilmenizdir. İşiniz bittiğinde, saf kodla kullanmak istediğiniz değişmez yapıya dönüşmek için vektörünüzü "dondurursunuz".
Benim düşüncem, Data.Sequence
bir liste uygun değilse varsayılan olarak kullanmanız gerektiğidir. Kullanım Data.Vector
yalnızca kullanım deseni birçok değişiklik yapmadan içermeyen eğer, ya eğer ST / IO monads içinde son derece yüksek performans gerek yoktur.
ST
Monad'ın tüm bu konuşması sizi şaşkın bırakıyorsa: saf hızlı ve güzelliğe sadık kalmak için daha fazla neden Data.Sequence
.