Pushing max hash key-values into an array in Ruby? - ruby-on-rails

I have a hash called count, defined as count = {4=>2, 5=>3, 6=>3, 7=>1}.
I want to take the max value, and then push the key that corresponds to that value into an array, so I do this:
array = []
array.push(count.max_by{|k,v| v}[0])
=>> [5]
However, 6 also has the value 3, which is another maximum value. How do I push this value into the array so I get [5,6] instead of just [5]?

This is the way to choose the max values of the hash:
count.values.max
=> 3
Use the select method on the hash:
count.select{ |k, v| v == count.values.max }
=> {5=>3, 6=>3}
Get the keys:
count.select{ |k, v| v == count.values.max }.keys
=> [5, 6]
And finally assign to an array:
array = count.select{ |k, v| v == count.values.max }.keys

This could probably be improved dramatically, but off the top of my head:
count.group_by{|k,v| v}.max_by{|k,v| k}.last.map(&:first)
First, group the key/value pairs of the hash so that the ones with the same value are in the same groups:
count.group_by{|k,v| v} #=> {2=>[[4, 2]], 3=>[[5, 3], [6, 3]], 1=>[[7, 1]]}
Then get the group with the maximum value:
.max_by{|k,v| k} #=> [3, [[5, 3], [6, 3]]]
Now, we just want the original keys out of that, so first we take the last element of the pair:
.last #=> [[5, 3], [6, 3]]
And we want just the first element of each of those nested pairs:
.map(&:first) #=> [5, 6]
This approach avoids one pass through the map as compared to the select-based solutions. It's probably not a significant performance win unless the data set is really huge, in which case the intermediate data structures being built by my solution will be more of a problem anyway.

max = count.values.max
array = count.keys.select{|k| count[k] == max}

Related

Using .map function to create hashes

I have an array [5,2,6,4] and I would like to create a structure such as the first minus the second etc until the last row.
I have tried using map, but not sure how to proceed since i might need indxes.
I would like to store the result in something that looks like:
{1 => (5, 2, 3), 2 =>(2,6,-4), 3 => (6,4,2)}
So an array of x should return x-1 hashes.
Anybody knows how to do? should be a simple one.
Thank you.
First, you want to work with the array elements in pairs: 5,2, 2,6, ... That means you want to use each_cons:
a.each_cons(2) { |(e1, e2)| ... }
Then you'll want the index to get the 1, 2, ... hash keys; that suggests throwing a Enumerator#with_index into the mix:
a.each_cons(2).with_index { |(e1, e2), i| ... }
Then you can use with_object to get the final piece (the hash) into play:
a.each_cons(2).with_index.with_object({}) { |((e1, e2), i), h| h[i + 1] = [e1, e2, e1 - e2] }
If you think all the parentheses in the block's arguments are too noisy then you can do it in steps rather than a single one-liner.
You can use each_index:
a = [5, 2, 6, 4]
h = {}
a[0..-2].each_index { |i| h[i+1] = [a[i], a[i+1], a[i] - a[i+1]] }
h
=> {1=>[5, 2, 3], 2=>[2, 6, -4], 3=>[6, 4, 2]}
Try to use
each_with_index
Suppose you have an array:
arr = [3,[2,3],4,5]
And you want to covert with hash(key-value pair). 'Key' denotes an index of an array and 'value' denotes value of an array. Take a blank hash and iterate with each_with_index and pushed into the hash and finally print the hash.
Try this:
hash={}
arr.each_with_index do |val, index|
hash[index]=val
end
p hash
Its output will be:
{0=>3, 1=>[2, 3], 2=>4, 3=>5}
If you want that index always starts with 1 or 2 etc then use
arr.each.with_index(1) do |val, index|
hash[index] = val
end
Output will be:
{1=>3, 2=>[2, 3], 3=>4, 4=>5}

What is the best way to access an element from 2d array saved as a hash value?

