Answers:
我并不是说您的问题或给出的答案有什么问题,但也许您想了解一下Hoogle这个很棒的工具,它可以在将来节省您的时间:使用Hoogle,您可以搜索标准库函数与给定签名相匹配。因此,!!
如果您不了解,则可能会搜索“带有Int
和的清单,并返回一个诸如此类的清单的清单”,即
Int -> [a] -> a
Lo和看哪,与!!
作为第一个结果(虽然类型签名实际上有相反的两个参数相比,我们搜索的内容)。整洁吧?
另外,如果您的代码依赖于索引(而不是从列表的开头使用),则列表实际上可能不是正确的数据结构。对于基于索引的O(1)访问,有更有效的替代方法,例如数组或向量。
使用的替代方法(!!)
是使用
镜头包装及其element
功能和相关的操作员。该
透镜提供了用于访问各种超出列表结构和嵌套结构的一个统一的接口。在下文中,我将重点提供示例,并对镜头包装的类型签名和理论进行介绍
。如果您想了解更多有关该理论的知识,那么不错的起点是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
使用infix运算符访问列表
> [1,2,3,4,5] ^? element 2 -- 0 based indexing
Just 3
与(!!)
this 不同,在超出范围访问元素时不会抛出异常,Nothing
而是返回。通常建议避免使用诸如(!!)
或之类的部分函数,head
因为它们具有更多的极端情况,并且更有可能导致运行时错误。您可以在此Wiki页面上阅读更多有关为什么要避免使用部分功能的信息。
> [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
我们可以从vector包访问标准的int索引数组,从standard 文本包访问 文本,从 标准 bytestring包访问字节串,以及许多其他标准数据结构。通过使它们成为类型类Taversable的实例,可以将这种标准的访问方法扩展到您的个人数据结构,请参见Lens文档中的示例Traversables的更长列表。。
用镜头劈开挖掘嵌套结构很简单。例如,访问列表列表中的元素:
> [[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
吗?
直接答案已经给出:Use !!
。
但是,新手通常会过度使用该运算符,这在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的根本原因是为了避免此类运行时错误!