How to make list comprehensions more readable? - python

I have this bit of code below, and i think it's kinda hard to understand if you are new to python.
How would i go about making it more readable to a group of newcomers to python (students)
def right_inwrongplace(userGuess, number):
correct_places = [True if v == number[i] else False for i, v in enumerate(userGuess)]
g = [v for i, v in enumerate(userGuess) if not correct_places[i]]
n = [v for i, v in enumerate(number) if not correct_places[i]]
return len([i for i in g if i in n])

Here are a few improvements:
True if x else False is simply bool(x) or, as you are doing a comparison already, just that expression, i.e. v == number[i].
Since you are accessing the number by positional index you can just zip the two sequences.
So for the first you'd get:
correct_places = [x == y for x, y in zip(userGuess, number)]
The same argument with zip applies to the following two comprehensions (you can again iterate over the original string):
g = [x for x, y in zip(userGuess, number) if x != y]
n = [y for x, y in zip(userGuess, number) if x != y]
Given that this is basically the same comprehension two times, and that we don't need correct_places anymore we can instead do the following:
g, n = zip(*[(x, y) for x, y in zip(userGuess, number) if x != y])
Then you can sum instead of len:
return sum(x in n for x in g)
So basically you can use the following code:
g, n = zip(*(xy for xy in zip(userGuess, num) if xy[0] != xy[1])
return sum(x in n for x in g)

Related

Find number of pairs that add up to a specific number from two different lists?

a = [1,2,3,4,5,6,7]
b = [56,59,62,65,67,69]
def sumOfTwo(a,b,v):
for i in range (len(a)):
val_needed = v - a[i]
for j in range (len(b)):
if b[j] == val_needed:
x = b[j]
y = a[i]
print(x,y)
sumOfTwo(a,b,v=70)
Output: 5 65
What if more pairs are possible from the given lists in the problem, how do I do that?
Help.
What are more ways to achieve this?
If you just want to print matched values, you just have to indent the print statement to be inside theif, as stated below. Also, you should use a more pythonic approach to for loops and also for variable assignments.
a = [1,2,3,4,5,6,7]
b = [56,59,62,65,67,69]
def sumOfTwo(a,b,v):
for i in a:
val_needed = v - i
for j in b:
if j == val_needed:
x, y = j, i
print(x,y)
sumOfTwo(a,b,v=70)
Using a list comprehension:
a = [1,2,3,4,5,6,7]
b = [56,59,62,65,67,69]
c = [(x, y)
for x in a
for y in b
if x + y == 70]
print(c)
This yields
[(1, 69), (3, 67), (5, 65)]

python change the list's values based on a for loop(or other suitable method)

I need to change the list's value to 0 if the input is below 0, and change it to 255 if the input is above 255.
i.e. if the list is [265,45,-23], I need to change it to [255,45,0].
Tried to figure it out for hours, but gave up a bit (the initial input is a tuple). Here is my code so far, but I have no idea which logic I should use:
def rgb(r, g, b):
x = (r,g,b)
x = list(x)
for i in x:
if i<0:
x=0
if i>255:
x=255
print(x)
You could do this in a single line using a list comprehension:
def rgb(r,g,b): return [min(255,max(0,n)) for n in (r,g,b)]
Output:
rgb(265,45,-23)
[255, 45, 0]
You don't need the tuple. Try this:
def rgb(r, g, b):
x = [r,g,b]
for i in range(len(x)):
if x[i] < 0:
x[i] = 0
if x[i] > 255:
x[i] = 255
return x
# Output: [255, 45, 0]
When you assign to x you're replacing the whole variable with a new value, not just the current element of the iteration.
You can use enumerate() to get the list index and value together, and use the index in the assignment.
for i, val in enumerate(x):
if val < 0:
x[i] = 0
elif val > 255:
x[i] = 255
You can also use a list comprehension:
x = [min(255, max(0, val)) for val in x]
You can use list comprehension to go through r, g and b. And at the same time create a new list that tests the conditions.
def rgb(r, g, b):
return [0 if x < 0 else 255 if x > 255 else x for x in (r,g,b)]
You can call it like:
print(rgb(265,45,-23))
or like:
x = [265,45,-23]
print(rgb(*x))
You can use max(0, min(255, x)) for a list element x. Then for the whole list you can use a list comprehension:
x = [max(0, min(255, y)) for y in x]
Alternatively you can use the ternary operator:
x = [255 if y > 255 else (0 if y < 0 else y) for y in x]
Solution
def rgb(r, g, b):
rgb_tuple = (r,g,b)
rgb_list = list(rgb_tuple)
new_list = []
for color in rgb_list:
if color<0:
new_list.append(0)
elif color>255:
new_list.append(255)
else:
new_list.append(color)
print(new_list)
return new_list
rgb(266, 80, -9)

How to stop a loop?

def sum_div(x, y):
for k in range(x,y+1):
for z in range(x,y+1):
sx = 0
sy = 0
for i in range(1, k+1):
if k % i == 0:
sx += i
for j in range(1, z+1):
if z % j == 0:
sy += j
if sx == sy and k!= z:
print "(", k ,",", z, ")"
x = input("Dati x : ")
y = input("Dati y : ")
sum_div(x, y)
How do I stop the looping if the value of z == y?
The loops print a pair of numbers in a range from x to y, but when it hit the y value the loop prints a reverse pair of numbers that I don't need it to.
The break command will break out of the loop. So a line like this:
if (z == y):
break
should do what you want.
What you're think you are asking for is the break command, but what you're actually looking for is removal of duplication.
Your program lacks some clarity. For instance:
for i in range(1, k+1):
if k % i == 0:
sx += i
for j in range(1, z+1):
if z % j == 0:
sy += j
These two things are doing essentially the same thing, which can be written more cleanly with a list comprehension (in the REPL):
>>> def get_divisors(r: int) -> list:
... return [i if r % i == 0 else 0 for i in range(1, r+1)]
...
...
>>> get_divisors(4)
>>> [1, 2, 0, 4]
>>> sum(get_divisors(4))
>>> 7
Your line:
while y:
... will infinitely loop if you find a match. You should just remove it. while y means "while y is true", and any value there will evaluate as true.
This reduces your program to the following:
def get_divisors(r: int) -> list:
return [i if r % i == 0 else 0 for i in range(1, r+1)]
def sum_div(x, y):
for k in range(x,y+1):
sum_of_x_divisors = sum(get_divisors(k)) # Note this is moved here to avoid repeating work.
for z in range(x,y+1):
sum_of_y_divisors = sum(get_divisors(z))
if sum_of_x_divisors == sum_of_y_divisors and k!= z:
print("({},{})".format(k, z))
Testing this in the REPL it seems correct based on the logic of the code:
>>> sum_div(9,15)
(14,15)
(15,14)
>>> sum_div(21, 35)
(21,31)
(31,21)
(33,35)
(35,33)
But it's possible that for sum_div(9,15) you want only one of (14,15) and (15,14). However, this has nothing to do with breaking your loop, but the fact that what you're attempting to do has two valid values when k and z don't equal each other. This is demonstrated by the second test case, where (33,35) is a repeated value, but if you broke the for loop on (21,31) you would not get that second set of values.
One way we can account for this is by reordering when work is done:
def sum_div(x, y):
result_set = set() # Sets cannot have duplicate values
for k in range(x,y+1):
sum_of_x_divisors = sum(get_divisors(k))
for z in range(x,y+1):
sum_of_y_divisors = sum(get_divisors(z))
if sum_of_x_divisors == sum_of_y_divisors and k!= z:
result_set.add(tuple(sorted((k,z)))) # compile the result set by sorting it and casting to a tuple, so duplicates are implicitly removed.
for k, z in result_set: # Print result set after it's been compiled
print("({},{})".format(k, z))
And we see a correct result:
>>> sum_div(9,15)
(14,15)
>>> sum_div(21,35)
(21,31)
(33,35)
Or, the test case you provided in comments. Note the lack of duplicates:
>>> sum_div(10,25)
(16,25)
(14,15)
(15,23)
(10,17)
(14,23)
Some takeaways:
Break out functions that are doing the same thing so you can reason more easily about it.
Name your variables in a human-readable format so that we, the readers of your code (which includes you) understands what is going on.
Don't use loops unless you're actually looping over something. for, while, etc. only need to be used if you're planning on going over a list of things.
When asking questions, be sure to always include test input, expected output and what you're actually getting back.
The current best-practice for printing strings is to use the .format() function, to make it really clear what you're printing.

how to write generate Fibonacci in python

def fib(a, b, f):
fib must generate (using yield) the generalized Fibonacci
sequence, a and b is first and second element. f is function to get the third element instead of a+b as normal Fibonacci sequence. Use take function(which show below) to test it.
my code is below
def fib(a, b, f):
x = a
y = b
yield x
x, y = y, f(x,y)
fib(x,y,f)
I don't know what is wrong of my code, when I try to test it, it show
"TypeError: 'generator' object is not subscriptable"
the test case is:
take(5, fib(0, 1, lambda x, y: x - y))
It should out put:
[0, 1, -1, 2, -3]
and take function as i write is :
def take(n, iterable):
x = []
if n <= 0:
return x
else:
for i in range (0,n):
x.append(iterable[i])
return x
The message means that generators do not support indexing, so iterable[i] fails. Instead, use the next() function to get the next item from the iterator.
def take(n, iterable):
x = []
if n > 0
itr = iter(iterable) # Convert the iterable to an iterator
for _ in range(n): # Repeat n times
x.append(next(itr)) # Append the next item from the iterator
return x
Also, your fib() function will not work. You should not recurse at the end of the function; instead write a loop that yields a value each iteration.
def fib(a, b, f):
x = a
y = b
while True:
yield x
x, y = y, f(x,y)
You can't index into the results coming from a generator function like fib(). This following avoids that by using zip() with a range() argument. zip() stops automatically when one of its arguments reaches then end.
def fib(a, b, f):
x, y = a, b
while True:
yield x
x, y = y, f(x, y)
def take(n, iterable):
return [] if n <= 0 else [v for _, v in zip(range(n), iterable)]
print( take(5, fib(0, 1, lambda x, y: x-y)) )
Output:
[0, 1, -1, 2, -3]
Fibonacci simplest method you'll ever come across:
a , b =0 , 1
for n in range(100):#any number
print(a)
a = a + b
print (b)
b = b + a
def fibo(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fibo(n - 1) + fibo(n - 2)
n = int(input("enter the number: "))
for i in range(n):
print(fibo(i))

Randomly generating 3-tuples with distinct elements in python

I am trying to generate 3-tuples (x,y,z) in python such that no two of x , y or z have the same value. Furthermore , the variables x , y and z can be defined over separate ranges (0,p) , (0,q) and (0,r). I would like to be able to generate n such tuples. One obvious way is to call random.random() for each variable and check every time whether x=y=z . Is there a more efficient way to do this ?
You can write a generator that yields desired elements, for example:
def product_no_repeats(*args):
for p in itertools.product(*args):
if len(set(p)) == len(p):
yield p
and apply reservoir sampling to it:
def reservoir(it, k):
ls = [next(it) for _ in range(k)]
for i, x in enumerate(it, k + 1):
j = random.randint(0, i)
if j < k:
ls[j] = x
return ls
xs = range(0, 3)
ys = range(0, 4)
zs = range(0, 5)
size = 4
print reservoir(product_no_repeats(xs, ys, zs), size)

Categories

Resources