Decompress an array in Python - python

I need to decompress an array and I am not sure where to start.
Here is the input of the function
def main():
# Test case for Decompress function
B = [6, 2, 7, 1, 3, 5, 1, 9, 2, 0]
A = Decompress(B)
print(A)
I want this to come out
A = [2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 9, 0, 0]
If you can't see the pattern, B[1] is how many times B[2] shows up in A[], and then B[3] is how many times B[4] shows up in A[], and so on.
How do I write a function for this?

Compact version with zip() and itertools.chain.from_iterable:
from itertools import chain
list(chain.from_iterable([v] * c for c, v in zip(*([iter(B)]*2))))
Demo:
>>> B = [6, 2, 7, 1, 3, 5, 1, 9, 2, 0]
>>> from itertools import chain
>>> list(chain.from_iterable([v] * c for c, v in zip(*([iter(B)]*2))))
[2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 9, 0, 0]
Breaking this down:
zip(*([iter(B)]*2))) pairs counts with values:
>>> zip(*([iter(B)]*2))
[(6, 2), (7, 1), (3, 5), (1, 9), (2, 0)]
It is a fairly standard Python trick to get pairs out of a input iterable.
([v] * c for c, v in zip(*([iter(B)]*2))) is a generator expression that takes the counts and values and produces lists with the value repeated count times:
>>> next([v] * c for c, v in zip(*([iter(B)]*2)))
[2, 2, 2, 2, 2, 2]
chain.from_iterable takes the various lists produced by the generator expression and lets you iterate over them as if they were one long list.
list() turns it all back to a list.

def unencodeRLE(i):
i = list(i) #Copies the list to a new list, so the original one is not changed.
r = []
while i:
count = i.pop(0)
n = i.pop(0)
r+= [n for _ in xrange(count)]
return r

One more one-liner:
def decompress(vl):
return sum([vl[i] * [vl[i+1]] for i in xrange(0, len(vl), 2)], [])
A list comprehension extracts and unpacks pairs (xrange(0, len(vl), 2) iterates through start indices of pairs, vl[i] is a number of repetitions, vl[i+1] is what to repeat).
sum() joins the results together ([] is the initial value the unpacked lists are sequentially added to).

A slightly faster solution (with Python 2.7.3):
A=list(chain.from_iterable( [ B[i]*[B[i+1]] for i in xrange(0,len(B),2) ] ) )
>>> timeit.Timer(
setup='B=[6,2,7,1,3,5,1,9,2,0];from itertools import chain',
stmt='A=list(chain.from_iterable( [ B[i]*[B[i+1]] for i in xrange(0,len(B),2) ] ) )').timeit(100000)
0.22841787338256836
Comparing with:
>>> timeit.Timer(
setup='B=[6,2,7,1,3,5,1,9,2,0];from itertools import chain',
stmt='A=list(chain.from_iterable([v] * c for c, v in zip(*([iter(B)]*2))))').timeit(100000)
0.31104111671447754

Related

Python inception of generators

