daru 0.1.4.1 → 0.1.5
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/.travis.yml +3 -0
- data/CONTRIBUTING.md +27 -3
- data/Guardfile +7 -0
- data/History.md +39 -1
- data/README.md +1 -1
- data/daru.gemspec +9 -2
- data/lib/daru.rb +4 -1
- data/lib/daru/accessors/gsl_wrapper.rb +93 -91
- data/lib/daru/accessors/nmatrix_wrapper.rb +109 -107
- data/lib/daru/category.rb +22 -15
- data/lib/daru/core/group_by.rb +13 -2
- data/lib/daru/core/merge.rb +37 -31
- data/lib/daru/core/query.rb +10 -2
- data/lib/daru/dataframe.rb +95 -34
- data/lib/daru/date_time/index.rb +15 -16
- data/lib/daru/date_time/offsets.rb +14 -11
- data/lib/daru/formatters/table.rb +2 -2
- data/lib/daru/index/categorical_index.rb +201 -0
- data/lib/daru/index/index.rb +289 -0
- data/lib/daru/index/multi_index.rb +266 -0
- data/lib/daru/maths/statistics/vector.rb +13 -9
- data/lib/daru/monkeys.rb +0 -7
- data/lib/daru/plotting/gruff/category.rb +1 -0
- data/lib/daru/plotting/gruff/dataframe.rb +3 -3
- data/lib/daru/plotting/nyaplot/dataframe.rb +1 -1
- data/lib/daru/vector.rb +36 -21
- data/lib/daru/version.rb +1 -1
- data/spec/accessors/array_wrapper_spec.rb +3 -0
- data/spec/accessors/{wrappers_spec.rb → gsl_wrapper_spec.rb} +0 -35
- data/spec/accessors/nmatrix_wrapper_spec.rb +32 -0
- data/spec/{categorical_spec.rb → category_spec.rb} +3 -0
- data/spec/core/group_by_spec.rb +17 -1
- data/spec/core/merge_spec.rb +38 -1
- data/spec/core/query_spec.rb +5 -0
- data/spec/dataframe_spec.rb +230 -57
- data/spec/date_time/offsets_spec.rb +84 -3
- data/spec/formatters/table_formatter_spec.rb +9 -0
- data/spec/index/categorical_index_spec.rb +2 -0
- data/spec/index/index_spec.rb +17 -2
- data/spec/{math → maths}/arithmetic/dataframe_spec.rb +0 -0
- data/spec/{math → maths}/arithmetic/vector_spec.rb +0 -0
- data/spec/{math → maths}/statistics/dataframe_spec.rb +1 -1
- data/spec/{math → maths}/statistics/vector_spec.rb +7 -12
- data/spec/plotting/gruff/category_spec.rb +44 -0
- data/spec/plotting/gruff/dataframe_spec.rb +84 -0
- data/spec/plotting/gruff/vector_spec.rb +70 -0
- data/spec/plotting/nyaplot/category_spec.rb +51 -0
- data/spec/plotting/{dataframe_spec.rb → nyaplot/dataframe_spec.rb} +0 -83
- data/spec/plotting/nyaplot/vector_spec.rb +66 -0
- data/spec/spec_helper.rb +3 -2
- data/spec/vector_spec.rb +68 -1
- metadata +53 -24
- data/lib/daru/index.rb +0 -761
- data/spec/plotting/vector_spec.rb +0 -230
data/lib/daru/date_time/index.rb
CHANGED
@@ -87,8 +87,7 @@ module Daru
|
|
87
87
|
def verify_start_and_end start, en
|
88
88
|
raise ArgumentError, 'Start and end cannot be the same' if start == en
|
89
89
|
raise ArgumentError, 'Start must be lesser than end' if start > en
|
90
|
-
raise ArgumentError,
|
91
|
-
'Only same time zones are allowed' if start.zone != en.zone
|
90
|
+
raise ArgumentError, 'Only same time zones are allowed' if start.zone != en.zone
|
92
91
|
end
|
93
92
|
|
94
93
|
def infer_offset data
|
@@ -285,7 +284,7 @@ module Daru
|
|
285
284
|
# @option opts [String, Daru::DateOffset, Daru::Offsets::*] :freq ('D') The interval
|
286
285
|
# between each date in the index. This can either be a string specifying
|
287
286
|
# the frequency (i.e. one of the frequency aliases) or an offset object.
|
288
|
-
# @option opts [
|
287
|
+
# @option opts [Integer] :periods The number of periods that should go into
|
289
288
|
# this index. Takes precedence over `:end`.
|
290
289
|
# @return [DateTimeIndex] DateTimeIndex object of the specified parameters.
|
291
290
|
#
|
@@ -389,7 +388,7 @@ module Daru
|
|
389
388
|
# @param [String, DateTime] first Start of the slice as a string or DateTime.
|
390
389
|
# @param [String, DateTime] last End of the slice as a string or DateTime.
|
391
390
|
def slice first, last
|
392
|
-
if first.is_a?(
|
391
|
+
if first.is_a?(Integer) && last.is_a?(Integer)
|
393
392
|
DateTimeIndex.new(to_a[first..last], freq: @offset)
|
394
393
|
else
|
395
394
|
first = Helper.find_date_string_bounds(first)[0] if first.is_a?(String)
|
@@ -427,7 +426,7 @@ module Daru
|
|
427
426
|
# Shift all dates in the index by a positive number in the future. The dates
|
428
427
|
# are shifted by the same amount as that specified in the offset.
|
429
428
|
#
|
430
|
-
# @param [
|
429
|
+
# @param [Integer, Daru::DateOffset, Daru::Offsets::*] distance Distance by
|
431
430
|
# which each date should be shifted. Passing an offset object to #shift
|
432
431
|
# will offset each data point by the offset value. Passing a positive
|
433
432
|
# integer will offset each data point by the same offset that it was
|
@@ -445,7 +444,7 @@ module Daru
|
|
445
444
|
# index.shift(4)
|
446
445
|
# #=>#<DateTimeIndex:83979630 offset=YEAR periods=10 data=[2016-01-01T00:00:00+00:00...2025-01-01T00:00:00+00:00]>
|
447
446
|
def shift distance
|
448
|
-
distance.is_a?(
|
447
|
+
distance.is_a?(Integer) && distance < 0 and
|
449
448
|
raise IndexError, "Distance #{distance} cannot be negative"
|
450
449
|
|
451
450
|
_shift(distance)
|
@@ -454,14 +453,14 @@ module Daru
|
|
454
453
|
# Shift all dates in the index to the past. The dates are shifted by the same
|
455
454
|
# amount as that specified in the offset.
|
456
455
|
#
|
457
|
-
# @param [
|
456
|
+
# @param [Integer, Daru::DateOffset, Daru::Offsets::*] distance Integer or
|
458
457
|
# Daru::DateOffset. Distance by which each date should be shifted. Passing
|
459
458
|
# an offset object to #lag will offset each data point by the offset value.
|
460
459
|
# Passing a positive integer will offset each data point by the same offset
|
461
460
|
# that it was created with.
|
462
461
|
# @return [DateTimeIndex] A new lagged DateTimeIndex object.
|
463
462
|
def lag distance
|
464
|
-
distance.is_a?(
|
463
|
+
distance.is_a?(Integer) && distance < 0 and
|
465
464
|
raise IndexError, "Distance #{distance} cannot be negative"
|
466
465
|
|
467
466
|
_shift(-distance)
|
@@ -480,17 +479,17 @@ module Daru
|
|
480
479
|
# :nocov:
|
481
480
|
|
482
481
|
# @!method year
|
483
|
-
# @return [Array<
|
482
|
+
# @return [Array<Integer>] Array containing year of each index.
|
484
483
|
# @!method month
|
485
|
-
# @return [Array<
|
484
|
+
# @return [Array<Integer>] Array containing month of each index.
|
486
485
|
# @!method day
|
487
|
-
# @return [Array<
|
486
|
+
# @return [Array<Integer>] Array containing day of each index.
|
488
487
|
# @!method hour
|
489
|
-
# @return [Array<
|
488
|
+
# @return [Array<Integer>] Array containing hour of each index.
|
490
489
|
# @!method min
|
491
|
-
# @return [Array<
|
490
|
+
# @return [Array<Integer>] Array containing minutes of each index.
|
492
491
|
# @!method sec
|
493
|
-
# @return [Array<
|
492
|
+
# @return [Array<Integer>] Array containing seconds of each index.
|
494
493
|
[:year, :month, :day, :hour, :min, :sec].each do |meth|
|
495
494
|
define_method(meth) do
|
496
495
|
each_with_object([]) do |d, arr|
|
@@ -521,7 +520,7 @@ module Daru
|
|
521
520
|
private
|
522
521
|
|
523
522
|
def get_by_range first, last
|
524
|
-
return slice(first, last) if first.is_a?(
|
523
|
+
return slice(first, last) if first.is_a?(Integer) && last.is_a?(Integer)
|
525
524
|
|
526
525
|
raise ArgumentError, "Keys #{first} and #{last} are out of bounds" if
|
527
526
|
Helper.key_out_of_bounds?(first, @data) && Helper.key_out_of_bounds?(last, @data)
|
@@ -548,7 +547,7 @@ module Daru
|
|
548
547
|
end
|
549
548
|
|
550
549
|
def _shift distance
|
551
|
-
if distance.is_a?(
|
550
|
+
if distance.is_a?(Integer)
|
552
551
|
raise IndexError, 'To lag non-freq date time index pass an offset.' unless @offset
|
553
552
|
|
554
553
|
start = @data[0][0]
|
@@ -78,10 +78,19 @@ module Daru
|
|
78
78
|
def - date_time
|
79
79
|
@offset + date_time
|
80
80
|
end
|
81
|
+
|
82
|
+
def -@
|
83
|
+
@offset
|
84
|
+
end
|
81
85
|
end
|
82
86
|
|
83
87
|
module Offsets
|
84
88
|
class DateOffsetType < DateOffset
|
89
|
+
# @method initialize
|
90
|
+
# Initialize one of the subclasses of DateOffsetType with the number of the times
|
91
|
+
# the offset should be applied, which is the supplied as the argument.
|
92
|
+
#
|
93
|
+
# @param n [Integer] The number of times an offset should be applied.
|
85
94
|
def initialize n=1
|
86
95
|
@n = n
|
87
96
|
end
|
@@ -95,12 +104,6 @@ module Daru
|
|
95
104
|
# @abstract
|
96
105
|
# @private
|
97
106
|
class Tick < DateOffsetType
|
98
|
-
# @method initialize
|
99
|
-
# Initialize one of the subclasses of Tick with the number of the times
|
100
|
-
# the offset should be applied, which is the supplied as the argument.
|
101
|
-
#
|
102
|
-
# @param n [Integer] The number of times an offset should be applied.
|
103
|
-
|
104
107
|
def + date_time
|
105
108
|
date_time + @n*multiplier
|
106
109
|
end
|
@@ -121,7 +124,7 @@ module Daru
|
|
121
124
|
FREQ = 'S'.freeze
|
122
125
|
|
123
126
|
def multiplier
|
124
|
-
1.
|
127
|
+
1.to_r / 24 / 60 / 60
|
125
128
|
end
|
126
129
|
end
|
127
130
|
|
@@ -136,7 +139,7 @@ module Daru
|
|
136
139
|
FREQ = 'M'.freeze
|
137
140
|
|
138
141
|
def multiplier
|
139
|
-
|
142
|
+
1.to_r / 24 / 60
|
140
143
|
end
|
141
144
|
end
|
142
145
|
|
@@ -151,7 +154,7 @@ module Daru
|
|
151
154
|
FREQ = 'H'.freeze
|
152
155
|
|
153
156
|
def multiplier
|
154
|
-
|
157
|
+
1.to_r / 24
|
155
158
|
end
|
156
159
|
end
|
157
160
|
|
@@ -166,7 +169,7 @@ module Daru
|
|
166
169
|
FREQ = 'D'.freeze
|
167
170
|
|
168
171
|
def multiplier
|
169
|
-
1
|
172
|
+
1
|
170
173
|
end
|
171
174
|
end
|
172
175
|
|
@@ -210,7 +213,7 @@ module Daru
|
|
210
213
|
|
211
214
|
class Week < DateOffset
|
212
215
|
def initialize *args
|
213
|
-
@n =
|
216
|
+
@n = args[0].is_a?(Hash) ? 1 : args[0]
|
214
217
|
opts = args[-1]
|
215
218
|
@weekday = opts[:weekday] || 0
|
216
219
|
end
|
@@ -39,11 +39,11 @@ module Daru
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def construct_formatter rows, spacing
|
42
|
-
width = rows.flatten.map(&:size).max
|
42
|
+
width = rows.flatten.map(&:size).max || 0
|
43
43
|
width = [3, width].max # not less than 'nil'
|
44
44
|
width = [width, spacing].min # not more than max width
|
45
45
|
|
46
|
-
" %#{width}.#{width}s" * rows.first.size
|
46
|
+
" %#{width}.#{width}s" * rows.first.size if rows.first
|
47
47
|
end
|
48
48
|
|
49
49
|
def pretty_to_s(val)
|
@@ -0,0 +1,201 @@
|
|
1
|
+
module Daru
|
2
|
+
class CategoricalIndex < Index
|
3
|
+
# Create a categorical index object.
|
4
|
+
# @param indexes [Array<object>] array of indexes
|
5
|
+
# @return [Daru::CategoricalIndex] categorical index
|
6
|
+
# @example
|
7
|
+
# Daru::CategoricalIndex.new [:a, 1, :a, 1, :c]
|
8
|
+
# # => #<Daru::CategoricalIndex(5): {a, 1, a, 1, c}>
|
9
|
+
def initialize indexes
|
10
|
+
# Create a hash to map each category to positional indexes
|
11
|
+
categories = indexes.each_with_index.group_by(&:first)
|
12
|
+
@cat_hash = categories.map { |cat, group| [cat, group.map(&:last)] }.to_h
|
13
|
+
|
14
|
+
# Map each category to a unique integer for effective storage in @array
|
15
|
+
map_cat_int = categories.keys.each_with_index.to_h
|
16
|
+
|
17
|
+
# To link every instance to its category,
|
18
|
+
# it stores integer for every instance representing its category
|
19
|
+
@array = map_cat_int.values_at(*indexes)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Duplicates the index object and return it
|
23
|
+
# @return [Daru::CategoricalIndex] duplicated index object
|
24
|
+
def dup
|
25
|
+
# Improve it by intializing index by hash
|
26
|
+
Daru::CategoricalIndex.new to_a
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns true index or category is valid
|
30
|
+
# @param index [object] the index value to look for
|
31
|
+
# @return [true, false] true if index is included, false otherwise
|
32
|
+
def include? index
|
33
|
+
@cat_hash.include? index
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns array of categories
|
37
|
+
# @example
|
38
|
+
# x = Daru::CategoricalIndex.new [:a, 1, :a, 1, :c]
|
39
|
+
# x.categories
|
40
|
+
# # => [:a, 1, :c]
|
41
|
+
def categories
|
42
|
+
@cat_hash.keys
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns positions given categories or positions
|
46
|
+
# @note If the argument does not a valid category it treats it as position
|
47
|
+
# value and return it as it is.
|
48
|
+
# @param [Array<object>] *indexes categories or positions
|
49
|
+
# @example
|
50
|
+
# x = Daru::CategoricalIndex.new [:a, 1, :a, 1, :c]
|
51
|
+
# x.pos :a, 1
|
52
|
+
# # => [0, 1, 2, 3]
|
53
|
+
def pos *indexes
|
54
|
+
positions = indexes.map do |index|
|
55
|
+
if include? index
|
56
|
+
@cat_hash[index]
|
57
|
+
elsif index.is_a?(Numeric) && index < @array.size
|
58
|
+
index
|
59
|
+
else
|
60
|
+
raise IndexError, "#{index.inspect} is neither a valid category"\
|
61
|
+
' nor a valid position'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
positions.flatten!
|
66
|
+
positions.size == 1 ? positions.first : positions.sort
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns index value from position
|
70
|
+
# @param pos [Integer] the position to look for
|
71
|
+
# @return [object] category corresponding to position
|
72
|
+
# @example
|
73
|
+
# idx = Daru::CategoricalIndex.new [:a, :b, :a, :b, :c]
|
74
|
+
# idx.index_from_pos 1
|
75
|
+
# # => :b
|
76
|
+
def index_from_pos pos
|
77
|
+
cat_from_int @array[pos]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns enumerator enumerating all index values in the order they occur
|
81
|
+
# @return [Enumerator] all index values
|
82
|
+
# @example
|
83
|
+
# idx = Daru::CategoricalIndex.new [:a, :a, :b]
|
84
|
+
# idx.each.to_a
|
85
|
+
# # => [:a, :a, :b]
|
86
|
+
def each
|
87
|
+
return enum_for(:each) unless block_given?
|
88
|
+
@array.each { |pos| yield cat_from_int pos }
|
89
|
+
self
|
90
|
+
end
|
91
|
+
|
92
|
+
# Compares two index object. Returns true if every instance of category
|
93
|
+
# occur at the same position
|
94
|
+
# @param [Daru::CateogricalIndex] other index object to be checked against
|
95
|
+
# @return [true, false] true if other is similar to self
|
96
|
+
# @example
|
97
|
+
# a = Daru::CategoricalIndex.new [:a, :a, :b]
|
98
|
+
# b = Daru::CategoricalIndex.new [:b, :a, :a]
|
99
|
+
# a == b
|
100
|
+
# # => false
|
101
|
+
def == other
|
102
|
+
self.class == other.class &&
|
103
|
+
size == other.size &&
|
104
|
+
to_h == other.to_h
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns all the index values
|
108
|
+
# @return [Array] all index values
|
109
|
+
# @example
|
110
|
+
# idx = Daru::CategoricalIndex.new [:a, :b, :a]
|
111
|
+
# idx.to_a
|
112
|
+
def to_a
|
113
|
+
each.to_a
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns hash table mapping category to positions at which they occur
|
117
|
+
# @return [Hash] hash table mapping category to array of positions
|
118
|
+
# @example
|
119
|
+
# idx = Daru::CategoricalIndex.new [:a, :b, :a]
|
120
|
+
# idx.to_h
|
121
|
+
# # => {:a=>[0, 2], :b=>[1]}
|
122
|
+
def to_h
|
123
|
+
@cat_hash
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns size of the index object
|
127
|
+
# @return [Integer] total number of instances of all categories
|
128
|
+
# @example
|
129
|
+
# idx = Daru::CategoricalIndex.new [:a, :b, :a]
|
130
|
+
# idx.size
|
131
|
+
# # => 3
|
132
|
+
def size
|
133
|
+
@array.size
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns true if index object is storing no category
|
137
|
+
# @return [true, false] true if index object is empty
|
138
|
+
# @example
|
139
|
+
# i = Daru::CategoricalIndex.new []
|
140
|
+
# # => #<Daru::CategoricalIndex(0): {}>
|
141
|
+
# i.empty?
|
142
|
+
# # => true
|
143
|
+
def empty?
|
144
|
+
@array.empty?
|
145
|
+
end
|
146
|
+
|
147
|
+
# Return subset given categories or positions
|
148
|
+
# @param [Array<object>] *indexes categories or positions
|
149
|
+
# @return [Daru::CategoricalIndex] subset of the self containing the
|
150
|
+
# mentioned categories or positions
|
151
|
+
# @example
|
152
|
+
# idx = Daru::CategoricalIndex.new [:a, :b, :a, :b, :c]
|
153
|
+
# idx.subset :a, :b
|
154
|
+
# # => #<Daru::CategoricalIndex(4): {a, b, a, b}>
|
155
|
+
def subset *indexes
|
156
|
+
positions = pos(*indexes)
|
157
|
+
new_index = positions.map { |pos| index_from_pos pos }
|
158
|
+
|
159
|
+
Daru::CategoricalIndex.new new_index.flatten
|
160
|
+
end
|
161
|
+
|
162
|
+
# Takes positional values and returns subset of the self
|
163
|
+
# capturing the categories at mentioned positions
|
164
|
+
# @param [Array<Integer>] positional values
|
165
|
+
# @return [object] index object
|
166
|
+
# @example
|
167
|
+
# idx = Daru::CategoricalIndex.new [:a, :b, :a, :b, :c]
|
168
|
+
# idx.at 0, 1
|
169
|
+
# # => #<Daru::CategoricalIndex(2): {a, b}>
|
170
|
+
def at *positions
|
171
|
+
positions = preprocess_positions(*positions)
|
172
|
+
validate_positions(*positions)
|
173
|
+
if positions.is_a? Integer
|
174
|
+
index_from_pos(positions)
|
175
|
+
else
|
176
|
+
Daru::CategoricalIndex.new positions.map(&method(:index_from_pos))
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Add specified index values to the index object
|
181
|
+
# @param [Array<object>] *indexes index values to add
|
182
|
+
# @return [Daru::CategoricalIndex] index object with added values
|
183
|
+
# @example
|
184
|
+
# idx = Daru::CategoricalIndex.new [:a, :b, :a, :b, :c]
|
185
|
+
# idx.add :d
|
186
|
+
# # => #<Daru::CategoricalIndex(6): {a, b, a, b, c, d}>
|
187
|
+
def add *indexes
|
188
|
+
Daru::CategoricalIndex.new(to_a + indexes)
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def int_from_cat cat
|
194
|
+
@cat_hash.keys.index cat
|
195
|
+
end
|
196
|
+
|
197
|
+
def cat_from_int cat
|
198
|
+
@cat_hash.keys[cat]
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,289 @@
|
|
1
|
+
module Daru
|
2
|
+
class Index
|
3
|
+
include Enumerable
|
4
|
+
# It so happens that over riding the .new method in a super class also
|
5
|
+
# tampers with the default .new method for class that inherit from the
|
6
|
+
# super class (Index in this case). Thus we first alias the original
|
7
|
+
# new method (from Object) to __new__ when the Index class is evaluated,
|
8
|
+
# and then we use an inherited hook such that the old new method (from
|
9
|
+
# Object) is once again the default .new for the subclass.
|
10
|
+
# Refer http://blog.sidu.in/2007/12/rubys-new-as-factory.html
|
11
|
+
class << self
|
12
|
+
alias :__new__ :new
|
13
|
+
|
14
|
+
def inherited subclass
|
15
|
+
class << subclass
|
16
|
+
alias :new :__new__
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# We over-ride the .new method so that any sort of Index can be generated
|
22
|
+
# from Daru::Index based on the types of arguments supplied.
|
23
|
+
def self.new *args, &block
|
24
|
+
# FIXME: I'm not sure this clever trick really deserves our attention.
|
25
|
+
# Most of common ruby libraries just avoid it in favor of usual
|
26
|
+
# factor method, like `Index.create`. When `Index.new(...).class != Index`
|
27
|
+
# it just leads to confusion and surprises. - zverok, 2016-05-18
|
28
|
+
source = args.first
|
29
|
+
|
30
|
+
MultiIndex.try_from_tuples(source) ||
|
31
|
+
DateTimeIndex.try_create(source) ||
|
32
|
+
allocate.tap { |i| i.send :initialize, *args, &block }
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.coerce maybe_index
|
36
|
+
maybe_index.is_a?(Index) ? maybe_index : Daru::Index.new(maybe_index)
|
37
|
+
end
|
38
|
+
|
39
|
+
def each(&block)
|
40
|
+
return to_enum(:each) unless block_given?
|
41
|
+
|
42
|
+
@relation_hash.each_key(&block)
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader :relation_hash, :size
|
47
|
+
|
48
|
+
def initialize index
|
49
|
+
index =
|
50
|
+
case index
|
51
|
+
when nil
|
52
|
+
[]
|
53
|
+
when Integer
|
54
|
+
index.times.to_a
|
55
|
+
when Enumerable
|
56
|
+
index.to_a
|
57
|
+
else
|
58
|
+
raise ArgumentError,
|
59
|
+
"Cannot create index from #{index.class} #{index.inspect}"
|
60
|
+
end
|
61
|
+
|
62
|
+
@relation_hash = index.each_with_index.to_h.freeze
|
63
|
+
@keys = @relation_hash.keys
|
64
|
+
@size = @relation_hash.size
|
65
|
+
end
|
66
|
+
|
67
|
+
def ==(other)
|
68
|
+
return false if self.class != other.class || other.size != @size
|
69
|
+
|
70
|
+
@relation_hash.keys == other.to_a &&
|
71
|
+
@relation_hash.values == other.relation_hash.values
|
72
|
+
end
|
73
|
+
|
74
|
+
def [](key, *rest)
|
75
|
+
case
|
76
|
+
when key.is_a?(Range)
|
77
|
+
by_range key
|
78
|
+
when !rest.empty?
|
79
|
+
by_multi_key key, *rest
|
80
|
+
else
|
81
|
+
by_single_key key
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns true if all arguments are either a valid category or position
|
86
|
+
# @param [Array<object>] *indexes categories or positions
|
87
|
+
# @return [true, false]
|
88
|
+
# @example
|
89
|
+
# idx.valid? :a, 2
|
90
|
+
# # => true
|
91
|
+
# idx.valid? 3
|
92
|
+
# # => false
|
93
|
+
def valid? *indexes
|
94
|
+
indexes.all? { |i| to_a.include?(i) || (i.is_a?(Numeric) && i < size) }
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns positions given indexes or positions
|
98
|
+
# @note If the arugent is both a valid index and a valid position,
|
99
|
+
# it will treated as valid index
|
100
|
+
# @param [Array<object>] *indexes indexes or positions
|
101
|
+
# @example
|
102
|
+
# x = Daru::Index.new [:a, :b, :c]
|
103
|
+
# x.pos :a, 1
|
104
|
+
# # => [0, 1]
|
105
|
+
def pos *indexes
|
106
|
+
indexes = preprocess_range(indexes.first) if indexes.first.is_a? Range
|
107
|
+
|
108
|
+
if indexes.size == 1
|
109
|
+
self[indexes.first]
|
110
|
+
else
|
111
|
+
indexes.map { |index| by_single_key index }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def subset *indexes
|
116
|
+
if indexes.first.is_a? Range
|
117
|
+
slice indexes.first.begin, indexes.first.end
|
118
|
+
elsif include? indexes.first
|
119
|
+
# Assume 'indexes' contain indexes not positions
|
120
|
+
Daru::Index.new indexes
|
121
|
+
else
|
122
|
+
# Assume 'indexes' contain positions not indexes
|
123
|
+
Daru::Index.new indexes.map { |k| key k }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Takes positional values and returns subset of the self
|
128
|
+
# capturing the indexes at mentioned positions
|
129
|
+
# @param [Array<Integer>] positional values
|
130
|
+
# @return [object] index object
|
131
|
+
# @example
|
132
|
+
# idx = Daru::Index.new [:a, :b, :c]
|
133
|
+
# idx.at 0, 1
|
134
|
+
# # => #<Daru::Index(2): {a, b}>
|
135
|
+
def at *positions
|
136
|
+
positions = preprocess_positions(*positions)
|
137
|
+
validate_positions(*positions)
|
138
|
+
if positions.is_a? Integer
|
139
|
+
key(positions)
|
140
|
+
else
|
141
|
+
self.class.new positions.map(&method(:key))
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def inspect threshold=20
|
146
|
+
if size <= threshold
|
147
|
+
"#<#{self.class}(#{size}): {#{to_a.join(', ')}}>"
|
148
|
+
else
|
149
|
+
"#<#{self.class}(#{size}): {#{to_a.first(threshold).join(', ')} ... #{to_a.last}}>"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def slice *args
|
154
|
+
start = args[0]
|
155
|
+
en = args[1]
|
156
|
+
|
157
|
+
if start.is_a?(Integer) && en.is_a?(Integer)
|
158
|
+
Index.new @keys[start..en]
|
159
|
+
else
|
160
|
+
start_idx = @relation_hash[start]
|
161
|
+
en_idx = @relation_hash[en]
|
162
|
+
|
163
|
+
Index.new @keys[start_idx..en_idx]
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Produce new index from the set union of two indexes.
|
168
|
+
def |(other)
|
169
|
+
Index.new(to_a | other.to_a)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Produce a new index from the set intersection of two indexes
|
173
|
+
def & other
|
174
|
+
Index.new(to_a & other.to_a)
|
175
|
+
end
|
176
|
+
|
177
|
+
def to_a
|
178
|
+
@relation_hash.keys
|
179
|
+
end
|
180
|
+
|
181
|
+
def key(value)
|
182
|
+
return nil unless value.is_a?(Numeric)
|
183
|
+
@keys[value]
|
184
|
+
end
|
185
|
+
|
186
|
+
def include? index
|
187
|
+
@relation_hash.key? index
|
188
|
+
end
|
189
|
+
|
190
|
+
def empty?
|
191
|
+
@relation_hash.empty?
|
192
|
+
end
|
193
|
+
|
194
|
+
def dup
|
195
|
+
Daru::Index.new @relation_hash.keys
|
196
|
+
end
|
197
|
+
|
198
|
+
def add *indexes
|
199
|
+
Daru::Index.new(to_a + indexes)
|
200
|
+
end
|
201
|
+
|
202
|
+
def _dump(*)
|
203
|
+
Marshal.dump(relation_hash: @relation_hash)
|
204
|
+
end
|
205
|
+
|
206
|
+
def self._load data
|
207
|
+
h = Marshal.load data
|
208
|
+
|
209
|
+
Daru::Index.new(h[:relation_hash].keys)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Provide an Index for sub vector produced
|
213
|
+
#
|
214
|
+
# @param input_indexes [Array] the input by user to index the vector
|
215
|
+
# @return [Object] the Index object for sub vector produced
|
216
|
+
def conform(*)
|
217
|
+
self
|
218
|
+
end
|
219
|
+
|
220
|
+
def reorder(new_order)
|
221
|
+
from = to_a
|
222
|
+
self.class.new(new_order.map { |i| from[i] })
|
223
|
+
end
|
224
|
+
|
225
|
+
private
|
226
|
+
|
227
|
+
def preprocess_range rng
|
228
|
+
start = rng.begin
|
229
|
+
en = rng.end
|
230
|
+
|
231
|
+
if start.is_a?(Integer) && en.is_a?(Integer)
|
232
|
+
@keys[start..en]
|
233
|
+
else
|
234
|
+
start_idx = @relation_hash[start]
|
235
|
+
en_idx = @relation_hash[en]
|
236
|
+
|
237
|
+
@keys[start_idx..en_idx]
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def by_range rng
|
242
|
+
slice rng.begin, rng.end
|
243
|
+
end
|
244
|
+
|
245
|
+
def by_multi_key *key
|
246
|
+
if include? key[0]
|
247
|
+
Daru::Index.new key.map { |k| k }
|
248
|
+
else
|
249
|
+
# Assume the user is specifing values for index not keys
|
250
|
+
# Return index object having keys corresponding to values provided
|
251
|
+
Daru::Index.new key.map { |k| key k }
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def by_single_key key
|
256
|
+
if @relation_hash.key?(key)
|
257
|
+
@relation_hash[key]
|
258
|
+
elsif key.is_a?(Numeric) && key < size
|
259
|
+
key
|
260
|
+
else
|
261
|
+
raise IndexError, "Specified index #{key.inspect} does not exist"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# Raises IndexError when one of the positions is an invalid position
|
266
|
+
def validate_positions *positions
|
267
|
+
positions = [positions] if positions.is_a? Integer
|
268
|
+
positions.each do |pos|
|
269
|
+
raise IndexError, "#{pos} is not a valid position." if pos >= size
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
# Preprocess ranges, integers and array in appropriate ways
|
274
|
+
def preprocess_positions *positions
|
275
|
+
if positions.size == 1
|
276
|
+
case positions.first
|
277
|
+
when Integer
|
278
|
+
positions.first
|
279
|
+
when Range
|
280
|
+
size.times.to_a[positions.first]
|
281
|
+
else
|
282
|
+
raise ArgumentError, 'Unkown position type.'
|
283
|
+
end
|
284
|
+
else
|
285
|
+
positions
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|