red_amber 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,71 +3,145 @@
3
3
  module RedAmber
4
4
  # mix-ins for the class DataFrame
5
5
  module DataFrameVariableOperation
6
- # pick up some variables to create sub DataFrame
6
+ # Array is refined
7
+ using RefineArray
8
+
9
+ # Pick up variables (columns) to create a new DataFrame
10
+ #
11
+ # @note DataFrame#pick creates a DataFrame with single key.
12
+ # DataFrame#[] creates a Vector if single key is specified.
13
+ #
14
+ # @overload pick(keys)
15
+ # Pick variables by Symbols or Strings.
16
+ #
17
+ # @param keys [Symbol, String, <Symbol, String>]
18
+ # key name(s) of variables to pick.
19
+ # @return [DataFrame]
20
+ # Picked DataFrame.
21
+ #
22
+ # @overload pick(booleans)
23
+ # Pick variables by booleans.
24
+ #
25
+ # @param booleans [<true, false, nil>]
26
+ # boolean array to pick variables at true.
27
+ # @return [DataFrame]
28
+ # Picked DataFrame.
29
+ #
30
+ # @overload pick(indices)
31
+ # Pick variables by column indices.
32
+ #
33
+ # @param indices [Integer, Float, Range<Integer>, Vector, Arrow::Array]
34
+ # numeric array to pick variables by column index.
35
+ # @return [DataFrame]
36
+ # Picked DataFrame.
37
+ #
7
38
  def pick(*args, &block)
8
- picker = args
9
39
  if block
10
- raise DataFrameArgumentError, 'Must not specify both arguments and block.' unless args.empty?
40
+ unless args.empty?
41
+ raise DataFrameArgumentError, 'Must not specify both arguments and block.'
42
+ end
11
43
 
12
- picker = [instance_eval(&block)]
44
+ args = [instance_eval(&block)]
13
45
  end
14
- picker.flatten!
15
- return DataFrame.new if picker.empty? || picker == [nil]
16
-
17
- key_vector = Vector.new(keys)
18
- vec = parse_to_vector(picker, vsize: n_keys)
19
-
20
- ary =
21
- if vec.boolean?
22
- key_vector.filter(*vec).to_a
23
- elsif vec.numeric?
24
- key_vector.take(*vec).to_a
25
- elsif vec.string? || vec.dictionary?
26
- vec.to_a
27
- else
28
- raise DataFrameArgumentError, "Invalid argument #{args}"
29
- end
30
46
 
31
- # DataFrame#[] creates a Vector if single key is specified.
32
- # DataFrame#pick creates a DataFrame with single key.
33
- DataFrame.new(@table[ary])
47
+ case args
48
+ in [] | [nil]
49
+ return DataFrame.new
50
+ in [*] if args.symbols?
51
+ return DataFrame.create(@table.select_columns(*args))
52
+ in [*] if args.booleans?
53
+ picker = keys.select_by_booleans(args)
54
+ return DataFrame.create(@table.select_columns(*picker))
55
+ in [(Vector | Arrow::Array | Arrow::ChunkedArray) => a]
56
+ picker = a.to_a
57
+ else
58
+ picker = parse_args(args, n_keys)
59
+ end
60
+
61
+ return DataFrame.new if picker.compact.empty?
62
+
63
+ if picker.booleans?
64
+ picker = keys.select_by_booleans(picker)
65
+ return DataFrame.create(@table.select_columns(*picker))
66
+ end
67
+ picker.compact!
68
+ raise DataFrameArgumentError, "some keys are duplicated: #{args}" if picker.uniq!
69
+
70
+ DataFrame.create(@table.select_columns(*picker))
34
71
  end
35
72
 
36
- # drop some variables to create remainer sub DataFrame
73
+ # Drop some variables (columns) to create a remainer DataFrame
74
+ #
75
+ # @note DataFrame#drop creates a DataFrame even if it is a single column.
76
+ #
77
+ # @overload drop(keys)
78
+ # Drop variables by Symbols or Strings.
79
+ #
80
+ # @param keys [Symbol, String, <Symbol, String>]
81
+ # key name(s) of variables to drop.
82
+ # @return [DataFrame]
83
+ # Remainer DataFrame.
84
+ #
85
+ # @overload drop(booleans)
86
+ # Drop variables by booleans.
87
+ #
88
+ # @param booleans [<true, false, nil>]
89
+ # boolean array of variables to drop at true.
90
+ # @return [DataFrame]
91
+ # Remainer DataFrame.
92
+ #
93
+ # @overload drop(indices)
94
+ # Pick variables by column indices.
95
+ #
96
+ # @param indices [Integer, Float, Range<Integer>, Vector, Arrow::Array]
97
+ # numeric array of variables to drop by column index.
98
+ # @return [DataFrame]
99
+ # Remainer DataFrame.
100
+ #
37
101
  def drop(*args, &block)
