ฉันจะรับองค์ประกอบที่ n จากรายการได้อย่างไร


99

ฉันจะเข้าถึงรายการตามดัชนีใน Haskell ซึ่งเป็นอะนาล็อกกับรหัส C นี้ได้อย่างไร

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

คำตอบ:


161

ดูที่นี่ , !!ผู้ประกอบการที่ใช้เป็น

Ie [1,2,3]!!1ช่วยให้คุณ2เนื่องจากรายการถูกจัดทำดัชนี 0


87
โดยส่วนตัวแล้วฉันไม่สามารถเข้าใจได้ว่าตัวเข้าถึงดัชนีที่ไม่ส่งคืนประเภทอาจจะเป็นที่ยอมรับในฐานะ Haskell สำนวนได้อย่างไร [1,2,3]!!6จะทำให้คุณมีข้อผิดพลาดรันไทม์ มันอาจจะได้อย่างง่ายดายมากหลีกเลี่ยงได้หากมีชนิด!! [a] -> Int -> Maybe aเหตุผลที่เรามี Haskell คือการหลีกเลี่ยงข้อผิดพลาดรันไทม์ดังกล่าว!
worldsayshi

10
มันเป็นการแลกเปลี่ยน สัญลักษณ์ที่พวกเขาเลือกน่าจะเป็นสัญลักษณ์ที่น่าตกใจที่สุดที่พวกเขามี ดังนั้นฉันคิดว่าแนวคิดคืออนุญาตให้ใช้กับเคส edge ได้ แต่ทำให้มันโดดเด่นแบบไม่ใช้สำนวน
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)). หมายเหตุสิ่งนี้จะล้มเหลวอย่างย่อยยับในรายการที่ไม่มีที่สิ้นสุด
djvs

2
!!เป็นฟังก์ชันบางส่วนและไม่ปลอดภัย ดูความคิดเห็นด้านล่างและใช้lens stackoverflow.com/a/23627631/2574719
goetzc

90

ฉันไม่ได้บอกว่ามีอะไรผิดปกติกับคำถามของคุณหรือคำตอบที่ได้รับ แต่บางทีคุณอาจอยากรู้เกี่ยวกับเครื่องมือที่ยอดเยี่ยมนั่นคือHoogleเพื่อช่วยตัวเองในอนาคต: ด้วย Hoogle คุณสามารถค้นหาฟังก์ชันไลบรารีมาตรฐานได้ ที่ตรงกับลายเซ็นที่กำหนด ดังนั้นหากคุณไม่ทราบอะไรเลย!!ในกรณีของคุณคุณอาจค้นหา "สิ่งที่ใช้Intและรายการของสิ่งที่ส่งกลับและส่งคืนสิ่งใดสิ่งหนึ่ง" นั่นคือ

Int -> [a] -> a

แท้จริงแล้วดูด้วย!!เป็นผลลัพธ์แรก (แม้ว่าลายเซ็นประเภทจะมีอาร์กิวเมนต์สองรายการที่ตรงกันข้ามเมื่อเทียบกับสิ่งที่เราค้นหา) เรียบร้อยเหรอ?

นอกจากนี้หากโค้ดของคุณอาศัยการจัดทำดัชนี (แทนที่จะใช้จากด้านหน้าของรายการ) รายการอาจไม่ใช่โครงสร้างข้อมูลที่เหมาะสม สำหรับการเข้าถึงดัชนีที่ใช้ O (1) มีทางเลือกที่มีประสิทธิภาพมากขึ้นเช่นอาร์เรย์หรือพาหะ


4
Hoogle นั้นยอดเยี่ยมมาก โปรแกรมเมอร์ของ Haskell ทุกคนควรรู้ มีทางเลือกที่เรียกว่า Hayoo ( holumbus.fh-wedel.de/hayoo/hayoo.html ) มันค้นหาขณะที่คุณพิมพ์ แต่ดูเหมือนจะไม่ฉลาดเท่า Hoogle
musiKk

61

