1 module schlib.lookup; 2 import std.range; 3 4 private struct LookupNode(K, V, bool useTruthiness) 5 { 6 size_t hash; 7 K key; 8 V value; 9 static if(!useTruthiness) 10 { 11 bool valid = false; 12 } 13 14 this(size_t h, K k, V v) 15 { 16 hash = h; 17 key = k; 18 value = v; 19 static if(!useTruthiness) 20 valid = true; 21 } 22 23 bool opCast(B : bool)() const 24 { 25 static if(useTruthiness) 26 return !!key; 27 else 28 return valid; 29 } 30 } 31 32 private struct LookupTable(K, V, bool useTruthiness) 33 { 34 import std.typecons : Nullable; 35 private alias Bucket = LookupNode!(K, V, useTruthiness); 36 private Bucket[] buckets; 37 alias KeyType = K; 38 39 ref inout(V) opIndex(const(K) key) inout 40 { 41 if(auto v = key in this) 42 { 43 return *v; 44 } 45 import std.format; 46 throw new Exception(format("Key %s not in lookup table", key)); 47 } 48 49 inout(V)* opBinaryRight(string op : "in")(const(K) key) inout 50 { 51 if(buckets.length) 52 { 53 auto hash = hashOf(key); 54 auto idx = hash % buckets.length; 55 static foreach(const x; 0 .. 2) 56 foreach(ref b; x ? buckets[0 .. idx] : buckets[idx .. $]) 57 if(!b) 58 // this is where it would have gone... 59 return null; 60 else if(b.hash == hash && b.key == key) 61 return &b.value; 62 } 63 return null; 64 } 65 66 private void _insert(K key, ref V val) 67 { 68 auto hash = hashOf(key); 69 auto idx = hash % buckets.length; 70 static foreach(const x; 0 .. 2) 71 foreach(ref b; x ? buckets[0 .. idx] : buckets[idx .. $]) 72 if(!b || (b.hash == hash && b.key == key)) 73 { 74 b = Bucket(hash, key, val); 75 return; 76 } 77 assert(0); // should not get here. 78 } 79 } 80 81 LookupTable!(ElementType!R, size_t, useTruthiness) indexLookup(bool useTruthiness = false, R)(R rng) if (isRandomAccessRange!R) 82 { 83 // simple equation -- use 2x the number of elements but with one less to make it a little more randomized 84 LookupTable!(ElementType!R, size_t, useTruthiness) result; 85 if(rng.length == 0) 86 // just return an empty table. 87 return result; 88 result.buckets = new result.Bucket[rng.length * 2 - 1]; 89 90 size_t i = 0; 91 static if(hasLvalueElements!R) 92 { 93 foreach(ref k; rng) 94 { 95 result._insert(k, i); 96 ++i; 97 } 98 } 99 else 100 { 101 foreach(k; rng) 102 { 103 result._insert(k, i); 104 ++i; 105 } 106 } 107 108 return result; 109 } 110 111 112 unittest 113 { 114 string[] names = ["a", "b", "c", "d", "e", "f", "g"]; 115 auto lookup = names.indexLookup!true; 116 auto lookup2 = names.indexLookup!false; 117 foreach(i, n; names) 118 { 119 assert(lookup[n] == i, n); 120 assert(lookup2[n] == i, n); 121 } 122 } 123 124 struct LookupByField(K, R, bool useTruthiness) if (isRandomAccessRange!R) 125 { 126 private R src; 127 private LookupTable!(K, size_t, useTruthiness) lookup; 128 alias Elem = ElementType!(R); 129 130 static if(hasLvalueElements!R) 131 inout(Elem)* opBinaryRight(string op : "in")(const(K) key) inout 132 { 133 if(auto i = key in lookup) 134 return &src[*i]; 135 return null; 136 } 137 138 auto ref inout(Elem) opIndex()(const(K) key) inout 139 { 140 return src[lookup[key]]; 141 } 142 } 143 144 auto fieldLookup(string fieldName, bool useTruthiness = false, R)(R src) if (isRandomAccessRange!R) 145 { 146 return fieldLookup!((auto ref x) => __traits(getMember, x, fieldName), useTruthiness)(src); 147 } 148 149 auto fieldLookup(alias genKey, bool useTruthiness = false, R)(R src) if (isRandomAccessRange!R && is(typeof(genKey(src.front)))) 150 { 151 import std.algorithm : map; 152 auto idxlookup = indexLookup(src.save.map!(genKey)); 153 return LookupByField!(idxlookup.KeyType, R, useTruthiness)(src.save, idxlookup); 154 } 155 156 unittest 157 { 158 static struct S 159 { 160 int intval; 161 string stringval; 162 } 163 164 auto src = [S(1, "hi"), S(2, "there"), S(3, "foo")]; 165 auto byInt = src.fieldLookup!"intval"; 166 auto byString = src.fieldLookup!((ref x) => x.stringval); 167 168 foreach(ref x; src) 169 { 170 assert(byInt[x.intval] == x); 171 assert(byString[x.stringval] == x); 172 173 assert(x.intval in byInt && *(x.intval in byInt) == x); 174 assert(x.stringval in byString && *(x.stringval in byString) == x); 175 } 176 177 assert(!("bar" in byString)); 178 assert(!(5 in byInt)); 179 } 180 181 unittest 182 { 183 // test lookuptable on empty range. 184 int[] arr; 185 auto byIdx = arr.indexLookup; 186 assert(byIdx.buckets.length == 0); 187 assert(!(0 in byIdx)); 188 }