Module: Enumerable

Included in:
RangeList, Table, Vector
Defined in:
lib/quality_extensions/array/all_same.rb,
lib/quality_extensions/symbol/match.rb,
lib/quality_extensions/color/gradiate.rb,
lib/quality_extensions/enumerable/every.rb,
lib/quality_extensions/enumerable/all_same.rb,
lib/quality_extensions/enumerable/select_bang.rb,
lib/quality_extensions/enumerable/grep_indexes.rb,
lib/quality_extensions/enumerable/max_by_value.rb,
lib/quality_extensions/enumerable/select_while.rb,
lib/quality_extensions/enumerable/map_with_index.rb,
lib/quality_extensions/enumerable/grep_with_index.rb,
lib/quality_extensions/enumerable/grep_plus_offset.rb,
lib/quality_extensions/enumerable/group_by_and_map.rb,
lib/quality_extensions/enumerable/select_with_index.rb

Overview

Author

Tyler Rick, Erik Veenstra

Copyright

-

License

-

Submit to Facets?

Maybe

  • Created, based on Facets group_by

++

Instance Method Summary collapse

Instance Method Details

#all_same?Boolean

Returns:

  • (Boolean)


11
12
13
# File 'lib/quality_extensions/array/all_same.rb', line 11

def all_same?
  all? {|a| a == first}
end

#every(n) ⇒ Object

Yields every nth object (if invoked with a block), or returns an array of every nth object.

every(2), for example, would return every other element from the enumerable:

[1, 2, 3, 4, 5, 6].every(2)               -> [1, 3, 5]
[1, 2, 3, 4, 5, 6].every(2) { |i| ... }   -> nil


23
24
25
26
27
28
29
30
31
# File 'lib/quality_extensions/enumerable/every.rb', line 23

def every(n)
  result = [] unless block_given?
  each_with_index do |object, i|
    if i % n == 0
      block_given?? yield(object) : result << object
    end
  end
  return block_given?? nil : result
end

#gradiate(options = {}) ⇒ Object

Sorts objects in the enumeration and applies a color scale to them.

Color ranges must be in the form [x, y], where x and y are either fixnums (e.g. 255, 0xFF) or hexadecimal strings (e.g. ‘FF’).

Ranges can be provided for each RGB color e.g.

gradiate(:red => red_range)

…and a default range (for all colors) can be set using :all e.g.

gradiate(:all => default_range, :green => green_range)

If no color ranges are supplied then the sorted enumeration will be returned.

Objects contained in the enumeration are expected to have a color (or colour) attribute/method that returns a Color::RGB object (or similar).

By default, objects are sorted using :to_i. This can be overidden by setting options[:compare_using] to a different method symbol.

By default, objects are ordered “smallest” first. To reverse this set options[:order] to either :desc or :reverse.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/quality_extensions/color/gradiate.rb', line 42

def gradiate(options={})
  ranges = [:red, :green, :blue].map do |col|
    if range = (options[col] || options[:all])
      a, b = range.map { |x| x.respond_to?(:hex) ? x.hex : x.to_i }
      a, b = b, a if a > b # smallest first
      c = b.diff(a) / (self.size - 1)
      next (a..b).every(c)
    else [] end
  end

  objects = sort_by { |object| object.send(options[:compare_using] || :to_i) }
  objects = objects.reverse if [:desc, :reverse].include?(options[:order])
  objects.zip(*ranges).collect do |object, red, green, blue|
    color = object.respond_to?(:colour) ? object.colour : object.color
    color.red = red if red
    color.green = green if green
    color.blue = blue if blue
    next object
  end
end

#grep_indexes(regexp) ⇒ Object

Returns the indexes of the elements that match regexp.

%w[a ab abc].grep_indexes(/a$/) => [0] %w[a ab abc].grep_indexes(/b/) => [1, 2]



17
18
19
20
21
22
23
# File 'lib/quality_extensions/enumerable/grep_indexes.rb', line 17

