Split a number into its digits with Haskell

2019-01-06 19:37发布

问题:

Given an arbitrary number, how can I process each digit of the number individually?

Edit I've added a basic example of the kind of thing Foo might do.

For example, in C# I might do something like this:

static void Main(string[] args)
{
    int number = 1234567890;
    string numberAsString = number.ToString();

    foreach(char x in numberAsString)
    {
        string y = x.ToString();
        int z = int.Parse(y);
        Foo(z);
    }
}

void Foo(int n)
{
    Console.WriteLine(n*n);
}

回答1:

Have you heard of div and mod?

You'll probably want to reverse the list of numbers if you want to treat the most significant digit first. Converting the number into a string is an impaired way of doing things.

135 `div` 10 = 13
135 `mod` 10 = 5

Generalize into a function:

digs :: Integral x => x -> [x]
digs 0 = []
digs x = digs (x `div` 10) ++ [x `mod` 10]

Or in reverse:

digs :: Integral x => x -> [x]
digs 0 = []
digs x = x `mod` 10 : digs (x `div` 10)

This treats 0 as having no digits. A simple wrapper function can deal with that special case if you want to.

Note that this solution does not work for negative numbers (the input x must be integral, i.e. a whole number).



回答2:

digits :: Integer -> [Int]
digits = map (read . (:[])) . show

or you can return it into []:

digits :: Integer -> [Int]
digits = map (read . return) . show

or, with Data.Char.digitToInt:

digits :: Integer -> [Int]
digits = map digitToInt . show

the same as Daniel's really, but pointless and uses Int, because a digit shouldn't really exceed maxBound :: Int.



回答3:

Using the same technique used in your post, you can do:

digits :: Integer -> [Int]
digits n = map (\x -> read [x] :: Int) (show n)

See it in action:

Prelude> digits 123
[1,2,3]

Does that help?



回答4:

You could also just reuse digits from Hackage.



回答5:

You can use

digits = map (`mod` 10) . reverse . takeWhile (> 0) . iterate (`div` 10)

or for reverse order

rev_digits = map (`mod` 10) . takeWhile (> 0) . iterate (`div` 10)

The iterate part generates an infinite list dividing the argument in every step by 10, so 12345 becomes [12345,1234,123,12,1,0,0..]. The takeWhile part takes only the interesting non-null part of the list. Then we reverse (if we want to) and take the last digit of each number of the list.

I used point-free style here, so you can imagine an invisible argument n on both sides of the "equation". However, if you want to write it that way, you have to substitute the top level . by $:

digits n = map(`mod` 10) $ reverse $ takeWhile (> 0) $ iterate (`div`10) n


回答6:

Textbook unfold

import qualified Data.List as L
digits = reverse . L.unfoldr (\x -> if x == 0 then Nothing else Just (mod x 10, div x 10))


回答7:

Via list comprehension:

import Data.Char

digits :: Integer -> [Integer]
digits n = [toInteger (digitToInt x) | x <- show n]

output:

> digits 1234567890
[1,2,3,4,5,6,7,8,9,0]


回答8:

Here's an improvement on an answer above. This avoids the extra 0 at the beginning ( Examples: [0,1,0] for 10, [0,1] for 1 ). Use pattern matching to handle cases where x < 10 differently:

toDigits :: Integer -> [Integer] -- 12 -> [1,2], 0 -> [0], 10 -> [1,0]
toDigits x
    | x < 10 = [x]
    | otherwise = toDigits (div x 10) ++ [mod x 10]

I would have put this in a reply to that answer, but I don't have the needed reputation points :(



回答9:

For returning a list of [Integer]

import Data.Char
toDigits :: Integer -> [Integer]
toDigits n = map (\x -> toInteger (digitToInt x)) (show n)


回答10:

The accepted answer is great but fails in cases of negative numbers since mod (-1) 10 evaluates to 9. If you would like this to handle negative numbers properly... which may not be the case the following code will allow for it.

digs :: Int -> [Int]
digs 0 = []
digs x
  | x < 0 = digs ((-1) * x)
  | x > 0 = digs (div x 10) ++ [mod x 10]


回答11:

Applicative. Pointfree. Origami. Neat.

Enjoy:

import Data.List                                                                
import Data.Tuple                                                               
import Data.Bool                                                                
import Control.Applicative 

digits = unfoldr $ liftA2 (bool Nothing) (Just . swap . (`divMod` 10)) (> 0) 


回答12:

I was lazy to write my custom function so I googled it and tbh I was surprised that none of the answers on this website provided a really good solution – high performance and type safe. So here it is, maybe somebody would like to use it. Basically:

  1. It is type safe - it returns a type checked non-empty list of Word8 digits (all the above solutions return a list of numbers, but it cannot happen that we get [] right?)
  2. This one is performance optimized with tail call optimization, fast concatenation and no need to do any reversing of the final values.
  3. It uses special assignment syntax which in connection to -XStrict allows Haskell to fully do strictness analysis and optimize the inner loop.

Enjoy:

{-# LANGUAGE Strict #-}

digits :: Integral a => a -> NonEmpty Word8
digits = go [] where
    go s x = loop (head :| s) tail where
        head = fromIntegral (x `mod` 10)
        tail = x `div` 10
    loop s@(r :| rs) = \case
        0 -> s
        x -> go (r : rs) x


回答13:

The accepted answer is correct except that it will output an empty list when input is 0, however I believe the output should be [0] when input is zero.

And I don't think it deal with the case when the input is negative. Below is my implementation, which solves the above two problems.

toDigits :: Integer -> [Integer]
toDigits n
 | n >=0 && n < 10 = [n]
 | n >= 10 = toDigits (n`div`10) ++ [n`mod`10]
 | otherwise = error "make sure your input is greater than 0" 


回答14:

I tried to keep using tail recursion

toDigits :: Integer -> [Integer]
toDigits x = reverse $ toDigitsRev x

toDigitsRev :: Integer -> [Integer]
toDigitsRev x
    | x <= 0 = []
    | otherwise = x `rem` 10 : toDigitsRev (x `quot` 10)


回答15:

digits = reverse . unfoldr go
  where go = uncurry (*>) . (&&&) (guard . (>0)) (Just . swap . (`quotRem` 10))


标签: haskell