I have a hash, its values are 2 dimensional arrays, e.g.
hash = {
"first" => [[1,2,3],[4,5,6]],
"second" => [[7,88,9],[6,2,6]]
}
I want to access the elements to print them in xls file.
I did it in this way:
hash.each do |key, value|
value.each do |arr1|
arr1.each do |arr2|
arr2.each do |arr3|
sheet1.row(row).push arr3
end
end
end
end
Is there a better way to access each single element without using each-statement 4 times?
The desired result is to get each value from key-value pair as an array, e.g.
=> [1,2,3,4,5,6] #first loop
=> [7,88,9,6,2,6] #second loop
#and so on
hash = { "first" =>[[1, 2,3],[4,5,6]],
"second"=>[[7,88,9],[6,2,6]] }
hash.values.map(&:flatten)
#=> [[1, 2, 3, 4, 5, 6], [7, 88, 9, 6, 2, 6]]
Isn't it as simple as something like:
hash.each do |k,v|
sheet1.row(row).concat v.flatten
end

ruby uniq on the first two columns of two dimensional array

I have a two dimensional array with three fields in the second dimension.
Is it possible to use uniq on the first two fields of the second dimension?
I have seen array.uniq! {|c| c.first}. When I am correct, this apply uniq on the first field of the array.
Is it possible to use something like array.uniq! {|c| c.first c.second}?
#array = Array.new()
#array << Array.new([journal.from_account_number, journal.from_account,
journal.buchungsart])
There are several entries in #array.
The question was how to get unique values from array not considering journal.buchungsart.
The answer was: #array = #array.uniq! {|c| [c.first, c.second]}
Yes, put the values in an array:
array.uniq! {|c| [c.first, c.second]}
Just return an array of two elements in the block.
Since Array#second is not defined in the standard library, do
array.uniq! { |c| [c[0], c[1]] }
instead, which can be further simplified to array.uniq! { |c| c[0..1] }
You could also make use of the fact that hash keys are unique.
arr = [[1, 2, 3],
[2, 1, 4],
[1, 2, 5]]
a = arr.each_with_object({}) { |row, h| h.update(row.first(2)=>row) }.values
#=> [[1, 2, 5], [2, 1, 4]]
See Hash#update (aka merge!).
Before extracting the hash values with Hash#value, we have computed the following hash.
arr.each_with_object({}) { |row, h| h.update(row.first(2)=>row) }
#=> {[1, 2]=>[1, 2, 5], [2, 1]=>[2, 1, 4]}
Notice that, for given values of the first two elements in a row, it is the last row of arr with those values that is to be "kept". The first such row in arr is to be kept, use the following.
arr.each_with_object({}) { |row, h| h.update(row.first(2)=>row) { |_,o,_| o } }.values
#=> [[1, 2, 3], [2, 1, 4]]
This does not mutate arr. If arr is to be modified, write
arr.replace(a)
where a is defined above.
Array#first accepts a parameter :
%w(a b c d e f).first(2)
# => ["a", "b"]
so you could just use :
array.uniq!{ |c| c.first(2) }

remove array from multidimensional array if first 3 values aren't unique in ruby on rails

