xpflow 0.1b
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/bin/xpflow +96 -0
- data/lib/colorado.rb +198 -0
- data/lib/json/add/core.rb +243 -0
- data/lib/json/add/rails.rb +8 -0
- data/lib/json/common.rb +423 -0
- data/lib/json/editor.rb +1369 -0
- data/lib/json/ext.rb +28 -0
- data/lib/json/pure/generator.rb +442 -0
- data/lib/json/pure/parser.rb +320 -0
- data/lib/json/pure.rb +15 -0
- data/lib/json/version.rb +8 -0
- data/lib/json.rb +62 -0
- data/lib/mime/types.rb +881 -0
- data/lib/mime-types.rb +3 -0
- data/lib/restclient/abstract_response.rb +106 -0
- data/lib/restclient/exceptions.rb +193 -0
- data/lib/restclient/net_http_ext.rb +55 -0
- data/lib/restclient/payload.rb +235 -0
- data/lib/restclient/raw_response.rb +34 -0
- data/lib/restclient/request.rb +316 -0
- data/lib/restclient/resource.rb +169 -0
- data/lib/restclient/response.rb +24 -0
- data/lib/restclient.rb +174 -0
- data/lib/xpflow/bash.rb +341 -0
- data/lib/xpflow/bundle.rb +113 -0
- data/lib/xpflow/cmdline.rb +249 -0
- data/lib/xpflow/collection.rb +122 -0
- data/lib/xpflow/concurrency.rb +79 -0
- data/lib/xpflow/data.rb +393 -0
- data/lib/xpflow/dsl.rb +816 -0
- data/lib/xpflow/engine.rb +574 -0
- data/lib/xpflow/ensemble.rb +135 -0
- data/lib/xpflow/events.rb +56 -0
- data/lib/xpflow/experiment.rb +65 -0
- data/lib/xpflow/exts/facter.rb +30 -0
- data/lib/xpflow/exts/g5k.rb +931 -0
- data/lib/xpflow/exts/g5k_use.rb +50 -0
- data/lib/xpflow/exts/gui.rb +140 -0
- data/lib/xpflow/exts/model.rb +155 -0
- data/lib/xpflow/graph.rb +1603 -0
- data/lib/xpflow/graph_xpflow.rb +251 -0
- data/lib/xpflow/import.rb +196 -0
- data/lib/xpflow/library.rb +349 -0
- data/lib/xpflow/logging.rb +153 -0
- data/lib/xpflow/manager.rb +147 -0
- data/lib/xpflow/nodes.rb +1250 -0
- data/lib/xpflow/runs.rb +773 -0
- data/lib/xpflow/runtime.rb +125 -0
- data/lib/xpflow/scope.rb +168 -0
- data/lib/xpflow/ssh.rb +186 -0
- data/lib/xpflow/stat.rb +50 -0
- data/lib/xpflow/stdlib.rb +381 -0
- data/lib/xpflow/structs.rb +369 -0
- data/lib/xpflow/taktuk.rb +193 -0
- data/lib/xpflow/templates/ssh-config.basic +14 -0
- data/lib/xpflow/templates/ssh-config.inria +18 -0
- data/lib/xpflow/templates/ssh-config.proxy +13 -0
- data/lib/xpflow/templates/taktuk +6590 -0
- data/lib/xpflow/templates/utils/batch +4 -0
- data/lib/xpflow/templates/utils/bootstrap +12 -0
- data/lib/xpflow/templates/utils/hostname +3 -0
- data/lib/xpflow/templates/utils/ping +3 -0
- data/lib/xpflow/templates/utils/rsync +12 -0
- data/lib/xpflow/templates/utils/scp +17 -0
- data/lib/xpflow/templates/utils/scp_many +8 -0
- data/lib/xpflow/templates/utils/ssh +3 -0
- data/lib/xpflow/templates/utils/ssh-interactive +4 -0
- data/lib/xpflow/templates/utils/taktuk +19 -0
- data/lib/xpflow/threads.rb +187 -0
- data/lib/xpflow/utils.rb +569 -0
- data/lib/xpflow/visual.rb +230 -0
- data/lib/xpflow/with_g5k.rb +7 -0
- data/lib/xpflow.rb +349 -0
- metadata +135 -0
@@ -0,0 +1,122 @@
|
|
1
|
+
|
2
|
+
# stuff related to collecting execution results
|
3
|
+
|
4
|
+
module XPFlow
|
5
|
+
|
6
|
+
class FileResult
|
7
|
+
|
8
|
+
attr_reader :filename
|
9
|
+
attr_reader :info
|
10
|
+
|
11
|
+
def initialize(filename, info)
|
12
|
+
@filename = filename
|
13
|
+
@info = info
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
class Collection
|
19
|
+
|
20
|
+
attr_reader :path
|
21
|
+
|
22
|
+
def initialize(path)
|
23
|
+
@results = {}
|
24
|
+
@files = {}
|
25
|
+
@path = path
|
26
|
+
end
|
27
|
+
|
28
|
+
def create(subdir)
|
29
|
+
return Collection.new(File.join(@path, subdir))
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_path()
|
33
|
+
if File.directory?(@path)
|
34
|
+
FileUtils.remove_entry_secure(@path)
|
35
|
+
end
|
36
|
+
Dir.mkdir(@path)
|
37
|
+
end
|
38
|
+
|
39
|
+
def collect_result(key, result)
|
40
|
+
@results[key] = result
|
41
|
+
end
|
42
|
+
|
43
|
+
def save_all
|
44
|
+
index = 0
|
45
|
+
@results.each do |key, result|
|
46
|
+
prefix = File.join(@path, key.to_s)
|
47
|
+
if result.is_a?(ManyExecutionResult)
|
48
|
+
files = save_manyresult(result, prefix)
|
49
|
+
@files[key] = files
|
50
|
+
else
|
51
|
+
raise "Can't collect results of type #{result.class}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def save_manyresult(result, prefix)
|
57
|
+
summary = "#{prefix}-summary.yaml"
|
58
|
+
info = []
|
59
|
+
files = []
|
60
|
+
pad = result.length.to_s.length
|
61
|
+
counter = 1
|
62
|
+
result.to_list.each do |r|
|
63
|
+
basename = "#{counter.to_s.rjust(pad, '0')}"
|
64
|
+
stdout_file = "#{basename}.stdout"
|
65
|
+
stderr_file = "#{basename}.stderr"
|
66
|
+
stdout_file = "#{prefix}-#{stdout_file}"
|
67
|
+
stderr_file = "#{prefix}-#{stderr_file}"
|
68
|
+
r.save_stdout(stdout_file)
|
69
|
+
r.save_stderr(stderr_file)
|
70
|
+
node = r.node
|
71
|
+
this_info = {
|
72
|
+
# TODO: add more data, e.g., provenance, real paths?
|
73
|
+
:stdout => stdout_file,
|
74
|
+
:stderr => stderr_file,
|
75
|
+
:host => node.host,
|
76
|
+
:user => node.user
|
77
|
+
}
|
78
|
+
info.push(this_info)
|
79
|
+
files.push(FileResult.new(stdout_file, this_info))
|
80
|
+
counter += 1
|
81
|
+
end
|
82
|
+
IO.write(summary, info.to_yaml)
|
83
|
+
return files
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
class CollectionLibrary < SyncedActivityLibrary
|
89
|
+
|
90
|
+
activities :collect_result, :save_all_results,
|
91
|
+
:get_files, :transform_to_float,
|
92
|
+
:result_collection
|
93
|
+
|
94
|
+
def setup
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
def result_collection
|
99
|
+
return Scope.current[:__collection__]
|
100
|
+
end
|
101
|
+
|
102
|
+
def collect_result(key, result)
|
103
|
+
return result_collection.collect_result(key, result)
|
104
|
+
end
|
105
|
+
|
106
|
+
def save_all_results
|
107
|
+
return result_collection().save_all()
|
108
|
+
end
|
109
|
+
|
110
|
+
def transform_to_float(results, script)
|
111
|
+
files = @files[results]
|
112
|
+
script = $files[script]
|
113
|
+
floats = files.map do |r|
|
114
|
+
x = proxy.run :"__core__.system", "#{script} #{r.filename}"
|
115
|
+
x.to_f
|
116
|
+
end
|
117
|
+
return ValueData.new(floats)
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
|
2
|
+
module XPFlow
|
3
|
+
|
4
|
+
class Semaphore
|
5
|
+
|
6
|
+
def initialize(n)
|
7
|
+
@n = n
|
8
|
+
@mutex = Mutex.new
|
9
|
+
@cond = ConditionVariable.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def acquire
|
13
|
+
@mutex.synchronize do
|
14
|
+
while @n == 0
|
15
|
+
@cond.wait(@mutex)
|
16
|
+
end
|
17
|
+
@n -= 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def release
|
22
|
+
@mutex.synchronize do
|
23
|
+
@n += 1
|
24
|
+
@cond.signal
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def synchronize
|
29
|
+
begin
|
30
|
+
@mutex.acquire
|
31
|
+
yield
|
32
|
+
ensure
|
33
|
+
@mutex.release
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
class SyncQueue
|
40
|
+
|
41
|
+
attr_reader :q
|
42
|
+
|
43
|
+
def initialize
|
44
|
+
@q = []
|
45
|
+
@elements = Semaphore.new(0)
|
46
|
+
@mutex = Mutex.new
|
47
|
+
@cv = ConditionVariable.new # empty queue
|
48
|
+
end
|
49
|
+
|
50
|
+
def push(x)
|
51
|
+
@mutex.synchronize do
|
52
|
+
@q.push(x)
|
53
|
+
end
|
54
|
+
@elements.release
|
55
|
+
end
|
56
|
+
|
57
|
+
def pop
|
58
|
+
@elements.acquire
|
59
|
+
return @mutex.synchronize do
|
60
|
+
x = @q.shift
|
61
|
+
@cv.broadcast if @q.length == 0
|
62
|
+
x = yield(x) if block_given?
|
63
|
+
x
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# waits for the queue to be empty
|
68
|
+
|
69
|
+
def wait_empty
|
70
|
+
@mutex.synchronize do
|
71
|
+
while @q.length > 0
|
72
|
+
@cv.wait(@mutex)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
data/lib/xpflow/data.rb
ADDED
@@ -0,0 +1,393 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
#
|
4
|
+
# Data collection classes.
|
5
|
+
#
|
6
|
+
|
7
|
+
module XPFlow
|
8
|
+
|
9
|
+
class ValueData
|
10
|
+
|
11
|
+
attr_reader :values
|
12
|
+
|
13
|
+
def initialize(vals = nil)
|
14
|
+
vals = [] if vals.nil?
|
15
|
+
@values = vals
|
16
|
+
end
|
17
|
+
|
18
|
+
def push(x)
|
19
|
+
@values.push(x)
|
20
|
+
end
|
21
|
+
|
22
|
+
def ==(x)
|
23
|
+
return (@values == x) if x.is_a?(Array)
|
24
|
+
return (@values == x.values)
|
25
|
+
end
|
26
|
+
|
27
|
+
def average
|
28
|
+
return @values.reduce(:+).to_f / @values.length
|
29
|
+
end
|
30
|
+
|
31
|
+
def average_variance
|
32
|
+
raise 'The variance computation for sample with less than 2 elements is impossible' if @values.length < 2
|
33
|
+
m = average()
|
34
|
+
s = @values.map { |x| (x - m)**2 }.reduce(:+)
|
35
|
+
return [ m, s / (@values.length - 1) ]
|
36
|
+
end
|
37
|
+
|
38
|
+
def variance
|
39
|
+
return average_variance().last
|
40
|
+
end
|
41
|
+
|
42
|
+
def average_stddev
|
43
|
+
m, v = average_variance()
|
44
|
+
return m, v ** 0.5
|
45
|
+
end
|
46
|
+
|
47
|
+
def stddev
|
48
|
+
return average_stddev().last
|
49
|
+
end
|
50
|
+
|
51
|
+
#Credit for cdf_inverse : http://home.online.no/~pjacklam/notes/invnorm/
|
52
|
+
# inverse standard normal cumulative distribution function
|
53
|
+
def cdf_inverse(p)
|
54
|
+
a = [0, -3.969683028665376e+01, 2.209460984245205e+02, -2.759285104469687e+02, 1.383577518672690e+02, -3.066479806614716e+01, 2.506628277459239e+00]
|
55
|
+
b = [0, -5.447609879822406e+01, 1.615858368580409e+02, -1.556989798598866e+02, 6.680131188771972e+01, -1.328068155288572e+01]
|
56
|
+
c = [0, -7.784894002430293e-03, -3.223964580411365e-01, -2.400758277161838e+00, -2.549732539343734e+00, 4.374664141464968e+00, 2.938163982698783e+00]
|
57
|
+
d = [0, 7.784695709041462e-03, 3.224671290700398e-01, 2.445134137142996e+00, 3.754408661907416e+00]
|
58
|
+
#Define break-points.
|
59
|
+
p_low = 0.02425
|
60
|
+
p_high = 1.0 - p_low
|
61
|
+
|
62
|
+
x = 0.0
|
63
|
+
q = 0.0
|
64
|
+
#Rational approximation for lower region.
|
65
|
+
if 0.0 < p && p < p_low
|
66
|
+
q = Math.sqrt(-2.0*Math.log(p))
|
67
|
+
x = (((((c[1]*q+c[2])*q+c[3])*q+c[4])*q+c[5])*q+c[6]) / ((((d[1]*q+d[2])*q+d[3])*q+d[4])*q+1.0)
|
68
|
+
|
69
|
+
#Rational approximation for central region.
|
70
|
+
elsif p_low <= p && p <= p_high
|
71
|
+
q = p - 0.5
|
72
|
+
r = q*q
|
73
|
+
x = (((((a[1]*r+a[2])*r+a[3])*r+a[4])*r+a[5])*r+a[6])*q / (((((b[1]*r+b[2])*r+b[3])*r+b[4])*r+b[5])*r+1.0)
|
74
|
+
|
75
|
+
#Rational approximation for upper region.
|
76
|
+
elsif p_high < p && p < 1.0
|
77
|
+
q = Math.sqrt(-2.0*Math.log(1.0-p))
|
78
|
+
x = -(((((c[1]*q+c[2])*q+c[3])*q+c[4])*q+c[5])*q+c[6]) / ((((d[1]*q+d[2])*q+d[3])*q+d[4])*q+1.0)
|
79
|
+
end
|
80
|
+
|
81
|
+
#The relative error of the approximation has
|
82
|
+
#absolute value less than 1.15 × 10−9. One iteration of
|
83
|
+
#Halley’s rational method (third order) gives full machine precision.
|
84
|
+
if 0 < p && p < 1
|
85
|
+
e = 0.5 * Math.erfc(-x/Math.sqrt(2.0)) - p
|
86
|
+
u = e * Math.sqrt(2.0*Math::PI) * Math.exp((x**2.0)/2.0)
|
87
|
+
x = x - u/(1.0 + x*u/2.0)
|
88
|
+
end
|
89
|
+
x
|
90
|
+
end
|
91
|
+
def minimal_sample_prel(prec,confidance)
|
92
|
+
minimal_sample_both(prec,nil,confidance)
|
93
|
+
end
|
94
|
+
def minimal_sample_pabs(prec,confidance)
|
95
|
+
minimal_sample_both(nil,prec,condidance)
|
96
|
+
end
|
97
|
+
#vectors is values and prec and confidence is in percentage
|
98
|
+
def minimal_sample_both(prec_rel,prec,confidence)
|
99
|
+
avg = average
|
100
|
+
prec = prec_rel.nil? ? prec : avg*prec_rel
|
101
|
+
critical_value=cdf_inverse((1-confidence)/2)
|
102
|
+
((critical_value*stddev() / prec) ** 2).to_i + 1
|
103
|
+
end
|
104
|
+
|
105
|
+
TSTUDENT = [
|
106
|
+
nil, 12.71, 4.303, 3.182, 2.776, 2.571, 2.447, 2.365, 2.306, 2.262, 2.228,
|
107
|
+
2.201, 2.179, 2.160, 2.145, 2.131, 2.120, 2.110, 2.101, 2.093, 2.086, 2.080,
|
108
|
+
2.074, 2.069, 2.064, 2.060, 2.056, 2.052, 2.048, 2.045, 2.042, 2.021, 2.009,
|
109
|
+
2.000, 1.990, 1.984, 1.980, 1.960 ]
|
110
|
+
|
111
|
+
def confidence_interval
|
112
|
+
if size() >= TSTUDENT.length
|
113
|
+
factor = TSTUDENT[-1]
|
114
|
+
else
|
115
|
+
factor = TSTUDENT[size()]
|
116
|
+
end
|
117
|
+
m, s = average_stddev()
|
118
|
+
d = (factor * s).to_f / (@values.length ** 0.5)
|
119
|
+
return [m - d, m + d]
|
120
|
+
end
|
121
|
+
|
122
|
+
def confidence_precision
|
123
|
+
a, b = confidence_interval()
|
124
|
+
return (b - a) * 0.5
|
125
|
+
end
|
126
|
+
|
127
|
+
def confidence_ratio
|
128
|
+
# provides conf. as a percentage around the estimated mean value
|
129
|
+
prec = confidence_precision()
|
130
|
+
mean = average().abs
|
131
|
+
return (prec / mean)
|
132
|
+
end
|
133
|
+
|
134
|
+
def conf_ratio
|
135
|
+
return confidence_ratio
|
136
|
+
end
|
137
|
+
|
138
|
+
def map(&block)
|
139
|
+
arr = @values.map(&block)
|
140
|
+
return ValueData.new(arr)
|
141
|
+
end
|
142
|
+
|
143
|
+
def sort
|
144
|
+
return ValueData.new(@values.sort)
|
145
|
+
end
|
146
|
+
|
147
|
+
def to_s
|
148
|
+
return "<Data: #{@values.inspect}>"
|
149
|
+
end
|
150
|
+
|
151
|
+
def sum
|
152
|
+
return @values.reduce(:+)
|
153
|
+
end
|
154
|
+
|
155
|
+
def size
|
156
|
+
return @values.length
|
157
|
+
end
|
158
|
+
|
159
|
+
def length
|
160
|
+
return size()
|
161
|
+
end
|
162
|
+
|
163
|
+
def append(x)
|
164
|
+
return ValueData.new(self.values + [ x ])
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
class NamedTuple
|
170
|
+
|
171
|
+
def initialize(base, array)
|
172
|
+
@base = base
|
173
|
+
@array = array
|
174
|
+
end
|
175
|
+
|
176
|
+
def [](label)
|
177
|
+
label = @base.unlabel(label) unless label.is_a?(Fixnum)
|
178
|
+
return @array[label]
|
179
|
+
end
|
180
|
+
|
181
|
+
def value(idx)
|
182
|
+
return @array[idx]
|
183
|
+
end
|
184
|
+
|
185
|
+
def values(idxs)
|
186
|
+
return idxs.map { |i| @array[i] }
|
187
|
+
end
|
188
|
+
|
189
|
+
def method_missing(name, *args)
|
190
|
+
idx = @base.unlabel(name)
|
191
|
+
raise NoMethodError.new("undefined method '#{name}'", name) if (idx.nil? or args.length != 0)
|
192
|
+
return @array[idx]
|
193
|
+
end
|
194
|
+
|
195
|
+
def to_s
|
196
|
+
labels = @base.labels
|
197
|
+
s = @array.each_with_index.map { |el, i|
|
198
|
+
"#{labels[i]}=#{el}"
|
199
|
+
}.join(', ')
|
200
|
+
return "<#{s}>"
|
201
|
+
end
|
202
|
+
|
203
|
+
def to_a
|
204
|
+
return @array
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
class NamedTupleBase
|
210
|
+
|
211
|
+
attr_reader :labels
|
212
|
+
|
213
|
+
def initialize(labels)
|
214
|
+
@labels = labels
|
215
|
+
end
|
216
|
+
|
217
|
+
def build(row)
|
218
|
+
return row if row.is_a?(NamedTuple)
|
219
|
+
if row.is_a?(Hash)
|
220
|
+
row = @labels.map { |l| row[l] }
|
221
|
+
elsif row.is_a?(Array)
|
222
|
+
else
|
223
|
+
raise
|
224
|
+
end
|
225
|
+
return NamedTuple.new(self, row)
|
226
|
+
end
|
227
|
+
|
228
|
+
def unlabel(label)
|
229
|
+
return @labels.index(label)
|
230
|
+
end
|
231
|
+
|
232
|
+
def unlabels(labs)
|
233
|
+
return labs.map { |l| unlabel(l) }
|
234
|
+
end
|
235
|
+
|
236
|
+
def split_labels(labs)
|
237
|
+
left = []
|
238
|
+
right = []
|
239
|
+
@labels.each_with_index { |l, i|
|
240
|
+
idx = labs.index(l)
|
241
|
+
if idx.nil?
|
242
|
+
right.push(l)
|
243
|
+
else
|
244
|
+
left.push([idx, l])
|
245
|
+
end
|
246
|
+
}
|
247
|
+
left = left.sort.map { |x| x.last }
|
248
|
+
return [left, right]
|
249
|
+
end
|
250
|
+
|
251
|
+
def to_s
|
252
|
+
s = @labels.map { |x| x.to_s }.join(', ')
|
253
|
+
return "<Base: #{s}>"
|
254
|
+
end
|
255
|
+
|
256
|
+
end
|
257
|
+
|
258
|
+
class RowData
|
259
|
+
|
260
|
+
def initialize(labs, rows = nil)
|
261
|
+
rows = [] if rows.nil?
|
262
|
+
@base = NamedTupleBase.new(labs)
|
263
|
+
@rows = rows
|
264
|
+
end
|
265
|
+
|
266
|
+
def labels
|
267
|
+
return @base.labels
|
268
|
+
end
|
269
|
+
|
270
|
+
def duplicate(rows = nil)
|
271
|
+
return RowData.new(@base.labels, rows)
|
272
|
+
end
|
273
|
+
|
274
|
+
def length
|
275
|
+
return @rows.length
|
276
|
+
end
|
277
|
+
|
278
|
+
def width
|
279
|
+
return @base.labels.length
|
280
|
+
end
|
281
|
+
|
282
|
+
def push(row)
|
283
|
+
row = @base.build(row)
|
284
|
+
@rows.push(row)
|
285
|
+
end
|
286
|
+
|
287
|
+
def append(rows)
|
288
|
+
rows.each { |r| push(r) }
|
289
|
+
end
|
290
|
+
|
291
|
+
def column(label)
|
292
|
+
values = @rows.map { |row| row[label] }
|
293
|
+
return ValueData.new(values)
|
294
|
+
end
|
295
|
+
|
296
|
+
def _group(labs, key_f = nil)
|
297
|
+
# groups data by labels; returns hash
|
298
|
+
labs = @base.unlabels(labs)
|
299
|
+
groups = {}
|
300
|
+
@rows.each { |r|
|
301
|
+
key = r.values(labs)
|
302
|
+
key = key_f.call(key) unless key_f.nil?
|
303
|
+
groups[key] = duplicate() unless groups.key?(key)
|
304
|
+
groups[key].push(r)
|
305
|
+
}
|
306
|
+
return groups
|
307
|
+
end
|
308
|
+
|
309
|
+
def _select(labs)
|
310
|
+
rows = RowData.new(labs)
|
311
|
+
labs = @base.unlabels(labs)
|
312
|
+
@rows.each { |row| rows.push(row.values(labs)) }
|
313
|
+
return rows
|
314
|
+
end
|
315
|
+
|
316
|
+
def _cluster(labs, key_f = nil)
|
317
|
+
groups = _group(labs, key_f)
|
318
|
+
left, right = @base.split_labels(labs)
|
319
|
+
h = groups.map { |k, v| [ k, v._select(right) ] }
|
320
|
+
return Hash[h]
|
321
|
+
end
|
322
|
+
|
323
|
+
def _sort(&block)
|
324
|
+
rows = @rows.clone() # shallow copy of the rows
|
325
|
+
rows.sort!(&block)
|
326
|
+
return duplicate(rows)
|
327
|
+
end
|
328
|
+
|
329
|
+
def _filter(&block)
|
330
|
+
rows = @rows.select(&block)
|
331
|
+
return duplicate(rows)
|
332
|
+
end
|
333
|
+
|
334
|
+
def _map(&block)
|
335
|
+
@rows.map(&block)
|
336
|
+
end
|
337
|
+
|
338
|
+
def _expand(new_labels, &block)
|
339
|
+
labs = @base.labels + new_labels
|
340
|
+
rows = RowData.new(labs)
|
341
|
+
@rows.each { |r|
|
342
|
+
ext = block.call(r)
|
343
|
+
ext = [ext] unless ext.is_a?(Array)
|
344
|
+
rows.push(r.to_a + ext)
|
345
|
+
}
|
346
|
+
return rows
|
347
|
+
end
|
348
|
+
|
349
|
+
def _discard(old_labels)
|
350
|
+
labs = @base.labels - old_labels
|
351
|
+
rows = RowData.new(labs)
|
352
|
+
idxs = @base.unlabels(labs)
|
353
|
+
@rows.each { |r|
|
354
|
+
rows.push(r.values(idxs))
|
355
|
+
}
|
356
|
+
return rows
|
357
|
+
end
|
358
|
+
|
359
|
+
def to_s
|
360
|
+
s = @rows.map { |el| el.to_s }.join(", ")
|
361
|
+
return "[ #{s} ]"
|
362
|
+
end
|
363
|
+
|
364
|
+
### USER FRIENDLY PART
|
365
|
+
|
366
|
+
def self.create(*labs)
|
367
|
+
return RowData.new(labs)
|
368
|
+
end
|
369
|
+
|
370
|
+
def collect(*row)
|
371
|
+
row = row.first if row.length == 1 and row.first.is_a?(Hash)
|
372
|
+
push(row)
|
373
|
+
end
|
374
|
+
|
375
|
+
def table
|
376
|
+
return @rows.map { |r| r.to_a }
|
377
|
+
end
|
378
|
+
|
379
|
+
def sort(&block); _sort(&block) end
|
380
|
+
def select(*labs); _select(labs) end
|
381
|
+
def group(*labs); _group(labs) end
|
382
|
+
def group_one(label); _group([label], lambda { |k| k.first }) end
|
383
|
+
def cluster(*labs); _cluster(labs) end
|
384
|
+
def cluster_one(label); _cluster([label], lambda { |k| k.first }) end
|
385
|
+
def filter(&block); _filter(&block) end
|
386
|
+
def map(&block); _map(&block) end
|
387
|
+
def expand(*labs, &block); _expand(labs, &block) end
|
388
|
+
def discard(*labs); _discard(labs) end
|
389
|
+
|
390
|
+
end
|
391
|
+
|
392
|
+
end
|
393
|
+
|