【问题标题】:Struggling with Haskell syntax with if's and else's使用 if 和 else 的 Haskell 语法苦苦挣扎
【发布时间】:2019-09-23 23:11:30
【问题描述】:

当我尝试使用 GHCI 编译我的脚本时,我正在努力使用 Haskell 的语法,并且收到“错误:输入‘if’时解析错误”。我的代码的目的是返回无序列表的最小值和最大值。它通过将列表分成两半并找到每一半的最小值和最大值来递归地执行此操作。如果列表只有一个元素,它只会返回该元素既是最大值又是最小值,如果列表有两个元素,它会进行简单的比较以找到最小值和最大值。这是我的代码,“splitlist”函数取自在线资源:

splitlist :: [a] -> ([a], [a])
splitlist xs = splitAt ((length xs + 1) `div` 2) xs

minMax :: [Int] -> [Int]
minMax x
    if length x == 1 then 
        [x, x]
    else if length x == 2 then 
        if (head x > tail x) then 
            [tail x, head x]
        else 
            [head x, tail x]
    else 
        listOfLists = splitlist x
        list1 = listOfLists!!0
        list2 = listOfLists!!1
        minMax1 = minMax list1
        minMax2 = minMax list2
        if (minMax1!!0 < minMax2!!0) then 
            min = minMax1!!0
        else 
            min = minMax2!!0
        if (minMax!!1 > minMax2!!1) then 
            max = minMax1!!1
        else
            max = minMax2!!1
        [min, max]

我也怀疑我并没有尝试以 Haskell 的方式来做这件事,我是一个初学者,仍然不小心把它当作 python 对待。理想情况下,如果有人能告诉我我的方法做错了什么,然后向我展示一种更符合 Haskell 设计原则的方法,我将非常感激。

【问题讨论】:

  • 恐怕不是一个快速的答案,但如果你是 Haskell 的新手,那么我强烈推荐 LYAH。除此之外,这将教你如何编写惯用的 Haskell 代码,使用高阶函数 - 然后你将看到如何在一行代码中找到列表的最小值和最大值,而不必做所有这些丑陋的事情if/else 语句。 (尽管这样做很天真,但还有比这种丑陋的分而治之更好的选择。不用说,有标准库函数可以找到列表的最小值和最大值。)
  • 你又错过了minMax x 和函数体之间的=
  • FWIW 将列表分成两半对此目的似乎不是很有用。无论如何,您能做的最好的事情就是对元素进行线性扫描,那么为什么不这样做呢?
  • Haskell 中没有赋值。名称可以定义为引用值,使用letwhere
  • 作为一个经验法则,您应该避免在代码中使用像 head,tail,!! 这样的部分函数,​​并使用模式匹配来代替,这通常会导致代码更简单。 length xs == 1 也是一种反模式,当 O(1) 模式匹配就足够并免费提供头部和尾部时,成本为 O(N)。

标签: if-statement haskell syntax


【解决方案1】:

您的函数中有几处不是有效的 Haskell 语法。您缺少 =,并且您正试图以 Haskell 不允许的方式声明变量。

我试图重新排列您的代码以使其正确,这就是我得到的:

splitlist :: [a] -> ([a], [a])
splitlist xs = splitAt ((length xs + 1) `div` 2) xs

minMax :: [Int] -> [Int]
minMax x = if length x == 1 then 
              [head x, head x]
           else if length x == 2 then 
                    if (head x > (head $ tail x)) then 
                      [(head $ tail x), head x]
                    else 
                      [head x, (head $ tail x)]
                else [if (minMax1!!0 < minMax2!!0) then minMax1!!0 else minMax2!!0,
                      if (minMax1!!1 > minMax2!!1) then minMax1!!1 else minMax2!!1]
  where
        listOfLists = splitlist x
        list1 = fst listOfLists
        list2 = snd listOfLists
        minMax1 = minMax list1
        minMax2 = minMax list2

看起来您的算法按预期工作!这有点棘手,因为这通常不是大多数人倾向于编写 Haskell 方法的方式。

这是一个不同的实现,可能对比较有用:

minMax        :: [Int] -> [Int]
minMax []     = []
minMax (x:xs) = minMax' x x xs
  where
    minMax' a b []     = [a, b]
    minMax' a b (x:xs) = minMax' (min a x) (max b x) xs

【讨论】:

  • 我忘了补充:tail 是一种返回列表的方法,其中删除了第一个元素。看起来您正试图使用​​它来返回列表中的最后一项。由于列表中只有两个元素,因此我将 tail 替换为 head $ tail,这应该可以满足您的需求。
  • 或者你可以只使用last,它确实给出了列表的最后一个元素(但是像所有这些函数一样,如果你给它一个空列表,你的程序会崩溃)
  • 在您的不同实现中,我将改为使用 minMax' a b (x:xs) = minMax' (min a x) (max b x) xs 并删除第二个 [x] 案例。每次递归都不需要消耗两个元素。 minMax (x:xs) = minMax' x x xs 也应该类似地工作。此外,OP 应该考虑返回 Maybe (Int, Int),而不是返回 [Int] 感觉不对。
  • @chi 哦,是的,好点子。 Maybe (Int, Int) 似乎更加地道。我希望该方法与 Retsek 规范相匹配,但我想既然他要求使用更多的 Haskell 方法来做到这一点,没有理由坚持[Int]
  • @chi 哦,是的,这更有意义。我不知道为什么我要递归地取两个元素。
猜你喜欢
  • 1970-01-01
  • 2019-03-21
  • 2019-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多