Відповіді:
Подивіться, тут використовується оператор !!
.
Тобто [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
і список Whatevers і повертає єдине таке", а саме
Int -> [a] -> a
Ось і ось , !!
як перший результат (хоча підпис типу насправді має два аргументи зворотно порівняно з тим, що ми шукали). Акуратно, так?
Крім того, якщо ваш код покладається на індексацію (замість того, щоб споживати з передньої частини списку), списки можуть насправді не бути належною структурою даних. Для доступу на основі індексу O (1) існують більш ефективні альтернативи, такі як масиви або вектори .
Альтернативою для використання (!!)
є використання
пакету об'єктивів та його element
функції та пов'язаних з ними операторів.
Об'єктив забезпечує єдиний інтерфейс для доступу широкого спектру структур і вкладених структур вище і поза списками. Нижче я зупинюсь на наданні прикладів, а також змалюю як підписи типу, так і теорію, що стоїть за
пакетом об'єктива . Якщо ви хочете дізнатися більше про теорію, гарне місце для початку - це файл readme у рефіта 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 індексованих масивів з векторного пакету, тексту зі стандартного текстового пакета, відвідників від стандартного пакету бітестрінгу та багатьох інших стандартних структур даних. Цей стандартний метод доступу може бути розширений до ваших структур персональних даних, зробивши їх екземпляром класу типу " Обертовий" , див. Більш довгий список прикладів " Перехідні" в документації на об'єктив. .
Закопувати в гніздові структури досить просто, якщо зламати об'єктив . Наприклад, доступ до елемента в списку списків:
> [[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. Якщо ви пишете zip ["foo","bar","baz"] [0..]
, ви отримуєте новий список з індексами, "прикріпленими" до кожного елемента в парі:, [("foo",0),("bar",1),("baz",2)]
що часто саме те, що вам потрібно.
Стандартний тип даних 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 - це уникати таких помилок виконання!