Elegant multdimensional loops in python [duplicate] - python

This question already has answers here:
Dynamic For Loops in Python
(2 answers)
Closed 2 years ago.
I wanted to calculate which whole numbers can be expressed by the square sum of three whole numbers. In my case until i,j,k all equal 4.
The (inelegant) code I used for this purpose is as follows:
nums = []
for i in range(4):
for j in range(4):
for k in range(4):
nums.append(i*i + j*j + k*k)
Is there a more pythonic or more elegant solution to this problem?
Ideally for as many variables (i, j, k, ..., n) as i want to.

You can use itertools.product to do this:
from itertools import product
for i, j, k in product(range(4), repeat=3):
# do something with i, j and k
If you don't know in advance how many variables you would need, you don't have to unpack the product into i, j and k. You Can leave it as:
for nums in product(range(4), repeat=3):
# do something with nums

If you want to do this for larger values, I would suggest using itertools
import itertools
def sumsq(x):
return sum(y * y for y in x)
max_integer = 4
num_terms = 3
num = [sumsq(x) for x in itertools.combinations_with_replacement(range(1, max_integer + 1), num_terms)]

Can be done in one line using list comprehension as:
l = [i*i + j*j + k*k for i in range(4) for j in range(4) for k in range(4)]

Related

updating a variable inside an if condition within a list comprehension

