Files
obitools4/pkg/obiutils/minmultiset.go
2025-11-18 15:30:20 +01:00

119 lines
2.8 KiB
Go

package obiutils
import (
"container/heap"
)
// MinMultiset maintient un multiset de valeurs et expose le minimum courant.
// T doit être comparable pour servir de clé de map. L'ordre est défini par less.
type MinMultiset[T comparable] struct {
pq priorityQueue[T] // tas min
less func(a, b T) bool
count map[T]int // cardinalité logique par valeur
pending map[T]int // suppressions en attente (lazy delete)
size int // taille logique totale
}
// New crée un multiset. less doit imposer un ordre strict total.
func NewMinMultiset[T comparable](less func(a, b T) bool) *MinMultiset[T] {
m := &MinMultiset[T]{
pq: priorityQueue[T]{less: less},
less: less,
count: make(map[T]int),
pending: make(map[T]int),
}
heap.Init(&m.pq)
return m
}
// Add ajoute une occurrence.
func (m *MinMultiset[T]) Add(v T) {
heap.Push(&m.pq, v)
m.count[v]++
m.size++
}
// RemoveOne retire UNE occurrence de v. Retourne false si absente.
func (m *MinMultiset[T]) RemoveOne(v T) bool {
if m.count[v] == 0 {
return false
}
m.count[v]--
m.pending[v]++
m.size--
m.shrink()
return true
}
// Min retourne le minimum courant. ok=false si vide.
func (m *MinMultiset[T]) Min() (v T, ok bool) {
if m.size == 0 {
var zero T
return zero, false
}
m.cleanTop()
return m.pq.data[0], true
}
// Len retourne la taille logique.
func (m *MinMultiset[T]) Len() int { return m.size }
// --- interne ---
// retire du sommet toutes les valeurs marquées à supprimer.
func (m *MinMultiset[T]) cleanTop() {
for m.pq.Len() > 0 {
top := m.pq.data[0]
if m.pending[top] > 0 {
m.pending[top]--
if m.pending[top] == 0 {
delete(m.pending, top) // ← nettoyage de la map
}
heap.Pop(&m.pq)
continue
}
break
}
}
// rééquilibre le tas si trop de tombstones.
func (m *MinMultiset[T]) shrink() {
// nettoyage léger au retrait pour borner la dérive
if m.pq.Len() > 0 {
m.cleanTop()
}
}
// priorityQueue implémente heap.Interface pour T.
type priorityQueue[T any] struct {
data []T
less func(a, b T) bool
}
func (q priorityQueue[T]) Len() int { return len(q.data) }
func (q priorityQueue[T]) Less(i, j int) bool { return q.less(q.data[i], q.data[j]) }
func (q priorityQueue[T]) Swap(i, j int) { q.data[i], q.data[j] = q.data[j], q.data[i] }
func (q *priorityQueue[T]) Push(x any) { q.data = append(q.data, x.(T)) }
func (q *priorityQueue[T]) Pop() any {
n := len(q.data)
x := q.data[n-1]
q.data = q.data[:n-1]
return x
}
func (q priorityQueue[T]) peek() (T, bool) {
if len(q.data) == 0 {
var z T
return z, false
}
return q.data[0], true
}
func (q *priorityQueue[T]) Top() (T, bool) { return q.peek() }
func (q *priorityQueue[T]) PushValue(v T) { heap.Push(q, v) }
func (q *priorityQueue[T]) PopValue() (T, bool) {
if q.Len() == 0 {
var z T
return z, false
}
return heap.Pop(q).(T), true
}