krahets/hello-algo · error · IndexError

堆为空

Error message

堆为空

What it means

Raised by `MyMaxHeap#pop` (chapter_heap/my_heap.rb:83) when `is_empty?` is true. The method's contract is to remove and return the maximum element (heap root); calling it on an empty heap has no element to swap, pop, or sift down, so it rejects the call with IndexError before touching the internal array. This is a precondition guard — the heap never stores a sentinel root, so an empty pop would otherwise corrupt state or return nil.

Source

Thrown at codes/ruby/chapter_heap/my_heap.rb:83

  ### 从节点 i 开始,从底至顶堆化 ###
  def sift_up(i)
    loop do
      # 获取节点 i 的父节点
      p = parent(i)
      # 当“越过根节点”或“节点无须修复”时,结束堆化
      break if p < 0 || @max_heap[i] <= @max_heap[p]
      # 交换两节点
      swap(i, p)
      # 循环向上堆化
      i = p
    end
  end

  ### 元素出堆 ###
  def pop
    # 判空处理
    raise IndexError, "堆为空" if is_empty?
    # 交换根节点与最右叶节点(交换首元素与尾元素)
    swap(0, size - 1)
    # 删除节点
    val = @max_heap.pop
    # 从顶至底堆化
    sift_down(0)
    # 返回堆顶元素
    val
  end

  ### 从节点 i 开始,从顶至底堆化 ###
  def sift_down(i)
    loop do
      # 判断节点 i, l, r 中值最大的节点,记为 ma
      l, r, ma = left(i), right(i), i
      ma = l if l < size && @max_heap[l] > @max_heap[ma]
      ma = r if r < size && @max_heap[r] > @max_heap[ma]

View on GitHub (pinned to 69932aed18)

Solutions

  1. Guard every pop site with `unless heap.is_empty?` before calling `heap.pop`.
  2. Switch the loop condition to `while heap.size > 0` instead of a fixed iteration count.
  3. Wrap the pop call in `rescue IndexError` if the caller legitimately tolerates empty drains.

Example fix

// before
while i < n
  heap.pop
end

# after
until heap.is_empty?
  heap.pop
end
Defensive patterns

Strategy: validation

Validate before calling

return nil if heap.is_empty?
heap.pop

Type guard

def heap_popable?(heap)
  heap.respond_to?(:is_empty?) && heap.respond_to?(:pop) && !heap.is_empty?
end

Try / catch

begin
  heap.pop
rescue IndexError => e
  nil # empty heap is expected, return sentinel
end

Prevention

When it happens

Trigger: Calling `heap.pop` when the heap's internal `@max_heap` array has zero elements. This happens after draining the heap via repeated pops, after constructing a heap with no initial data, or when interleaving build and drain phases without checking `is_empty?`.

Common situations: Running a heap-sort driver that pops `size` times but off-by-one in the loop count; draining a priority queue to exhaustion then popping once more; test harnesses that call pop in a loop without a guard.

Related errors


AI-assisted analysis of krahets/hello-algo@69932aed18 (2026-08-13). Data as JSON: /api/errors/b162aedc60d02a41. Report an issue: GitHub.