如何从列表中获取第 n 个元素?

我如何在哈斯克尔通过索引访问一个类似于这个 c 代码的列表?

int a[] = { 34, 45, 56 };
return a[1];
216014 次浏览

给你,使用的操作符是 !!

也就是说,[1,2,3]!!1给你 2,因为列表是0索引的。

我并不是说你的问题或者给出的答案有什么问题,但是也许你想知道 胡格尔这个奇妙的工具,它可以在将来节省你的时间: 通过 Hoogle,你可以搜索与给定的签名相匹配的标准库函数。因此,对于 !!一无所知的情况下,您可能会搜索“接受一个 Int和一个列表并返回一个这样的列表的东西”,即

Int -> [a] -> a

Lo 和 请看,以 !!作为第一个结果(尽管与我们搜索的结果相比,类型签名实际上具有相反的两个参数)。不错吧?

此外,如果您的代码依赖于索引(而不是从列表的前端使用) ,那么列表实际上可能不是合适的数据结构。对于基于 O (1)索引的访问,有更有效的替代方案,如 数组向量

直接的答案已经给出了: 使用 !!

然而,新手往往会过度使用这个操作符,这在哈斯克尔很昂贵(因为你使用的是单链表,而不是数组)。有几种有用的技术可以避免这种情况,最简单的一种是使用 zip。如果编写 zip ["foo","bar","baz"] [0..],您将得到一个新的列表,其中的索引“附加”到一对中的每个元素: [("foo",0),("bar",1),("baz",2)],这通常正是您所需要的。

使用 (!!)的另一种方法是使用 镜头 包及其 element函数和相关的操作符 Len 提供了一个统一的界面,用于访问列表之上和之外的各种结构和嵌套结构。下面我将重点介绍一些例子,并对类型签名和背后的理论进行修饰 镜头 包装。如果您想了解更多关于这个理论的信息,最好从 Github 回购的自述文件开始。

访问列表和其他数据类型

Getting access to the lens package

在命令行:

$ 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

Unlike the (!!) this will not throw an exception when accessing an element out of bounds and will return Nothing instead. It is often recommend to avoid partial functions like (!!) or head since they have more corner cases and are more likely to cause a run time error. You can read a little more about why to avoid partial functions at 这个维基页面.

> [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

We can now access the elements of the tree in depth-first order:

> 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

方法访问标准的整型索引数组 矢量 包,标准文本 Text package,字节串来自标准 Bytestring 包和许多其他标准数据结构。这种标准的访问方法可以扩展到您的个人数据结构,方法是使它们成为类型类 可以坐车的一个实例,参见更长的示例 Traversables in the Lens documentation.列表。


嵌套结构

Digging down into nested structures is simple with the lens 黑客攻击. For example accessing an element in a list of lists:

> [[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要求,就可以使用任意类型任意深度嵌套。因此,访问文本序列的树列表不费吹灰之力。


Changing the nth element

A common operation in many languages is to assign to an indexed position in an array. In python you might:

>>> 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只是一个函数和 (&)操作符,它是 镜头 包,就是反向函数的应用程序。这里有一个比较常见的函数应用程序。

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

对于 Traversable的任意嵌套,赋值也可以很好地工作。

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

你可以使用 !!,但是如果你想用递归的方式来做,那么下面是一种方法:

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标准。

!!是一个不安全的部分定义函数,引发了超出范围指数的崩溃。请注意,标准库包含一些这样的部分函数(headlast等)。为安全起见,请使用选项类型 MaybeSafe模块。

一个相当有效的、健壮的总索引函数示例(对于指数≥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

Working with linked lists, often ordinals are convenient:

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

我知道这是一个老职位... 但它可能是有用的人..。 以“ 功能强大”的方式..。

import Data.List


safeIndex :: [a] -> Int -> Maybe a
safeIndex xs i
| (i> -1) && (length xs > i) = Just (xs!!i)
| otherwise = Nothing

“也许”是一个合理的方法。

只是提出一个替代方案,您需要获得一个可以事先确定的默认值。

atDefault :: a -> Integer -> [a] -> a
atDefault aDef _ [] = aDef                        -- case: is empty anyway
atDefault _ 0  (a:_) = a                          -- case: index is 0 -> take it
atDefault aDef nIndex  (a:la)
| nIndex > 0 = atDefault aDef (nIndex - 1) la -- case: index is positive
| otherwise  = aDef                           -- case: index is negative

A use case might be the following:

您希望通过使用八进制字节列表实现一组无穷尽的数字。假设列表总是有一组给定的 n 个元素,假设 n 是 > 0-但是元素也可能被访问,这时你通常认为它超出了索引范围(index > = n 或 index < 0)-但是你没有访问。相反,您将提供0作为默认值。

例如:

module Main where


import qualified Data.Word as W
import qualified Data.Bits as Bts
import Data.Bits ((.|.))
import qualified Data.List as L


main :: IO ()
main = do
print $ atDefault 0x00 (-1) myOctet
print $ atDefault 0x00 0 myOctet
print $ atDefault 0x00 1 myOctet
print $ atDefault 0x00 2 myOctet
print $ atDefault 0x00 3 myOctet
print $ atDefault 0x00 4 myOctet


myOctet = toOctets (0xA4B3C2D1 :: W.Word32)


atDefault :: a -> Integer -> [a] -> a
atDefault aDef _ [] = aDef                        -- case: is empty anyway
atDefault _ 0  (a:_) = a                          -- case: index is 0 -> take it
atDefault aDef nIndex  (a:la)
| nIndex > 0 = atDefault aDef (nIndex - 1) la -- case: index is positive
| otherwise  = aDef                           -- case: index is negative


class Octetable w where
toOctets :: w -> [W.Word8]


instance Octetable W.Word32 where
toOctets w32 =
[ fromIntegral (w32 `Bts.shiftR` 24)
, fromIntegral (w32 `Bts.shiftR` 16)
, fromIntegral (w32 `Bts.shiftR` 8)
, fromIntegral w32
]

输出

0
164
179
194
209
0

还有一种选择是使用 genericIndex :: Integral i => [a] -> i -> a函数,它在索引类型参数方面的限制比 (! !) : [ a ]-> Int-> a少。在某些情况下会很方便。

下面的函数看起来很理想,因为它不是部分函数(不像 !!) ,而是由现有库(relude)实现的:

(!!?) :: [a] -> Int -> Maybe a

https://hackage.haskell.org/package/relude-1.0.0.1/docs/Relude-List.html#v:-33--33--63-

Prelude> import Relude.List
Prelude Relude.List> ['a'..'f'] !!? 0
Just 'a'
Prelude Relude.List> ['a'..'f'] !!? 100
Nothing