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

时间:2011-03-07 07:52:56

标签: haskell

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

int a[] = { 34, 45, 56 };
return a[1];

10 个答案:

答案 0 :(得分:133)

查看here,运营商!!

即。 [1,2,3]!!1为您提供2,因为列表是0索引的。

答案 1 :(得分:80)

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

Int -> [a] -> a

Lo和behold!!作为第一个结果(虽然类型签名实际上有两个参数反向与我们搜索的相比)。干净,是吗?

此外,如果您的代码依赖于索引(而不是从列表的前面消费),那么列表实际上可能不是正确的数据结构。对于基于O(1)索引的访问,有更有效的替代方案,例如arraysvectors

答案 2 :(得分:57)

使用(!!)的替代方法是使用 lens包及其element函数和关联的运算符。该 lens提供了一个统一的接口,用于访问列表上方和下方的各种结构和嵌套结构。下面我将重点介绍示例,并将掩盖类型签名和背后的理论 lens包。如果您想了解更多关于理论的知识,可以从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等部分功能,因为它们有更多的边角情况,更有可能导致运行时错误。您可以在this wiki page处阅读更多有关为何避免部分功能的内容。

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


使用列表以外的类型

然而,这不仅限于列表。例如,相同的技术适用于标准trees 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

我们也可以从中访问sequences containers包裹:

> import qualified Data.Sequence as Seq
> Seq.fromList [1,2,3,4] ^? element 3
Just 4

我们可以从中访问标准的int索引数组 vector包,标准中的文字 text包,标准的字节串 bytestring包和许多其他标准数据结构。这种标准访问方法可以通过将它们作为类型Taversable的实例扩展到您的个人数据结构,请参阅更长的示例Traversables in the Lens documentation.列表。


嵌套结构

使用镜头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要求,您就可以任意类型地任意嵌套。因此,访问文本序列树的列表并不容易。


更改第n个元素

许多语言的常见操作是分配给数组中的索引位置。在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]]

答案 3 :(得分:11)

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

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

答案 4 :(得分:4)

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

使用链接列表,通常是顺序:

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

答案 5 :(得分:3)

您可以使用!!,但如果您想以递归方式执行此操作,则以下是一种方法:

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

答案 6 :(得分:0)

我知道这是一个旧帖子...但是对某人可能有用... 以“ 功能 ”的方式...

import Data.List

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

答案 7 :(得分:0)

“可能的方式”是一种合理的方法。

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

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

用例可能如下:

您想通过使用八位字节列表来表示无穷无尽的数字。假设列表总是有一组给定的 n 个元素,假设 n > 0 - 但也可以访问元素,你通常认为它超出索引范围(索引 >= n 或索引 < 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

答案 8 :(得分:0)

另一种选择是使用 genericIndex :: Integral i => [a] -> i -> a 函数,它对索引类型参数的限制比 (!!) :: [a] -> Int -> a 少。在某些情况下可以派上用场。

答案 9 :(得分:0)

以下函数看起来很理想,因为它不是部分函数(与 !! 不同),并且由现有库(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