I have this code right down here:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if head == None:
return
pre, node = None, head
while node:
pre, node.next, node = node, pre, node.next
return pre
I am trying to vizualize how this works. If it starts on a list, the pre becomes the head, since node was assigned to head. then node.next is assigned to pre, so it points to itself? Finally, node becomes node.next, which is itself? am I missing something here?
Multiple assignment isn't the same as several assignments one after the other. The difference is that the values on the right hand side of the statement all get evaluated before anything get rebound. The values on the right hand side are in fact packed up in a tuple, then unpacked into the names on the left hand side.
That's important in this situation as it means that node.next on the right hand side gets its value saved, so that when you rebind it to something else (pre), the old value is still available to become the new node value after the assignment.
You may want to play around with some simpler assignments, like the classic swap operation:
x = 1
y = 2
x, y = y, x # swap x and y's values
print(x, y) # prints "2 1"
_tup = y, x # this is how it works, first pack the RHS values into a tuple
x, y = _tup # then unpack the values into the names on the LHS
print(x, y) # prints "1 2" as we've swapped back
The main idea is to convert the original head node becomes the last node of the new linked list and convert the original last one become the new head node and convert the link direction between nodes.
suppose the original linked list consists 2 nodes.
first, pre = None, the node = head, then node.next = pre that means the original head node becomes the last node of the new linked list. node = node.next that means to convert the link direction between nodes. node.next = pre means to convert the original last one becomes the new head.
while repeatedly execute the above process
Here is a related question that links to docs on evaluation order: Tuple unpacking order changes values assigned
From https://docs.python.org/3/reference/expressions.html#evaluation-order, the example expr3, expr4 = expr1, expr2 shows evaluation order through the suffix number. It shows that the right side of assignment is evaluated first, from left to right, then the left side of assignment is evaluated, also from left to right.
For mutable objects like in this question, it gets more confusing without knowing the evaluation order.
To prove that it is indeed left-to-right on the left-hand-side, you can imagine what happens when pre, node.next, node = node, pre, node.next is assigned from right-to-left, meaning:
node = node.next
node.next = pre
pre = node
This wouldn't be reversing the Linked List at all.
Other ways to write this reversal:
Sometimes you can see others express this pattern as
pre, pre.next, node = node, pre, node.next
(Notice the 2nd element on LHS changed from node.next to pre.next.
This still works because after the first evaluation of pre = node, pre and node are referring to the same node. However, it introduces an extra dependency on the first evaluation of pre = node, which adds unnecessary cognitive load on the reader.
If we remained at pre, node.next, node = node, pre, node.next, then even swapping the first two variables (do it on both left and right of assignment) works:
node.next, pre, node = pre, node, node.next.
This is also my most preferred form since the right-hand-side naturally follows a previous,current,next order of the linked list.
Generally, we should place the dependent objects on the left of independent objects when ordering a tuple of variables on the left-hand-side. Any ordering with node = node.next before node.next = pre should break the implementation. (One example already shown in the thought experiment above on right-to-left evaluation order.)
Related
I am working on LeetCode problem 430. Flatten a Multilevel Doubly Linked List:
You are given a doubly linked list, which contains nodes that have a
next pointer, a previous pointer, and an additional child pointer.
This child pointer may or may not point to a separate doubly linked
list, also containing these special nodes. These child lists may have
one or more children of their own, and so on, to produce a multilevel
data structure as shown in the example below.
Given the head of the first level of the list, flatten the list so
that all the nodes appear in a single-level, doubly linked list. Let
curr be a node with a child list. The nodes in the child list should
appear after curr and before curr.next in the flattened list.
Return the head of the flattened list. The nodes in the list must have
all of their child pointers set to null.
My Approach
This is what I intended to do:
Traverse through the head node and wherever a child node appears, change the link of the current node to the child node.
Then, add all the nodes traversed to a separate LList (in my code it is ll). I have kept dummy at the head of the new LList.
While reaching at every child node, add that node address to a stack. So, when doing a pop() operation, the last child node added will be retrieved first.
After that, pop and iterate through that child node till None and add new nodes to new LList i.e. ll. Lastly, return dummy.next
My Code
class Solution:
def flatten(self, head: 'Optional[Node]') -> 'Optional[Node]':
curr = head
ll = dummy = ListNode(0)
stack = []
while curr:
if curr.child:
stack.append(curr)
ll.next = curr
curr.next = curr.child
else:
ll.next = curr
ll = ll.next
curr = curr.next
while stack:
curr_node = stack.pop()
while curr_node:
ll.next = curr_node
ll = ll.next
curr_node = curr_node.next
return dummy.next
The Problem
I am getting a Time Limit Exceeded warning. Can someone please tell me if I am correct with my approach and where I am going wrong?
The second part of your code is creating a cycle in the ll list. The node that you pop from the stack was already put in the ll list at the time you pushed that node on the stack. But you append it again to the ll list, making a cycle. And so while curr_node: is becoming an infinite loop.
The general idea for the algorithm is fine though, but push the next node on the stack, instead of the current node.
Furthermore, you should:
Clear the child reference once you have processed it
Adapt the prev references, so they are consistent with the changes to some of the next references. Remember: it is a doubly linked list you get and need to return.
I would not:
Lag behind with a separate ll reference. You can just work with curr
Use the dummy node. Although I understand its utility, once you have dealt with the case of the empty list (as a boundary case), there is really no benefit anymore
So here is how I would do it:
class Solution:
def flatten(self, head: 'Optional[Node]') -> 'Optional[Node]':
if not head:
return
stack = []
current = head
while current:
if current.child:
if current.next:
stack.append(current.next)
current.next = current.child
current.next.prev = current
current.child = None
elif not current.next and stack:
current.next = stack.pop()
current.next.prev = current
current = current.next
return head
I am attempting to use DFS to solve a graph problem, and have ran into a wall. This is my implementation in Python3. unvisited is an array of ints, start and end are ints in unvisited, path is an empty array filled out as the DFS goes, and edges is a dictionary of edges.
def traverse(unvisited, start, end, path, edges):
copy_unvisited = unvisited.copy()
copy_path = path.copy()
current = start
copy_unvisited.remove(current)
copy_path.append(current)
if current == end and len(copy_unvisited)==0:
#print is just for me to check my answers
print(copy_path)
return copy_path
for i in edges[current]:
if i in copy_unvisited:
return traverse(copy_unvisited, i, end, copy_path, edges)
The goal is to find a path that starts on start and visits every int in unvisited until ending on end. Therefore, I'm running into a problem with the recursion (I think) because in cases where the path is wrong, I don't want to return anything; instead, I want the DFS to continue. With the way the code is written now, I'm getting "None" returned, even though the correct solution is being printed (because of my print statement). How can I fix this issue?
Thanks in advance!
Edit: If unvisited = [1,3,4,5], start = 4, end = 5, edges = {1: (4,5), 3: (1), 4: (1,3,5), 5: (1,4)},
traverse(unvisited, 4, 5, [], edges) should return [4,3,1,5], but instead I get None. traverse does not have a return type if a wrong path is followed, which is why I think I'm getting None. It eventually finds the right path and prints it.
I have found a solution by introducing a new parameter sol, and when the correct path is reached, I copy each entry of copy_path into sol. Then, I return sol at the END, after the recursive call. I also removed the return statement for the recursive call.
def traverse(unvisited, start, end, path, edges,sol):
copy_unvisited = unvisited.copy()
copy_path = path.copy()
current = start
copy_unvisited.remove(current)
copy_path.append(current)
if current == end and len(copy_unvisited)==0:
for i in copy_path:
sol.append(i)
for i in edges[current]:
if i in copy_unvisited:
traverse(copy_unvisited, i, end, copy_path, edges,sol)
return sol
Feels a little inelegant, so I'm open to better ways to solve this problem!
First of all, with the input that you gave, the output should be [4 1 5], since the first edge linked to the vertex 4 is 1 and at the time of the first execution of the DPS 1 still in the unvisited list.
The main problem with your original code is these two instructions:
The
len(copy_unvisited)==0:
in the first if and the
if i in copy_unvisited:
at the end.
Basically you have this:
First call of the function:
node 4, unvisited (uv): [1,3,4,5], path=[]
At the for: 4 uv: [1,3,5] edges: (1, 3, 5)
So i=1, i is in uv, then:
Second call of the function:
node 1, uv: [1,3,5], path=[4]
At the for: 1 uv: [3,5] edges: (4,5)
So i=4, but 4 is not in uv,
So i=5, 5 is in uv, then:
Third call of the function *OBS1*
node 5, uv: [3,5], path=[4,1]
it will not enter in the first if, because len(uv) is not equals 0.
At the for: 5 uv: [3] edges: (1,4)
So i=1, but 1 is not in uv
So i=4, but 4 is not in uv
The for ends, so the function ends and not call itself, neither return a value.
I am pretty sure the DPS should end when you get in the wanted node, or, when you have no more nodes to visit. In the first case, it should return the path it found, if not, it means the end node there is no path from the start node to the end node (you have it in a disconnected graph).
I tried modify your implementation, but I found it difficult to deal with this "unvisited" variable and with the path being updated in this way. I don't think you are conceptually incorrect, both in the overview of how DFS should work, and in how to update the recurrence, but I was unable to make it work that way and so I modified the algorithm so that it updated the path to as it unstack the functions, instead of doing so while it calls the functions.
def traverse(start,end,edges,visited=[],path = []):
if start == end:
path.append(start)
return path
if start not in visited:
visited.append(start)
if start not in edges:
return path
for i in edges[start]:
path = traverse(i,end,edges,visited,path )
if len(path)>0:
break
if len(path)>0 and start not in path:
path.append(start)
return path
This will return the path, written in reverse, if you want in the normal order just take the final result of the function, which will be a list and apply the .reverse () method on it.
This implementation above is a slight modification of the one I found here: https://likegeeks.com/depth-first-search-in-python/ I just added the part of finding a path between two specific points.
Your "sol" solution work, partially, because, when the DFS get into the the third call and left the for without calling the function again, it, at least can return a empty list, then the third call leaves the stack and the algorithm continues to the next i of the second function call, that is 3. The problem with this implementation is the DFS will read all nodes before return a path to the destination.
I have this mathematical expression:
tree = ast.parse('1 + 2 + 3 + x')
which corresponds to this abstract syntax tree:
Module(body=[Expr(value=BinOp(left=BinOp(left=BinOp(left=Num(n=1), op=Add(), right=Num(n=2)), op=Add(), right=Num(n=3)), op=Add(), right=Name(id='x', ctx=Load())))])
and I would like to simplify it - that is, get this:
Module(body=[Expr(value=BinOp(left=Num(n=6), op=Add(), right=Name(id='x', ctx=Load())))])
According to the documentation, I should use the NodeTransformer class. A suggestion in the docs says the following:
Keep in mind that if the node you’re operating on has child nodes you
must either transform the child nodes yourself or call the
generic_visit() method for the node first.
I tried implementing my own transformer:
class Evaluator(ast.NodeTransformer):
def visit_BinOp(self, node):
print('Evaluating ', ast.dump(node))
for child in ast.iter_child_nodes(node):
self.visit(child)
if type(node.left) == ast.Num and type(node.right) == ast.Num:
print(ast.literal_eval(node))
return ast.copy_location(ast.Subscript(value=ast.literal_eval(node)), node)
else:
return node
What it should do in this specific case is simplify 1+2 into 3, and then 3 +3 into 6.
It does simplify the binary operations I want to simplify, but it doesn't update the original Syntax Tree. I tried different approaches but I still don't get how I can recursively simplify all binary operations (in a depth-first manner). Could anyone point me in the right direction?
Thank you.
There are three possible return values for the visit_* methods:
None which means the node will be deleted,
node (the node itself) which means no change will be applied,
A new node, which will replace the old one.
So when you want to replace the BinOp with a Num you need to return a new Num node. The evaluation of the expression cannot be done via ast.literal_eval as this function only evaluates literals (not arbitrary expressions). Instead you can use eval for example.
So you could use the following node transformer class:
import ast
class Evaluator(ast.NodeTransformer):
ops = {
ast.Add: '+',
ast.Sub: '-',
ast.Mult: '*',
ast.Div: '/',
# define more here
}
def visit_BinOp(self, node):
self.generic_visit(node)
if isinstance(node.left, ast.Num) and isinstance(node.right, ast.Num):
# On Python <= 3.6 you can use ast.literal_eval.
# value = ast.literal_eval(node)
value = eval(f'{node.left.n} {self.ops[type(node.op)]} {node.right.n}')
return ast.Num(n=value)
return node
tree = ast.parse('1 + 2 + 3 + x')
tree = ast.fix_missing_locations(Evaluator().visit(tree))
print(ast.dump(tree))
I'm trying to create a minimax type AI which would go through 4 layers of moves and try to pick out the best possible move based on a certain heuristic. The thing is in my state machine if I ever reach a node which is an illegal move then I return the value None instead of a normal point value that my heuristic function would give. When dealing with this in my minimax function I'm kind of unsure how to go about it in the best way. So far it kind of looks like this and was wondering if this makes sense.
def ai_min_max(board, ai_mancala, player_mancala, ai_choices, player_choices, target_depth, cur_depth, maxTurn, position):
#base case where we call our heuristic function to tell us what the value of this state is
if cur_depth == target_depth :
#return the heuristic value for this state
return first_heuristic(board, ai_mancala, player_mancala, ai_choices, player_choices, position)
#if we are currently on a level where we are maximizing our function
if maxTurn :
#set the value to negative infinity
max_eval = float("-inf")
#go through the 10 possible choices you can make
for x in range(len(ai_choices)) :
new_position = position + [x]
my_eval = ai_min_max(board, ai_mancala, player_mancala, ai_choices, player_choices, target_depth, cur_depth +1, False, new_position)
#update the current max only if we have a valid movement, if not then do not update
if my_eval is not None:
max_eval = max(max_eval, my_eval)
if max_eval == float("-inf") :
return float("inf")
return max_eval
#if it is the minimizing player's turn
else :
min_eval = float("inf")
for x in range(len(player_choices)) :
new_position = position + [x]
my_eval = ai_min_max(board, ai_mancala, player_mancala, ai_choices, player_choices, target_depth, cur_depth +1, True, new_position)
if my_eval is not None:
min_eval = min(min_eval, my_eval)
#if there were no valid moves
if min_eval == float("inf") :
return float("-inf")
return min_eval
Typically in a minimax implementation you never actually make a recursive call on illegal moves - these are never generated in the first place. But, in some instances it can be easier (or less expensive) to actually apply the move to find out whether it is legal or not. For instance, if you have to apply a complex computation to find out if a move is legal, then you don't want to do it twice (once when you generate potential moves and once when you search on them). So, I will assume this is the case here.
Given this, does it make sense to return a special value as in your code above.
No, there is a better approach. At a min node you can return -inf to the parent when the move is illegal, and at a max node you can return inf to the parent. In this way, the illegal moves have the worse possible value, and will be handled naturally by the rest of the search without any other special cases. This makes the main minimax/alpha-beta loop much simpler.
The only complication to this is if a max player at the root has all moves losing it might return the illegal move. You can handle this case outside the main search - where testing a single move is very cheap compared to the full search - and just return any legal move if the move returned was illegal.
I am implementing the merge sort algorithm in Python. Previously, I have implemented the same algorithm in C, it works fine there, but when I implement in Python, it outputs an unsorted array.
I've already rechecked the algorithm and code, but to my knowledge the code seems to be correct.
I think the issue is related to the scope of variables in Python, but I don't have any clue for how to solve it.
from random import shuffle
# Function to merge the arrays
def merge(a,beg,mid,end):
i = beg
j = mid+1
temp = []
while(i<=mid and j<=end):
if(a[i]<a[j]):
temp.append(a[i])
i += 1
else:
temp.append(a[j])
j += 1
if(i>mid):
while(j<=end):
temp.append(a[j])
j += 1
elif(j>end):
while(i<=mid):
temp.append(a[i])
i += 1
return temp
# Function to divide the arrays recursively
def merge_sort(a,beg,end):
if(beg<end):
mid = int((beg+end)/2)
merge_sort(a,beg,mid)
merge_sort(a,mid+1,end)
a = merge(a,beg,mid,end)
return a
a = [i for i in range(10)]
shuffle(a)
n = len(a)
a = merge_sort(a, 0, n-1)
print(a)
To make it work you need to change merge_sort declaration slightly:
def merge_sort(a,beg,end):
if(beg<end):
mid = int((beg+end)/2)
merge_sort(a,beg,mid)
merge_sort(a,mid+1,end)
a[beg:end+1] = merge(a,beg,mid,end) # < this line changed
return a
Why:
temp is constructed to be no longer than end-beg+1, but a is the initial full array, if you managed to replace all of it, it'd get borked quick. Therefore we take a "slice" of a and replace values in that slice.
Why not:
Your a luckily was not getting replaced, because of Python's inner workings, that is a bit tricky to explain but I'll try.
Every variable in Python is a reference. a is a reference to a list of variables a[i], which are in turn references to a constantant in memory.
When you pass a to a function it makes a new local variable a that points to the same list of variables. That means when you reassign it as a=*** it only changes where a points. You can only pass changes outside either via "slices" or via return statement
Why "slices" work:
Slices are tricky. As I said a points to an array of other variables (basically a[i]), that in turn are references to a constant data in memory, and when you reassign a slice it goes trough the slice element by element and changes where those individual variables are pointing, but as a inside and outside are still pointing to same old elements the changes go through.
Hope it makes sense.
You don't use the results of the recursive merges, so you essentially report the result of the merge of the two unsorted halves.