Bir listeden n'inci elemanı nasıl alabilirim?


97

Haskell'de, bu C koduna benzer şekilde indekse göre bir listeye nasıl erişebilirim?

int a[] = { 34, 45, 56 };
return a[1];

Yanıtlar:


155

Buraya bakın , kullanılan operatör !!.

Yani , listeler 0 endeksli olduğundan [1,2,3]!!1size verir 2.


86
Kişisel olarak, Belki bir türü döndürmeyen bir dizin erişimcisinin deyimsel Haskell olarak kabul edilebilir olduğunu anlayamıyorum. [1,2,3]!!6size bir çalışma zamanı hatası verecektir. !!Tip olsaydı çok kolay önlenebilirdi [a] -> Int -> Maybe a. Haskell'e sahip olmamızın nedeni, bu tür çalışma zamanı hatalarından kaçınmaktır!
worldsayshi

9
Bu bir değiş tokuş. Seçtikleri sembol muhtemelen sahip olabilecekleri en korkutucu semboldür. Bence fikir, uç durumlar için izin vermek, ancak deyimsel olmayan bir şekilde öne çıkmasını sağlamaktı.
cdosborn

3
itemOf :: Int -> [a] -> Maybe a; x `itemOf` xs = let xslen = length xs in if ((abs x) > xslen) then Nothing else Just (xs !! (x `mod` xslen)). Unutmayın, bu sonsuz bir listede feci şekilde başarısız olacaktır.
djvs

2
!!kısmi ve dolayısıyla güvenli olmayan bir işlevdir. Aşağıdaki yoruma bir göz atın ve lens stackoverflow.com/a/23627631/2574719
adresini

90

Sorunuzda veya verilen yanıtta bir sorun olduğunu söylemiyorum, ancak gelecekte kendinize zaman kazandıracak harika bir araç olan Hoogle'ı öğrenmek isteyebilirsiniz: Hoogle ile, standart kitaplık işlevlerini arayabilirsiniz. belirli bir imzayla eşleşen. Bu nedenle, hakkında hiçbir şey bilmiyorsanız !!, sizin durumunuzda "her şeyi bir Intve bir listesini alan ve tek bir böyle her şeyi döndüren bir şey ", yani

Int -> [a] -> a

Lo ve işte , !!ilk sonuç olarak (her ne kadar tip imzası aslında aradığımıza kıyasla iki bağımsız değişkeni tersten içeriyor olsa da). Güzel, ha?

Ayrıca, kodunuz indekslemeye dayanıyorsa (listenin önünden tüketmek yerine), listeler aslında uygun veri yapısı olmayabilir. O (1) indeks tabanlı erişim için, diziler veya vektörler gibi daha verimli alternatifler vardır .


4
Hoogle kesinlikle harika. Her Haskell programcısı bunu bilmelidir. Hayoo ( holumbus.fh-wedel.de/hayoo/hayoo.html ) adında bir alternatif var . Siz yazdıkça arar, ancak Hoogle kadar akıllı görünmez.
musiKk

61

Kullanmanın bir alternatifi (!!), lens paketini ve elementişlevini ve ilgili operatörleri kullanmaktır. Lens yukarıda ve listeler ötesinde yapıları ve iç içe yapıların çeşitli erişim için tek tip bir arayüz sağlar. Aşağıda örnekler vermeye odaklanacağım ve hem tip imzalarını hem de lens paketinin arkasındaki teoriyi inceleyeceğim . Teori hakkında daha fazla bilgi edinmek istiyorsanız, başlamak için iyi bir yer, github deposundaki benioku dosyasıdır .

Listelere ve diğer veri türlerine erişim

Lens paketine erişim

Komut satırında:

$ cabal install lens
$ ghci
GHCi, version 7.6.3: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
> import Control.Lens


Listelere erişim

Infix operatörüyle bir listeye erişmek için

> [1,2,3,4,5] ^? element 2  -- 0 based indexing
Just 3

