The folding of ternary operator in a list - python

The following code is available (demo)
f=lambda m, x:m and(x&1 and m.pop(0)or m.pop(0)[::-1])+f(m, x+1)
print(f([[4, 3, 2, 1], [5, 6, 7, 8], [12, 11, 10, 9], [13, 14, 15, 16]],0))
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
Here there is logic:
m.pop(0) if x&1 else m.pop(0)[::-1]
Please explain why when folding to the following view, is the code not executing correctly?
[m.pop(0)[::-1],m.pop(0)][x&1]
I don't know much about Python, will be grateful for any help, thank you.
UPD: If I change the logic, I get this result:
f=lambda m,x:m and([m.pop(0)[::-1],m.pop(0)][x&1])+f(m,x+1)
print(f([[4, 3, 2, 1], [5, 6, 7, 8], [12, 11, 10, 9], [13, 14, 15, 16]],0))
# [1, 2, 3, 4, 13, 14, 15, 16]
PS. Code-golf (If this is important, the essence of the code is that it bypasses the two-dimensional array in the form of a snake)
Solution:
[m.pop(0)[::-1],m.pop(0)][x&1] => (lambda: m.pop(0)[::-1], lambda: m.pop(0))[x&1]().
https://ideone.com/u6Qp4O

The issue is that the trinary if only evaluates one of its branch so only one pop call occurs. In your code there are two pop calls that both are evaluated.
[m.pop(0)[::-1], m.pop(0)][x&1]
To avoid that, if one must write it this way, and not using an a trinary A if C else B, one has to thunk, lambda:, each case and then after indexing the list of cases, call the case that was selected:
[lambda: m.pop(0)[::-1], lambda: m.pop(0)][x&1]()
One then could remove the common expression:
item = m.pop(0)
[lambda: item[::-1], lambda: item][x&1]()
Doing this before thunking, would result in:
item = m.pop(0)
[item[::-1], item][x&1]
This would not produce an error if item was slice-able. But it would uselessly produce and discard a reversed copy of item. But if the item is say an int when x is odd and a list when even this would result in an error:
>>> 3[::-1]
TypeError: 'int' object has no attribute '__getitem__'
Because all sub expressions in an expression are evaluated unless they have been delayed by a lambda.
A thunk is a term for a function that takes no arguments.

The short-circuiting behavior is different between all three versions of the expression given (if certain elements of m are false). The a and b or c idiom was commonly used before Python had a conditional operator, but is inequivalent if b is false.

Related

in python, How do I pass each value of a list seperately as variable lenght arguments in a function?

I want to pass each values of a list as separate arguments in a function that accepts variable length arguments(*args) .
When I pass each argument directly it works fine, but when I pass each argument as a separate value of a list, it does not give expected result.
For instance,
def foo(no,*values):
values=list(values)
for i in range(0,len(values)):
values[i]=values[i]*no
return(values)
print(foo(5,6,7,8,9,10))
This works fine and gives me my expected output as:
[30, 35, 40, 45, 50]
But this does not:
def foo(no,*values):
values=list(values)
for i in range(0,len(values)):
values[i]=values[i]*no
return(values)
h=[6,7,8,9,10]
print(foo(5,[i for i in h]))
This generates following output:
Actual Result:
[[6, 7, 8, 9, 10, 6, 7, 8, 9, 10, 6, 7, 8, 9, 10, 6, 7, 8, 9, 10, 6, 7, 8, 9, 10]]
Expected Result:
[30, 35, 40, 45, 50]
You can unpack lists using *:
def myfunc(no, *values):
...
arr = [1,2,3,4]
myfunc(5, *arr) # >>> myfunc(5, 1, 2, 3, 4)
Because [i for i in h] is returning a list: [6,7,8,9,10]
and multiplying a list, which you are essentially doing will just append the content of the list to itself.
To get the expected output you have to give your function just the contents of your list, so you can either call it via foo(5, *[i for i in h]) or just foo(5, *h).
However foo(5, *[i for i in h]) would be quite unnecessary.
When you call the function, you can use * operator which will unpack your list:
def foo(no,*values):
return [map(lambda i: no *i,values)]
foo(5,*[i for i in [6,7,8,9,10]])
#will return [30, 35, 40, 45, 50]
In pep-3132 you can find all the use-cases for iterable unpacking: https://www.python.org/dev/peps/pep-3132/

