7

If I have the integer 123 and I want to break the digits into an array [1,2,3] what is the best way of doing this? I have messed around with this a lot and I have the following working:

var number = 123    
var digits = Array(String(number)).map{Int(strtoul((String($0)),nil,16))}

I look at it and feel there might be an better/easier way of doing this. If not then maybe it will come up on web searches. Any alternatives ideas?

pn1 dude
  • 4,286
  • 5
  • 30
  • 26
  • Better in what respect? Shorter, faster, ...? – Martin R Sep 09 '15 at 18:29
  • I use map a lot and this works good with all numbers that I've tried. I was wondering of other approaches to this. – pn1 dude Sep 09 '15 at 18:50
  • I just realized that my question doesn't really match to a single answer. But as I look through answers to other questions I don't always go with the accepted answer. I love the alternative ideas! – pn1 dude Sep 09 '15 at 18:54

6 Answers6

9

It is easier to work on the UTF-8 representation of the number string because the UTF-8 code unit of a decimal digit can easily be converted to the corresponding integer by subtracting a constant:

let asciiZero = UInt8(ascii: "0")
let digits = map(String(number).utf8) { Int($0 - asciiZero) }

This also turned out to be significantly faster.

If performance is the primary goal then you should restrict the method to simple integer arithmetic, without using strings or characters:

var digits : [Int] = []
while number > 0 {
    digits.insert(number % 10, atIndex: 0)
    number /= 10
}

Here is my complete test code for your convenience (compiled with Xcode 6.4 in Release mode on a MacBook Pro).

func digits1(number : Int) -> [Int] {
    let digits = Array(String(number)).map{Int(strtoul((String($0)), nil, 16))}
    return digits
}

func digits2(number : Int) -> [Int] {
    // Use a static property so that the constant is initialized only once.
    struct Statics {
        static let asciiZero = UInt8(ascii: "0")
    }

    let digits = map(String(number).utf8) { Int($0 - Statics.asciiZero) }
    return digits
}

func digits3(var number : Int) -> [Int] {
    var digits : [Int] = []
    while number > 0 {
        digits.insert(number % 10, atIndex: 0)
        number /= 10
    }
    return digits
}

func measure(converter: (Int)-> [Int]) {
    let start = NSDate()
    for n in 1 ... 1_000_000 {
        let digits = converter(n)
    }
    let end = NSDate()
    println(end.timeIntervalSinceDate(start))
}

measure(digits1) // 10.5 s
measure(digits2) // 1.5 s
measure(digits3) // 0.9 s

Update for Swift 3:

func digits(_ number: Int) -> [Int] {
    var number = number
    var digits: [Int] = []
    while number > 0 {
        digits.insert(number % 10, at: 0)
        number /= 10
    }
    return digits
}

print(digits(12345678)) // [1, 2, 3, 4, 5, 6, 7, 8]

This also turned out to be slightly faster than appending the digits to an array and reversing it at the end.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
3

Another Swift 3 alternative is making use of the global sequence(state:next:) method.

Swift 3.1

let number = 123456
let array = Array(sequence(state: number,
    next: { return $0 > 0 ? ($0 % 10, $0 = $0/10).0 : nil }
    ).reversed())

print(array) // [1, 2, 3, 4, 5, 6]

Swift 3.0

let number = 123456
let array = Array(sequence(state: number,
    next: { (num: inout Int) -> Int? in
        return num > 0 ? (num % 10, num /= 10).0 : nil
    }).reversed())

print(array) // [1, 2, 3, 4, 5, 6]

The approach above assumes a non-negative number, and will moreover return an empty array ([]) is case number is 0. To cover the full range of natural numbers as follows:

// -123 -> [1, 2, 3]
// 0    -> [0]
// 123  -> [1, 2, 3]

We can modify the above to:

// for some number ...
let number = ...

// Swift 3.1
let array: [Int]
if number == 0 { array = [0] }
else {
    array =  Array(sequence(state: abs(number),
    next: { return $0 > 0 ? ($0 % 10, $0 = $0/10).0 : nil }
    ).reversed())
}

// Swift 3.0
let array: [Int]
if number == 0 { array = [0] }
else {
    array = Array(sequence(state: number,
    next: { (num: inout Int) -> Int? in
        return num > 0 ? (num % 10, num /= 10).0 : nil
    }).reversed())
}

Some details regarding the tuple return above

In the single line return above, we've made use of the neat "()-return operation inlined as tuple member of type ()", a method that I first saw used by @MartinR in his improvement proposal to update the following answer. We use the last member of a (Int, ()) tuple to mutate the state property num; the first member of the tuple will be computed prior to the execution of the ()-return operation in "computing" the 2nd tuple member.

We can draw an analogy between this tuple method and the approach of executing a closure with a single defer and return statement. I.e., the return statement:

return num > 0 ? (num % 10, num /= 10).0 : nil

