如何通过不使用“列表推导”来获取列表中元素的索引?

时间:2019-05-10 14:14:11

标签: list haskell recursion indexing list-comprehension

我是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]

我的错误在哪里?

3 个答案:

答案 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)

顺便说一句,您不需要显式递归来避免此处的列表理解。一方面,列表理解基本上是mapfilter的使用的替代。我本来要明确地写出来,但是我看到@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