OK, so I have this array of arrays. Each array within the larger array is very much the same, ten specific values. If my value at location 3 is a specific value, then I want to iterate through the rest of the remaining arrays within the larger array and see if the first 3 values at locations 0, 1, and 2 match. if they then match, I'd like to delete the original array. I'm having a hard time with it, maybe there is an easy way? I'm sure there is, I'm fairly new to this whole coding stuff =) So much appreciation in advance for your help....
here's where I'm at:
#projectsandtrials.each do |removed|
if removed[3] == ["Not Harvested"]
#arraysforloop = #projectsandtrials.clone
#arraysforloop1 = #arraysforloop.clone.delete(removed)
#arraysforloop1.each do |m|
if (m & [removed[0], removed[1], removed[2]]).any?
#projectsandtrials.delete(removed)
end
end
end
end
Lets look at your situation:
#projectsandtrials.each do |removed|
// some logic, yada yada
#projectsandtrials.delete(removed)
end
You can't just delete stuff out of an array you're iterating through. At least not until you finish iterating through it. What you should be using instead is a filtering method like reject instead of just an each.
So instead of deleting right there, you should just return true when using reject.
I think about it like this when iterating through arrays.
Do I want the array to stay the same size and have the same content?
Use each.
Do I want the array to be the same size, but have different content?
Use map.
Do I want the array to be less than or equal to the current size?
Use select or reject.
Do I want it to end up being a single value?
Use reduce.
Code
def prune(arr, val)
arr.values_at(*(0..arr.size-4).reject { |i| arr[i][3] == val &&
arr[i+1..i+3].transpose[0,3].map(&:uniq).all? { |a| a.size==1 } }.
concat((arr.size-3..arr.size-1).to_a))
end
Example
arr = [ [1,2,3,4,0],
[3,4,5,6,1],
[3,4,5,4,2],
[3,4,5,6,3],
[3,4,5,6,4],
[3,4,0,6,5],
[2,3,5,4,6],
[2,3,5,5,7],
[2,3,5,7,8],
[2,3,5,8,9],
[2,3,5,7,0]
]
Notice that the last values of the elements (arrays) of arr are consecutive. This is to help you identify the elements of prune(arr, 4) (below) that have been dropped.
prune(arr, 4)
# => [[3, 4, 5, 6, 1],
# [3, 4, 5, 4, 2],
# [3, 4, 5, 6, 3],
# [3, 4, 5, 6, 4],
# [3, 4, 0, 6, 5],
# [2, 3, 5, 5, 7],
# [2, 3, 5, 7, 8],
# [2, 3, 5, 8, 9],
# [2, 3, 5, 7, 0]]
Explanation
The arrays at indices 0 and 6 have not been included in array returned.
arr[0] ([1,2,3,4,0]) has not been included because arr[0][3] = val = 4 and arr[1], arr[2] and arr[3] all begin [3,4,5].
arr[6] ([2,3,5,4,6]) has not been included because arr[6][3] = 4 and arr[7], arr[8] and arr[9] all begin [2,3,5].
arr[2] ([3,4,5,5,2]) has been included because, while arr[2][3] = 4, arr[3][0,3], arr[4][0,3] and arr[5][0,3] all not all equal (i.e., arr[5][2] = 0).
Notice that the last three elements of arr will always be included in the array returned.
Now let's examine the calculations. First consider the following.
arr.size
#=> 11
a = (0..arr.size-4).reject { |i| arr[i][3] == val &&
arr[i+1..i+3].transpose[0,3].map(&:uniq).all? { |a| a.size==1 } }
#=> (0..7).reject { |i| arr[i][3] == val &&
arr[i+1..i+3].transpose[0,3].map(&:uniq).all? { |a| a.size==1 } }
#=> [1, 2, 3, 4, 5, 7]
Consider reject's block calculation for i=0 (recall val=4).
arr[i][3] == val && arr[i+1..i+3].transpose[0,3].map(&:uniq).all? {|a| a.size==1 }}
#=> 4 == 4 && arr[1..3].transpose[0,3].map(&:uniq).all? { |a| a.size==1 }
#=> [[3,4,5,6,1],
# [3,4,5,4,2],
# [3,4,5,6,3]].transpose[0,3].map(&:uniq).all? { |a| a.size==1 }
#=> [[3, 3, 3],
# [4, 4, 4],
# [5, 5, 5],
# [6, 4, 6],
# [1, 2, 3]][0,3].map(&:uniq).all? { |a| a.size==1 }
#=> [[3, 3, 3],
# [4, 4, 4],
# [5, 5, 5]].map(&:uniq).all? { |a| a.size==1 }
#=> [[3], [4], [5]].all? { |a| a.size==1 }
#=> true
meaning arr[0] is to be rejected; i.e., not included in the returned array.
The remaining block calculations (for i=1,...,10) are similar.
We have computed
a #=> [1, 2, 3, 4, 5, 7]
which are the indices of all elements of arr except the last 3 that are to be retained. To a we add the indices of the last three elements of arr.
b = a.concat((arr.size-3..arr.size-1).to_a)
#=> a.concat((8..10).to_a)
#=> a.concat([8,9,10])
#=> [1, 2, 3, 4, 5, 7, 8, 9, 10]
Lastly,
arr.values_at(*b)
returns the array given in the example.
Your code snippet seems fine, although there are couple of things to note:
#arraysforloop.clone.delete(removed) removes all the occurences of removed array (not only the first one). E.g. [1,2,3,1].delete(1) would leave you with [2,3]. You could fix it with using an iterator for #projectsandtrials and delete_at method.
delete method returns the same argument you pass to it (or nil if no matches found). So #arraysforloop1 = #arraysforloop.clone.delete(removed) makes your #arraysforloop1 to contain the removed array's elements only! Removing an assignment could save you.
I see no reason to have two cloned arrays, #arraysforloop and #arraysforloop1, as the former one is not used anyhow later. May be we could omit one of them?
#projectsandtrials.delete(removed) leaves you in a strange state, as long as you're iterating the same array right now. This could end up with you missing the right next element after the removed one. Here is a simple snippet to illustrate the behaviour:
> a = [1,2,3]
> a.each{|e, index| puts("element is: #{e}"); a.delete(1);}
element is: 1
element is: 3
As you see, after deleting element 1 the loop moved to element 3 directly, omitting the 2 (as it became the first element in array and algorithm thinks it's been handled already).
One of the possibilities to make it less messy is to split it to a bundle of methods. Here is an option:
def has_searched_element? row
# I leave this method implementation to you
end
def next_rows_contain_three_duplicates?(elements, index)
# I leave this method implementation to you
end
def find_row_ids_to_remove elements
[].tap do |result|
elements.each_with_index do |row, index|
condition = has_searched_element?(row) && next_rows_contain_three_duplicates?(elements, index)
result << index if condition
end
end
end
row_ids_to_remove = find_row_ids_to_remove(#projectsandtrials)
# now remove all the elements at those ids out of #projectsandtrials

make sure two fields in array are unique

I need to make sure the follower_id and followed_id are unique in an array which also includes a third number, called value. All are integers. It is the combination of follower_id and followed_id that needs to be unique not the individual numbers themselves. Here is what I have
Relationship.populate 1..20 do |relationship|
relationship.follower_id = (1..20)
relationship.followed_id = (1..20)
relationship.value = rand(1..5)
end
this would ensure that
1,3,5
1,3,5
2,3,5
1,2,5
would be
1,3,5
2,3,5
1,2,5
Assuming, that the order in pairs is not to be taken into account, and you want to eliminate triples, even having different values, here you go:
a = [[1,3,5], [3,1,5], [2,3,5], [2,3,6], [1,2,5]]
# to count [1,3,5] and [3,1,5] as similar
a.group_by { |(fd,fr,_)| [fd,fr].sort }.values.map &:first
# to count [1,3,5] and [3,1,5] as different
a.group_by { |(fd,fr,_)| [fd,fr] }.values.map &:first
#⇒ [[1,3,5], [3,1,5], [2,3,5], [1,2,5]]
a = [[1,3,5], [1,3,4], [3,1,2], [2,3,2], [2,3,1], [1,2,3]]
If the order of the first two elements of each element of a is important:
a.uniq { |e| e[0,2] }
#=> [[1, 3, 5], [3, 1, 2], [2, 3, 2], [1, 2, 3]]
If the order of the first two elements of each element of a is not important:
require 'set'
a.uniq { |e| Set.new e[0,2] }
#=> [[1, 3, 5], [2, 3, 2], [1, 2, 3]]
Perfect Uniqueness
Here's a solution assuming the order within the triples matters and you want each triple to be perfectly unique. Just use the uniq method on the Array class. It works like this:
[
[1,3,5],
[1,3,5],
[2,3,5],
[1,2,5]
].uniq
#=> [[1, 3, 5], [2, 3, 5], [1, 2, 5]]
Partial Uniqueness
If instead, you only care about the first two being unique, pass the uniq method a block that returns whatever subset you want to be unique. If you only want the first two elements of the triple and can discard duplicates even when the third element is unique, you can just pass it the range 0..1.
[
[1,3,5],
[1,3,5],
[2,3,5],
[1,2,5],
[1,2,6]
].uniq { |triple| triple[0..1] }
#=> [[1, 3, 5], [2, 3, 5], [1, 2, 5]]
Note that the last element, [1,2,6] was discarded even though it ended in 6 because it was considered a duplicate of [1,2,5]. This is because [1,2,5][0..1] #=> [1,2] and [1,2,6][0..1] #=> [1,2].

Resources