rust 0.7 → 0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/ruby-rust +3 -0
- data/lib/{rust-csv.rb → rust/core/csv.rb} +2 -1
- data/lib/rust/core/rust.rb +157 -0
- data/lib/rust/core/types/all.rb +4 -0
- data/lib/{rust-core.rb → rust/core/types/dataframe.rb} +17 -335
- data/lib/rust/core/types/datatype.rb +161 -0
- data/lib/rust/core/types/factor.rb +131 -0
- data/lib/rust/core/types/language.rb +166 -0
- data/lib/rust/core/types/list.rb +81 -0
- data/lib/rust/core/types/matrix.rb +132 -0
- data/lib/rust/core/types/s4class.rb +59 -0
- data/lib/rust/core/types/utils.rb +109 -0
- data/lib/rust/core.rb +7 -0
- data/lib/rust/models/all.rb +4 -0
- data/lib/rust/models/anova.rb +60 -0
- data/lib/rust/models/regression.rb +205 -0
- data/lib/rust/plots/all.rb +4 -0
- data/lib/rust/plots/basic-plots.rb +111 -0
- data/lib/{rust-plots.rb → rust/plots/core.rb} +1 -169
- data/lib/rust/plots/distribution-plots.rb +62 -0
- data/lib/rust/stats/all.rb +4 -0
- data/lib/{rust-basics.rb → rust/stats/correlation.rb} +2 -2
- data/lib/{rust-descriptive.rb → rust/stats/descriptive.rb} +24 -4
- data/lib/{rust-effsize.rb → rust/stats/effsize.rb} +7 -13
- data/lib/{rust-probabilities.rb → rust/stats/probabilities.rb} +1 -1
- data/lib/{rust-tests.rb → rust/stats/tests.rb} +84 -90
- data/lib/rust.rb +4 -9
- metadata +31 -13
- data/lib/rust-calls.rb +0 -80
@@ -0,0 +1,161 @@
|
|
1
|
+
require_relative '../rust'
|
2
|
+
|
3
|
+
module Rust
|
4
|
+
class RustDatatype
|
5
|
+
def self.pull_variable(variable, forced_interpreter = nil)
|
6
|
+
r_type = Rust._pull("as.character(typeof(#{variable}))")
|
7
|
+
r_class = Rust._pull("as.character(class(#{variable}))")
|
8
|
+
|
9
|
+
if forced_interpreter
|
10
|
+
raise ArgumentError, "Expected null or class as forced_interpreter" if forced_interpreter && !forced_interpreter.is_a?(Class)
|
11
|
+
raise ArgumentError, "Class #{forced_interpreter} can not handle type #{r_type}, class #{r_class}" unless forced_interpreter.can_pull?(r_type, r_class)
|
12
|
+
|
13
|
+
return forced_interpreter.pull_variable(variable, r_type, r_class)
|
14
|
+
end
|
15
|
+
|
16
|
+
candidates = []
|
17
|
+
ObjectSpace.each_object(Class) do |type|
|
18
|
+
if type < RustDatatype
|
19
|
+
if type.can_pull?(r_type, r_class)
|
20
|
+
candidates << type
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
if candidates.size > 0
|
26
|
+
type = candidates.max_by { |c| c.pull_priority }
|
27
|
+
|
28
|
+
puts "Using #{type} to pull #{variable}" if Rust.debug?
|
29
|
+
return type.pull_variable(variable, r_type, r_class)
|
30
|
+
else
|
31
|
+
if Rust._pull("length(#{variable})") == 0
|
32
|
+
return []
|
33
|
+
else
|
34
|
+
return Rust._pull(variable)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.pull_priority
|
40
|
+
0
|
41
|
+
end
|
42
|
+
|
43
|
+
def load_in_r_as(variable_name)
|
44
|
+
raise "Loading #{self.class} in R was not implemented"
|
45
|
+
end
|
46
|
+
|
47
|
+
def r_mirror_to(other_variable)
|
48
|
+
varname = self.mirrored_R_variable_name
|
49
|
+
|
50
|
+
Rust._eval("#{varname} = #{other_variable}")
|
51
|
+
Rust["#{varname}.hash"] = self.r_hash
|
52
|
+
|
53
|
+
return varname
|
54
|
+
end
|
55
|
+
|
56
|
+
def r_mirror
|
57
|
+
varname = self.mirrored_R_variable_name
|
58
|
+
|
59
|
+
if !Rust._pull("exists(\"#{varname}\")") || Rust._pull("#{varname}.hash") != self.r_hash
|
60
|
+
puts "Loading #{varname}" if Rust.debug?
|
61
|
+
Rust[varname] = self
|
62
|
+
Rust["#{varname}.hash"] = self.r_hash
|
63
|
+
else
|
64
|
+
puts "Using cached value for #{varname}" if Rust.debug?
|
65
|
+
end
|
66
|
+
|
67
|
+
return varname
|
68
|
+
end
|
69
|
+
|
70
|
+
def r_hash
|
71
|
+
self.hash.to_s
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
def mirrored_R_variable_name
|
76
|
+
return "rust.mirrored.#{self.object_id}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class Null < RustDatatype
|
81
|
+
def self.can_pull?(type, klass)
|
82
|
+
return type == "NULL" && klass == "NULL"
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.pull_variable(variable, type, klass)
|
86
|
+
return nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class TrueClass
|
92
|
+
def to_R
|
93
|
+
"TRUE"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class FalseClass
|
98
|
+
def to_R
|
99
|
+
"FALSE"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Object
|
104
|
+
def to_R
|
105
|
+
raise TypeError, "Unsupported type for #{self.class}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class NilClass
|
110
|
+
def to_R
|
111
|
+
return "NULL"
|
112
|
+
end
|
113
|
+
|
114
|
+
def load_in_r_as(variable)
|
115
|
+
Rust._eval("#{variable} <- NULL")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class Numeric
|
120
|
+
def to_R
|
121
|
+
self.inspect
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class Float
|
126
|
+
def to_R
|
127
|
+
return self.nan? ? "NA" : super
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class Symbol
|
132
|
+
def to_R
|
133
|
+
return self.to_s.inspect
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class Array
|
138
|
+
def to_R
|
139
|
+
return "c(#{self.map { |e| e.to_R }.join(",")})"
|
140
|
+
end
|
141
|
+
|
142
|
+
def distribution
|
143
|
+
result = {}
|
144
|
+
self.each do |value|
|
145
|
+
result[value] = result[value].to_i + 1
|
146
|
+
end
|
147
|
+
return result
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class String
|
152
|
+
def to_R
|
153
|
+
return self.inspect
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class Range
|
158
|
+
def to_R
|
159
|
+
[range.min, range.max].to_R
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require_relative 'datatype'
|
2
|
+
|
3
|
+
module Rust
|
4
|
+
class Factor < RustDatatype
|
5
|
+
def self.can_pull?(type, klass)
|
6
|
+
return klass == "factor"
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.pull_variable(variable, type, klass)
|
10
|
+
levels = Rust["levels(#{variable})"]
|
11
|
+
values = Rust["as.integer(#{variable})"]
|
12
|
+
|
13
|
+
return Factor.new(values, levels)
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_in_r_as(variable_name)
|
17
|
+
Rust['tmp.levels'] = @levels.map { |v| v.to_s }
|
18
|
+
Rust['tmp.values'] = @values
|
19
|
+
|
20
|
+
Rust._eval("#{variable_name} <- factor(tmp.values, labels=tmp.levels)")
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(values, levels)
|
24
|
+
@levels = levels.map { |v| v.to_sym }
|
25
|
+
@values = values
|
26
|
+
end
|
27
|
+
|
28
|
+
def levels
|
29
|
+
@levels
|
30
|
+
end
|
31
|
+
|
32
|
+
def ==(other)
|
33
|
+
return false unless other.is_a?(Factor)
|
34
|
+
|
35
|
+
return @levels == other.levels && self.to_a == other.to_a
|
36
|
+
end
|
37
|
+
|
38
|
+
def [](i)
|
39
|
+
FactorValue.new(@values[i], @levels[@values[i] - 1])
|
40
|
+
end
|
41
|
+
|
42
|
+
def []=(i, value)
|
43
|
+
raise "The given value is outside the factor bounds" if value.is_a?(Integer) && (value < 1 || value > @levels.size)
|
44
|
+
|
45
|
+
if value.is_a?(FactorValue)
|
46
|
+
raise "Incompatible factor value, different levels used" unless @levels.include?(value.level) || @levels.index(value.level) + 1 == @value.value
|
47
|
+
value = value.value
|
48
|
+
end
|
49
|
+
|
50
|
+
if value.is_a?(String) || value.is_a?(Symbol)
|
51
|
+
value = value.to_sym
|
52
|
+
raise "Unsupported value #{value}; expected #{@levels.join(", ")}" unless @levels.include?(value)
|
53
|
+
|
54
|
+
value = @levels.index(value) + 1
|
55
|
+
end
|
56
|
+
|
57
|
+
@values[i] = value
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_a
|
61
|
+
@values.map { |v| FactorValue.new(v, @levels[v - 1]) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def method_missing(method, *args, &block)
|
65
|
+
raise NoMethodError, "Undefined method #{method} for Factor" if method.to_s.end_with?("!") || method.end_with?("=")
|
66
|
+
|
67
|
+
self.to_a.method(method).call(*args, &block)
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_s
|
71
|
+
self.to_a.to_s
|
72
|
+
end
|
73
|
+
|
74
|
+
def inspect
|
75
|
+
self.to_a.inspect
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class FactorValue
|
80
|
+
def initialize(value, level)
|
81
|
+
@value = value
|
82
|
+
@level = level
|
83
|
+
end
|
84
|
+
|
85
|
+
def value
|
86
|
+
@value
|
87
|
+
end
|
88
|
+
|
89
|
+
def level
|
90
|
+
@level
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_i
|
94
|
+
@value
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_sym
|
98
|
+
@level
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_R
|
102
|
+
self.to_i
|
103
|
+
end
|
104
|
+
|
105
|
+
def inspect
|
106
|
+
@level.inspect
|
107
|
+
end
|
108
|
+
|
109
|
+
def ==(other)
|
110
|
+
if other.is_a?(FactorValue)
|
111
|
+
@value == other.value && @level == other.level
|
112
|
+
elsif other.is_a?(Integer)
|
113
|
+
@value == other
|
114
|
+
elsif other.is_a?(Symbol)
|
115
|
+
@level == other
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def hash
|
120
|
+
@value.hash + @level.hash
|
121
|
+
end
|
122
|
+
|
123
|
+
def eql?(other)
|
124
|
+
return self == other
|
125
|
+
end
|
126
|
+
|
127
|
+
def method_missing(method, *args, &block)
|
128
|
+
@level.method(method).call(*args, &block)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require_relative 'datatype'
|
2
|
+
|
3
|
+
module Rust
|
4
|
+
class Formula < RustDatatype
|
5
|
+
def self.can_pull?(type, klass)
|
6
|
+
return klass == "formula" || (klass.is_a?(Array) && klass.include?("formula"))
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.pull_variable(variable, type, klass)
|
10
|
+
formula_elements = Rust._pull("as.character(#{variable})")
|
11
|
+
|
12
|
+
assert("The number of elements of a formula must be 2 or 3: #{formula_elements} given") { formula_elements.size > 1 && formula_elements.size < 4 }
|
13
|
+
if formula_elements.size == 2
|
14
|
+
return Formula.new(nil, formula_elements[1])
|
15
|
+
elsif formula_elements.size == 3
|
16
|
+
return Formula.new(formula_elements[2], formula_elements[1])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def load_in_r_as(variable_name)
|
21
|
+
Rust._eval("#{variable_name} <- #{self.left_part} ~ #{self.right_part}")
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :left_part
|
25
|
+
attr_reader :right_part
|
26
|
+
|
27
|
+
def initialize(left_part, right_part)
|
28
|
+
raise ArgumentError, "Expected string" if left_part && !left_part.is_a?(String)
|
29
|
+
raise ArgumentError, "Expected string" if !right_part.is_a?(String)
|
30
|
+
|
31
|
+
@left_part = left_part || ""
|
32
|
+
@right_part = right_part
|
33
|
+
end
|
34
|
+
|
35
|
+
def ==(oth)
|
36
|
+
return false unless oth.is_a?(Formula)
|
37
|
+
|
38
|
+
return @left_part == oth.left_part && @right_part == oth.right_part
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_R
|
42
|
+
return "#@left_part ~ #@right_part"
|
43
|
+
end
|
44
|
+
|
45
|
+
def inspect
|
46
|
+
return self.to_R.strip
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Call < RustDatatype
|
51
|
+
def self.can_pull?(type, klass)
|
52
|
+
return klass == "call"
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.pull_variable(variable, type, klass)
|
56
|
+
return Call.new(Rust["deparse(#{variable})"])
|
57
|
+
end
|
58
|
+
|
59
|
+
def load_in_r_as(variable_name)
|
60
|
+
Rust["call.str"] = @value
|
61
|
+
Rust._eval("#{variable_name} <- str2lang(call.str)")
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize(value)
|
65
|
+
@value = value
|
66
|
+
end
|
67
|
+
|
68
|
+
def value
|
69
|
+
@value
|
70
|
+
end
|
71
|
+
|
72
|
+
def inspect
|
73
|
+
@value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class Environment < RustDatatype
|
78
|
+
def self.can_pull?(type, klass)
|
79
|
+
return type == "environment" && klass == "environment"
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.pull_variable(variable, type, klass)
|
83
|
+
warn "Exchanging R environments is not supported!"
|
84
|
+
return Environment.new
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.load_in_r_as(variable)
|
88
|
+
warn "Exchanging R environments is not supported!"
|
89
|
+
Rust._eval("#{variable} <- environment()")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class Function
|
94
|
+
attr_reader :name
|
95
|
+
attr_reader :arguments
|
96
|
+
attr_reader :options
|
97
|
+
|
98
|
+
def initialize(name)
|
99
|
+
@function = name
|
100
|
+
@arguments = Arguments.new
|
101
|
+
@options = Options.new
|
102
|
+
end
|
103
|
+
|
104
|
+
def options=(options)
|
105
|
+
raise TypeError, "Expected Options" unless options.is_a?(Options)
|
106
|
+
|
107
|
+
@options = options
|
108
|
+
end
|
109
|
+
|
110
|
+
def arguments=(arguments)
|
111
|
+
raise TypeError, "Expected Arguments" unless options.is_a?(Arguments)
|
112
|
+
|
113
|
+
@arguments = arguments
|
114
|
+
end
|
115
|
+
|
116
|
+
def to_R
|
117
|
+
params = [@arguments.to_R, @options.to_R].select { |v| v != "" }.join(",")
|
118
|
+
return "#@function(#{params})"
|
119
|
+
end
|
120
|
+
|
121
|
+
def call
|
122
|
+
Rust._eval(self.to_R)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class SimpleFormula
|
127
|
+
def initialize(dependent, independent)
|
128
|
+
@dependent = dependent
|
129
|
+
@independent = independent
|
130
|
+
end
|
131
|
+
|
132
|
+
def to_R
|
133
|
+
return "#@dependent ~ #@independent"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class Variable
|
138
|
+
def initialize(name)
|
139
|
+
@name = name
|
140
|
+
end
|
141
|
+
|
142
|
+
def to_R
|
143
|
+
@name
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class Arguments < Array
|
148
|
+
def to_R
|
149
|
+
return self.map { |v| v.to_R }.join(", ")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class Options < Hash
|
154
|
+
def to_R
|
155
|
+
return self.map { |k, v| "#{k}=#{v.to_R}" }.join(", ")
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.from_hash(hash)
|
159
|
+
options = Options.new
|
160
|
+
hash.each do |key, value|
|
161
|
+
options[key.to_s] = value
|
162
|
+
end
|
163
|
+
return options
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require_relative 'datatype'
|
2
|
+
|
3
|
+
module Rust
|
4
|
+
class List < RustDatatype
|
5
|
+
def self.can_pull?(type, klass)
|
6
|
+
return type == "list"
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.pull_variable(variable, type, klass)
|
10
|
+
return List.new(klass) if Rust._pull("length(#{variable})") == 0
|
11
|
+
|
12
|
+
names = [Rust["names(#{variable})"]].flatten
|
13
|
+
length = Rust["length(#{variable})"]
|
14
|
+
|
15
|
+
list = List.new(klass, names)
|
16
|
+
for i in 0...length
|
17
|
+
list[i] = Rust["#{variable}[[#{i + 1}]]"]
|
18
|
+
end
|
19
|
+
|
20
|
+
return list
|
21
|
+
end
|
22
|
+
|
23
|
+
def load_in_r_as(variable_name)
|
24
|
+
Rust._eval("#{variable_name} <- list()")
|
25
|
+
@data.each do |key, value|
|
26
|
+
Rust["#{variable_name}[[#{key + 1}]]"] = value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(klass, names = [])
|
31
|
+
@data = {}
|
32
|
+
@names = names
|
33
|
+
@klass = klass
|
34
|
+
end
|
35
|
+
|
36
|
+
def [](key)
|
37
|
+
key = get_key(key)
|
38
|
+
|
39
|
+
return @data[key]
|
40
|
+
end
|
41
|
+
alias :| :[]
|
42
|
+
|
43
|
+
def []=(key, value)
|
44
|
+
key = get_key(key)
|
45
|
+
|
46
|
+
return @data[key] = value
|
47
|
+
end
|
48
|
+
|
49
|
+
def names
|
50
|
+
@names
|
51
|
+
end
|
52
|
+
|
53
|
+
def inspect
|
54
|
+
result = ""
|
55
|
+
values_inspected = @data.map { |k, v| [k, v.inspect.split("\n").map { |l| " " + l }.join("\n")] }.to_h
|
56
|
+
max_length = [values_inspected.map { |k, v| v.split("\n").map { |line| line.length }.max.to_i }.max.to_i, 100].min
|
57
|
+
|
58
|
+
@data.keys.each do |i|
|
59
|
+
result << "-" * max_length + "\n"
|
60
|
+
result << (@names[i] || "[[#{i}]]") + "\n"
|
61
|
+
result << values_inspected[i] + "\n"
|
62
|
+
end
|
63
|
+
result << "-" * max_length
|
64
|
+
|
65
|
+
return result
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def get_key(key)
|
70
|
+
if key.is_a?(String)
|
71
|
+
new_key = @names.index(key)
|
72
|
+
raise ArgumentError, "Wrong key: #{key}" unless new_key
|
73
|
+
key = new_key
|
74
|
+
end
|
75
|
+
|
76
|
+
raise ArgumentError, "The key should be either a string or an integer" unless key.is_a?(Integer)
|
77
|
+
|
78
|
+
return key
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require_relative 'datatype'
|
2
|
+
|
3
|
+
module Rust
|
4
|
+
class Matrix < RustDatatype
|
5
|
+
def self.can_pull?(type, klass)
|
6
|
+
return klass.is_a?(Array) && klass.include?("matrix")
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.pull_variable(variable, type, klass)
|
10
|
+
if Rust._pull("length(#{variable})") == 1
|
11
|
+
core = ::Matrix[[Rust._pull("#{variable}[1]")]]
|
12
|
+
else
|
13
|
+
core = Rust._pull(variable)
|
14
|
+
end
|
15
|
+
row_names = [Rust["rownames(#{variable})"]].flatten
|
16
|
+
column_names = [Rust["colnames(#{variable})"]].flatten
|
17
|
+
|
18
|
+
row_names = nil if row_names.all? { |v| v == nil }
|
19
|
+
column_names = nil if column_names.all? { |v| v == nil }
|
20
|
+
|
21
|
+
Matrix.new(core, row_names, column_names)
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_in_r_as(variable_name)
|
25
|
+
matrix = ::Matrix[*@data]
|
26
|
+
|
27
|
+
Rust[variable_name] = matrix
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(data, row_names = nil, column_names = nil)
|
31
|
+
@data = data.clone
|
32
|
+
|
33
|
+
@row_names = row_names
|
34
|
+
@column_names = column_names
|
35
|
+
|
36
|
+
if @data.is_a?(::Matrix)
|
37
|
+
@data = @data.row_vectors.map { |v| v.to_a }
|
38
|
+
end
|
39
|
+
|
40
|
+
if self.flatten.size == 0
|
41
|
+
raise "Empty matrices are not allowed"
|
42
|
+
else
|
43
|
+
raise TypeError, "Expected array of array" unless @data.is_a?(Array) || @data[0].is_a?(Array)
|
44
|
+
raise TypeError, "Only numeric matrices are supported" unless self.flatten.all? { |e| e.is_a?(Numeric) }
|
45
|
+
raise "All the rows must have the same size" unless @data.map { |row| row.size }.uniq.size == 1
|
46
|
+
raise ArgumentError, "Expected row names #@row_names to match the number of rows in #{self.inspect}" if @row_names && @row_names.size != self.rows
|
47
|
+
raise ArgumentError, "Expected column names #@column_names to match the number of columns in #{self.inspect}" if @column_names && @column_names.size != self.cols
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def [](i, j)
|
52
|
+
i, j = indices(i, j)
|
53
|
+
|
54
|
+
return @data[i][j]
|
55
|
+
end
|
56
|
+
|
57
|
+
def rows
|
58
|
+
@data.size
|
59
|
+
end
|
60
|
+
|
61
|
+
def cols
|
62
|
+
@data[0].size
|
63
|
+
end
|
64
|
+
|
65
|
+
def flatten
|
66
|
+
return @data.flatten
|
67
|
+
end
|
68
|
+
|
69
|
+
def []=(i, j, value)
|
70
|
+
i, j = indices(i, j)
|
71
|
+
|
72
|
+
@data[i][j] = value
|
73
|
+
end
|
74
|
+
|
75
|
+
def inspect
|
76
|
+
row_names = @row_names || (0...self.rows).to_a.map { |v| v.to_s }
|
77
|
+
column_names = @column_names || (0...self.cols).to_a.map { |v| v.to_s }
|
78
|
+
|
79
|
+
separator = " | "
|
80
|
+
col_widths = column_names.map do |colname|
|
81
|
+
[
|
82
|
+
colname,
|
83
|
+
(
|
84
|
+
[colname ? colname.length : 1] +
|
85
|
+
@data.map {|r| r[column_names.index(colname)]}.map { |e| e.inspect.length }
|
86
|
+
).max
|
87
|
+
]
|
88
|
+
end.to_h
|
89
|
+
col_widths[:rowscol] = row_names.map { |rowname| rowname.length }.max + 3
|
90
|
+
|
91
|
+
result = ""
|
92
|
+
result << "-" * (col_widths.values.sum + ((col_widths.size - 1) * separator.length)) + "\n"
|
93
|
+
result << (" " * col_widths[:rowscol]) + column_names.map { |colname| (" " * (col_widths[colname] - colname.length)) + colname }.join(separator) + "\n"
|
94
|
+
result << "-" * (col_widths.values.sum + ((col_widths.size - 1) * separator.length)) + "\n"
|
95
|
+
|
96
|
+
@data.each_with_index do |row, i|
|
97
|
+
row_name = row_names[i]
|
98
|
+
row = column_names.zip(row)
|
99
|
+
|
100
|
+
index_part = "[" + (" " * (col_widths[:rowscol] - row_name.length - 3)) + "#{row_name}] "
|
101
|
+
row_part = row.map { |colname, value| (" " * (col_widths[colname] - value.inspect.length)) + value.inspect }.join(separator)
|
102
|
+
|
103
|
+
result << index_part + row_part + "\n"
|
104
|
+
end
|
105
|
+
|
106
|
+
result << "-" * (col_widths.values.sum + ((col_widths.size - 1) * separator.length))
|
107
|
+
|
108
|
+
return result
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
def indices(i, j)
|
113
|
+
if i.is_a?(String)
|
114
|
+
ri = @row_names.index(i)
|
115
|
+
raise ArgumentError, "Can not find row #{i}" unless ri
|
116
|
+
i = ri
|
117
|
+
end
|
118
|
+
|
119
|
+
if j.is_a?(String)
|
120
|
+
rj = @column_names.index(j)
|
121
|
+
raise ArgumentError, "Can not find column #{j}" unless rj
|
122
|
+
j = rj
|
123
|
+
end
|
124
|
+
|
125
|
+
raise ArgumentError, "Expected i and j to be both integers or strings" unless i.is_a?(Integer) && j.is_a?(Integer)
|
126
|
+
raise "Wrong i" unless i.between?(0, @data.size - 1)
|
127
|
+
raise "Wrong j" unless j.between?(0, @data[0].size - 1)
|
128
|
+
|
129
|
+
return [i, j]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|