hashery 1.5.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.ruby +30 -17
- data/.yardopts +1 -0
- data/Config.rb +28 -0
- data/{QED.rdoc → DEMO.rdoc} +0 -0
- data/HISTORY.rdoc +37 -0
- data/LICENSE.txt +26 -0
- data/NOTICE.txt +46 -0
- data/README.rdoc +10 -7
- data/lib/hashery.rb +6 -6
- data/lib/hashery.yml +30 -17
- data/lib/hashery/association.rb +169 -109
- data/lib/hashery/casting_hash.rb +128 -135
- data/lib/hashery/core_ext.rb +89 -61
- data/lib/hashery/crud_hash.rb +365 -0
- data/lib/hashery/dictionary.rb +545 -345
- data/lib/hashery/fuzzy_hash.rb +177 -125
- data/lib/hashery/ini_hash.rb +321 -0
- data/lib/hashery/key_hash.rb +54 -179
- data/lib/hashery/linked_list.rb +245 -191
- data/lib/hashery/lru_hash.rb +292 -202
- data/lib/hashery/open_cascade.rb +133 -78
- data/lib/hashery/open_hash.rb +127 -61
- data/lib/hashery/ordered_hash.rb +128 -122
- data/lib/hashery/path_hash.rb +238 -0
- data/lib/hashery/property_hash.rb +144 -80
- data/lib/hashery/query_hash.rb +85 -29
- data/lib/hashery/stash.rb +7 -3
- data/lib/hashery/static_hash.rb +46 -41
- data/test/case_association.rb +65 -4
- data/test/case_dictionary.rb +149 -5
- data/test/{case_keyhash.rb → case_key_hash.rb} +20 -14
- data/test/case_lru_hash.rb +162 -0
- data/test/{case_opencascade.rb → case_open_cascade.rb} +4 -8
- data/test/case_open_hash.rb +87 -0
- data/test/case_query_hash.rb +226 -0
- data/test/helper.rb +8 -0
- metadata +33 -63
- data/COPYING.rdoc +0 -45
- data/lib/hashery/basic_object.rb +0 -74
- data/lib/hashery/basic_struct.rb +0 -288
- data/lib/hashery/basicobject.rb +0 -1
- data/lib/hashery/basicstruct.rb +0 -1
- data/lib/hashery/castinghash.rb +0 -1
- data/lib/hashery/fuzzyhash.rb +0 -1
- data/lib/hashery/ini.rb +0 -268
- data/lib/hashery/keyhash.rb +0 -1
- data/lib/hashery/linkedlist.rb +0 -1
- data/lib/hashery/lruhash.rb +0 -1
- data/lib/hashery/memoizer.rb +0 -64
- data/lib/hashery/open_object.rb +0 -1
- data/lib/hashery/opencascade.rb +0 -1
- data/lib/hashery/openhash.rb +0 -1
- data/lib/hashery/openobject.rb +0 -1
- data/lib/hashery/orderedhash.rb +0 -1
- data/lib/hashery/ostructable.rb +0 -186
- data/lib/hashery/propertyhash.rb +0 -1
- data/lib/hashery/queryhash.rb +0 -1
- data/lib/hashery/statichash.rb +0 -1
- data/qed/01_openhash.rdoc +0 -57
- data/qed/02_queryhash.rdoc +0 -21
- data/qed/03_castinghash.rdoc +0 -13
- data/qed/04_statichash.rdoc +0 -22
- data/qed/05_association.rdoc +0 -59
- data/qed/06_opencascade.rdoc +0 -58
- data/qed/07_fuzzyhash.rdoc +0 -141
- data/qed/08_properyhash.rdoc +0 -38
- data/qed/09_ostructable.rdoc +0 -56
- data/qed/applique/ae.rb +0 -1
- data/test/case_basicstruct.rb +0 -192
- data/test/case_openhash.rb +0 -22
@@ -0,0 +1,365 @@
|
|
1
|
+
require 'hashery/core_ext'
|
2
|
+
|
3
|
+
module Hashery
|
4
|
+
|
5
|
+
# The CRUDHash is essentailly the same as the Hash class, but it reduces the
|
6
|
+
# the set of necessary methods to the fundametal CRUD requirements. All other
|
7
|
+
# methods route through these CRUD methods. This is a better general design,
|
8
|
+
# although it is, of course, a little bit slower. The utility of this class
|
9
|
+
# becomes appearent when subclassing or delegating, as only a handful of methods
|
10
|
+
# need to be changed for all other methods to work accordingly.
|
11
|
+
#
|
12
|
+
# In addition to the CRUD features, CRUDHash supports a `#key_proc`, akin to
|
13
|
+
# `#default_proc`, that can be used to normalize keys.
|
14
|
+
#
|
15
|
+
class CRUDHash < ::Hash
|
16
|
+
|
17
|
+
#
|
18
|
+
# This method is overridden to ensure that new entries pass through
|
19
|
+
# the `#store` method.
|
20
|
+
#
|
21
|
+
# hash - [#each] Single Hash, associative array or just a list of pairs.
|
22
|
+
#
|
23
|
+
def self.[](*hash)
|
24
|
+
h = new
|
25
|
+
if hash.size == 1
|
26
|
+
hash.first.each do |k,v|
|
27
|
+
h.store(k, v)
|
28
|
+
end
|
29
|
+
else
|
30
|
+
hash.each do |(k,v)|
|
31
|
+
h.store(k, v)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
h
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Alternate to #new which auto-creates sub-dictionaries as needed.
|
39
|
+
# By default the `default_proc` procuced a empty Hash and is
|
40
|
+
# self-referential so every such Hash also has the same `default_proc`.
|
41
|
+
#
|
42
|
+
# args - Pass-thru arguments to `#new`.
|
43
|
+
# block - Alternate internal procedure for default proc.
|
44
|
+
#
|
45
|
+
# Examples
|
46
|
+
#
|
47
|
+
# d = CRUDHash.auto
|
48
|
+
# d["a"]["b"]["c"] = "abc" #=> { "a"=>{"b"=>{"c"=>"abc"}}}
|
49
|
+
#
|
50
|
+
# Returns `Hash`.
|
51
|
+
#
|
52
|
+
def self.auto(*args, &block)
|
53
|
+
if block
|
54
|
+
leet = lambda { |hsh, key| hsh[key] = block.call(hsh, key) }
|
55
|
+
else
|
56
|
+
leet = lambda { |hsh, key| hsh[key] = new(&leet) }
|
57
|
+
end
|
58
|
+
new(*args, &leet)
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Set `key_proc`.
|
63
|
+
#
|
64
|
+
# Examples
|
65
|
+
#
|
66
|
+
# ch = CRUDHash.new
|
67
|
+
# ch.key_proc = Proc.new{ |key| key.to_sym }
|
68
|
+
#
|
69
|
+
# Returns `Proc`.
|
70
|
+
#
|
71
|
+
def key_proc=(proc)
|
72
|
+
raise ArgumentError unless Proc === proc or NilClass === proc
|
73
|
+
@key_proc = proc
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Get/set `key_proc`.
|
78
|
+
#
|
79
|
+
# Examples
|
80
|
+
#
|
81
|
+
# ch = CRUDHash.new
|
82
|
+
# ch.key_proc
|
83
|
+
#
|
84
|
+
# Returns `Proc`.
|
85
|
+
#
|
86
|
+
def key_proc(&block)
|
87
|
+
@key_proc = block if block
|
88
|
+
@key_proc
|
89
|
+
end
|
90
|
+
|
91
|
+
#
|
92
|
+
# Allow `#default_proc` to take a block.
|
93
|
+
#
|
94
|
+
# block - The `Proc` object to set the `default_proc`.
|
95
|
+
#
|
96
|
+
# Returns `Proc`, the `default_proc`.
|
97
|
+
#
|
98
|
+
def default_proc(&block)
|
99
|
+
self.default_proc = block if block
|
100
|
+
super()
|
101
|
+
end
|
102
|
+
|
103
|
+
#
|
104
|
+
# CRUD method for checking if key exists.
|
105
|
+
#
|
106
|
+
# key - Hash key to lookup.
|
107
|
+
#
|
108
|
+
# Returns `true/false`.
|
109
|
+
#
|
110
|
+
def key?(key)
|
111
|
+
super cast_key(key)
|
112
|
+
end
|
113
|
+
|
114
|
+
#
|
115
|
+
# CRUD method for reading value.
|
116
|
+
#
|
117
|
+
# key - Hash key to lookup.
|
118
|
+
#
|
119
|
+
# Returns value of Hash entry.
|
120
|
+
#
|
121
|
+
def read(key)
|
122
|
+
super cast_key(key)
|
123
|
+
end
|
124
|
+
|
125
|
+
#
|
126
|
+
# CRUD method for create and update.
|
127
|
+
#
|
128
|
+
# key - The `Object` to act as indexing key.
|
129
|
+
# value - The `Object` to associate with key.
|
130
|
+
#
|
131
|
+
# Returns +value+.
|
132
|
+
#
|
133
|
+
def store(key, value)
|
134
|
+
super(cast_key(key), value)
|
135
|
+
end
|
136
|
+
|
137
|
+
#
|
138
|
+
# CRUD method for delete.
|
139
|
+
#
|
140
|
+
# key - Hash key to remove.
|
141
|
+
#
|
142
|
+
# Returns value of deleted Hash entry.
|
143
|
+
#
|
144
|
+
def delete(key)
|
145
|
+
super cast_key(key)
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# Like #read but raises an error if key is not present.
|
150
|
+
#
|
151
|
+
# key - Hash key to lookup.
|
152
|
+
#
|
153
|
+
# Returns the `Object` that is the Hash entry's value.
|
154
|
+
#
|
155
|
+
def fetch(key)
|
156
|
+
raise KeyError, "key not found: #{key.inspect}" unless key?(key)
|
157
|
+
read key
|
158
|
+
end
|
159
|
+
|
160
|
+
#
|
161
|
+
# Update Hash with +assoc+.
|
162
|
+
#
|
163
|
+
# assoc - Two-element `Array` or a `Hash`.
|
164
|
+
#
|
165
|
+
# Returns +assoc+.
|
166
|
+
#
|
167
|
+
def <<(assoc)
|
168
|
+
case assoc
|
169
|
+
when Hash
|
170
|
+
update(assoc)
|
171
|
+
when Array
|
172
|
+
assoc.each_slice(2) do |(k,v)|
|
173
|
+
store(k,v)
|
174
|
+
end
|
175
|
+
else
|
176
|
+
raise ArgumentError # or TypeError ?
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
#
|
181
|
+
# Operator for `#read`.
|
182
|
+
#
|
183
|
+
# key - Index key to lookup.
|
184
|
+
#
|
185
|
+
# Returns `Object` value of key.
|
186
|
+
#
|
187
|
+
def [](key)
|
188
|
+
#if key?(key)
|
189
|
+
# fetch(key)
|
190
|
+
#elsif default_proc
|
191
|
+
# default_proc.call(self, key)
|
192
|
+
#else
|
193
|
+
# default
|
194
|
+
#end
|
195
|
+
read(key)
|
196
|
+
end
|
197
|
+
|
198
|
+
#
|
199
|
+
# Operator for `#store`.
|
200
|
+
#
|
201
|
+
# key - The `Object` to act as indexing key.
|
202
|
+
# value - The `Object` to associate with key.
|
203
|
+
#
|
204
|
+
# Returns +value+.
|
205
|
+
#
|
206
|
+
def []=(key,value)
|
207
|
+
store(key,value)
|
208
|
+
end
|
209
|
+
|
210
|
+
#
|
211
|
+
# Update the Hash with another hash.
|
212
|
+
#
|
213
|
+
# other - Other hash or hash-like object to add to the hash.
|
214
|
+
#
|
215
|
+
# Returns +self+.
|
216
|
+
#
|
217
|
+
def update(other)
|
218
|
+
other.each do |k,v|
|
219
|
+
store(k, v)
|
220
|
+
end
|
221
|
+
self
|
222
|
+
end
|
223
|
+
|
224
|
+
#
|
225
|
+
# Alias for `#update`.
|
226
|
+
#
|
227
|
+
alias merge! update
|
228
|
+
|
229
|
+
#
|
230
|
+
# Merge the Hash with another hash, returning a new Hash.
|
231
|
+
#
|
232
|
+
# other - Other hash or hash-like object to add to the hash.
|
233
|
+
#
|
234
|
+
# Returns `Hash`.
|
235
|
+
#
|
236
|
+
def merge(other)
|
237
|
+
#super(other.rekey{ |key| cast_key(key) })
|
238
|
+
copy = dup
|
239
|
+
other.each{ |k,v| copy.store(k, v) }
|
240
|
+
copy
|
241
|
+
end
|
242
|
+
|
243
|
+
#
|
244
|
+
# Iterate over each hash pair.
|
245
|
+
#
|
246
|
+
def each #:yield:
|
247
|
+
if block_given?
|
248
|
+
keys.each do |k|
|
249
|
+
yield(k, read(k))
|
250
|
+
end
|
251
|
+
else
|
252
|
+
to_enum(:each)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
#
|
257
|
+
# Alias for #each.
|
258
|
+
#
|
259
|
+
alias each_pair each
|
260
|
+
|
261
|
+
#
|
262
|
+
# Alias for `#key?`.
|
263
|
+
#
|
264
|
+
alias has_key? key?
|
265
|
+
|
266
|
+
#
|
267
|
+
# Alias for `#key?`.
|
268
|
+
#
|
269
|
+
alias member? key?
|
270
|
+
|
271
|
+
#
|
272
|
+
# Alias for `#key?`.
|
273
|
+
#
|
274
|
+
alias include? key? # why isn't it an alias for `#has_value?` ?
|
275
|
+
|
276
|
+
#
|
277
|
+
# Replace current entries with those from another Hash,
|
278
|
+
# or Hash-like object. Each entry is run through the
|
279
|
+
# casting procedure as it is added.
|
280
|
+
#
|
281
|
+
# other - Hash-like object.
|
282
|
+
#
|
283
|
+
# Returns +self+.
|
284
|
+
#
|
285
|
+
def replace(other)
|
286
|
+
super cast(other)
|
287
|
+
end
|
288
|
+
|
289
|
+
#
|
290
|
+
# Get the values at.
|
291
|
+
#
|
292
|
+
# keys - List of keys to lookup.
|
293
|
+
#
|
294
|
+
# Returns `Array` of values.
|
295
|
+
#
|
296
|
+
def values_at(*keys)
|
297
|
+
super *keys.map{ |key| cast_key(key) }
|
298
|
+
end
|
299
|
+
|
300
|
+
# Convert CRUDHash to regular Hash.
|
301
|
+
#
|
302
|
+
# TODO: Since a CRUDHash is a subclass of Hash should #to_hash just `#dup`
|
303
|
+
# insted of converting to traditional Hash?
|
304
|
+
#
|
305
|
+
def to_hash
|
306
|
+
h = {}; each{ |k,v| h[k] = v }; h
|
307
|
+
end #unless method_defined?(:to_hash)
|
308
|
+
|
309
|
+
#
|
310
|
+
# Convert CRUDHash to regular Hash.
|
311
|
+
#
|
312
|
+
# TODO: Since a CRUDHash is a subclass of Hash should #to_h just `#dup`
|
313
|
+
# insted of converting to traditional Hash?
|
314
|
+
#
|
315
|
+
# Returns `Hash`.
|
316
|
+
#
|
317
|
+
alias :to_h :to_hash
|
318
|
+
|
319
|
+
private
|
320
|
+
|
321
|
+
#
|
322
|
+
# Cast a given `hash` in accordance to the `#key_proc`.
|
323
|
+
#
|
324
|
+
# hash - Any object the responds to `#each` like a Hash.
|
325
|
+
#
|
326
|
+
# Returns `Hash`.
|
327
|
+
#
|
328
|
+
def cast(hash)
|
329
|
+
h = {}
|
330
|
+
hash.each do |k,v|
|
331
|
+
h[cast_key(k)] = v
|
332
|
+
end
|
333
|
+
h
|
334
|
+
end
|
335
|
+
|
336
|
+
#
|
337
|
+
# Callback for normalizing hash keys.
|
338
|
+
#
|
339
|
+
# key - Index key.
|
340
|
+
#
|
341
|
+
# Returns key after passing through the `key_proc`.
|
342
|
+
#
|
343
|
+
def cast_key(key)
|
344
|
+
@key_proc ? @key_proc.call(key) : key
|
345
|
+
end
|
346
|
+
|
347
|
+
# TODO: Consider value callback procs for future version of CRUDHash.
|
348
|
+
#
|
349
|
+
# #
|
350
|
+
# # Callback for writing value.
|
351
|
+
# #
|
352
|
+
# def cast_write(value)
|
353
|
+
# @write_proc ? @write_proc.call(value) : value
|
354
|
+
# end
|
355
|
+
#
|
356
|
+
# #
|
357
|
+
# # Callback for reading value.
|
358
|
+
# #
|
359
|
+
# def cast_read(value)
|
360
|
+
# @read_proc ? @read_proc.call(value) : value
|
361
|
+
# end
|
362
|
+
|
363
|
+
end
|
364
|
+
|
365
|
+
end
|
data/lib/hashery/dictionary.rb
CHANGED
@@ -1,90 +1,160 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# The Dictionary class is a Hash that preserves order.
|
4
|
-
# So it has some array-like extensions also. By defualt
|
5
|
-
# a Dictionary object preserves insertion order, but any
|
6
|
-
# order can be specified including alphabetical key order.
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# hsh =
|
14
|
-
# hsh['
|
15
|
-
# hsh['a']
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
# p hsh.
|
29
|
-
# p hsh.
|
30
|
-
# p hsh.
|
31
|
-
# p hsh.
|
32
|
-
# p hsh.keys #=> ["to_begin", "a", "c", "z"
|
33
|
-
# p hsh.
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
# *
|
40
|
-
# *
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
# *
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
hsh[args.shift] = args.shift
|
1
|
+
module Hashery
|
2
|
+
|
3
|
+
# The Dictionary class is a Hash that preserves order.
|
4
|
+
# So it has some array-like extensions also. By defualt
|
5
|
+
# a Dictionary object preserves insertion order, but any
|
6
|
+
# order can be specified including alphabetical key order.
|
7
|
+
#
|
8
|
+
# Using a Dictionary is almost the same as using a Hash.
|
9
|
+
#
|
10
|
+
# # You can do simply
|
11
|
+
# hsh = Dictionary.new
|
12
|
+
# hsh['z'] = 1
|
13
|
+
# hsh['a'] = 2
|
14
|
+
# hsh['c'] = 3
|
15
|
+
# p hsh.keys #=> ['z','a','c']
|
16
|
+
#
|
17
|
+
# # or using Dictionary[] method
|
18
|
+
# hsh = Dictionary['z', 1, 'a', 2, 'c', 3]
|
19
|
+
# p hsh.keys #=> ['z','a','c']
|
20
|
+
#
|
21
|
+
# # but this don't preserve order
|
22
|
+
# hsh = Dictionary['z'=>1, 'a'=>2, 'c'=>3]
|
23
|
+
# p hsh.keys #=> ['a','c','z']
|
24
|
+
#
|
25
|
+
# # Dictionary has useful extensions: push, pop and unshift
|
26
|
+
# p hsh.push('to_end', 15) #=> true, key added
|
27
|
+
# p hsh.push('to_end', 30) #=> false, already - nothing happen
|
28
|
+
# p hsh.unshift('to_begin', 50) #=> true, key added
|
29
|
+
# p hsh.unshift('to_begin', 60) #=> false, already - nothing happen
|
30
|
+
# p hsh.keys #=> ["to_begin", "a", "c", "z", "to_end"]
|
31
|
+
# p hsh.pop #=> ["to_end", 15], if nothing remains, return nil
|
32
|
+
# p hsh.keys #=> ["to_begin", "a", "c", "z"]
|
33
|
+
# p hsh.shift #=> ["to_begin", 30], if nothing remains, return nil
|
34
|
+
#
|
35
|
+
# == Notes
|
36
|
+
#
|
37
|
+
# * You can use #order_by to set internal sort order.
|
38
|
+
# * #<< takes a two element [k,v] array and inserts.
|
39
|
+
# * Use ::auto which creates Dictionay sub-entries as needed.
|
40
|
+
# * And ::alpha which creates a new Dictionary sorted by key.
|
41
|
+
#
|
42
|
+
# == Acknowledgments
|
43
|
+
#
|
44
|
+
# Dictionary is a port of OrderHash 2.0 Copyright (c) 2005 Jan Molic.
|
45
|
+
#
|
46
|
+
# People who have contributed to this class since then include:
|
47
|
+
#
|
48
|
+
# * Andrew Johnson (merge, to_a, inspect, shift and Hash[])
|
49
|
+
# * Jeff Sharpe (reverse and reverse!)
|
50
|
+
# * Thomas Leitner (has_key? and key?)
|
51
|
+
#
|
52
|
+
# OrderedHash is public domain.
|
53
|
+
#
|
54
|
+
class Dictionary
|
55
|
+
|
56
|
+
include Enumerable
|
57
|
+
|
58
|
+
class << self
|
59
|
+
#--
|
60
|
+
# TODO is this needed? Doesn't the super class do this?
|
61
|
+
#++
|
62
|
+
|
63
|
+
def [](*args)
|
64
|
+
hsh = new
|
65
|
+
if Hash === args[0]
|
66
|
+
hsh.replace(args[0])
|
67
|
+
elsif (args.size % 2) != 0
|
68
|
+
raise ArgumentError, "odd number of elements for Hash"
|
69
|
+
else
|
70
|
+
while !args.empty?
|
71
|
+
hsh[args.shift] = args.shift
|
72
|
+
end
|
74
73
|
end
|
74
|
+
hsh
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# Like #new but the block sets the order.
|
79
|
+
#
|
80
|
+
def new_by(*args, &blk)
|
81
|
+
new(*args).order_by(&blk)
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Alternate to #new which creates a dictionary sorted by key.
|
86
|
+
#
|
87
|
+
# d = Dictionary.alpha
|
88
|
+
# d["z"] = 1
|
89
|
+
# d["y"] = 2
|
90
|
+
# d["x"] = 3
|
91
|
+
# d #=> {"x"=>3,"y"=>2,"z"=>2}
|
92
|
+
#
|
93
|
+
# This is equivalent to:
|
94
|
+
#
|
95
|
+
# Dictionary.new.order_by { |key,value| key }
|
96
|
+
|
97
|
+
def alpha(*args, &block)
|
98
|
+
new(*args, &block).order_by_key
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# Alternate to #new which auto-creates sub-dictionaries as needed.
|
103
|
+
#
|
104
|
+
# Examples
|
105
|
+
#
|
106
|
+
# d = Dictionary.auto
|
107
|
+
# d["a"]["b"]["c"] = "abc" #=> { "a"=>{"b"=>{"c"=>"abc"}}}
|
108
|
+
#
|
109
|
+
def auto(*args)
|
110
|
+
#AutoDictionary.new(*args)
|
111
|
+
leet = lambda { |hsh, key| hsh[key] = new(&leet) }
|
112
|
+
new(*args, &leet)
|
75
113
|
end
|
76
|
-
hsh
|
77
114
|
end
|
78
115
|
|
79
|
-
# Like #new but the block sets the order.
|
80
116
|
#
|
81
|
-
|
82
|
-
|
117
|
+
# New Dictiionary.
|
118
|
+
#
|
119
|
+
def initialize(*args, &blk)
|
120
|
+
@order = []
|
121
|
+
@order_by = nil
|
122
|
+
if blk
|
123
|
+
dict = self # This ensure autmatic key entry effect the
|
124
|
+
oblk = lambda{ |hsh, key| blk[dict,key] } # dictionary rather then just the interal hash.
|
125
|
+
@hash = Hash.new(*args, &oblk)
|
126
|
+
else
|
127
|
+
@hash = Hash.new(*args)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
#
|
132
|
+
# Order of keys.
|
133
|
+
#
|
134
|
+
# Returns [Array].
|
135
|
+
#
|
136
|
+
def order
|
137
|
+
reorder if @order_by
|
138
|
+
@order
|
139
|
+
end
|
140
|
+
|
141
|
+
#
|
142
|
+
# Keep dictionary sorted by a specific sort order.
|
143
|
+
#
|
144
|
+
# block - Ordering procedure.
|
145
|
+
#
|
146
|
+
# Returns +self+.
|
147
|
+
#
|
148
|
+
def order_by( &block )
|
149
|
+
@order_by = block
|
150
|
+
order
|
151
|
+
self
|
83
152
|
end
|
84
153
|
|
85
|
-
# Alternate to #new which creates a dictionary sorted by key.
|
86
154
|
#
|
87
|
-
#
|
155
|
+
# Keep dictionary sorted by key.
|
156
|
+
#
|
157
|
+
# d = Dictionary.new.order_by_key
|
88
158
|
# d["z"] = 1
|
89
159
|
# d["y"] = 2
|
90
160
|
# d["x"] = 3
|
@@ -93,332 +163,462 @@ class Dictionary
|
|
93
163
|
# This is equivalent to:
|
94
164
|
#
|
95
165
|
# Dictionary.new.order_by { |key,value| key }
|
96
|
-
|
97
|
-
|
98
|
-
|
166
|
+
#
|
167
|
+
# The initializer Dictionary#alpha also provides this.
|
168
|
+
#
|
169
|
+
# Returns +self+.
|
170
|
+
#
|
171
|
+
def order_by_key
|
172
|
+
@order_by = Proc.new{ |k,v| k }
|
173
|
+
order
|
174
|
+
self
|
99
175
|
end
|
100
176
|
|
101
|
-
# Alternate to #new which auto-creates sub-dictionaries as needed.
|
102
177
|
#
|
103
|
-
#
|
104
|
-
# d["a"]["b"]["c"] = "abc" #=> { "a"=>{"b"=>{"c"=>"abc"}}}
|
178
|
+
# Keep dictionary sorted by value.
|
105
179
|
#
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
180
|
+
# d = Dictionary.new.order_by_value
|
181
|
+
# d["z"] = 1
|
182
|
+
# d["y"] = 2
|
183
|
+
# d["x"] = 3
|
184
|
+
# d #=> {"x"=>3,"y"=>2,"z"=>2}
|
185
|
+
#
|
186
|
+
# This is equivalent to:
|
187
|
+
#
|
188
|
+
# Dictionary.new.order_by { |key,value| value }
|
189
|
+
#
|
190
|
+
def order_by_value
|
191
|
+
@order_by = Proc.new{ |k,v| v }
|
192
|
+
order
|
193
|
+
self
|
110
194
|
end
|
111
|
-
end
|
112
195
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
@
|
122
|
-
else
|
123
|
-
@hash = Hash.new(*args)
|
196
|
+
#
|
197
|
+
# Re-apply the sorting procedure.
|
198
|
+
#
|
199
|
+
def reorder
|
200
|
+
if @order_by
|
201
|
+
assoc = @order.collect{ |k| [k,@hash[k]] }.sort_by(&@order_by)
|
202
|
+
@order = assoc.collect{ |k,v| k }
|
203
|
+
end
|
204
|
+
@order
|
124
205
|
end
|
125
|
-
end
|
126
|
-
|
127
|
-
#
|
128
206
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
# Keep dictionary sorted by a specific sort order.
|
135
|
-
|
136
|
-
def order_by( &block )
|
137
|
-
@order_by = block
|
138
|
-
order
|
139
|
-
self
|
140
|
-
end
|
207
|
+
#def ==( hsh2 )
|
208
|
+
# return false if @order != hsh2.order
|
209
|
+
# super hsh2
|
210
|
+
#end
|
141
211
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
#
|
154
|
-
# The initializer Dictionary#alpha also provides this.
|
155
|
-
|
156
|
-
def order_by_key
|
157
|
-
@order_by = lambda { |k,v| k }
|
158
|
-
order
|
159
|
-
self
|
160
|
-
end
|
212
|
+
#
|
213
|
+
# Is the dictionary instance equivalent to another?
|
214
|
+
#
|
215
|
+
def ==(hsh2)
|
216
|
+
if hsh2.is_a?( Dictionary )
|
217
|
+
@order == hsh2.order &&
|
218
|
+
@hash == hsh2.instance_variable_get("@hash")
|
219
|
+
else
|
220
|
+
false
|
221
|
+
end
|
222
|
+
end
|
161
223
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
# d #=> {"x"=>3,"y"=>2,"z"=>2}
|
169
|
-
#
|
170
|
-
# This is equivalent to:
|
171
|
-
#
|
172
|
-
# Dictionary.new.order_by { |key,value| value }
|
224
|
+
#
|
225
|
+
# Lookup entry with key.
|
226
|
+
#
|
227
|
+
def [] key
|
228
|
+
@hash[ key ]
|
229
|
+
end
|
173
230
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
231
|
+
#
|
232
|
+
# Featch entry given +key+.
|
233
|
+
#
|
234
|
+
def fetch(key, *a, &b)
|
235
|
+
@hash.fetch(key, *a, &b)
|
236
|
+
end
|
179
237
|
|
180
|
-
|
238
|
+
#
|
239
|
+
# Store operator.
|
240
|
+
#
|
241
|
+
# h[key] = value
|
242
|
+
#
|
243
|
+
# Or with additional index.
|
244
|
+
#
|
245
|
+
# h[key,index] = value
|
246
|
+
#
|
247
|
+
def []=(k, i=nil, v=nil)
|
248
|
+
if v
|
249
|
+
insert(i,k,v)
|
250
|
+
else
|
251
|
+
store(k,i)
|
252
|
+
end
|
253
|
+
end
|
181
254
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
255
|
+
#
|
256
|
+
# Insert entry into dictionary at specific index position.
|
257
|
+
#
|
258
|
+
# index - [Integer] Position of order placement.
|
259
|
+
# key - [Object] Key to associate with value.
|
260
|
+
# value - [Object] Value to associate with key.
|
261
|
+
#
|
262
|
+
# Returns `value` stored.
|
263
|
+
#
|
264
|
+
def insert(index, key, value)
|
265
|
+
@order.insert(index, key)
|
266
|
+
@hash.store(key, value)
|
186
267
|
end
|
187
|
-
@order
|
188
|
-
end
|
189
268
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
269
|
+
#
|
270
|
+
# Add entry into dictionary.
|
271
|
+
#
|
272
|
+
# Returns `value`.
|
273
|
+
#
|
274
|
+
def store(key, value)
|
275
|
+
@order.push(key) unless @hash.has_key?(key)
|
276
|
+
@hash.store(key, value)
|
277
|
+
end
|
194
278
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
279
|
+
#
|
280
|
+
# Clear dictionary of all entries.
|
281
|
+
#
|
282
|
+
def clear
|
283
|
+
@order = []
|
284
|
+
@hash.clear
|
201
285
|
end
|
202
|
-
end
|
203
286
|
|
204
|
-
|
205
|
-
|
206
|
-
|
287
|
+
#
|
288
|
+
# Delete the entry with given +key+.
|
289
|
+
#
|
290
|
+
def delete(key)
|
291
|
+
@order.delete(key)
|
292
|
+
@hash.delete(key)
|
293
|
+
end
|
207
294
|
|
208
|
-
|
209
|
-
|
210
|
-
|
295
|
+
#
|
296
|
+
# Iterate over each key.
|
297
|
+
#
|
298
|
+
def each_key
|
299
|
+
order.each { |k| yield( k ) }
|
300
|
+
self
|
301
|
+
end
|
211
302
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
303
|
+
#
|
304
|
+
# Iterate over each value.
|
305
|
+
#
|
306
|
+
def each_value
|
307
|
+
order.each { |k| yield( @hash[k] ) }
|
308
|
+
self
|
309
|
+
end
|
219
310
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
311
|
+
#
|
312
|
+
# Iterate over each key-value pair.
|
313
|
+
#
|
314
|
+
def each
|
315
|
+
order.each { |k| yield( k,@hash[k] ) }
|
316
|
+
self
|
225
317
|
end
|
226
|
-
end
|
227
318
|
|
228
|
-
|
229
|
-
@order.insert( i,k )
|
230
|
-
@hash.store( k,v )
|
231
|
-
end
|
319
|
+
alias each_pair each
|
232
320
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
321
|
+
#
|
322
|
+
# Delete entry if it fits conditional block.
|
323
|
+
#
|
324
|
+
def delete_if
|
325
|
+
order.clone.each { |k| delete k if yield(k,@hash[k]) }
|
326
|
+
self
|
327
|
+
end
|
237
328
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
329
|
+
#
|
330
|
+
# List of all dictionary values.
|
331
|
+
#
|
332
|
+
# Returns [Array].
|
333
|
+
#
|
334
|
+
def values
|
335
|
+
ary = []
|
336
|
+
order.each { |k| ary.push @hash[k] }
|
337
|
+
ary
|
338
|
+
end
|
242
339
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
340
|
+
#
|
341
|
+
# List of all dictionary keys.
|
342
|
+
#
|
343
|
+
# Returns [Array].
|
344
|
+
#
|
345
|
+
def keys
|
346
|
+
order
|
347
|
+
end
|
247
348
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
349
|
+
#
|
350
|
+
# Invert the dictionary.
|
351
|
+
#
|
352
|
+
# Returns [Dictionary] New dictionary that is inverse of the original.
|
353
|
+
#
|
354
|
+
def invert
|
355
|
+
hsh2 = self.class.new
|
356
|
+
order.each { |k| hsh2[@hash[k]] = k }
|
357
|
+
hsh2
|
358
|
+
end
|
252
359
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
360
|
+
#
|
361
|
+
# Reject entries based on give condition block and return
|
362
|
+
# new dictionary.
|
363
|
+
#
|
364
|
+
# Returns [Dictionary].
|
365
|
+
#
|
366
|
+
def reject(&block)
|
367
|
+
self.dup.delete_if(&block)
|
368
|
+
end
|
257
369
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
370
|
+
#
|
371
|
+
# Reject entries based on give condition block.
|
372
|
+
#
|
373
|
+
# Returns [Hash] of rejected entries.
|
374
|
+
#
|
375
|
+
# FIXME: This looks like it is implemented wrong!!!
|
376
|
+
#
|
377
|
+
def reject!( &block )
|
378
|
+
hsh2 = reject(&block)
|
379
|
+
self == hsh2 ? nil : hsh2
|
380
|
+
end
|
263
381
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
382
|
+
#
|
383
|
+
# Replace dictionary entries with new table.
|
384
|
+
#
|
385
|
+
def replace(hsh2)
|
386
|
+
case hsh2
|
387
|
+
when Dictionary
|
388
|
+
@order = hsh2.order
|
389
|
+
@hash = hsh2.to_h
|
390
|
+
when Hash
|
391
|
+
@hash = hsh2
|
392
|
+
@order = @hash.keys
|
393
|
+
else
|
394
|
+
@hash = hsh2.to_h
|
395
|
+
@order = @hash.keys
|
396
|
+
end
|
397
|
+
reorder
|
398
|
+
end
|
268
399
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
400
|
+
#
|
401
|
+
# Remove entry from the to top of dictionary.
|
402
|
+
#
|
403
|
+
def shift
|
404
|
+
key = order.first
|
405
|
+
key ? [key,delete(key)] : super
|
406
|
+
end
|
274
407
|
|
275
|
-
|
276
|
-
|
277
|
-
|
408
|
+
#
|
409
|
+
# Push entry on to the top of dictionary.
|
410
|
+
#
|
411
|
+
def unshift( k,v )
|
412
|
+
unless @hash.include?( k )
|
413
|
+
@order.unshift( k )
|
414
|
+
@hash.store( k,v )
|
415
|
+
true
|
416
|
+
else
|
417
|
+
false
|
418
|
+
end
|
419
|
+
end
|
278
420
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
421
|
+
#
|
422
|
+
# Same as #push.
|
423
|
+
#
|
424
|
+
def <<(kv)
|
425
|
+
push(*kv)
|
426
|
+
end
|
284
427
|
|
285
|
-
|
286
|
-
|
287
|
-
|
428
|
+
#
|
429
|
+
# Push entry on to bottom of the dictionary.
|
430
|
+
#
|
431
|
+
def push(k,v)
|
432
|
+
unless @hash.include?( k )
|
433
|
+
@order.push( k )
|
434
|
+
@hash.store( k,v )
|
435
|
+
true
|
436
|
+
else
|
437
|
+
false
|
438
|
+
end
|
439
|
+
end
|
288
440
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
441
|
+
#
|
442
|
+
# Pop entry off the bottom of dictionary.
|
443
|
+
#
|
444
|
+
def pop
|
445
|
+
key = order.last
|
446
|
+
key ? [key,delete(key)] : nil
|
447
|
+
end
|
293
448
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
449
|
+
#
|
450
|
+
# Inspection string for Dictionary.
|
451
|
+
#
|
452
|
+
# Returns [String].
|
453
|
+
#
|
454
|
+
def inspect
|
455
|
+
ary = []
|
456
|
+
each {|k,v| ary << k.inspect + "=>" + v.inspect}
|
457
|
+
'{' + ary.join(", ") + '}'
|
302
458
|
end
|
303
|
-
reorder
|
304
|
-
end
|
305
459
|
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
460
|
+
#
|
461
|
+
# Duplicate dictionary.
|
462
|
+
#
|
463
|
+
# Returns [Dictionary].
|
464
|
+
#
|
465
|
+
def dup
|
466
|
+
a = []
|
467
|
+
each{ |k,v| a << k; a << v }
|
468
|
+
self.class[*a]
|
469
|
+
end
|
310
470
|
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
471
|
+
#
|
472
|
+
# Update dictionary with other hash.
|
473
|
+
#
|
474
|
+
# Returns self.
|
475
|
+
#
|
476
|
+
def update( hsh2 )
|
477
|
+
hsh2.each { |k,v| self[k] = v }
|
478
|
+
reorder
|
479
|
+
self
|
318
480
|
end
|
319
|
-
end
|
320
481
|
|
321
|
-
|
322
|
-
push(*kv)
|
323
|
-
end
|
482
|
+
alias :merge! update
|
324
483
|
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
484
|
+
#
|
485
|
+
# Merge other hash creating new dictionary.
|
486
|
+
#
|
487
|
+
# Returns [Dictionary].
|
488
|
+
#
|
489
|
+
def merge(hsh2)
|
490
|
+
self.dup.update(hsh2)
|
332
491
|
end
|
333
|
-
end
|
334
492
|
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
493
|
+
#
|
494
|
+
# Select items from dictiornary.
|
495
|
+
#
|
496
|
+
# Returns [Array] of two-element arrays.
|
497
|
+
#
|
498
|
+
def select
|
499
|
+
ary = []
|
500
|
+
each { |k,v| ary << [k,v] if yield k,v }
|
501
|
+
ary
|
502
|
+
end
|
339
503
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
504
|
+
#
|
505
|
+
# Reverse the order of the dictionary.
|
506
|
+
#
|
507
|
+
# Returns self.
|
508
|
+
#
|
509
|
+
def reverse!
|
510
|
+
@order.reverse!
|
511
|
+
self
|
512
|
+
end
|
345
513
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
514
|
+
#
|
515
|
+
# Reverse the order of duplicte dictionary.
|
516
|
+
#
|
517
|
+
# Returns [Dictionary].
|
518
|
+
#
|
519
|
+
def reverse
|
520
|
+
dup.reverse!
|
521
|
+
end
|
351
522
|
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
523
|
+
#
|
524
|
+
# Get/set initial entry value.
|
525
|
+
#
|
526
|
+
def first(x=nil)
|
527
|
+
return @hash[order.first] unless x
|
528
|
+
order.first(x).collect { |k| @hash[k] }
|
529
|
+
end
|
358
530
|
|
359
|
-
|
360
|
-
|
361
|
-
|
531
|
+
#
|
532
|
+
# Get/set last entry value.
|
533
|
+
#
|
534
|
+
def last(x=nil)
|
535
|
+
return @hash[order.last] unless x
|
536
|
+
order.last(x).collect { |k| @hash[k] }
|
537
|
+
end
|
362
538
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
539
|
+
#
|
540
|
+
# Number of items in the dictionary.
|
541
|
+
#
|
542
|
+
def length
|
543
|
+
@order.length
|
544
|
+
end
|
368
545
|
|
369
|
-
|
370
|
-
@order.reverse!
|
371
|
-
self
|
372
|
-
end
|
546
|
+
alias :size :length
|
373
547
|
|
374
|
-
|
375
|
-
|
376
|
-
|
548
|
+
#
|
549
|
+
# Is the dictionary empty?
|
550
|
+
#
|
551
|
+
# Returns `true` or `false`.
|
552
|
+
#
|
553
|
+
def empty?
|
554
|
+
@hash.empty?
|
555
|
+
end
|
377
556
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
557
|
+
#
|
558
|
+
# Does the dictionary have a given +key+.
|
559
|
+
#
|
560
|
+
# Returns `true` or `false`.
|
561
|
+
#
|
562
|
+
def has_key?(key)
|
563
|
+
@hash.has_key?(key)
|
564
|
+
end
|
383
565
|
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
566
|
+
#
|
567
|
+
# Does the dictionary have a given +key+.
|
568
|
+
#
|
569
|
+
# Returns `true` or `false`.
|
570
|
+
#
|
571
|
+
def key?(key)
|
572
|
+
@hash.key?(key)
|
573
|
+
end
|
389
574
|
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
575
|
+
#
|
576
|
+
# Convert to array.
|
577
|
+
#
|
578
|
+
# Returns [Array] of two-element arrays.
|
579
|
+
#
|
580
|
+
def to_a
|
581
|
+
ary = []
|
582
|
+
each { |k,v| ary << [k,v] }
|
583
|
+
ary
|
584
|
+
end
|
394
585
|
|
395
|
-
|
396
|
-
|
397
|
-
|
586
|
+
#
|
587
|
+
# Convert to array then to string.
|
588
|
+
#
|
589
|
+
# Returns [String].
|
590
|
+
#
|
591
|
+
def to_s
|
592
|
+
self.to_a.to_s
|
593
|
+
end
|
398
594
|
|
399
|
-
|
400
|
-
|
401
|
-
|
595
|
+
#
|
596
|
+
# Get a duplicate of the underlying hash table.
|
597
|
+
#
|
598
|
+
# Returns [Hash].
|
599
|
+
#
|
600
|
+
def to_hash
|
601
|
+
@hash.dup
|
602
|
+
end
|
402
603
|
|
403
|
-
|
404
|
-
|
405
|
-
|
604
|
+
#
|
605
|
+
# Get a duplicate of the underlying hash table.
|
606
|
+
#
|
607
|
+
# Returns [Hash].
|
608
|
+
#
|
609
|
+
def to_h
|
610
|
+
@hash.dup
|
611
|
+
end
|
406
612
|
|
407
|
-
|
408
|
-
ary = []
|
409
|
-
each { |k,v| ary << [k,v] }
|
410
|
-
ary
|
411
|
-
end
|
613
|
+
protected
|
412
614
|
|
413
|
-
|
414
|
-
|
415
|
-
|
615
|
+
#
|
616
|
+
# Underlying hash table.
|
617
|
+
#
|
618
|
+
def hash_table
|
619
|
+
@hash
|
620
|
+
end
|
416
621
|
|
417
|
-
def to_hash
|
418
|
-
@hash.dup
|
419
622
|
end
|
420
623
|
|
421
|
-
def to_h
|
422
|
-
@hash.dup
|
423
|
-
end
|
424
624
|
end
|