Separate array of hashes by nil - ruby-on-rails

I have a Hash value lik this:
hs = {2012 => [7,nil,3], 2013 => [2, 6, nil, 8], 2014 => [9, 1, 2, 8]}
The keys are years. I want to collect values backwards until nil appears like this:
some_separate_method(hs)
{2013 => [8], 2014 => [9, 1, 2, 8]}
I thought this is not difficult to implement by using reverse_each, but I couldn't. How can I make a method like this?
Edit
With AmitA's code I could make it.
new_hs = []
hs.reverse_each{|k,v| new_hs << [k,v]; break if v.include?(nil)}
new_hs = Hash[new_hs.sort]

How about this:
res = {}
hs.to_a.reverse.each do |k, arr|
res[k] = arr.split(nil).last
break unless res[k].length == arr.length
end

A pure-Ruby solution:
hs.reverse_each.with_object({}) do |(k,v),h|
h[k] = v.dup
ndx = v.rindex(&:nil?)
if ndx
h[k] = h[k][ndx+1..-1]
break h
end
end
#=> {2014=>[9, 1, 2, 8], 2013=>[8]}
v.dup is to avoid mutatinghs.

This looks a bit long but I think it should help anyone who's new to ruby and has a similar problem.
def some_separate_method(hs)
new_hash = {}
hs.each do | key, value |
new_arr = []
value.reverse.each do | item |
break if item.nil?
new_arr.unshift(item)
end
new_hash[key] = new_arr
end
new_hash
end
#=> {2012=>[3], 2013=>[8], 2014=>[9, 1, 2, 8]}

Related

How do I split a string given an array of split positions?

I'm using Ruby 2.4 and Rails 5. I have an array of indexes within a line
[5, 8, 10]
How do I take the above array, and a string, and form anotehr array of strings that are split by the above indexes? FOr instance, if the string is
abcdefghijklmn
and split it based ont eh above indexes, I would have an array with the following strings
abcde
fgh
ij
klmn
Try this
str = "abcdefghijklmn"
positions = [5, 8, 10]
parts = [0, *positions, str.size].each_cons(2).map { |a, b| str[a...b] }
# => ["abcde", "fgh", "ij", "klmn"]
Or,
If the positions are constant and known ahead of runtime (for example if they were the format for a phone number or credit card) just use a regexp
str.match(/(.....)(...)(..)(.*)/).captures
# => ["abcde", "fgh", "ij", "klmn"]
This will get the Job done
str = "abcdefghijklmn"
arr_1 = [5, 8, 10]
arr_2, prev = [], 0
(arr_1.length + 1).times do |x|
if arr_1[x] == nil then arr_1[x] = str.size end
arr_2 << str[prev..arr_1[x] -1]
prev = arr_1[x]
end
p arr_2
---------------------------------------
Program Run Output
["abcde", "fgh", "ij", "klmn"]
---------------------------------------
I hope this Helps

ruby arrays count of most frequent element [duplicate]

This question already has answers here:
How to find an item in array which has the most occurrences [duplicate]
(11 answers)
Closed 6 years ago.
I'm trying to figure out how to find a count of the most frequent element in an array of integers. I can think of a few methods that might be helpful but when I get to writing an expression inside the block I get complete lost on how to compare an element with the next and previous element. Any ideas? All help is really really appreciated!!!
An easy was is to determine all the unique values, convert each to its count in the array, then determine the largest count.
def max_count(arr)
arr.uniq.map { |n| arr.count(n) }.max
end
For example:
arr = [1,2,4,3,2,6,3,4,2]
max_count(arr)
#=> 3
There are three steps:
a = arr.uniq
#=> [1, 2, 4, 3, 6]
b = a.map { |n| arr.count(n) }
#=> [1, 3, 2, 2, 1]
b.max
#=> 3
A somewhat more efficient way (because the elements of arr are enumerated only once) is to use a counting hash:
def max_count(arr)
arr.each_with_object(Hash.new(0)) { |n,h| h[n] += 1 }.values.max
end
max_count(arr)
#=> 3
We have:
a = arr.each_with_object(Hash.new(0)) { |n,h| h[n] += 1 }
#=> {1=>1, 2=>3, 4=>2, 3=>2, 6=>1}
b = a.values
#=> [1, 3, 2, 2, 1]
b.max
#=> 3
See Hash::new for an explanation of Hash.new(0). Briefly, if h = Hash.new(0) and h does not have a key k, h[k] will return the default value, which here is zero. h[k] += 1 expands to h[k] = h[k] + 1, so if h does not have a key k, this becomes h[k] = 0 + 1. On the other hand, if, say, h[k] => 2, then h[k] = h[k] + 1 #=> h[k] = 3 + 1.

