@@ -182,30 +182,58 @@ def __len__(self):
182182
183183 def gets (self , key ):
184184 '''Yield values for key in insertion order.'''
185- key , h = self .hash_key (key )
186- start , nslots = self .index [h & 0xff ]
185+ # Algorithm from the spec: https://cr.yp.to/cdb/cdb.txt
186+ # "Compute the hash value of the key in the record."
187+ key , hashed_key = self .hash_key (key )
188+
189+ # "The hash value modulo 256 is the number of a hash table."
190+ slot_number , table_number = divmod (hashed_key , 256 )
191+ table_pos , table_len = self .index [table_number ]
192+
193+ # If the length of that table is 0, then there's no matching record.
194+ if not table_len :
195+ return
196+
197+ # Otherwise, compute the position of the end of the table - we'll
198+ # need it to be able to search the whole table.
199+ table_end = table_pos + (self .pair_size * table_len )
200+
201+ # "The hash value divided by 256, modulo the length of that table, is a
202+ # slot number."
203+ slot_number %= table_len
204+
205+ # "Probe that slot, the next higher slot, and so on, until you find the
206+ # record or run into an empty slot."
207+ slot_pos = table_pos + (self .pair_size * slot_number )
208+ while True :
209+ hash_value , byte_pos = self .read_pair (
210+ self .data [slot_pos :slot_pos + self .pair_size ]
211+ )
212+ slot_pos += self .pair_size
213+
214+ # We ran into an empty slot: the search is finished.
215+ if not byte_pos :
216+ break
217+
218+ # Potential hit - we might have found a matching record.
219+ if hash_value == hashed_key :
220+ key_size , value_size = self .read_pair (
221+ self .data [byte_pos :byte_pos + self .pair_size ]
222+ )
223+ byte_pos += self .pair_size
187224
188- if nslots :
189- end = start + (nslots * self .pair_size )
190- slot_off = start + (((h >> 8 ) % nslots ) * self .pair_size )
225+ candidate_key = self .data [byte_pos :byte_pos + key_size ]
226+ byte_pos += key_size
191227
192- for pos in chain (range (slot_off , end , self .pair_size ),
193- range (start , slot_off , self .pair_size )):
194- rec_h , rec_pos = self .read_pair (
195- self .data [pos :pos + self .pair_size ]
196- )
228+ candidate_value = self .data [byte_pos :byte_pos + value_size ]
229+ byte_pos += value_size
230+ if candidate_key == key :
231+ yield candidate_value
197232
198- if not rec_h :
199- break
200- elif rec_h == h :
201- klen , dlen = self .read_pair (
202- self .data [rec_pos :rec_pos + self .pair_size ]
203- )
204- rec_pos += self .pair_size
205-
206- if self .data [rec_pos :rec_pos + klen ] == key :
207- rec_pos += klen
208- yield self .data [rec_pos :rec_pos + dlen ]
233+ # If we've not run into an empty slot yet, we're not finished.
234+ # To go to the "next higher slot," we jump to the table's start.
235+ if slot_pos == table_end :
236+ slot_pos = table_pos
209237
210238 def get (self , key , default = None ):
211239 '''Get the first value for key, returning default if missing.'''
0 commit comments