Bagaimana saya bisa mendapatkan elemen ke-n dari daftar?

97

Bagaimana saya bisa mengakses daftar menurut indeks di Haskell, analog dengan kode C ini?

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

Jawaban:

154

Lihatlah di sini , operator yang digunakan yaitu!! .

Yaitu [1,2,3]!!1memberi Anda 2, karena daftar diindeks 0.

phimuemue
sumber
86
Secara pribadi saya tidak dapat memahami bagaimana pengakses indeks-at yang tidak mengembalikan tipe Mungkin dapat diterima sebagai idiomatik Haskell. [1,2,3]!!6akan memberi Anda kesalahan runtime. Ini dapat dengan mudah dihindari jika !!memiliki tipe [a] -> Int -> Maybe a. Alasan utama kami memiliki Haskell adalah untuk menghindari kesalahan runtime seperti itu!
worldsayshi
9
Ini pengorbanan. Simbol yang mereka pilih mungkin adalah simbol paling mengkhawatirkan yang bisa mereka miliki. Jadi saya pikir idenya adalah mengizinkannya untuk kasus-kasus tepi, tetapi membuatnya menonjol sebagai non-idiomatik.
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)). Perhatikan, ini akan gagal secara serempak pada daftar yang tak terbatas.
djvs
2
!!adalah fungsi parsial dan dengan demikian tidak aman. Lihatlah komentar di bawah ini dan gunakan lens stackoverflow.com/a/23627631/2574719
goetzc
90

Saya tidak mengatakan bahwa ada yang salah dengan pertanyaan Anda atau jawaban yang diberikan, tapi mungkin Anda ingin tahu tentang alat hebat Hoogle untuk menghemat waktu Anda di masa mendatang: Dengan Hoogle, Anda dapat mencari fungsi perpustakaan standar yang cocok dengan tanda tangan yang diberikan. Jadi, tidak tahu apa-apa tentang !!, dalam kasus Anda, Anda mungkin mencari "sesuatu yang mengambil Intdan daftar siapa dan mengembalikan satu seperti apa pun", yaitu

Int -> [a] -> a

Lo dan lihatlah , dengan!! sebagai hasil pertama (walaupun jenis tanda tangan sebenarnya memiliki dua argumen secara terbalik dibandingkan dengan apa yang kita mencari). Rapi, ya?

Selain itu, jika kode Anda bergantung pada pengindeksan (alih-alih memakan dari depan daftar), daftar mungkin sebenarnya bukan struktur data yang tepat. Untuk akses berbasis indeks O (1) terdapat alternatif yang lebih efisien, seperti array atau vektor .

gspr
sumber
4
Hoogle benar-benar hebat. Setiap programmer Haskell harus mengetahuinya. Ada alternatif yang disebut Hayoo ( holumbus.fh-wedel.de/hayoo/hayoo.html ). Ini mencari saat Anda mengetik tetapi tampaknya tidak secerdas Hoogle.
musiKk
61

Alternatif untuk menggunakan (!!)adalah dengan menggunakan paket lensa dan elementfungsinya serta operator terkait. The lensa menyediakan antarmuka yang seragam untuk mengakses berbagai struktur dan struktur bersarang atas dan di luar daftar. Di bawah ini saya akan fokus pada pemberian contoh dan akan mengabaikan kedua tanda tangan tipe dan teori di balik paket lensa . Jika Anda ingin tahu lebih banyak tentang teori, tempat yang baik untuk memulai adalah file readme di repo github .

Mengakses daftar dan tipe data lainnya

Mendapatkan akses ke paket lensa

Di baris perintah:

$ 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


Mengakses daftar

Untuk mengakses daftar dengan operator infix

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

Berbeda dengan (!!)ini , ini tidak akan memunculkan pengecualian saat mengakses elemen di luar batas dan akan kembali Nothingsebagai gantinya. Seringkali disarankan untuk menghindari fungsi parsial seperti (!!)atau headkarena mereka memiliki lebih banyak kasus sudut dan lebih cenderung menyebabkan kesalahan waktu proses. Anda dapat membaca lebih banyak tentang mengapa menghindari fungsi parsial di halaman wiki ini .

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

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

Anda dapat memaksa teknik lensa menjadi fungsi parsial dan membuat pengecualian saat di luar batas dengan menggunakan (^?!)operator alih-alih (^?)operator.

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


Bekerja dengan tipe selain daftar

Namun ini tidak hanya terbatas pada daftar. Misalnya, teknik yang sama bekerja pada pohon dari paket kontainer standar .

 > 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

Sekarang kita dapat mengakses elemen pohon secara mendalam-urutan pertama:

> 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

Kami juga dapat mengakses urutan dari paket kontainer :

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

Kita dapat mengakses array indeks int standar dari paket vektor , teks dari paket teks standar , bytestring dari paket bytestring standar , dan banyak struktur data standar lainnya. Metode akses standar ini dapat diperluas ke struktur data pribadi Anda dengan menjadikannya sebagai instance kelas tipe Taversable , lihat daftar yang lebih panjang dari contoh Traversable dalam dokumentasi Lens. .


Struktur bersarang

Menggali struktur bersarang sangatlah mudah dengan hackage lensa . Misalnya mengakses elemen dalam daftar daftar:

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

Komposisi ini berfungsi bahkan ketika struktur data bersarang memiliki tipe yang berbeda. Jadi misalnya jika saya punya daftar pohon:

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

Anda dapat menumpuk secara sewenang-wenang dengan jenis arbitrer selama memenuhi Traversablepersyaratan. Jadi mengakses daftar pohon urutan teks bukanlah masalah.


Mengubah elemen ke-n

Operasi umum dalam banyak bahasa adalah menetapkan ke posisi terindeks dalam array. Dengan python Anda mungkin:

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

The lensa paket memberikan fungsi ini dengan (.~)operator. Meskipun tidak seperti di python, daftar asli tidak bermutasi, tetapi daftar baru dikembalikan.

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

element 3 .~ 9hanyalah sebuah fungsi dan (&)operator, bagian dari paket lensa , hanyalah aplikasi fungsi terbalik. Ini dia dengan aplikasi fungsi yang lebih umum.

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

Penugasan kembali berfungsi dengan baik dengan penumpukan acak Traversables.

> [[1,2,3],[4,5,6]] & element 0 . element 1 .~ 9
[[1,9,3],[4,5,6]]
Davorak
sumber
3
Bolehkah saya menyarankan untuk menautkan ke Data.Traversabledaripada mengekspor ulang lens?
dfeuer
@ pdfeuer - Saya menambahkan tautan ke Data.Traversable di basis. Saya juga menyimpan tautan lama dan menunjukkan bahwa ada daftar yang lebih panjang dari contoh yang dapat ditelusuri dalam dokumentasi Lens. Terima kasih untuk sarannya.
Davorak
11

Jawaban langsung sudah diberikan: Gunakan !!.

Namun pemula sering cenderung terlalu sering menggunakan operator ini, yang mahal di Haskell (karena Anda bekerja pada daftar tertaut tunggal, bukan pada array). Ada beberapa teknik yang berguna untuk menghindari hal ini, yang paling mudah adalah menggunakan zip. Jika Anda menulis zip ["foo","bar","baz"] [0..], Anda mendapatkan daftar baru dengan indeks "dilampirkan" ke setiap elemen berpasangan:, [("foo",0),("bar",1),("baz",2)]yang seringkali persis seperti yang Anda butuhkan.

Landei
sumber
2
Anda juga perlu berhati-hati dengan tipe Anda di sana. Sebagian besar waktu Anda tidak ingin berakhir dengan indeks menjadi Integer lambat daripada Ints mesin cepat. Bergantung pada apa sebenarnya fungsi Anda dan seberapa eksplisit pengetikan Anda, Haskell mungkin menyimpulkan tipe [0 ..] menjadi [Integer] bukan [Int].
chrisdb
4

Anda dapat menggunakan !!, tetapi jika Anda ingin melakukannya secara rekursif maka di bawah ini adalah salah satu cara untuk melakukannya:

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

Tipe data daftar standar Haskell forall t. [t] dalam penerapannya sangat mirip dengan daftar tertaut C kanonik, dan pada dasarnya berbagi properti. Daftar tertaut sangat berbeda dari array. Terutama, akses oleh indeks adalah O (n) linier-, bukan operasi waktu-konstan O (1).

Jika Anda sering memerlukan akses acak, pertimbangkan Data.Arraystandarnya.

!!adalah fungsi yang ditentukan sebagian yang tidak aman, yang memicu error untuk indeks yang berada di luar rentang. Sadarilah bahwa perpustakaan standar berisi beberapa fungsi parsial seperti ( head, last, dll). Untuk keamanan, gunakan tipe opsi Maybe, atau Safemodul.

Contoh fungsi pengindeksan total yang cukup efisien dan kuat (untuk indeks ≥ 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

Bekerja dengan daftar tertaut, seringkali ordinal nyaman:

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

sumber
Fungsi ini berulang selamanya untuk Ints negatif dan nonpositif masing-masing.
Bjartur Thorlacius