Why is this python loop containing consecutive duplicate items breaking? - python

I am attempting solve this problem. In the problem am required to iterate over a list of directions (NORTH, SOUTH, EAST, WEST) and discard any adjacent opposite directions (NORTH and SOUTH, EAST and WEST) to return a reduced array containing only non-redundant directions. When I iterate over a list that does not contain consecutive duplicates, such as ["NORTH", "SOUTH", "SOUTH", "EAST", "WEST", "NORTH", "WEST"], my code works fine, but it breaks when I iterate over a list with consecutive duplicates, like ['EAST', 'EAST', 'WEST']. Why is my code doing this, and how can I fix it to handle consecutive duplicates?
def dirReduc(arr):
for direction in arr[:-1]:
try:
gotdata = arr[arr.index(direction)+1]
except IndexError:
gotdata = 'null'
except ValueError:
gotdata = 'null'
if gotdata is 'null':
if arr == dirReduc(arr):
return arr
else:
return dirReduc(arr)
elif cancel_pair(direction, arr[arr.index(direction)+1]):
del arr[arr.index(direction):arr.index(direction)+2]
return arr
def cancel_pair(dir1, dir2):
if dir1 in ('NORTH') and dir2 in ('SOUTH') or dir1 in ('SOUTH') and dir2 in ('NORTH'):
return True
elif dir1 in ('WEST') and dir2 in ('EAST') or dir1 in ('EAST') and dir2 in ('WEST'):
return True
return False

A for loop is not a good match for this problem. If you delete a pair of items, you may need to backtrack to see if a new pair was created. A while loop is much more natural:
opposite_directions = {
"NORTH": "SOUTH",
"SOUTH": "NORTH",
"EAST": "WEST",
"WEST": "EAST"
}
def dirReduc(arr):
i = 0
while i < len(arr) - 1:
if arr[i] == opposite_directions[arr[i+1]]:
del arr[i:i+2]
if i > 0:
i -= 1 # back up a step if we just deleted some moves from the middle
else:
i += 1
return arr
I also replaced your cancel_pairs function with a simple dictionary lookup. Python's dictionaries are great, and they're often a better choice than a complicated if/else block.

EDIT
I'm leaving this here because I think the explanation and the recursive version are helpful, but #Blckknght's answer is superior in that it does less work (by backing up only one element rather than restarting with each iteration).
Here's some working code. I realize it's a bit of a departure from your code. (You can ignore the cancel_pair implementation; I wrote my own before I saw yours. Yours looks fine.)
def cancel_pair(dir1, dir2):
return tuple(sorted((dir1, dir2))) in (("NORTH", "SOUTH"), ("EAST", "WEST"))
def dirReduc(directions):
did_something = True
while did_something:
did_something = False
for i in range(len(directions) - 1):
if cancel_pair(directions[i], directions[i + 1]):
did_something = True
directions = directions[:i] + directions[i + 2:]
break
return directions
I think there are a couple issues with your code:
You're modifying the list of directions while you're iterating over it. This is generally a bad idea, and specifically here, I think it may cause you to skip over elements (because the length of the list is changing).
You're only taking one pass through the array. In the problem you linked to on Codewars, you need to repeat the process after removing a canceling pair.
You're using index to figure out the current index, but it will always tell you the index of the first occurrence of the value you're passing in. You need to instead keep track of the index yourself. I chose to just iterate over the indices, since I need to use the index + 1 anyway.
UPDATE
A recursive approach might be easier to understand, depending on your programming background:
def dirReduc(directions):
for i in range(len(directions) - 1):
if cancel_pair(directions[i], directions[i + 1]):
return dirReduc(directions[:i] + directions[i + 2:])
return directions

