sorting a multidimensional array in ruby - ruby

I have the following array:
[["2010-01-10", 2], ["2010-01-09", 5], ["2009-12-11", 3], ["2009-12-12", 12], ["2009-12-13", 0]]
I just want to sort it by the second value in each group and return the highest one, like i want to the output to be 12 with the given input above.
update
I might add that I made this into an array using to_a, from a hash, so if there is away to do the same with a hash that would be even better.

To sort by second value
x=[["2010-01-10", 2], ["2010-01-09", 5], ["2009-12-11", 3], ["2009-12-12", 12], ["2009-12-13", 0]]
x.sort_by{|k|k[1]}
=> [["2009-12-13", 0], ["2010-01-10", 2], ["2009-12-11", 3], ["2010-01-09", 5], ["2009-12-12", 12]]

Call the sort method on your hash to sort it.
hash = hash.sort { |a, b| b[1] <=> a[1] }
Then convert your hash to an array and extract the first value.
result = hash.to_a[0][1]

Use this on your hash:
hash.values.max
If you only need the highest element, there is no need to sort it!

If you want the key-value pair with the max value:
hash.max_by {|key, val| val} # => ["2009-12-12", 12]
requires Ruby 1.8.7+

Related

How can I sort this hash with an array as a value?

I have the following hash
{"f"=>[0, 1], "i"=>[1, 2], "n"=>[2, 2], "d"=>[3, 1], "g"=>[6, 1]}
I ultimately want to grab this value out of it "i"=>[1, 2]. I want to grab this value because the logic is that out of all these characters in some string, i has the highest value of occurrences 2 but appears first in a string given by its index in the array 1.
So for a string 'finding' the i character would be the first returned. I've made it so I generated this hash, now I just need to sort it so that the character that has the lowest index but the highest count will be first. Does anyone have an elegant solution to this?
In the Ruby sort_by block, Ruby can compare arrays, which is done in element order. In your case, you want reverse order by the second element of the array, then order by first element. So you can construct your sort block as follows:
arr = {"f"=>[0, 1], "i"=>[1, 2], "n"=>[2, 2], "d"=>[3, 1], "g"=>[6, 1]}
arr.sort_by { |a| [-a[1][1], a[1][0]] }.first
Ruby firsts converts arr to an array that looks like this:
[["f", [0, 1]], ["i", [1, 2]], ["n", [2, 2]], ["d", [3, 1]], ["g", [6, 1]]]
Then for each element that looks like [letter, [position, count]] (represented by the sort block argument, a), it is comparing [-count, position] for the sort.
This will give:
["i", [1,2]]
You can then do with that form whatever you wish.
Note, you can use max_by ... instead of sort_by ... .first in the above. I completely forgot about max_by, but Jörg W Mittag's nice answer reminded me.
If I understand the question correctly, there is no need to sort the hash at all to get the answer you are looking for, since you never actually need the sorted hash, you only need the maximum.
So, something like this should do the trick:
hash.max_by {|(_, (idx, frequency))| [frequency, -idx] }
#=> [?i, [1, 2]]
The entire thing would then look something like this:
str = 'Hello Nett'
str.
each_char.
with_index.
each_with_object(Hash.new {|h, k| h[k] = [0, nil]}) do |(char, idx), acc|
next unless /\p{Alphabetic}/ === char
char = char.downcase
acc[char][1] ||= idx
acc[char][0] += 1
end.
max_by {|(_, (frequency, idx))| [frequency, -idx] }
#=> [?n, [2, 1]]

