How the linked list works? - python

I do not have computer science background. I am trying to learn coding by myself, and I'm doing it, partly, by solving the problems on LeetCode.
Anyway, there are the problems that use Linked Lists. And I already found info that linked list have to be simulated in Phython. My problem is that I really cannot get what is behind linked list. For instance, what kind of problems those are suppose to target?
And in general how linked list function. Any link for such info would be really helpfull.
The recent problem I looked at LeetCode asks to swap every two adjacent nodes and return its head. And LeetCode offers following solution, that I cannot actually figure out how it acutaly works.
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def swapPairs(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
pre = self
pre.next = head
while pre.next and pre.next.next:
a = pre.next
b = a.next
pre.next =b
b.next =a
a.next =b.next
pre = a
return self.next
As I said, I do not understand this solution. I tried to use example list 1->2->3->4 that should return list 2->1->4->3
All I managed is to make only one pass through the loop, and then computer should exit the loop, but then what happens? How are the last two numbers switched? How does this code work at all if list has only 2 elements, to me it seems impossible.
If you could just direct me to the online literature that explains something like this, I would be most grateful.
Thanks.

a linked-list acts almost the same as an array. There are a few main differences though. In a linked-list, the memory used doesn't (and almost never is) contiguous memory. So in an array, if u have 5 items and you look at the memory all 5 items will be right next to each other (for the most part). However each 'item' in a linked list has a pointer that points directly to the next item, removing the need to have contiguous memory. So an array is a 'list' of items that exist contiguously in memory and a linked-list is a 'list' of objects that each hold an item and a pointer to the next item. This is considered a single linked-list as traversal is only possible from one direction. There is also a double linked-list where each node now has a pointer to the next node and another pointer for the previous node allowing traversal from both directions.
https://www.cs.cmu.edu/~adamchik/15-121/lectures/Linked%20Lists/linked%20lists.html
the link will help you get familiar with visualizing how these linked-lists work. I would probably focus on inserting before and after as these should help you understand what your loop is doing.

Linked lists don't "exist" in Python as the language basically has an iterable builtin list object. Under the hood I'm sure this is implemented as a linked list in C code (most common implementation of Python).
The main feature is that a linked list is easily extendible, wheras an array has to be manually resized if you wish to expand it. Again, in Python these details are all abstracted away. So trying to work an example of linked lists in Python is pointless in my opinion, as you won't learn anything.
You should be doing this in C to get an actual understanding of memory allocation and pointers.
That said, given your example, each ListNode contains a value (like an array), but rather than just that, it has a variable 'next' where you store another ListNode object. This object, just like the first, has a value, and a variable that stores another ListNode object.This can continue for as many objects as desired.
The way the code works is that when we say pre.next, this refers to the ListNode object stored there, and the next object after that is pre.next.next. This works because pre.next is a ListNode object, which has a variable next.
Again, read up on linked lists in C. If you plan to work in higher level languages, I would say you don't really need an understanding of linked lists, as these data structures come "free" with most high level languages.

Related

Returning list of different results that are created recursively in Python

Lately I've been working with some recursive problems in Python where I have to generate a list of possible configurations (i.e list of permutations of a given string, list of substrings, etc..) using recursion. I'm having a very hard time in finding the best practice and also in understanding how to manage this sort of variable in recursion.
I'll give the example of the generate binary trees problem. I more-or-less know what I have to implement in the recursion:
If n=1, return just one node.
If n=3, return the only possible binary tree.
For n>3, crate one node and then explore the possibilities: left node is childless, right node is childless, neither node is childless. Explore these possibilites recursively.
Now the thing I'm having the most trouble visualising is how exactly I am going to arrive to the list of trees. Currently the practice I do is pass along a list in the function call (as an argument) and the function would return this list, but then the problem is in case 3 when calling the recursive function to explore the possibilites for the nodes it would be returning a list and not appending nodes to a tree that I am building. When I picture the recursion tree in my head I imagine a "tree" variable that is unique to each of the tree leaves, and these trees are added to a list which is returned by the "root" (i.e first) call. But I don't know if that is possible. I thought of a global list and the recursive function not returning anything (just appending to it) but the problem I believe is that at each call the function would receive a copy of the variable.
How can I deal with generating combinations and returning lists of configurations in these cases in recursion? While I gave an example, the more general the answer the better. I would also like to know if there is a "best practice" when it comes to that.
Currently the practice I do is pass along a list in the function call (as an argument) and the function would return this list
This is not the purest way to attack a recursive problem. It would be better if you can make the recursive function such that it solves the sub problem without an extra parameter variable that it must use. So the recursive function should just return a result as if it was the only call that was ever made (by the testing framework). So in the example, that recursive call should return a list with trees.
Alternatively the recursive function could be a sub-function that doesn't return a list, but yields the individual values (in this case: trees). The caller can then decide whether to pack that into a list or not. This is more pythonic.
As to the example problem, it is also important to identify some invariants. For instance, it is clear that there are no solutions when n is even. As to recursive aspect: once you have decided to create a root, then both its left and right sided subtree will have an odd number of nodes. Of course, this is an observation that is specific to this problem, but it is important to look for such problem properties.
Finally, it is equally important to see if the same sub problems can reoccur multiple times. This surely is the case in the example problem: for instance, the left subtree may sometimes have the same number of nodes as the right subtree. In such cases memoization will improve efficiency (dynamic programming).
When the recursive function returns a list, the caller can then iterate that list to retrieve its elements (trees in the example), and use them to build an extended result that satisfies the caller's task. In the example case that means that the tree taken from the recursively retrieved list, is appended as a child to a new root. Then this new tree is appended to a new list (not related to the one returned from the recursive call). This new list will in many cases be longer, although this depends on the type of problem.
To further illustrate the way to tackle these problems, here is a solution for the example problem: one which uses the main function for the recursive calls, and using memoization:
class Solution:
memo = { 1: [TreeNode()] }
def allPossibleFBT(self, n: int) -> List[Optional[TreeNode]]:
# If we didn't solve this problem before...
if n not in self.memo:
# Create a list for storing the results (the trees)
results = []
# Before creating any root node,
# decide the size of the left subtree.
# It must be odd
for num_left in range(1, n, 2):
# Make the recursive call to get all shapes of the
# left subtree
left_shapes = self.allPossibleFBT(num_left)
# The remainder of the nodes must be in the right subtree
num_right = n - 1 - num_left # The root also counts as 1
right_shapes = self.allPossibleFBT(num_right)
# Now iterate the results we got from recursion and
# combine them in all possible ways to create new trees
for left in left_shapes:
for right in right_shapes:
# We have a combination. Now create a new tree from it
# by putting a root node on top of the two subtrees:
tree = TreeNode(0, left, right)
# Append this possible shape to our results
results.append(tree)
# All done. Save this for later re-use
self.memo[n] = results
return self.memo[n]
This code can be made more compact using list comprehension, but it may make the code less readable.
Don't pass information into the recursive calls, unless they need that information to compute their local result. It's much easier to reason about recursion when you write without side effects. So instead of having the recursive call put its own results into a list, write the code so that the results from the recursive calls are used to create the return value.
Let's take a trivial example, converting a simple loop to recursion, and using it to accumulate a sequence of increasing integers.
def recursive_range(n):
if n == 0:
return []
return recursive_range(n - 1) + [n]
We are using functions in the natural way: we put information in with the arguments, and get information out using the return value (rather than mutation of the parameters).
In your case:
Now the thing I'm having the most trouble visualising is how exactly I am going to arrive to the list of trees.
So you know that you want to return a list of trees at the end of the process. So the natural way to proceed, is that you expect each recursive call to do that, too.
How can I deal with generating combinations and returning lists of configurations in these cases in recursion? While I gave an example, the more general the answer the better.
The recursive calls return their lists of results for the sub-problems. You use those results to create the list of results for the current problem.
You don't need to think about how recursion is implemented in order to write recursive algorithms. You don't need to think about the call stack. You do need to think about two things:
What are the base cases?
How does the problem break down recursively? (Alternately: why is recursion a good fit for this problem?)
The thing is, recursion is not special. Making the recursive call is just like calling any other function that would happen to give you the correct answer for the sub-problem. So all you need to do is understand how solving the sub-problems helps you to solve the current one.

looping in list while changing it

I want to loop through the original entries of a list even though it gets updated.
itterable = copy.deepcopy(dict[node].adjacent_nodes)
for i in itterable:
new_node = create_node(i)
dict[node].adjacent_nodes.append(new_node)
dict[node].adjacent_nodes.remove(i)
I want my dict[node].adjacent_nodes to be updated only the number of times as there are elements in dict[node].adjacent_nodes initially. This does not happen. What am I missing?
__
For example if dict[node].adjacent_nodes = [1,2] initially. Then my for loop should run 2 times. Each time creating a new_node and adding to dict[node].adjacent_nodes. At the end the for loop should terminate with dict[node].adjacent_nodes = [create_node(1), create_node(2)]
You didn't specify a full working example of the problem with output vs expected output, so I'll guess some possible issues:
A recursive loop can occur in the creation of this compound node object - in the create_node() part or in the copy.deepcopy() (complex objects that include a direct or indirect reference to self).
While doing the copy.deepcopy() you might copy some data that is later causing a problem as it is not meant to be copied (e.g. the global state of the graph etc.).
create_node() affects the itterable or the adjacent_nodes structures.
The append or remove do not behave the way you expect them to in your data struct.
Hope some of it helps you.

After I implemented a linked list in python, how do I free the memory of it?

Here is my implementation of the node of linked list
class ListNode(object):
def __init__(self,val):
self.val = val
self.next = None
Base on my understanding of how python manages the memory, I think when I need to free the memory, I still need to use a function like this below, isn't it?
def free_linked_list(head):
while head:
next_node = head.next
del head # As long as no variable is pointed to this node, it would be freed from the memory. Is it?
head = next_node
So, my problem is:
1. Is my understanding right?
2. If not, how could I free the memory of this linked list? Or python has already do it for me?
Yes your understanding is correct. As long as the object is no longer referenced, Python's garbage collection will take care of it. You can also explicitly call gc.collect() to force garbage collection if you really need too. Here's a link to the docs: https://docs.python.org/2/library/gc.html
You're correct that Python uses garbage collection in lieu of manual memory management like you might have in C or other low level languages. This means that the Python runtime utilizes a background thread that periodically marks and removes "orphaned" objects that are not referenced by any other object in your program.
In fact, your example is a bit more complicated than it needs to be. As long as there's no reference to the head of the list, then the entire list is "stranded" and will by garbage collected by the Python runtime.

Is it implicit in python that 'other' is another object in a list? Parameter has never been introduced properly

I just don't understand how an example in a book lists a parameter 'other' which is never introduced. When called the function, Python automatically understands it to be the other elements of the Class?
See the example:
def get_neighbors(self, others, radius, angle):
"""Return the list of neighbors within the given radius and angle."""
boids = []
for other in others:
if other is self: continue
offset = other.pos - self.pos
# if not in range, skip it
if offset.mag > radius:
continue
# if not within viewing angle, skip it
if self.vel.diff_angle(offset) > angle:
continue
# otherwise add it to the list
boids.append(other)
return boids
Nowhere else in the code there is a mention of 'other'.
Thanks, just trying to understand the mechanisms.
Updated answer, in response to comment
Python doesn't have any special behavior for a method parameter named "others", or for any of the other parameters in your example.
Most likely the book you're reading simply didn't explain (yet) how that function will be invoked. It's also possible that the book made a mistake (in which case, perhaps you should find a better book!).
Original answer (for posterity)
The name other is declared by the for statement:
for other in others:
...
From the Python documentation for the for statement:
The suite is then executed once for each item provided by the iterator, in the order of ascending indices. Each item in turn is assigned to the target list using the standard rules for assignments, and then the suite is executed.
Here, "the iterator" is derived from the list others, and "the target list" is simply the variable other. So on each iteration through the loop, the other variable is assigned ("using the standard rules for assignments") the next value from the list.
The DocString for that method should include the list of arguments and explain the expected type for each (I am planning to update this code soon, and I will improve the documentation).
In this case, others should be a list (or other sequence) of objects that have an attribute named pos (probably the same type as other).
Note that there is nothing special about the name 'others'.

Python loop with lists

I was writing some Python 3.2 code and this question came to me:
I've got these variables:
# a list of xml.dom nodes (this is just an example!)
child_nodes = [node1, node2, node3]
# I want to add every item in child_node into this node (this also a xml.dom Node)
parent = xml_document.createElement('TheParentNode')
This is exactly what I want to do:
for node in child_nodes:
if node is not None:
parent.appendChild(node)
I wrote it in one line like this:
[parent.appendChild(c) for c in child_nodes if c is not None]
I'm not going to use the list result at all, I only need the appendChild to do its work.
I'm not a very experienced python programmer, so I wonder which one is better?
I like the single line solution, but I would like to know from experienced python programmers:
Which one is better, in a context of either code beauty/maintainability) and performance/memory use.
The former is preferable in this situation.
The latter is called a list comprehension and creates a new list object full of the results of each call to parent.appendChild(c). And then discards it.
However, if you want to make a list based on this kind of iteration, then you should certainly employ a list comprehension.
The question of code beauty/maintainability is a tricky one. It's really up to you and whoever you work with to decide.
For a long time I was uncomfortable with list comprehensions and so on and preferred writing it the first way because it was easier for me to read. Some people I work with, however, prefer the second method.

Categories