我有这段代码将返回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
答案 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
之后,我们可以使用内置函数fromMaybe
将Nothing
的大小写转换为-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
(即Nothing
或Int
)使用毕竟不是那么特别的“特殊”值的原因-它仍然是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]