【问题标题】:Haskell Novice Trouble with Splitting a List in HalfHaskell 新手将列表分成两半的麻烦
【发布时间】:2014-11-23 15:04:13
【问题描述】:

这是我尝试编写一个函数,将偶数长度的列表分成相等的两半。

halve :: [a] -> ([a], [a])
halve x 
   | even len = (take half x, drop half x)
   | otherwise = error "Cannnot halve a list of odd length"
   where
      len = length x
      half = len / 2

我收到以下错误:

 No instance for (Fractional Int) arising from a use of ‘/’
    In the expression: len / 2
    In an equation for ‘half’: half = len / 2
    In an equation for ‘halve’:

我不明白这个错误,但我怀疑 Haskell 需要提前被告知 len 是你可以除以 2 的东西。那么,我该如何纠正这个例子?我的代码在惯用的haskell附近吗?如果有其他关于我的代码的 cmets,我将不胜感激。

【问题讨论】:

标签: haskell integer-division


【解决方案1】:

/ 用于当您很高兴得到非整数答案时。由于您已经检查了数字的偶数,您可以愉快地使用div 的整数除法:

halve :: [a] -> ([a], [a]) 
halve x | even len = (take half x, drop half x) 
        | otherwise = error "Cannnot halve a list of odd length" 
   where len = length x 
         half = len `div` 2

(我个人很乐意不准确地将奇数长度列表减半并避免错误消息,但这取决于您。)

这种区别由类型表示:

(/) :: Fractional a => a -> a -> a
div :: Integral a => a -> a -> a 

所以你只能在类型支持非整数除法时使用/,当它是整数类型时你只能使用div。这样你就不会误以为你在做一种除法,而实际上你在做另一种。

干得好,顺便说一句,你想得很好。

“没有实例...”

实际上,“No instance for ...”错误消息几乎总是因为类型错误。当我以错误的顺序放置论点时,我最常得到它。在这种情况下,当您的类型为Int 时,您已经使用了其他类型的除法。

它说“无实例”,因为您尝试使用的函数适用于一类类型,但您提供的数据类型不在该类(的实例)中。编译器将此视为缺少的实例声明,通常这只是一个错误并且完全是错误的类型。我很少打算将某些东西作为类的实例然后忘记,而我更经常将参数放在错误的位置。

【讨论】:

  • 谢谢。我注意到 Haskell 的错误对于新手来说并不容易理解。随着时间的推移会变得更好吗?
  • @ArmenTsirunyan 是的,一旦你习惯了类型系统,它们就会变得更容易理解。
【解决方案2】:

请注意,列表减半可以在结构上完成,无需任何索引。并行地对列表进行两次遍历,一次以另一次的速度前进。当快的触底时,慢的已经到了一半。

halve :: [a] -> ([a], [a])
halve xs = go xs xs
  where
    go xs []  = ([],xs)
    go (x:xs) [_] = ([x],xs)
    go (x:xs) (_:_:ys) = let (first,last) = go xs ys in (x:first, last)

go 中的第二个子句是奇数长度列表的“决胜局”。照原样,代码将奇数放在上半场的末尾。如果您愿意,只需将右侧更改为 ([],x:xs)

这个想法是 Danvy 和 Goldberg (http://www.brics.dk/RS/05/3/BRICS-RS-05-3.pdf) 确定的“There And Back Again”模式的一半关键。

【讨论】:

  • 这看起来像是 Will Ness 在评论中建议的方法的一个版本,但您的方法更懒惰——第一个列表的元素逐渐可用。不错。
  • 哦,没听懂。好吧,它已经脱离了民间传说并进入了具有该来源的文献 :) 但是,在另一个,老实说不是故意的苦涩的情况下,请注意,我发现 Jubobs 的答案在我发布我的答案后 2 小时就长了一个尾巴。这很常见吗?与 wiki 相比,这不是违背问答网站的目的吗?
  • Jubob 的加法和你的答案完全不同,所以我不会担心。
  • @Kris 我不记得我更新答案时的确切想法,但我的意图不是抄袭你的。
【解决方案3】:

length函数的类型签名是[a] -> Int;这告诉你length 返回一个Int

此外,/ 运算符(类型签名Fractional a => a -> a -> a)仅与具有Fractional 实例的类型兼容;因为no Fractional instance exists for Int,你不能写类似的东西

length x / 2

使用div函数(类型签名Integral a => a -> a -> a)执行整数除法:

div len 2

此外,还有一些方法可以改进halve 的实现。

  1. 您可以使用splitAt half x 而不是(take half x, drop half x),它只需要通过x 一次。
  2. 这是一个更自然(这就是纸牌玩家所做的!)和更高效(无需计算长度)的实现:

    halve :: [a] -> ([a], [a])
    halve []  = ([], [])
    halve [x] = ([x], [])  -- necessary case for input lists that contains an odd number of elements
    halve (x : y : zs) = (x : xs, y : ys)
      where
        (xs, ys) = halve zs
    

【讨论】:

    【解决方案4】:

    takedrop 需要 Int 类型的参数。 (/) 执行小数除法,因此其结果不能是 Int 类型。改用div 来执行整数除法:

    halve :: [a] -> ([a], [a])
    halve x 
       | even len = (take half x, drop half x)
       | otherwise = error "Cannnot halve a list of odd length"
       where
          len = length x
          half = len `div` 2
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-19
      相关资源
      最近更新 更多