如何在Haskell中按索引访问列表,与此C代码类似?
int a[] = { 34, 45, 56 };
return a[1];
答案 0 :(得分:133)
查看here,运营商!!
。
即。 [1,2,3]!!1
为您提供2
,因为列表是0索引的。
答案 1 :(得分:80)
我不是说你的问题或给出的答案有任何问题,但也许你想知道Hoogle这个节省时间的好工具:使用Hoogle,您可以搜索与给定签名匹配的标准库函数。所以,对!!
一无所知,在你的情况下,你可能会搜索“需要Int
的东西和一个whatevers列表并返回一个这样的东西”,即
Int -> [a] -> a
Lo和behold,!!
作为第一个结果(虽然类型签名实际上有两个参数反向与我们搜索的相比)。干净,是吗?
此外,如果您的代码依赖于索引(而不是从列表的前面消费),那么列表实际上可能不是正确的数据结构。对于基于O(1)索引的访问,有更有效的替代方案,例如arrays或vectors。
答案 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
要求,您就可以任意类型地任意嵌套。因此,访问文本序列树的列表并不容易。
许多语言的常见操作是分配给数组中的索引位置。在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
标准。
!!
是一个不安全的部分定义函数,导致超出范围的索引崩溃。请注意,标准库包含一些此类部分函数(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
答案 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