递归Haskell函数确定元素在列表中的位置

时间:2019-02-02 23:18:03

标签: haskell recursion

我有这段代码将返回char数组中char的索引,但是如果值不在数组中,我希望我的函数返回类似-1的值。照原样,如果元素不在数组中,函数将返回数组的大小。关于如何更改代码以应用此功能的任何想法?

我正在尝试不使用任何高级功能来执行此操作。我只想要没有内置功能的简单代码。

cmd.exe

例如:

export

但是我想要

isPartOf :: [(Char)] -> (Char) -> Int
isPartOf [] a = 0
isPartOf (a:b) c
    | a == c = 0
    | otherwise = 1 + isPartOf b c

4 个答案:

答案 0 :(得分:3)

让我们尝试定义这样的函数,但是如果元素不在列表中,则不返回-1,而是返回Nothing

isPartOf :: Eq a => [a] -> a -> Maybe Int
isPartOf [] _ = Nothing
isPartOf (x : xs) a | x == a = Just 0
                     | otherwise = fmap ((+) 1) (isPartOf xs a)

因此,它的工作原理如下:

>> isPartOf [('a'),('b'),('c')] ('z')
Nothing
it :: Maybe Int

>> isPartOf [('a'),('b'),('c')] ('c')
Just 2
it :: Maybe Int

之后,我们可以使用内置函数fromMaybeNothing的大小写转换为-1

>> fromMaybe (-1) $ isPartOf [('a'),('b'),('c')] ('c')
2
it :: Int

>> fromMaybe (-1) $ isPartOf [('a'),('b'),('c')] ('z')
-1
it :: Int

如果您好奇是否已存在这样的功能,可以为此使用Hoogle,搜索[a] -> a -> Maybe Int函数:https://www.haskell.org/hoogle/?hoogle=%5Ba%5D+-%3E+a+-%3E+Maybe+Int

第一个答案将是elemIndex

>> elemIndex 'c' [('a'),('b'),('c')]
Just 2
it :: Maybe Int

>> elemIndex 'z' [('a'),('b'),('c')]
Nothing
it :: Maybe Int

希望这会有所帮助。

答案 1 :(得分:3)

实现此目标的最小更改是

isPartOf :: [Char] -> Char -> Int
isPartOf [] a = (-1)    -- was: 0
isPartOf (a:b) c
    | a == c = 0
    | otherwise = 1 +   -- was: isPartOf b c
          if  (isPartOf b c) < 0  then  (-2)  else  (isPartOf b c)

这在计算上是很糟糕的。它两次重新计算相同的值;更糟糕的是,计算是通过递归调用完成的,因此递归调用将进行两次,并且总体时间复杂度将从线性变为指数!

我们不要那样做。而且,Char有什么特别之处? Char有很多特别之处,但这里没有使用,除了比较(==)

可以通过相等性比较其值的类型称为属于Eq(对于“相等性”)类型类:Eq a => a的类型。 a是一个类型变量,可以假定任何类型;但是在这里,必须这样……是的,属于Eq类型的类。

所以我们写

isPartOf :: Eq a => [a] -> a -> Int
isPartOf [] a = (-1)
isPartOf (a:b) c
    | a == c    = 0
    | otherwise = let d = isPartOf b c in
                  1 + if  d < 0  then  (-2)  else  d

(-2)看起来非常特别!使用 guards 的更紧凑,更惯用的版本也将使我们能够解决以下问题:

isPartOf :: Eq a => [a] -> a -> Int
isPartOf [] a = (-1)
isPartOf (a:b) c
    | a == c    = 0
    | d < 0     = d 
    | otherwise = 1 + d
          where 
          d = isPartOf b c

是的,我们可以在d子句中定义where,并将其用在我们的警卫人员以及每个子句的主体中。由于懒惰,如果不需要它的值,甚至像第一个子句一样,它甚至都不会被计算一次。

现在此代码可以通过了。

Maybe数据类型的Functor接口/实例捕获条件传递和转换:

fmap f Nothing  = Nothing     -- is not changed
fmap f (Just x) = Just (f x)  -- is changed

the other answer here正在使用的。但是当我们仅开始学习Haskell时,它可以被视为“幻想”。

当您编写了更多类似的功能,并一遍又一遍地手动重复相同的模式而变得“烦恼”时,您将不胜感激并希望使用它。但是只有然后

另一个需要注意的是,我们的代码从递归的基本案例 back 方式计算其结果。

但是它可以代替对它计算on the way forward,因此当找到匹配字符时,它可以立即返回它。如果找到了列表结尾,则丢弃到目前为止计算出的结果,并返回(-1)。这是the second answer采取的方法。

尽管创建其他函数会乱码全局名称空间。通常是通过内部定义 来实现的,即所谓的“ worker / wrapper”转换:

isPartOf :: Eq a => [a] -> a -> Int
isPartOf xs c = go xs 0
   where
   go [] i = (-1)
   go (a:b) i
    | a == c    = i
    | otherwise = -- go b (1 + i)
                  go b $! (1 + i)

额外的好处是,我们不需要传递不变的值c -从内部“工作者”函数go的角度来看,它可以在外部范围中使用,由我们的功能isPartOf“包装”,并且只能由我们的功能$!访问。

i是一种特殊的调用运算符,可确保立即计算其参数值且不会延迟。这样可以消除不必要的(在这种情况下)懒惰,并进一步提高了代码效率。

但是从设计的整体清洁度的角度来看,最好返回包装在Maybe中的索引Just i(即NothingInt)使用毕竟不是那么特别的“特殊”值的原因-它仍然是Maybe Int

最好让类型反映我们的意图,isPartOf :: Eq a => [a] -> a -> Maybe Int isPartOf ..... ....... ....... Nothing ..... ....... ....... Just i ..... ....... 可以清楚而干净地表达它,因此我们不必记住哪些值是特殊的,哪些是常规的,因此,该知识不是我们程序文本的外部,而是它固有的。

这是一个小巧而容易的更改,结合了前两个版本的最佳部分:

{{1}}

(没有代码经过测试。如果有错误,欢迎您查找并纠正它们,并通过测试进行验证)。

答案 2 :(得分:2)

如果仅将当前元素idx传递到下一个递归,则可以轻松实现:

isPartOf :: [Char] -> Char -> Int
isPartOf lst c = isPartOf' lst c 0

isPartOf' :: [Char] -> Char -> Int -> Int
isPartOf' [] a _ = -1
isPartOf' (a:b) c idx
    | a == c = idx
    | otherwise = isPartOf' b c (idx + 1)

答案 3 :(得分:0)

您正在使用函数作为累加器。这很酷,除了带有负数的添加项。累加器不能从累加转换为负数1.您需要函数累加器提供两种不同的东西。您可以将计数器用于一件事情,如果由于找不到匹配项而发出不必要的1且没有任何损失,则不需要进行计数。计数将是另一个参数。啊。您可以使用Maybe,但这很复杂。像上面的两个功能更简单。这是两个功能。第一个是您的,但是累加器不是可累加的,而是串联的。

cIn (x:xs) c | x == c    =  [1]
             | null xs   = [-1]
             | otherwise = 1:cIn xs c


Cin ['a','b','c'] 'c'

[1,1,1]

cIn ['a','b','c'] 'x'

[1,1,-1]

第二个功能是

f ls = if last ls == 1 then sum ls else -1 

它将

f $ Cin ['a','b','c'] 'c'

3

f $ Cin ['a','b','c'] 'x'

-1

您可以通过将[1]更改为[0]

将索引库归零