Order of execution in nested loops in python [duplicate]

This question already has answers here:
Why does python use 'else' after for and while loops?
(24 answers)
Why does this `else` block work yet it is not on the same level as the `if` case? [duplicate]
(3 answers)
Closed 3 years ago.
I cant understand the difference between those two code blocks, first the correct one is:
number_list=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
prime_list=[]
for i in number_list:
for j in range(2,i):
if i%j==0:
break
else: prime_list.append(i)
print('Primes are:',prime_list)
Output is:
Primes are: [1, 2, 3, 5, 7, 11, 13]
but moving the else statement forward inside the block below the if statement (which i thought was the right thing to do) results in a different and wrong output:
number_list=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
prime_list=[]
for i in number_list:
for j in range(2,i):
if i%j==0:
break
else: prime_list.append(i)
print('Primes are:',prime_list)
Output is:
Primes are: [3, 5, 5, 5, 7, 7, 7, 7, 7, 9, 11, 11, 11, 11, 11, 11, 11, 11, 11, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13]
Why is this so? how does the code behave with the else statement indented?
In the first example else is used for: for loop. It means that the code inside else statement is executed only if for loop ends without break.
So in your case only of any number from the range (2, i) is not a divider of i.
In the second case else is used for: if statement. It means that: if if is not true, then execute else statement. In this scenario let's assume i=5. For:
j = 2 => i%j = 1 => else statement is executed: prime_list.append(5)
j = 3 => i%j = 2 => else statement is executed: prime_list.append(5)
j = 4 => i%j = 1 => else statement is executed: prime_list.append(5)
And thats why you have 3 times 5 in your list.
The else in different scope has difference means:
else with the same scope of your for-loop is the else condition. It will be execute when not-break in your for loop.
else in the for-loop is used to exit the for loop in your second case. The else is related on your if statement
Because when moving in, it will loop and most of the case exit the first if statement, then since the else says to add the value, it would keep adding to it.
There is other ways to do this and more efficiently, like below:
prime_list=[i for i in number_list if all(i % x for x in range(2, i))][1:]
print('Primes are:',prime_list)

Working around evaluation time discrepancy in generators

