Have a problem with a linked list assignment

this is my assignment
https://ucsb-cs9.github.io/s23/lab/lab05/
this is my code:
testFile.py

from Book import Book
def test_book_details():
    b = Book("Ready Player One", "Cline, Ernest", 2011)
    assert b.getBookDetails() == "Title: Ready Player One, Author: Cline, Ernest, Year: 2011"

from BookCollection import BookCollection

def test_get_books_by_author():
    b0 = Book("Cujo", "King, Stephen", 1981)
    b1 = Book("The Shining", "King, Stephen", 1977)
    b2 = Book("Ready Player One", "Cline, Ernest", 2011)
    b3 = Book("Rage", "King, Stephen", 1977)

    bc = BookCollection()
    bc.insertBook(b0)
    bc.insertBook(b1)
    bc.insertBook(b2)
    bc.insertBook(b3)

    print(bc.getBooksByAuthor("KING, Stephen"))

    expected_output = ("Title: Rage, Author: King, Stephen, Year: 1977\n"
                       "Title: The Shining, Author: King, Stephen, Year: 1977\n"
                       "Title: Cujo, Author: King, Stephen, Year: 1981")

    assert bc.getBooksByAuthor("KING, Stephen") == expected_output

def test_get_all_books_in_collection():
    b0 = Book("Cujo", "King, Stephen", 1981)
    b1 = Book("The Shining", "King, Stephen", 1977)
    b2 = Book("Ready Player One", "Cline, Ernest", 2011)
    b3 = Book("Rage", "King, Stephen", 1977)

    bc = BookCollection()
    bc.insertBook(b0)
    bc.insertBook(b1)
    bc.insertBook(b2)
    bc.insertBook(b3)

    expected_output = ("Title: Ready Player One, Author: Cline, Ernest, Year: 2011\n"
                       "Title: Rage, Author: King, Stephen, Year: 1977\n"
                       "Title: The Shining, Author: King, Stephen, Year: 1977\n"
                       "Title: Cujo, Author: King, Stephen, Year: 1981")

    assert bc.getAllBooksInCollection() == expected_output

def test_remove_author():
    b0 = Book("Cujo", "King, Stephen", 1981)
    b1 = Book("The Shining", "King, Stephen", 1977)
    b2 = Book("Ready Player One", "Cline, Ernest", 2011)
    b3 = Book("Rage", "King, Stephen", 1977)

    bc = BookCollection()
    bc.insertBook(b0)
    bc.insertBook(b1)
    bc.insertBook(b2)
    bc.insertBook(b3)

    bc.removeAuthor("king, stephen")

    assert bc.recursiveSearchTitle("Cujo", bc.head) == False
    assert bc.recursiveSearchTitle("The Shining", bc.head) == False
    assert bc.recursiveSearchTitle("Rage", bc.head) == False
    assert bc.recursiveSearchTitle("Ready Player One", bc.head) == True


def test_recursive_search_title():
    b0 = Book("Cujo", "King, Stephen", 1981)
    b1 = Book("The Shining", "King, Stephen", 1977)
    bc = BookCollection()
    bc.insertBook(b0)
    bc.insertBook(b1)
    assert bc.recursiveSearchTitle("CUJO", bc.head) == True
    assert bc.recursiveSearchTitle("Twilight", bc.head) == False

test_book_details()
test_get_books_by_author()
test_get_all_books_in_collection()
test_remove_author()
test_recursive_search_title()

BookCollection.py