def grep_indexes(regexp)
  indexes = []
  each_with_index {|el, i|
    indexes << i if el =~ regexp
  }
  indexes
end

#grep_plus_offset(regexp, offset, wrap_around = false) ⇒ Object

For each element that matches regexp return the element that is offset elements forward.

Examples:

%w[1 2 3].grep_plus_offset(/1/, -1) => [nil] %w[1 2 3].grep_plus_offset(/1/, 0) => [‘1’] %w[1 2 3].grep_plus_offset(/1/, 1) => [‘2’] %w[1 2 3].grep_plus_offset(/1/, 2) => [‘3’] %w[1 2 3].grep_plus_offset(/1/, 3) => [nil]

caller(0).grep_plus_offset(/in ‘synchronize’/, 1) => the line that called synchronize



28
29
30
31
32
33
34
35
36
# File 'lib/quality_extensions/enumerable/grep_plus_offset.rb', line 28

def grep_plus_offset(regexp, offset, wrap_around = false)
  indexes = grep_indexes(regexp).map_send(:+, offset)
  # If any indexes are negative, replace with (maximum index + 1) so that values_at will return
  # nil for that element (instead of returning an element from the end -- values_at(-1) returns
  # the last element, for example), the same as how providing a positive that results in an offset
  # > maximum_index (length - 1) results in a nil being returned for that index.
  indexes.map! {|_| _ < 0 ? length : _ } unless wrap_around
  values_at *indexes
end

#grep_with_index(pattern) ⇒ Object



24
25
26
27
28
# File 'lib/quality_extensions/enumerable/grep_with_index.rb', line 24

def grep_with_index(pattern)
  $debug = true
  each.with_index.grep(pattern)
  $debug = false
end

#grep_with_regexp_support_for_symbols(pattern, &block) ⇒ Object



87
88
89
90
91
# File 'lib/quality_extensions/symbol/match.rb', line 87

def grep_with_regexp_support_for_symbols(pattern, &block)
  map { |element|
    element.is_a?(Symbol) ? element.to_s : element
  }.grep_without_regexp_support_for_symbols(pattern, &block)
end

#group_by_and_mapObject

#group_by_and_map is used to group items in a collection by something they have in common. The common factor is the key in the resulting hash, the array of like elements is the value.

This differs from the normal group_by in that it lets you map the values (perhaps removing the key from the value since that would be redundant) all in one step.

# need better example
(1..6).group_by_and_map { |n| next n % 3, n }
     # => { 0 => [3,6], 1 => [1, 4], 2 => [2,5] }

[
  ['31a4', 'v1.3'],
  ['9f2b', 'current'],
  ['9f2b', 'v2.0']
].group_by_and_map { |e| e[0], e[1] }
     # => {"31a4"=>["v1.3"], "9f2b"=>["current", "v2.0"]}

results.group_by_and_map { |a| a[0], a[1..-1] }

CREDIT: Erik Veenstra, Tyler Rick



37
38
39
40
41
42
43
44
45
# File 'lib/quality_extensions/enumerable/group_by_and_map.rb', line 37

def group_by_and_map #:yield:
  h = Hash.new
  each { |e| 
    result = yield(e)
    #p result
    (h[result[0]] ||= []) << result[1]
  }
  h
end

#map_with_index(&block) ⇒ Object



19
20
21
# File 'lib/quality_extensions/enumerable/map_with_index.rb', line 19

def map_with_index(&block)
  dup.map_with_index!(&block)
end

#map_with_index!Object



13
14
15
16
17
# File 'lib/quality_extensions/enumerable/map_with_index.rb', line 13

def map_with_index!
  each_with_index do |e, i|
    self[i] = yield(e, i)
  end
end

#max_by_value(&block) ⇒ Object

Instead of returns the object in enum that gives the maximum value from the given block, like max_by does, returns the *maximum value* calculated by the given block (which is tested on each object in enum, just like in max_by).

Notice the difference:

['a','abc','ab'].max_by       {|el| el.length}.should == 'abc'
['a','abc','ab'].max_by_value {|el| el.length}.should == 3


