Blossom Project

Hi @dgoelitz,

Within the __init__ method you have this:

    self.array = [LinkedList for i in range(size)]

Consequently, self.array becomes a list containing the LinkedList type, instead of containing instances of LinkedList. The problem is that you are not calling the LinkedList constructor, but instead are merely specifying the LinkeList type. It is analogous to doing something like this:

    nums = [int for i in range(7)]
    print(nums)

The output would be as follows instead of presenting a list of actual int objects:

[<class 'int'>, <class 'int'>, <class 'int'>, <class 'int'>, <class 'int'>, <class 'int'>, <class 'int'>]

Getting back to the Blossom project, do the following instead, so that the LinkedList constructor is invoked:

    self.array = [LinkedList() for i in range(size)]

Edited on January 3, 2019 to display the list of <class 'int'> items

1 Like

Thank you so much. This makes sense. I have to call LinkedList(). I’ll try to remember that.

1 Like

I have a question. I understand things by visualizing it (which can be hard for stuff like this). My question comes from the usage of LinkedList() and Node().

Calling LinkedList() creates a Node with value=None. So in the:

self.array = [LinkedList() for i in range(size)]

portion we are creating a list with a new LinkedList at each index, which in LinkedLists, it turn creates a Node() at each index of self.array at a default value of None.

self.array = [ LinkedList(None), LinkedList(None), . . . , LinkedList(None)] which I think does essentially the following (but something is missing in my logic here, I think):
self.array = [Node(None), Node(None), . . . Node(None)]

What I don’t understand is why using LinkedList here works for this code (we never use any of the methods in LinkedLists such as next_node) and why we couldn’t write it as:

self.array = [Node() for i in range(size)]

I think they are doing the same thing. However I get an error saying:

Traceback (most recent call last):
File “script.py”, line 37, in
blossom.assign(item[0], item[1])
File “script.py”, line 19, in assign
for item in list_at_array:
TypeError: ‘Node’ object is not iterable

So Node object is not iterable, but LinkedList object is? Can someone help me understand why LinkedList() is used here and not Node()? Why Node() doesn’t work?

It looks like I was looking at my LinkedLists code in my notes from a previous excersize when we built our own LinkedList class. That version has self.head_node = Node(value) in the constructor.

I found how to open the linked_list.py to see what it is actually doing in this lesson, and it appears this version does not use Node() at all. I found we actually do use .insert() from LinkedList in the Blossom project. So this makes sense now that we are actually using LinkedList class.

I’m surprised in the retrieve method why we don’t use a method to iterate to find the matching key and get the value. That a for loop on a LinkedList works? Maybe I can still get clarification on what’s happening and why it works?

Here is the linked_list.py provided in the lesson. They never instantiate Node() in the LinkedList() how are they able to use the methods from Node()?

class Node:
  def __init__(self, value):
    self.value = value
    self.next_node = None
    
  def get_value(self):
    return self.value
  
  def get_next_node(self):
    return self.next_node
  
  def set_next_node(self, next_node):
    self.next_node = next_node

class LinkedList:
  def __init__(self, head_node=None):
    self.head_node = head_node
  
  def insert(self, new_node):
    current_node = self.head_node

    if not current_node:
      self.head_node = new_node

    while(current_node):
      next_node = current_node.get_next_node()
      if not next_node:
        current_node.set_next_node(new_node)
      current_node = next_node

  def __iter__(self):
    current_node = self.head_node
    while(current_node):
      yield current_node.get_value()
      current_node = current_node.get_next_node()

The for loop works due to the __iter__ method that has been defined within the LinkedList class. See Iterators for some official documentation.

The Blossom project relies on the assign method of the HashMap class to create Node instances whenever a new flower is added to the database. This is the statement that does it:

    payload = Node([key, value])

It does seem a bit risky for the LinkedList class to rely on some external code to always hand it a valid Node object when an item needs to be added to it. It might make an interesting experiment to redesign the LinkedList's insert method to instead take the object to be inserted as an argument, and to have that method instantiate the Node that is to hold that object.