from BookCollectionNode import BookCollectionNode
class BookCollection:
    def __init__(self):
        self.head = None

    def isEmpty(self):
        return self.head == None

    def getNumberOfBooks(self):
        count = 0
        current = self.head
        while current != None:
            count += 1
            current = current.getNext()
        return count

    def insertBook(self, book):
        node = BookCollectionNode(book)
        if self.isEmpty():
            self.head = node
        elif node > self.head:
            node.setNext(self.head)
            self.head = node
        else:
            current = self.head
            while current.getNext() is not None and current.getNext() > node:
                current = current.getNext()
            node.setNext(current.getNext())
            current.setNext(node)

    def getBooksByAuthor(self, author):
        result = []
        current = self.head
        while current != None:
            if current.getData().getAuthor().lower() == author.lower():
                result.append(current.getData())
            current = current.getNext()
        return result

    def getAllBooksInCollection(self):
        result = []
        current = self.head
        while current != None:
            result.append(current.getData())
            current = current.getNext()

    def removeAuthor(self, author):
        author = author.lower()
        current = self.head
        previous = None
        while current is not None:
            if current.getData().getAuthor().lower() == author:
                if previous is None:
                    # Removing the head node
                    self.head = current.getNext()
                else:
                    previous.setNext(current.getNext())
                # Move the current pointer to the next node
                current = current.getNext()
            else:
                # Move the previous and current pointers to the next node
                previous = current
                current = current.getNext()

    def recursiveSearchTitle(self, title, bookNode):
        if bookNode is None:
            return False
        elif bookNode.getData().getTitle().lower() == title.lower():
            return True
        else:
            return self.recursiveSearchTitle(title, bookNode.getNext())

BookCollectionNode.py

class BookCollectionNode:
    def __init__(self, data):
        self.data = data
        self.next = None
    def getData(self):
        return self.data
    def getNext(self):
        return self.next
    def setData(self, data):
        self.data = data
    def setNext(self, next):
        self.next = next

Book.py

class Book:
    def __init__(self, title, author, year):
        self.title = title
        self.author = author
        self.year = year

    def getTitle(self):
        return self.title

    def getAuthor(self):
        return self.author

    def getYear(self):
        return self.year

    def getBookDetails(self):
        return f"Title: {self.title}, Author: {self.author}, Year: {self.year}"

    def __gt__(self, other):
        return self.title.upper() > other.title.upper()

So, you have problem, but you’re not going to tell us what it is?

I keep failing the assert statements of my testfile.

The traceback will tell you where it’s having a problem.

how can I fix these problems?

Do you know on which line it’s failing? The traceback will tell you.

Do you know why that line is failing? Think about what it’s meant to be doing and why it can’t.

here is the line that it is failing in:

"C:\Users\cshang\PycharmProjects\if\read\Ordered Linked Lists\venv\Scripts\python.exe" "C:/Program Files/JetBrains/PyCharm Community Edition 2022.2.1/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --target testFile.py::test_book_details 
Testing started at 2:02 PM ...
Launching pytest with arguments testFile.py::test_book_details --no-header --no-summary -q in C:\Users\cshang\PycharmProjects\if\read\Ordered Linked Lists\book

============================= test session starts =============================
collecting ... 
testFile.py:None (testFile.py)
testFile.py:77: in <module>
    test_get_books_by_author()
testFile.py:16: in test_get_books_by_author
    bc.insertBook(b1)
BookCollection.py:21: in insertBook
    elif node > self.head:
E   TypeError: '>' not supported between instances of 'BookCollectionNode' and 'BookCollectionNode'
collected 0 items / 1 error
ERROR: not found: C:\Users\cshang\PycharmProjects\if\read\Ordered Linked Lists\book\testFile.py::test_book_details

============================== 1 error in 0.58s ===============================
(no name 'C:\\Users\\cshang\\PycharmProjects\\if\\read\\Ordered Linked Lists\\book\\testFile.py::test_book_details' in any of [<Module testFile.py>])


Process finished with exit code 4

So why is it failing?

It’s failing because you’re trying to compare 2 instances of BookCollectionNode using >, but that isn’t supported.

It is supported on Book (you’ve defined __gt__ in that class), but it’s not supported on BookCollectionNode (you haven’t defined __gt__ in that class).

So, should you be comparing the nodes, or the books on those nodes? And is the comparison following the specification in the assignment?

    def __gt__(self, other):
        self.data > other.data

is this a good way for book collection node?

I probably wouldn’t define the comparison on the node itself, but on the book instead, and then code the tests appropriately, unless the assignment explicitly says that the comparison method must be on the node.

See where the assignment says this?

Lastly, your Book class will overload the > operator (__gt__). This will be used when finding the proper position of a Book in the Ordered Linked List using the specification above. We can compare books using the > operator while walking down the list and checking if the inserted book is > than a specific Book in the Ordered Linked List.

Do you think the code is doing this already? (If so, what part of the code do you think will do this?) If you did not, what happens if you try doing it?