I'm trying to make a general way to generate all the combinations of multiple ranges or lists, for example,
[range(0, 2), range(2, 5), range(4, 6), range(2, 3)],
which should return a 2x3x2x1 = 12 element list.
[[0, 2, 4, 2],
[0, 2, 5, 2],
[0, 3, 4, 2],
[0, 3, 5, 2],
[0, 4, 4, 2],
[0, 4, 5, 2],
[1, 2, 4, 2],
[1, 2, 5, 2],
[1, 3, 4, 2],
[1, 3, 5, 2],
[1, 4, 4, 2],
[1, 4, 5, 2]]
So far, everything is fine here. When I hardcode it, by doing
x = [ ( [a,b] for a in rgs[0] for b in rgs[1] ) ]
x.append( ( a + [b] for a in x[-1] for b in rgs[2]) )
x.append( ( a + [b] for a in x[-1] for b in rgs[3]) )
I get the good result. However, when I attempt to generalize it, by doing
x = [ ( [a,b] for a in rgs[0] for b in rgs[1] ) ]
for i in range(1,len(rgs)-1):
x.append( ( a + [b] for a in x[-1] for b in rgs[i+1]) )
I obtain a 6-element list:
[[0, 2, 2, 2],
[0, 3, 2, 2],
[0, 4, 2, 2],
[1, 2, 2, 2],
[1, 3, 2, 2],
[1, 4, 2, 2]]
Also, what I notice is that all the ranges generated after the first two use the range in rgs[-1] instead of the correct ones. I struggle to understand why this happens, as I beleive that those two code example are identical, except the latter is the more general form for arbitrary large number of ranges.
You can use itertools.product to output a list of tuples
Input:
import itertools
a= [range(0, 2), range(2, 5), range(4, 6), range(2, 3)]
list(itertools.product(*a))
Output:
[(0, 2, 4, 2),
(0, 2, 5, 2),
(0, 3, 4, 2),
(0, 3, 5, 2),
(0, 4, 4, 2),
(0, 4, 5, 2),
(1, 2, 4, 2),
(1, 2, 5, 2),
(1, 3, 4, 2),
(1, 3, 5, 2),
(1, 4, 4, 2),
(1, 4, 5, 2)]
I did not get the same result when running your first code. I had to change it up a bit:
x = [ ( [a,b] for a in rgs[0] for b in rgs[1] ) ]
x.append( ( a + [b] for a in x[-1] for b in rgs[2]) )
x = list( a + [b] for a in x[-1] for b in rgs[3])
Most people that don't know about itertools would have done it this way:
x=[]
for i0 in rgs[0]:
for i1 in rgs[1]:
for i2 in rgs[2]:
for i3 in rgs[3]:
x.append([i0,i1,i2,i3])
Or using a list comprehension (DON'T DO THIS, it is VERY messy looking):
[[i0,i1,i2,i3] for i3 in rgs[3] for i2 in rgs[2] for i1 in rgs[1] for i0 in rgs[0]]
Your issue has to do with creating generator expressions in a loop. Generator expressions are implemented as functions, and like functions, they can have "free" variables that they look up in the containing namespaces. Your generator expressions are accessing the i from outside their definition, and because of this, they end up seeing a different i value you expect.
Here's an example that might be easier to understand:
def gen()
print(i)
yield 10
x = []
for i in range(3):
x.append(gen()) # add several generators while `i` has several different values
for g in x:
list(g) # consume the generators, so they can print `i`
Here, rather than using the i value for something useful, I've written a generator function that just prints it out. If you run this code, you'll see that all the generators print out 2, since that's the value of i when they finally run (after the first loop ended).
Your situation is a little more subtle, as you're consuming the previous generator as you create the next one, but the general idea is the same. The generator expression loop that you expect to be over rgs[2] is actually over rgs[3] because it's actually being looked up with rgs[i+1] and i increased between the time the generator expression was declared and when it was consumed.

List of consecutive numbers repeated according to values in another list

My target is to get a list of consecutive numbers, repeated accordingly with the initial list values. Lets say I have:
initialList=[1,2,3,5]
And I want to get:
targetList=[0,1,1,2,2,2,3,3,3,3,3]
...I'm totally new with Python, sorry for this -probably- very first steps question. Tried many searchs but the results didn't match with my needs, unfortunately. Thank you very much in advance.
The newbie-friendly solution is to use two loops:
result = []
number = 0
for repeat in initialList:
for _ in range(repeat):
result.append(number)
number += 1
print(result) # [0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3]
If you prefer one-liners for whatever reason, you can combine enumerate and range to get
result = [num for num, repeat in enumerate(initialList) for _ in range(repeat)]
IMO, this is a more maintainable functional solution:
initialList = [1, 2, 3, 5]
def listify(x):
return [x]
# create sub-lists [[0], [1], [2], [3], ...]
sublists = map(listify, range(len(initialList)))
# attach to each sub-list the repititions required [([0], 1), ([2], 2), ...]
sublists_with_rep_spec = zip(sublists, initialList)
# create repetitions based on initialList (using list multiplication)
sublists_with_repetitions = starmap(operator.mul, sublists_with_rep_spec)
# flatten everything out
result = chain.from_iterable(sublists_with_repetitions)
print(list(result))
Note that this is all lazy (on python3) so everything will "happen" only when you actually call list.
Here is another way using repeat and chain.from_iterable
from itertools import repeat, chain
list(chain.from_iterable((repeat(idx, num)) for idx, num in enumerate(initialList)))
[0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3]
You can use enumerate:
initialList=[1,2,3,5]
final_result = [i for b in [[c]*d for c, d in enumerate(initialList)] for i in b]
Output:
[0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3]
This is possible via itertools, if you wish to remove the need for nested logic. itertools is part of the standard library.
For improving your understanding of Python, I recommend you see #Ajax1234's solution for some nested list comprehensions.
from itertools import chain
initialList = [1,2,3,5]
targetList = list(chain.from_iterable([i]*j for i, j in enumerate(initialList)))
# [0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3]
Note: you can replace [i]*j with itertools.repeat(i, j) or numpy.repeat(i, j) if you use numpy. The former may be preferable as it is lazy.
Very simple solution using sum and enumerate
initialList = [1, 2, 3, 5]
targetList = sum((times*[index] for index, times in enumerate(initialList)), [])
You can try this approach:
data=[[i]*initialList[i] for i,j in enumerate(initialList)]
print([k for i in data for k in i])
Just for fun I tried with lambda :
initialList=[1,2,3,5]
print(list(map(lambda x:[x]*initialList[x],range(0,len(initialList)))))
lambda result is in nested list.
My solution
>>> initialList=[1,2,3,5]
>>> sum(([num]*count for num, count in enumerate(initialList)), [])
[0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3]
Another easy way:
from functools import reduce
initialList = [1,2,3,5]
targetList = [[index]*item for index, item in enumerate(initialList)]
targetList = reduce(lambda x,y: x+y, targetList)
print(targetList)
# [0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3]
I find most of the current answers either poor performance-wise or hard to read. An alternative functional way of doing this would be by using such itertools functions as chain.from_iterable, repeat, and count:
from itertools import chain, count, repeat
initial_list = [1, 2, 3, 5]
result = list(chain.from_iterable(map(repeat, count(), initial_list)))
# [0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3]

Repeat list if index range is out of bounds

I have a Python list
a = [1, 2, 3, 4]
and I'd like to get a range of indices such that if I select the indices 0 through N, I'm getting (for N=10) the repeated
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2]
I could of course repeat the list via (int(float(N) / len(a) - 0.5) + 1) * a first and select the range [0:10] out of that, but that feels rather clumsy.
Any hints?
You can simply use the modulo operator when accessing the list, i.e.
a[i % len(a)]
This will give you the same result, but doesn't require to actually store the redundant elements.
You can use itertools.cycle and itertools.islice:
from itertools import cycle, islice
my_list = list(islice(cycle(my_list), 10))
Note that if you just want to iterate over this once, you should avoid calling list and just iterate over the iterable, since this avoids allocating repeated elements.
One easy way is to use modulo with list comprehensions à la
a = [1, 2, 3 ,4]
[k % len(a) for k in range(10)]
>>> a = [1, 2, 3, 4]
>>> (a*3)[:-2]
>>> [1, 2, 3, 4, 1, 2, 3, 4, 1, 2]
Thought I would offer a solution using the * operator for lists.
import math
def repeat_iterable(a, N):
factor = N / len(a) + 1
repeated_list = a * factor
return repeated_list[:N]
Sample Output:
>>> print repeat_iterable([1, 2, 3, 4], 10)
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2]
>>> print repeat_iterable([1, 2, 3, 4], 3)
[1, 2, 3]
>>> print repeat_iterable([1, 2, 3, 4], 0)
[]
>>> print repeat_iterable([1, 2, 3, 4], 14)
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2]
How about faking it? Python is good at faking.
class InfiniteList(object):
def __init__(self, data):
self.data = data
def __getitem__(self, i):
return self.data[i % len(self.data)]
x = InfiniteList([10, 20, 30])
x[0] # 10
x[34] # 20
Of course, you could add __iter__, support for slices etc. You could also add a limit (N), but this is the general idea.

