Haskell: Listeler, Diziler, Vektörler, Diziler


230

Haskell öğreniyorum ve Haskell listelerinin performans farklılıkları ve (dilinizi ekleyin) dizilerinin birkaç makalesini okuyorum.

Bir öğrenci olarak, performans farkını düşünmeden bile listeleri kullanıyorum. Yakın zamanda araştırmaya başladım ve Haskell'de çok sayıda veri yapısı kütüphanesi buldum.

Birisi veri yapıları bilgisayar bilimi teorisinde çok derine inmeden Listeler, Diziler, Vektörler, Diziler arasındaki farkı açıklayabilir mi?

Ayrıca, bir veri yapısını diğeri yerine kullanacağınız bazı yaygın kalıplar var mı?

Eksik ve faydalı olabilecek başka veri yapısı şekilleri var mı?


1
Listeler ve diziler hakkında bu cevaba bir göz atın: stackoverflow.com/questions/8196667/haskell-arrays-vs-lists Vektörler çoğunlukla dizilerle aynı performansa sahiptir, ancak daha büyük bir API'dir.
Grzegorz Chrupała

Burada tartışılan Data.Map'i görmek güzel olurdu. Bu, özellikle çok boyutlu veriler için yararlı bir veri yapısı gibi görünüyor.
Martin Capodici

Yanıtlar:


339

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::vectorda 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.Sequencedahili olarak parmak ağaçlarına dayanır (biliyorum, bunu bilmek istemezsiniz), bu da bazı güzel özelliklere sahip oldukları anlamına gelir.

  1. Tamamen işlevsel. Data.Sequencetamamen kalıcı bir veri yapısıdır.
  2. 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.
  3. ϴ (log n) dizinin ortasına erişim. Bu, yeni sekanslar yapmak için değerlerin eklenmesini içerir
  4. 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.VectorPaket 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.Vectoristersiniz 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. Charlisteler convient, ancak C dizeleri 20 kat daha yavaş sırayla çalıştırmak eğilimindedir, bu yüzden kullanmaktan çekinmeyin Data.Textveya ç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 toListiş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.Vectorvs 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.Sequenceve []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 oldListinanı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 newValue3000 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 Vectorsve 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, Vectorpakette , tüm vektörü kopyalamak gibi değişiklikler yapmak snocve bunları yapmak zorunda kalan işlemler anlamına gelir, bu consnedenle zaman O(n)ayırın. Bunun tek istisnası Vector.Mutable, STmonad (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.Sequencebir liste uygun değilse varsayılan olarak kullanmanız gerektiğidir. Kullanım Data.Vectoryalnı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.

STMonad'ı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.


45
Duyduğum bir görüş, listelerin temelde Haskell'deki bir veri yapısı kadar bir kontrol yapısı olduğudur. Ve bu mantıklı: farklı bir dilde döngü için bir C stili kullanacağınız zaman [1..], Haskell'de bir liste kullanırsınız . Listeler geri izleme gibi eğlenceli şeyler için de kullanılabilir. Onları kontrol yapıları (bir çeşit) olarak düşünmek, nasıl kullanıldıklarını anlamamıza yardımcı oldu.
Tikhon Jelvis

21
Mükemmel cevap. Benim tek şikayet "Dizileri fonksiyonel" onları biraz azdır olduğunu. Diziler işlevsel awesomesauce'dur. Onlara bir başka bonus da hızlı bir şekilde bir araya gelmek ve bölünmek (log n).
Dan Burton

3
@DanBurton Fuarı. Muhtemelen satmadım Data.Sequence. Parmak ağaçları, bilgisayar tarihindeki en harika icatlardan biridir (Guibas muhtemelen bir gün Turing ödülü almalıdır) ve Data.Sequencemükemmel bir uygulamadır ve çok kullanışlı bir API'ye sahiptir.
Philip JF

3
"Eğer ST / IO monads içinde son derece yüksek performans için gerekirse UseData.Vector yalnızca kullanım deseni birçok modifikasyon yapmayı gerektirebilir ya vermezse .." İlginç ifadeler, sen çünkü eğer edilmektedir (birçok değişiklik yapmadan tekrar tekrar gibi (100k kez) ) 100k elemanları gelişen, o zaman yapmak , ihtiyaç ST / IO Vektör uygun performansı elde etmek
misterbee

4
(Saf) vektörleri ve kopyalama ile ilgili endişeler, kısmen akış füzyon ile hafifletilebilen, örneğin, bu: import qualified Data.Vector.Unboxed as VU; main = print (VU.cons 'a' (VU.replicate 100 'b'))Çekirdek 404 bayt (101 karakter) tek bir tahsise derlenir: hpaste.org/65015
FunctorSalad
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.