daru 0.1.4.1 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.travis.yml +3 -0
  4. data/CONTRIBUTING.md +27 -3
  5. data/Guardfile +7 -0
  6. data/History.md +39 -1
  7. data/README.md +1 -1
  8. data/daru.gemspec +9 -2
  9. data/lib/daru.rb +4 -1
  10. data/lib/daru/accessors/gsl_wrapper.rb +93 -91
  11. data/lib/daru/accessors/nmatrix_wrapper.rb +109 -107
  12. data/lib/daru/category.rb +22 -15
  13. data/lib/daru/core/group_by.rb +13 -2
  14. data/lib/daru/core/merge.rb +37 -31
  15. data/lib/daru/core/query.rb +10 -2
  16. data/lib/daru/dataframe.rb +95 -34
  17. data/lib/daru/date_time/index.rb +15 -16
  18. data/lib/daru/date_time/offsets.rb +14 -11
  19. data/lib/daru/formatters/table.rb +2 -2
  20. data/lib/daru/index/categorical_index.rb +201 -0
  21. data/lib/daru/index/index.rb +289 -0
  22. data/lib/daru/index/multi_index.rb +266 -0
  23. data/lib/daru/maths/statistics/vector.rb +13 -9
  24. data/lib/daru/monkeys.rb +0 -7
  25. data/lib/daru/plotting/gruff/category.rb +1 -0
  26. data/lib/daru/plotting/gruff/dataframe.rb +3 -3
  27. data/lib/daru/plotting/nyaplot/dataframe.rb +1 -1
  28. data/lib/daru/vector.rb +36 -21
  29. data/lib/daru/version.rb +1 -1
  30. data/spec/accessors/array_wrapper_spec.rb +3 -0
  31. data/spec/accessors/{wrappers_spec.rb → gsl_wrapper_spec.rb} +0 -35
  32. data/spec/accessors/nmatrix_wrapper_spec.rb +32 -0
  33. data/spec/{categorical_spec.rb → category_spec.rb} +3 -0
  34. data/spec/core/group_by_spec.rb +17 -1
  35. data/spec/core/merge_spec.rb +38 -1
  36. data/spec/core/query_spec.rb +5 -0
  37. data/spec/dataframe_spec.rb +230 -57
  38. data/spec/date_time/offsets_spec.rb +84 -3
  39. data/spec/formatters/table_formatter_spec.rb +9 -0
  40. data/spec/index/categorical_index_spec.rb +2 -0
  41. data/spec/index/index_spec.rb +17 -2
  42. data/spec/{math → maths}/arithmetic/dataframe_spec.rb +0 -0
  43. data/spec/{math → maths}/arithmetic/vector_spec.rb +0 -0
  44. data/spec/{math → maths}/statistics/dataframe_spec.rb +1 -1
  45. data/spec/{math → maths}/statistics/vector_spec.rb +7 -12
  46. data/spec/plotting/gruff/category_spec.rb +44 -0
  47. data/spec/plotting/gruff/dataframe_spec.rb +84 -0
  48. data/spec/plotting/gruff/vector_spec.rb +70 -0
  49. data/spec/plotting/nyaplot/category_spec.rb +51 -0
  50. data/spec/plotting/{dataframe_spec.rb → nyaplot/dataframe_spec.rb} +0 -83
  51. data/spec/plotting/nyaplot/vector_spec.rb +66 -0
  52. data/spec/spec_helper.rb +3 -2
  53. data/spec/vector_spec.rb +68 -1
  54. metadata +53 -24
  55. data/lib/daru/index.rb +0 -761
  56. data/spec/plotting/vector_spec.rb +0 -230
@@ -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 [Fixnum] :periods The number of periods that should go into
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?(Fixnum) && last.is_a?(Fixnum)
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 [Fixnum, Daru::DateOffset, Daru::Offsets::*] distance Distance by
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?(Fixnum) && distance < 0 and
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 [Fixnum, Daru::DateOffset, Daru::Offsets::*] distance Fixnum or
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?(Fixnum) && distance < 0 and
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<Fixnum>] Array containing year of each index.
482
+ # @return [Array<Integer>] Array containing year of each index.
484
483
  # @!method month
485
- # @return [Array<Fixnum>] Array containing month of each index.
484
+ # @return [Array<Integer>] Array containing month of each index.
486
485
  # @!method day
487
- # @return [Array<Fixnum>] Array containing day of each index.
486
+ # @return [Array<Integer>] Array containing day of each index.
488
487
  # @!method hour
489
- # @return [Array<Fixnum>] Array containing hour of each index.
488
+ # @return [Array<Integer>] Array containing hour of each index.
490
489
  # @!method min
491
- # @return [Array<Fixnum>] Array containing minutes of each index.
490
+ # @return [Array<Integer>] Array containing minutes of each index.
492
491
  # @!method sec
493
- # @return [Array<Fixnum>] Array containing seconds of each index.
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?(Fixnum) && last.is_a?(Fixnum)
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?(Fixnum)
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.1574074074074073e-05
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
- 0.0006944444444444445
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
- 0.041666666666666664
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.0
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 = !args[0].is_a?(Hash)? args[0] : 1
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