ฉันจะเข้าถึงรายการตามดัชนีใน Haskell ซึ่งเป็นอะนาล็อกกับรหัส C นี้ได้อย่างไร
int a[] = { 34, 45, 56 };
return a[1];
ฉันจะเข้าถึงรายการตามดัชนีใน Haskell ซึ่งเป็นอะนาล็อกกับรหัส C นี้ได้อย่างไร
int a[] = { 34, 45, 56 };
return a[1];
คำตอบ:
ดูที่นี่ , !!
ผู้ประกอบการที่ใช้เป็น
Ie [1,2,3]!!1
ช่วยให้คุณ2
เนื่องจากรายการถูกจัดทำดัชนี 0
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))
. หมายเหตุสิ่งนี้จะล้มเหลวอย่างย่อยยับในรายการที่ไม่มีที่สิ้นสุด
!!
เป็นฟังก์ชันบางส่วนและไม่ปลอดภัย ดูความคิดเห็นด้านล่างและใช้lens
stackoverflow.com/a/23627631/2574719
ฉันไม่ได้บอกว่ามีอะไรผิดปกติกับคำถามของคุณหรือคำตอบที่ได้รับ แต่บางทีคุณอาจอยากรู้เกี่ยวกับเครื่องมือที่ยอดเยี่ยมนั่นคือHoogleเพื่อช่วยตัวเองในอนาคต: ด้วย Hoogle คุณสามารถค้นหาฟังก์ชันไลบรารีมาตรฐานได้ ที่ตรงกับลายเซ็นที่กำหนด ดังนั้นหากคุณไม่ทราบอะไรเลย!!
ในกรณีของคุณคุณอาจค้นหา "สิ่งที่ใช้Int
และรายการของสิ่งที่ส่งกลับและส่งคืนสิ่งใดสิ่งหนึ่ง" นั่นคือ
Int -> [a] -> a
แท้จริงแล้วดูด้วย!!
เป็นผลลัพธ์แรก (แม้ว่าลายเซ็นประเภทจะมีอาร์กิวเมนต์สองรายการที่ตรงกันข้ามเมื่อเทียบกับสิ่งที่เราค้นหา) เรียบร้อยเหรอ?
นอกจากนี้หากโค้ดของคุณอาศัยการจัดทำดัชนี (แทนที่จะใช้จากด้านหน้าของรายการ) รายการอาจไม่ใช่โครงสร้างข้อมูลที่เหมาะสม สำหรับการเข้าถึงดัชนีที่ใช้ O (1) มีทางเลือกที่มีประสิทธิภาพมากขึ้นเช่นอาร์เรย์หรือพาหะ
อีกทางเลือกหนึ่งในการใช้งาน(!!)
คือการใช้
แพ็คเกจเลนส์และ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
ข้อกำหนด ดังนั้นการเข้าถึงรายการต้นไม้ของลำดับข้อความจึงไม่ใช่เรื่องยาก
การดำเนินการทั่วไปในหลายภาษาคือการกำหนดให้กับตำแหน่งที่จัดทำดัชนีในอาร์เรย์ ใน 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]
การมอบหมายงานอีกครั้งทำงานได้ดีอย่างสมบูรณ์แบบด้วยการซ้อนของTraversable
s โดยพลการ
> [[1,2,3],[4,5,6]] & element 0 . element 1 .~ 9
[[1,9,3],[4,5,6]]
Data.Traversable
แทนที่จะส่งออกใหม่ในlens
หรือไม่
คำตอบที่ได้รับตรงแล้ว: !!
ใช้
อย่างไรก็ตามมือใหม่มักจะใช้ตัวดำเนินการนี้มากเกินไปซึ่งมีราคาแพงใน Haskell (เนื่องจากคุณทำงานในรายการที่เชื่อมโยงเดียวไม่ใช่ในอาร์เรย์) มีเทคนิคที่เป็นประโยชน์หลายประการในการหลีกเลี่ยงปัญหานี้วิธีที่ง่ายที่สุดคือการใช้ซิป หากคุณเขียนzip ["foo","bar","baz"] [0..]
คุณจะได้รับรายการใหม่ที่มีดัชนี "แนบ" กับแต่ละองค์ประกอบในคู่: [("foo",0),("bar",1),("baz",2)]
ซึ่งมักจะเป็นสิ่งที่คุณต้องการ
คุณสามารถใช้ได้!!
แต่ถ้าคุณต้องการทำซ้ำด้านล่างเป็นวิธีหนึ่งที่จะทำได้:
dataAt :: Int -> [a] -> a
dataAt _ [] = error "Empty List!"
dataAt y (x:xs) | y <= 0 = x
| otherwise = dataAt (y-1) xs
ประเภทข้อมูลรายการมาตรฐานของ 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
[1,2,3]!!6
จะทำให้คุณมีข้อผิดพลาดรันไทม์ มันอาจจะได้อย่างง่ายดายมากหลีกเลี่ยงได้หากมีชนิด!!
[a] -> Int -> Maybe a
เหตุผลที่เรามี Haskell คือการหลีกเลี่ยงข้อผิดพลาดรันไทม์ดังกล่าว!