Thanks! So I read the Iterator portion in the article and it still doesn’t explain to me clearly what is going on. In the code, we have a list of LinkedLists at each index.

We go into the index in .assign() and .retrieve() and loop through the LinkedList at that index using the for loop. It’s easy to visualize a for loop on a list, but a LinkedList has values, keys (included in this case as a part of the value), and pointers and I haven’t really seen what it looks like printed. In previous sections we have used a method to go to the next key and see what’s in it.

But here we are doing a for loop, but this isn’t a typical list, it is structured to be navigated through using the methods. I don’t fully understand how :

for i in list_at_index:
      if i[0] == key:
        return i[1]

or

 for item in list_at_array:
      if key == item[0]:
        item[1] = value
        return

Above item is each node in the linked list without using the pointers or any other method (as far as I can see). How does this work? Does iter know when a for or while loop or another iterative process is acting on the LinkedList? Then performs the .get_next_node()?

An __iter__ method, if defined, conveys to the Python interpreter how to iterate through an instance of the class that contains that method. Here’s that method for the LinkedList class:

  def __iter__(self):
    current_node = self.head_node
    while(current_node):
      yield current_node.get_value()
      current_node = current_node.get_next_node()

With that method’s having been defined, the Python interpreter uses it to iterate through a LinkedList when we request that process via a for loop.

We start out with current_node referring to the head node. The for loop iterates as long as current_node has not yet become None. With each iteration, this yield statement hands out the value that is at current_node, which appears to us as the next item in the list:

      yield current_node.get_value()

Then we advance to the next Node in preparation for the next iteration:

      current_node = current_node.get_next_node()

That keeps happening until current_node gets to None, meaning we have reached the end of the LinkedList.

Edited on January 20, 2019 to add the following example:

Here is a basic example of how the potential for iteration can be included in a class:

class RocketLauncher:
    def __init__(self):
        self.count = 10
    def __iter__(self):
        while self.count > 0:
            # the next item
            yield "{:2d}!".format(self.count)
            self.count -= 1
        # the final item
        yield "Blast off!!!"

launcher = RocketLauncher()
for exclamation in launcher:
    print(exclamation)

Output:

10!
 9!
 8!
 7!
 6!
 5!
 4!
 3!
 2!
 1!
Blast off!!!

Hi, thanks. By the way, how did you find it?

Find what exactly? This was a while ago, so you may need to fill me in a little about what you are asking.

Thanks for the helpful responses.

Any idea why

print(blossom.retrieve('morning glory'))

returns “None” in the following code?

from linked_list import Node, LinkedList
from blossom_lib import flower_definitions

class HashMap:
  
  def __init__(self, size):
    self.array_size = size
    self.array = [LinkedList() for number in range(size)]

  def hash(self, key):
    return sum(key.encode())
    
  def compress(self, hash_code):
    return hash_code % self.array_size

  def assign(self, key, value):
    array_index = self.compress(self.hash(key))
    payload = Node([key, value])
    #self.array[array_index] = [key, value]
    list_at_array = self.array[array_index]
    for item in list_at_array:
      if item[0] is key:
        item[1] = value
        return
    list_at_array.insert(payload)

  def retrieve(self, key):
    array_index = self.compress(self.hash(key))
    list_at_index = self.array[array_index]
    for item in list_at_index:
      if item[0] is key:
        return item[1]
    return None

blossom = HashMap(len(flower_definitions))
for flower in flower_definitions:
  blossom.assign(flower[0], flower[1])

print(blossom.retrieve('daisy'))
print(blossom.retrieve('rose'))
print(blossom.retrieve('sunflower'))
print(blossom.retrieve('wisteria'))
print(blossom.retrieve('morning glory'))
print(blossom.retrieve('periwinkle'))

Is 'morning glory' included within the flower_definitions object provided by the blossom_lib.py file?

Edited on April 11, 2020 to add the following:

If 'morning glory' is indeed there, then consider why the following occurred during an interactive Python session:

>>> x = 'morning glory'
>>> y = 'morning glory'
>>> x is y
False

