Sunday, March 27, 2011

Python: Convert list of ints to one number?

I have a list of integers that I would like to convert to one number like:

numList = [1,2,3]
num = magic(numList)

print num, type(num)
>>> 123, <type 'int'>

What is the best way to implement the magic function?

Thanks for your help.

EDIT
I did find this, but it seems like there has to be a better way.

EDIT 2
Let's give some credit to Triptych and cdleary for their great answers! Thanks guys.

From stackoverflow
  • # Over-explaining a bit:
    def magic(numList):         # [1,2,3]
        s = map(str, numList)   # ['1','2','3']
        s = ''.join(s)          # '123'
        s = int(s)              # 123
        return s
    
    
    # How I'd probably write it:
    def magic(numList):
        s = ''.join(map(str, numList))
        return int(s)
    
    
    # As a one-liner  
    num = int(''.join(map(str,numList)))
    
    
    # Functionally:
    s = reduce(lambda x,y: x+str(y), numList, '')
    num = int(s)
    
    
    # Using some oft-forgotten built-ins:
    s = filter(str.isdigit, repr(numList))
    num = int(s)
    
    TokenMacGuy : I had thought the map function was deprecated in favor of list comprehensions, but now I can no longer find a note to that effect. thank you, I'll be adding that back into my vocabulary.
    John Fouhy : I assume there's a bug in your "# How I'd probably write it:" and it should be `''.join(map(str, numList))` ? Also, for your "Cleverly" option, you need to int() the result.
    Robert Gould : Yes your missing map in #2. for a moment there I thought you really were doing magic!
    Triptych : haha thanks - I was editing a bunch and missed it
    Alabaster Codify : TokenMacGuy: you mean this? - http://www.artima.com/weblogs/viewpost.jsp?thread=98196 map, reduce, filter, lambda were all to go in 3k originally
    mdec : +1 for the different versions
    dbr : +1 for the first two methods, the "functional" way seems a bit silly, and the filter/str.isdigit way seems like a horrible hack
  • pseudo-code:

    int magic(list nums)
    {
      int tot = 0
    
      while (!nums.isEmpty())
      {
        int digit = nums.takeFirst()
        tot *= 10
        tot += digit
      }
    
      return tot
    }
    Dana : I think you missed the part where he was looking for a python solution :P
    cdleary : That's okay -- Andrew's solution was actually one of the fastest when converted to Python. +1 from me!
    J.F. Sebastian : It is the fastest solution (@cdleary's implementation in Python) if the list size is less than 30. http://stackoverflow.com/questions/489999/python-convert-list-of-ints-to-one-number/493944#493944
  • def magic(numbers):
        return int(''.join([ "%d"%x for x in numbers]))
    
    elliot42 : nice to see someone else whose brain works like mine.
  • Two solutions:

    >>> nums = [1, 2, 3]
    >>> magic = lambda nums: int(''.join(str(i) for i in nums)) # Generator exp.
    >>> magic(nums)
    123
    >>> magic = lambda nums: sum(digit * 10 ** (len(nums) - 1 - i) # Summation
    ...     for i, digit in enumerate(nums))
    >>> magic(nums)
    123
    

    The map-oriented solution actually comes out ahead on my box -- you definitely should not use sum for things that might be large numbers:

    Timeit Comparison

    import collections
    import random
    import timeit
    
    import matplotlib.pyplot as pyplot
    
    MICROSECONDS_PER_SECOND = 1E6
    FUNS = []
    def test_fun(fun):
        FUNS.append(fun)
        return fun
    
    @test_fun
    def with_map(nums):
        return int(''.join(map(str, nums)))
    
    @test_fun
    def with_interpolation(nums):
        return int(''.join('%d' % num for num in nums))
    
    @test_fun
    def with_genexp(nums):
        return int(''.join(str(num) for num in nums))
    
    @test_fun
    def with_sum(nums):
        return sum(digit * 10 ** (len(nums) - 1 - i)
            for i, digit in enumerate(nums))
    
    @test_fun
    def with_reduce(nums):
        return int(reduce(lambda x, y: x + str(y), nums, ''))
    
    @test_fun
    def with_builtins(nums):
        return int(filter(str.isdigit, repr(nums)))
    
    @test_fun
    def with_accumulator(nums):
        tot = 0
        for num in nums:
            tot *= 10
            tot += num
        return tot
    
    def time_test(digit_count, test_count=10000):
        """
        :return: Map from func name to (normalized) microseconds per pass.
        """
        print 'Digit count:', digit_count
        nums = [random.randrange(1, 10) for i in xrange(digit_count)]
        stmt = 'to_int(%r)' % nums
        result_by_method = {}
        for fun in FUNS:
            setup = 'from %s import %s as to_int' % (__name__, fun.func_name)
            t = timeit.Timer(stmt, setup)
            per_pass = t.timeit(number=test_count) / test_count
            per_pass *= MICROSECONDS_PER_SECOND
            print '%20s: %.2f usec/pass' % (fun.func_name, per_pass)
            result_by_method[fun.func_name] = per_pass
        return result_by_method
    
    if __name__ == '__main__':
        pass_times_by_method = collections.defaultdict(list)
        assert_results = [fun([1, 2, 3]) for fun in FUNS]
        assert all(result == 123 for result in assert_results)
        digit_counts = range(1, 100, 2)
        for digit_count in digit_counts:
            for method, result in time_test(digit_count).iteritems():
                pass_times_by_method[method].append(result)
        for method, pass_times in pass_times_by_method.iteritems():
            pyplot.plot(digit_counts, pass_times, label=method)
        pyplot.legend(loc='upper left')
        pyplot.xlabel('Number of Digits')
        pyplot.ylabel('Microseconds')
        pyplot.show()
    
    Casey : holy shit, thats awesome....thanks for doing that!
    cdleary : No problem, but remember you should probably use what's most readable unless you find it's a bottleneck. I just like timing things. ;-)
    J.F. Sebastian : I've measured performance of the above function. The results are slightly different e.g. with_accumulator() is faster for small `digit_count`. See http://stackoverflow.com/questions/489999/python-convert-list-of-ints-to-one-number/493944#493944
  • def magic(number):
        return int(''.join(str(i) for i in number))
    
    dbr : Nitpick - you can remove the `[ ]` and do the str'ing as a generation expression. `int(''.join(str(i) for i in number))` - it's.. two bytes quicker!
  • This seems pretty clean, to me.

    def magic( aList, base=10 ):
        n= 0
        for d in aList:
            n = base*n + d
        return n
    
  • This method works in 2.x as long as each element in the list is only a single digit. But you shouldn't actually use this. It's horrible.

    >>> magic = lambda l:int(`l`[1::3])
    >>> magic([3,1,3,3,7])
    31337
    
  • Using a generator expression:

    def magic(numbers):
        digits = ''.join(str(n) for n in numbers)
        return int(digits)
    
    dbr : +1 for no strange usage of lambda/map/etc
  • Just for completeness, here's a variant that uses print() (works on Python 2.6-3.x):

    from __future__ import print_function
    try: from cStringIO import StringIO
    except ImportError:
         from io import StringIO
    
    def to_int(nums, _s = StringIO()):
        print(*nums, sep='', end='', file=_s)
        s = _s.getvalue()
        _s.truncate(0)
        return int(s)
    


    I've measured performance of @cdleary's functions. The results are slightly different.

    Each function tested with the input list generated by:

    def randrange1_10(digit_count): # same as @cdleary
        return [random.randrange(1, 10) for i in xrange(digit_count)]
    

    You may supply your own function via --sequence-creator=yourmodule.yourfunction command-line argument (see below).

    The fastest functions for a given number of integers in a list (len(nums) == digit_count) are:

    • len(nums) in 1..30

      def _accumulator(nums):
          tot = 0
          for num in nums:
              tot *= 10
              tot += num
          return tot
      
    • len(nums) in 30..1000

      def _map(nums):
          return int(''.join(map(str, nums)))
      
      
      def _imap(nums):
          return int(''.join(imap(str, nums)))
      

    Figure: N = 1000

    |------------------------------+-------------------|
    | Fitting polynom              | Function          |
    |------------------------------+-------------------|
    | 1.00  log2(N)   +  1.25e-015 | N                 |
    | 2.00  log2(N)   +  5.31e-018 | N*N               |
    | 1.19  log2(N)   +      1.116 | N*log2(N)         |
    | 1.37  log2(N)   +      2.232 | N*log2(N)*log2(N) |
    |------------------------------+-------------------|
    | 1.21  log2(N)   +      0.063 | _interpolation    |
    | 1.24  log2(N)   -      0.610 | _genexp           |
    | 1.25  log2(N)   -      0.968 | _imap             |
    | 1.30  log2(N)   -      1.917 | _map              |
    

    Figure: N = 1000_000

    To plot the first figure download cdleary.py and make-figures.py and run (numpy and matplotlib must be installed to plot):

    $ python cdleary.py
    

    Or

    $ python make-figures.py --sort-function=cdleary._map \
    > --sort-function=cdleary._imap \
    > --sort-function=cdleary._interpolation \
    > --sort-function=cdleary._genexp --sort-function=cdleary._sum \
    > --sort-function=cdleary._reduce --sort-function=cdleary._builtins \
    > --sort-function=cdleary._accumulator \
    > --sequence-creator=cdleary.randrange1_10 --maxn=1000
    
    dbr : That's a strange way to write it in a Python 2.6/3.0 way.. `print(''.join(str(x) for x in [1,2,3,4,5]))` will work in Python 2.5, 2.6, 3.x, probably more...
    J.F. Sebastian : @dbr: the purpose was to use the print function. It is not recommended way, that's why I wrote "for completeness".

0 comments:

Post a Comment