What's a good way to create a string array in Ruby based on integer variables?

The integer variables are:
toonie = 2, loonie = 1, quarter = 1, dime = 0, nickel = 1, penny = 3
I want the final output to be
"2 toonies, 1 loonie, 1 quarter, 1 nickel, 3 pennies"
Is there a way to interpolate this all from Ruby code inside [] array brackets and then add .join(", ")?
Or will I have to declare an empty array first, and then write some Ruby code to add to the array if the integer variable is greater than 0?
I would do something like this:
coins = { toonie: 2, loonie: 1, quarter: 1, dime: 0, nickel: 1, penny: 3 }
coins.map { |k, v| pluralize(v, k) if v > 0 }.compact.join(', ')
#=> "2 toonie, 1 loonie, 1 quarter, 1 nickel, 3 penny"
Note that pluralize is a ActionView::Helpers::TextHelper method. Therefore it is only available in views and helpers.
When you want to use your example outside of views, you might want to use pluralize from ActiveSupport instead - what makes the solution slightly longer:
coins.map { |k, v| "#{v} #{v == 1 ? k : k.pluralize}" if v > 0 }.compact.join(', ')
#=> "2 toonie, 1 loonie, 1 quarter, 1 nickel, 3 penny"
Can be done in rails:
hash = {
"toonie" => 2,
"loonie" => 1,
"quarter" => 1,
"dime" => 0,
"nickel" => 1,
"penny" => 3
}
hash.to_a.map { |ele| "#{ele.last} #{ele.last> 1 ? ele.first.pluralize : ele.first}" }.join(", ")
Basically what you do is convert the hash to an array, which will look like this:
[["toonie", 2], ["loonie", 1], ["quarter", 1], ["dime", 0], ["nickel", 1], ["penny", 3]]
Then you map each element to the function provided, which takes the inner array, takes the numeric value in the last entry, places it in a string and then adds the plural or singular value based on the numeric value you just checked. And finally merge it all together
=> "2 toonies, 1 loonie, 1 quarter, 1 nickel, 3 pennies"
I'm not sure what exactly you're looking for, but I would start with a hash like:
coins = {"toonie" => 2, "loonie" => 1, "quarter" => 1, "dime" => 0, "nickel" => 1, "penny" => 3}
then you can use this to print the counts
def coin_counts(coins)
(coins.keys.select { |coin| coins[coin] > 0}.map {|coin| coins[coin].to_s + " " + coin}).join(", ")
end
If you would like appropriate pluralizing, you can do the following:
include ActionView::Helpers::TextHelper
def coin_counts(coins)
(coins.keys.select { |coin| coins[coin] > 0}.map {|coin| pluralize(coins[coin], coin)}).join(", ")
end
This is just for fun and should not be used in production but you can achieve it like
def run
toonie = 2
loonie = 1
quarter = 1
dime = 0
nickel = 1
penny = 3
Kernel::local_variables.each_with_object([]) { |var, array|
next if eval(var.to_s).to_i.zero?
array << "#{eval(var.to_s)} #{var}"
}.join(', ')
end
run # returns "2 toonie, 1 loonie, 1 quarter, 1 nickel, 3 penny"
The above does not implement the pluralization requirement because it really depends if you will have irregular plural nouns or whatever.
I would go with a hash solution as described in the other answers

