我是Haskell编程的新手,我尝试通过/不使用列表推导来解决问题。
问题是要找到列表中元素的索引,然后返回索引列表(在列表中找到元素的位置)。
我已经通过使用列表推导解决了这个问题,但是现在我有一些不使用列表推导来解决问题的问题。
以我的递归方式:
我尝试压缩[0..(length list)]
的列表以及该列表本身。
然后,如果元素a等于列表中的元素->用压缩的list(my index)
的Tupel的第一个元素创建一个新列表,然后以递归方式搜索该函数,直到列表为[]。 / p>
这是我的列表理解(有效):
positions :: Eq a => a -> [a] -> [Int]
positions a list = [x | (x,y) <- zip [0..(length list)] list, a == y]
这是我的递归方式(不起作用):
positions' :: Eq a => a -> [a] -> [Int]
positions' _ [] = []
positions' a (x:xs) =
let ((n,m):ns) = zip [0..(length (x:xs))] (x:xs)
in if (a == m) then n:(positions' a xs)
else (positions' a xs)
*抱歉,我不知道如何突出显示单词
但是ghci说:
*Main> positions' 2 [1,2,3,4,5,6,7,8,8,9,2]
[0,0]
应该是这样(我的列表理解):
*Main> positions 2 [1,2,3,4,5,6,7,8,8,9,2]
[1,10]
我的错误在哪里?
答案 0 :(得分:5)
尝试的问题只是当您说:
let ((n,m):ns) = zip [0..(length (x:xs))] (x:xs)
然后n
将始终为0
。这是因为您将(n,m)
与zip [0..(length (x:xs))] (x:xs)
的第一个元素进行匹配,第一个元素必须始终为(0,x)
。
这本身不是问题-但这确实意味着您必须正确处理递归步骤。您现在拥有它的方式,positions _ _
(如果不是空的话)将始终以0
作为其第一个元素,因为您允许它找到匹配项的唯一方法是(如果它位于列表的开头),则索引为0
。这意味着您的结果将始终是正确长度的列表,但包含所有元素0
-如您所见。
问题不在于您的递归方案,而在于您没有修改结果以说明您并不总是希望将0
添加到结果列表。由于每个递归调用只向要查找的索引加1,因此您要做的就是在递归结果上使用map
增量函数(+1)
:
positions' :: Eq a => a -> [a] -> [Int]
positions' _ [] = []
positions' a (x:xs) =
let ((0,m):ns) = zip [0..(length (x:xs))] (x:xs)
in if (a == m) then 0:(map (+1) (positions' a xs))
else (map (+1) (positions' a xs))
(请注意,我已将您的let
更改为明确的,n
将始终为0
-我更愿意以这种方式明确声明,但这本身并不会改变输出。)由于m
始终绑定到x
上,并且根本没有使用ns
,因此我们可以省略let,插入m
的定义:
positions' :: Eq a => a -> [a] -> [Int]
positions' _ [] = []
positions' a (x:xs) =
if a == x
then 0 : map (+1) (positions' a xs)
else map (+1) (positions' a xs)
如果愿意,可以继续排除重复的map (+1) (positions' a xs)
。
顺便说一句,您不需要显式递归来避免此处的列表理解。一方面,列表理解基本上是map
和filter
的使用的替代。我本来要明确地写出来,但是我看到@WillemVanOnsem给出了这个答案,所以我只想向您介绍他的答案。
另一种方式,尽管如果要求您自己实现这一点,可能不可接受,但是将仅使用内置的elemIndices函数,该函数正是您要在此处实现的功能。
答案 1 :(得分:5)
我们可以使用filter :: (a -> Bool) -> [a] -> [a]
和map :: (a -> b) -> [a] -> [b]
方法,例如:
positions :: Eq a => a -> [a] -> [Int]
positions x = map fst . filter ((x ==) . snd) . zip [0..]
因此,我们首先构造形式为(i, yi)
的元组,接下来我们进行过滤,以便仅保留x == yi
的这些元组,最后获取这些元组的第一项。
例如:
Prelude> positions 'o' "foobaraboof"
[1,2,8,9]
答案 2 :(得分:2)
您的
let ((n,m):ns) = zip [0..(length (x:xs))] (x:xs)
等同于
== {- by laziness -}
let ((n,m):ns) = zip [0..] (x:xs)
== {- by definition of zip -}
let ((n,m):ns) = (0,x) : zip [1..] xs
== {- by pattern matching -}
let {(n,m) = (0,x)
; ns = zip [1..] xs }
== {- by pattern matching -}
let { n = 0
; m = x
; ns = zip [1..] xs }
但是您从不引用ns
!因此,我们完全不需要它的绑定:
positions' a (x:xs) =
let { n = 0 ; m = x } in
if (a == m) then n : (positions' a xs)
else (positions' a xs)
因此,实际上,您拥有
positions' :: Eq a => a -> [a] -> [Int]
positions' _ [] = []
positions' a (x:xs) =
if (a == x) then 0 : (positions' a xs) -- NB: 0
else (positions' a xs)
这就是为什么您只生产0
的原因。但是您想产生正确的索引:0, 1, 2, 3, ...
。
首先,让我们进一步调整代码
positions' :: Eq a => a -> [a] -> [Int]
positions' a = go xs
where
go [] = []
go (x:xs) | a == x = 0 : go xs -- NB: 0
| otherwise = go xs
这称为工作程序/包装程序转换。 go
是一个工作程序,positions'
是一个包装程序。通话之间无需传递a
,它不会改变,而且我们仍然可以访问它。关于 inner 函数go
,它在包围范围中。我们还使用了 guards ,而不是更冗长,视觉上不太明显的if ... then ... else
。
现在,我们只需要使用一些东西-正确的索引值-而不是0。
要使用它,我们必须首先拥有它。它是什么?它从0开始,然后在输入列表的每一步中递增。
我们什么时候在输入列表中迈出一步?在递归调用中:
positions' :: Eq a => a -> [a] -> [Int]
positions' a = go xs 0
where
go [] _ = []
go (x:xs) i | a == x = 0 : go xs (i+1) -- NB: 0
| otherwise = go xs (i+1)
_
作为一种模式,意味着我们不在乎参数的值-它在那里,但是我们不会使用它。
现在剩下要做的就是使用那个i
代替那个0
。