Adding recursive copy method to linked list implementation in Python? - python

Hi I implemented a linked list in Python and I got this piece of code that can copy the linked list to another linked list but I cannot understand why its inserting at 0 index instead?
Then wouldn't the copied list be reversed? But I tried running it and the output is correct and is in order?
My insert function
def insert(self, index, item):
if index<0 and index<-1*len(self):
raise IndexError("Negative index out of range")
elif index>0 and index>=len(self):
raise IndexError("Positive index out of range")
if index==0:
self.head=Node(item,self.head)
elif index>0:
node=self._get_node(index-1)
node.next=Node(item,node.next)
else:
node=self._get_node(index+len(self))
node.next=Node(item,node.next)
self.count+=1
My copy function
def copy(self):
new_list=linkedList()
self._copy_aux_(self.head,new_list)
return new_list
def _copy_aux_(self, node, new_list):
if node is not None:
self._copy_aux_(node.next, new_list)
new_list.insert(0,node.item)
Can someone please help explain this?Any help will be appreciated thanks!
EDIT:Okay apparently it inserts the last item first?Why is that?

1 -When you call insert with index equal to zero you insert the list item upfront, that is, in the location pointed by head(see method def insert(self, index, item):
2 - When the method copy is called the method def _copy_aux_(self, node, new_list): is called recursively until the last item of the list is reached, which has node.next equals to None.
3 - After that, after each return from the method _copy_aux_, it starts inserting the itens in the new_list up front from the last to the first item, which gives the correct order.
I suggest to include prints to trace the list copy recursion, as the code below:
def copy(self):
print('Trace copy:')
new_list=linkedList()
self._copy_aux_(self.head,new_list)
return new_list
def _copy_aux(self, node, new_list):
if node is not None:
self._copy_aux_(node.next, new_list)
new_list.insert(0,node.item)
print(new_list)
then run the following example (taken from https://repl.it/#MuhammadFermi/week8-2):
list = linkedList()
list.insert(0, 3)
list.insert(0, 9)
list.insert(0, 2)
list.insert(0, 5)
list.insert(0, 1)
list.insert(2, 6)
list.insert(10, 7)
print(list)
print(len(list))
print(3 in list)
list2 = list.copy()
print('list2 =')
print(list2)
You should get the following results:
1, 5, 6, 2, 9, 3, 7,
7
True
Trace copy:
7,
3, 7,
9, 3, 7,
2, 9, 3, 7,
6, 2, 9, 3, 7,
5, 6, 2, 9, 3, 7,
1, 5, 6, 2, 9, 3, 7,
list2 =
1, 5, 6, 2, 9, 3, 7,
Process finished with exit code 0

I suggest flattening your list, and then re-inserting the values into a new list:
class LinkedList:
def __init__(self, value = None):
self.head = value
self._next = None
def insert_node(self, _val):
if self.head is None:
self.head = _val
else:
getattr(self._next, 'insert_node', lambda x:setattr(self, '_next', LinkedList(x)))(_val)
def flatten(self):
return [self.head, *getattr(self._next, 'flatten', lambda :[])()]
#classmethod
def copy(cls, l):
t = cls()
for i in l.flatten():
t.insert_node(i)
return t
l = LinkedList()
for i in range(10):
l.insert_node(i)
new_l = LinkedList.copy(l)

Related

How find all pairs equal to N in a list

I have a problem with this algorithm- I have to find pairs in list:
[4, 8, 9, 0, 12, 1, 4, 2, 12, 12, 4, 4, 8, 11, 12, 0]
which are equal to 12. The thing is that after making a pair those numbers (elements) can not be used again.
For now, I have code which you can find below. I have tried to delete numbers from the list after matching, but I feel that there is an issue with indexing after this.
It looks very easy but still not working. ;/
class Pairs():
def __init__(self, sum, n, arr ):
self.sum = sum
self.n = n
self.arr = arr
def find_pairs(self):
self.n = len(self.arr)
for i in range(0, self.n):
for j in range(i+1, self.n):
if (self.arr[i] + self.arr[j] == self.sum):
print("[", self.arr[i], ",", " ", self.arr[j], "]", sep = "")
self.arr.pop(i)
self.arr.pop(j-1)
self.n = len(self.arr)
i+=1
def Main():
sum = 12
arr = [4, 8, 9, 0, 12, 1, 4, 2, 12, 12, 4, 4, 8, 11, 12, 0]
n = len(arr)
obj_Pairs = Pairs(sum, n, arr)
obj_Pairs.find_pairs()
if __name__ == "__main__":
Main()
update:
Thank you guys for the fast answers!
I've tried your solutions, and unfortunately, it is still not exactly what I'm looking for. I know that the expected output should look like this: [4, 8], [0, 12], [1, 11], [4, 8], [12, 0]. So in your first solution, there is still an issue with duplicated elements, and in the second one [4, 8] and [12, 0] are missing. Sorry for not giving output at the beginning.
With this problem you need to keep track of what numbers have already been tried. Python has a Counter class that will hold the count of each of the elements present in a given list.
The algorithm I would use is:
create counter of elements in list
iterate list
for each element, check if (target - element) exists in counter and count of that item > 0
decrement count of element and (target - element)
from collections import Counter
class Pairs():
def __init__(self, target, arr):
self.target = target
self.arr = arr
def find_pairs(self):
count_dict = Counter(self.arr)
result = []
for num in self.arr:
if count_dict[num] > 0:
difference = self.target - num
if difference in count_dict and count_dict[difference] > 0:
result.append([num, difference])
count_dict[num] -= 1
count_dict[difference] -= 1
return result
if __name__ == "__main__":
arr = [4, 8, 9, 0, 12, 1, 4, 2, 12, 12, 4, 4, 8, 11, 12, 0]
obj_Pairs = Pairs(12, arr)
result = obj_Pairs.find_pairs()
print(result)
Output:
[[4, 8], [8, 4], [0, 12], [12, 0], [1, 11]]
Demo
Brief
If you have learned about hashmaps and linked lists/deques, you can consider using auxiliary space to map values to their indices.
Pro:
It does make the time complexity linear.
Doesn't modify the input
Cons:
Uses extra space
Uses a different strategy from the original. If this is for a class and you haven't learned about the data structures applied then don't use this.
Code
from collections import deque # two-ended linked list
class Pairs():
def __init__(self, sum, n, arr ):
self.sum = sum
self.n = n
self.arr = arr
def find_pairs(self):
mp = {} # take advantage of a map of values to their indices
res = [] # resultant pair list
for idx, elm in enumerate(self.arr):
if mp.get(elm, None) is None:
mp[elm] = deque() # index list is actually a two-ended linked list
mp[elm].append(idx) # insert this element
comp_elm = self.sum - elm # value that matches
if mp.get(comp_elm, None) is not None and mp[comp_elm]: # there is no match
# match left->right
res.append((comp_elm, elm))
mp[comp_elm].popleft()
mp[elm].pop()
for pair in res: # Display
print("[", pair[0], ",", " ", pair[1], "]", sep = "")
# in case you want to do further processing
return res
def Main():
sum = 12
arr = [4, 8, 9, 0, 12, 1, 4, 2, 12, 12, 4, 4, 8, 11, 12, 0]
n = len(arr)
obj_Pairs = Pairs(sum, n, arr)
obj_Pairs.find_pairs()
if __name__ == "__main__":
Main()
Output
$ python source.py
[4, 8]
[0, 12]
[4, 8]
[1, 11]
[12, 0]
To fix your code - few remarks:
If you iterate over array in for loop you shouldn't be changing it - use while loop if you want to modify the underlying list (you can rewrite this solution to use while loop)
Because you're iterating only once the elements in the outer loop - you only need to ensure you "popped" elements in the inner loop.
So the code:
class Pairs():
def __init__(self, sum, arr ):
self.sum = sum
self.arr = arr
self.n = len(arr)
def find_pairs(self):
j_pop = []
for i in range(0, self.n):
for j in range(i+1, self.n):
if (self.arr[i] + self.arr[j] == self.sum) and (j not in j_pop):
print("[", self.arr[i], ",", " ", self.arr[j], "]", sep = "")
j_pop.append(j)
def Main():
sum = 12
arr = [4, 8, 9, 0, 12, 1, 4, 2, 12, 12, 4, 4, 8, 11, 12, 0]
obj_Pairs = Pairs(sum, arr)
obj_Pairs.find_pairs()
if __name__ == "__main__":
Main()

Returning list of all paths in BST python

Say I have a binary tree with the paths from root to leaf being 3-1-3, 3-4-5, 3-4-1 ... How would I go about returning a list of all the different paths? This is what I have so far, but all it does is return [[3, 1, 3, 4, 1, 5], [3, 1, 3, 4, 1, 5], [3, 1, 3, 4, 1, 5]], which is three lists of all the nodes in the tree instead of a separate list for each path.
self.res = []
def helper(root, temp):
if not root:
return
temp.append(root.val)
if not root.left and not root.right:
self.res.append(temp)
return
helper(root.left, temp)
helper(root.right, temp)
helper(root, [])
You can't append to temp like that you need to create a different copy of the current temp for both the left and right branch recursive calls.
Here is a slightly modified version of your recursive DFS approach that should work as you desire, by doing that:
class BinaryTreePaths:
def __init__(self):
self.res = []
def get_binary_tree_paths(self, root):
if not root:
return []
self.helper(root, [])
return self.res
def helper(self, node, temp):
if not node.left and not node.right:
self.res.append(temp + [node.val])
if node.left:
self.helper(node.left, temp + [node.val])
if node.right:
self.helper(node.right, temp + [node.val])

Conditional checking between two int objects

I am iterating in a for loop, constructing a list, whilst comparing the last number of that list with another number.
I would like my code to see if the last item of the list is smaller than the item it is being compared to, and if it is, to add it to the end of its list and then to continue.
if the last item of the list is larger, i would like to pop off the last item of the list. i would then like to subject it to the same conditionals.
here is my code, it is not working, it wont re check the conditionals after popping off the last item of the list.
if tempList:
lastNum=tempList[-1]
#############################################
if element < lastNum:
incList.append(tempList)
tempList.pop()
lastNum=tempList[-1]
#############################################
elif lastNum < element:
tempList.append(element)
continue
You can bundle this into a function:
def append_if_lower_else_pop_end_from_list_until_lower(l, num):
"""Add num to l if l[-1] < num, else pop() from l until"""
while l and l[-1] > num:
l.pop()
l.append(num)
# this is not strictly needed - lists are mutable so you are mutating it
# returning it would only make sense for chaining off it with other methods
return l
k = [3,5,7,9,11,13]
print(k)
append_if_lower_else_pop_end_from_list_until_lower(k, 10)
print(k)
append_if_lower_else_pop_end_from_list_until_lower(k, 6)
print(k)
append_if_lower_else_pop_end_from_list_until_lower(k, 10)
print(k)
append_if_lower_else_pop_end_from_list_until_lower(k, 10)
print(k)
append_if_lower_else_pop_end_from_list_until_lower(k, -10)
print(k)
Output:
[3, 5, 7, 9, 11, 13] # start
[3, 5, 7, 9, 10] # after adding 10
[3, 5, 6] # after addding 6
[3, 5, 6, 10] # after adding 10
[3, 5, 6, 10, 10] # after adding 10 again
[-10] # after adding -10
Why return the list as well: example for chaining:
k = [3,5,17,9,11,13]
append_if_lower_else_pop_end_from_list_until_lower(k, 10).sort()
print(k)
Output:
[3, 5, 9, 10, 17]
Try this out:
yourlist = [3,1,4]
n = 1
resultlist = yourlist[:-1] if yourlist[-1]>=n else yourlist+[n]
print(resultlist)
n = 5
resultlist = yourlist[:-1] if yourlist[-1]>=n else yourlist+[n]
print(resultlist)
Output:
[3,1]
[3,1,4,5]

Trying to For Loop a list and append to new list, but it returns after the first item

I'm trying to solve this task:
Loop through list A and create a new list with only items form list A that's between 0-5.
What am I doing wrong here?
a = [100, 1, 10, 2, 3, 5, 8, 13, 21, 34, 55, 98]
def new_list(x):
for item in range(len(x)):
new = []
if x[item] < 5 and x[item] > 0:
(new.append(item))
return new
print(new_list(a))
I'm just getting [1] as an answer.
You return command is inside the loop so as soon as it goes through the first case it returns the value exiting the function.
Here is an example of what your code should look like
a = [100, 1, 10, 2, 3, 5, 8, 13, 21, 34, 55, 98]
def new_list(x):
new = []
for item in range(len(x)):
if x[item] < 5 and x[item] > 0:
new.append(x[item])
return new
print new_list(a)
You can achieve the same result by using a list comprehension
def new_list(x):
return [item for item in x if 0 < item < 5]
You're resetting new to a brand new empty list each time through the loop, which discards any work done in prior iterations.
Also, in the if statement you're calling return, which exits your function immediately, so you never process the remainder of the list.
You probably wanted something like this instead:
def new_list(x):
new = []
for item in x:
if 0 < item < 5:
new.append(item)
return new
Just my recommendation. You could use filter() here instead of a making your own loop.
a = [100, 1, 10, 2, 3, 5, 8, 13, 21, 34, 55, 98]
def new_list(x, low=0, high=5):
return filter(lambda f: f in range(low, high), x)
Filter returns a new list with elements passing a given predicate and it's equivalent to
[item for item in iterable if function(item)]
as per the documentation.
Therefore
print new_list(a)
Results in:
[1, 2, 3, 5]
This way you can check any values such as:
print new_list(a, 5, 10)
[5, 8]
Three errors:
you are reinstantiating new with each iteration of the for loop.
you should return new when the list is finished building, at the end of the function.
You are appending item, but this is your index. In your code, you would have to append x[item].
Code with corrections:
a = [100, 1, 10, 2, 3, 5, 8, 13, 21, 34, 55, 98]
def new_list(x):
new = []
for item in range(len(x)):
if x[item] < 5 and x[item] > 0:
new.append(x[item])
return new
print(new_list(a))
Output:
[1, 2, 3]
Suggestions:
Don't index, loop over the items of x directly (for item in x: ...).
Use chained comparisons, e.g. 0 < item < 5.
Consider a list comprehension.
Code with all three suggestions:
>>> [item for item in a if 0 < item < 5]
>>> [1, 2, 3]
Just a suggestion!
The empty list is inside the For Loop meaning that a new empty list is created every iteration
The 'return' is also inside the for loop which is less than ideal, you want it to be returned after the loop has been exhausted and all suitable elements have been appended.
a = [100, 1, 10, 2, 3, 5, 8, 13, 21, 34, 55, 98]
def new_list(x):
new = []
for item in range(len(x)):
if x[item] < 5 and x[item] > 0:
new.append(item)
return new
print(new_list(a))

Inserting a set of elements alternatively

There is a list with elements of similar nature (4 7's,3 5's, etc.) that I want to insert in right left order into a another list ().
newlst = []
lst = [7, 7, 7, 7, 5, 5, 5, 3, 3, 3, 2, 2]
So the first thing being inserted into newlst is the group of 7's:
newlst = [7,7,7,7]
Subsequently, the group of 5's is inserted into the list on the right:
newlst = [7, 7, 7, 7, 5, 5, 5]
And then the group of 3's is inserted on the left, and after that the group of 2's is inserted on the right. The final list looks like this
newlst = [3, 3, 3, 7, 7, 7, 7, 5, 5, 5, 2, 2]
In order to add elements in the list on a right left basis, I did this:
for i in lst:
lst.insert(0,i)
else:
lst.append(i)
The insert method inserts elements into the 0 index (which is the right of the list) and append adds elements at the end of the list (which is the left of the list). However, I'm having problems adding the group of elements into the newlst. To that end, I thought using a dictionary would be a good idea.
myDict = {2: 2, 3: 3, 5: 3, 7: 4}
EDIT:
for k, v in myDict.items():
if k in lst:
for i in range(v):
lst.append(i)
else:
lst.insert(0,i)
The intention of this dictionary is for each key, I want to insert the key value 'x' times, e.g. the key 7, would be inserted 4 times: [7,7,7,7]. Is there a way to achieve this in Python so I can get the output newlist: [3, 3, 3, 7, 7, 7, 7, 5, 5, 5, 2, 2] ?
You can accomplish this pretty easily with a deque, along with cycle and groupby
from collections import deque
from itertools import groupby, cycle
#creates a deque object
d = deque()
#creates a repeating iterator to alternate function calls
c = cycle([d.extendleft, d.extend])
lst = [7, 7, 7, 7, 5, 5, 5, 3, 3, 3, 2, 2]
for _, items in groupby(lst):
#calls the alternated function to extend the items
next(c)(items)
print(list(d))
>>> [3, 3, 3, 7, 7, 7, 7, 5, 5, 5, 2, 2]
Here is your initial code:
newlst = []
lst = [7, 7, 7, 7, 5, 5, 5, 3, 3, 3, 2, 2]
myDict = {2: 2, 3: 3, 5: 3, 7: 4}
for k, v in myDict.items():
if k in lst:
for i in range(v):
lst.append(i)
else:
lst.insert(0,i)
You have a few major problems here:
k is always in lst, by definition. That means your check is not a valid way to alternate.
Your data is getting appended/prepended to lst instead of newlst.
A dict is a hash-table. This means that the order of the keys will pretty much never be in the order you defined them in.
The first item can be solved through enumeration:
for i, (k, v) in enumerate(myDict.items()):
if i % 2:
newlst = [k] * v + newlst
else:
newlst += [k] * v
I've fixed the list you are appending to, and am using [k] * v to construct the prepended/appended list. newlst += [k] * v is equivalent to newlst.extend([k] * v). However, keep in mind that newlst = [k] * v + newlst creates a new list object rather than concatenating in-place.
The third item can be fixed using OrderedDict instead of a regular dict:
from collections import OrderedDict
...
myDict = OrderedDict([(2, 2), (3, 3), (5, 3), (7, 4)])
That will make the keys run in the order that you want. In fact, you don't need to construct myDict by hand at all. You can combine OrderedDict with a Counter to get the exact same result dynamically. The recipe for this is given in the OrderedDict docs:
from collections import Counter, OrderedDict
...
class OrderedCounter(Counter, OrderedDict):
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))
def __reduce__(self):
return self.__class__, (OrderedDict(self),)
myDict = OrderedCounter(lst)
All this is pretty verbose and not very efficient. As #Wondercricket's answer points out, you can use the functions in itertools to perform the same task using generators.
This is what you want to do?
list = [7, 7, 7, 7, 5, 5, 5, 3, 3, 3, 2, 2]
def list_to_answerlist(list, default=[]):
if not list:
return default
else:
result = []
direction = True # Insert Direction: True = Insert Left / False = Insert Right
actual_group = list[0]
for element in list:
if (element != actual_group):
direction = not direction # Change Insert Direction
actual_group = element # Update Actual Group
if direction: # Insert Left
result.insert(0,element)
else: # Insert Right
result.append(element)
return result
new_list = list_to_answerlist(list) # output = [3, 3, 3, 7, 7, 7, 7, 5, 5, 5, 2, 2]

Categories

Resources