22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/quality_extensions/enumerable/max_by_value.rb', line 22

def max_by_value(&block)
  max_value = nil
  each do |el|
    value = yield el
    if max_value.nil?
      max_value = value
    else
      max_value = [value, max_value].max
    end
  end
  max_value
end

#reject_with_indexObject



42
43
44
45
46
47
48
49
# File 'lib/quality_extensions/enumerable/select_with_index.rb', line 42

def reject_with_index
  index = -1
  if block_given?
    reject { |x| index += 1; yield(x, index) }
  else
    self
  end
end

#reject_with_index!Object



51
52
53
54
55
56
57
58
# File 'lib/quality_extensions/enumerable/select_with_index.rb', line 51

def reject_with_index!
  index = -1
  if block_given?
    reject! { |x| index += 1; yield(x, index) }
  else
    self
  end
end

#select!Object

end



19
20
21
# File 'lib/quality_extensions/enumerable/select_bang.rb', line 19

def select!
  reject! { |x| !yield(x) }
end

#select_from(inclusive = true) ⇒ Object Also known as: select_all_starting_with, reject_until

Returns all elements of enum starting at the first element for which block is truthy, and continuing until the end.

Examples:

(0..3).select_from {|v| v == 1} # => [1, 2, 3] (0..3).select_from(false) {|v| v == 1} # => [2, 3]

‘b’=>2, ‘c’=>3, ‘d’=>1.select_from {|k, v| v == 2} ) # => “c”=>3, “d”=>1 ‘b’=>2, ‘c’=>3, ‘d’=>1.select_from(false) {|k, v| v == 2} ) # => “d”=>1

Compare to Array#from in ActiveSupport. (maybe should rename to #from?)



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/quality_extensions/enumerable/select_while.rb', line 221

def select_from(inclusive = true)
  return self unless block_given?

  selecting = false
  select do |*args|
    # Once we hit the first truthy result, selecting will be true to the end
    select_this_el = !!yield(*args)
    this_is_first_true = !selecting && select_this_el
    selecting = selecting || select_this_el
    if inclusive
      selecting
    else
      selecting && !this_is_first_true
    end
  end
end

#select_until(inclusive = true) ⇒ Object

Returns an array containing all consecutive elements of enum for which block is not false, starting at the first element. So it is very much like select, but unlike select, it stops searching as soon as block ceases to be true. (That means it will stop searching immediately if the first element doesn’t match.)

This might be preferable to select, for example, if:

  • you have a very large collection of elements

  • the desired elements are expected to all be consecutively occuring and are all at the beginning of the collection

  • it would be costly to continue iterating all the way to the very end

If inclusive is false, only those elements occuring before the first element for which block is true will be returned. If inclusive is true (the default), those elements occuring up to and including the first element for which block is true will be returned.

Examples:

(0..3).select_until {|v| v == 1} # => [0, 1] (0..3).select_until(false) {|v| v == 1} # => [0]

‘b’=>2, ‘c’=>3, ‘d’=>1.select_until {|k, v| v == 2} ) # => “b”=>2 ‘b’=>2, ‘c’=>3, ‘d’=>1.select_until(false) {|k, v| v == 2} ) # => “a”=>1

puts caller # => 30 lines of context, many of them so far removed that they are irrelevant puts caller.select_until {|l| l =~ %r(/app/) } # only print the stack back to the first frame from our own code



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/quality_extensions/enumerable/select_while.rb', line 57

def select_until(inclusive = true)
  return self unless block_given?

  done = false
  if inclusive
    select do |*args|
      returning = !done
      done = true if yield(*args)
      returning
    end
  else
    select do |*args|
      done = true if yield(*args)
      !done
    end
  end
end

#select_until_last(inclusive = true) ⇒ Object

Whereas select_until goes until it reaches the first element for which block is true, select_until_last goes until it reaches the last element for which block is true.



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/quality_extensions/enumerable/select_while.rb', line 138

