简体   繁体   中英

How do a split integers within a list into single digits only?

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?

simplest way is,

>>> [int(i) for i in range(9,12) for i in str(i)]
[9, 1, 0, 1, 1]
>>> 

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

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]

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.

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

将其转换为字符串,然后返回列表:)

lambda x: list(''.join(str(e) for e in x))

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)

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

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