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:
Buraya bakın , kullanılan operatör !!
.
Yani , listeler 0 endeksli olduğundan [1,2,3]!!1
size verir 2
.
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.
!!
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
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 Int
ve 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 .
Kullanmanın bir alternatifi (!!)
,
lens paketini ve element
iş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 .
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
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 Nothing
bunun yerine geri dönecektir . Genellikle gibi kısmi fonksiyonlarını önlemek için tavsiye edilir (!!)
ya head
da 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
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 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
Traversable
Gereksinimi 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.
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 .~ 9
sadece 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]]
Data.Traversable
yeniden dışa aktarma yerine bağlantı vermeyi önerebilir miyim lens
?
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.
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.Array
standardı 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
, last
vs.). Güvenlik için bir seçenek türü Maybe
veya Safe
modü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
[1,2,3]!!6
size 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!