keso 0.1.3
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/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +23 -0
- data/Rakefile +44 -0
- data/VERSION +1 -0
- data/keso.gemspec +72 -0
- data/lib/keso.rb +8 -0
- data/lib/realvar.rb +12 -0
- data/lib/values/attribute.rb +44 -0
- data/lib/values/heading.rb +125 -0
- data/lib/values/immutable_hash.rb +112 -0
- data/lib/values/immutable_set.rb +151 -0
- data/lib/values/relation.rb +455 -0
- data/lib/values/tuple.rb +149 -0
- data/spec/attribute_spec.rb +61 -0
- data/spec/heading_spec.rb +125 -0
- data/spec/immutable_hash_spec.rb +200 -0
- data/spec/immutable_set_spec.rb +163 -0
- data/spec/relation_spec.rb +358 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/tuple_spec.rb +155 -0
- metadata +107 -0
@@ -0,0 +1,151 @@
|
|
1
|
+
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
class ImmutableSet
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
|
8
|
+
@set = Set.new;
|
9
|
+
|
10
|
+
args.each do |value|
|
11
|
+
if value.is_a? ImmutableSet
|
12
|
+
@set.merge value.set
|
13
|
+
elsif value.is_a? Set
|
14
|
+
@set.merge value
|
15
|
+
elsif value.is_a? Array
|
16
|
+
@set.merge value
|
17
|
+
elsif not value.nil?
|
18
|
+
@set.add value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def size
|
25
|
+
@set.size
|
26
|
+
end
|
27
|
+
|
28
|
+
alias :count :size
|
29
|
+
|
30
|
+
def each &block
|
31
|
+
@set.to_a.each do |value|
|
32
|
+
block.call value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def add *values
|
37
|
+
|
38
|
+
new_set = @set.clone
|
39
|
+
|
40
|
+
values.each do |value|
|
41
|
+
if value.is_a? ImmutableSet
|
42
|
+
new_set.merge(value.to_a)
|
43
|
+
elsif value.is_a? Set
|
44
|
+
new_set.merge(value)
|
45
|
+
elsif value.is_a? Array
|
46
|
+
new_set.merge(value)
|
47
|
+
else
|
48
|
+
new_set.add(value)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
ImmutableSet.new(new_set)
|
53
|
+
end
|
54
|
+
|
55
|
+
def hash
|
56
|
+
@set.hash
|
57
|
+
end
|
58
|
+
|
59
|
+
def eql? other
|
60
|
+
self == other
|
61
|
+
end
|
62
|
+
|
63
|
+
def == other
|
64
|
+
if other.equal?(self)
|
65
|
+
true
|
66
|
+
elsif !self.class.equal?(other.class)
|
67
|
+
false
|
68
|
+
else
|
69
|
+
self.set.eql?(other.set)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def delete *values
|
74
|
+
|
75
|
+
new_set = @set.clone
|
76
|
+
|
77
|
+
values.each do |value|
|
78
|
+
if value.is_a? ImmutableSet
|
79
|
+
new_set.subtract(value.to_a)
|
80
|
+
elsif value.is_a? Set
|
81
|
+
new_set.subtract(value)
|
82
|
+
elsif value.is_a? Array
|
83
|
+
new_set.subtract(value)
|
84
|
+
else
|
85
|
+
new_set.delete(value)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
ImmutableSet.new(new_set)
|
90
|
+
end
|
91
|
+
|
92
|
+
alias :remove :delete
|
93
|
+
|
94
|
+
def to_a
|
95
|
+
@set.to_a
|
96
|
+
end
|
97
|
+
|
98
|
+
def include? value
|
99
|
+
@set.include? value
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
|
107
|
+
|
108
|
+
def subset? other_set
|
109
|
+
self.set.subset?(other_set.set)
|
110
|
+
end
|
111
|
+
|
112
|
+
def superset? other_set
|
113
|
+
self.set.superset?(other_set.set)
|
114
|
+
end
|
115
|
+
|
116
|
+
alias :superset_of? :superset?
|
117
|
+
|
118
|
+
def proper_subset? other_set
|
119
|
+
self.set.proper_subset?(other_set.set)
|
120
|
+
end
|
121
|
+
|
122
|
+
alias :proper_subset_of? :proper_subset?
|
123
|
+
|
124
|
+
def proper_superset? other_set
|
125
|
+
self.set.proper_superset?(other_set.set)
|
126
|
+
end
|
127
|
+
|
128
|
+
alias :proper_superset_of? :proper_superset?
|
129
|
+
|
130
|
+
def union *values
|
131
|
+
self.add *values
|
132
|
+
end
|
133
|
+
|
134
|
+
def complement other_set
|
135
|
+
ImmutableSet.new(self.set - other_set.set)
|
136
|
+
end
|
137
|
+
|
138
|
+
def intersect other_set
|
139
|
+
ImmutableSet.new(self.set & other_set.set)
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
|
144
|
+
protected
|
145
|
+
|
146
|
+
def set
|
147
|
+
@set
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
@@ -0,0 +1,455 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# Subset,Superset
|
4
|
+
#
|
5
|
+
# Union, Intersection, Complement, Cartesian, join
|
6
|
+
# natrual_join: Joins two relations based on all common parts of there headers
|
7
|
+
#
|
8
|
+
|
9
|
+
|
10
|
+
#
|
11
|
+
# Ref
|
12
|
+
#
|
13
|
+
# http://en.wikipedia.org/wiki/Set_%28mathematics%29
|
14
|
+
# http://en.wikipedia.org/wiki/Relational_algebra
|
15
|
+
#
|
16
|
+
# Set methods
|
17
|
+
#
|
18
|
+
# r1.subset(r2) # boolean result
|
19
|
+
# r1.proper_subset(r2) # boolean result
|
20
|
+
# r1.superset(r2) # boolean result
|
21
|
+
# r1.proper_superset(r2) # boolean result
|
22
|
+
# r1.union(r2) # r1 + r2, set result
|
23
|
+
# r1.intersect(r2) # all in r1 that is also in r2, set result
|
24
|
+
# r1.complement(r2) # r1 - r2, set result
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
#
|
29
|
+
# Relational methods
|
30
|
+
#
|
31
|
+
# r1.project('Name','age') # results in a relation of only the supplied attributes, set result
|
32
|
+
# r1.project_all_but('Name','age') # results in a relation with all attributes exepct the supplied ones, set result
|
33
|
+
# r1.rename('Name','PersonName') # changes the attribute name from "Name" to "PersonName", set result
|
34
|
+
# r1.select('age' >= 18) # results in a raltion with where ('age' >= 18) is true, set result
|
35
|
+
# r1.join(r2) do |tupel| ... end
|
36
|
+
# r1.cartesian_product(r2) # new relation with r1 * r2 tuples with all attributes of r1 and r2, set result
|
37
|
+
# r1.natrual_join(r2) # cartesian_product with a selection based on equal attributes having to have equal value, set result
|
38
|
+
# r1.group
|
39
|
+
# r1.ungroup
|
40
|
+
# r1.summarize(:name) do |tuples,new_tuple|
|
41
|
+
# new_tuple.add('avrange_age',avg(tuples,'age'))
|
42
|
+
# new_tuple.add('max_age',max(tuples,'age'))
|
43
|
+
# new_tuple.add('min_age',min(tuples,'age'))
|
44
|
+
# new_tuple.add('median_age',median(tuples,'age'))
|
45
|
+
# end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Add a to_keso to active record and also create a gem
|
49
|
+
#
|
50
|
+
|
51
|
+
# Tutorial D
|
52
|
+
#
|
53
|
+
# r1.project('Name','Age').group(['Age'],'Names') # results in a new relation as r{'Age' => integer, 'Names' => r{'Name' => String}}
|
54
|
+
# r1.project('Name','Age').group(['Age'],'Names').ungroup('Names'[,{'Name' => 'New_name'}]) # results in a new relation as r{'Age' => integer, 'Name' => String}
|
55
|
+
#
|
56
|
+
|
57
|
+
# A relation is a set of tupels constrainted to what can be described as a relational type by a heading, this means that
|
58
|
+
# the relation only takes tuples with the same heading as the relation.
|
59
|
+
# It has a body
|
60
|
+
# It has a heading
|
61
|
+
#
|
62
|
+
# All parts of the body must of the heading
|
63
|
+
#
|
64
|
+
#
|
65
|
+
#
|
66
|
+
#
|
67
|
+
#
|
68
|
+
|
69
|
+
class Relation
|
70
|
+
|
71
|
+
attr_reader :body, :heading
|
72
|
+
|
73
|
+
def initialize *value
|
74
|
+
value = value[0]
|
75
|
+
if value.is_a? Tuple
|
76
|
+
@heading = value.heading
|
77
|
+
@body = ImmutableSet.new value
|
78
|
+
elsif value.is_a? Heading
|
79
|
+
@heading = value
|
80
|
+
@body = ImmutableSet.new
|
81
|
+
elsif value.is_a? Relation
|
82
|
+
@heading = value.heading
|
83
|
+
@body = value.body
|
84
|
+
elsif value.is_a? Hash
|
85
|
+
@heading = Heading.new value
|
86
|
+
@body = ImmutableSet.new
|
87
|
+
elsif value.nil?
|
88
|
+
# oo... so you want an empty relation ?
|
89
|
+
@heading = Heading.new
|
90
|
+
@body = ImmutableSet.new
|
91
|
+
else
|
92
|
+
raise "Only accept Tuple,Heading,Relation or Hash types"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def add tuple
|
97
|
+
if tuple.is_a? Tuple
|
98
|
+
if tuple.heading == @heading
|
99
|
+
Relation.new(@heading).set_body(@body.add tuple)
|
100
|
+
else
|
101
|
+
throw 'its not of the same heading!'+", #{tuple.heading.inspect} != #{@heading.inspect}"
|
102
|
+
end
|
103
|
+
else
|
104
|
+
throw "Only tuples are supported"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def subset_of? other_relation
|
109
|
+
@body.subset? other_relation.get_body
|
110
|
+
end
|
111
|
+
|
112
|
+
def proper_subset_of? other_relation
|
113
|
+
@body.proper_subset_of? other_relation.get_body
|
114
|
+
end
|
115
|
+
|
116
|
+
def superset_of? other_relation
|
117
|
+
@body.superset_of? other_relation.get_body
|
118
|
+
end
|
119
|
+
|
120
|
+
def proper_superset_of? other_relation
|
121
|
+
@body.proper_superset_of? other_relation.get_body
|
122
|
+
end
|
123
|
+
|
124
|
+
def union other_relation
|
125
|
+
if self.heading == other_relation.heading
|
126
|
+
new_body = self.get_body.union(other_relation.get_body)
|
127
|
+
new_relation = Relation.new self.heading
|
128
|
+
new_relation.set_body new_body
|
129
|
+
return new_relation
|
130
|
+
else
|
131
|
+
throw "Not of the same heading"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def intersect other_relation
|
136
|
+
if self.heading == other_relation.heading
|
137
|
+
new_body = self.get_body.intersect other_relation.get_body
|
138
|
+
new_relation = Relation.new self.heading
|
139
|
+
new_relation.set_body new_body
|
140
|
+
return new_relation
|
141
|
+
else
|
142
|
+
throw "Not of the same heading"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def complement other_relation
|
147
|
+
if self.heading == other_relation.heading
|
148
|
+
new_body = self.get_body.complement other_relation.get_body
|
149
|
+
new_relation = Relation.new self.heading
|
150
|
+
new_relation.set_body new_body
|
151
|
+
return new_relation
|
152
|
+
else
|
153
|
+
throw "Not of the same heading"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
|
159
|
+
def project *args
|
160
|
+
|
161
|
+
new_heading = Heading.new
|
162
|
+
|
163
|
+
args.each do |symbol|
|
164
|
+
if self.heading[symbol].nil?
|
165
|
+
throw "no attribute with the name #{symbol}"
|
166
|
+
end
|
167
|
+
|
168
|
+
new_heading = new_heading.add(self.heading[symbol])
|
169
|
+
end
|
170
|
+
|
171
|
+
if new_heading.count == 0
|
172
|
+
return Relation.new new_heading
|
173
|
+
end
|
174
|
+
|
175
|
+
new_relation = Relation.new new_heading
|
176
|
+
@body.each do |tuple|
|
177
|
+
new_tuple = Tuple.new
|
178
|
+
|
179
|
+
args.each do |symbol|
|
180
|
+
new_tuple = new_tuple.add(symbol => tuple[symbol])
|
181
|
+
end
|
182
|
+
|
183
|
+
new_relation = new_relation.add new_tuple
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
|
188
|
+
return new_relation
|
189
|
+
end
|
190
|
+
|
191
|
+
def project_all_but *args
|
192
|
+
|
193
|
+
new_heading = Heading.new self.heading
|
194
|
+
|
195
|
+
args.each do |symbol|
|
196
|
+
if self.heading[symbol].nil?
|
197
|
+
throw "no attribute with the name #{symbol}"
|
198
|
+
end
|
199
|
+
|
200
|
+
new_heading = new_heading.remove symbol
|
201
|
+
end
|
202
|
+
|
203
|
+
if new_heading.count == 0
|
204
|
+
return Relation.new new_heading
|
205
|
+
end
|
206
|
+
|
207
|
+
new_relation = Relation.new new_heading
|
208
|
+
@body.each do |tuple|
|
209
|
+
new_tuple = Tuple.new
|
210
|
+
|
211
|
+
new_heading.each do |attribute|
|
212
|
+
new_tuple = new_tuple.add(attribute => tuple[attribute.name])
|
213
|
+
end
|
214
|
+
|
215
|
+
new_relation = new_relation.add new_tuple
|
216
|
+
end
|
217
|
+
|
218
|
+
return new_relation
|
219
|
+
end
|
220
|
+
|
221
|
+
def rename from,to
|
222
|
+
new_relation = Relation.new self.heading.rename(from,to)
|
223
|
+
self.each do |tuple|
|
224
|
+
new_relation = new_relation.add tuple.rename(from,to)
|
225
|
+
end
|
226
|
+
|
227
|
+
new_relation
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
|
232
|
+
def count
|
233
|
+
@body.size
|
234
|
+
end
|
235
|
+
|
236
|
+
def size
|
237
|
+
self.count
|
238
|
+
end
|
239
|
+
|
240
|
+
def length
|
241
|
+
self.count
|
242
|
+
end
|
243
|
+
|
244
|
+
def each &block
|
245
|
+
@body.each do |value|
|
246
|
+
block.call value
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
|
251
|
+
def select &block
|
252
|
+
new_relation = Relation.new self.heading
|
253
|
+
self.each do |tuple|
|
254
|
+
new_relation = new_relation.add(tuple) if block.call(tuple)
|
255
|
+
end
|
256
|
+
|
257
|
+
new_relation
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
def join other_relation,&block
|
262
|
+
new_relation = Relation.new(self.heading.add(other_relation.heading))
|
263
|
+
self.each do |tuple|
|
264
|
+
other_relation.each do |tuple2|
|
265
|
+
tuple3 = tuple.add(tuple2)
|
266
|
+
new_relation = new_relation.add(tuple3) if block.call(tuple3)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
new_relation
|
271
|
+
end
|
272
|
+
|
273
|
+
def cartesian_product other_relation
|
274
|
+
self.join other_relation do |tuple|
|
275
|
+
true
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def natrual_join other_relation
|
280
|
+
|
281
|
+
the_same = []
|
282
|
+
|
283
|
+
self.heading.each do |attribute|
|
284
|
+
other_relation.heading.each do |attribute2|
|
285
|
+
if attribute == attribute2
|
286
|
+
the_same.push attribute
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
old_and_new_name = {}
|
292
|
+
the_same.each do |value|
|
293
|
+
other_relation = other_relation.rename(value.name,value.name+"_2")
|
294
|
+
old_and_new_name[value.name] = value.name+"_2"
|
295
|
+
end
|
296
|
+
|
297
|
+
to_return = self.join(other_relation) do |tuple|
|
298
|
+
|
299
|
+
r = true
|
300
|
+
|
301
|
+
old_and_new_name.each do |name,name2|
|
302
|
+
unless tuple[name] == tuple[name2]
|
303
|
+
r = false
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
r
|
308
|
+
end
|
309
|
+
|
310
|
+
|
311
|
+
to_return = to_return.project_all_but *(old_and_new_name.values)
|
312
|
+
|
313
|
+
to_return
|
314
|
+
end
|
315
|
+
|
316
|
+
|
317
|
+
def summarize column_names, &block
|
318
|
+
|
319
|
+
column_names = [column_names] unless column_names.is_a? Array
|
320
|
+
|
321
|
+
new_tuples = []
|
322
|
+
|
323
|
+
self.group(column_names,"temp_12345").each do |tuple|
|
324
|
+
new_tuples.push(block.call(tuple.remove("temp_12345"),tuple['temp_12345']))
|
325
|
+
end
|
326
|
+
|
327
|
+
to_return = Relation.new new_tuples.first.heading
|
328
|
+
|
329
|
+
new_tuples.each do |tuple|
|
330
|
+
to_return = to_return.add tuple
|
331
|
+
end
|
332
|
+
|
333
|
+
to_return
|
334
|
+
end
|
335
|
+
|
336
|
+
|
337
|
+
def ungroup column_name
|
338
|
+
|
339
|
+
new_tuples = []
|
340
|
+
self.each do |tuple|
|
341
|
+
inner_relation = tuple[column_name]
|
342
|
+
tuple = tuple.remove column_name
|
343
|
+
inner_relation.each do |inner_tuple|
|
344
|
+
temp = tuple
|
345
|
+
inner_tuple.each do |attribute,value|
|
346
|
+
temp = temp.add({attribute => value})
|
347
|
+
new_tuples.push(temp)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
new_heading = self.heading.remove column_name
|
353
|
+
self.heading[column_name].type.each do |attribute|
|
354
|
+
new_heading = new_heading.add attribute
|
355
|
+
end
|
356
|
+
|
357
|
+
to_return = Relation.new new_heading
|
358
|
+
new_tuples.each do |tuple|
|
359
|
+
to_return = to_return.add tuple
|
360
|
+
end
|
361
|
+
|
362
|
+
to_return
|
363
|
+
end
|
364
|
+
|
365
|
+
def group column_names,new_column_name
|
366
|
+
|
367
|
+
# create the headings
|
368
|
+
new_heading = Heading.new
|
369
|
+
new_inner_heading = self.heading
|
370
|
+
|
371
|
+
column_names.each do |column_name|
|
372
|
+
throw "there is no #{column_name} in [#{self.heading.names.join(',')}]" if self.heading[column_name].nil?
|
373
|
+
new_heading = new_heading.add self.heading[column_name]
|
374
|
+
end
|
375
|
+
|
376
|
+
column_names.each do |column_name|
|
377
|
+
new_inner_heading = new_inner_heading.remove column_name
|
378
|
+
end
|
379
|
+
|
380
|
+
new_heading = new_heading.add(:name => new_column_name, :type => new_inner_heading)
|
381
|
+
|
382
|
+
# split the data
|
383
|
+
temp_data = {}
|
384
|
+
|
385
|
+
self.each do |tuple|
|
386
|
+
new_tuple = Tuple.new
|
387
|
+
new_inner_tuple = Tuple.new
|
388
|
+
|
389
|
+
new_heading.each do |attribute|
|
390
|
+
new_tuple = new_tuple.add(attribute => tuple[attribute]) unless attribute.name.to_s == new_column_name.to_s
|
391
|
+
end
|
392
|
+
|
393
|
+
new_inner_heading.each do |attribute|
|
394
|
+
new_inner_tuple = new_inner_tuple.add(attribute => tuple[attribute])
|
395
|
+
end
|
396
|
+
|
397
|
+
temp_data[new_tuple] ||= []
|
398
|
+
temp_data[new_tuple].push new_inner_tuple
|
399
|
+
end
|
400
|
+
|
401
|
+
|
402
|
+
# create the new realation
|
403
|
+
new_relation = Relation.new new_heading
|
404
|
+
|
405
|
+
temp_data.each do |tuple,array_of_tuples|
|
406
|
+
|
407
|
+
inner_relation = Relation.new new_inner_heading
|
408
|
+
|
409
|
+
array_of_tuples.each do |inner_tuple|
|
410
|
+
inner_relation = inner_relation.add(inner_tuple)
|
411
|
+
end
|
412
|
+
|
413
|
+
tuple = tuple.add(new_column_name => inner_relation)
|
414
|
+
new_relation = new_relation.add(tuple)
|
415
|
+
end
|
416
|
+
|
417
|
+
new_relation
|
418
|
+
end
|
419
|
+
|
420
|
+
def eql? other
|
421
|
+
self == other
|
422
|
+
end
|
423
|
+
|
424
|
+
def == other
|
425
|
+
if other.equal?(self)
|
426
|
+
true
|
427
|
+
elsif !self.class.equal?(other.class)
|
428
|
+
false
|
429
|
+
else
|
430
|
+
self.get_body.eql?(other.get_body)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
def hash
|
435
|
+
self.get_body.hash
|
436
|
+
end
|
437
|
+
|
438
|
+
protected
|
439
|
+
|
440
|
+
def set_body body
|
441
|
+
@body = body
|
442
|
+
self
|
443
|
+
end
|
444
|
+
|
445
|
+
def get_body
|
446
|
+
@body
|
447
|
+
end
|
448
|
+
|
449
|
+
|
450
|
+
end
|
451
|
+
|
452
|
+
|
453
|
+
|
454
|
+
|
455
|
+
|