Finding the mode of a Ruby Array (simplified_

I'm trying to find the mode of an Array. Mode = the element(s) that appear with the most frequency.
I know there are lots of tricks with #enumerable, but I'm not there yet in my learning. The exercise I'm doing assumes I can solve this problem without understanding enumerable.
I've written out my game plan, but I'm stuck on the 2nd part. I'm not sure if it's possible to compare a hash key against an array, and if found, increment the value.
def mode(array)
# Push array elements to hash. Hash should overwrite dup keys.
myhash = {}
array.each do |x|
myhash[x] = 0
end
# compare Hash keys to Array. When found, push +=1 to hash's value.
if myhash[k] == array[x]
myhash[k] += 1
end
# Sort hash by value
# Grab the highest hash value
# Return key(s) per the highest hash value
# rejoice!
end
test = [1, 2, 3, 3, 3, 4, 5, 6, 6, 6]
mode(test) # => 3, 6 (because they each appear 3 times)
You can create a hash with a default initial value:
myhash = Hash.new(0)
Then increment specific occurrences:
myhash["foo"] += 1
myhash["bar"] += 7
myhash["bar"] += 3
p myhash # {"foo"=>1, "bar"=>10}
With that understanding, if you replace your initial hash declaration and then do the incrementing in your array.each iterator, you're practically done.
myhash.sort_by{|key,value| value}[-1]
gives the last entry in the sorted set of hash values, which should be your mode. Note that there may be multiple modes, so you can iterate backwards while the value portion remains constant to determine them all.
There are many, many ways you could do this. Here are a few.
#1
array = [3,1,4,5,4,3]
a = array.uniq #=> [3, 1, 4, 5]
.map {|e| [e, array.count(e)]}
#=> [[3, 2], [1, 1], [4, 2], [5, 1]]
.sort_by {|_,cnt| -cnt} #=> [[3, 2], [4, 2], [1, 1], [5, 1]]
a.take_while {|_,cnt| cnt == a.first.last}
#=> [[3, 2], [4, 2]]
.map(&:first) #=> [3, 4]
#2
array.sort #=> [1, 3, 3, 4, 4, 5]
.chunk {|e| e}
#<Enumerator: #<Enumerator::Generator:0x000001021820b0>:each>
.map { |e,a| [e, a.size] } #=> [[1, 1], [3, 2], [4, 2], [5, 1]]
.sort_by { |_,cnt| -cnt } #=> [[4, 2], [3, 2], [1, 1], [5, 1]]
.chunk(&:last)
#<Enumerator: #<Enumerator::Generator:0x00000103037e70>:each>
.first #=> [2, [[4, 2], [3, 2]]]
.last #=> [[4, 2], [3, 2]]
.map(&:first) #=> [4, 3]
#3
h = array.each_with_object({}) { |e,h|
(h[e] || 0) += 1 } #=> {3=>2, 1=>1, 4=>2, 5=>1}
max_cnt = h.values.max #=> 2
h.select { |_,cnt| cnt == max_cnt }.keys
#=> [3, 4]
#4
a = array.group_by { |e| e } #=> {3=>[3, 3], 1=>[1], 4=>[4, 4], 5=>[5]}
.map {|e,ees| [e,ees.size]}
#=> [[3, 2], [1, 1], [4, 2], [5, 1]]
max = a.max_by(&:last) #=> [3, 2]
.last #=> 2
a.select {|_,cnt| cnt == max}.map(&:first)
#=> [3, 4]
In your approach, you have first initialized a hash containing keys taken from the unique values of the array, with the associated values all set to zero. For example, the array [1,2,2,3] would create the hash {1: 0, 2: 0, 3: 0}.
After this, you plan to count the instances of each of the values in the array by incrementing the value for the associated key in the hash by one for each instance. So, after finding the number 1 in the array, the hash would look like so: {1: 1, 2: 0, 3: 0}. You clearly need to do this for each value in the array, so given your approach and current level of understanding, I would suggest looping through the array again:
array.each do |x|
myhash[x] += 1
end
As you can see, we don't need to check that myhash[k] == array[x] since we have already created a key:value pair for each number in the array.
However, while this approach will work, it's not very efficient: we're having to loop through the array twice. The first time to initialize all the key:value pairs to some default (zero, in this case), and the second to count the frequencies of each number.
Since the default value for each key will be zero, we can remove the need to initialize the defaults by using a different hash constructor. myhash = {} will return nil if we access a key that doesn't exist, but myhash = Hash.new(0) will return 0 if we access a non-existent key (note that you could provide any other value or variable, if required).
By providing a default value of zero, we can get rid of the first loop entirely. When the second loop finds a key that doesn't exist, it will use the default provided and automatically initialize it.
def mode(array)
array.group_by{ |e| e }.group_by{ |k, v| v.size }.max.pop.map{ |e| e.shift }
end
Using the simple_stats gem:
test = [1, 2, 3, 3, 3, 4, 5, 6, 6, 6]
test.modes #=> [3, 6]
If it is an unsorted array, we can sort the array in descending order
array = array.sort!
Then use the sorted array to create a hash default 0 and with each element of the array as a key and number of occurrence as the value
hash = Hash.new(0)
array.each {|i| hash[i] +=1 }
Then mode will be the first element if the hash is sorted in descending order of value(number of occurrences)
mode = hash.sort_by{|key, value| -value}.first[0]

Is there any way to get the Nth key or value of an ordered hash other than converting it to an array?

In Ruby 1.9.x I have a hash that maintains its order
hsh = {9=>2, 8=>3, 5=>2, 4=>2, 2=>1}
Is there a way to get say the key of the third element other than this:
hsh.to_a[2][0]
Try using Hash#keys and Hash#values:
thirdKey = hsh.keys[2]
thirdValue = hsh.values[2]
Why do you use hash instead of an array? The array fits perfect for ordered collections.
array = [[9, 2], [8, 3], [5, 2], [4, 2], [2, 1]]
array.each { |pair| puts pair.first }
Sorting of arrays is very simple, ultimately.
disordered_array = [[4,2], [9,2], [8,3], [2,1], [5,2]]
disordered_array.sort { |a,b| b <=> a }
=> [[9, 2], [8, 3], [5, 2], [4, 2], [2, 1]]
Correct me if I'm wrong.

Sorting by a value in an object in an array in Ruby

I have a bunch of objects in an array and would like to sort by a value that each object has. The attribute in question in each object is a numeric value.
For example:
[[1, ..bunch of other stuff],[5, ""],[12, ""],[3, ""],]
would become:
[[1, ..bunch of other stuff],[3, ""],[5, ""],[12, ""],]
I want to sort by the numerical value stored in each of the objects.
[5, 3, 4, 1, 2] becomes [1, 2, 3, 4, 5], however these numbers are stored inside objects.
The other answers are good but not minimal. How about this?
lst.sort_by &:first
The sort method can take a block to use when comparing elements:
lst = [[1, 'foo'], [4, 'bar'], [2, 'qux']]
=> [[1, "foo"], [4, "bar"], [2, "qux"]]
srtd = lst.sort {|x,y| x[0] <=> y[0] }
=> [[1, "foo"], [2, "qux"], [4, "fbar"]]
Assuming that you want to sort only according to the first element,
[[1, ..bunch of other stuff],[5, ""],[12, ""],[3, ""],].
sort_by{|n, *args| n}
or
[[1, ..bunch of other stuff],[5, ""],[12, ""],[3, ""],].
sort_by{|n, args| n}
When sorting objects and complex structures use sort_by. Sort_by performs a "Schwartzian Transform" which can make a major difference in sort speed.
Because you didn't provide enough information to be usable I'll recommend you read the docs linked above. You'll find its very easy to implement and can make a big difference.

Count if there is more than 1 occurrence in a 2d array in Ruby

I've got a sorted array:
array = [[4, 13], [1, 12], [3, 8], [2, 8], [0, 3]]
Which shows me a position (array[n][0]) and the number of occurrences of that position (array[n][1]).
I need to test to see if more than one item in the array has the same number of occurrences as the last item.
I thought I might be able to do it with this:
array.detect {|i| i[1] == array.last[1] }.length
But it returns 2 for the above array, and seems to also return 2 for the following array:
array = [[4, 13], [1, 12], [3, 8], [2, 3], [0, 3]]
When I run it without length it always returns the first occurrence.
Is there a way to get this to count the occurrences?
EDIT:
Sorry, will ask my follow up question in a new question.
Try using find_all instead of detect. detect returns the first match. In your first example, that's array[3], which is another array of length 2. That's why it's returning 2 (it should always be returning 2 or nil for your arrays). find_all will return an array of the matches (instead of the first match itself), and its length will be the value you want.
Here's a clearer way of doing what you want, with none of the associated worries.
array.count{|i| i[1] == array.last[1]}

Resources