If you implement this comparison for Book instances, do you see how that can be used to write the correct logic in the BookCollection? (Hint: when you choose a place in the collection for the new book, do you actually want to compare the existing nodes, or the existing books? Can you write code that tells you the book that is in each node? If you compare the books, does that tell you where to put the node for the new one?)

my current code
Book.py

class Book:
    def __init__(self, title = "", author = "", year = ""):
        self.title = title
        self.author = author
        self.year = year

    def getTitle(self):
        return self.title

    def getAuthor(self):
        return self.author

    def getYear(self):
        return self.year

    def getBookDetails(self):
        return f"Title: {self.title}, Author: {self.author}, Year: {self.year}"

    def __str__(self):
        return f"Title: {self.title}, Author: {self.author}, Year: {self.year}"

    def __gt__(self, other):
        if self.author.upper() > other.getAuthor().upper():
            return True
        else:
            return False
        if self.year > other.getYear():
            return True
        else:
            return False
        if self.title.upper() > other.getTitle().upper():
            return True
        else:
            return False

BookCollection.py

from BookCollectionNode import BookCollectionNode
from Book import Book
class BookCollection:
    def __init__(self):
        self.head = None

    def isEmpty(self):
        return self.head == None

    def getNumberOfBooks(self):
        count = 0
        current = self.head
        while current != None:
            count += 1
            current = current.getNext()
        return count

    def insertBook(self, book):
        if self.head == None:
            node = BookCollectionNode(book)
            self.head = node
            return
        tempNode = self.head
        prevNode = None
        while tempNode != None and book > tempNode.getData():
            prevNode = tempNode
            tempNode = tempNode.getNext()
            if tempNode == self.head:
                node = BookCollectionNode(book)
                node.setNext(tempNode)
                self.head = node
                return
        node = BookCollectionNode(book)
        prevNode.setNext(node)
        node.setNext(tempNode)
        return


    def getBooksByAuthor(self, author):
        result = []
        current = self.head
        while current != None:
            if current.getData().getAuthor().lower() == author.lower():
                result.append(current.getData())
            current = current.getNext()
        return result

    def getAllBooksInCollection(self):
        result = []
        current = self.head
        while current != None:
            result.append(current.getData())
            current = current.getNext()

    def removeAuthor(self, author):
        author = author.lower()
        current = self.head
        previous = None
        while current is not None:
            if current.getData().getAuthor().lower() == author:
                if previous is None:
                    # Removing the head node
                    self.head = current.getNext()
                else:
                    previous.setNext(current.getNext())
                # Move the current pointer to the next node
                current = current.getNext()
            else:
                # Move the previous and current pointers to the next node
                previous = current
                current = current.getNext()

    def recursiveSearchTitle(self, title, bookNode):
        if bookNode is None:
            return False
        elif bookNode.getData().getTitle().lower() == title.lower():
            return True
        else:
            return self.recursiveSearchTitle(title, bookNode.getNext())

BookCollectionNode.py

class BookCollectionNode:
    def __init__(self, data):
        self.data = data
        self.next = None
    def getData(self):
        return self.data
    def getNext(self):
        return self.next
    def setData(self, newData):
        self.data = newData
    def setNext(self, newNext):
        self.next = newNext

    def __gt__(self, other):
        self.data > other.data

testFile.py


from Book import Book
from BookCollection import BookCollection
def test_get_books_by_author():
    b0 = Book("Cujo", "King, Stephen", 1981)
    b1 = Book("The Shining", "King, Stephen", 1977)
    b2 = Book("Ready Player One", "Cline, Ernest", 2011)
    b3 = Book("Rage", "King, Stephen", 1977)

    bc = BookCollection()
    bc.insertBook(b0)
    bc.insertBook(b1)
    bc.insertBook(b2)
    bc.insertBook(b3)

    print(bc.getBooksByAuthor("KING, Stephen"))

    expected_output = ("Title: Rage, Author: King, Stephen, Year: 1977\n"
                       "Title: The Shining, Author: King, Stephen, Year: 1977\n"
                       "Title: Cujo, Author: King, Stephen, Year: 1981")

    assert bc.getBooksByAuthor("KING, Stephen") == expected_output

