注意:我试图在不使用库函数的情况下实现这一点
一般来说,这不是一个好主意。一个更好的练习是这样的:
- 弄清楚如何使用库函数来实现它。
- 弄清楚如何自己实现您在步骤 1 中使用的任何库函数。
通过这种方式,您将学习三个关键技能:
- 什么是标准库函数,以及它们何时有用的示例。
- 如何将问题分解成更小的部分
- 如何编写库中的基本功能。
但是,在这种情况下,您的函数与中whatIndex
的函数或多或少相同,因此您的问题归结为编写您自己的此库函数版本。elemIndex
Data.List
这里的诀窍是你想在递归列表时增加一个计数器。编写尾递归函数有一种标准技术,称为累积参数。它是这样工作的:
- 你编写了一个辅助函数,与“前端”函数相比,它需要一个额外的参数(或更多)来跟踪额外的信息。
- 然后,您将“真实”函数定义为对辅助函数的调用。
因此对于elemIndex
,辅助函数将是这样的(i
作为当前元素索引的累积参数):
-- I'll leave the blanks for you to fill.
elemIndex' i x [] = ...
elemIndex' i x (x':xs) = ...
那么“驱动程序”函数是这样的:
elemIndex x xs = elemIndex 0 x xs
但是这里我必须提到一个严重的问题:让这个函数在 Haskell 中表现良好是很棘手的。尾递归在严格(非惰性)函数式语言中是一个有用的技巧,但在 Haskell 中则不然,因为:
- Haskell 中的尾递归函数仍然可以破坏堆栈,
- 非尾递归函数可以在恒定空间中运行。
我的这个较旧的答案显示了第二点的一个例子。
因此,在您的情况下,非尾递归解决方案可能是您可以提供的最简单的解决方案,它将在恒定空间中运行(即,不要在长列表中破坏堆栈):
elemIndex x xs = elemIndex' x (zip xs [0..])
elemIndex' x pairs = snd (find (\(x', i) -> x == x') pairs)
-- | Combine two lists by pairing together their first elements, their second
-- elements, etc., until one of the lists runs out.
--
-- EXERCISE: write this function on your own!
zip :: [a] -> [b] -> [(a, b)]
zip xs ys = ...
-- | Return the first element x of xs such that pred x == True. Returns Nothing if
-- there isn't one, Just x if there is one.
--
-- EXERCISE: write this function on your own!
find :: (a -> Bool) -> [a] -> Maybe a
find pred xs = ...