opp = {}
opp['NORTH'] = 'SOUTH'
opp['EAST'] = 'WEST'
opp['SOUTH'] = 'NORTH'
opp['WEST'] = 'EAST'
def red(lst):
i = 0
j = 1
while j< len(lst):
if(opp[lst[i]] == lst[j]):
lst[i] = 0
lst[j] = 0
lst = [x for x in lst if x != 0]
i =0
j =1
else:
i+=1
j+=1
print(i,j)
this is a cleaner way to do it, I store the opposite directions and then iterate over the list making a new copy every time i 'pop' opposite elements.
keep in mind that the input list will be modified by this code so you might want to pass a copy of the input list if needed.

There are a lot of mistakes in this function. The most important two local mistakes are:
arr.index(direction) will always find the first instance of direction in arr, even after you've gone on to the next one
you have del arr[x:y] in the middle of a loop over arr, which will have unpredictable results.1
But also, I don't think your algorithm does what it's supposed to do. I would write this like this:
import re
_rcd_removals = re.compile(
r"\b(?:SOUTH NORTH|NORTH SOUTH|WEST EAST|EAST WEST)\b")
def remove_cancelling_directions(dirs):
"""DIRS is a list of uppercase cardinal directions as strings
(NORTH, SOUTH, EAST, WEST). Remove pairs of elements that
cancel each others' motion, e.g. NORTH immediately followed
by SOUTH. Modifies DIRS in place and returns nothing."""
dirs[:] = _rcd_removals.sub("", " ".join(dirs)).split()
Regexes are great at sliding-window edits to sequences of words. Working on this as an array is going to be a lot finickier.
If you need to collapse pairs that are only visible after other pairs have been collapsed (e.g. WEST SOUTH NORTH EAST should become the empty list), then you should iterate to a fixed point:
import re
_rcd_removals = re.compile(
r"\b(?:SOUTH NORTH|NORTH SOUTH|WEST EAST|EAST WEST)\b")
_rcd_fixwhite = re.compile(" {2,}")
def remove_cancelling_directions_repeatedly(dirs):
ds = " ".join(dirs)
prev_ds = None
while prev_ds != ds:
prev_ds = ds
ds = _rcd_removals.sub("", ds)
ds = _rcd_fixwhite.sub(" ", ds)
dirs[:] = ds.split()
1 Can anyone point me at official Python documentation stating that the effect of mutating a list while iterating over it is unpredictable? I know that it is, but I can't find an official rule. (The spec for list goes out of its way to point out that mutating a list while sorting it has undefined behavior.)

Related

for loop while decrementing the size of the list

so I have a list of 5 or fewer elements and the elements are just integers from 0-9 and the elements are randomly assigned and its possible for the list to have 5 zeros or 5 ones etc, and I have the following function to check if there is a zero in the list. this will return the index of the first zero it finds. just ignore the .getValue()
def check0(self):
'''
check for the index of the first card with value 0 in hand
:return:
'''
index = 0
found = False
while not found and index<len(self.hand):
if self.hand[index].getValue() == 0:
found = True
index += 1
if not found:
index = -1
return index
but the problem is that it always returns the first zero it finds in the list. in another class I am using this function to check if the hand has any zeros.
I need to write a for loop or some other loop that will traverse the list hand and tell me if all the elements in the hand are zeros.
so the only solution I can think of for this problem is to traverse the list once and when the first zero is found increment the counter and then traverse the list again this time excluding the zero that had already been found.
for example:
I have the list
[0,0,0,0,0]
in the first traversal, the check0() method will return the index 0 for the first zero but then I traverse the list again this time excluding the first zero and repeating that until I reach the last element.
I was thinking something like this:
def find_zeros():
counter = 0
for I in some_list(0,len(some_list),-1):
if I.check0() != -1:
counter += 1
if counter == len(some_list):
return True
return False
can anyone help me with this issue?
let me know if anything is unclear
also I'm not allowed to import anything and time complexity isn't an issue
"I need to write a for loop or some other loop that will traverse the list hand and tell me if all the elements in the hand are zeros." (OP)
Well, to check if all elements in your list are zero you could use count:
lst1 = [0,0,0,0,0]
print(len(lst1) == lst1.count(0))
Or maybe list comprehension:
lst1 = [0,0,0,0,0]
print(lst1 == [nr for nr in lst1 if nr == 0])
probably better written using all like:
lst1 = [0,0,0,0,0]
print(all(i==0 for i in lst1))
Or maybe create a second list the same size:
lst1 = [0,0,0,0,0]
print(lst1 == [0]*len(lst1))
You can use enumerate for this type of problem.
for index, ch in enumerate(list_name):
print(i, ch)
This will give you the index of each and every character in the list.
You can use an 'if' statement later to check if 'ch' is a zero.
Hope it helped.
listt=[1,0,2,0,1]
for i in range(len(listt)):
if listt[i]==0:
print(i)
break #if you want to find first occurence
To check all ekements are 0,
if len(set(listt))==1 and listt[0]==0:
print("All index have 0 ")
You could define the function like this:
def check0(self):
index = (self.hand+[0]).index(0)
return -1 if not any(self.hand) else index

