用Haskell编写以下程序的更好方法
我正在写一个函数来减少自由词。可以考虑如下算法: 这个想法是取消列表中的项目,如果它们彼此为负数且相邻。重复应用它,直到没有更多的可取消。例如 [-2,1,-1,2,3]->[-2,2,3]->[3] 我编写了以下代码。看起来不太优雅。它多次使用head、tail,这个函数的输入总共有3个模式,如果可以减少到2个,那就更好了。 我想知道是否有更优雅的方法用Haskell来写。我想我可以用折叠来做这个,但我不知道如何自然地做用Haskell编写以下程序的更好方法,haskell,Haskell,我正在写一个函数来减少自由词。可以考虑如下算法: 这个想法是取消列表中的项目,如果它们彼此为负数且相邻。重复应用它,直到没有更多的可取消。例如 [-2,1,-1,2,3]->[-2,2,3]->[3] 我编写了以下代码。看起来不太优雅。它多次使用head、tail,这个函数的输入总共有3个模式,如果可以减少到2个,那就更好了。 我想知道是否有更优雅的方法用Haskell来写。我想我可以用折叠来做这个,但我不知道如何自然地做 freeReduce [] = [] freeReduce [x] =
freeReduce [] = []
freeReduce [x] = [x]
freeReduce (x:xs)
| x == -(head xs) = freeReduce (tail xs)
| otherwise = if' (rest == [])
[x]
(if' (x == - (head rest)) (tail rest) (x:rest))
where rest = freeReduce xs
下面的代码是否足够
freeReduce[] = []
freeReduce(x:xs)
| rest == [] = [x]
| x == -(head rest) = (tail rest)
| otherwise = (x:rest)
where rest = freeReduce xs
这个想法是,rest
总是尽可能地减少,因此唯一的方法是,在rest
之前有一个x
,结果是rest
的头部取消,剩下rest
的尾部
编辑:添加了一行来处理空的
rest
这里有一行,我希望它能涵盖所有情况,因为我没有对它进行过太多测试
freeReduce = foldr (\i a -> if a /= [] && i == -(head a) then tail a else i:a ) []
这是我能说得最清楚的:
freeReduce [] = []
freeReduce (x : xs) = case freeReduce xs of
y : ys | y == -x -> ys
ys -> x : ys
或相当于:
freeReduce = foldr f []
where f x (y : ys) | y == -x = ys
f x ys = x : ys
(均未经测试。)
似乎freeduce
天生就是严格的
(我最初的错误尝试:
freeReduce (x : y : rest) | x == -y = freeReduce rest
freeReduce (x : rest) = x : freeReduce rest
freeReduce [] = []
(未测试。)您需要在当前检查点之前和之后访问元素,因此如下所示:
freeReduce :: (Num a) => [a] -> [a]
freeReduce = red []
where red xs [] = reverse xs
red (x:xs) (y:ys) | x == -y = red xs ys
red xs (y:ys) = red (y:xs) ys
freeReduce (x1 : x2 : xs) = ....
freeReduce [x] = [x]
freeReduce [] = []
您将元素从第二个列表移动到第一个列表,并且只比较这些列表的顶部。因此,这是对列表的一次扫描,然后在最后将其反转回来。这看起来像是家庭作业,所以我只给出一个提示 您需要比较列表中的前两项,但也允许列表中只有一个元素或没有元素,因此您的案例如下所示:
freeReduce :: (Num a) => [a] -> [a]
freeReduce = red []
where red xs [] = reverse xs
red (x:xs) (y:ys) | x == -y = red xs ys
red xs (y:ys) = red (y:xs) ys
freeReduce (x1 : x2 : xs) = ....
freeReduce [x] = [x]
freeReduce [] = []
这涵盖了所有情况。因此,现在您只需要决定如何处理相邻的项目x1和x2,以及如何将其输入到剩余的计算中。收集向后列表中已检查的元素,并在找到匹配项时“后退一步”:
freeReduce xs = reduce [] xs where
reduce acc [] = reverse acc
reduce [] (x:y:ys) | x == -y = reduce [] ys
reduce (a:as) (x:y:ys) | x == -y = reduce as (a:ys)
reduce acc (x:xs) = reduce (x:acc) xs
您可以将其拆分为两个单独的函数,一个只检查列表的前两个元素是否相互抵消,另一个则使用该函数来减少整个列表
-- check if the first two elements cancel each other
headReduce (x:y:zs) | x == -y = zs
headReduce xs = xs
-- build a whole reduced list from that
freeReduce [] = []
freeReduce (x:xs) = headReduce (x : freeReduce xs)
这是因为如果一个列表被完全缩减,并且您在前面添加了另一个元素,唯一新的可能缩减是前两个元素现在相互抵消。然后每次归纳时,
freeduce
的结果总是完全减少。此外,您假设rest
不是[]
;如果是这样的话,你就会犯错误。我喜欢这种方法。然而,freeReduce[-2,1,-1,2,3]
产生了[-2,2,3]
(刚刚测试过),这不是期望的结果。很好。我修正了我的答案。我喜欢你的第二个定义。函数f
有一个明确定义的含义:给定一个字母x
和一个缩略词xs
,它返回x
和xs
的乘积的缩略词。我会将f
命名为类似于consReduce
的名称。我希望能为这个简单、优雅的解决方案再加一个+1。看起来与@dave440的foldr
解决方案的想法相同。很好。我想-把一个列表当作一个堆栈,在列表上推新的数字,当它们取消时弹出。如果将变量xs
替换为stack
,将x
替换为peek
,则解决方案也可以被视为堆栈。