2

I am trying to make a code that converts integers in array to a given base and padding them to make them from the same size. The following code which I manipulated from a code on stackoverflow by Alex Martelli, doesn't work when I apply numpy.vectorize on it, although it works for single arrays:

def int2base(x, base,size):
    ret=np.zeros(size)
    if x==0: return ret
    digits = []
    while x:
        digits.append(x % base)
        x /= base
    digits.reverse()
    ret[size-len(digits):]=digits[:]
    return ret
vec_int2base=np.vectorize(int2base)
vec_int2base(np.asarray([2,1,5]),base=3,size=3)

Which terminates with the following error:

...
   1640             if ufunc.nout == 1:
   1641                 _res = array(outputs,
-> 1642                              copy=False, subok=True, dtype=otypes[0])
   1643             else:
   1644                 _res = tuple([array(_x, copy=False, subok=True, dtype=_t)

ValueError: setting an array element with a sequence. 

Is there any better way to write it for vectors case, and what am I missing here.

Community
  • 1
  • 1
Cupitor
  • 11,007
  • 19
  • 65
  • 91

3 Answers3

2

I use the "fromBase10" function (below) a fair amount while I am programming in assembly. Note that it doesn't pad the output, but numpy.vectorize does work with it. Just don't forget to pad.

## Execute this to convert a base 10 into any arbitrary base
def fromBase10(number, base):
    if not number:
        return '0'
    sign = 1 if number > 0 else -1
    alphanum = string.digits + string.ascii_lowercase
    nums = alphanum[:base]
    res = ''
    number *= sign

    while number:
        number, mod = divmod(number, base)
        res += nums[mod]
    return ('' if sign == 1 else '-') + res[::-1]

Note that I copied the basic routine from someone else on Stack Exchange, but I no longer remember where. I just don't want to claim credit where it isn't mine :)

slightlynybbled
  • 2,408
  • 2
  • 20
  • 38
  • My code returns a vector not a string. I am afraid this code would face the same problem if I change that. Mine is not a letter based conversion but a mathematical definition of `base conversion`. Anyway thanks. – Cupitor Nov 26 '13 at 19:38
  • 1
    `i2b = lambda x,b,s :i2b(x//b,b,s-1)+ str(x%b) if x else "0"*max(s,0)` ... but its still a string :P – Joran Beasley Nov 26 '13 at 19:41
  • @JoranBeasley, Thanks but doesn't help :D – Cupitor Nov 26 '13 at 19:45
2

Here's a version that is vectorized:

import numpy as np


def int2base(x, base, size=None, order='decreasing'):
    x = np.asarray(x)
    if size is None:
        size = int(np.ceil(np.log(np.max(x))/np.log(base)))
    if order == "decreasing":
        powers = base ** np.arange(size - 1, -1, -1)
    else:
        powers = base ** np.arange(size)
    digits = (x.reshape(x.shape + (1,)) // powers) % base
    return digits

If x has shape shp, the result has shape shp + (size,). If size is not given, the size is based on the largest value in x. order determines the order of the digits; use order="decreasing" (the default) to convert, say, 123 to [1, 2, 3]. Use order="increasing" to get [3, 2, 1]. (The latter might be more natural, as the index of the digit in the result matches the power of the base for that digit.)

Examples:

In [97]: int2base([255, 987654321], 10)
Out[97]: 
array([[0, 0, 0, 0, 0, 0, 2, 5, 5],
       [9, 8, 7, 6, 5, 4, 3, 2, 1]])

In [98]: int2base([255, 987654321], 10, size=12)
Out[98]: 
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 5, 5],
       [0, 0, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1]])

In [99]: int2base([255, 987654321], 10, order="increasing")
Out[99]: 
array([[5, 5, 2, 0, 0, 0, 0, 0, 0],
       [1, 2, 3, 4, 5, 6, 7, 8, 9]])

In [100]: int2base([255, 987654321], 16)
Out[100]: 
array([[ 0,  0,  0,  0,  0,  0, 15, 15],
       [ 3, 10, 13, 14,  6,  8, 11,  1]])
Warren Weckesser
  • 110,654
  • 19
  • 194
  • 214
-1

Thank you Warren for this vectorized implementation.

I made another vectorized implementation based on yours that is more efficient.

def base_representation(x, base, size=None):
    x = np.asarray(x)
    if size is None:
        size = int(np.floor(np.log(np.max(x))/np.log(base)))+1
    arrays = []
    for _ in range(size):
        x, reminder = np.divmod(x, base)
        arrays.append(reminder)
    return np.stack(arrays, axis=-1)

Note that a bug on size has been corrected : the formula should be size=floor(log(x)/log(base))+1.

Best regards.