def select_until_last(inclusive = true)
  return self unless block_given?

  # Find the index of the last-matching element
  last = nil
  #each_with_index do |item, i|
  #  last = i if yield(item)
  #end
  each_with_index do |*args|
    i = args.pop
    last = i if yield(*args.first)
  end

  # If they want to exclude the last-matching element, adjust the index by -1 (if possible)
  #if last && !inclusive
  #  if last == 0
  #    # If the last-matching element was also the first element, then we want to select *none* of the elements
  #    last = nil
  #  else
  #    last -= 1
  #  end
  #end
  # Select all elements up to last-matching element
  #self.to_a[0 .. last]   # (didn't work for hashes)
  #select_with_index do |item, i|
  #  true if last && i <= last
  #end
  #select_while_with_index do |item, i|
  #  #puts "#{i} <= #{last} => #{last && i <= last}"
  #  last && i <= last
  #end

  # Select all elements up to last-matching element
  if last.nil?
    select do |*args|
      false
    end
  else
    select_until_with_index(inclusive) do |*args|
      i = args.last
      i == last
    end
  end
end

#select_until_last_with_index(inclusive = true) ⇒ Object



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/quality_extensions/enumerable/select_while.rb', line 183

def select_until_last_with_index(inclusive = true)
  return self unless block_given?

  # Find the index of the last-matching element
  last = nil
  each_with_index do |*args|
    #p args
    i = args.last
    last = i if yield(*args)
  end

  # Select all elements up to last-matching element
  if last.nil?
    select do |*args|
      false
    end
  else
    select_until_with_index(inclusive) do |*args|
      i = args.last
      i == last
    end
  end
end

#select_until_with_index(inclusive = true) ⇒ Object

Same as select_until but each time an element of the enum is yielded, an index/counter is also included as an argument.

This is probably only useful for collections that have some kind of predictable ordering (such as Arrays). Fortunately, Hashes have a predictable order in Ruby 1.9.



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/quality_extensions/enumerable/select_while.rb', line 79

def select_until_with_index(inclusive = true)
  return self unless block_given?

  done = false
  if inclusive
    select_with_index do |*args|
      returning = !done
      done = true if yield(*args)
      returning
    end
  else
    select_with_index do |*args|
      done = true if yield(*args)
      !done
    end
  end
end

#select_while(include_first_false_element = false) ⇒ Object

Better name? select_consec?



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/quality_extensions/enumerable/select_while.rb', line 98

def select_while(include_first_false_element = false)
  return self unless block_given?

  done = false
  if include_first_false_element
    select do |*args|
      returning = !done
      done = true if !yield(*args)
      returning
    end
  else
    select do |*args|
      #puts "!done=#{!done};  !yield(#{args}) => #{!yield(*args)}"
      done = true if !yield(*args)
      !done
    end
  end
end

#select_while_with_index(include_first_false_element = false) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/quality_extensions/enumerable/select_while.rb', line 117

def select_while_with_index(include_first_false_element = false)
  return self unless block_given?

  done = false
  if include_first_false_element
    select_with_index do |*args|
      returning = !done
      done = true if !yield(*args)
      returning
    end
  else
    select_with_index do |*args|
      #puts "!done=#{!done};  !yield(#{args}) => #{!yield(*args)}"
      done = true if !yield(*args)
      !done
    end
  end
end

#select_with_indexObject

end



23
24
25
26
27
28
29
30
31
# File 'lib/quality_extensions/enumerable/select_with_index.rb', line 23

def select_with_index
  index = -1
  if block_given?
    #select { |x| index += 1; yield(x, index) }  # not hash friendly?
    select { |*args| index += 1; yield(*(args + [index])) }
  else
    self
  end
end

#select_with_index!Object



33
34
35
36
37
38
39
40
# File 'lib/quality_extensions/enumerable/select_with_index.rb', line 33

def select_with_index!
  index = -1
  if block_given?
    select! { |x| index += 1; yield(x, index) }
  else
    self
  end
end