Why min Heap of Python is not returning the minimum value here?

Question:

I printed out every iteration. The contents of the list are perfect. But for some reason heappop is returning -8 val even though there is -15 in the list. Help me out

class FoodRatings:

    def __init__(self, foods: List[str], cuisines: List[str], ratings: List[int]):
        
        self.find_cuisine = defaultdict()
        self.hashset = defaultdict(list)
        
        for f, c, r in zip(foods, cuisines, ratings):
            self.find_cuisine[f] = (c, r)
            heapq.heappush(self.hashset[c], (-r, f))    
  
    def changeRating(self, food: str, newRating: int) -> None:

        c, r = self.find_cuisine[food]
        self.hashset[c].remove((-r, food))
        heapq.heappush(self.hashset[c], (-newRating, food))
        self.find_cuisine[food] = (c, newRating)

    def highestRated(self, cuisine: str) -> str:
        
        r, f =  heapq.heappop(self.hashset[cuisine])
        heapq.heappush(self.hashset[cuisine], (r, f))
        return f

Input:

foods = ["czopaaeyl","lxoozsbh","kbaxapl"],

cuisines = ["dmnuqeatj","dmnuqeatj","dmnuqeatj"],

ratings = [11,2,15]],

calls

"FoodRatings"

"changeRating" – ["czopaaeyl",12],

"highestRated" – ["dmnuqeatj"],

"changeRating" – ["kbaxapl",8],

"changeRating" – ["lxoozsbh",5],

"highestRated" – ["dmnuqeatj"],

My output: [null,null,"kbaxapl",null,null,"kbaxapl"]

Expected Output: [null,null,"kbaxapl",null,null,"czopaaeyl"]

Asked By: Maverick

||

Answers:

A heapq list is a tree that just happens to be stored in a list, not a sorted list. Python won’t stop you from removing an element from the middle of it using list.remove, but doing that can scramble the tree so that it no longer gives the right results. One way to fix that is to call heapq.heapify on the list after you change it.

Also note that you don’t need to pop and re-push just to look at the lowest element. The lowest element is at index 0.

Answered By: Dan Getz

You should only mutate a heap using the heapq methods. By calling remove you bring inconsistency to the tree, as the indexes of nodes are altered, changing parent-child relationships, and thereby likely breaking the heap property at one or more places in the tree.

What you can do is to not remove the node you want to replace, but to mark it as deleted, without changing its ordering. Then, if ever that node bubbles up to the top of the tree, and you want to get the top value, only then remove it through a heappop call. Keep doing this until the top value is a node that was not marked as deleted.

To mark a node as deleted, you cannot use an (immutable) tuple. Use a list instead. And then the marking can consist of extending such a list with a 3rd dummy value. The length of 3 will distinguish it from the expected length of 2.

Here is the modified code with comments where changes were made:

class FoodRatings:
    def __init__(self, foods: List[str], cuisines: List[str], ratings: List[int]):
        self.find_cuisine = defaultdict()
        self.hashset = defaultdict(list)
        for f, c, r in zip(foods, cuisines, ratings):
            node = [-r, f]  # Use a mutable list instead of tuple
            self.find_cuisine[f] = (c, node)  # Add reference to that node
            heapq.heappush(self.hashset[c], node)
  
    def changeRating(self, food: str, newRating: int) -> None:
        c, node = self.find_cuisine[food]
        node.append("deleted")  # Don't remove, but mark as deleted
        node = [-newRating, food]  # Create new node
        heapq.heappush(self.hashset[c], node)
        self.find_cuisine[food] = (c, node)

    def highestRated(self, cuisine: str) -> str:
        while self.hashset[cuisine] and len(self.hashset[cuisine][0]) > 2:
            heapq.heappop(self.hashset[cuisine])  # Ignore deleted nodes
        if self.hashset[cuisine]:
            return self.hashset[cuisine][0][1]  # Just peek at index 0
Answered By: trincot
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.