用Haskell编写以下程序的更好方法

用Haskell编写以下程序的更好方法,haskell,Haskell,我正在写一个函数来减少自由词。可以考虑如下算法: 这个想法是取消列表中的项目,如果它们彼此为负数且相邻。重复应用它,直到没有更多的可取消。例如 [-2,1,-1,2,3]->[-2,2,3]->[3] 我编写了以下代码。看起来不太优雅。它多次使用head、tail,这个函数的输入总共有3个模式,如果可以减少到2个,那就更好了。 我想知道是否有更优雅的方法用Haskell来写。我想我可以用折叠来做这个,但我不知道如何自然地做 freeReduce [] = [] freeReduce [x] =

我正在写一个函数来减少自由词。可以考虑如下算法:

这个想法是取消列表中的项目,如果它们彼此为负数且相邻。重复应用它,直到没有更多的可取消。例如 [-2,1,-1,2,3]->[-2,2,3]->[3]

我编写了以下代码。看起来不太优雅。它多次使用head、tail,这个函数的输入总共有3个模式,如果可以减少到2个,那就更好了。 我想知道是否有更优雅的方法用Haskell来写。我想我可以用折叠来做这个,但我不知道如何自然地做

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 == -(hea­d 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
,则解决方案也可以被视为堆栈。