38
- dropper = args
39
102
  if block
40
- raise DataFrameArgumentError, 'Must not specify both arguments and block.' unless args.empty?
103
+ unless args.empty?
104
+ raise DataFrameArgumentError, 'Must not specify both arguments and block.'
105
+ end
41
106
 
42
- dropper = [instance_eval(&block)]
107
+ args = [instance_eval(&block)]
43
108
  end
44
- dropper.flatten!
45
-
46
- key_vector = Vector.new(keys)
47
- vec = parse_to_vector(dropper, vsize: n_keys)
48
-
49
- ary =
50
- if vec.boolean?
51
- key_vector.filter(*vec.primitive_invert).each.map(&:to_sym) # Array
52
- elsif vec.numeric?
53
- keys - key_vector.take(*vec).each.map(&:to_sym) # Array
54
- elsif vec.string? || vec.dictionary?
55
- keys - vec.to_a.map { _1&.to_sym } # Array
109
+ return self if args.empty? || empty?
110
+
111
+ picker =
112
+ if args.symbols?
113
+ keys - args
114
+ elsif args.booleans?
115
+ keys.reject_by_booleans(args)
116
+ elsif args.integers?
117
+ keys.reject_by_indices(args)
56
118
  else
57
- raise DataFrameArgumentError, "Invalid argument #{args}"
119
+ dropper = parse_args(args, n_keys)
120
+ if dropper.booleans?
121
+ keys.reject_by_booleans(dropper)
122
+ elsif dropper.symbols?
123
+ keys - dropper
124
+ else
125
+ dropper.compact!
126
+ unless dropper.integers?
127
+ raise DataFrameArgumentError, "Invalid argument #{args}"
128
+ end
129
+
130
+ keys.reject_by_indices(dropper)
131
+ end
58
132
  end
59
133
 
60
- return DataFrame.new if ary.empty?
134
+ return DataFrame.new if picker.empty?
61
135
 
62
- # DataFrame#[] creates a Vector if single key is specified.
63
- # DataFrame#drop creates a DataFrame with single key.
64
- DataFrame.new(@table[ary])
136
+ DataFrame.create(@table.select_columns(*picker))
65
137
  end
66
138
 
67
139
  # rename variables to create a new DataFrame
68
140
  def rename(*renamer, &block)
69
141
  if block
70
- raise DataFrameArgumentError, 'Must not specify both arguments and a block' unless renamer.empty?
142
+ unless renamer.empty?
143
+ raise DataFrameArgumentError, 'Must not specify both arguments and a block'
144
+ end
71
145
 
72
146
  renamer = [instance_eval(&block)]
73
147
  end
@@ -90,35 +164,23 @@ module RedAmber
90
164
 
91
165
  # assign variables to create a new DataFrame
92
166
  def assign(*assigner, &block)
93
- appender, fields, arrays = assign_update(*assigner, &block)
94
- return self if appender.is_a?(DataFrame)
95
-
96
- append_to_fields_and_arrays(appender, fields, arrays, append_to_left: false) unless appender.empty?
97
-
98
- DataFrame.new(Arrow::Table.new(Arrow::Schema.new(fields), arrays))
167
+ assign_update(*assigner, append_to_left: false, &block)
99
168
  end
100
169
 
101
170
  def assign_left(*assigner, &block)
102
- appender, fields, arrays = assign_update(*assigner, &block)
103
- return self if appender.is_a?(DataFrame)
104
-
105
- append_to_fields_and_arrays(appender, fields, arrays, append_to_left: true) unless appender.empty?
106
-
107
- DataFrame.new(Arrow::Table.new(Arrow::Schema.new(fields), arrays))
171
+ assign_update(*assigner, append_to_left: true, &block)
108
172
  end
109
173
 
110
174
  private
111
175
 
112
- def assign_update(*assigner, &block)
176
+ def assign_update(*assigner, append_to_left: false, &block)
113
177
  if block
114
178
  assigner_from_block = instance_eval(&block)
115
179
  assigner =