Bunun aksine, (!!)sınırların dışındaki bir öğeye erişirken bir istisna atmayacak ve Nothingbunun yerine geri dönecektir . Genellikle gibi kısmi fonksiyonlarını önlemek için tavsiye edilir (!!)ya headda daha fazla köşe olgu var ve bir çalışma zamanı hatasına neden daha olasıdır çünkü. Bu wiki sayfasında neden kısmi işlevlerden kaçınmanız gerektiği hakkında biraz daha bilgi edinebilirsiniz .

> [1,2,3] !! 9
*** Exception: Prelude.(!!): index too large

> [1,2,3] ^? element 9
Nothing

Lens tekniğini kısmi bir işlev olmaya zorlayabilir ve sınırların dışındayken (^?!)operatör yerine operatörü kullanarak bir istisna atabilirsiniz (^?).

> [1,2,3] ^?! element 1
2
> [1,2,3] ^?! element 9
*** Exception: (^?!): empty Fold


Listeler dışındaki türlerle çalışma

Ancak bu sadece listelerle sınırlı değildir. Örneğin, aynı teknik standart kap paketindeki ağaçlarda da işe yarar.

 > import Data.Tree
 > :{
 let
  tree = Node 1 [
       Node 2 [Node 4[], Node 5 []]
     , Node 3 [Node 6 [], Node 7 []]
     ]
 :}
> putStrLn . drawTree . fmap show $tree
1
|
+- 2
|  |
|  +- 4
|  |
|  `- 5
|
`- 3
   |
   +- 6
   |
   `- 7

Artık ağacın öğelerine derinlemesine öncelik sırasına göre erişebiliriz:

> tree ^? element 0
Just 1
> tree ^? element 1
Just 2
> tree ^? element 2
Just 4
> tree ^? element 3
Just 5
> tree ^? element 4
Just 3
> tree ^? element 5
Just 6
> tree ^? element 6
Just 7

Biz de erişebilir dizileri gelen konteynerlerin paket:

> import qualified Data.Sequence as Seq
> Seq.fromList [1,2,3,4] ^? element 3
Just 4

Standart int indeksli dizilere vektör paketinden, standart metin paketinden metne , standart bytestring paketinden bytestrings ve diğer birçok standart veri yapısına erişebiliriz . Bu standart erişim yöntemi, kişisel veri yapılarınızı Taversable tip sınıfının bir örneği haline getirerek genişletilebilir , Lens belgelerinde Traversable örneğinin daha uzun bir listesine bakın . .


İç içe yapılar

İç içe geçmiş yapılara dalmak , lens hackleme ile basittir . Örneğin bir liste listesindeki bir öğeye erişim:

> [[1,2,3],[4,5,6]] ^? element 0 . element 1
Just 2
> [[1,2,3],[4,5,6]] ^? element 1 . element 2
Just 6

Bu bileşim, iç içe geçmiş veri yapıları farklı türlerde olduğunda bile çalışır. Örneğin bir ağaç listem olsaydı:

> :{
 let
  tree = Node 1 [
       Node 2 []
     , Node 3 []
     ]
 :}
> putStrLn . drawTree . fmap show $ tree
1
|
+- 2
|
`- 3
> :{
 let 
  listOfTrees = [ tree
      , fmap (*2) tree -- All tree elements times 2
      , fmap (*3) tree -- All tree elements times 3
      ]            
 :}

> listOfTrees ^? element 1 . element 0
Just 2
> listOfTrees ^? element 1 . element 1
Just 4

TraversableGereksinimi karşıladıkları sürece rastgele türlerle keyfi olarak derinlemesine iç içe yerleştirebilirsiniz . Bu nedenle, metin dizilerinin ağaçlarından oluşan bir listeye erişmek zor değil.


N'inci öğeyi değiştirme

Pek çok dilde ortak bir işlem, bir dizideki dizinlenmiş bir konuma atamaktır. Python'da şunları yapabilirsiniz:

>>> a = [1,2,3,4,5]
>>> a[3] = 9
>>> a
[1, 2, 3, 9, 5]

Lens paketi ile bu işlevselliği verir (.~)operatörü. Python'dan farklı olarak orijinal liste mutasyona uğramaz, bunun yerine yeni bir liste döndürülür.