อีกทางเลือกหนึ่งในการใช้งาน(!!)คือการใช้ แพ็คเกจเลนส์และelementฟังก์ชันและตัวดำเนินการที่เกี่ยวข้อง เลนส์ให้อินเตอร์เฟซเครื่องแบบสำหรับการเข้าถึงความหลากหลายของโครงสร้างและโครงสร้างที่ซ้อนกันข้างต้นและนอกเหนือรายการ ด้านล่างนี้ฉันจะเน้นไปที่การให้ตัวอย่างและจะอธิบายทั้งลายเซ็นประเภทและทฤษฎีที่อยู่เบื้องหลัง แพ็คเกจเลนส์ หากคุณต้องการทราบข้อมูลเพิ่มเติมเกี่ยวกับทฤษฎีเป็นสถานที่ที่ดีที่จะเริ่มเป็นแฟ้ม readme ที่repo GitHub

การเข้าถึงรายการและประเภทข้อมูลอื่น ๆ

การเข้าถึงแพ็คเกจเลนส์

ที่บรรทัดคำสั่ง:

$ 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

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

ซึ่งแตกต่างจากสิ่ง(!!)นี้จะไม่ทำให้เกิดข้อยกเว้นเมื่อเข้าถึงองค์ประกอบนอกขอบเขตและจะกลับมาNothingแทน มักจะแนะนำให้หลีกเลี่ยงฟังก์ชันบางส่วนเช่น(!!)หรือheadเนื่องจากมีกรณีมุมมากกว่าและมีแนวโน้มที่จะทำให้เกิดข้อผิดพลาดขณะทำงาน คุณสามารถอ่านเล็ก ๆ น้อย ๆ เพิ่มเติมเกี่ยวกับสาเหตุที่จะหลีกเลี่ยงการทำงานบางส่วนที่หน้าวิกินี้

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

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

คุณสามารถบังคับให้เทคนิคเลนส์เป็นฟังก์ชันบางส่วนและโยนข้อยกเว้นเมื่ออยู่นอกขอบเขตโดยใช้ตัว(^?!)ดำเนินการแทนตัว(^?)ดำเนินการ

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


การทำงานกับประเภทอื่นที่ไม่ใช่รายการ

อย่างไรก็ตามสิ่งนี้ไม่ได้ จำกัด เพียงแค่รายการเท่านั้น ตัวอย่างเช่นเทคนิคเดียวกันนี้ใช้กับต้นไม้จากแพ็คเกจคอนเทนเนอร์มาตรฐาน

 > 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

ตอนนี้เราสามารถเข้าถึงองค์ประกอบของต้นไม้ในลำดับแรกเชิงลึก:

> 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

นอกจากนี้เรายังสามารถเข้าถึงลำดับจาก แพ็คเกจคอนเทนเนอร์ :

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

เราสามารถเข้าถึงอาร์เรย์ int มาตรฐานที่จัดทำดัชนีได้จาก แพ็คเกจเวกเตอร์ข้อความจาก แพ็คเกจข้อความมาตรฐานbytestrings จากแพ็คเกจbytestringมาตรฐาน และโครงสร้างข้อมูลมาตรฐานอื่น ๆ อีกมากมาย วิธีการเข้าถึงมาตรฐานนี้สามารถขยายไปยังโครงสร้างข้อมูลส่วนบุคคลของคุณได้โดยทำให้เป็นอินสแตนซ์ของประเภทTaversableโปรดดูรายการตัวอย่างTraversables ที่ยาวขึ้นในเอกสาร Lens .


โครงสร้างที่ซ้อนกัน

ขุดลงไปในโครงสร้างที่ซ้อนกันเป็นเรื่องง่ายกับเลนส์hackage ตัวอย่างเช่นการเข้าถึงองค์ประกอบในรายการ:

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

องค์ประกอบนี้ทำงานได้แม้โครงสร้างข้อมูลที่ซ้อนกันจะเป็นประเภทต่างๆ ตัวอย่างเช่นถ้าฉันมีรายชื่อต้นไม้:

> :{
 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ข้อกำหนด ดังนั้นการเข้าถึงรายการต้นไม้ของลำดับข้อความจึงไม่ใช่เรื่องยาก


การเปลี่ยนองค์ประกอบที่ n

การดำเนินการทั่วไปในหลายภาษาคือการกำหนดให้กับตำแหน่งที่จัดทำดัชนีในอาร์เรย์ ใน python คุณอาจ:

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

เลนส์แพคเกจให้ฟังก์ชันการทำงานนี้กับ(.~)ผู้ประกอบการ แม้ว่ารายการเดิมจะแตกต่างจาก python แต่รายการเดิมจะไม่กลายพันธุ์ แต่รายการใหม่จะถูกส่งกลับ

> 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เป็นเพียงฟังก์ชันและตัว(&)ดำเนินการซึ่งเป็นส่วนหนึ่งของ แพ็คเกจเลนส์เป็นเพียงแอปพลิเคชันฟังก์ชันย้อนกลับ นี่คือแอปพลิเคชันฟังก์ชันทั่วไป

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

การมอบหมายงานอีกครั้งทำงานได้ดีอย่างสมบูรณ์แบบด้วยการซ้อนของTraversables โดยพลการ

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

3
ฉันขอแนะนำให้เชื่อมโยงData.Traversableแทนที่จะส่งออกใหม่ในlensหรือไม่
dfeuer

@dfeuer - ฉันได้เพิ่มลิงค์ไปยัง Data ซึ่งสามารถแลกเปลี่ยนได้ในฐาน ฉันยังเก็บลิงค์เก่าไว้และชี้ให้เห็นว่ามีรายการตัวอย่างการเดินทางอีกต่อไปในเอกสารของ Lens ขอบคุณสำหรับคำแนะนำ
Davorak

11

คำตอบที่ได้รับตรงแล้ว: !!ใช้

อย่างไรก็ตามมือใหม่มักจะใช้ตัวดำเนินการนี้มากเกินไปซึ่งมีราคาแพงใน Haskell (เนื่องจากคุณทำงานในรายการที่เชื่อมโยงเดียวไม่ใช่ในอาร์เรย์) มีเทคนิคที่เป็นประโยชน์หลายประการในการหลีกเลี่ยงปัญหานี้วิธีที่ง่ายที่สุดคือการใช้ซิป หากคุณเขียนzip ["foo","bar","baz"] [0..]คุณจะได้รับรายการใหม่ที่มีดัชนี "แนบ" กับแต่ละองค์ประกอบในคู่: [("foo",0),("bar",1),("baz",2)]ซึ่งมักจะเป็นสิ่งที่คุณต้องการ


2
คุณต้องระวังประเภทของคุณที่นั่นด้วย เวลาส่วนใหญ่คุณไม่ต้องการลงเอยด้วยการที่ดัชนีเป็นจำนวนเต็มช้าแทนที่จะเป็น Ints ของเครื่องที่รวดเร็ว ขึ้นอยู่กับว่าฟังก์ชันของคุณทำหน้าที่อะไรและความชัดเจนในการพิมพ์ของคุณ Haskell อาจอนุมานประเภทของ [0 .. ] เป็น [Integer] แทน [Int]
chrisdb

4

คุณสามารถใช้ได้!!แต่ถ้าคุณต้องการทำซ้ำด้านล่างเป็นวิธีหนึ่งที่จะทำได้:

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

4

ประเภทข้อมูลรายการมาตรฐานของ Haskell forall t. [t]ในการนำไปใช้งานมีลักษณะใกล้เคียงกับรายการที่เชื่อมโยงมาตรฐาน C และแบ่งปันคุณสมบัติเป็นหลัก รายการที่เชื่อมโยงแตกต่างจากอาร์เรย์มาก โดยเฉพาะอย่างยิ่งการเข้าถึงโดยดัชนีคือ O (n) เชิงเส้น - แทนที่จะเป็นการดำเนินการตามเวลาคงที่ O (1)

หากคุณต้องการการเข้าถึงแบบสุ่มบ่อยๆให้พิจารณาData.Arrayมาตรฐาน

!!เป็นฟังก์ชันที่กำหนดไว้บางส่วนที่ไม่ปลอดภัยซึ่งกระตุ้นให้เกิดความผิดพลาดสำหรับดัชนีนอกช่วง โปรดทราบว่าห้องสมุดมาตรฐานมีฟังก์ชั่นบางส่วนบางอย่างเช่น ( head, lastฯลฯ ) เพื่อความปลอดภัยให้ใช้ประเภทตัวเลือกMaybeหรือSafeโมดูล

ตัวอย่างฟังก์ชันการจัดทำดัชนีที่มีประสิทธิภาพและมีประสิทธิภาพพอสมควร (สำหรับดัชนี≥ 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

การทำงานกับรายการที่เชื่อมโยงมักจะสะดวก:

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

ฟังก์ชันเหล่านี้เรียกคืนตลอดไปสำหรับ Ints เชิงลบและเชิงลบตามลำดับ
Bjartur Thorlacius
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.