116
- if assigner.empty?
117
- # block only
180
+ case assigner_from_block
181
+ in _ if assigner.empty? # block only
118
182
  [assigner_from_block]
119
- # If Ruby >= 3.0, one line pattern match can be used
120
- # assigner_from_block in [Array, *]
121
- elsif multiple_assigner?(assigner_from_block)
183
+ in [Vector, *] | [Array, *] | [Arrow::Array, *]
122
184
  assigner.zip(assigner_from_block)
123
185
  else
124
186
  assigner.zip([assigner_from_block])
@@ -128,10 +190,10 @@ module RedAmber
128
190
  case assigner
129
191
  in [] | [nil] | [{}] | [[]]
130
192
  return self
131
- in [Hash => key_array_pairs]
132
- # noop
133
193
  in [(Symbol | String) => key, (Vector | Array | Arrow::Array) => array]
134
194
  key_array_pairs = { key => array }
195
+ in [Hash => key_array_pairs]
196
+ # noop
135
197
  in [Array => array_in_array]
136
198
  key_array_pairs = try_convert_to_hash(array_in_array)
137
199
  in [Array, *] => array_in_array1
@@ -151,20 +213,27 @@ module RedAmber
151
213
  appender[key] = array
152
214
  end
153
215
  end
154
- [appender, *update_fields_and_arrays(updater)]
216
+ fields, arrays = *update_fields_and_arrays(updater)
217
+ return self if appender.is_a?(DataFrame)
218
+
219
+ unless appender.empty?
220
+ append_to_fields_and_arrays(appender, fields, arrays, append_to_left)
221
+ end
222
+
223
+ DataFrame.create(Arrow::Table.new(Arrow::Schema.new(fields), arrays))
155
224
  end
156
225
 
157
226
  def try_convert_to_hash(array)
158
227
  array.to_h
159
228
  rescue TypeError
160
229
  [array].to_h
161
- rescue TypeError # rubocop:disable Lint/DuplicateRescueException
162
- raise DataFrameArgumentError, "Invalid argument in Array #{array}"
163
230
  end
164
231
 
165
232
  def rename_by_hash(key_pairs)
166
233
  not_existing_keys = key_pairs.keys - keys
167
- raise DataFrameArgumentError, "Not existing: #{not_existing_keys}" unless not_existing_keys.empty?
234
+ unless not_existing_keys.empty?
235
+ raise DataFrameArgumentError, "Not existing: #{not_existing_keys}"
236
+ end
168
237
 
169
238
  fields =
170
239
  keys.map do |key|
@@ -175,7 +244,7 @@ module RedAmber
175
244
  @table.schema[key]
176
245
  end
177
246
  end
178
- DataFrame.new(Arrow::Table.new(Arrow::Schema.new(fields), @table.columns))
247
+ DataFrame.create(Arrow::Table.new(Arrow::Schema.new(fields), @table.columns))
179
248
  end
180
249
 
181
250
  def update_fields_and_arrays(updater)
@@ -185,7 +254,9 @@ module RedAmber
185
254
  data = updater[key]
186
255
  next unless data
187
256
 
188
- raise DataFrameArgumentError, "Data size mismatch (#{data.size} != #{size})" if data.nil? || data.size != size
257
+ if data.size != size
258
+ raise DataFrameArgumentError, "Data size mismatch (#{data.size} != #{size})"
259
+ end
189
260
 
190
261
  a = Arrow::Array.new(data.is_a?(Vector) ? data.to_a : data)
191
262
  fields[i] = Arrow::Field.new(key, a.value_data_type)
@@ -194,10 +265,12 @@ module RedAmber
194
265
  [fields, arrays]
195
266
  end
196
267
 
197
- def append_to_fields_and_arrays(appender, fields, arrays, append_to_left: false)
268
+ def append_to_fields_and_arrays(appender, fields, arrays, append_to_left)
198
269
  enum = append_to_left ? appender.reverse_each : appender.each
199
270
  enum.each do |key, data|
200
- raise DataFrameArgumentError, "Data size mismatch (#{data.size} != #{size})" if data.size != size
271
+ if data.size != size
272
+ raise DataFrameArgumentError, "Data size mismatch (#{data.size} != #{size})"
273
+ end
201
274
 
202
275
  a = Arrow::Array.new(data.is_a?(Vector) ? data.to_a : data)
203
276
 
@@ -210,14 +283,5 @@ module RedAmber
210
283
  end
211
284
  end
212
285
  end
