简体   繁体   中英

Haskell: Filter List with those that are Integers

How would I filter a list so that I only return the list of those that are integers?

For example, filtering a list like [1, 1.2, 2, 2.2] would return [1, 2] .

Considering your list to be of type [Double] as you can not have (in any simple way) a list with elements of different types.

Once you have a list of double, you can use the function ceiling .

ceiling 2.1 = 3
ceiling 2.0 = 2

so a function to check if a number has no fractional part can be written as

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

now you can do filter on this

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

(Edit) The above approach of comparing equality does not work for large numbers like

> nonFractional  (12345678987654321.5)
True

Using @David's idea if you change the definition of nonFractional as

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

Then it seems to work for large fractions as well

> nonFractional  (12345678987654321.5)
True

First of all, your list should be homogenous, so you can't have list of Integer s and Doubles .

There is a nice function properFraction , which decomposes a number into its whole and fractional parts:

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

So, we can define a function to figure out is number have a non-zero fractional part or not.

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

No we can filter your list with that function:

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

Update :

I should admit that my solution isn't valid and workable for some cases in the real world. Because of something like

> properFraction (11111111111111111111.1)
(11111111111111110656,0.0)

Anyway, it's hard to imagine the case when it's needed to filter what you calling an Integer from some list of values that you have. And there is no such way to define that any number with floating point have zero floating part with 100% probability.

Maybe some wrapper over Integer and Double will be helpful.

What about this:

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

test:

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

A number of solutions have been posted for Rational , where in actuality you really only need to compare the denominator to 1:

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

This can be generalized to any Real and is one of the safest methods to check whether a number has a fractional part:

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

That function does not solve the rounding error problem, but that's natural. When rounding errors bother you, you're using the wrong data type.

It depends where you got the data from.

Haskell doesn't let you mix pure integers with non-integers, so your integers will get tainted with the inaccuracy inherent in data types like Double unless you use something more accurate like Rational , but given that you don't want the non-integers anyway, throw them away at source, before they're numeric data, if you can.

  • If you got the data from a user, either use an input form that only allows them to enter sequences of digits, or use getInt below.
  • If you got the data from a database or other text-based source, use getInt below.
  • If you got the data from some code you don't control (library or external call), is there an alternative that will give you just integers?
    If so, use it, if not, use one of the other rounding-based solutions in the other answers.

getInt converts a String to an Integer, cunningly ignoring anything that isn't an Integer:

import Data.Char (isDigit)

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

So getInt "12345" is Just 12345 whereas getInt 12345678987654321.1 is Nothing . We can use that to remove non-integer input from some list:

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

or more consisely, we could write
getInts = catMaybes.map getInt .

Now catMaybes :: [Maybe a] -> [a] and it gets rid of the Nothing s and unwraps the Just s. We'll need to
import Data.Maybe (catMaybes) at the top to get it.

If your data comes as a floating point number of some sort, bear in mind there's no true equality in a floating point type, so even if you convert to a more accurate representation before checking, it's logically impossible for you to ever know whether the original data represented an exact integer or just something quite close to an integer that the floating point representation rounded before the data got to you. For example:

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

whereas

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

But if you can choose the data type, you're in control of the generating code, so choose not to include non-integers!

Summary: it's easiest to get rid of non-integers before you turn them into numerical data, and you're not subject to occasional bizzare rounding errors.

Your list will have to be of type [Double] or [Integer] , or some other type of number. You cannot mix types.

That said, if you have a list of doubles and you're trying to filter out those that are not integers, you can always use round , floor , or ceiling to check equivalency to the number.

For example:

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

Then you can just filter your data using this, using filter :

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM