keso 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|