213
-
214
- def multiple_assigner?(assigner)
215
- case assigner
216
- in [Vector, *] | [Array, *] | [Arrow::Array, *]
217
- true
218
- else
219
- false
220
- end
221
- end
222
286
  end
223
287
  end
@@ -5,6 +5,8 @@ module RedAmber
5
5
  class Group
6
6
  include Enumerable # This feature is experimental
7
7
 
8
+ using RefineArrowTable
9
+
8
10
  # Creates a new Group object.
9
11
  #
10
12
  # @param dataframe [DataFrame] dataframe to be grouped.
@@ -18,7 +20,6 @@ module RedAmber
18
20
  d = @group_keys - @dataframe.keys
19
21
  raise GroupArgumentError, "#{d} is not a key of\n #{@dataframe}." unless d.empty?
20
22
 
21
- @filters = @group_counts = @base_table = nil
22
23
  @group = @dataframe.table.group(*@group_keys)
23
24
  end
24
25
 
@@ -29,11 +30,14 @@ module RedAmber
29
30
  define_method(function) do |*summary_keys|
30
31
  summary_keys = Array(summary_keys).flatten
31
32
  d = summary_keys - @dataframe.keys
32
- raise GroupArgumentError, "#{d} is not a key of\n #{@dataframe}." unless summary_keys.empty? || d.empty?
33
+ unless summary_keys.empty? || d.empty?
34
+ raise GroupArgumentError, "#{d} is not a key of\n #{@dataframe}."
35
+ end
33
36
 
34
- table = @group.aggregate(*build_aggregation_keys("hash_#{function}", summary_keys))
35
- df = DataFrame.new(table)
36
- df.pick(@group_keys, df.keys - @group_keys)
37
+ table = @group.aggregate(*build_aggregation_keys("hash_#{function}",
38
+ summary_keys))
39
+ g = @group_keys.map(&:to_s)
40
+ DataFrame.new(table[g + (table.keys - g)])
37
41
  end
38
42
  end
39
43
 
@@ -76,7 +80,7 @@ module RedAmber
76
80
  end
77
81
 
78
82
  def group_count
79
- DataFrame.new(add_columns_to_table(base_table, [:group_count], [group_counts]))
83
+ DataFrame.create(add_columns_to_table(base_table, [:group_count], [group_counts]))
80
84
  end
81
85
 
82
86
  def inspect
@@ -95,6 +99,11 @@ module RedAmber
95
99
  end
96
100
  end
97
101
 
102
+ # experimental
103
+ def agg_sum(*summary_keys)
104
+ call_aggregating_function(:sum, summary_keys, _options = nil)
105
+ end
106
+
98
107
  private
99
108
 
100
109
  def build_aggregation_keys(function_name, summary_keys)
@@ -105,7 +114,7 @@ module RedAmber
105
114
  end
106
115
  end
107
116
 
108
- # @group_counts.sum == @dataframe.size
117
+ # @note `@group_counts.sum == @dataframe.size``
109
118
  def group_counts
110
119
  @group_counts ||= filters.map(&:sum)
111
120
  end
@@ -5,46 +5,68 @@ module RedAmber
5
5
  module Helper
6
6
  private
7
7
 
8
+ # If num is larger than 1 return 's' to be plural.
9
+ #
10
+ # @param num [Numeric] some number.
11
+ # @return ['s', ''] return 's' if num is larger than 1.
12
+ # Otherwise return ''.
8
13
  def pl(num)
9
14
  num > 1 ? 's' : ''
10
15
  end
11
16
 
12
- def booleans?(enum)
13
- enum.all? { |e| e.is_a?(TrueClass) || e.is_a?(FalseClass) || e.is_a?(NilClass) }
14
- end
15
-
16
- def parse_to_vector(args, vsize: size)
17
- a = args.reduce([]) do |accum, elem|
18
- accum.concat(normalize_element(elem, vsize: vsize))
17
+ # Parse the argments in an Array
18
+ # and returns a parsed Array.
19
+ #
20
+ # @param args
21
+ # [<Integer, Symbol, true, false, nil, Array, Range, Enumerator, String, Float>]
22
+ # arguments.
23
+ # @param array_size [Integer] size of target Array to use in a endless Range.
24
+ # @return [<Integer, Symbol, true, false, nil>] parsed flat Array.
25
+ # @note This method is recursively called to parse.
26
+ def parse_args(args, array_size)
27
+ args.flat_map do |elem|
28
+ case elem
29
+ when Integer, Symbol, NilClass, TrueClass, FalseClass
30
+ elem
31
+ when Array
32
+ parse_args(elem, array_size)
33
+ when Range
34
+ parse_range(elem, array_size)
35
+ when Enumerator
36
+ parse_args(Array(elem), array_size)
37
+ when String
38
+ elem.to_sym
39
+ when Float
40
+ elem.floor.to_i
41
+ else
42
+ Array(elem)
43
+ end
19
44
  end
