| // Copyright 2026 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package graph | |
| import ( | |
| "fmt" | |
| "iter" | |
| "slices" | |
| ) | |
| // An Index is an immutable, bijective map between [0, N) and an ordered list of keys. | |
| type Index[Key comparable] struct { | |
| // There are three Index representations: | |
| // | |
| // - If identN > 0, an identity map of [0, identN). | |
| // - If index == nil, a sorted integer index in values. | |
| // - Otherwise, a full index in values and index. | |
| identN int | |
| values []Key | |
| index map[Key]int | |
| } | |
| // NewIndex returns an index for the specified list of values. | |
| func NewIndex[Key comparable](values iter.Seq[Key]) *Index[Key] { | |
| vs := slices.Collect(values) | |
| if len(vs) == 0 { | |
| return new(Index[Key]) | |
| } | |
| // Fast path: a naturally sorted list needs no index. (Sadly, there's no way | |
| // to ask "is Key ordered?") | |
| if vi, ok := any(vs).([]int); ok && slices.IsSorted(vi) { | |
| return &Index[Key]{values: vs} | |
| } | |
| index := make(map[Key]int, len(vs)) | |
| for i, v := range vs { | |
| index[v] = i | |
| } | |
| return &Index[Key]{values: vs, index: index} | |
| } | |
| // NewIdentityIndex returns an index that maps [0, n) to [0, n). | |
| func NewIdentityIndex(n int) *Index[int] { | |
| if n < 0 { | |
| panic("n < 0") | |
| } | |
| // If n == 0, this is actually a "sorted integer index", but it doesn't | |
| // matter because everything is out of bounds either way. | |
| return &Index[int]{identN: n} | |
| } | |
| // Value maps an index to a key. | |
| func (ix *Index[Key]) Value(index int) Key { | |
| if ix.identN > 0 { | |
| if index < 0 || index >= ix.identN { | |
| panic(fmt.Sprintf("index %d out of range [0, %d)", index, ix.identN)) | |
| } | |
| return any(index).(Key) | |
| } | |
| if index < 0 || index >= len(ix.values) { | |
| panic(fmt.Sprintf("index %d out of range [0, %d)", index, ix.identN)) | |
| } | |
| return ix.values[index] | |
| } | |
| // Index maps a key to an index. | |
| func (ix *Index[Key]) Index(key Key) int { | |
| if key, ok := any(key).(int); ok { | |
| // Integer-only optimizations. | |
| switch { | |
| case ix.identN > 0: | |
| // Identity. | |
| if 0 <= key && key < ix.identN { | |
| return key | |
| } | |
| goto oob | |
| case ix.index == nil: | |
| // Sorted integers. | |
| if i, ok := slices.BinarySearch(any(ix.values).([]int), key); ok { | |
| return i | |
| } | |
| goto oob | |
| } | |
| } | |
| if i, ok := ix.index[key]; ok { | |
| return i | |
| } | |
| oob: | |
| panic(fmt.Sprintf("key %v not in index", key)) | |
| } |