index.go (2347B)
1 // Copyright 2026 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package graph 6 7 import ( 8 "fmt" 9 "iter" 10 "slices" 11 ) 12 13 // An Index is an immutable, bijective map between [0, N) and an ordered list of keys. 14 type Index[Key comparable] struct { 15 // There are three Index representations: 16 // 17 // - If identN > 0, an identity map of [0, identN). 18 // - If index == nil, a sorted integer index in values. 19 // - Otherwise, a full index in values and index. 20 21 identN int 22 values []Key 23 index map[Key]int 24 } 25 26 // NewIndex returns an index for the specified list of values. 27 func NewIndex[Key comparable](values iter.Seq[Key]) *Index[Key] { 28 vs := slices.Collect(values) 29 if len(vs) == 0 { 30 return new(Index[Key]) 31 } 32 33 // Fast path: a naturally sorted list needs no index. (Sadly, there's no way 34 // to ask "is Key ordered?") 35 if vi, ok := any(vs).([]int); ok && slices.IsSorted(vi) { 36 return &Index[Key]{values: vs} 37 } 38 39 index := make(map[Key]int, len(vs)) 40 for i, v := range vs { 41 index[v] = i 42 } 43 return &Index[Key]{values: vs, index: index} 44 } 45 46 // NewIdentityIndex returns an index that maps [0, n) to [0, n). 47 func NewIdentityIndex(n int) *Index[int] { 48 if n < 0 { 49 panic("n < 0") 50 } 51 // If n == 0, this is actually a "sorted integer index", but it doesn't 52 // matter because everything is out of bounds either way. 53 return &Index[int]{identN: n} 54 } 55 56 // Value maps an index to a key. 57 func (ix *Index[Key]) Value(index int) Key { 58 if ix.identN > 0 { 59 if index < 0 || index >= ix.identN { 60 panic(fmt.Sprintf("index %d out of range [0, %d)", index, ix.identN)) 61 } 62 return any(index).(Key) 63 } 64 if index < 0 || index >= len(ix.values) { 65 panic(fmt.Sprintf("index %d out of range [0, %d)", index, ix.identN)) 66 } 67 return ix.values[index] 68 } 69 70 // Index maps a key to an index. 71 func (ix *Index[Key]) Index(key Key) int { 72 if key, ok := any(key).(int); ok { 73 // Integer-only optimizations. 74 switch { 75 case ix.identN > 0: 76 // Identity. 77 if 0 <= key && key < ix.identN { 78 return key 79 } 80 goto oob 81 82 case ix.index == nil: 83 // Sorted integers. 84 if i, ok := slices.BinarySearch(any(ix.values).([]int), key); ok { 85 return i 86 } 87 goto oob 88 } 89 } 90 if i, ok := ix.index[key]; ok { 91 return i 92 } 93 94 oob: 95 panic(fmt.Sprintf("key %v not in index", key)) 96 }