{"record":{"id":"b162aedc60d02a41","repo":"krahets/hello-algo","slug":"error-b162ae","errorCode":null,"errorMessage":"堆为空","messagePattern":"堆为空","errorType":"exception","errorClass":"IndexError","httpStatus":null,"severity":"error","filePath":"codes/ruby/chapter_heap/my_heap.rb","lineNumber":83,"sourceCode":"\n  ### 从节点 i 开始，从底至顶堆化 ###\n  def sift_up(i)\n    loop do\n      # 获取节点 i 的父节点\n      p = parent(i)\n      # 当“越过根节点”或“节点无须修复”时，结束堆化\n      break if p < 0 || @max_heap[i] <= @max_heap[p]\n      # 交换两节点\n      swap(i, p)\n      # 循环向上堆化\n      i = p\n    end\n  end\n\n  ### 元素出堆 ###\n  def pop\n    # 判空处理\n    raise IndexError, \"堆为空\" if is_empty?\n    # 交换根节点与最右叶节点（交换首元素与尾元素）\n    swap(0, size - 1)\n    # 删除节点\n    val = @max_heap.pop\n    # 从顶至底堆化\n    sift_down(0)\n    # 返回堆顶元素\n    val\n  end\n\n  ### 从节点 i 开始，从顶至底堆化 ###\n  def sift_down(i)\n    loop do\n      # 判断节点 i, l, r 中值最大的节点，记为 ma\n      l, r, ma = left(i), right(i), i\n      ma = l if l < size && @max_heap[l] > @max_heap[ma]\n      ma = r if r < size && @max_heap[r] > @max_heap[ma]\n","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/krahets/hello-algo/blob/69932aed1891a7b7f6a0de88cd116d3fe13e7032/codes/ruby/chapter_heap/my_heap.rb#L65-L101","documentation":"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.","triggerScenarios":"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?`.","commonSituations":"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.","solutions":["Guard every pop site with `unless heap.is_empty?` before calling `heap.pop`.","Switch the loop condition to `while heap.size > 0` instead of a fixed iteration count.","Wrap the pop call in `rescue IndexError` if the caller legitimately tolerates empty drains."],"exampleFix":"// before\nwhile i < n\n  heap.pop\nend\n\n# after\nuntil heap.is_empty?\n  heap.pop\nend","handlingStrategy":"validation","validationCode":"return nil if heap.is_empty?\nheap.pop","typeGuard":"def heap_popable?(heap)\n  heap.respond_to?(:is_empty?) && heap.respond_to?(:pop) && !heap.is_empty?\nend","tryCatchPattern":"begin\n  heap.pop\nrescue IndexError => e\n  nil # empty heap is expected, return sentinel\nend","preventionTips":["Always check is_empty? before pop on any heap.","Use until heap.is_empty? as the drain loop condition instead of a fixed count.","Never cache size across pops — re-query each iteration."],"tags":["ruby","heap","precondition","empty-state"],"backgroundTag":null,"analyzedSha":"69932aed1891a7b7f6a0de88cd116d3fe13e7032","analyzedAt":"2026-08-13T23:02:37.581Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}