i tried my best to write the following code in a list comprehension but ended up getting wrong
for i in range(1,n+1):
L.append(i+(k*sin))
if(i % (k) == 0):
sin*=-1
return [i+(k*sin) if(i % (k*2) == 0) sin*=-1 for i in range(1,n+1)]
You can do this with a list comprehension, but you should take a different approach.
Let's say k == 3: then your sequence will look like this:
1 + k*sin
2 + k*sin
3 + k*sin
4 + -k*sin
5 + -k*sin
6 + -k*sin
7 + k*sin
8 + k*sin
9 + k*sin
...
Basically, you just want a list comprehension that looks like
[i + n*sin for i, n in zip(range(1, n+1), ???)]
The question is, what do we replace ??? with to get our sequence of repeating k and -k values?
The itertools module provides several tools for generating this:
repeat - repeat a value some number of times
chain - concatenate two or more sequences into a single sequence
cycle - cycle through the values in a sequence repeatedly
In this case, it's
cycle(chain(repeat(k, k), repeat(-k, k)))
giving you
from itertools import repeat, chain, cycle
[i + n*sin for i, n in zip(range(1, n+1), cycle(chain(repeat(k, k), repeat(-k, k)))]
zip ensures we only take a finite number of values from the infinite sequence.
Another approach is to use another tool from itertool, islice, to take only the first n values from the cycle. Then we don't need range anymore; we can use enumerate to number those values from 1 to n instead.
from itertools import repeat, chain, cycle, islice
[i + n*sin for i, n in enumerate(islice(cycle(chain(repeat(k, k), repeat(-k, k))), n), start=1]
Both read better if you define ks first:
ks = cycle(chain(repeat(k, k), repeat(-k, k)))
[i + n*sin for i, n in zip(range(1, n+1), ks)]
or
ks = cycle(chain(repeat(k, k), repeat(-k, k)))
[i + n*sin for i, n in enumerate(islice(ks, n), start=1)]
You can also optimize this a bit by repeating the value of k*sin, rather than performing the multiplication for each element of the list.
ks = cycle(chain(repeat(k*sin, k), repeat(-k*sin, k)))
[i + n for i, n in ...] # using either zip or enumerate as before
What you want is to mathematically formulate your problem a little bit better, than it can easily be represented as a list comprehension.
In essence, you want the a formula for the signal of sin which is dependent on the index i and on k that will invert the signal every k. That is represented by (-1) ** (i - 1) // k.
So, if you include that into your code, you should arrive at exactly what you want:
return [i + (k * ((-1) ** ((i - 1) // k)) * sin) for i in range(1,n+1)]
Hope that helps!

List of length n with p randomly allocated ones else zero [duplicate]

This question already has answers here:
Insert in a list random of 0 and 1 with a certain number of 1's
(4 answers)
Closed 3 years ago.
What's the shortest way (as in short code) to get a list of length n with p < n randomly allocated ones else zero. Say n = 6 and p = 2, I'd like something like [0,1,0,0,1,0].
First create a list with the correct number of 1s and 0s and then shuffle it:
import random
def random_ones(n, p):
ls = [1] * p + [0] * (n - p)
random.shuffle(ls)
return ls
You could sample the indices:
from random import sample
l = [0] * n
idxs = sample(range(n), p)
for idx in idxs:
l[idx] = 1
Or shorter:
from random import sample
idxs = sample(range(n), p)
l = [1 if i in idxs else 0 for i in range(n)]
This is perhaps not the most efficient way of doing it, but I feel it's a little simpler and easier to understand, so hopefully you'll be able to use it in future. :)
from random import randint
list,counter=[],0
for i in range(n):
list.append(1 if randint(1,2)==2 and counter!=p else 0)
counter+=1

Finding two integers that multiply to 20. Can I make this code more "pythonic"?

I did this code that finds two integers in a said list (in this case [2,4,5,1,6,40,-1]) that multiply to twenty. I got a little stuck in the beginning, but adding a function to it solved my problems. I showed this code to a friend of mine who's a programmer and he said I could make this code more "pythonic", but I have no clue how.
Here's the code:
num_list = [2,4,5,1,6,40,-1]
def get_mult_num(given_list):
for i in given_list:
for j in range(i+1, len(given_list)): #for j not to be == i and to be in the list
mult_two_numbers = i * j
if mult_two_numbers == 20:
return i,j
print(get_mult_num(num_list))
I don't necessarily think it is 'unpythonic', you are using standard Python idioms to loop over your data and produce a single result or None. The term Pythonic is nebulous, a subject marred in "I know it when I see it" parameters.
Not that you produced a correct implementation. While i loops over given_numbers, j loops over an integer from i + 2 through to len(given_numbers), mixing values from given_list with indices? For your sample input, you are taking j from the half-open ranges [4, 7), [6, 7), [7, 7) (empty), [3, 7), [8, 7) (empty), [42, 7) (empty) and [1, 7), respectively. That it produces the correct answer at all is luck, not due to correctness; if you give your function the list [2, 10], it'll not find a solution! You want to loop over given_numbers again, limited with slicing, or generate indices starting at the current index of i, but then your outer loop needs to add a enumerate() call too:
for ii, i in enumerate(given_numbers):
for j in given_numbers[ii + 1:]:
# ...
or
for ii, i in enumerate(given_numbers):
for jj in range(ii + 1, len(given_numbers)):
j = given_numbers[jj]
# ...
All this is not nearly as efficient as it can be; the Python standard library offers you the tools to generate your i, j pairs without a nested for loop or slicing or other forms of filtering.
Your double loop should generate combinations of the integer inputs, so use the itertools.combinations() object to generate unique i, j pairs:
from itertools import combinations
def get_mult_num(given_list):
return [(i, j) for i, j in combinations(given_list, 2) if i * j == 20]
This assumes there can be zero or more such solutions, not just a single solution.
If you only ever need the first result or None, you can use the next() function:
def get_mult_num(given_list):
multiplies_to_20 = (
(i, j) for i, j in combinations(given_list, 2)
if i * j == 20)
return next(multiplies_to_20, None)
Next, rather than produce all possible combinations, you may want to invert the problem. If you turn given_list into a set, you can trivially check if the target number 20 can be divided cleanly without remainder by any of your given numbers and where the result of the division is larger and is also an integer in the set of numbers. That gives you an answer in linear time.
You can further limit the search by dividing with numbers smaller than the square root of the target value, because you won't find a larger value to match in your input numbers (given a number n and it's square root s, by definition s * (s + 1) is going to be larger than n).
If we add an argument for the target number to the function and make it a generator function, then you get:
def gen_factors_for(target, numbers):
possible_j = set(numbers)
limit = abs(target) ** 0.5
for i in numbers:
if abs(i) < limit and target % i == 0:
j = target // i
if j in possible_j and abs(j) > abs(i):
yield i, j
This approach is a lot faster than testing all permutations, especially if you need to find all possible factors. Note that I made both functions generators here to even out the comparisons:
>>> import random, operator
>>> from timeit import Timer
>>> def gen_factors_for_division(target, numbers):
... possible_j = set(numbers)
... limit = abs(target) ** 0.5
... for i in numbers:
... if abs(i) < limit and target % i == 0:
... j = target // i
... if j in possible_j and abs(j) > abs(i):
... yield i, j
...
>>> def gen_factors_for_combinations(target, given_list):
... return ((i, j) for i, j in combinations(given_list, 2) if i * j == target)
...
>>> numbers = [random.randint(-10000, 10000) for _ in range(100)]
>>> targets = [operator.mul(*random.sample(set(numbers), 2)) for _ in range(5)]
>>> targets += [t + random.randint(1, 100) for t in targets] # add likely-to-be-unsolvable numbers
>>> for (label, t) in (('first match:', 'next({}, None)'), ('all matches:', 'list({})')):
... print(label)
... for f in (gen_factors_for_division, gen_factors_for_combinations):
... test = t.format('f(t, n)')
... timer = Timer(
... f"[{test} for t in ts]",
... 'from __main__ import targets as ts, numbers as n, f')
... count, total = timer.autorange()
... print(f"{f.__name__:>30}: {total / count * 1000:8.3f}ms")
...
first match:
gen_factors_for_division: 0.219ms
gen_factors_for_combinations: 4.664ms
all matches:
gen_factors_for_division: 0.259ms
gen_factors_for_combinations: 3.326ms
Note that I generate 10 different random targets, to try to avoid a lucky best-case-scenario hit for either approach.
[(i,j) for i in num_list for j in num_list if i<j and i*j==20]
This is my take on it, which uses enumerate:
def get_mult_num(given_list):
return [
item1, item2
for i, item1 in enumerate(given_list)
for item2 in given_list[:i]
if item1*item2 == 20
]
I think your friend may be hinting towards using comprehensions when it makes the code cleaner (sometimes it doesn't).
I can think of using list-comprehension. This also helps to find multiple such-pairs if they exist in the given list.
num_list = [2,4,5,1,6,40,-1]
mult_num = [(num_list[i],num_list[j]) for i in range(len(num_list)) for j in range(i+1, len(num_list)) if num_list[i]*num_list[j] == 20]
print mult_num
Output:
[(4, 5)]
I came up with this. It reverses the approach a little bit, in that it searches in num_list for the required pair partner that the iteration value val would multiply to 20 with. This makes the code easier and needs no imports, even if it's not the most efficient way.
for val in num_list:
if 20 / val in num_list:
print(val, int(20/val))
You could make it more pythonic by using itertools.combinations, instead of nested loops, to find all pairs of numbers. Not always, but often iterating over indices as in for i in range(len(L)): is less pythonic than directly iterating over values as in for v in L:.
Python also allows you to make your function into a generator via the yield keyword so that instead of just returning the first pair that multiplies to 20, you get every pair that does by iterating over the function call.
import itertools
def factors(x, numbers):
""" Generate all pairs in list of numbers that multiply to x.
"""
for a, b in itertools.combinations(numbers, 2):
if a * b == x:
yield (a, b)
numbers = [2, 4, 5, 1, 6, 40, -1]
for pair in factors(20, numbers):
print(pair)

Using recursion to create a list combination

I'm in trouble creating a combination of elements from list.
What i would like to do is to create a recursive function in Python which returns a combination of elements for example list a = [1,2,3,4,5,6,7,8] and a result will be combinations [1,2,3,4],[1,3,4,5],[1,4,5,6],[1,2,4,5] etc. For 8 elements it should return 70 combinations (if i did my math right). Although the best option would be that the combinations don't repeat.
I tried to code it, but what i get is only [1,2,3,4],[1,3,4,5] etc but not combination [1,5,7,8]
I know there is a special function but i'd like to do it recursively. Any suggestions?
nimed = ["A","B","C","D","E","F","G","H"]
def kombinatsioonid(listike,popitav):
if len(listike) < 4:
return
tyhi = []
for c in range(len(listike)):
tyhi.append(listike[c])
listike.pop(popitav)
print(tyhi)
kombinatsioonid(listike,popitav)
kombinatsioonid(nimed,1)
This can be done in this way :
def combination(l,n, mylist=[]):
if not n: print(mylist)
for i in range(len(l)):
mylist.append(l[i])
combination(l[i+1:], n-1, mylist)
mylist.pop()
l = ["A","B","C","D","E","F","G","H"]
n=4
combination(l, n)
For each element x in a, generate all k-1 combinations from the elements right to it, and prepend x to each one. If k==0, simply return one empty combination, thus exiting the recursion:
def combs(a, k):
if k == 0:
return [[]]
r = []
for i, x in enumerate(a):
for c in combs(a[i+1:], k - 1):
r.append([x] + c)
#print '\t' * k, k, 'of', a, '=', r
return r
Uncomment the "print" line to see what's going on.
As a side note, it's better to use English variable and function names, just for the sake of interoperability (your very question being an example).

"for loop" with two variables? [duplicate]

This question already has answers here:
How do I iterate through two lists in parallel?
(8 answers)
Closed 2 years ago.
How can I include two variables in the same for loop?
t1 = [a list of integers, strings and lists]
t2 = [another list of integers, strings and lists]
def f(t): #a function that will read lists "t1" and "t2" and return all elements that are identical
for i in range(len(t1)) and for j in range(len(t2)):
...
If you want the effect of a nested for loop, use:
import itertools
for i, j in itertools.product(range(x), range(y)):
# Stuff...
If you just want to loop simultaneously, use:
for i, j in zip(range(x), range(y)):
# Stuff...
Note that if x and y are not the same length, zip will truncate to the shortest list. As #abarnert pointed out, if you don't want to truncate to the shortest list, you could use itertools.zip_longest.
UPDATE
Based on the request for "a function that will read lists "t1" and "t2" and return all elements that are identical", I don't think the OP wants zip or product. I think they want a set:
def equal_elements(t1, t2):
return list(set(t1).intersection(set(t2)))
# You could also do
# return list(set(t1) & set(t2))
The intersection method of a set will return all the elements common to it and another set (Note that if your lists contains other lists, you might want to convert the inner lists to tuples first so that they are hashable; otherwise the call to set will fail.). The list function then turns the set back into a list.
UPDATE 2
OR, the OP might want elements that are identical in the same position in the lists. In this case, zip would be most appropriate, and the fact that it truncates to the shortest list is what you would want (since it is impossible for there to be the same element at index 9 when one of the lists is only 5 elements long). If that is what you want, go with this:
def equal_elements(t1, t2):
return [x for x, y in zip(t1, t2) if x == y]
This will return a list containing only the elements that are the same and in the same position in the lists.
There's two possible questions here: how can you iterate over those variables simultaneously, or how can you loop over their combination.
Fortunately, there's simple answers to both. First case, you want to use zip.
x = [1, 2, 3]
y = [4, 5, 6]
for i, j in zip(x, y):
print(str(i) + " / " + str(j))
will output
1 / 4
2 / 5
3 / 6
Remember that you can put any iterable in zip, so you could just as easily write your exmple like:
for i, j in zip(range(x), range(y)):
# do work here.
Actually, just realised that won't work. It would only iterate until the smaller range ran out. In which case, it sounds like you want to iterate over the combination of loops.
In the other case, you just want a nested loop.
for i in x:
for j in y:
print(str(i) + " / " + str(j))
gives you
1 / 4
1 / 5
1 / 6
2 / 4
2 / 5
...
You can also do this as a list comprehension.
[str(i) + " / " + str(j) for i in range(x) for j in range(y)]
Any reason you can't use a nested for loop?
for i in range(x):
for j in range(y):
#code that uses i and j
for (i,j) in [(i,j) for i in range(x) for j in range(y)]
should do it.
If you really just have lock-step iteration over a range, you can do it one of several ways:
for i in range(x):
j = i
…
# or
for i, j in enumerate(range(x)):
…
# or
for i, j in ((i,i) for i in range(x)):
…
All of the above are equivalent to for i, j in zip(range(x), range(y)) if x <= y.
If you want a nested loop and you only have two iterables, just use a nested loop:
for i in range(x):
for i in range(y):
…
If you have more than two iterables, use itertools.product.
Finally, if you want lock-step iteration up to x and then to continue to y, you have to decide what the rest of the x values should be.
for i, j in itertools.zip_longest(range(x), range(y), fillvalue=float('nan')):
…
# or
for i in range(min(x,y)):
j = i
…
for i in range(min(x,y), max(x,y)):
j = float('nan')
…
"Python 3."
Add 2 vars with for loop using zip and range; Returning a list.
Note: Will only run till smallest range ends.
>>>a=[g+h for g,h in zip(range(10), range(10))]
>>>a
>>>[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
For your use case, it may be easier to utilize a while loop.
t1 = [137, 42]
t2 = ["Hello", "world"]
i = 0
j = 0
while i < len(t1) and j < len(t2):
print t1[i], t2[j]
i += 1
j += 1
# 137 Hello
# 42 world
As a caveat, this approach will truncate to the length of your shortest list.
I think you are looking for nested loops.
Example (based on your edit):
t1=[1,2,'Hello',(1,2),999,1.23]
t2=[1,'Hello',(1,2),999]
t3=[]
for it1, e1 in enumerate(t1):
for it2, e2 in enumerate(t2):
if e1==e2:
t3.append((it1,it2,e1))
# t3=[(0, 0, 1), (2, 1, 'Hello'), (3, 2, (1, 2)), (4, 3, 999)]
Which can be reduced to a single comprehension:
[(it1,it2,e1) for it1, e1 in enumerate(t1) for it2, e2 in enumerate(t2) if e1==e2]
But to find the common elements, you can just do:
print set(t1) & set(t2)
# set([(1, 2), 1, 'Hello', 999])
If your list contains non-hashable objects (like other lists, dicts) use a frozen set:
from collections import Iterable
s1=set(frozenset(e1) if isinstance(e1,Iterable) else e1 for e1 in t1)
s2=set(frozenset(e2) if isinstance(e2,Iterable) else e2 for e2 in t2)
print s1 & s2

Categories

Resources