20
- Vector.new(a)
21
45
  end
22
46
 
23
- def normalize_element(elem, vsize: size)
24
- case elem
25
- when NilClass
26
- [nil]
27
- when Range
28
- bg = elem.begin
29
- en = elem.end
30
- if [bg, en].any?(Integer)
31
- bg += vsize if bg&.negative?
32
- en += vsize if en&.negative?
33
- en -= 1 if en.is_a?(Integer) && elem.exclude_end?
34
- if bg&.negative? || (en && en >= vsize)
35
- raise DataFrameArgumentError, "Index out of range: #{elem} for 0..#{vsize - 1}"
36
- end
37
-
38
- Array(0...vsize)[elem]
39
- elsif bg.nil? && en.nil?
40
- Array(0...vsize)
41
- else
42
- Array(elem)
47
+ # Parse a Range to an Array
48
+ #
49
+ # @param range [Range] Range to parse.
50
+ # @param array_size [Integer] size of target Array to use in a endless Range.
51
+ # @return [Array<Integer, Symbol, String>] parsed Array.
52
+ def parse_range(range, array_size)
53
+ bg = range.begin
54
+ en = range.end
55
+ if [bg, en].any?(Integer)
56
+ bg += array_size if bg&.negative?
57
+ en += array_size if en&.negative?
58
+ en -= 1 if en.is_a?(Integer) && range.exclude_end?
59
+ if bg&.negative? || (en && en >= array_size)
60
+ raise IndexError, "Index out of range: #{range} for 0..#{array_size - 1}"
43
61
  end
44
- when Enumerator
45
- elem.to_a
62
+
63
+ Array(0...array_size)[range]
64
+ elsif bg.nil?
65
+ raise DataFrameArgumentError, "Cannot use beginless Range: #{range}"
66
+ elsif en.nil?
67
+ raise DataFrameArgumentError, "Cannot use endless Range: #{range}"
46
68
  else
47
- Array[elem]
69
+ Array(range)
48
70
  end
49
71
  end
50
72
  end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RedAmber
