How do the Array methods below differ from each other in Ruby? - ruby

I am getting confused with the Array methods below. Can anyone help me understand how differently they work from each other with the help of simple snippet?
array.sort and array.sort { | a,b | block }
array.to_a and array.to_ary
array.size and array.length
array.reverse and array.reverse_each {|item| block }
array.fill(start [, length] ) { |index| block } and
array.fill(range) { |index| block }

Please read the documentation for Array.
sort:
a=[3,1,2]
a.sort # => [1, 2, 3]
a.sort{|a,b| b<=>a} # => [3, 2, 1]
use the second one if you need some custom way to sort elements.
to_a vs. to_ary:
class Foo < Array;end
b=Foo[1,2]
b.to_ary.class # returns self
b.to_a.class # converts to array
size and length are exactly the same.
reverse_each is pretty much the same as reverse.each.
If you want to fill only a part of the array, you can call Array.fill either with a range or start,length. Those are just different ways to achieve the same:
(["a"]*10).fill("b",2..7)
(["a"]*10).fill("b",2,6)
both return ["a", "a", "b", "b", "b", "b", "b", "b", "a", "a"].

Related

How to understand the work flow in ruby enumerator chain

The code below produces two different results.
letters = %w(e d c b a)
letters.group_by.each_with_index { |item, index| index % 3 }
#=> {0=>["e", "b"], 1=>["d", "a"], 2=>["c"]}
letters.each_with_index.group_by { |item, index| index % 3 }
#=> {0=>[["e", 0], ["b", 3]], 1=>[["d", 1], ["a", 4]], 2=>[["c", 2]]}
I think the execution flow is from right to left, and the data flow is from the left to right. The block should be passed as parameter from right to left.
Using puts, I observed that the block is executed in the inner each.
In the first chain, group_by should ask each for data, each will return the result of index%3, and group_by should process the result and yield it to another block. But how is the block passed? If the block is executed in each, each would not pass two parameters item and index but only one parameter item.
In the second chain, in my understanding, each_with_index will receive the data from each method first; each yields to index%3. In that case, how can each_with_index process index%3?
It seems my understanding is somehow wrong. Can anyone illustrate theses two examples with details and give the general work flow in such cases?
Proxy objects
Both execution and data flows are from left to right, as with any method call in Ruby.
Conceptually, it can help to read Enumerators call chains from right to left, though, because they're a kind of a proxy object.
Called without block, they just remember in which order which method has been called. The method is then only really called when it's needed, for example when the Enumerator is converted back to an Array or the elements are printed on screen.
If no such method is called at the end of the chain, basically nothing happens:
[1,2,3].each_with_index.each_with_index.each_with_index.each_with_index
# #<Enumerator: ...>
[1,2,3].each_with_index.each_with_index.each_with_index.each_with_index.to_a
# [[[[[1, 0], 0], 0], 0], [[[[2, 1], 1], 1], 1], [[[[3, 2], 2], 2], 2]]
This behaviour makes it possible to work with very large streams of objects, without needing to pass huge arrays between method calls. If the output isn't needed, nothing is calculated. If 3 elements are needed at the end, only 3 elements are calculated.
The proxy pattern is heavily used in Rails, for example with ActiveRecord::Relation :
#person = Person.where(name: "Jason").where(age: 26)
It would be inefficient to launch 2 DB queries in this case. You can only know that at the end of the chained methods, though. Here's a related answer (How does Rails ActiveRecord chain “where” clauses without multiple queries?)
MyEnumerator
Here's a quick and dirty MyEnumerator class. It might help you understand the logic for the method calls in your question:
class MyEnumerator < Array
def initialize(*p)
#methods = []
#blocks = []
super
end
def group_by(&b)
save_method_and_block(__method__, &b)
self
end
def each_with_index(&b)
save_method_and_block(__method__, &b)
self
end
def to_s
"MyEnumerable object #{inspect} with methods : #{#methods} and #{#blocks}"
end
def apply
result = to_a
puts "Starting with #{result}"
#methods.zip(#blocks).each do |m, b|
if b
puts "Apply method #{m} with block #{b} to #{result}"
else
puts "Apply method #{m} without block to #{result}"
end
result = result.send(m, &b)
end
result
end
private
def save_method_and_block(method, &b)
#methods << method
#blocks << b
end
end
letters = %w[e d c b a]
puts MyEnumerator.new(letters).group_by.each_with_index { |_, i| i % 3 }.to_s
# MyEnumerable object ["e", "d", "c", "b", "a"] with methods : [:group_by, :each_with_index] and [nil, #<Proc:0x00000001da2518#my_enumerator.rb:35>]
puts MyEnumerator.new(letters).group_by.each_with_index { |_, i| i % 3 }.apply
# Starting with ["e", "d", "c", "b", "a"]
# Apply method group_by without block to ["e", "d", "c", "b", "a"]
# Apply method each_with_index with block #<Proc:0x00000000e2cb38#my_enumerator.rb:42> to #<Enumerator:0x00000000e2c610>
# {0=>["e", "b"], 1=>["d", "a"], 2=>["c"]}
puts MyEnumerator.new(letters).each_with_index.group_by { |_item, index| index % 3 }.to_s
# MyEnumerable object ["e", "d", "c", "b", "a"] with methods : [:each_with_index, :group_by] and [nil, #<Proc:0x0000000266c220#my_enumerator.rb:48>]
puts MyEnumerator.new(letters).each_with_index.group_by { |_item, index| index % 3 }.apply
# Apply method each_with_index without block to ["e", "d", "c", "b", "a"]
# Apply method group_by with block #<Proc:0x0000000266bd70#my_enumerator.rb:50> to #<Enumerator:0x0000000266b938>
# {0=>[["e", 0], ["b", 3]], 1=>[["d", 1], ["a", 4]], 2=>[["c", 2]]}

