Finding the mode of a Ruby Array (simplified_ - ruby

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]

Related

Convert Ruby array of elements to Hash of counts with indices

Given a two dimensional array in Ruby:
[ [1, 1, 1],
[1, 1],
[1, 1, 1, 1],
[1, 1]
]
I'd like to create a Hash, where the keys are the counts of each internal array, and the values are arrays of indices of the original array whose internal array sizes have the particular count. The resulting Hash would be:
{ 2 => [1, 3], 3 => [0], 4 => [2] }
How do I concisely express this functionally in Ruby? I am attempting something akin to Hash.new([]).tap { |h| array.each_with_index { |a, i| h[a.length] << i } }, but the resulting Hash is empty.
There are two problems with your code. The first is that when h is empty and you write, say, h[2] << 1, since h does not have a key 2, h[2] returns the default, so this expression becomes [] << 1 #=> [1], but [1] is not attached to the hash, so no key and value are added.
You need to write h[2] = h[2] << 11. If you do that, your code returns h #=> {3=>[0, 1, 2, 3], 2=>[0, 1, 2, 3], 4=>[0, 1, 2, 3]}. Unfortunately, that's still incorrect, which takes us to the second problem with your code: you did not define the newly-created hash's default value correctly.
First note that
h[3].object_id
#=> 70113420279440
h[2].object_id
#=> 70113420279440
h[4].object_id
#=> 70113420279440
Aha, all three values are the same object! new's argument [] is returned by h[k] when h does not have a key k. The problem is that is the same array is returned for all keys k added to the hash, so you would be adding a key-value pair to an empty array for the first new key, then adding a second key-value pair to that same array for the next new key, and so on. See below for how the hash needs to be defined.
With these two changes your code works fine, but I would suggest writing it as follows.
arr = [ [1, 1, 1], [1, 1], [1, 1, 1, 1], [1, 1] ]
arr.each_with_index.with_object(Hash.new {|h,k| h[k]=[]}) { |(a,i),h|
h[a.size] << i }
#=> {3=>[0], 2=>[1, 3], 4=>[2]}
which use the form of Hash::new that uses a block to calculate the hash's default value (i.e., the value returned by h[k] when a hash h does not have a key k),
or
arr.each_with_index.with_object({}) { |(a,i),h| (h[a.size] ||= []) << i }
#=> {3=>[0], 2=>[1, 3], 4=>[2]}
both of which are effectively the following:
h = {}
arr.each_with_index do |a,i|
sz = a.size
h[sz] = [] unless h.key?(sz)
h[a.size] << i
end
h #=> {3=>[0], 2=>[1, 3], 4=>[2]}
Another way is to use Enumerable#group_by, grouping on array size, after picking up the index for each inner array.
h = arr.each_with_index.group_by { |a,i| a.size }
#=> {3=>[[[1, 1, 1], 0]],
# 2=>[[[1, 1], 1], [[1, 1], 3]],
# 4=>[[[1, 1, 1, 1], 2]]}
h.each_key { |k| h[k] = h[k].map(&:last) }
#=> {3=>[0], 2=>[1, 3], 4=>[2]}
1 The expression h[2] = h[2] << 1 uses the methods Hash#[]= and Hash#[], which is why h[2] on the left of = does not return the default value. This expression can alternatively be written h[2] ||= [] << 1.
arry = [ [1, 1, 1],
[1, 1],
[1, 1, 1, 1],
[1, 1]
]
h = {}
arry.each_with_index do |el,i|
c = el.count
h.has_key?(c) ? h[c] << i : h[c] = [i]
end
p h
This will give you
{3=>[0], 2=>[1, 3], 4=>[2]}

Assigning values to a 2D array using "each" method

I'm trying to transpose [[0, 1, 2], [3, 4, 5], [6, 7, 8]]. I get [[2, 5, 8], [2, 5, 8], [2, 5, 8]].
I can see what is happening with the line p transposed_arr but do not understand why this is happening. At every iteration it changes every row instead of only one.
def my_transpose(arr)
# number of rows
m = arr.count
#number of columns
n = arr[0].count
transposed_arr = Array.new(n, Array.new(m))
# loop through the rows
arr.each_with_index do |row, index1|
# loop through the colons of one row
row.each_with_index do |num, index2|
# swap indexes to transpose the initial array
transposed_arr[index2][index1] = num
p transposed_arr
end
end
transposed_arr
end
You need to make only one wee change and your method will work fine. Replace:
transposed_arr = Array.new(n, Array.new(m))
with:
transposed_arr = Array.new(n) { Array.new(m) }
The former makes transposed_arr[i] the same object (an array of size m) for all i. The latter creates a separate array of size m for each i
Case 1:
transposed_arr = Array.new(2, Array.new(2))
transposed_arr[0].object_id
#=> 70235487747860
transposed_arr[1].object_id
#=> 70235487747860
Case 2:
transposed_arr = Array.new(2) { Array.new(2) }
transposed_arr[0].object_id
#=> 70235478805680
transposed_arr[1].object_id
#=> 70235478805660
With that change your method returns:
[[0, 1, 2],
[3, 4, 5],
[6, 7, 8]]

Operate on array elements without changing index