How to define duplicate items in a Python tuple?

What are some good ways to define a tuple consisting of integers where the number of occurrences of each item is known ?
For example,
I want to define a tuple with 3 2's, 2 4's and 1, 3, 5 occur once.
For this, I can always go the manual way :
foo = (1, 2, 2, 2, 3, 4, 4, 5)
However, this becomes a bit messy when the number of items in the list is large.
So, I want to know what are some ways to automate the task of generating the desired number of duplicates of each item.
You can do it like this:
>>> (1,) * 1 + (2,) * 3 + (4,) * 2 + (5,) * 1
(1, 2, 2, 2, 4, 4, 5)
One way is to use sequence multiplication. Here's a simple version that makes no attempt to avoid creating unnecessary intermediate objects:
accumulator = ()
for (val, count) in some_data_structure:
accumulator += (val,) * count
That can be improved, the main point is to demonstrate that (1,) * 5 gives you (1, 1, 1, 1, 1). Note that this copies the object reference - that's fine for integers, but can cause confusion if you're trying to multiply a sequence of mutable objects.
If you have a tuple of tuples denoting the value and frequency, you can do the following:
tuples = ((1,1), (2,3), (3,1), (4,2), (5,1))
tuple(i for i, n in tuples for _ in range(n)) # Use xrange in Python 2.X
# (1, 2, 2, 2, 3, 4, 4, 5)
Or, if you know that the values are always going to be 1, 2, 3, ..., n, you can use enumerate with a tuple of the frequencies.
freqs = (1, 3, 1, 2, 1)
tuple(i for i, n in enumerate(freqs, 1) for _ in range(n))
# (1, 2, 2, 2, 3, 4, 4, 5)
If you're curious about the use of the double comprehension in the generator expression, you may want to check out this question.
If your tuple has not many number, you can do it in the simplest way.
(1,)+(2,)*3+(3,)+(4,)*2+(5,)
Otherwise, just turn it into a function.
def myTuple(*val):
return sum(((i,) * n for i, n in val), ())
myTuple((1,1),(2,3),(3,1),(4,2),(5,1))
>>>(1, 2, 2, 2, 3, 4, 4, 5)
you can also call it with:
val = ((1,1),(2,3),(3,1),(4,2),(5,1))
myTuple(*val)
>>>(1, 2, 2, 2, 3, 4, 4, 5)
Something like this could work:
>>> result = tuple()
>>> for item, repeat in ((1, 1), (2, 3), (3, 1), (4, 2), (5, 1)):
... result = result + (item,) * repeat
>>> result
(1, 2, 2, 2, 3, 4, 4, 5)
So you want the inverse function of collections.Counter. Here is how you could do it,
# make a dict of counts (list of tuples is better)
counts = {1: 1, 2: 3, 4: 2, 3:1, 5: 1}
t = tuple(k for k,v in sorted(counts.items()) for _ in range(v))
(1, 2, 2, 2, 3, 4, 4, 5)
# for k,v in list_of_tuples, for a list of tuples
You can define the following function
def a_tuple(*data):
l = []
for i, cnt in data: l.extend([i]*cnt)
return tuple(l)
and use it like this
print(a_tuple((1,1), (2,3), (3,1), (4,2), (5,1)))
to produce the following output
(1, 2, 2, 2, 3, 4, 4, 5)
Have a look to the .extend() method of list if you don't understand how the function works.

