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 }