I'm trying to operate on certain elements of an array while referencing their index in the block. Operating on the whole array is easy
arr = [1, 2, 3, 4, 5, 6, 7, 8]
arr.each_with_index { |num, index| puts "#{num}, "#{index}" }
But what if I want to work just with elements 4, 6 to return
4, 3
6, 5
I can create a new array composed of certain elements of the original and run the block on that, but then the index changes.
How can I select the elements and their index?
Just put a condition on it:
indice = [3, 5]
arr.each_with_index do
|num, index| puts "#{num}, #{index}" if indice.include?(index)
end
This is another style:
indice = [3, 5]
arr.each_with_index do
|num, index|
next unless indice.include?(index)
puts "#{num}, #{index}"
end
I cannot tell from the question whether you are given values in the array and want to obtain their indices, or vice-versa. I therefore will suggest one method for each task. I will use this array for examples:
arr = [1, 2, 3, 4, 5, 6, 7, 8]
Values to Indices
If you are given values:
vals = [4, 6]
you can retrieve the number-index pairs like this:
vals.map { |num| [num, arr.index(num)] }
#=> [[4, 3], [6, 5]]
or print them directly:
vals.each { |num| puts "#{num}, #{arr.index(num)}" }
# 4, 3
# 6, 5
#=> [4, 6]
If an element of vals is not present in arr:
vals = [4, 99]
vals.map { |num| [num, arr.index(num)] }
#=> [[4, 3], [99, nil]]
Indices to Values
If you are given indices:
indices = [3, 5]
you can retrieve the index-value pairs like this:
indices.zip(arr.values_at(*indices))
#=> [[3, 4], [5, 6]]
and then print in whatever format you like.
If an index is out-of-range, nil will be returned:
indices.zip(arr.values_at(*[3, 99]))
#=> [[3, 4], [5, nil]]

Replace array elements with map

I have two arrays:
#a = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
#b = [a, b, c]
I need to replace n-th column in a with b like:
swap_column(0)
#=> [a, 2, 3]
[b, 5, 6]
[c, 8, 9]
(This is for using Cramer's rule for solving equations system, if anybody wonders.)
The code I've come up with:
def swap_column(n)
#a.map.with_index { |row, j| row[n] = #b[j] }
end
How do I get rid of assignment here so that map returns the modified matrix while leaving #a intact?
What you wanted is dup. Also, you had the return value of the map.with_index block wrong.
def swap_column(i)
#a.map.with_index{|row, j| row = row.dup; row[i] = #b[j]; row}
end
or
def swap_column(i)
#a.map.with_index{|row, j| row.dup.tap{|row| row[i] = #b[j]}}
end
The answer by sawa is good and the main point is you need to dup your inner arrays for this to work properly. The only reason for this additional post is to point out that often when you are using with_index so that you can directly 1:1 index into another array you can simplify the code by using zip.
def swap_column(n)
#a.zip(#b).map {|r,e| r.dup.tap{|r| r[n] = e}}
end
What zip does is combine your two arrays into a new array where each element is an array made of the two corresponding elements of the initial arrays. In this case it would be an array of an array and an element you want to later use for replacement. We then map over those results and automatically destructure each element into the two pieces. We then dup the array piece and tap it to replace the nth element.
You can use transpose to do the following:
class M
attr :a, :b
def initialize
#a = [[1,2,3],
[4,5,6],
[7,8,9]
]
#b = [:a, :b, :c]
end
def swap_column(n)
t = #a.transpose
t[0] = #b
t.transpose
end
end
m = M.new
=> #<M:0x007ffdc2952e38 #a=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], #b=[:a, :b, :c]>
m.swap_column(0)
=> [[:a, 2, 3], [:b, 5, 6], [:c, 8, 9]]
m # m is unchanged
=> #<M:0x007ffdc2952e38 #a=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], #b=[:a, :b, :c]>

How do I add a cumulative sum to an array for only one value?

I have an array of arrays with x and y values:
[[some_date1, 1], [some_date2, 3], [some_date3, 5], [some_date4, 7]]
The result should only sum the y values (1, 3, 5, 7) so that the result is like this:
[[some_date1, 1], [some_date2, 4], [some_date3, 9], [some_date4, 16]]
How is this possible in Ruby?
Yes, this is possible in Ruby. You can use [map][1] and do something like this:
sum = 0
array.map {|x,y| [x, (sum+=y)]}
This is how it works. For the given the input:
array = ["one", 1], ["two", 2]
It will iterate through each of the elements in the array e.g.) the first element would be ["one", 1].
It will then take that element (which is an array itself) and assign the variable x to the first element in that array e.g.) "one" and y to the second e.g.) 1.
Finally, it will return an array with the result like this:
=> ["one", 1], ["two", 3]
You can use map:
a = [[:some_date1, 1], [:some_date2, 3], [:some_date3, 5], [:some_date4, 7]]
sum = 0
a.map { |f, v| [f, (sum = sum + v)]}
=> [[:some_date1, 1], [:some_date2, 4], [:some_date3, 9], [:some_date4, 16]]
Since sum will be nil in the first iteration it is necessary to call to_i on it.
a = [['some_date1', 1], ['some_date2', 3], ['some_date3', 5], ['some_date4', 7]]
a.each_cons(2){|a1, a2| a2[1] += a1[1]}
last = 0
arr.map do |a, b|
last = last + b
[a, last]
end
I'd use:
ary = [['some_date1', 1], ['some_date2', 3], ['some_date3', 5], ['some_date4', 7]]
ary.inject(0) { |m, a|
m += a[-1]
a[-1] = m
}
After running, ary is:
[["some_date1", 1], ["some_date2", 4], ["some_date3", 9], ["some_date4", 16]]
The reason I prefer this is it doesn't require the addition of an accumulator variable. inject returns a value but it gets thrown away without an assignment.

Resources