> let a = [1,2,3,4,5]
> a & element 3 .~ 9
[1,2,3,9,5]
> a
[1,2,3,4,5]

element 3 .~ 9sadece bir işlevdir ve lens paketinin bir (&)parçası olan operatör yalnızca ters işlev uygulamasıdır. İşte burada daha yaygın fonksiyon uygulaması ile.

> (element 3 .~ 9) [1,2,3,4,5]
[1,2,3,9,5]

Atama, URL'lerin keyfi olarak yerleştirilmesiyle mükemmel bir şekilde çalışır Traversable.

> [[1,2,3],[4,5,6]] & element 0 . element 1 .~ 9
[[1,9,3],[4,5,6]]

3
İçinde Data.Traversableyeniden dışa aktarma yerine bağlantı vermeyi önerebilir miyim lens?
dfeuer

@dfeuer - Base'de Data.Traversable'a bir bağlantı ekledim. Ayrıca eski bağlantıyı korudum ve Lens belgelerinde daha uzun bir örnek traverable listesi olduğunu belirttim. Önerin için teşekkürler.
Davorak

11

Doğru cevap zaten verilmişti: Kullan !!.

Bununla birlikte, yeni başlayanlar genellikle bu operatörü aşırı kullanma eğilimindedir, bu da Haskell'de pahalıdır (çünkü dizilerde değil, tek bağlantılı listelerde çalışıyorsunuz). Bundan kaçınmak için birkaç faydalı teknik vardır, en kolayı zip kullanmaktır. Eğer yazarsanız zip ["foo","bar","baz"] [0..], bir çiftteki her elemana "eklenmiş" indislerin olduğu yeni bir liste alırsınız: [("foo",0),("bar",1),("baz",2)]ki bu genellikle tam olarak ihtiyacınız olan şeydir.


2
Ayrıca oradaki tipleriniz konusunda dikkatli olmalısınız. Çoğu zaman, indekslerin hızlı makine İnt'leri yerine yavaş Tamsayılar olmasını istemezsiniz. İşlevinizin tam olarak ne yaptığına ve yazmanızın ne kadar açık olduğuna bağlı olarak Haskell, [0 ..] türünün [Int] yerine [Tamsayı] olduğunu çıkarabilir.
chrisdb

4

Kullanabilirsiniz !!, ancak bunu yinelemeli olarak yapmak istiyorsanız, aşağıda bunu yapmanın bir yolu vardır:

dataAt :: Int -> [a] -> a
dataAt _ [] = error "Empty List!"
dataAt y (x:xs)  | y <= 0 = x
                 | otherwise = dataAt (y-1) xs

4

Haskell'in forall t. [t]uygulamadaki standart liste veri türü , kanonik C bağlantılı bir listeye çok benzer ve temelde özelliklerini paylaşır. Bağlı listeler dizilerden çok farklıdır. En önemlisi, indekse göre erişim, bir O (1) sabit zamanlı işlem yerine bir O (n) doğrusaldır.

Sık rastgele erişime ihtiyacınız varsa, Data.Arraystandardı düşünün .

!!aralık dışı endeksler için bir çökmeye neden olan güvensiz, kısmen tanımlanmış bir işlevdir. Standart kütüphane gibi bazı kısmi işlevler (içerdiğini unutmayın head, lastvs.). Güvenlik için bir seçenek türü Maybeveya Safemodülü kullanın.

Makul derecede verimli, sağlam bir toplam (endeksler ≥ 0 için) dizinleme işlevi örneği:

data Maybe a = Nothing | Just a

lookup :: Int -> [a] -> Maybe a
lookup _ []       = Nothing
lookup 0 (x : _)  = Just x
lookup i (_ : xs) = lookup (i - 1) xs

Bağlantılı listelerle çalışmak, genellikle sıra sayılar uygundur:

nth :: Int -> [a] -> Maybe a
nth _ []       = Nothing
nth 1 (x : _)  = Just x
nth n (_ : xs) = nth (n - 1) xs

Bu işlevler, sırasıyla negatif ve pozitif olmayan İnts için sonsuza kadar yinelenir.
Bjartur Thorlacius
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.