Making map! enumerator do what I want - ruby

Having an array
a = 1, 2, 3, 4
And an enumerator:
e = a.map!
Then, by calling e.next repeatedly, array a gets nicely destroyed:
e.next #=> 1
a #=> [1, 2, 3, 4]
e.next #=> 2
a #=> [nil, 2, 3, 4]
e.next #=> 3
a #=> [nil, nil, 3, 4]
That's so hilarious! But when I try
e.next { |x| 2 * x } # => 4
I get
a #=> [nil, nil, nil, 4]
instead of desired
a #=> [nil, nil, nil, 8]
What am I misunderstanding? How to make a.map! do what I want with the elements?
My problem is, that I do not fully understand enumerators. With the previous code in place, for example, enumerator e constitutes a backdoor to a:
e.each { 42 }
a #=> [42, 42, 42, 42]
I would like to know, how to do this gradually, with values other than nil. (I can gradually fill it with nils using e.rewind and e.next several times, as I shown before.

To make map! behave as you want, you need the Enumerator#feed method, consider this
ary = *1..4
enum = ary.map!
# the `loop` method handles `StopIteration` for us
loop do
x = enum.next
enum.feed(x * 2)
end
ary
# => [2, 4, 6, 8]

From reference it seems that Enumerator#next doesn't accept a block, so that doesn't have effect of your next call. If you just want to in-place double the last element while clearing all other, do something like, consider straight approach (like a = a[0..-2].map!{|x| nil} + [a.last*2], maybe more elegant). Anyway, could you please provide us with a more detailed usecase to make sure you are doing what you really need?

a.map! accepts a block, but returns an enumerator if no block is supplied. Enumerator#next does not accept a block.
You want to use this to accomplish your goal:
a.map! {|x| x * 2}
if you want to multiply all elements in the array by 2.
For info on next, check out http://ruby-doc.org/core-2.0/Enumerator.html#method-i-next
If you want the output to be exactly [nil, nil, nil, 8] you could do something like:
func = lambda { |x|
unless x == 4
nil
else
x * 2
end
}
a.map!(&func) #> [nil, nil, nil, 8]

Related

How do I spread a list out with spacers?

I want to evenly spread out a list, like [1,2,3,4], into a different list, like [1, nil, nil, 2, nil, nil, 3, nil, 4, nil], based on how big I want the new list to be.
So
[1, 2, 3, 4] to a list with a size of 9 would be: [1, nil, nil, 2, nil, 3, nil, 4, nil]
And [1,2,3,4] to a list with a size of 4 would be: [1,2,3,4]
I will need to spread "uneven" spacers, which should go from left to right - look at my first example.
I answered my own question below.
I decided to make a gem for this: gem install CreamCheese
After installing and requiring the gem (require 'CreamCheese'), you can spread out a list with the knife.
require 'CreamCheese'
CreamCheese::Knife.spread [1,2,3,4], 9 #=> [1, nil, nil, 2, nil, 3, nil, 4, nil]
CreamCheese::Knife.spread [1,2,3,4], 4 #=> [1,2,3,4]
I wouldn't go as far as using a gem for this one, as it can be solved in a couple of lines of Ruby. Unfortunately, you don't specify precisely how you want to handle cases where the spread isn't even ("even" being when each item is followed by the same number of spacers).
In the simple case of an even spread:
# The general case
spread_array = array.flat_map {|el| [el] + [spacer] * (spread / array.size - 1)
# An example
[1, 3].flat_map {|el| [el] + [nil] * 2 } #=> [1, nil, nil, 3, nil, nil]
If you wish to add the leftover spacing after the leftmost elements, you can use this:
# Extra spread goes on the leftmost items
spread_array = array.flat_map.with_index do |el, i|
[el] + [spacer] * (spread / array.size - (i < spread % array.size ? 0 : 1))
end

Ruby enumerator with chaining

I am a newbie of Ruby and I don't quite understand the following code.
n = [1,2,3]
n.map!.select{|x| x+=1}
puts n.inspect
will give me
[nil, nil, nil]
I know that n.map! will give me an enumerator but I don't get it why calling select on it will make n becomes nil. I know that map! will modify the original object, I just don't understand how that's achieved.
Update:
I tried n.map!.select {|x| true}, n is still [nil, nil, nil]
I appreciate your help! Thanks!
It's because the select method is used for selection. It returns the element that satisfies the condition you placed in your block. In your case, you didn't put any condition so it returned nil
e.g.
You want to do something like this:
n = [1,2,3]
n.select { |num| num < 3 }
#=> This should return
#=> [1,2]
In the comments, it says:
It's to do with the in-place modification of map!
that's true. Let me make it clearer here:
In the definition of Array#map!, it says:
map! {|item| block } → ary click to toggle source
map! → Enumerator
Invokes the given block once for each element of self, replacing the element with the value returned by the block
So:
Example 1
[58] pry(main)> a = [1,2,3]
=> [1, 2, 3]
[60] pry(main)> e = a.map!
=> #<Enumerator: ...>
[61] pry(main)> e.each {|n| n + 1}
=> [2, 3, 4]
[62] pry(main)> a
=> [2, 3, 4]
However:
Example 2
[71] pry(main)> a = [1,2,3]
=> [1, 2, 3]
[72] pry(main)> e = a.map!
=> #<Enumerator: ...>
[73] pry(main)> e.next
=> 1
[74] pry(main)> a
=> [1, 2, 3]
[75] pry(main)> e.peek
=> 2
[76] pry(main)> a
=> [nil, 2, 3]
I think this due to Enumerator#each call takes a block, and that would be the return value from example 1; but when Enumerator#next is called (same goes to Enumerator.to_a), it cannot evaluate the block, therefore, returning nil.

Ruby check array, return the indexes, which data is exist

How can I check if there's a data that not nil in an array, and then return the index of that data?
Example:
myary = [nil, nil, 300, nil, nil] # <= index 2 is 300
now is there a method to get the value 2? As we know the index 2 is 300 and not nil.
I need to get the index not the value. And moreover there probably will ot only one element that is not nil, perhaps the array could be like this
myotherary = [nil, nil, 300, 400, nil] # <= index 2,3 = 300,400
now for this I need to get 2 and 3 value, is this posibble?
Okay thank you very much, I appreciate all answer.
P.S : Please no flaming, if you don't want to help then just leave, I have spent some time to solve this matter and not succeed. I'm not going to ask here if I can solve it by myself. I had enough of them who not helping, instead asking "what method have you tried?" or write something else that actually not helping but harrasing.
You can use map.with_index:
myary.map.with_index { |v, i| i if v }.compact
# => [2]
myotherary.map.with_index { |v, i| i if v }.compact
# => [2, 3]
I would be inclined to use Enumerable#select in part because it reads well; the word "select" describes what you want to do.
Code
For just the indices:
def indices_only(arr)
arr.size.times.select { |i| arr[i] }
end
If it would be more useful to return both non-nil values and corresponding indices:
def values_and_indices(arr)
arr.each_with_index.select(&:first)
end
Examples
arr1 = [nil, nil, 300, nil, nil]
arr2 = [nil, nil, 300, 400, nil]
indices_only(arr1) #=> [2]
indices_only(arr2) #=> [2, 3]
values_and_indices(arr1) #=> [[300, 2]]
values_and_indices(arr2) #=> [[300, 2], [400, 3]]

What happens with method after `end` like `end.compact`?

In this code,
working_days = open(ARGV[0].to_s,'r').each_line.map do |line|
do_something
end.compact
the map function returns an array [1, nil, 3, nil]. I appended compact to the keyword end. I want to know what is behind the scene. After I add compact, does the return array become:
[1] → [1,3] or
[1] → [1, nil] → [1, nil, 3] → [1, nil, 3, nil] → [1, 3]
How can I use pry to inspect every step?
Will compact be sent with the do end block into map function ?
There's no magic here. Your code is identical to the following:
tmp = open(...).each_line.map do |line|
do_something
end
working_days = tmp.compact
You've simply removed the middle step, assigning the return value of map to a temporary variable.
It's the difference between doing this...
a(b(c()))
and doing this:
tmp = c()
tmp = b(tmp)
tmp = a(tmp)
You're simply invoking a function on a return value directly, rather than using a second statement.
I don't understand why you are thinking it in a complicated way. It is done as:
[1, nil, 3, nil] → [1, 3]
There are no intermediate steps that you can observe.

How to map and remove nil values in Ruby

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]

Resources