def test_get_all_books_in_collection():
    b0 = Book("Cujo", "King, Stephen", 1981)
    b1 = Book("The Shining", "King, Stephen", 1977)
    b2 = Book("Ready Player One", "Cline, Ernest", 2011)
    b3 = Book("Rage", "King, Stephen", 1977)

    bc = BookCollection()
    bc.insertBook(b0)
    bc.insertBook(b1)
    bc.insertBook(b2)
    bc.insertBook(b3)

    expected_output = ("Title: Ready Player One, Author: Cline, Ernest, Year: 2011\n"
                       "Title: Rage, Author: King, Stephen, Year: 1977\n"
                       "Title: The Shining, Author: King, Stephen, Year: 1977\n"
                       "Title: Cujo, Author: King, Stephen, Year: 1981")

    assert bc.getAllBooksInCollection() == expected_output

def test_remove_author():
    b0 = Book("Cujo", "King, Stephen", 1981)
    b1 = Book("The Shining", "King, Stephen", 1977)
    b2 = Book("Ready Player One", "Cline, Ernest", 2011)
    b3 = Book("Rage", "King, Stephen", 1977)

    bc = BookCollection()
    bc.insertBook(b0)
    bc.insertBook(b1)
    bc.insertBook(b2)
    bc.insertBook(b3)

    bc.removeAuthor("king, stephen")

    assert bc.recursiveSearchTitle("Cujo", bc.head) == False
    assert bc.recursiveSearchTitle("The Shining", bc.head) == False
    assert bc.recursiveSearchTitle("Rage", bc.head) == False
    assert bc.recursiveSearchTitle("Ready Player One", bc.head) == True


def test_recursive_search_title():
    b0 = Book("Cujo", "King, Stephen", 1981)
    b1 = Book("The Shining", "King, Stephen", 1977)
    bc = BookCollection()
    bc.insertBook(b0)
    bc.insertBook(b1)
    assert bc.recursiveSearchTitle("CUJO", bc.head) == True
    assert bc.recursiveSearchTitle("Twilight", bc.head) == False


my errors:

Autograder Results
testBookCollectionDefaultConstruction (test_functions.TestLab05) (5/5)
testBookCollectionGetNumberOfBooks (test_functions.TestLab05) (0/5)
Test Failed: 'NoneType' object has no attribute 'setNext'
testBookCollectionInsertionEmptyCollection (test_functions.TestLab05) (0/5)
Test Failed: [<Book.Book object at 0x7f902e4ecd90>] != 'Title: The Fellowship of the Ring, Author: Tolkien, J.R.R., Year: 1954\n'
test_BookConstruction (test_functions.TestLab05) (5/5)
test_BookDefaultConstruction (test_functions.TestLab05) (0/5)
Test Failed: '' != None
test_BookGreaterThanOperator (test_functions.TestLab05) (0/5)
Test Failed: False != True
test_GetBookDetails (test_functions.TestLab05) (10/10)
test_getAllBooksInCollection (test_functions.TestLab05) (0/15)
Test Failed: 'NoneType' object has no attribute 'setNext'
test_getBooksByAuthor (test_functions.TestLab05) (0/15)
Test Failed: 'NoneType' object has no attribute 'setNext'
test_recursiveSearchTitle (test_functions.TestLab05) (0/15)
Test Failed: 'NoneType' object has no attribute 'setNext'
test_removeAuthorInCollection (test_functions.TestLab05) (0/15)
Test Failed: 'NoneType' object has no attribute 'setNext'
  1. Look at Book.__gt__. If the one author is greater than the other author, it returns True, otherwise it returns False. It never reaches the lines where it compares the years or the titles.

  2. getAllBooksInCollection makes a list of books, but never returns it.

  3. A list will never match a tuple.

  4. A book will never match a string.

how can you fix the errors related to 3 and 4?

Pay close attention to what your assignment says each method should do, and what it should return.

You should write a constructor that allows the user to construct a book object by passing in values for all of the fields. Your constructor should set the title and author attribues to empty strings (""), and the year attribute to None by default.

In your constructor for Book, is year set to None by default?

getBooksByAuthor(self, author) - method that returns a str containing all of the Book details by a specified author. Note that each book will be in its own line (each line ending with a \n character).

Does your getBooksByAuthor method return such a string?

getAllBooksInCollection(self) - method that returns a str containing the details of all Books in the BookCollection. Note that each book will be in its own line (each line ending with a \n character).