4
+ # Add additional capabilities to Hash
5
+ module RefineHash
6
+ refine Hash do
7
+ # Convert self to an Arrow::Table
8
+ def to_arrow
9
+ Arrow::Table.new(self)
10
+ end
11
+ end
12
+ end
13
+
14
+ # Add additional capabilities to Array-like classes
15
+ module RefineArrayLike
16
+ refine Array do
17
+ def to_arrow_array
18
+ Arrow::Array.new(self)
19
+ end
20
+ end
21
+
22
+ refine Range do
23
+ def to_arrow_array
24
+ Arrow::Array.new(Array(self))
25
+ end
26
+ end
27
+
28
+ # common methods for Arrow::Array and Arrow::ChunkedArray
29
+ # Refinement#include is deprecated and will be removed in Ruby 3.2
30
+ refine Arrow::Array do
31
+ def to_arrow_array
32
+ self
33
+ end
34
+
35
+ def type_class
36
+ value_data_type.class
37
+ end
38
+
39
+ def boolean?
40
+ value_data_type.instance_of?(Arrow::BooleanDataType)
41
+ end
42
+
43
+ def numeric?
44
+ value_data_type.class < Arrow::NumericDataType
45
+ end
46
+
47
+ def float?
48
+ value_data_type.class < Arrow::FloatingPointDataType
49
+ end
50
+
51
+ def integer?
52
+ value_data_type.class < Arrow::IntegerDataType
53
+ end
54
+
55
+ def list?
56
+ is_a? Arrow::ListArray
57
+ end
58
+
59
+ def unsigned_integer?
60
+ value_data_type.instance_of?(Arrow::UInt8DataType) ||
61
+ value_data_type.instance_of?(Arrow::UInt16DataType) ||
62
+ value_data_type.instance_of?(Arrow::UInt32DataType) ||
63
+ value_data_type.instance_of?(Arrow::UInt64DataType)
64
+ end
65
+
66
+ def string?
67
+ value_data_type.instance_of?(Arrow::StringDataType)
68
+ end
69
+
70
+ def dictionary?
71
+ value_data_type.instance_of?(Arrow::DictionaryDataType)
72
+ end
73
+
74
+ def temporal?
75
+ value_data_type.class < Arrow::TemporalDataType
76
+ end
77
+
78
+ def primitive_invert
79
+ n = Arrow::Function.find(:is_null).execute([self])
80
+ i = Arrow::Function.find(:if_else).execute([n, false, self])
81
+ Arrow::Function.find(:invert).execute([i]).value
82
+ end
83
+ end
84
+
85
+ refine Arrow::ChunkedArray do
86
+ def to_arrow_array
87
+ self
88
+ end
89
+
90
+ def type_class
91
+ value_data_type.class
92
+ end
93
+
94
+ def boolean?
95
+ value_data_type.instance_of?(Arrow::BooleanDataType)
96
+ end
97
+
98
+ def numeric?
99
+ value_data_type.class < Arrow::NumericDataType
100
+ end
101
+
102
+ def float?
103
+ value_data_type.class < Arrow::FloatingPointDataType
104
+ end
105
+
106
+ def integer?
107
+ value_data_type.class < Arrow::IntegerDataType
108
+ end
109
+
110
+ def unsigned_integer?
111
+ value_data_type.instance_of?(Arrow::UInt8DataType) ||
112
+ value_data_type.instance_of?(Arrow::UInt16DataType) ||
113
+ value_data_type.instance_of?(Arrow::UInt32DataType) ||
114
+ value_data_type.instance_of?(Arrow::UInt64DataType)
115
+ end
116
+
117
+ def string?
118
+ value_data_type.instance_of?(Arrow::StringDataType)
119
+ end
120
+
121
+ def dictionary?
122
+ value_data_type.instance_of?(Arrow::DictionaryDataType)
123
+ end
124
+
125
+ def temporal?
126
+ value_data_type.class < Arrow::TemporalDataType
127
+ end
128
+
129
+ def list?
130
+ value_type.nick == 'list'
131
+ end
132
+
133
+ def primitive_invert
134
+ n = Arrow::Function.find(:is_null).execute([self])
135
+ i = Arrow::Function.find(:if_else).execute([n, false, self])
136
+ Arrow::Function.find(:invert).execute([i]).value
137
+ end
138
+ end
139
+ end
140
+
141
+ # Add additional capabilities to Arrow::Table
142
+ module RefineArrowTable
143
+ refine Arrow::Table do
144
+ def keys
145
+ columns.map(&:name)
146
+ end
147
+
148
+ def key?(key)
149
+ keys.include?(key)
150
+ end
151
+ end
152
+ end
153
+
154
+ # Add additional capabilities to Array
155
+ module RefineArray
156
+ refine Array do
157
+ def integers?
158
+ all? { |e| e.is_a?(Integer) } # rubocop:disable Performance/RedundantEqualityComparisonBlock
159
+ end
160
+
161
+ def booleans?
162
+ all? { |e| e.is_a?(TrueClass) || e.is_a?(FalseClass) || e.is_a?(NilClass) }
163
+ end
164
+
165
+ def symbols?
166
+ all? { |e| e.is_a?(Symbol) } # rubocop:disable Performance/RedundantEqualityComparisonBlock
167
+ end
168
+
169
+ def strings?
170
+ all? { |e| e.is_a?(String) } # rubocop:disable Performance/RedundantEqualityComparisonBlock
171
+ end
172
+
173
+ def symbols_or_strings?
174
+ all? { |e| e.is_a?(Symbol) || e.is_a?(String) }
175
+ end
176
+
177
+ # convert booleans to indices
178
+ def booleans_to_indices
179
+ (0...size).select.with_index { |_, i| self[i] }
180
+ end
181
+
182
+ # select elements by booleans
183
+ def select_by_booleans(booleans)
184
+ select.with_index { |_, i| booleans[i] }
185
+ end
186
+
187
+ # reject elements by booleans
188
+ def reject_by_booleans(booleans)
189
+ reject.with_index { |_, i| booleans[i] }
190
+ end
191
+
192
+ # reject elements by indices
193
+ # notice: order by indices is not considered.
194
+ def reject_by_indices(indices)
195
+ reject.with_index { |_, i| indices.include?(i) || indices.include?(i - size) }
196
+ end
197
+ end
198
+ end
199
+ end