Câu trả lời:
Nhìn ở đây , toán tử được sử dụng là !!
.
Tức là [1,2,3]!!1
cung cấp cho bạn 2
, vì danh sách được lập chỉ mục 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))
. Lưu ý, điều này sẽ thất bại thảm hại trong danh sách vô hạn.
!!
là một chức năng một phần và do đó không an toàn. Hãy nhìn vào những nhận xét dưới đây và sử dụng lens
stackoverflow.com/a/23627631/2574719
Tôi không nói rằng có điều gì sai với câu hỏi của bạn hoặc câu trả lời được đưa ra, nhưng có thể bạn muốn biết về công cụ tuyệt vời là Hoogle để tiết kiệm thời gian cho chính bạn trong tương lai: Với Hoogle, bạn có thể tìm kiếm các chức năng thư viện tiêu chuẩn phù hợp với một chữ ký nhất định. Vì vậy, không biết bất cứ điều gì !!
, trong trường hợp của bạn, bạn có thể tìm kiếm "thứ gì đó lấy một Int
và một danh sách các whatevers và trả về một cái gì đó như vậy bất cứ thứ gì", cụ thể là
Int -> [a] -> a
Lo và kìa , với !!
kết quả đầu tiên (mặc dù chữ ký kiểu thực sự có hai đối số ngược lại so với những gì chúng tôi đã tìm kiếm). Gọn gàng hả?
Ngoài ra, nếu mã của bạn dựa vào lập chỉ mục (thay vì sử dụng từ phía trước danh sách), thực tế danh sách có thể không phải là cấu trúc dữ liệu thích hợp. Đối với truy cập dựa trên chỉ mục O (1), có nhiều lựa chọn thay thế hiệu quả hơn, chẳng hạn như mảng hoặc vectơ .
Một thay thế cho việc sử dụng (!!)
là sử dụng
gói ống kính và element
chức năng của nó và các nhà khai thác liên quan. Các
ống kính cung cấp một giao diện thống nhất để truy cập vào một loạt các cấu trúc và cấu trúc lồng nhau trên và vượt ra ngoài danh sách. Dưới đây tôi sẽ tập trung vào việc cung cấp các ví dụ và sẽ giải thích về cả chữ ký loại và lý thuyết đằng sau
gói ống kính . Nếu bạn muốn biết thêm về lý thuyết, một nơi tốt để bắt đầu là tệp readme tại github repo .
Tại dòng lệnh:
$ 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
Để truy cập danh sách bằng toán tử infix
> [1,2,3,4,5] ^? element 2 -- 0 based indexing
Just 3
Không giống như (!!)
điều này sẽ không đưa ra một ngoại lệ khi truy cập một phần tử ngoài giới hạn và Nothing
thay vào đó sẽ trả về . Bạn thường nên tránh các chức năng từng phần như (!!)
hoặc head
vì chúng có nhiều trường hợp góc hơn và có nhiều khả năng gây ra lỗi thời gian chạy hơn. Bạn có thể đọc thêm một chút về lý do tại sao phải tránh các chức năng một phần tại trang wiki này .
> [1,2,3] !! 9
*** Exception: Prelude.(!!): index too large
> [1,2,3] ^? element 9
Nothing
Bạn có thể buộc kỹ thuật thấu kính là một chức năng riêng phần và đưa ra một ngoại lệ khi vượt ra ngoài giới hạn bằng cách sử dụng (^?!)
toán tử thay vì (^?)
toán tử.
> [1,2,3] ^?! element 1
2
> [1,2,3] ^?! element 9
*** Exception: (^?!): empty Fold
Tuy nhiên, điều này không chỉ giới hạn trong danh sách. Ví dụ, kỹ thuật tương tự hoạt động trên cây từ gói container tiêu chuẩn .
> 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
Bây giờ chúng ta có thể truy cập các phần tử của cây theo thứ tự sâu nhất:
> 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
Chúng tôi cũng có thể truy cập các chuỗi từ gói vùng chứa :
> import qualified Data.Sequence as Seq
> Seq.fromList [1,2,3,4] ^? element 3
Just 4
Chúng ta có thể truy cập các mảng được lập chỉ mục int tiêu chuẩn từ gói vector , văn bản từ gói văn bản tiêu chuẩn , bytestrings từ gói bytestring tiêu chuẩn và nhiều cấu trúc dữ liệu tiêu chuẩn khác. Phương pháp truy cập tiêu chuẩn này có thể được mở rộng cho các cấu trúc dữ liệu cá nhân của bạn bằng cách biến chúng thành một phiên bản của Kiểu có thể lật ngang , hãy xem danh sách dài hơn về các cấu trúc Có thể chuyển ngang mẫu trong tài liệu Ống kính. .
Việc đào sâu vào các cấu trúc lồng nhau rất đơn giản với ống kính hack . Ví dụ truy cập một phần tử trong danh sách các danh sách:
> [[1,2,3],[4,5,6]] ^? element 0 . element 1
Just 2
> [[1,2,3],[4,5,6]] ^? element 1 . element 2
Just 6
Thành phần này hoạt động ngay cả khi cấu trúc dữ liệu lồng nhau thuộc các kiểu khác nhau. Ví dụ: nếu tôi có một danh sách các cây:
> :{
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
Bạn có thể lồng sâu tùy ý với các loại tùy ý miễn là đáp ứng được Traversable
yêu cầu. Vì vậy, việc truy cập một danh sách các cây gồm các chuỗi văn bản là không tốn kém.
Một thao tác phổ biến trong nhiều ngôn ngữ là gán cho một vị trí được lập chỉ mục trong một mảng. Trong python, bạn có thể:
>>> a = [1,2,3,4,5]
>>> a[3] = 9
>>> a
[1, 2, 3, 9, 5]
Các
ống kính gói cho chức năng này với các (.~)
nhà điều hành. Mặc dù không giống như trong python, danh sách ban đầu không bị đột biến, thay vào đó, một danh sách mới được trả về.
> 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
chỉ là một chức năng và người (&)
vận hành, một phần của
ống kính gói , chỉ là ứng dụng chức năng đảo ngược. Đây là ứng dụng chức năng phổ biến hơn.
> (element 3 .~ 9) [1,2,3,4,5]
[1,2,3,9,5]
Phép gán lại hoạt động hoàn toàn tốt với việc lồng các Traversable
s tùy ý .
> [[1,2,3],[4,5,6]] & element 0 . element 1 .~ 9
[[1,9,3],[4,5,6]]
Data.Traversable
thay vì tái xuất vào lens
không?
Câu trả lời thẳng thắn đã được đưa ra: Sử dụng !!
.
Tuy nhiên, người mới thường có xu hướng lạm dụng toán tử này, toán tử này đắt tiền trong Haskell (vì bạn làm việc trên danh sách liên kết đơn, không phải trên mảng). Có một số kỹ thuật hữu ích để tránh điều này, cách dễ nhất là sử dụng zip. Nếu bạn viết zip ["foo","bar","baz"] [0..]
, bạn sẽ nhận được một danh sách mới với các chỉ số được "đính kèm" cho mỗi phần tử trong một cặp : [("foo",0),("bar",1),("baz",2)]
, thường chính xác là những gì bạn cần.
Kiểu dữ liệu danh sách chuẩn của Haskell forall t. [t]
triển khai gần giống với danh sách liên kết C chuẩn và chia sẻ các thuộc tính cơ bản của nó. Danh sách được liên kết rất khác với mảng. Đáng chú ý nhất, truy cập theo chỉ mục là một O (n) tuyến tính-, thay vì một O (1) hoạt động thời gian không đổi.
Nếu bạn yêu cầu truy cập ngẫu nhiên thường xuyên, hãy xem xét Data.Array
tiêu chuẩn.
!!
là một chức năng được xác định một phần không an toàn, gây ra sự cố cho các chỉ số nằm ngoài phạm vi. Hãy nhận biết rằng các thư viện chuẩn chứa một số chức năng một phần như vậy ( head
, last
, vv). Để đảm bảo an toàn, hãy sử dụng một loại tùy chọn Maybe
hoặcSafe
mô-đun.
Ví dụ về hàm lập chỉ mục tổng hiệu quả, mạnh mẽ (đối với chỉ số ≥ 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
Làm việc với danh sách được liên kết, thứ tự thường rất thuận tiện:
nth :: Int -> [a] -> Maybe a
nth _ [] = Nothing
nth 1 (x : _) = Just x
nth n (_ : xs) = nth (n - 1) xs
[1,2,3]!!6
sẽ cung cấp cho bạn một lỗi thời gian chạy. Nó có thể rất dễ dàng tránh được nếu!!
có loại[a] -> Int -> Maybe a
. Lý do chính chúng tôi có Haskell là để tránh các lỗi thời gian chạy như vậy!