Related
My expectation for this code is the number 3. Why doesn't this work?
mixed_array=[1,'cat',2]
mixed_array.inject(0) do |memo,num|
memo += num if num.is_a?Integer
end
NoMethodError: undefined method `+' for nil:NilClass
You were almost there:
mixed_array.inject(0) do |memo, num|
next memo unless num.is_a?(Integer)
memo + num
end
#=> 3
Making your code working:
mixed_array.inject(0) do |memo, num|
memo += num if num.is_a?(Integer)
memo # return memo on each iteration, because it becomes nil with non-integer element
end
#=> 3
What you have doesn't work because:
The value of memo += num if num.is_a?Integer is nil when num is not an Integer.
The value of each block is fed to the next iteration as memo.
Your block evaluates to nil on the second iteration so you're going to end up trying to evaluate:
nil += 2 if 2.is_a? Integer
and there's your NoMethodError.
You're probably better off doing this in two steps for clarity:
mixed_array.select { |e| e.is_a? Integer }.inject(:+)
or maybe even the looser version:
mixed_array.select { |e| e.is_a? Numeric }.inject(:+)
or with newer versions of Ruby:
mixed_array.select { |e| e.is_a? Numeric }.sum
If you're not dogmatically opposed to ternaries then you could also say things like:
mixed_array.inject(0) { |memo, num| memo + (num.is_a?(Integer) ? num : 0) }
mixed_array.sum { |e| e.is_a?(Integer) ? e : 0 }
If you know that the non-numeric elements of mixed_array are strings that don't look like numbers or start with numbers (i.e. nothing like '0', '11 pancakes', ...) then you could say:
mixed_array.map(&:to_i).inject(:+)
mixed_array.inject(0) { |memo, num| memo + num.to_i }
...
but that's probably making too many assumptions.
You can do this a lot more easily if you consider it as a two stage operation rather than one:
mixed_array=[1,'cat',2]
mixed_array.grep(Integer).inject(0, :+)
# => 3
This filters out all the non-Integer elements from the array and adds the rest together.
Remember that inject takes the return value from the previous iteration as the seed for the next. Your if clause must return an alternate value. You end up with this if you fix it:
memo += num.is_a?(Integer) ? num : 0
You could also go with a good-enough solution like:
memo += num.to_i
Depending on what sort of data you're trying to screen out.
mixed_array = [1, 'cat', 2, [3, 4], :a, { b: 5, c: 6 }, 7]
mixed_array.reduce(0) { |tot, obj| tot += Integer(obj) rescue 0 }
#=> 10
When the array may include one or more floats and you want to return a float:
mixed_array = [1, 'cat', 2, [3, 4], :a, { b: 5, c: 6 }, 7, 8.123]
mixed_array.reduce(0) { |tot, obj| tot += Float(obj) rescue 0 }
#=> 18.122999999999998
See Kernel::Integer and Kernel::Float.
I would like to extend the Array class with a uniq_elements method which returns those elements with multiplicity of one. I also would like to use closures to my new method as with uniq. For example:
t=[1,2,2,3,4,4,5,6,7,7,8,9,9,9]
t.uniq_elements # => [1,3,5,6,8]
Example with closure:
t=[1.0, 1.1, 2.0, 3.0, 3.4, 4.0, 4.2, 5.1, 5.7, 6.1, 6.2]
t.uniq_elements{|z| z.round} # => [2.0, 5.1]
Neither t-t.uniq nor t.to_set-t.uniq.to_set works. I don't care of speed, I call it only once in my program, so it can be a slow.
Helper method
This method uses the helper:
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
This method is similar to Array#-. The difference is illustrated in the following example:
a = [3,1,2,3,4,3,2,2,4]
b = [2,3,4,4,3,4]
a - b #=> [1]
c = a.difference b #=> [1, 3, 2, 2]
As you see, a contains three 3's and b contains two, so the first two 3's in a are removed in constructing c (a is not mutated). When b contains as least as many instances of an element as does a, c contains no instances of that element. To remove elements beginning at the end of a:
a.reverse.difference(b).reverse #=> [3, 1, 2, 2]
Array#difference! could be defined in the obvious way.
I have found many uses for this method: here, here, here, here, here, here, here, here, here, here, here, here, here, here, here, here, here, here, here, here, here, here and here.
I have proposed that this method be added to the Ruby core.
When used with Array#-, this method makes it easy to extract the unique elements from an array a:
a = [1,3,2,4,3,4]
u = a.uniq #=> [1, 2, 3, 4]
u - a.difference(u) #=> [1, 2]
This works because
a.difference(u) #=> [3,4]
contains all the non-unique elements of a (each possibly more than once).
Problem at Hand
Code
class Array
def uniq_elements(&prc)
prc ||= ->(e) { e }
a = map { |e| prc[e] }
u = a.uniq
uniques = u - a.difference(u)
select { |e| uniques.include?(prc[e]) ? (uniques.delete(e); true) : false }
end
end
Examples
t = [1,2,2,3,4,4,5,6,7,7,8,9,9,9]
t.uniq_elements
#=> [1,3,5,6,8]
t = [1.0, 1.1, 2.0, 3.0, 3.4, 4.0, 4.2, 5.1, 5.7, 6.1, 6.2]
t.uniq_elements { |z| z.round }
# => [2.0, 5.1]
Here's another way.
Code
require 'set'
class Array
def uniq_elements(&prc)
prc ||= ->(e) { e }
uniques, dups = {}, Set.new
each do |e|
k = prc[e]
((uniques.key?(k)) ? (dups << k; uniques.delete(k)) :
uniques[k] = e) unless dups.include?(k)
end
uniques.values
end
end
Examples
t = [1,2,2,3,4,4,5,6,7,7,8,9,9,9]
t.uniq_elements #=> [1,3,5,6,8]
t = [1.0, 1.1, 2.0, 3.0, 3.4, 4.0, 4.2, 5.1, 5.7, 6.1, 6.2]
t.uniq_elements { |z| z.round } # => [2.0, 5.1]
Explanation
if uniq_elements is called with a block, it is received as the proc prc.
if uniq_elements is called without a block, prc is nil, so the first statement of the method sets prc equal to the default proc (lambda).
an initially-empty hash, uniques, contains representations of the unique values. The values are the unique values of the array self, the keys are what is returned when the proc prc is passed the array value and called: k = prc[e].
the set dups contains the elements of the array that have found to not be unique. It is a set (rather than an array) to speed lookups. Alternatively, if could be a hash with the non-unique values as keys, and arbitrary values.
the following steps are performed for each element e of the array self:
k = prc[e] is computed.
if dups contains k, e is a dup, so nothing more needs to be done; else
if uniques has a key k, e is a dup, so k is added to the set dups and the element with key k is removed from uniques; else
the element k=>e is added to uniques as a candidate for a unique element.
the values of unique are returned.
class Array
def uniq_elements
counts = Hash.new(0)
arr = map do |orig_val|
converted_val = block_given? ? (yield orig_val) : orig_val
counts[converted_val] += 1
[converted_val, orig_val]
end
uniques = []
arr.each do |(converted_val, orig_val)|
uniques << orig_val if counts[converted_val] == 1
end
uniques
end
end
t=[1,2,2,3,4,4,5,6,7,7,8,9,9,9]
p t.uniq_elements
t=[1.0, 1.1, 2.0, 3.0, 3.4, 4.0, 4.2, 5.1, 5.7, 6.1, 6.2]
p t.uniq_elements { |elmt| elmt.round }
--output:--
[1, 3, 5, 6, 8]
[2.0, 5.1]
Array#uniq does not find non-duplicated elements, rather Array#uniq removes duplicates.
Use Enumerable#tally:
class Array
def uniq_elements
tally.select { |_obj, nb| nb == 1 }.keys
end
end
t=[1,2,2,3,4,4,5,6,7,7,8,9,9,9]
t.uniq_elements # => [1,3,5,6,8]
If you are using Ruby < 2.7, you can get tally with the backports gem
require 'backports/2.7.0/enumerable/tally'
class Array
def uniq_elements
zip( block_given? ? map { |e| yield e } : self )
.each_with_object Hash.new do |(e, v), h| h[v] = h[v].nil? ? [e] : false end
.values.reject( &:! ).map &:first
end
end
[1,2,2,3,4,4,5,6,7,7,8,9,9,9].uniq_elements #=> [1, 3, 5, 6, 8]
[1.0, 1.1, 2.0, 3.0, 3.4, 4.0, 4.2, 5.1, 5.7, 6.1, 6.2].uniq_elements &:round #=> [2.0, 5.1]
Creating and calling a default proc is a waste of time, and
Cramming everything into one line using tortured constructs doesn't make the code more efficient--it just makes the code harder to understand.
In require statements, rubyists don't capitalize file names.
....
require 'set'
class Array
def uniq_elements
uniques = {}
dups = Set.new
each do |orig_val|
converted_val = block_given? ? (yield orig_val) : orig_val
next if dups.include? converted_val
if uniques.include?(converted_val)
uniques.delete(converted_val)
dups << converted_val
else
uniques[converted_val] = orig_val
end
end
uniques.values
end
end
t=[1,2,2,3,4,4,5,6,7,7,8,9,9,9]
p t.uniq_elements
t=[1.0, 1.1, 2.0, 3.0, 3.4, 4.0, 4.2, 5.1, 5.7, 6.1, 6.2]
p t.uniq_elements {|elmt|
elmt.round
}
--output:--
[1, 3, 5, 6, 8]
[2.0, 5.1]
c = [1, 2, 3, 4, 5, 6, 7, nil, nil, nil]
c.map { |i| p i if i > 10 }
NoMethodError: undefined method `>' for nil:NilClass
How to avoid nil values during comparison?
Try as below :
c.each { |i| p i if i.to_i > 10 } # fix is to use `to_i`
Use each method instead of map, when you want to traverse the array, and outputs some elements when there is a match, as you are doing. If you use the method to_i, then whenever there is nil in your source array, then nil.to_i will convert it to 0. And if it found any integers like 1,2 etc, then you will get back the same integers.
Look also NilClass#to_i and Fixnum#to_i methods.
There are a couple ways.
Use Array.compact:
c.compact.each { |i| p i if i > 10 }
The Array#compact method gives you a new array with all nils removed.
Use next:
c.each do |i|
next if i.nil?
p i if i > 10
end
This avoids creating a new array.
Note the use of each instead of map. The latter is used when you are creating a new array with the result of applying the block to each element in the original array, such as c.map { |i| i + 1 }, which is equivalent to c.map &:succ.
If all you need to do is iterate over the array and apply side effects such as printing numbers, each should suffice.
Try this one:
c = [1, 2, 3, 4, 5, 6, 7, nil, nil, nil]
c.compact.each{|i| p i if i > 10}
c.each { |i| p i if !i.nil? && i > 10 }
I have a map which either changes a value or sets it to nil. I then want to remove the nil entries from the list. The list doesn't need to be kept.
This is what I currently have:
# A simple example function, which returns a value or nil
def transform(n)
rand > 0.5 ? n * 10 : nil }
end
items.map! { |x| transform(x) } # [1, 2, 3, 4, 5] => [10, nil, 30, 40, nil]
items.reject! { |x| x.nil? } # [10, nil, 30, 40, nil] => [10, 30, 40]
I'm aware I could just do a loop and conditionally collect in another array like this:
new_items = []
items.each do |x|
x = transform(x)
new_items.append(x) unless x.nil?
end
items = new_items
But it doesn't seem that idiomatic. Is there a nice way to map a function over a list, removing/excluding the nils as you go?
You could use compact:
[1, nil, 3, nil, nil].compact
=> [1, 3]
I'd like to remind people that if you're getting an array containing nils as the output of a map block, and that block tries to conditionally return values, then you've got code smell and need to rethink your logic.
For instance, if you're doing something that does this:
[1,2,3].map{ |i|
if i % 2 == 0
i
end
}
# => [nil, 2, nil]
Then don't. Instead, prior to the map, reject the stuff you don't want or select what you do want:
[1,2,3].select{ |i| i % 2 == 0 }.map{ |i|
i
}
# => [2]
I consider using compact to clean up a mess as a last-ditch effort to get rid of things we didn't handle correctly, usually because we didn't know what was coming at us. We should always know what sort of data is being thrown around in our program; Unexpected/unknown data is bad. Anytime I see nils in an array I'm working on, I dig into why they exist, and see if I can improve the code generating the array, rather than allow Ruby to waste time and memory generating nils then sifting through the array to remove them later.
'Just my $%0.2f.' % [2.to_f/100]
Try using reduce or inject.
[1, 2, 3].reduce([]) { |memo, i|
if i % 2 == 0
memo << i
end
memo
}
I agree with the accepted answer that we shouldn't map and compact, but not for the same reasons.
I feel deep inside that map then compact is equivalent to select then map. Consider: map is a one-to-one function. If you are mapping from some set of values, and you map, then you want one value in the output set for each value in the input set. If you are having to select before-hand, then you probably don't want a map on the set. If you are having to select afterwards (or compact) then you probably don't want a map on the set. In either case you are iterating twice over the entire set, when a reduce only needs to go once.
Also, in English, you are trying to "reduce a set of integers into a set of even integers".
Ruby 2.7+
There is now!
Ruby 2.7 is introducing filter_map for this exact purpose. It's idiomatic and performant, and I'd expect it to become the norm very soon.
For example:
numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]
In your case, as the block evaluates to falsey, simply:
items.filter_map { |x| process_x url }
"Ruby 2.7 adds Enumerable#filter_map" is a good read on the subject, with some performance benchmarks against some of the earlier approaches to this problem:
N = 100_000
enum = 1.upto(1_000)
Benchmark.bmbm do |x|
x.report("select + map") { N.times { enum.select { |i| i.even? }.map{ |i| i + 1 } } }
x.report("map + compact") { N.times { enum.map { |i| i + 1 if i.even? }.compact } }
x.report("filter_map") { N.times { enum.filter_map { |i| i + 1 if i.even? } } }
end
# Rehearsal -------------------------------------------------
# select + map 8.569651 0.051319 8.620970 ( 8.632449)
# map + compact 7.392666 0.133964 7.526630 ( 7.538013)
# filter_map 6.923772 0.022314 6.946086 ( 6.956135)
# --------------------------------------- total: 23.093686sec
#
# user system total real
# select + map 8.550637 0.033190 8.583827 ( 8.597627)
# map + compact 7.263667 0.131180 7.394847 ( 7.405570)
# filter_map 6.761388 0.018223 6.779611 ( 6.790559)
Definitely compact is the best approach for solving this task. However, we can achieve the same result just with a simple subtraction:
[1, nil, 3, nil, nil] - [nil]
=> [1, 3]
In your example:
items.map! { |x| process_x url } # [1, 2, 3, 4, 5] => [1, nil, 3, nil, nil]
it does not look like the values have changed other than being replaced with nil. If that is the case, then:
items.select{|x| process_x url}
will suffice.
If you wanted a looser criterion for rejection, for example, to reject empty strings as well as nil, you could use:
[1, nil, 3, 0, ''].reject(&:blank?)
=> [1, 3, 0]
If you wanted to go further and reject zero values (or apply more complex logic to the process), you could pass a block to reject:
[1, nil, 3, 0, ''].reject do |value| value.blank? || value==0 end
=> [1, 3]
[1, nil, 3, 0, '', 1000].reject do |value| value.blank? || value==0 || value>10 end
=> [1, 3]
You can use #compact method on the resulting array.
[10, nil, 30, 40, nil].compact => [10, 30, 40]
each_with_object is probably the cleanest way to go here:
new_items = items.each_with_object([]) do |x, memo|
ret = process_x(x)
memo << ret unless ret.nil?
end
In my opinion, each_with_object is better than inject/reduce in conditional cases because you don't have to worry about the return value of the block.
One more way to accomplish it will be as shown below. Here, we use Enumerable#each_with_object to collect values, and make use of Object#tap to get rid of temporary variable that is otherwise needed for nil check on result of process_x method.
items.each_with_object([]) {|x, obj| (process x).tap {|r| obj << r unless r.nil?}}
Complete example for illustration:
items = [1,2,3,4,5]
def process x
rand(10) > 5 ? nil : x
end
items.each_with_object([]) {|x, obj| (process x).tap {|r| obj << r unless r.nil?}}
Alternate approach:
By looking at the method you are calling process_x url, it is not clear what is the purpose of input x in that method. If I assume that you are going to process the value of x by passing it some url and determine which of the xs really get processed into valid non-nil results - then, may be Enumerabble.group_by is a better option than Enumerable#map.
h = items.group_by {|x| (process x).nil? ? "Bad" : "Good"}
#=> {"Bad"=>[1, 2], "Good"=>[3, 4, 5]}
h["Good"]
#=> [3,4,5]
(1..4).collect do |x|
next if x == 3
x + 1
end # => [2, 3, nil, 5]
# desired => [2, 3, 5]
If the condition for next is met, collect puts nil in the array, whereas what I'm trying to do is put no element in the returned array if the condition is met. Is this possible without calling delete_if { |x| x == nil } on the returned array?
My code excerpt is heavily abstracted, so looking for a general solution to the problem.
There is method Enumerable#reject which serves just the purpose:
(1..4).reject{|x| x == 3}.collect{|x| x + 1}
The practice of directly using an output of one method as an input of another is called method chaining and is very common in Ruby.
BTW, map (or collect) is used for direct mapping of input enumerable to the output one. If you need to output different number of elements, chances are that you need another method of Enumerable.
Edit: If you are bothered by the fact that some of the elements are iterated twice, you can use less elegant solution based on inject (or its similar method named each_with_object):
(1..4).each_with_object([]){|x,a| a << x + 1 unless x == 3}
I would simply call .compact on the resultant array, which removes any instances of nil in an array. If you'd like it to modify the existing array (no reason not to), use .compact!:
(1..4).collect do |x|
next if x == 3
x
end.compact!
In Ruby 2.7+, it’s possible to use filter_map for this exact purpose. From the docs:
Returns an array containing truthy elements returned by the block.
(0..9).filter_map {|i| i * 2 if i.even? } #=> [0, 4, 8, 12, 16]
{foo: 0, bar: 1, baz: 2}.filter_map {|key, value| key if value.even? } #=> [:foo, :baz]
For the example in the question: (1..4).filter_map { |x| x + 1 unless x == 3 }.
See this post for comparison with alternative methods, including benchmarks.
just a suggestion, why don't you do it this way:
result = []
(1..4).each do |x|
next if x == 3
result << x
end
result # => [1, 2, 4]
in that way you saved another iteration to remove nil elements from the array. hope it helps =)
i would suggest to use:
(1..4).to_a.delete_if {|x| x == 3}
instead of the collect + next statement.
You could pull the decision-making into a helper method, and use it via Enumerable#reduce:
def potentially_keep(list, i)
if i === 3
list
else
list.push i
end
end
# => :potentially_keep
(1..4).reduce([]) { |memo, i| potentially_keep(memo, i) }
# => [1, 2, 4]