About the method "select" and "join"

so I posted a question earlier about displaying factors of non-prime numbers and got this as a solution.
Below is a part of the code, but I have a little bit trouble understanding a few terms in them (since I am relatively new to ruby).
def factors(n)
(1..n/2).select{|e| (n%e).zero?}.push(n)
end
For instance,
What does the method select do in general?
What does .zero? do?
Then,
puts "#{n} is not a prime number =>#{factors(n).join(',')}"
What does .join(',') do?
It would be greatly appreciated if anyone can explain these terms to me in basic terms or simple concepts.
select method filters a collection. You specify the condition (the predicate) in the block and select returns items who match that condition.
[1,2,3,4].select(&:even?)
=> [2, 4]
i.zero? is another way to write i == 0
[0,2,3,0].select(&:zero?)
=> [0, 0]
join method concatenate a collection as a string with the parameter as a separators between items
[0,2,3,0].select(&:zero?).join(' and ')
=> "0 and 0"
Note
[1,2,3].select(&:even?)
is a simpler way to write
[1,2,3].select { |item| item.even? }
i will try explain by following links and text from documentation
join()
Returns a string created by converting each element of the array to a string, separated by the given separator. If the separator is nil, it uses current $,. If both the separator and $, are nil, it uses empty string.
[ "a", "b", "c" ].join #=> "abc"
[ "a", "b", "c" ].join("-") #=> "a-b-c"
select
Returns a new array containing all elements of ary for which the given block returns a true value.
If no block is given, an Enumerator is returned instead.
[1,2,3,4,5].select { |num| num.even? } #=> [2, 4]
a = %w{ a b c d e f }
a.select { |v| v =~ /[aeiou]/ } #=> ["a", "e"]
zero?
zero? → true or false
Returns true if num has a zero value.
Ruby html documentation: ruby-doc.org

Set multiple keys to the same value at once for a Ruby hash

