The code should be straight forward but I found that it works sometimes well and sometimes not at all. when trying to print the solutions immediately it works fine but when I try to store, use , or do anything else with them, the generator simply keeps giving the same solution over and over
def isvalid(grid,y,x,n):
for i in range(9):
if i!=y and grid[i][x]==n:
return False
if i!=x and grid[y][i]==n:
return False
sy=3*(y//3)
sx=3*(x//3)
for dy in range(3) :
for dx in range(3) :
if sy+dy!=y and sx+dx!=x:
if grid[sy+dy][sx+dx]==n: return False
return True
def solve(grid):
number_list = [1,2,3,4,5,6,7,8,9]
for y in range(9):
for x in range(9):
if grid[y][x]==0:
random.shuffle(number_list)
for i in number_list:
if isvalid(grid,y,x,i):
grid[y][x]=i
yield from solve(grid)
grid[y][x]=0
return
yield grid
with this code the first case works fine and prints different grids
but in the #2 one the solutions are the same and the result is true
grid=[[0]*9 for x in range(9)]
s= solve(grid)
# 1
for i in s:
print(i)
# 2
solutions=[]
for i,n in zip(s,range(3)):
solutions.append(i)
print(solutions[0]==solutions[1])
Your code uses a single mutable data structure grid which the solve function modifies in place at each step. Every yield grid statement is simply returning a reference to this mutable object, so the list you collect is a list of references to the same thing, in its latest state.
Note: you can see your solutions point to the same object by checking:
print(solutions[0] is solutions[1])
Try yielding a deep copy of the grid each time (if you want to collect a list of them), or print the grids as they are generated rather than collecting them (edit: IIUC your print approach #1 currently shows different solutions as expected):
yield [[entry for entry in row] for row in grid]
This could be memory intensive especially when you start with an empty grid as you have done. Probably better to test with an example which has fewer solutions first.
Related
I am trying to write a function that takes a sequence of numbers and determines if all the numbers are different from each other.
This was my first attempt
def differ(data):
for i in data:
print(i)
for j in data:
print(j)
if i==j:
return False
return True
print(differ([1,23,4,1,2,3,1,2]))
print(differ([1,2,3,4]))
1
1
False
1
1
False
Apparently, the for loop didn't loop over all the numbers in the data. Why did this happen?
I wrote another function using range().
def differ(data):
for i in range(1,len(data)):
print("i:",i)
for j in range(i):
print("j:",j)
if data[i]==data[j]:
return False
return True
print(differ([1,2,2,4,53]))
i: 1
j: 0
i: 2
j: 0
j: 1
False
It works, however, I don't understand why my first attempt didn't work.
The problem with your first attempt is that it will eventually compare the same number with itself.
In the sequence [1,2,3,4] the first function will start off with i=1 and j=1. They are both looking at the first number in the list causing it to fail.
The second attempt avoids this by only looking at the numbers before it. range(i) doesn't actually include i, so j can only ever be less than i meaning they will never point to the same value in the list.
I think Tim Roberts answer explains perfectly why your code is not working as you expect. I would like to complement the answer:
Answering your question
Arrays are similar to lists, one of the differences is that the former consists only of elements with the same data type.
In the other hand, iterators are objects which you can iterate through, these objects must implement at least the following 2 methods on their respective classes: __iter__() and __next__()
Cool way to do what you want in 1 line
You can achieve what you want with this:
def differ(data):
return len(set(data)) == len(data)
print(differ([1, 2, 3, 4])) # True
print(differ([1, 23, 4, 1, 2, 3, 1, 2])) # False
So basically, the magic here happens when you use set(). Sets are similar to lists, but one of the main differences is that they can't have repeated elements. By transforming the list to a set you are removing every duplicated element, so if there is a difference in the length after casting to a set, it means there was at least one duplicated element.
I am building a function to extract all negatives from a list called xs and I need it to add those extracted numbers into another list called new_home. I have come up with a code that I believe should work, however; it is only showing an empty list.
Example input/output:
xs=[1,2,3,4,0,-1,-2,-3,-4] ---> new_home=[1,2,3,4,0]
Here is my code that returns an empty list:
def extract_negatives(xs):
new_home=[]
for num in range(len(xs)):
if num <0:
new_home= new_home+ xs.pop(num)
return
return new_home
Why not use
[v for v in xs if v >= 0]
def extract_negatives(xs):
new_home=[]
for num in range(len(xs)):
if xs[num] < 0:
new_home.append(xs[num])
return new_home
for your code
But the Chuancong Gao solution is better:
def extract_negative(xs):
return [v for v in xs if v >= 0]
helper function filter could also help. Your function actually is
new_home = filter(lambda x: x>=0, xs)
Inside the loop of your code, the num variable doesn't really store the value of the list as you expect. The loop just iterates for len(xs) times and passes the current iteration number to num variable.
To access the list elements using loop, you should construct loop in a different fashion like this:
for element in list_name:
print element #prints all element.
To achieve your goal, you should do something like this:
another_list=[]
for element in list_name:
if(element<0): #only works for elements less than zero
another_list.append(element) #appends all negative element to another_list
Fortunately (or unfortunately, depending on how you look at it) you aren't examining the numbers in the list (xs[num]), you are examining the indexes (num). This in turn is because as a Python beginner you probably nobody haven't yet learned that there are typically easier ways to iterate over lists in Python.
This is a good (or bad, depending on how you look at it) thing, because had your code taken that branch you would have seen an exception occurring when you attempted to add a number to a list - though I agree the way you attempt it seems natural in English. Lists have an append method to put new elements o the end, and + is reserved for adding two lists together.
Fortunately ignorance is curable. I've recast your code a bit to show you how you might have written it:
def extract_negatives(xs):
out_list = []
for elmt in xs:
if elmt < 0:
out_list.append(elmt)
return out_list
As #ChuangongGoa suggests with his rather terse but correct answer, a list comprehension such as he uses is a much better way to perform simple operations of this type.
For example, the below code
primeList = []
for val in range(2, num):
if not any(val % i == 0 for i in primeList):
primeList.append(val)
How can I turn this exact piece of code into list comprehension?
No, you can't, because the list doesn't exist as a Python object until the comprehension is finished iterating. You can't reference an object that doesn't exist. Honestly, I would just leave this as a for loop - list comprehensions aren't a magic bullet to replace all list-constructing loops.
However... you can get tricky and use a generator, which is only evaluated on demand. Combining this with the extend() method of a list, which adds elements to the list as it obtains them (in my tests, anyway), you can use the current contents of the list you're extending as you extend it.
# make sure to set `num`
plist = []
plist.extend(c for c in range(2, num) if not any(c % p == 0 for p in plist))
Fair warning: as far as I know, the fact that extend() adds elements to the list as it produces them is not part of the specification, so whether this bit of code works could be implementation-dependent. But I tested it in Python 2.7 and 3.4 and got a list of primes each time.
Actually, if you really really want to, you can create an identical copy of the list within the list comprehension and refer to that...
primeListCopy = []
primeList = [primeListCopy.append(val) or val for val in range(2, num)
if not any(val % i == 0 for i in primeListCopy)]
This uses the fact that primeListCopy.append(val) or val evaluates to val, because assignment to list returns None and or evaluates to the right side value.
This is definitely worse performance-wise than a simple for-loop. I wrote this in response to OP's question "what could be the closest mimic when i had nothing but list comprehension. ( this is not a development code, just my experiments)"
That said, the additional work only adds O(n) of work so doesn't actually increase the algorithmic complexity of the loop. It's conceivable (though not very likely) that the list comprehension optimization will make this faster than the original for-loop.
#!/usr/bin/python
from __future__ import print_function
import math
def is_prime(x):
if (x == 0 or x == 1 or x < 0):
return False
for i in range(2, int(math.sqrt(x)) + 1):
if (not (x % i)):
return False
return True
def filter_primes(max):
return [val for val in range(2, max) if is_prime(val)]
def main():
primes = filter_primes(20)
print(primes)
if __name__ == "__main__":
main()
The code above starts with defining a function called is_prime(x) which returns True if x is a prime number, False otherwise. Then a function called filter_primes(max) uses is_prime with list comprehension to filter the prime numbers into an array that's returned. The maximum number of the range is specified via max. The main function just invokes the API to test it out.
EDIT:
But maybe I've misunderstood what you meant by "turn this exact piece of code into list comprehension". Depending on what you really want to do, using a generator is a great idea for dynamic purposes.
I've created a function named number(x) that tests a number to see whether or not a number is perfect or not. Now my goal is to create a tester function that tests all numbers from 1 to 1000 and return numbers that are perfect. This is the code i have for the test function:
def unittest():
for i in range(0,1000):
perfect(i)
if True:
return i
It's not working, but i think i'm close. Any advice or help?
I think you meant this, and notice the correct parameters for range, and how we use a list to accumulate all the results - otherwise, the function will return only one value!
def unittest():
ans = []
for i in range(1, 1001):
if perfect(i):
ans.append(i)
return ans
Alternatively, and not recommended (it's redundant), you could test if the returned value was True:
def unittest():
ans = []
for i in range(1, 1001):
if perfect(i) is True :
ans.append(i)
return ans
Yet another alternative would be to use list comprehensions, which is more idiomatic and potentially faster than using an explicit loop:
def unittest():
return [i for i in range(1, 1001) if perfect(i)]
When you return, that's the end of your function. If you want to return all of the perfect numbers, you have to keep track of them and return them all at the end.
On top of that, your if True: means you'll return 0 whether it's perfect or not.
So, what you need to do is:
def unittest():
results = []
for i in range(1000):
if perfect(i):
results.append(i)
return results
There actually is a way to solve this without building the list, by using yield instead of return. That's probably too advanced for you to learn right now, but I'll explain it anyway. First, here's the code:
def unittest():
for i in range(1000):
if perfect(i):
yield i
See the tutorial section on Iterators, and the following two sections, for details. But basically, a yield is like a return that doesn't return. What your function actually returns is not a list, but a generator. If someone then iterates over that generator, it will go through your function until the first yield, then go through until the next yield, and so on until you're done. The tutorial explains this much better than a single paragraph ever could.
How can I update the upper limit of a loop in each iteration? In the following code, List is shortened in each loop. However, the lenList in the for, in loop is not, even though I defined lenList as global. Any ideas how to solve this? (I'm using Python 2.sthg)
Thanks!
def similarity(List):
import difflib
lenList = len(List)
for i in range(1,lenList):
import numpy as np
global lenList
a = List[i]
idx = [difflib.SequenceMatcher(None, a, x).ratio() for x in List]
z = idx > .9
del List[z]
lenList = len(List)
X = ['jim','jimmy','luke','john','jake','matt','steve','tj','pat','chad','don']
similarity(X)
Looping over indices is bad practice in python. You may be able to accomplish what you want like this though (edited for comments):
def similarity(alist):
position = 0
while position < len(alist):
item = alist[position]
position += 1
# code here that modifies alist
A list will evaluate True if it has any entries, or False when it is empty. In this way you can consume a list that may grow during the manipulation of its items.
Additionally, if you absolutely have to have indices, you can get those as well:
for idx, item in enumerate(alist):
# code here, where items are actual list entries, and
# idx is the 0-based index of the item in the list.
In ... 3.x (I believe) you can even pass an optional parameter to enumerate to control the starting value of idx.
The issue here is that range() is only evaluated once at the start of the loop and produces a range generator (or list in 2.x) at that time. You can't then change the range. Not to mention that numbers and immutable, so you are assigning a new value to lenList, but that wouldn't affect any uses of it.
The best solution is to change the way your algorithm works not to rely on this behaviour.
The range is an object which is constructed before the first iteration of your loop, so you are iterating over the values in that object. You would instead need to use a while loop, although as Lattyware and g.d.d.c point out, it would not be very Pythonic.
What you are effectively looping on in the above code is a list which got generated in the first iteration itself.
You could have as well written the above as
li = range(1,lenList)
for i in li:
... your code ...
Changing lenList after li has been created has no effect on li
This problem will become quite a lot easier with one small modification to how your function works: instead of removing similar items from the existing list, create and return a new one with those items omitted.
For the specific case of just removing similarities to the first item, this simplifies down quite a bit, and removes the need to involve Numpy's fancy indexing (which you weren't actually using anyway, because of a missing call to np.array):
import difflib
def similarity(lst):
a = lst[0]
return [a] + \
[x for x in lst[1:] if difflib.SequenceMatcher(None, a, x).ratio() > .9]
From this basis, repeating it for every item in the list can be done recursively - you need to pass the list comprehension at the end back into similarity, and deal with receiving an empty list:
def similarity(lst):
if not lst:
return []
a = lst[0]
return [a] + similarity(
[x for x in lst[1:] if difflib.SequenceMatcher(None, a, x).ratio() > .9])
Also note that importing inside a function, and naming a variable list (shadowing the built-in list) are both practices worth avoiding, since they can make your code harder to follow.