Skip to content

proposal: container/heap: efficient in-order Iter function #73659

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ClickerMonkey opened this issue May 10, 2025 · 2 comments
Closed

proposal: container/heap: efficient in-order Iter function #73659

ClickerMonkey opened this issue May 10, 2025 · 2 comments
Labels
LibraryProposal Issues describing a requested change to the Go standard library or x/ libraries, but not to a tool Proposal
Milestone

Comments

@ClickerMonkey
Copy link

ClickerMonkey commented May 10, 2025

Proposal Details

The recommended way to iterate over a heap in order is to copy it and pop it until it's empty. This may not be possible for some heap.Interface implementations or may be very costly (the data copying) depending on the elements in the heap. I'm proposing the following func be added to the container/heap module. It operates in O(n log n), only allocates half of the size of the heap using only ints, and only relies on Len() and Less(i,j) on the heap.Interface.

Recommended usage:

for heapIndex := range heap.Iter(myHeap) {
  // heapIndex = the index in the heap's backing structure (typically a slice)
}

A more concrete implementation using the myHeap type in heap_test.go.

// Values returns an iterator over the values in this heap implementation.
func (h *myHeap) Values() iter.Seq[int] {
	return func(yield func(int) bool) {
		for i := range heap.Iter(h) {
			if !yield((*h)[i]) {
				return
			}
		}
	}
}
// usage
for v := range myHeap.Values() {
  // the value in the heap the is not less than the one before it
}

Implementation:

// Iterates a heap interface in-order without copying.
// This is a modified breadth first search that looks at the
// children of the lowest value in the heap and yields them
// in order of their value. This will allocate an []int slice
// half the size of the heap. The underlying heap must not be
// modified during iteration.
func Iter(h Interface) iter.Seq[int] {
	n := h.Len()
	if n == 0 {
		return func(yield func(int) bool) {}
	}

	// front is all nodes in modified breadth first search
	// the breadth in the context of a heap are not items in the same
	// depth but are items all being looked at for the lowest value.
	// the lowest value is found, removed, yielded, and its children are added to the front.
	front := make([]int, (n+1)/2)
	frontSize := 1

	// adds to the front the child nodes of i
	split := func(heapIndex int) {
		if left := heapIndex*2 + 1; left < n {
			front[frontSize] = left
			frontSize++
		}
		if right := heapIndex*2 + 2; right < n {
			front[frontSize] = right
			frontSize++
		}
	}

	// takes the item at the given index in the front
	take := func(frontIndex int) int {
		taken := front[frontIndex]
		frontSize--
		front[frontIndex] = front[frontSize]
		return taken
	}

	// iterate until we've looked at all items on the heap
	return func(yield func(int) bool) {
		for frontSize > 0 {
			chosenIndex := 0
			for i := 1; i < frontSize; i++ {
				if h.Less(front[i], front[chosenIndex]) {
					chosenIndex = i
				}
			}
			chosen := take(chosenIndex)
			if !yield(chosen) {
				return
			}
			split(chosen)
		}
	}
}

First time filing a ticket and I can provide an MR with unit tests, I don't even know if these sort of suggestions are supported. Just straight up giving code. If this having an in-order implementation and other supported types not having it feels odd I can make a more comprehensive MR with whatever data structures make this feel like a more complete contribution. Otherwise I'll just keep this code to myself and anyone else interested can use it as well. I know I couldn't find any examples of code like this online and it saddened me.

@gopherbot gopherbot added this to the Proposal milestone May 10, 2025
@gabyhelp
Copy link

Related Issues

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

@gabyhelp gabyhelp added the LibraryProposal Issues describing a requested change to the Go standard library or x/ libraries, but not to a tool label May 10, 2025
@seankhliao
Copy link
Member

Heaps generally aren't sorted data structures, providing an iterator seems wrong.
See also #47632

@seankhliao seankhliao closed this as not planned Won't fix, can't repro, duplicate, stale May 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
LibraryProposal Issues describing a requested change to the Go standard library or x/ libraries, but not to a tool Proposal
Projects
None yet
Development

No branches or pull requests

4 participants