Does your getAllBooksInCollection method return such a string?

There’s also a problem with your insertBook method, because it will try to call setNext on None if it’s supposed to insert the book at the head of the list. You actually got it right in your initial version of the code, except that in that code, you should have compared Books instead of BookCollectionNodes.

2 Likes

Reading the assignment, I see that getBooksByAuthor and getAllBooksInCollection should return strings, which seems strange to me (I’d expect a list), but if that’s what they want…

I also notice that expected_output is, in fact, a string and not a tuple, as a first thought. Again, it’s not what I’d expect, which is any I overlooked it, but it’s what they want. However, there should be ‘\n’ at the end of each line.

fixed it:

    def insertBook(self, book):
        node = book
        if self.isEmpty():
            self.head = node
        elif node > self.head:
            node.setNext(self.head)
            self.head = node
        else:
            current = self.head
            while current.getNext() is not None and current.getNext() > node:
                current = current.getNext()
            node.setNext(current.getNext())
            current.setNext(node)

My new BookCollection.py:

from BookCollectionNode import BookCollectionNode
from Book import Book
class BookCollection:
    def __init__(self):
        self.head = None

    def isEmpty(self):
        return self.head == None

    def getNumberOfBooks(self):
        count = 0
        current = self.head
        while current != None:
            count += 1
            current = current.getNext()
        return count

    def insertBook(self, book):
        node = book
        if self.isEmpty():
            self.head = node
        elif node > self.head:
            node.setNext(self.head)
            self.head = node
        else:
            current = self.head
            while current.getNext() is not None and current.getNext() > node:
                current = current.getNext()
            node.setNext(current.getNext())
            current.setNext(node)


    def getBooksByAuthor(self, author):
        result = []
        current = self.head
        while current != None:
            if current.getData().getAuthor().lower() == author.lower():
                data = current.getData() + "\n"
                result.append(data)
            current = current.getNext()
        return result

    def getAllBooksInCollection(self):
        result = []
        current = self.head
        while current != None:
            data = current.getData() + "\n"
            result.append(data)
            current = current.getNext()
        return result

    def removeAuthor(self, author):
        author = author.lower()
        current = self.head
        previous = None
        while current is not None:
            if current.getData().getAuthor().lower() == author:
                if previous is None:
                    # Removing the head node
                    self.head = current.getNext()
                else:
                    previous.setNext(current.getNext())
                # Move the current pointer to the next node
                current = current.getNext()
            else:
                # Move the previous and current pointers to the next node
                previous = current
                current = current.getNext()

    def recursiveSearchTitle(self, title, bookNode):
        if bookNode is None:
            return False
        elif bookNode.getData().getTitle().lower() == title.lower():
            return True
        else:
            return self.recursiveSearchTitle(title, bookNode.getNext())

got these new errors now:

testBookCollectionDefaultConstruction (test_functions.TestLab05) (5/5)
testBookCollectionGetNumberOfBooks (test_functions.TestLab05) (0/5)
Test Failed: 'Book' object has no attribute 'getNext'
testBookCollectionInsertionEmptyCollection (test_functions.TestLab05) (0/5)
Test Failed: 'Book' object has no attribute 'getNext'
test_BookConstruction (test_functions.TestLab05) (5/5)
test_BookDefaultConstruction (test_functions.TestLab05) (5/5)
test_BookGreaterThanOperator (test_functions.TestLab05) (0/5)
Test Failed: False != True
test_GetBookDetails (test_functions.TestLab05) (10/10)
test_getAllBooksInCollection (test_functions.TestLab05) (0/15)
Test Failed: 'Book' object has no attribute 'getNext'
test_getBooksByAuthor (test_functions.TestLab05) (0/15)
Test Failed: 'Book' object has no attribute 'getNext'
test_recursiveSearchTitle (test_functions.TestLab05) (0/15)
Test Failed: 'Book' object has no attribute 'getNext'
test_removeAuthorInCollection (test_functions.TestLab05) (0/15)
Test Failed: 'Book' object has no attribute 'getNext'

You’re now using a book as though it was a node. You should be making the node as before, and then when you’re looking for the correct place to insert the node, you should be comparing the books that those nodes refer to.

Oh, and you should re-read what the assignment says about what getBooksByAuthor and getAllBooksInCollection should return.