Can my code be classified as a depth first search?

I wrote code for a DFS after reading about what it is but not actually seeing the code. I did this to challenge myself (I always have believed that to learn something new you must always first challenge yourself). The thing is after I wrote my code, I compared my implementation to the one in the book I read it in (Introduction to the Design and Analysis of Algorithms - A. Levitin) and it is completely different. So now I am wondering well it works as intended... is it still a DFS?
I made the implementation to solve a maze. I will give a rundown on my code and also upload the code here (Some people hate reading other people's code while others do.)
Algorithm (What I understood and did):
Convert maze into a graph/map
Set start position as current node and run loop in which...
I choose one of the adjacent nodes as the next current node and do this until I stumble upon a dead end. Also I am adding each node I pass through into a list that acts as my stack.
Once I am at a dead end, I keep poping items from the stack and each time I pop, I check if it has adjacent nodes that have not been visited.
Once I have found an unvisited adjacent node, we continue the entire process from step 3.
We do this until current node is the end position.
Then I just retrace my way back through the stack.
Here is my code:
# Depth First Search implementation for maze...
# from random import choice
from copy import deepcopy
import maze_builderV2 as mb
order = 10
space = ['X']+['_' for x in range(order)]+['X']
maze = [deepcopy(space) for x in range(order)]
maze.append(['X' for x in range(order+2)])
maze.insert(0, ['X' for x in range(order+2)])
finalpos = (order, order)
pos = (1, 1)
maze[pos[0]][pos[1]] = 'S' # Initializing a start position
maze[finalpos[0]][finalpos[1]] = 'O' # Initializing a end position
mb.mazebuilder(maze=maze)
def spit():
for x in maze:
print(x)
spit()
print()
mazemap = {}
def scan(): # Converts raw map/maze into a suitable datastructure.
for x in range(1, order+1):
for y in range(1, order+1):
mazemap[(x, y)] = []
t = [(x-1, y), (x+1, y), (x, y-1), (x, y+1)]
for z in t:
if maze[z[0]][z[1]] == 'X':
pass
else:
mazemap[(x, y)].append(z)
scan()
path = [pos] # stack
impossible = False
while path[-1] != finalpos:
curpos = path[-1]
i = 0
while i < len(mazemap[curpos]):
if mazemap[curpos][i] in path:
del mazemap[curpos][i]
else:
i += 1
nextpos = None
if mazemap[curpos] == []:
while nextpos == None:
try:
wrongpos = path.pop(-1)
if mazemap[wrongpos] == []:
pass
else:
path.append(wrongpos)
# nextpos = choice(mazemap[wrongpos])
nextpos = mazemap[wrongpos][-1]
mazemap[wrongpos].remove(nextpos)
except IndexError:
impossible = True
break
else:
# nextpos = choice(mazemap[curpos])
nextpos = mazemap[curpos][-1]
if impossible:
break
path.append(nextpos)
if not impossible:
for x in path:
if x == pos or x == finalpos:
pass
else:
maze[x[0]][x[1]] = 'W'
else:
print("This maze not solvable, Blyat!")
print()
spit()
As always, I greatly appreciate your suggestions!
Your algorithm looks DFS to me. DFS means exploring the path as deep as possible, backtrack to the previous node only if there is no solution and your algorithm works in a similar way by popping nodes from the stack. You just mimic the recursion stack using your own stack so it looks quite different from the standard solution.
Essentially, all recursive algorithms can be simulated using stack and loop. But most of the time doing this will make the algorithm much less readable. To tackle a difficult problem, I think the usual way to do it is to first come up with the recursive solution. After making sure the recursive solution is bug-free, then start implementing the iterative version using stack if you care a lot about the efficiency.
Other Suggestion:
if mazemap[curpos][i] in path: is a O(n) operation since path is a normal list. Consider using a separate hash set to store visited nodes and use the set to check repetition instead to make it O(1).

sub-sum from a list without loops

So i'm studying recursion and have to write some codes using no loops
For a part of my code I want to check if I can sum up a subset of a list to a specific number, and if so return the indexes of those numbers on the list.
For example, if the list is [5,40,20,20,20] and i send it with the number 60, i want my output to be [1,2] since 40+20=60.
In case I can't get to the number, the output should be an empty list.
I started with
def find_sum(num,lst,i,sub_lst_sum,index_lst):
if num == sub_lst_sum:
return index_lst
if i == len(sum): ## finished going over the list without getting to the sum
return []
if sub_lst_sum+lst[i] > num:
return find_sum(num,lst,i+1,sub_lst_sum,index_lst)
return ?..
index_lst = find_sum(60,[5,40,20,20,20],0,0,[])
num is the number i want to sum up to,
lst is the list of numbers
the last return should go over both the option that I count the current number in the list and not counting it.. (otherwise in the example it will take the five and there will be no solution).
I'm not sure how to do this..
Here's a hint. Perhaps the simplest way to go about it is to consider the following inductive reasoning to guide your recursion.
If
index_list = find_sum(num,lst,i+1)
Then
index_list = find_sum(num,lst,i)
That is, if a list of indices can be use to construct a sum num using elements from position i+1 onwards, then it is also a solution when using elements from position i onwards. That much should be clear. The second piece of inductive reasoning is,
If
index_list = find_sum(num-lst[i],lst,i+1)
Then
[i]+index_list = find_sum(num,lst,i)
That is, if a list of indices can be used to return a sum num-lst[i] using elements from position i+1 onwards, then you can use it to build a list of indices whose respective elements sum is num by appending i.
These two bits of inductive reasoning can be translated into two recursive calls to solve the problem. Also the first one I wrote should be used for the second recursive call and not the first (question: why?).
Also you might want to rethink using empty list for the base case where there is no solution. That can work, but your returning as a solution a list that is not a solution. In python I think None would be a the standard idiomatic choice (but you might want to double check that with someone more well-versed in python than me).
Fill in the blanks
def find_sum(num,lst,i):
if num == 0 :
return []
elif i == len(lst) :
return None
else :
ixs = find_sum(???,lst,i+1)
if ixs != None :
return ???
else :
return find_sum(???,lst,i+1)

Returning position of specific symbol in list of lists

For this function I'm writing I want to return the position of a specific character that is closest to the top left of the list of lists. It'd be great to accomplish this in the most basic way without importing modules. Here is the code I have so far then I will explain more about what I'm trying to do
def find_position(symbol,lst):
# Sample List: [['.','M','M','G','G'],
# ['.','.','.','.','h'],
# ['B','B','B','.','h']]
#
for sublist in lst:
for symbol in sublist:
if symbol == True:
lst.find(symbol)
return symbol
else:
return None
So if the function was instructed to find '.' then it should return (0,0) since it is in the first position of the first list. If the function was told to find 'h' then it should return (1,4) since it is in the 4th position (python starts from 0) of the second list. My current code doesn't look for the character closest to the top left as I'm not sure how to program that. Thanks in advance for any help I receive.
A function that iterates through all elements until it finds the character. As soon as it finds it, it returns the position. To find the psotion you can use the enumerate fucntion which returns a tuple containing a count (from start which defaults to 0) and the values obtained from iterating over sequence
def find_position(symbol,lst):
for index, my_list in enumerate(s):
for i,item in enumerate(my_list):
if symbol == item:
return "{},{}".format(index,i)
return "Not found"
s = [['.','M','M','G','G'],
['.','.','.','.','h'],
['B','B','B','.','h']]
a = find_position("h",s)
Daniel's way is a bit faster in time execution but this also works for comparison:
def find_pos(listname, symbol):
for i in range(len(listname)):
for j in listname[i]:
if j == symbol:
return i ,listname[i].index(j)

QUICKEST way to find a key in a dictionary

I have a dictionary, with over 11 million keys (and each value is a list).Each key is a unique integer.
e.g.
Dict1 = {11:"a",12:"b",22:"c",56:"d"}
Then, separately, I have a list of ranges, e.g.
[10-20,30-40,50-60]
And I want to say, for each range in my list of ranges, go through the dictionary and return the value, if the key is within the range.
So it would return:
10-20: "a","b"
50-60: "d"
The actual code that I used is:
for each_key in sorted(dictionary):
if each_key in range(start,end):
print str(dictionary[each_key])
The problem is that this technique is prohibitively long because it's going through all 11 million keys and checking if it's within the range or not.
Is there a way that I can say "skip through all of the dictionary keys until one in found that is higher than the start number" and then "stop once the end number is higher than the key"? Just basically some way that just zooms in on the portion of the dictionary within a certain range very quickly?
Thanks
Just use Python's EAFP principle. It's Easier to Ask Forgiveness than Permission.
Assume that all keys are valid, and catch the error if they're not:
for key in xrange(start, end):
try:
print str(dictionary[key])
except KeyError:
pass
This will just try to get each number as a key, and if there's a KeyError from a non existent key then it will move on to the next iteration.
Note that if you expect a lot of the keys will be missing, it might be faster to test first:
for key in xrange(start, end):
if key in dictionary:
print str(dictionary[key])
Note that xrange is just a slightly different function to range. It will produce the values one by one instead of creating the whole list in advance. It's useful to use in for loops and has no drawbacks in this case.
my thought for this problem is to find the correct keys first. The reason why your solution take too much time is that it use O(n) algorithm to find a correct key. If we can implement binary search method, the complexity will be reduced to O(log(n)), which helps a lot.
Following is my sample code. It works for the example, but I cannot promise it won't get some small bugs. Just find the idea there and implement yours.
def binarySearch(alist, target):
left = 0
right = len(alist) -1
if target>alist[-1]:
return len(alist)
while left < right:
m = (left + right) / 2
if alist[m] == target:
return m
if alist[m] < target:
left = m+1
else:
right = m
return left
def work(dictionary, start, end):
keys = sorted(dictionary.keys())
start_pos = binarySearch(keys, start)
end_pos = binarySearch(keys, end)
print [dictionary[keys[pos]] for pos in range(start_pos,end_pos)]
dictionary = {11:"a",12:"b",22:"c",56:"d"}
work(dictionary, 10, 20)
work(dictionary, 20, 40)
work(dictionary, 10, 60)
This solution ( using OrderedDict and filter ) can help you a bit.
from collections import OrderedDict
d = {2:3, 10:89, 4:5, 23:0}
od = OrderedDict(sorted(d.items()))
lst=["1-10","11-20","21-30"]
lower_lst=map(int,[i.split("-")[0] for i in lst])
upper_lst=map(int,[i.split("-")[1] for i in lst])
for low,up in zip(lower_lst,upper_lst):
print "In range {0}-{1}".format(low,up),filter(lambda a:low <= a[0] <= up,od.iteritems())

Categories

Resources