ruby compare two arrays of hash, with certain keys [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
There's two arrays of hash and I want remove the 'common' elements from the two arrays, based on certain keys. For example:
array1 = [{a: '1', b:'2', c:'3'}, {a: '4', b: '5', c:'6'}]
array2 = [{a: '1', b:'2', c:'10'}, {a: '3', b: '5', c:'6'}]
and the criteria keys are a and b. So when I get the result of something like
array1-array2 (don't have to overwrite '-' if there's better approach)
it will expect to get
[{a: '4', b: '5', c:'6'}]
sine we were using a and b as the comparing criteria. It will wipe the second element out since the value for a is different for array1.last and array2.last.
As I understand, you are given two arrays of hashes and a set of keys. You want to reject all elements (hashes) of the first array whose values match the values of any element (hash) of the second array, for all specified keys. You can do that as follows.
Code
require 'set'
def reject_partial_dups(array1, array2, keys)
set2 = array2.each_with_object(Set.new) do |h,s|
s << h.values_at(*keys) if (keys-h.keys).empty?
end
array1.reject do |h|
(keys-h.keys).empty? && set2.include?(h.values_at(*keys))
end
end
The line:
(keys-h.keys).empty? && set2.include?(h.values_at(*keys))
can be simplified to:
set2.include?(h.values_at(*keys))
if none of the values of keys in the elements (hashes) of array1 are nil. I created a set (rather than an array) from array2 in order to speed the lookup of h.values_at(*keys) in that line.
Example
keys = [:a, :b]
array1 = [{a: '1', b:'2', c:'3'}, {a: '4', b: '5', c:'6'}, {a: 1, c: 4}]
array2 = [{a: '1', b:'2', c:'10'}, {a: '3', b: '5', c:'6'}]
reject_partial_dups(array1, array2, keys)
#=> [{:a=>"4", :b=>"5", :c=>"6"}, {:a=>1, :c=>4}]
Explanation
First create set2
e0 = array2.each_with_object(Set.new)
#=> #<Enumerator: [{:a=>"1", :b=>"2", :c=>"10"}, {:a=>"3", :b=>"5", :c=>"6"}]
# #:each_with_object(#<Set: {}>)>
Pass the first element of e0 and perform the block calculation.
h,s = e0.next
#=> [{:a=>"1", :b=>"2", :c=>"10"}, #<Set: {}>]
h #=> {:a=>"1", :b=>"2", :c=>"10"}
s #=> #<Set: {}>
(keys-h.keys).empty?
#=> ([:a,:b]-[:a,:b,:c]).empty? => [].empty? => true
so compute:
s << h.values_at(*keys)
#=> s << {:a=>"1", :b=>"2", :c=>"10"}.values_at(*[:a,:b] }
#=> s << ["1","2"] => #<Set: {["1", "2"]}>
Pass the second (last) element of e0 to the block:
h,s = e0.next
#=> [{:a=>"3", :b=>"5", :c=>"6"}, #<Set: {["1", "2"]}>]
(keys-h.keys).empty?
#=> true
so compute:
s << h.values_at(*keys)
#=> #<Set: {["1", "2"], ["3", "5"]}>
set2
#=> #<Set: {["1", "2"], ["3", "5"]}>
Reject elements from array1
We now iterate through array1, rejecting elements for which the block evaluates to true.
e1 = array1.reject
#=> #<Enumerator: [{:a=>"1", :b=>"2", :c=>"3"},
# {:a=>"4", :b=>"5", :c=>"6"}, {:a=>1, :c=>4}]:reject>
The first element of e1 is passed to the block:
h = e1.next
#=> {:a=>"1", :b=>"2", :c=>"3"}
a = (keys-h.keys).empty?
#=> ([:a,:b]-[:a,:b,:c]).empty? => true
b = set2.include?(h.values_at(*keys))
#=> set2.include?(["1","2"] => true
a && b
#=> true
so the first element of e1 is rejected. Next:
h = e1.next
#=> {:a=>"4", :b=>"5", :c=>"6"}
a = (keys-h.keys).empty?
#=> true
b = set2.include?(h.values_at(*keys))
#=> set2.include?(["4","5"] => false
a && b
#=> false
so the second element of e1 is not rejected. Lastly:
h = e1.next
#=> {:a=>1, :c=>4}
a = (keys-h.keys).empty?
#=> ([:a,:c]-[:a,:b]).empty? => [:c].empty? => false
so return true (meaning the last element of e1 is not rejected), as there is no need to compute:
b = set2.include?(h.values_at(*keys))
So you really should try this out yourself because I am basically solving it for you.
The general approach would be:
For every time in array1
Check to see the same value in array2 has any keys and values with the same value
If they do then, delete it
You would probably end up with something like array1.each_with_index { |h, i| h.delete_if {|k,v| array2[i].has_key?(k) && array2[i][k] == v } }

Get the month numbers for the current quarter

I need an array with the numbers for the months in the current quarter. I want to supply Date.today and then get eg. [1,2,3].
How do I do that in the easiest way? (Not by using switch/case).
def quarter(date)
1 + ((date.month-1)/3).to_i
end
def quarter_month_numbers(date)
quarters = [[1,2,3], [4,5,6], [7,8,9], [10,11,12]]
quarters[(date.month - 1) / 3]
end
I would suggest building a hash indexed by month like so:
#quarters_by_month = Hash[(1..12).map {|v| i=((v-1)/3)*3; [v,[i+1, i+2, i+3]]}]
then any future lookup is just
#quarters_by_month[month]
Since #x3ro mentioned CPU time I thought it would be fun to benchmark all of the proposed solutions including the case statement which the OP wanted to exclude. Here are the results:
> ruby jeebus.rb
user system total real
case_statement: 0.470000 0.000000 0.470000 ( 0.469372)
quarter_month: 0.420000 0.000000 0.420000 ( 0.420217)
solution1: 0.740000 0.000000 0.740000 ( 0.733669)
solution2: 1.630000 0.010000 1.640000 ( 1.634004)
defined_hash: 0.470000 0.000000 0.470000 ( 0.469814)
Here is the code:
def case_statement(month)
case month
when 1,2,3
[1,2,3]
when 4,5,6
[4,5,6]
when 7,8,9
[7,8,9]
when 10,11,12
[10,11,12]
else
raise ArgumentError
end
end
def defined_hash(month)
#quarters_by_month[month]
end
def solution1(month)
(((month - 1) / 3) * 3).instance_eval{|i| [i+1, i+2, i+3]}
end
def solution2(month)
[*1..12][((month - 1) / 3) * 3, 3]
end
def quarter_month_numbers(month)
#quarters[(month - 1) / 3]
end
require 'benchmark'
n = 1e6
Benchmark.bm(15) do |x|
x.report('case_statement:') do
for i in 1..n do
case_statement(rand(11) + 1)
end
end
x.report('quarter_month:') do
#quarters = [[1,2,3], [4,5,6], [7,8,9], [10,11,12]]
for i in 1..n do
quarter_month_numbers(rand(11) + 1)
end
end
x.report('solution1:') do
for i in 1..n do
solution1(rand(11) + 1)
end
end
x.report('solution2:') do
for i in 1..n do
solution2(rand(11) + 1)
end
end
x.report('defined_hash:') do
#quarters_by_month = Hash[(1..12).map {|v| i=((v-1)/3)*3; [v,[i+1, i+2, i+3]]}]
for i in 1..n do
defined_hash(rand(11) + 1)
end
end
end
Solution 1
(((Date.today.month - 1) / 3) * 3).instance_eval{|i| [i+1, i+2, i+3]}
Solution 2
[*1..12][((Date.today.month - 1) / 3) * 3, 3]
You can do the following:
m = date.beginning_of_quarter.month
[m, m+1, m+2]
Demonstrated below in irb:
>> date=Date.parse "27-02-2011"
=> Sun, 27 Feb 2011
>> m = date.beginning_of_quarter.month
=> 1
>> [m, m+1, m+2]
=> [1, 2, 3]
I don't know how fast this is compared to the other methods, perhaps #Wes can kindly benchmark this way as well.
One advantage of this approach I think is the clarity of the code. It's not convoluted.
Have a look at this little snippet:
months = (1..12).to_a
result = months.map do |m|
quarter = (m.to_f / 3).ceil
((quarter-1)*3+1..quarter*3).to_a
end
puts result.inspect
For Array
month = Date.today.month # 6
quarters = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]
quarters.select { |quarter| quarter.include?(month) }
=> [[4, 5, 6]]
For Hash
month = Date.today.month # 6
quarters = {
[1, 2, 3] => 'First quarter',
[4, 5, 6] => 'Second quarter',
[7, 8, 9] => 'Third quarter',
[10, 11, 12] => 'Fourth quarter',
}
quarters.select { |quarter| quarter.include?(month) }
=> {[4, 5, 6]=>"Second quarter"}
Wish it helped ;)

Resources