could also be accomplished by executing such a closure instead ("long form", in this context)

return num > 0 ? { defer { num /= 10 }; return num % 10 }() : nil  

I haven't benchmarked these two approaches against each other, but I have a feeling the former will be faster when repeatedly being called as in the context of sequence(state:next:) above.


Swift 3.0 vs 3.1: anonymous argument in the next closure above

Due to the now closed (Swift 3.1 and onwards) bug reported in SR-1976 (Closure signature in Swift 3 required for inout params), there's a limitation in Swift's type inference for inout parameters to closures. See e.g. the following Q&A for details:

This is the reason why we have to explicitly annotate the type of the state in the next closure of the Swift 3.0 solution above, whereas we can make use of anonymous arguments in the next closure for the Swift 3.1 solution.

Community
  • 1
  • 1
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Hey, I just was about to add that solution :) – Martin R Nov 22 '16 at 08:45
  • @MartinR ah, I hope I didn't post it just as you were to update yours! Too bad we can't use anonymous arguments for the `next` closure (due to `inout` argument bug [SR-1976](https://bugs.swift.org/browse/SR-1976), i think), or it could be even neater :) – dfrib Nov 22 '16 at 08:50
  • The sequence-based solution is a bit faster than the explicit loop with inserting or appending to an array. I'll add some benchmarks later. – Martin R Nov 23 '16 at 10:46
  • @MartinR I didn't expect it to be on par with the explicit loop, that's nice. Thanks! – dfrib Nov 23 '16 at 12:43
  • `extension BinaryInteger { var digits: [Self] { var source = self return sequence(state: source) { _ in source > 0 ? (source % 10, source /= 10).0 : nil }.reversed() } }` – Leo Dabus Dec 10 '19 at 23:42
2

My take for Swift 2:

var x = 123
var digits = String(x).characters.map { Int(String($0))! } // [1,2,3]

It is more explicit about the characters, so I think it is quite readable.

Mundi
  • 79,884
  • 17
  • 117
  • 140
  • Yeah didn't notice the "Swift 2" thing... Still back on the 6.4 playground. I like the way it reads for sure and I will try it once we move to 7! – pn1 dude Sep 09 '15 at 19:23
1

The other answers have not taken the radix into account—the example demands 16, not 10.

public extension UnsignedInteger {
  /// The digits that make up this number.
  /// - Parameter radix: The base the result will use.
  /// - Note: Leading zeros are not taken into account. Zero itself will yield an empty array.
  @inlinable func digits(radix: Self = 10) -> [Self] {
    sequence(state: self) { quotient in
      guard quotient > 0 else { return nil }

      let division = quotient.quotientAndRemainder(dividingBy: radix)
      quotient = division.quotient
      return division.remainder
    }
    .reversed()
  }
}
(867_5309 as UInt32).digits()
[8,6,7, 5,3,0,9]

(0xF0 as UInt8).digits(radix: 0b10)
[1,1,1,1, 0,0,0,0]

(0xA0_B1_C2_D3_E4_F5 as UInt).digits(radix: 0x10)
[10,0, 11,1, 12,2, 13,3, 14,4, 15,5]

(0o707 as UInt16).digits(radix: 0o10)
[0b111, 0, 0b111]
0

I don't know if you are new to swift but let's be clear, the method where you use the map is the best for what you want to do:) There is another approach that i don't recommend cause having a good visualisation of your code structure is really important.

import Foundation

var number2 = 123
var number3 : String = "\(number2)"
var array : [String] = []
var str2 = ""
for i in number3.characters
{
    str2.append(i)
    var string = NSString(string: "str")
    string.doubleValue
    array.append(str2)
    str2 = ""
}

cheers

Korpel
  • 2,432
  • 20
  • 30
  • Been working with swift for awhile but with each new release I sometimes question how much I do know... I use map a lot. In the for loop "i" will by of type Character by default. – pn1 dude Sep 09 '15 at 19:01
  • map and join are two awesome functions of swift cause you actually avoid all the trouble of doing for loops and such. really good way to keep your code clean and code faster and smarter. i suggest you have a look at closures. those things are i think one of the best swift has. Really simplify things – Korpel Sep 09 '15 at 19:04
  • 1
    agreed -> map join closures! Oh my! – pn1 dude Sep 09 '15 at 19:32
-1

I'd say if it isn't broke don't fix it. I can thing of one other way, but it's not any shorter or anything:

var number = 123
var digits = map(String(number)) { String($0).toInt() ?? 0 }
The Beanstalk
  • 798
  • 1
  • 5
  • 20
  • You may be correct but this reads a bit nicer! Works fine in 6.4 playground. – pn1 dude Sep 09 '15 at 19:24
  • Just because something is not (yet) broken is **not** a reason to not fix it. Code optimization (event for aesthetics aka readability) is always in order. – Mundi Sep 09 '15 at 19:25