Reorder Python List

I have a list of 4 items like this:
a, b, c, d = [1, 2, 3, 4]
I'm reordering the list, flipping each pair:
[b, a, d, c]
Is there a way to do this in one expression? I've tried using list comprehension and unpacking, but can't seem to get it right.
I have [1, 2, 3, 4]. I'm trying to get [2, 1, 4, 3].
More generically, if you're looking to flip pairs of numbers in a list:
>>> L = [1, 2, 3, 4, 5, 6]
>>> from itertools import chain
>>> list(chain.from_iterable(zip(L[1::2], L[::2])))
[2, 1, 4, 3, 6, 5]
Look at this:
>>> lst = [1, 2, 3, 4]
>>> [y for x in zip(*[iter(lst)]*2) for y in x[::-1]]
[2, 1, 4, 3]
>>>
>>> lst = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> [y for x in zip(*[iter(lst)]*2) for y in x[::-1]]
[2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
>>>
If this is only about 4 member lists - this would suffice:
list = [1, 2, 3, 4]
reordered_list = [list[1], list[0], list[3],list[2]]
Because absolutely nobody has given an answer that works on generic iterables,
from itertools import chain
items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
zip(*[iter(items)]*2)
#>>> <zip object at 0x7fd673afd050>
[itms for itms in zip(*[iter(items)]*2)]
#>>> [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]
So zip(*[iter(x)]*2) means ix = iter(x); zip(ix, ix) which pairs each element.
Then you can reverse:
[(y, x) for (x, y) in zip(*[iter(items)]*2)]
#>>> [(2, 1), (4, 3), (6, 5), (8, 7), (10, 9)]
Putting it all together and flattening:
[itm for (x, y) in zip(*[iter(items)]*2) for itm in (y, x)]
#>>> [2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
It's generic and short!
If you want something faster at the expense of genericism, you'll be hard pressed to better this:
new = list(items)
new[::2], new[1::2] = new[1::2], new[::2]
new
#>>> [2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
Note that this still works on arbitrary iterables, but there are fewer layers of abstraction; you can't bump up the size of the flipped sub-lists as easily and can't output iterables, etc.
Do you mean this:
>>> a, b, c, d = [1, 2, 3, 4]
>>> b, a, d, c = a, b, c, d
>>> a
2
>>> b
1
>>> c
4
>>> d
3
?
Try this list comprenhension solution:
a = [1,2,3,4,5,6] # Any list with even number of elements
b = [a[e+1] if (e%2 == 0) else a[e-1] for e in range(len(a))]
This just works if the list a have an even number of elements.
In [1]: l = [1, 2, 3, 4]
In [2]: list(chain(*map(reversed, zip(l[::2], l[1::2]))))
Out[2]: [2, 1, 4, 3]
Am I missing something? Reorder given_list with a loop:
rez = []
for i in range(len(given_list)-1, -1, -1):
rez.append(given_list[i])
return rez

Categories