【问题标题】:Haskell list comprehension type error: Couldn't match expected type ‘Float’ with actual type ‘Int’Haskell 列表理解类型错误:无法将预期类型“Float”与实际类型“Int”匹配
【发布时间】:2021-12-22 10:35:46
【问题描述】:

我正在尝试创建一个毕达哥拉斯三元组列表,即每个包含(x, y, z) 的元组列表,使得x^2 + y^2 = z^2xyz[1..n] 范围内.

我在列表理解保护中遇到了似乎是类型错误的问题。这是我的代码:

-- integer square root
isqrt :: Int -> Float
isqrt = sqrt . fromIntegral

-- hypotenuse
hyp :: Int -> Int -> Float
hyp x y = isqrt (x^2 + y^2)

-- pythagorean triplets in range [1..n]
pyths :: Int -> [(Int, Int, Int)]
pyths n = [(x, y, truncate (hyp x y)) | x <- [1..n], y <- [x..n], elem (hyp x y) [1..n]]

这是错误:

    • Couldn't match expected type ‘Float’ with actual type ‘Int’
    • In the expression: n
      In the second argument of ‘elem’, namely ‘[1 .. n]’
      In the expression: elem (hyp x y) [1 .. n]
   |
11 | pyths n = [(x, y, truncate (hyp x y)) | x <- [1..n], y <- [x..n], elem (hyp x y) [1..n]]
   |                             

我可以通过将我的守卫从elem (hyp x y) [1..n] 编辑为elem (hyp x y) [1..fromIntegral n] 来解决该错误,这表明变量n 在列表中的某个时刻以某种方式从Int 转换为Float理解。

[1..5] 相比,[1..fromIntegral n] 看起来并不特别简洁或优雅,我应该使用另一种方法来解决或避免这个问题吗?

【问题讨论】:

    标签: haskell list-comprehension typeerror


    【解决方案1】:

    这表明变量 n 在列表理解过程中的某个时刻以某种方式从 Int 转换为 Float。

    恰恰相反。您已在类型签名中将 n 声明为 Int,这将永远修复它。这里的Floathyp x y。您在问hyp x y 是否是[1..n]element。 elem :: a -&gt; [a] -&gt; Bool,所以只有当你有一个与列表元素类型相同的对象时,问这个问题才有意义。这就是不匹配:要将elemFloat 和一个列表一起使用,您需要该列表是[Float]。但既然列表是[1..n],那么显然是[Int]

    至于你应该做哪些不同的事情,我建议不要对这个函数使用任何浮点运算。对于您正在使用的秤可能没问题,但我太担心舍入错误会给我错误的答案。您可以搜索 a^2 + b^2 == c^2 的三元组,而不是搜索 sqrt (a^2 + b^2) 是整数(需要 sqrt)的对。

    【讨论】:

    • 有道理,谢谢!我没有想到直接搜索条件。
    【解决方案2】:

    这里有一些可能的代码重写步骤,

    -- pythagorean triplets in range [1..n]
    pyths :: Int -> [(Int, Int, Int)]
    pyths n = 
      = [(x, y, truncate (hyp x y)) | x <- [1..n], y <- [x..n], 
                                      elem (hyp x y) [1..n]]
                                            ------- no good
      = [(x, y, truncate h) | x <- [1..n], y <- [x..n]
                            , let h = (hyp x y)
                            , elem h [1..n]]   -- still no good
    
      = [(x, y, truncate h) | x <- [1..n], y <- [x..n]
                            , let h = sqrt . fromIntegral $ (x^2 + y^2)
                            , elem h [1..n]]]     -- still not
    
      = [(x, y, h) | x <- [1..n], y <- [x..n]
                   , let h = truncate . sqrt . fromIntegral $ (x^2 + y^2)
                   , h^2 == x^2 + y^2    -- truncate, and test...
                   , elem h [1..n]]      -- yep!
    
      = [(x, y, h) | x <- [1..n], y <- [x..n]
                   , let h = truncate . sqrt $ fromIntegral (x^2 + y^2) + 0.01
                   , h^2 == x^2 + y^2
                   , h <= n]
    

    突然间它开始工作了。快吗?让我们看看,

    > pyths 100 & length
    52
    (0.06 secs, 0 bytes)
    
    > pyths 200 & length
    127
    (0.09 secs, 105081448 bytes)
    
    > pyths 500 & length
    386
    (0.61 secs, 731602904 bytes)
    
    > pyths 1000 & length
    881
    (2.31 secs, 2926682600 bytes)
    
    > logBase 2 (231/61)
    1.9210117038531713
    

    所以它只是二次方。有道理——(x,y) 对的枚举已经是这样了,直接计算 h 并测试它的相等性,对于每一对来说都是 O(1)


    但是对于每个(x,y) 组合,搜索 来满足相等的h 呢?在性能方面,这是个好主意吗?

    ps :: Int -> [(Int, Int, Int)]
    ps n = [(x, y, h) | x <- [1..n], y <- [x..n], h <- [y..n] , h^2 == x^2 + y^2]
    
    > ps 100 & length
    52
    (0.41 secs, 486358048 bytes)
    
    > ps 200 & length
    127
    (2.89 secs, 4268474328 bytes)
    
    > logBase 2 (289/41)
    2.8173736778825953    -- almost cubic!
    
    > 0.41 * 10 ** 2.82
    270.88431368311427
    
    > 2.89 * 5 ** 2.82
    270.39163693305665
    

    此版本运行到 1000 的预计时间是 270 秒,而不是上面上次重写的 2.3 秒。


    枚举两个范围的笛卡尔积是二次的;枚举三个范围的笛卡尔积是三次的。这才有意义。

    另一种可能比这里的第一个更快的方法根本不是搜索任何三元组,而是生成它们按顺序。

    【讨论】:

    • 这对我很有启发,因为我还在学习 Haskell,谢谢!我很惊讶最后一个实现对于大 n 的速度有多慢。
    猜你喜欢
    • 1970-01-01
    • 2017-04-07
    • 1970-01-01
    • 2012-09-14
    • 2016-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-08
    相关资源
    最近更新 更多