Wie kann ich das n-te Element aus einer Liste erhalten?


97

Wie kann ich analog zu diesem C-Code auf eine Liste nach Index in Haskell zugreifen?

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

Antworten:


154

Schauen Sie hier , der verwendete Operator ist !!.

Dh [1,2,3]!!1gibt Ihnen 2, da Listen 0-indiziert sind.


86
Persönlich kann ich nicht verstehen, wie ein At-Index-Accessor, der keinen Vielleicht-Typ zurückgibt, als idiomatischer Haskell akzeptabel ist. [1,2,3]!!6gibt Ihnen einen Laufzeitfehler. Es könnte sehr leicht vermieden werden, wenn !!der Typ hätte [a] -> Int -> Maybe a. Der Grund, warum wir Haskell haben, ist, solche Laufzeitfehler zu vermeiden!
Worldsayshi

9
Es ist ein Kompromiss. Das Symbol, das sie gewählt haben, ist wahrscheinlich das alarmierendste Symbol, das sie haben könnten. Ich denke, die Idee war, es für Randfälle zuzulassen, aber es als nicht idiomatisch hervorzuheben.
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)). Beachten Sie, dass dies bei einer unendlichen Liste katastrophal fehlschlägt.
DJVs

2
!!ist eine teilweise und damit unsichere Funktion. lens Werfen
goetzc

90

Ich sage nicht, dass mit Ihrer Frage oder der gegebenen Antwort etwas nicht stimmt, aber vielleicht möchten Sie etwas über das wunderbare Tool von Hoogle wissen, mit dem Sie sich in Zukunft Zeit sparen können: Mit Hoogle können Sie nach Standardbibliotheksfunktionen suchen die einer bestimmten Unterschrift entsprechen. Wenn Sie also nichts darüber wissen !!, könnten Sie in Ihrem Fall nach "etwas suchen, das eine Intund eine Liste von was auch immer nimmt und ein einzelnes solches zurückgibt, was auch immer", nämlich

Int -> [a] -> a

Und siehe da , mit !!als erstem Ergebnis (obwohl die Typensignatur tatsächlich die beiden Argumente im Vergleich zu dem, wonach wir gesucht haben, umgekehrt hat). Ordentlich, was?

Wenn Ihr Code auf der Indizierung beruht (anstatt von der Vorderseite der Liste zu konsumieren), sind Listen möglicherweise tatsächlich nicht die richtige Datenstruktur. Für den O (1) -indexbasierten Zugriff gibt es effizientere Alternativen wie Arrays oder Vektoren .


4
Hoogle ist absolut großartig. Jeder Haskell-Programmierer sollte es wissen. Es gibt eine Alternative namens Hayoo ( holumbus.fh-wedel.de/hayoo/hayoo.html ). Es sucht während der Eingabe, scheint aber nicht so clever zu sein wie Hoogle.
musiKk

61

Eine Alternative zur Verwendung (!!)besteht darin, das Objektivpaket und seine elementFunktion sowie die zugehörigen Bediener zu verwenden. Das Objektiv bietet eine einheitliche Schnittstelle für den Zugriff auf eine Vielzahl von Strukturen und verschachtelten Strukturen über Listen hinaus. Im Folgenden werde ich mich auf die Bereitstellung von Beispielen konzentrieren und sowohl die Typensignaturen als auch die Theorie hinter dem Objektivpaket beschönigen . Wenn Sie mehr über die Theorie erfahren möchten, ist die Readme-Datei im Github-Repo ein guter Ausgangspunkt .

Zugriff auf Listen und andere Datentypen

Zugriff auf das Objektivpaket

In der Kommandozeile:

$ 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


Zugriff auf Listen

Zugriff auf eine Liste mit dem Infix-Operator

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

Im Gegensatz (!!)dazu wird beim Zugriff auf ein Element außerhalb der Grenzen keine Ausnahme ausgelöst und Nothingstattdessen zurückgegeben. Es wird häufig empfohlen, Teilfunktionen wie (!!)oder zu vermeiden, headda sie mehr Eckfälle aufweisen und mit größerer Wahrscheinlichkeit einen Laufzeitfehler verursachen. Auf dieser Wiki-Seite erfahren Sie mehr darüber, warum Sie Teilfunktionen vermeiden sollten .

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

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

Sie können die Linsentechnik als Teilfunktion erzwingen und eine Ausnahme auslösen, wenn sie außerhalb der Grenzen liegt, indem Sie den (^?!)Operator anstelle des (^?)Operators verwenden.

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


Arbeiten mit anderen Typen als Listen

Dies ist jedoch nicht nur auf Listen beschränkt. Zum Beispiel funktioniert auf die gleiche Technik , die auf Bäumen aus dem Standard - Container - Paket.

 > 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

Wir können jetzt in der Reihenfolge der Tiefe zuerst auf die Elemente des Baums zugreifen:

> 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

Wir können auch über das Containerpaket auf Sequenzen zugreifen :

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

Wir können den Standard - int indizierten Arrays aus dem Zugriff auf Vektor - Paket, Text aus dem Standard - Text - Paket, fro bytestrings das Standard bytestring Paket und viele anderen Standard - Datenstrukturen. Diese Standardzugriffsmethode kann auf Ihre persönlichen Datenstrukturen erweitert werden, indem Sie sie zu einer Instanz der Typklasse Taversable machen . Eine längere Liste von Beispiel- Traversables finden Sie in der Lens-Dokumentation. .


Verschachtelte Strukturen

Das Eingraben in verschachtelte Strukturen ist mit dem Objektiv- Hackage einfach . Zum Beispiel Zugriff auf ein Element in einer Liste von Listen:

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

Diese Komposition funktioniert auch dann, wenn die verschachtelten Datenstrukturen unterschiedlichen Typs sind. Wenn ich zum Beispiel eine Liste von Bäumen hätte:

> :{
 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

Sie können beliebig tief mit beliebigen Typen verschachteln, solange diese die TraversableAnforderungen erfüllen. Der Zugriff auf eine Liste von Bäumen mit Textsequenzen ist also kein Problem.


Ändern des n-ten Elements

In vielen Sprachen wird häufig eine indizierte Position in einem Array zugewiesen. In Python könnten Sie:

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

Das Objektivpaket bietet diese Funktionalität für den (.~)Bediener. Obwohl im Gegensatz zu Python die ursprüngliche Liste nicht mutiert ist, wird eine neue Liste zurückgegeben.

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

element 3 .~ 9ist nur eine Funktion und der (&)Bediener, Teil des Objektivpakets , ist nur eine Umkehrfunktionsanwendung. Hier ist es mit der allgemeineren Funktionsanwendung.

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

Die Zuweisung funktioniert wieder einwandfrei mit willkürlicher Verschachtelung von Traversables.

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

3
Darf ich vorschlagen, auf zu verlinken Data.Traversableund nicht wieder zu exportieren lens?
dfeuer

@dfeuer - Ich habe einen Link zu Data.Traversable in base hinzugefügt. Ich habe auch den alten Link beibehalten und darauf hingewiesen, dass die Objektivdokumentation eine längere Liste von Beispiel-Traverables enthält. Danke für den Vorschlag.
Davorak

11

Die klare Antwort wurde bereits gegeben: Verwenden !!.

Neulinge neigen jedoch häufig dazu, diesen Operator zu überbeanspruchen, was in Haskell teuer ist (weil Sie an einzelnen verknüpften Listen arbeiten, nicht an Arrays). Es gibt verschiedene nützliche Techniken, um dies zu vermeiden. Die einfachste ist die Verwendung von Zip. Wenn Sie schreiben zip ["foo","bar","baz"] [0..], erhalten Sie eine neue Liste mit den Indizes, die an jedes Element in einem Paar "angehängt" sind. Dies [("foo",0),("bar",1),("baz",2)]ist häufig genau das, was Sie benötigen.


2
Sie müssen dort auch vorsichtig mit Ihren Typen sein. Meistens möchten Sie nicht, dass die Indizes langsame Ganzzahlen und keine schnellen Maschinen-Ints sind. Abhängig davon, was genau Ihre Funktion tut und wie explizit Ihre Eingabe ist, kann Haskell den Typ von [0 ..] als [Integer] anstelle von [Int] ableiten.
Chrisdb

4

Sie können verwenden !!, aber wenn Sie es rekursiv tun möchten, ist unten eine Möglichkeit, dies zu tun:

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

4

Der Standard-Listendatentyp von Haskell forall t. [t]in der Implementierung ähnelt stark einer kanonischen C-verknüpften Liste und teilt seine wesentlichen Eigenschaften. Verknüpfte Listen unterscheiden sich stark von Arrays. Insbesondere ist der Zugriff per Index eine O (n) -Linear- anstelle einer O (1) -Operation mit konstanter Zeit.

Wenn Sie häufigen Direktzugriff benötigen, beachten Sie den Data.ArrayStandard.

!!ist eine unsichere, teilweise definierte Funktion, die bei Indizes außerhalb des Bereichs zu einem Absturz führt. Beachten Sie, dass die Standard - Bibliothek einige solche Teilfunktionen enthält ( head, lastusw.). Verwenden Sie aus Sicherheitsgründen einen Optionstyp Maybeoder das SafeModul.

Beispiel einer einigermaßen effizienten, robusten Gesamtindexierungsfunktion (für Indizes ≥ 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

Bei der Arbeit mit verknüpften Listen sind häufig Ordnungszahlen praktisch:

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

Diese Funktionen werden für negative bzw. nicht positive Ints für immer wiederholt.
Bjartur Thorlacius
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.