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

Eon*_*nil 89 haskell

如何在Haskell中按索引访问列表,与此C代码类似?

int a[] = { 34, 45, 56 };
return a[1];
Run Code Online (Sandbox Code Playgroud)

phi*_*mue 142

看看这里,运营商!!.

[1,2,3]!!1给你2,因为列表是0索引.

  • 就个人而言,我无法理解不返回Maybe类型的at-index访问器如何被接受作为惯用的Haskell.`[1,2,3] !! 6`会给你一个运行时错误.如果`!!`的类型为`[a] - > Int - > Maybe a`,那么很容易避免.我们拥有Haskell的原因是为了避免这样的运行时错误! (78认同)
  • 这是一个权衡.他们选择的符号可能是他们可能拥有的最令人震惊的象征.所以我认为这个想法是允许它用于边缘情况,但是让它脱颖而出是非惯用的. (8认同)
  • ```itemOf :: Int - > [a] - >也许是; x`itemOf`xs =让xslen =长度xs in if((abs x)> xslen)然后没有别的Just(xs !!(x`mod`xslen))```.请注意,这将在无限列表上灾难性地失败. (3认同)
  • !!是部分功能,因此是不安全的。看看下面的评论并使用`lens` /sf/answers/1653934201/ (2认同)

gsp*_*spr 85

我并不是说你的问题或给出的答案有任何问题,但也许你想知道Hoogle为将来节省时间的好工具:使用Hoogle,你可以搜索标准库函数匹配给定签名.所以,不知道任何事情!!,在你的情况下,你可能会搜索"需要一个Int和一个whatevers列表的东西,并返回一个这样的东西",即

Int -> [a] -> a
Run Code Online (Sandbox Code Playgroud)

Lo和注视,与!!作为第一个结果(虽然类型签名实际上有相反的两个参数相比,我们搜索的内容).整洁,对吧?

此外,如果您的代码依赖于索引(而不是从列表的前面消耗),则列表实际上可能不是正确的数据结构.对于基于O(1)索引的访问,存在更有效的替代方案,例如数组向量.

  • Hoogle绝对棒极了.每个Haskell程序员都应该知道它.有一种叫做Hayoo的替代品(http://holumbus.fh-wedel.de/hayoo/hayoo.html).它会在您键入时进行搜索,但似乎并不像Hoogle那样聪明. (4认同)

Dav*_*rak 58

使用的另一种方法(!!)是使用 镜头包及其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
Run Code Online (Sandbox Code Playgroud)


访问列表

使用中缀运算符访问列表

> [1,2,3,4,5] ^? element 2  -- 0 based indexing
Just 3
Run Code Online (Sandbox Code Playgroud)

(!!)此不同的是,当访问一个超出边界的元素时不会抛出异常,Nothing而是会返回.通常建议避免使用部分函数(!!),head因为它们有更多的极端情况并且更有可能导致运行时错误.您可以在此Wiki页面上阅读更多关于为什么要避免部分功能的内容.

> [1,2,3] !! 9
*** Exception: Prelude.(!!): index too large

> [1,2,3] ^? element 9
Nothing
Run Code Online (Sandbox Code Playgroud)

您可以强制镜头技术成为部分函数,​​并在使用(^?!)运算符而不是(^?)运算符时超出范围时抛出异常.

> [1,2,3] ^?! element 1
2
> [1,2,3] ^?! element 9
*** Exception: (^?!): empty Fold
Run Code Online (Sandbox Code Playgroud)


使用列表以外的类型

然而,这不仅限于列表.例如,相同的技术适用于标准 容器包中的.

 > 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
Run Code Online (Sandbox Code Playgroud)

我们现在可以按深度优先顺序访问树的元素:

> 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
Run Code Online (Sandbox Code Playgroud)

我们还可以从 容器包中访问序列:

> import qualified Data.Sequence as Seq
> Seq.fromList [1,2,3,4] ^? element 3
Just 4
Run Code Online (Sandbox Code Playgroud)

我们可以从vector包,标准文本包中的 文本,标准bytestring包的 bytestrings以及许多其他标准数据结构访问标准的int索引数组 .这种标准访问方法可以通过将它们作为类型Taversable的实例扩展到您的个人数据结构,请参阅Lens文档中较长的示例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
Run Code Online (Sandbox Code Playgroud)

即使嵌套数据结构具有不同类型,此组合也可以工作.例如,如果我有一个树列表:

> :{
 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
Run Code Online (Sandbox Code Playgroud)

只要符合Traversable要求,您可以任意类型任意嵌套.因此,访问文本序列树的列表并不容易.


更改第n个元素

许多语言中的常见操作是分配给数组中的索引位置.在python中你可能会:

>>> a = [1,2,3,4,5]
>>> a[3] = 9
>>> a
[1, 2, 3, 9, 5]
Run Code Online (Sandbox Code Playgroud)

镜头包给出与此功能(.~)操作.虽然与python不同,原始列表不会发生变异,而是返回一个新列表.

> let a = [1,2,3,4,5]
> a & element 3 .~ 9
[1,2,3,9,5]
> a
[1,2,3,4,5]
Run Code Online (Sandbox Code Playgroud)

element 3 .~ 9只是一个功能,(&)操作员,镜头包的一部分 ,只是反向功能应用.这是更常见的功能应用程序.

> (element 3 .~ 9) [1,2,3,4,5]
[1,2,3,9,5]
Run Code Online (Sandbox Code Playgroud)

对任意嵌套Traversables 再次赋值再次完美.

> [[1,2,3],[4,5,6]] & element 0 . element 1 .~ 9
[[1,9,3],[4,5,6]]
Run Code Online (Sandbox Code Playgroud)

  • 我可以建议链接到`Data.Traversable`而不是`lens`中的重新导出? (3认同)

Lan*_*dei 11

直截了当的答案已经给出:使用!!.

然而,新手经常倾向于过度使用这个运算符,这在Haskell中很昂贵(因为你在单个链表上工作,而不是在数组上工作).有几种有用的技术可以避免这种情况,最简单的就是使用zip.如果你写zip ["foo","bar","baz"] [0..],你得到一个新的列表,其中索引"附加"到一对中的每个元素:[("foo",0),("bar",1),("baz",2)]这通常正是你所需要的.

  • 您还需要注意那里的类型。大多数情况下,您不希望索引是慢整数而不是快速机器整数。根据您的函数究竟做了什么以及您输入的明确程度,Haskell 可能会将 [0..] 的类型推断为 [Integer] 而不是 [Int]。 (2认同)

Abg*_*o80 5

您可以使用!!,但如果您想递归地使用它,那么下面是一种方法:

dataAt :: Int -> [a] -> a
dataAt _ [] = error "Empty List!"
dataAt y (x:xs)  | y <= 0 = x
                 | otherwise = dataAt (y-1) xs
Run Code Online (Sandbox Code Playgroud)