@appylpye Hi Glenn, yes, ‘morning glory’ is in the flower_definitions object provided by the blossom_lib.py file:

flower_definitions = [['begonia', 'cautiousness'], ['chrysanthemum', 'cheerfulness'], ['carnation', 'memories'], ['daisy', 'innocence'], ['hyacinth', 'playfulness'], ['lavender', 'devotion'], ['magnolia', 'dignity'], ['morning glory', 'unrequited love'], ['periwinkle', 'new friendship'], ['poppy', 'rest'], ['rose', 'love'], ['snapdragon', 'grace'], ['sunflower', 'longevity'], ['wisteria', 'good luck']]

I figure that what you mean by the x = " " and y = " ", but x != y example is that the original variable is somehow overwritten. I can’t see this in the code. “morning glory” is only referred to in the flower_definitions object. Did I understand you correctly?

OK, that’s good. To prepare to investigate the issue, we have made sure that ‘morning glory’ had not been mistakenly removed from the file, which is the original source of the data.

Now, we can move on to check whether the assign and retrieve methods are working correctly. Having confirmed that ‘morning glory’ is in the data, we can refine and test those methods, using that data.

There is an important difference between the is and == operators.

  • is checks for identity. For a True result, the two operands must not only have the same value. They must, in fact, be the same object. Two different objects can have the same value.
  • == checks for equivalence. For a True result, the operands need to have the same value, but might not be the same object.

Here, we assigned the same values to x and y:

>>> x = 'morning glory'
>>> y = 'morning glory'

However it turned out that they were two different objects, so the is test that followed in the earlier post came out False. If we had used == instead, the test would have come out True. That, in fact, is the test that we need in the assign and retrieve methods.

In actuality, we really only care whether the objects are equivalent here, not whether they are identical:

      if item[0] is key:

Accordingly, we should change the above to the following in both methods:

      if item[0] == key:

This is a helpful explanation. Thanks!

The thing I don’t understand is that if the

if item[0] is key: 

is the issue, then why does the code still work for the other items on the list?

print(blossom.retrieve('daisy'))
print(blossom.retrieve('rose'))
print(blossom.retrieve('sunflower'))
print(blossom.retrieve('wisteria'))
print(blossom.retrieve('morning glory'))
print(blossom.retrieve('periwinkle'))

In this list, only “morning glory” returns None.

The Python interpreter often, but not always, optimizes code by creating only one copy of a simple string when a program or interactive session includes several simple string literals that contain the exact same sequence of characters. When that occurs, the strings will pass both the == test and the is test. Here’s an example in an interactive session:

>>> a = "rose"
>>> b = "rose"
>>> c = "rose"
>>> a == b
True
>>> a is b
True
>>> b == c
True
>>> b is c
True

However, the Python interpreter does not guarantee that it will adhere to that strategy concerning the is test. It does, however, guarantee that two strings with the exact same sequence of characters will pass the == test.

Let’s try another string here:

>>> a = "morning glory"
>>> b = "morning glory"
>>> c = "morning glory"
>>> a == b
True
>>> a is b
False
>>> b == c
True
>>> b is c
False

For "morning glory", the Python interpreter chose to create three different equivalent strings. That might be because it is more complicated than "rose" in that it contains a space.

In conclusion, when we are interested in whether two strings contain the same sequence of characters, we should use the == operator.

1 Like

@appylpye Glenn, great explanation. Thanks for taking the time to give such a clear and concise answer. I understand now.

1 Like

Hi,
I am just curious if there is any way to see the content of blossom library class and the flower definition module. I have just completed this project and I am typing some flowers which are returning None. I know I can assign them meaning myself, but I also want to see what flowers are in the library.
Thanks.

Hi @java7724370697,

Note that you probably have this near the beginning of your program:

from blossom_lib import flower_definitions

You can see the flowers that are available by adding this line:

print(flower_definitions)

Also note the little envelope icon near the upper left corner of the editor pane. Click on the icon to see a list of files, then click on the name of a file to see its contents.

2 posts were split to a new topic: Blossom Project: NameError