Как мне получить доступ к списку по индексу, подобному этому C-коду с помощью списка Haskell?
int a[] = { 34, 45, 56 };
return a[1];
Как мне получить доступ к списку по индексу, подобному этому C-коду с помощью списка Haskell?
int a[] = { 34, 45, 56 };
return a[1];
Посмотрите здесь, оператор !!
.
т.е. [1,2,3]!!1
дает вам 2
, так как списки индексируются 0.
Я не говорю, что что-то не так с вашим вопросом или ответом, но, возможно, вам хотелось бы узнать о замечательном инструменте Hoogle, чтобы сэкономить время в будущем. С помощью Google Analytics вы можете искать стандартные библиотечные функции, соответствующие заданной сигнатуре. Таким образом, не зная ничего о !!
, в вашем случае вы можете искать "что-то, что принимает Int
и список whatevers и возвращает один такой, какой угодно", а именно
Int -> [a] -> a
Lo и behold, с !!
в качестве первого результата (хотя подпись типа фактически имеет два аргумента в обратном направлении по сравнению с тем, что мы искали). Аккуратно, да?
Кроме того, если ваш код основан на индексировании (вместо того, чтобы потреблять из списка), списки могут фактически не быть надлежащей структурой данных. Для O (1) доступа на основе индексов существуют более эффективные альтернативы, такие как arrays или векторы.
Альтернативой использованию (!!)
является использование
lens и его функции element
и связанных операторов.
lens обеспечивает единый интерфейс для доступа к широкому спектру структур и вложенных структур выше и выше списков. Ниже я сосредоточусь на предоставлении примеров и затушевываю как сигнатуры типов, так и теорию, лежащую в основе
lens. Если вы хотите больше узнать о теории, то хорошим местом для начала является файл readme в github repo.
В командной строке:
$ 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
Чтобы получить доступ к списку с помощью инфиксного оператора
> [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
Однако это не ограничивается списками. Например, тот же метод работает на деревьях из стандартного containers.
> 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
Мы также можем получить доступ к последовательностям из containers:
> import qualified Data.Sequence as Seq
> Seq.fromList [1,2,3,4] ^? element 3
Just 4
Мы можем получить доступ к стандартным int индексированным массивам из vector, текст из стандарта text пакет, который соответствует стандарту bytestring и многие другие стандартные структуры данных. Этот стандартный метод доступа можно расширить до ваших персональных структур данных, сделав их экземпляром класса Taversable, см. Более длинный список примеров Traversables в документации объектива..
Выкалывание в вложенные структуры просто с объективом 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]
lens предоставляет эту функциональность оператору (.~)
. Хотя в отличие от 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
является просто функцией и оператором (&)
, частью
lens, это просто приложение с обратной функцией. Здесь это с более распространенным приложением функции.
> (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]]
Ответ на straigt уже был дан: используйте !!
.
Однако новички часто склонны злоупотреблять этим оператором, что дорого стоит в Haskell (потому что вы работаете с одиночными связанными списками, а не с массивами). Существует несколько полезных методов, чтобы избежать этого, самый простой - использовать zip. Если вы пишете zip ["foo","bar","baz"] [0..]
, вы получаете новый список с индексом, "прикрепленным" к каждому элементу пары: [("foo",0),("bar",1),("baz",2)]
, что часто точно соответствует вам.
Тип данных стандартного списка Haskell forall t. [t]
в реализации очень похож на канонический С-связанный список и разделяет его по существу свойства. Связанные списки сильно отличаются от массивов. В частности, доступ по индексу является 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
Вы можете использовать !!
, но если вы хотите сделать это рекурсивно, то ниже это один из способов сделать это:
dataAt :: Int -> [a] -> a
dataAt _ [] = error "Empty List!"
dataAt y (x:xs) | y <= 0 = x
| otherwise = dataAt (y-1) xs