Odpowiedzi:
Spójrz tutaj , użyty operator to !!
.
Czyli [1,2,3]!!1
daje 2
, ponieważ listy są 0-indeksowane.
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))
. Uwaga, na nieskończonej liście zakończy się to katastrofalnym skutkiem.
!!
jest funkcją częściową, a zatem niebezpieczną. Spójrz na poniższy komentarz i użyj lens
stackoverflow.com/a/23627631/2574719
Nie mówię, że jest coś nie tak z Twoim pytaniem lub udzieloną odpowiedzią, ale może chcesz wiedzieć o wspaniałym narzędziu, jakim jest Hoogle, aby zaoszczędzić czas w przyszłości: Dzięki Hoogle możesz wyszukiwać standardowe funkcje biblioteki pasujące do danego podpisu. Tak więc, nie wiedząc nic o tym !!
, w twoim przypadku możesz wyszukać „coś, co pobiera Int
i listę wszystkich możliwych odpowiedzi i zwraca jedno takie cokolwiek”, a mianowicie
Int -> [a] -> a
Lo i behold , !!
jako pierwszy wynik (chociaż sygnatura typu w rzeczywistości ma dwa argumenty w odwrotnej kolejności w porównaniu z tym, czego szukaliśmy). Schludnie, co?
Ponadto, jeśli twój kod opiera się na indeksowaniu (zamiast pobierać od początku listy), listy mogą w rzeczywistości nie być właściwą strukturą danych. W przypadku dostępu opartego na indeksach O (1) istnieją bardziej wydajne alternatywy, takie jak tablice lub wektory .
Alternatywą dla używania (!!)
jest użycie
zestawu soczewek i jego element
funkcji oraz powiązanych operatorów.
Obiektyw zapewnia jednolity interfejs dostępu do różnorodnych struktur i struktur zagnieżdżonych wykraczające poza list. Poniżej skupię się na przedstawieniu przykładów i omówię zarówno oznaczenia typu, jak i teorię stojącą za
opakowaniem soczewek . Jeśli chcesz dowiedzieć się więcej o teorii, dobrym miejscem do rozpoczęcia jest plik readme w repozytorium github .
W wierszu poleceń:
$ 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
Dostęp do listy z operatorem infix
> [1,2,3,4,5] ^? element 2 -- 0 based indexing
Just 3
W przeciwieństwie do (!!)
this nie zgłosi wyjątku podczas uzyskiwania dostępu do elementu poza granicami i Nothing
zamiast tego zwróci . Często zaleca się unikanie funkcji częściowych, takich jak (!!)
lub, head
ponieważ mają one więcej przypadków narożnych i częściej powodują błąd w czasie wykonywania. Możesz przeczytać trochę więcej o tym, dlaczego unikać częściowych funkcji na tej stronie wiki .
> [1,2,3] !! 9
*** Exception: Prelude.(!!): index too large
> [1,2,3] ^? element 9
Nothing
Możesz wymusić na technice soczewki funkcję częściową i zgłosić wyjątek, gdy jest poza zakresem, używając (^?!)
operatora zamiast (^?)
operatora.
> [1,2,3] ^?! element 1
2
> [1,2,3] ^?! element 9
*** Exception: (^?!): empty Fold
Nie ogranicza się to jednak tylko do list. Na przykład ta sama technika działa na drzewach ze standardowego pakietu kontenerów .
> 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
Możemy teraz uzyskać dostęp do elementów drzewa w kolejności w pierwszej kolejności:
> 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
Możemy również uzyskać dostęp do sekwencji z pakietu kontenerów :
> import qualified Data.Sequence as Seq
> Seq.fromList [1,2,3,4] ^? element 3
Just 4
Możemy uzyskać dostęp do standardowych tablic indeksowanych int z pakietu wektorów , tekstu ze standardowego pakietu tekstowego , bajtów ze standardowego pakietu bajtowego i wielu innych standardowych struktur danych. Tę standardową metodę dostępu można rozszerzyć na struktury danych osobowych, czyniąc je instancją typeklasy Taversable , zobacz dłuższą listę przykładów Traversables w dokumentacji Obiektywu. .
Zagłębianie się w zagnieżdżone struktury jest proste dzięki hakowaniu soczewki . Na przykład dostęp do elementu na liście list:
> [[1,2,3],[4,5,6]] ^? element 0 . element 1
Just 2
> [[1,2,3],[4,5,6]] ^? element 1 . element 2
Just 6
Ta kompozycja działa nawet wtedy, gdy zagnieżdżone struktury danych są różnych typów. Na przykład gdybym miał listę drzew:
> :{
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
Możesz zagnieżdżać dowolnie głęboko z dowolnymi typami, o ile spełniają one Traversable
wymagania. Zatem dostęp do listy drzew z sekwencjami tekstu to nie problem.
Powszechną operacją w wielu językach jest przypisanie do indeksowanej pozycji w tablicy. W Pythonie możesz:
>>> a = [1,2,3,4,5]
>>> a[3] = 9
>>> a
[1, 2, 3, 9, 5]
Obiektyw pakiet daje tę funkcjonalność z (.~)
operatorem. Chociaż w przeciwieństwie do pythona oryginalna lista nie jest mutowana, zwracana jest raczej nowa lista.
> 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
to tylko funkcja, a (&)
operator, część zestawu
soczewek , jest po prostu odwrotnym zastosowaniem funkcji. Tutaj jest z bardziej powszechną aplikacją funkcji.
> (element 3 .~ 9) [1,2,3,4,5]
[1,2,3,9,5]
Przypisanie znowu działa doskonale z arbitralnym zagnieżdżeniem Traversable
s.
> [[1,2,3],[4,5,6]] & element 0 . element 1 .~ 9
[[1,9,3],[4,5,6]]
Data.Traversable
zamiast ponownego eksportu w lens
?
Prosta odpowiedź została już udzielona: użyj !!
.
Jednak początkujący często nadużywają tego operatora, który jest drogi w Haskell (ponieważ pracujesz na pojedynczych połączonych listach, a nie na tablicach). Istnieje kilka przydatnych technik, aby tego uniknąć, z których najłatwiejszym jest użycie zip. Jeśli napiszesz zip ["foo","bar","baz"] [0..]
, otrzymasz nową listę z indeksami „dołączonymi” do każdego elementu w parze:, [("foo",0),("bar",1),("baz",2)]
co często jest dokładnie tym, czego potrzebujesz.
Standardowy typ danych listy Haskell forall t. [t]
w implementacji bardzo przypomina kanoniczną listę połączoną z C i ma podobne właściwości. Połączone listy bardzo różnią się od tablic. Przede wszystkim dostęp przez indeks jest operacją liniową O (n), zamiast operacji O (1) w czasie stałym.
Jeśli potrzebujesz częstego losowego dostępu, rozważ Data.Array
standard.
!!
jest niebezpieczną, częściowo zdefiniowaną funkcją, wywołującą awarię dla indeksów poza zakresem. Należy pamiętać, że biblioteka standardowa zawiera takie częściowe funkcje (head
, last
etc.). Ze względów bezpieczeństwa użyj typu opcji Maybe
lub Safe
modułu.
Przykład w miarę wydajnej, solidnej funkcji indeksowania sumy (dla indeksów ≥ 0):
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
Praca z połączonymi listami często jest wygodna:
nth :: Int -> [a] -> Maybe a
nth _ [] = Nothing
nth 1 (x : _) = Just x
nth n (_ : xs) = nth (n - 1) xs
[1,2,3]!!6
wyświetli błąd wykonania. Gdyby!!
miał taki typ, bardzo łatwo można by tego uniknąć[a] -> Int -> Maybe a
. Powodem, dla którego mamy Haskell, jest unikanie takich błędów w czasie wykonywania!