I'm trying to create this huge hash, where there are many keys but only a few values.
So far I have it like so...
du_factor = {
"A" => 1,
"B" => 1,
"C" => 1,
"D" => 2,
"E" => 2,
"F" => 2,
...etc., etc., etc., on and on and on for longer than you even want to know. What's a shorter and more elegant way of creating this hash without flipping its structure entirely?
Edit: Hey so, I realized there was a waaaay easier and more elegant way to do this than the answers given. Just declare an empty hash, then declare some arrays with the keys you want, then use a for statement to insert them into the array, like so:
du1 = ["A", "B", "C"]
du2 = ["D", "E", "F"]
dufactor = {}
for i in du1
dufactor[i] = 1
end
for i in du740
dufactor[i] = 2
end
...but the fact that nobody suggested that makes me, the extreme Ruby n00b, think that there must be a reason why I shouldn't do it this way. Performance issues?
Combining Ranges with a case block might be another option (depending on the problem you are trying to solve):
case foo
when ('A'..'C') then 1
when ('D'..'E') then 2
# ...
end
Especially if you focus on your source code's readability.
How about:
vals_to_keys = {
1 => [*'A'..'C'],
2 => [*'D'..'F'],
3 => [*'G'..'L'],
4 => ['dog', 'cat', 'pig'],
5 => [1,2,3,4]
}
vals_to_keys.each_with_object({}) { |(v,arr),h| arr.each { |k| h[k] = v } }
#=> {"A"=>1, "B"=>1, "C"=>1, "D"=>2, "E"=>2, "F"=>2, "G"=>3, "H"=>3, "I"=>3,
# "J"=>3, "K"=>3, "L"=>3, "dog"=>4, "cat"=>4, "pig"=>4, 1=>5, 2=>5, 3=>5, 4=>5}
What about something like this:
du_factor = Hash.new
["A", "B", "C"].each {|ltr| du_factor[ltr] = 1}
["D", "E", "F"].each {|ltr| du_factor[ltr] = 2}
# Result:
du_factor # => {"A"=>1, "B"=>1, "C"=>1, "D"=>2, "E"=>2, "F"=>2}
Create an empty hash, then for each group of keys that share a value, create an array literal containing the keys, and use the array's '.each' method to batch enter them into the hash. Basically the same thing you did above with for loops, but it gets it done in three lines.
keys = %w(A B C D E F)
values = [1, 1, 1, 2, 2, 2]
du_factor = Hash[*[keys, values].transpose.flatten]
If these will be more than 100, writing them down to a CSV file might be better.
keys = [%w(A B C), %w(D E F)]
values = [1,2]
values.map!.with_index{ |value, idx| Array(value) * keys[idx].size }.flatten!
keys.flatten!
du_factor = Hash[keys.zip(values)]
Notice here that I used destructive methods (methods ending with !). this is important for performance and memory usage optimization.

How to remove elements of array in place returning the removed elements

I have an array arr. I want to destructively remove elements from arr based on a condition, returning the removed elements.
arr = [1,2,3]
arr.some_method{|a| a > 1} #=> [2, 3]
arr #=> [1]
My first try was reject!:
arr = [1,2,3]
arr.reject!{|a| a > 1}
but the returning blocks and arr's value are both [1].
I could write a custom function, but I think there is an explicit method for this. What would that be?
Update after the question was answered:
partition method turns out to be useful for implementing this behavior for hash as well. How can I remove elements of a hash, returning the removed elements and the modified hash?
hash = {:x => 1, :y => 2, :z => 3}
comp_hash, hash = hash.partition{|k,v| v > 1}.map{|a| Hash[a]}
comp_hash #=> {:y=>2, :z=>3}
hash #=> {:x=>1}
I'd use partition here. It doesn't modify self inplace, but returns two new arrays. By assigning the second array to arr again, it gets the results you want:
comp_arr, arr = arr.partition { |a| a > 1 }
See the documentation of partition.
All methods with a trailing bang ! modify the receiver and it seems to be a convention that these methods return the resulting object because the non-bang do so.
What you can to do though is something like this:
b = (arr.dup - arr.reject!{|a| a>1 })
b # => [2,3]
arr #=> [1]
Here is a link to a ruby styleguide which has a section on nameing - although its rather short
To remove (in place) elements of array returning the removed elements one could use delete method, as per Array class documentation:
a = [ "a", "b", "b", "b", "c" ]
a.delete("b") #=> "b"
a #=> ["a", "c"]
a.delete("z") #=> nil
a.delete("z") { "not found" } #=> "not found"
It accepts block so custom behavior could be added, as needed

Delete contents of array based on a set of indexes

delete_at only takes a single index. What's a good way to achieve this using built-in methods?
Doesn't have to be a set, can be an array of indexes as well.
arr = ["a", "b", "c"]
set = Set.new [1, 2]
arr.delete_at set
# => arr = ["a"]
One-liner:
arr.delete_if.with_index { |_, index| set.include? index }
Re-open the Array class and add a new method for this.
class Array
def delete_at_multi(arr)
arr = arr.sort.reverse # delete highest indexes first.
arr.each do |i|
self.delete_at i
end
self
end
end
arr = ["a", "b", "c"]
set = [1, 2]
arr.delete_at_multi(set)
arr # => ["a"]
This could of course be written as a stand-alone method if you don't want to re-open the class. Making sure the indexes are in reverse order is very important, otherwise you change the position of elements later in the array that are supposed to be deleted.
Try this:
arr.reject { |item| set.include? arr.index(item) } # => [a]
It's a bit ugly, I think ;) Maybe someone suggest a better solution?
Functional approach:
class Array
def except_values_at(*indexes)
([-1] + indexes + [self.size]).sort.each_cons(2).flat_map do |idx1, idx2|
self[idx1+1...idx2] || []
end
end
end
>> ["a", "b", "c", "d", "e"].except_values_at(1, 3)
=> ["a", "c", "e"]

Resources