0

Let's say I have something like this:

    list(range(9:12))

Which gives me a list:

    [9,10,11]

However I want it to be like:

    [9,1,0,1,1]

Which splits every integer into single digits, is there anyway of achieving this without sacrificing too much performance? Or is there a way of generating list like these in the first place?

yfan183
  • 547
  • 2
  • 7
  • 20

9 Answers9

4

simplest way is,

>>> [int(i) for i in range(9,12) for i in str(i)]
[9, 1, 0, 1, 1]
>>> 
Mohideen bin Mohammed
  • 18,813
  • 10
  • 112
  • 118
4

You can build the final result efficiently without having to build one large and/or small intermediate strings using itertools.chain.from_iterable.

In [18]: list(map(int, chain.from_iterable(map(str, range(9, 12)))))
Out[18]: [9, 1, 0, 1, 1]

In [12]: %%timeit
    ...: list(map(int, chain.from_iterable(map(str, range(9, 20)))))
    ...: 
100000 loops, best of 3: 8.19 µs per loop

In [13]: %%timeit
    ...: [int(i) for i in ''.join(map(str, range(9, 20)))]
    ...: 
100000 loops, best of 3: 9.15 µs per loop

In [14]: %%timeit
    ...: [int(x) for i in range(9, 20) for x in str(i)]
    ...: 
100000 loops, best of 3: 9.92 µs per loop

Timings scale with input. The itertools version also uses memory efficiently although it is marginally slower than the str.join version if used with list(map(int, ...)):

In [15]: %%timeit
    ...: list(map(int, chain.from_iterable(map(str, range(9, 200)))))
    ...: 
10000 loops, best of 3: 138 µs per loop

In [16]: %%timeit
    ...: [int(i) for i in ''.join(map(str, range(9, 200)))]
    ...: 
10000 loops, best of 3: 159 µs per loop

In [17]: %%timeit
    ...: [int(x) for i in range(9, 200) for x in str(i)]
    ...: 
10000 loops, best of 3: 182 µs per loop

In [18]: %%timeit
    ...: list(map(int, ''.join(map(str, range(9, 200)))))
    ...: 
10000 loops, best of 3: 130 µs per loop
Moses Koledoye
  • 77,341
  • 8
  • 133
  • 139
3

Convert the integers to strings, then split() the string and reconvert the digits back to ints.

li = range(9,12)

digitlist = [int(d) for number in li for d in str(number)]

Output:

[9,1,0,1,1]
Mohd
  • 5,523
  • 7
  • 19
  • 30
Zinki
  • 446
  • 4
  • 10
1

I've investigated how performant I can make this a little more. The first function I wrote was naive_single_digits, which uses the str approach, with a pretty efficient list comprehension.

def naive_single_digits(l):
    return [int(c) for n in l
                   for c in str(n)]

As you can see, this approach works:

In [2]: naive_single_digits(range(9, 15))
Out[2]: [9, 1, 0, 1, 1, 1, 2, 1, 3, 1, 4]

However, I thought that it would surely be unecessary to always build a str object for each item in the list - all we actually need is a base conversion to digits. Out of laziness, I copied this function from here. I've optimised it a bit by specifying it to base 10.

def base10(n):
    if n == 0:
        return [0]
    digits = []
    while n:
        digits.append(n % 10)
        n //= 10
    return digits[::-1]

Using this, I made

def arithmetic_single_digits(l):
    return [i for n in l
              for i in base10(n)]

which also behaves correctly:

In [3]: arithmetic_single_digits(range(9, 15))
Out[3]: [9, 1, 0, 1, 1, 1, 2, 1, 3, 1, 4]

Now to time it. I've also tested against one other answer (full disclosure: I modified it a bit to work in Python2, but that shouldn't have affected the performance much)

In [11]: %timeit -n 10 naive_single_digits(range(100000))
10 loops, best of 3: 173 ms per loop

In [10]: %timeit -n 10 list(map(int, itertools.chain(*map(str, range(100000)))))
10 loops, best of 3: 154 ms per loop

In [12]: %timeit arithmetic_single_digits(range(100000))
10 loops, best of 3: 93.3 ms per loop

As you can see arithmetic_single_digits is actually somewhat faster, although this is at the cost of more code and possibly less clarity. I've tested against ridiculously large inputs, so you can see a difference in performance - at any kind of reasonable scale, every answer here will be blazingly fast. Note that python's integer arithmetic is probably actually relatively slow, as it doesn't use a primitive integer type. If this were to be implemented in C, I'd suspect my approach to get a bit faster.

Comparing this to viblo's answer, using (pure) Python 3 (to my shame I haven't installed ipython for python 3):

print(timeit.timeit("digits(range(1, 100000))", number=10, globals=globals()))
print(timeit.timeit("arithmetic_single_digits(range(1, 100000))", number=10, globals=globals()))

This has the output of:

3.5284318959747907
0.806847038998967

My approach is quite a bit faster, presumably because I'm purely using integer arithmetic.

Izaak van Dongen
  • 2,450
  • 13
  • 23
1

Another way to write an arithmetic solution. Compared to Izaak van Dongens solution this doesnt use a while loop but calculates upfront how many iterations it need in the list comprehension/loop.

import itertools, math

def digits(ns):
    return list(itertools.chain.from_iterable(
        [
            [
                (abs(n) - (abs(n) // 10 **x)*10**x ) // 10**(x-1) 
                for x 
                in range(1+math.floor(math.log10(abs(n) if n!=0 else 1)), 0, -1)] 
            for n in ns
        ]
    ))

digits([-11,-10,-9,0,9,10,11])
viblo
  • 4,159
  • 4
  • 20
  • 28
  • Awesome : D. You're a braver person than I to get into the guts of the arithmetic like that. By the way, for `digits([0])` you get a math domain error, although 0 does have digits. – Izaak van Dongen Aug 23 '17 at 23:41
  • Thanks, I updated the code to handle 0 and negative integers. Unfortunately its hopelessly slow compared to the other solutions. – viblo Aug 24 '17 at 07:20
0

Turn it to a string then back into a list :)

lambda x: list(''.join(str(e) for e in x))
Bruno E
  • 156
  • 11
0

You can also do with map function

a=range(9,12)
res = []
b=[map(int, str(i)) for i in a]
for i in b:
    res.extend(i)

print(res)
nanithehaddock
  • 247
  • 1
  • 9
0

here is how I did it:

ls =  range(9,12)
lsNew = []
length = len(ls)
for i in range(length):
    item = ls[i]
    string = str(item)
    if len(string) > 1:
        split = list(string)
        lsNew = lsNew + split
    else:
        lsNew.append(item)
ls = lsNew
print(ls)
0
def breakall(L):
  if L == []:
    return []
  elif L[0] < 10:
    return [L[0]] + breakall(L[1:])
  else: 
    return breakall([L[0]//10]) + [L[0] % 10] + breakall(L[1:]) 

print(breakall([9,10,12]))
-->
[9, 1, 0, 1, 2]
englealuze
  • 1,445
  • 12
  • 19