【问题标题】:Haskell: Filter List with those that are IntegersHaskell:使用整数过滤列表
【发布时间】:2012-09-26 23:53:12
【问题描述】:

如何过滤列表以便只返回整数列表?

例如,过滤[1, 1.2, 2, 2.2] 之类的列表将返回[1, 2]

【问题讨论】:

  • 此列表中没有Integer。一个列表只有相同类型的元素,所以这里如果有一个双精度数都是双精度数。
  • 为什么需要这个?如果您提供一些上下文,我们可以提供更适合您问题的答案。
  • 如果你想忽略一些用户输入的非整数,你最好在数据仍然是字符串的时候解决这个问题。请注意,一般情况下,浮点数无法进行相等性测试,因为表示不准确,因此您只能测试近似相等性,并且您必须确定接近的程度足够的。这就是为什么其中一个答案使用 Fractional 类的原因 - 它们以精确表示形式存储,因此可以测试是否相等以及是否为整数。

标签: list haskell filter


【解决方案1】:

考虑到您的列表是[Double] 类型,因为您不能(以任何简单的方式)包含不同类型元素的列表。

一旦你有了一个double列表,你就可以使用函数ceiling

ceiling 2.1 = 3
ceiling 2.0 = 2

所以检查一个数字是否没有小数部分的函数可以写成

nonFractional d = (fromIntegral $ ceiling d) == d

现在您可以对此进行过滤

> filter nonFractional [1, 1.2, 2, 2.2]
[1.0,2.0]

(编辑) 上述比较相等的方法不适用于像

这样的大数
> nonFractional  (12345678987654321.5)
True

如果你将nonFractional 的定义更改为,使用@David 的想法

nonFractional d = (fromIntegral $ ceiling d :: Rational) == d

那么它似乎也适用于大分数

> nonFractional  (12345678987654321.5)
True

【讨论】:

  • Prelude> properFraction (12345678987654321.5) 给了(12345678987654322,0.0)。因此,properFraction 也是如此。
  • (fromIntegral $ ceiling d :: Rational) == d 呢?
  • @AndrewC :大卫的方法似乎奏效了。我已经更正了反映这一点的代码。
【解决方案2】:

首先,您的列表应该是同质的,因此您不能拥有Integers 和Doubles 的列表。

有一个很好的函数properFraction,它将一个数字分解成整数和小数部分:

properFraction :: (Fractional a, Integral b) => a -> (b,a)

所以,我们可以定义一个函数来判断数字是否有非零小数部分。

> let haveNoFractionalPart = (== 0.0) . snd . properFraction 
haveNoFractionalPart :: Double -> Bool

不,我们可以使用该功能过滤您的列表:

> filter haveNoFractionalPart [1, 1.2, 2, 2.2]
[1.0,2.0]

更新

我应该承认,我的解决方案在现实世界中的某些情况下无效且不可行。因为类似

> properFraction (11111111111111111111.1)
(11111111111111110656,0.0)

无论如何,很难想象需要从您拥有的某些值列表中过滤出您所称的 Integer 的情况。并且没有这样的方法可以定义任何带有浮点数的数字都以 100% 的概率具有零浮动部分。

也许对IntegerDouble 进行一些包装会有所帮助。

【讨论】:

    【解决方案3】:

    这个呢:

    filterInt :: (RealFrac a) => [a] -> [Integer]
    filterInt [] = []
    filterInt (x:xs)
        | frac == 0 = a : filterInt xs
        | otherwise = filterInt xs
      where
          (a, frac) = properFraction x
    

    测试:

    > let li = [1, 1.2, 2, 2.2]
    > filterInt li
    > [1,2]
    

    【讨论】:

      【解决方案4】:

      Rational 已经发布了许多解决方案,实际上您只需要将分母与 1 进行比较:

      hasFraction' :: Rational -> Bool
      hasFraction' = (/= 1) . denominator
      

      这可以推广到任何Real 并且是检查数字是否有小数部分的最安全方法之一:

      hasFraction :: (Real a) => a -> Bool
      hasFraction = hasFraction' . toRational
      

      该函数不能解决舍入误差问题,但这是很自然的。当舍入错误困扰您时,您使用了错误的数据类型。

      【讨论】:

      • "当舍入错误困扰您时,您使用了错误的数据类型。"绝对地。确保您知道它何时会困扰您,因为它可能会在编辑所有数据时安静地“工作”。
      【解决方案5】:

      这取决于您从哪里获得数据。

      Haskell 不允许您将纯整数与非整数混合, 所以你的整数会被Double等数据类型固有的不准确性所污染,除非你使用更准确的东西,比如Rational, 但鉴于您无论如何都不想要非整数,如果可以的话,在它们成为数字数据之前将它们从源头丢弃。

      • 如果您从用户那里获取数据,请使用仅允许他们输入数字序列的输入表单,或使用下面的getInt
      • 如果您从数据库或其他基于文本的来源获取数据,请使用下面的getInt
      • 如果您从某些您无法控制的代码(库或外部调用)中获取数据,是否有替代方案只为您提供整数?
        如果是,请使用它,如果不是,请在其他答案中使用其他基于舍入的解决方案之一。

      getInt 将 String 转换为 Integer,巧妙地忽略任何不是 Integer 的内容:

      import Data.Char (isDigit)
      
      getInt :: String -> Maybe Integer
      getInt xs | all isDigit xs = Just (read xs)
                | otherwise      = Nothing
      

      所以getInt "12345"Just 12345getInt 12345678987654321.1Nothing。 我们可以使用它从某个列表中删除非整数输入:

      getInts :: [String] -> [Integer]
      getInts xss = catMaybes $ map getInt xss
      

      或者更简洁地说,我们可以写成
      getInts = catMaybes.map getInt

      现在catMaybes :: [Maybe a] -> [a],它摆脱了Nothings 并解开Justs。我们需要在顶部
      import Data.Maybe (catMaybes) 获取它。

      如果您的数据以某种浮点数的形式出现, 请记住,浮点类型中没有真正的相等性, 因此,即使您在检查之前转换为更准确的表示, 从逻辑上讲,您永远不可能知道原始数据是否 表示一个精确的整数或只是一个非常接近整数的东西 浮点表示在数据到达您之前四舍五入。 例如:

      Prelude> (12345678987654321.6 :: Double) == 12345678987654322.0
      True
      

      Prelude> (12345678987654321.6 :: Rational) == 12345678987654322.0
      False
      

      但如果您可以选择数据类型,则您可以控制生成代码,因此请选择不包含非整数!

      总结:在将非整数转换为数值数据之前, 最容易去除它们, 而且您不会偶尔出现奇怪的舍入错误。

      【讨论】:

        【解决方案6】:

        您的列表必须是 [Double][Integer] 类型,或其他类型的数字。你不能混合类型。

        也就是说,如果您有一个双精度列表并且您试图过滤掉那些不是整数的,您可以随时使用roundfloorceiling 来检查数字的等价性。

        例如:

        isInt :: (RealFrac a) => a -> Bool
        isInt x = x == (fromIntegral $ round x)
        

        然后您可以使用 filter 过滤您的数据:

        filter isInt [1, 1.2, 2, 2.2] -- [1.0, 2.0]
        

        【讨论】:

          猜你喜欢
          • 2012-04-17
          • 2014-11-27
          • 2019-02-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多