I found myself running into the gotcha under 'evaluation time discrepancy' from this list today, and am having a hard time working around it.
As a short demonstration of my problem, I make infinite generators that skip every nth number, with n going from [2..5]:
from itertools import count
skip_lists = []
for idx in range(2, 5):
# skip every 2nd, 3rd, 4th.. number
skip_lists.append(x for x in count() if (x % idx) != 0)
# print first 10 numbers of every skip_list
for skip_list in skip_lists:
for _, num in zip(range(10), skip_list):
print("{}, ".format(num), end="")
print()
Expected output:
1, 3, 5, 7, 9, 11, 13, 15, 17, 19,
1, 2, 4, 5, 7, 8, 10, 11, 13, 14,
1, 2, 3, 5, 6, 7, 9, 10, 11, 13,
Actual output:
1, 2, 3, 5, 6, 7, 9, 10, 11, 13,
1, 2, 3, 5, 6, 7, 9, 10, 11, 13,
1, 2, 3, 5, 6, 7, 9, 10, 11, 13,
Once I remembered that great feature, I tried to "solve" it by binding the if clause variable to a constant that would be part of the skip_list:
from itertools import count
skip_lists = []
for idx in range(2, 5):
# bind the skip distance
skip_lists.append([idx])
# same as in the first try, but use bound value instead of 'idx'
skip_lists[-1].append(x for x in count() if (x % skip_lists[-1][0]) != 0)
# print first 10 numbers of every skip_list
for skip_list in (entry[1] for entry in skip_lists):
for _, num in zip(range(10), skip_list):
print("{}, ".format(num), end="")
print()
But again:
1, 2, 3, 5, 6, 7, 9, 10, 11, 13,
1, 2, 3, 5, 6, 7, 9, 10, 11, 13,
1, 2, 3, 5, 6, 7, 9, 10, 11, 13,
Apart from an actual solution, I would also love to learn why my hack didn't work.
The value of idx is never looked up until you start iterating on the generators (generators are evaluated lazily), at which point idx = 4 the latest iteratee value, is what is present in the module scope.
You can make each appended generator stateful in idx by passing idx to a function and reading the value from the function scope at each generator's evaluation time. This exploits the fact that the iterable source of a generator expression is evaluated at the gen. exp's creation time, so the function is called at each iteration of the loop, and idx is safely stored away in the function scope:
from itertools import count
skip_lists = []
def skip_count(skip):
return (x for x in count() if (x % skip) != 0)
for idx in range(2, 5):
# skip every 2nd, 3rd, 4th.. number
skip_lists.append(skip_count(idx))
Illustration of generator expression's iterable source evaluation at gen. exp's creation:
>>> (i for i in 5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
Your case is a bit trickier since the exclusions are actually done in a filter which is not evaluated at the gen exp's creation time:
>>> (i for i in range(2) if i in 5)
<generator object <genexpr> at 0x109a0da50>
The more reason why the for loop and filter all need to be moved into a scope that stores idx; not just the filter.
On a different note, you can use itertools.islice instead of the inefficient logic you're using to print a slice of the generator expressions:
from itertools import islice
for skip_list in skip_lists:
for num in islice(skip_list, 10):
print("{}, ".format(num), end="")
print()

Find lists which together contain all values from 0-23 in list of lists python

I have a list of lists. The lists within these list look like the following:
[0,2,5,8,7,12,16,18], [0,9,18,23,5,8,15,16], [1,3,4,17,19,6,13,23],
[9,22,21,10,11,20,14,15], [2,8,23,0,7,16,9,15], [0,5,8,7,9,11,20,16]
Every small list has 8 values from 0-23 and there are no value repeats within a small list.
What I need now are the three lists which have the values 0-23 stored. It is possible that there are a couple of combinations to accomplish it but I do only need one.
In this particular case the output would be:
[0,2,5,8,7,12,16,18], [1,3,4,17,19,6,13,23], [9,22,21,10,11,20,14,15]
I thought to do something with the order but I'm not a python pro so it is hard for me to handle all the lists within the list (to compare all).
Thanks for your help.
The following appears to work:
from itertools import combinations, chain
lol = [[0,2,5,8,7,12,16,18], [0,9,18,23,5,8,15,16], [1,3,4,17,19,6,13,23], [9,22,21,10,11,20,14,15], [2,8,23,0,7,16,9,15], [0,5,8,7,9,11,20,16]]
for p in combinations(lol, 3):
if len(set((list(chain.from_iterable(p))))) == 24:
print(p)
break # if only one is required
This displays the following:
([0, 2, 5, 8, 7, 12, 16, 18], [1, 3, 4, 17, 19, 6, 13, 23], [9, 22, 21, 10, 11, 20, 14, 15])
If it will always happen that 3 list will form numbers from 0-23, and you only want first list, then this can be done by creating combinations of length 3, and then set intersection:
>>> li = [[0,2,5,8,7,12,16,18], [0,9,18,23,5,8,15,16], [1,3,4,17,19,6,13,23], [9,22,21,10,11,20,14,15], [2,8,23,0,7,16,9,15], [0,5,8,7,9,11,20,16]]
>>> import itertools
>>> for t in itertools.combinations(li, 3):
... if not set(t[0]) & set(t[1]) and not set(t[0]) & set(t[2]) and not set(t[1]) & set(t[2]):
... print t
... break
([0, 2, 5, 8, 7, 12, 16, 18], [1, 3, 4, 17, 19, 6, 13, 23], [9, 22, 21, 10, 11, 20, 14, 15])
Let's do a recursive solution.
We need a list of lists that contain these values:
target_set = set(range(24))
This is a function that recursively tries to find a list of lists that match exactly that set:
def find_covering_lists(target_set, list_of_lists):
if not target_set:
# Done
return []
if not list_of_lists:
# Failed
raise ValueError()
# Two cases -- either the first element works, or it doesn't
try:
first_as_set = set(list_of_lists[0])
if first_as_set <= target_set:
# If it's a subset, call this recursively for the rest
return [list_of_lists[0]] + find_covering_lists(
target_set - first_as_set, list_of_lists[1:])
except ValueError:
pass # The recursive call failed to find a solution
# If we get here, the first element failed.
return find_covering_lists(target_set, list_of_lists[1:])

Sample k random permutations without replacement in O(N)

I need a number of unique random permutations of a list without replacement, efficiently. My current approach:
total_permutations = math.factorial(len(population))
permutation_indices = random.sample(xrange(total_permutations), k)
k_permutations = [get_nth_permutation(population, x) for x in permutation_indices]
where get_nth_permutation does exactly what it sounds like, efficiently (meaning O(N)). However, this only works for len(population) <= 20, simply because 21! is so mindblowingly long that xrange(math.factorial(21)) won't work:
OverflowError: Python int too large to convert to C long
Is there a better algorithm to sample k unique permutations without replacement in O(N)?
Up to a certain point, it's unnecessary to use get_nth_permutation to get permutations. Just shuffle the list!
>>> import random
>>> l = range(21)
>>> def random_permutations(l, n):
... while n:
... random.shuffle(l)
... yield list(l)
... n -= 1
...
>>> list(random_permutations(l, 5))
[[11, 19, 6, 10, 0, 3, 12, 7, 8, 16, 15, 5, 14, 9, 20, 2, 1, 13, 17, 18, 4],
[14, 8, 12, 3, 5, 20, 19, 13, 6, 18, 9, 16, 2, 10, 4, 1, 17, 15, 0, 7, 11],
[7, 20, 3, 8, 18, 17, 4, 11, 15, 6, 16, 1, 14, 0, 13, 5, 10, 9, 2, 19, 12],
[10, 14, 5, 17, 8, 15, 13, 0, 3, 16, 20, 18, 19, 11, 2, 9, 6, 12, 7, 4, 1],
[1, 13, 15, 18, 16, 6, 19, 8, 11, 12, 10, 20, 3, 4, 17, 0, 9, 5, 2, 7, 14]]
The odds are overwhelmingly against duplicates appearing in this list for len(l) > 15 and n < 100000, but if you need guarantees, or for lower values of len(l), just use a set to record and skip duplicates if that's a concern (though as you've observed in your comments, if n gets close to len(l)!, this will stall). Something like:
def random_permutations(l, n):
pset = set()
while len(pset) < n:
random.shuffle(l)
pset.add(tuple(l))
return pset
However, as len(l) gets longer and longer, random.shuffle becomes less reliable, because the number of possible permutations of the list increases beyond the period of the random number generator! So not all permutations of l can be generated that way. At that point, not only do you need to map get_nth_permutation over a sequence of random numbers, you also need a random number generator capable of producing every random number between 0 and len(l)! with relatively uniform distribution. That might require you to find a more robust source of randomness.
However, once you have that, the solution is as simple as Mark Ransom's answer.
To understand why random.shuffle becomes unreliable for large len(l), consider the following. random.shuffle only needs to pick random numbers between 0 and len(l) - 1. But it picks those numbers based on its internal state, and it can take only a finite (and fixed) number of states. Likewise, the number of possible seed values you can pass to it is finite. This means that the set of unique sequences of numbers it can generate is also finite; call that set s. For len(l)! > len(s), some permutations can never be generated, because the sequences that correspond to those permutations aren't in s.
What are the exact lengths at which this becomes a problem? I'm not sure. But for what it's worth, the period of the mersenne twister, as implemented by random, is 2**19937-1. The shuffle docs reiterate my point in a general way; see also what Wikipedia has to say on the matter here.
Instead of using xrange simply keep generating random numbers until you have as many as you need. Using a set makes sure they're all unique.
permutation_indices = set()
while len(permutation_indices) < k:
permutation_indices.add(random.randrange(total_permutations))
I had one implementation of nth_permutation (not sure from where I got it) which I modified for your purpose. I believe this would be fast enough to suit your need
>>> def get_nth_permutation(population):
total_permutations = math.factorial(len(population))
while True:
temp_population = population[:]
n = random.randint(1,total_permutations)
size = len(temp_population)
def generate(s,n,population):
for x in range(s-1,-1,-1):
fact = math.factorial(x)
d = n/fact
n -= d * fact
yield temp_population[d]
temp_population.pop(d)
next_perm = generate(size,n,population)
yield [e for e in next_perm]
>>> nth_perm = get_nth_permutation(range(21))
>>> [next(nth_perm) for k in range(1,10)]
You seem to be searching for the Knuth Shuffle! Good luck!
You could use itertools.islice instead of xrange():
CPython implementation detail: xrange() is intended to be simple and
fast Implementations may impose restrictions to achieve this. The C
implementation of Python restricts all arguments to native C longs
(“short” Python integers), and also requires that the number of
elements fit in a native C long. If a larger range is needed, an
alternate version can be crafted using the itertools module:
islice(count(start, step), (stop-start+step-1+2*(step<0))//step).

Categories