Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Encode array of integers into unique int

I have a fixed amount of int arrays of the form:

[a,b,c,d,e]

for example:

[2,2,1,1,2]

where a and b can be ints from 0 to 2, c and d can be 0 or 1, and e can be ints from 0 to 2.

Therefore there are: 3 * 3 * 2 * 2 * 3: 108 possible arrays of this form.

I would like to assign to each of those arrays a unique integer code from 0 to 107.

I am stuck, i thought of adding each numbers in the array, but two arrays such as:

[0,0,0,0,1] and [1,0,0,0,0]

would both add to 1.

Any suggestion?

Thank you.

like image 289
user2505650 Avatar asked Jan 23 '18 22:01

user2505650


4 Answers

You could use np.ravel_multi_index:

>>> np.ravel_multi_index([1, 2, 0, 1, 2], (3, 3, 2, 2, 3))
65

Validation:

>>> {np.ravel_multi_index(j, (3, 3, 2, 2, 3)) for j in itertools.product(*map(range, (3,3,2,2,3)))} == set(range(np.prod((3, 3, 2, 2, 3))))
True

Going back the other way:

>>> np.unravel_index(65, dims=(3, 3, 2, 2, 3))
(1, 2, 0, 1, 2)
like image 90
Paul Panzer Avatar answered Oct 20 '22 00:10

Paul Panzer


Just another way, similar to Horner's method for polynomials:

>>> array = [1, 2, 0, 1, 2]
>>> ranges = (3, 3, 2, 2, 3)
>>> reduce(lambda i, (a, r): i * r + a, zip(array, ranges), 0)
65

Unrolled that's ((((0 * 3 + 1) * 3 + 2) * 2 + 0) * 2 + 1) * 3 + 2 = 65.

like image 30
Stefan Pochmann Avatar answered Oct 20 '22 01:10

Stefan Pochmann


This is a little like converting digits from a varying-size number base to a standard integer. In base-10, you could have five digits, each from 0 to 9, and then you would convert them to a single integer via i = a*10000 + b*1000 + c*100 + d*10 + e*1.

Equivalently, for the decimal conversion, you could write i = np.dot([a, b, c, d, e], bases), where bases = [10*10*10*10, 10*10*10, 10*10, 10, 1].

You can do the same thing with your bases, except that your positions introduce multipliers of [3, 3, 2, 2, 3] instead of [10, 10, 10, 10, 10]. So you could set bases = [3*2*2*3, 2*2*3, 2*3, 3, 1] (=[36, 12, 6, 3, 1]) and then use i = np.dot([a, b, c, d, e], bases). Note that this will always give answers in the range of 0 to 107 if a, b, c, d, and e fall in the ranges you specified.

To convert i back into a list of digits, you could use something like this:

digits = []
remainder = i
for base in bases:
    digit, remainder = divmod(remainder, base)
    digits.append(digit)

On the other hand, to keep your life simple, you are probably better off using Paul Panzer's answer, which pretty much does the same thing. (I never thought of an n-digit number as the coordinates of a cell in an n-dimensional grid before, but it turns out they're mathematically equivalent. And np.ravel is an easy way to assign a serial number to each cell.)

like image 3
Matthias Fripp Avatar answered Oct 20 '22 01:10

Matthias Fripp


This data is small enough that you may simply enumerate them:

>>> L = [[a,b,c,d,e] for a in range(3) for b in range(3) for c in range(2) for d in range(2) for e in range(3)]
>>> L[0]
[0, 0, 0, 0, 0]
>>> L[107]
[2, 2, 1, 1, 2]

If you need to go the other way (from the array to the integer) make a lookup dict for it so that you will get O(1) instead of O(n):

>>> lookup = {tuple(x): i for i, x in enumerate(L)}
>>> lookup[1,1,1,1,1]
58
like image 1
wim Avatar answered Oct 20 '22 01:10

wim