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