Greg B
Greg B

Reputation: 14888

Split a number into its digits with Haskell

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);
}

Upvotes: 45

Views: 67946

Answers (17)

Marco_O
Marco_O

Reputation: 111

I would like to improve upon the answer of Dave Clarke in this page. It boils down to using div and mod on a number and adding their results to a list, only this time it won't appear reversed, nor resort to ++ (which is slower concatenation).

toDigits :: Integer -> [Integer]

toDigits n
  | n <= 0    = []
  | otherwise = numToDigits (n `mod` 10) (n `div` 10) []
    where
      numToDigits a 0 l = (a:l)
      numToDigits a b l = numToDigits (b `mod` 10) (b `div` 10) (a:l)

This program was a solution to a problem in the CIS 194 course at UPenn that is available right here. You divide the number to find its result as an integer and the remainder as another. You pass them to a function whose third argument is an empty list. The remainder will be added to the list in case the result of division is 0. The function will be called again in case it's another number. The remainders will add in order until the end.

Note: this is for numbers, which means that zeros to the left won't count, and it will allow you to have their digits for further manipulation.

Upvotes: 0

i474
i474

Reputation: 654

I've been following next steps(based on this comment):

  1. Convert the integer to a string.
  2. Iterate over the string character-by-character.
  3. Convert each character back to an integer, while appending it to the end of a list.

toDigits :: Integer -> [Integer]
toDigits a = [(read([m])::Integer) | m<-show(a)]

main = print(toDigits(1234))

Upvotes: 2

muhmuhten
muhmuhten

Reputation: 3341

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 point free and uses Int, because a digit shouldn't really exceed maxBound :: Int.

Upvotes: 29

Wojciech Danilo
Wojciech Danilo

Reputation: 11803

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

Upvotes: 3

Scarabyte
Scarabyte

Reputation: 303

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) 

Upvotes: 2

Leonard Ge
Leonard Ge

Reputation: 879

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" 

Upvotes: 0

Wessel Badenhorst
Wessel Badenhorst

Reputation: 15

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)

Upvotes: -2

Li Chanjuan
Li Chanjuan

Reputation: 11

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

Upvotes: -2

granmoe
granmoe

Reputation: 322

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 :(

Upvotes: 3

Andrew
Andrew

Reputation: 37969

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]

Upvotes: 3

michael.schuett
michael.schuett

Reputation: 4741

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]

Upvotes: 1

Duda Dornelles
Duda Dornelles

Reputation: 11

For returning a list of [Integer]

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

Upvotes: 1

Dave Clarke
Dave Clarke

Reputation: 1367

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).

Upvotes: 103

jon_darkstar
jon_darkstar

Reputation: 16768

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))

Upvotes: 15

hammar
hammar

Reputation: 139840

You could also just reuse digits from Hackage.

Upvotes: 15

Daniel
Daniel

Reputation: 1663

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?

Upvotes: 14

Landei
Landei

Reputation: 54574

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

Upvotes: 12

Related Questions