Right now I'm doing this, and it works:
groups = [[1, 1, 1], [2, 2]]
groups.select { |g| g.size == groups.max.size }.size
# => 1 # a clear majority
groups = [[1, 1], [2, 2]]
groups.select { |g| g.size == groups.max.size }.size
# => 2 # needs to be passed to another filter
but I have a suspicion there's a cleaner way.
You can do this snippet:
groups.group_by(&:size)[groups.max.size].size
Let me quickly explain what this does. I apologise in advance for the bad wording as "group" is a rather overloaded term here...
What it does, is first to group the arrays by size. This returns a hash:
groups = [[1, 1, 1], [2, 2]]
grouped = groups.group_by(&:size)
# => {3=>[[1, 1, 1]], 2=>[[2, 2]]}
Then, you take the array of group arrays containing exactly as many elements as the largest group
largest_list = grouped[groups.max.size]
# => [[2, 2]]
Now, you can simple get the size of this array to get the number of groups which have this length:
largest_list.size
# => 1
The reason why your approach is rather slow is that you calculate groups.max.size in your inner loop each time again.
Related
I'm fairly new to learning Ruby so please bear with me. I am working on a 7 kyu Ruby coding challenge and I've been tasked with finding how many people are left on the bus (first value represents people on, second value, people off) please look at comments in code for more detail.
below is a test example:
([[10, 0], [3, 5], [5, 8]]), # => should return 5"
This is my solution so far:
def number(bus_stops)
bus_stops.each{ | on, off | on[0] -= off[1] }
end
bus_stops
# loop through the array
# for the first array in the nested array subtract second value from first
# add the sum of last nested array to first value of second array and repeat
# subtract value of last element in nested array and repeat
How can I approach this? any resources you would recommend?
There would be many ways to achieve this. Here is one with inject
arr.map { |inner_array| inner_array.inject(&:-) }.inject(&:+)
Iterate over the arrays and calculate the count at each position of how many people would have been left on the bus (this can return negative integers). This will return
[10, -2, -3]
[10 on, none off][3 on, 5 off][5 on, 8 off]
Then inject a + operator between each element to calculate the sum of people left on the bus. This only works if you count from 0 people on and 0 people off.
Here are two other ways to compute the desired result.
arr = [[10, 0], [3, 5], [5, 8]]
Use Array#transpose
arr.transpose.map(&:sum).reduce(:-)
#=> 5
The steps are as follows.
a = arr.transpose
#=> [[10, 3, 5], [0, 5, 8]]
b = a.map(&:sum)
#=> [18, 13] ([total ons, total offs])
b.reduce(:-)
#=> 5
Use Matrix methods
require 'matrix'
(Matrix.row_vector([1] * arr.size) * Matrix[*arr] * Matrix.column_vector([1,-1]))[0,0]
#=> 5
The steps are as follows.
a = [1] * arr.size
#=> [1, 1, 1]
b = Matrix.row_vector(a)
#=> Matrix[[1, 1, 1]]
c = Matrix[*arr]
#=> Matrix[[10, 0], [3, 5], [5, 8]]
d = b * c
#=> Matrix[[18, 13]]
e = Matrix.column_vector([1,-1])
#=> Matrix[[1], [-1]]
f = d * e
#=> Matrix[[5]]
f[0,0]
#=> 5
See Matrix::[], Matrix::row_vector, Matrix::column_vector and Matrix#[]. Notice that the instance method [] is documented in Object.
sum takes a block, which is really simple in this case:
arr = [[10, 0], [3, 5], [5, 8]]
p arr.sum{|on, off| on - off} # => 5
So you were very close.
I am trying to sum the elements of an array by grouping by the first element.
ex:
[[1, 8], [3, 16], [1, 0], [1, 1], [1, 1]]
should give
[ {1 => 10}, {3 => 16} ]
It is summing the values in the original array where the first element was 1 and 3. The data structures in the end result don't matter, ex: an array of arrays, an array of hash or just a hash is fine.
Some tries:
k = [[1, 8], [3, 16], [1, 0], [1, 1], [1, 1]]
h = {}
k.inject({}) { |(a,b)| h[a] += b}
#=> undefined method `+' for nil:NilClass
data = [[1, 8], [3, 16], [1, 0], [1, 1], [1, 1]]
data.each_with_object({}) { |(k, v), res| res[k] ||= 0; res[k] += v }
gives
{1=>10, 3=>16}
there is also inject version although it's not so laconic:
data.inject({}) { |res, (k, v)| res[k] ||= 0; res[k] += v; res }
inject vs each_with_object
You're pretty close, some changes are needed on your code:
k.inject({}) do |hash, (a, b)|
if hash[a].nil?
hash[a] = b
else
hash[a] += b
end
hash
end
First of all, you don't need the h variable. #inject accepts an argument, often called the accumulator, which you can change it for each array element and then get as the return. Since you're already passing an empty hash to inject, you don't need the variable.
Next, you have to handle the case where the key doesn't yet exist on the hash, hence the if hash[a].nil?. In that case, we assign the value of b to the hash where the key is a. When the key exists in the hash, we can safely sum the value.
Another thing to notice is that you are using the wrong arguments of the block. When calling #inject, you first receive the accumulator (in this case, the hash), then the iteration element.
Documentation for #inject
k.group_by(&:first).transform_values {|v| v.map(&:last).sum }
You actually used the words "group by" in your question, but you never grouped the array in your code. Here, I first group the inner arrays by their first elements, ending up with:
{ 1 => [[1, 8], [1, 0], [1, 1], [1, 1]], 3 => [[3, 16]] }
Next, I only want the last element of all of the inner arrays, since I already know that the first is always going to be the key, so I use Hash#transform_values to map the two-element arrays to their last element. Lastly, I Enumerable#sum those numbers.
I would like to get all the possible partitions (disjoint subsets of a set which union is the original set) of a multiset (some elements are equal and non-distinguishable from each other).
Simpler case when one would like to yield the partitions of a simple set, in which there are no elements with multiplicity, in other words all elements are different. For this scenario I found this Ruby code on StackOwerflow which is very efficient, as not storing all the possible partitions, but yielding them to a block:
def partitions(set)
yield [] if set.empty?
(0 ... 2 ** set.size / 2).each do |i|
parts = [[], []]
set.each do |item|
parts[i & 1] << item
i >>= 1
end
partitions(parts[1]) do |b|
result = [parts[0]] + b
result = result.reject do |e|
e.empty?
end
yield result
end
end
end
Example:
partitions([1,2,3]){|e| puts e.inspect}
outputs:
[[1, 2, 3]]
[[2, 3], [1]]
[[1, 3], [2]]
[[3], [1, 2]]
[[3], [2], [1]]
As there are 5 different partitioning of the set [1,2,3] (Bell-number anyway: https://en.wikipedia.org/wiki/Bell_number)
However the another set which is in fact a multiset contains elements with multiplicity, then above code doesn't work of course:
partitions([1,1,2]){|e| puts e.inspect}
outputs:
[[1, 1, 2]]
[[1, 2], [1]] *
[[1, 2], [1]] *
[[2], [1, 1]]
[[2], [1], [1]]
One can see two identical partitions, denoted with *, which should be yielded only once.
My question is: how can I modify the def partitions() method to work with multisets too, or how can I filter out the identical partitionings, duplications in an efficient way? Are those identical partitionings coming always followed by each other in a consecutive manner?
My goal is to organize images with different aspect ratio to a montage, and the picture rows of the montage would be those set partitions. I would like to minimalize the difference of the heights between the picture rows (or the standard deviation equivalently) among the possible partitionings, but many times there are pictures with same aspect ratios this is why I try to deal with a multiset.
Yielding not partitons but powersets (all possibe subsets) of a multiset, filtering out the duplicates by simple memoization:
Montage optimization by backtracking on YouTube
You could put it in an array and use uniq:
arr = []
partitions([1,1,2]) { |e| arr << e }
puts arr.to_s
#-> [[[1, 1, 2]], [[1, 2], [1]], [[1, 2], [1]], [[2], [1, 1]], [[2], [1], [1]]]
puts arr.uniq.to_s
#-> [[[1, 1, 2]], [[1, 2], [1]], [[2], [1, 1]], [[2], [1], [1]]]
I am a little lost on the block below.
def sort_string(string)
string.split(" ").sort{|a,b| a.length <=> b.length}.join(" ")
end
The array is sorted based on the length (least to greatest). My confusion comes from what the variable b in the block of code is.
If I split the string "example string here" into an array and then sort it, how is [example],[string],[here] passed into the block {|a,b| a.length <=> b.length}? I don't understand how the elements of the array are passed into the code and then compared.
When using sort, Ruby passes two objects into the block. They are to be compared, either using the built-in <=> method, or by some machination you devise that determines whether one is less-than (-1), equal-to (0), or greater-than (1) the other. So, a is one and b is the other.
Meditate on this:
[1, 2, 3, 4].shuffle # => [4, 1, 3, 2]
.sort { |i, j|
[i, j] # => [4, 1], [4, 3], [1, 3], [4, 2], [3, 2], [1, 2]
i <=> j # => 1, 1, -1, 1, 1, -1
}
# => [1, 2, 3, 4]
Remember what <=> does and compare the values returned for the i <=> j comparison each time through the loop.
But of course you knew this from reading the documentation for sort:
http://ruby-doc.org/core-2.3.0/Enumerable.html#method-i-sort
http://ruby-doc.org/core-2.3.0/Array.html#method-i-sort
I'm working with chart data. I have three sets of data from three sources, and I'm trying to add them together by their year into a new away. In my example, the years are 0, 1, 2.
visual:
data = [[[year, value], [year, value], [year, value]],
[[year, value], [year, value], [year, value]],
[[year, value], [year, value], [year, value]]]
Here is an example with actual data:
data = [[[0, 1], [1, 2], [2, 3]],
[[0, 4], [1, 5], [2, 6]],
[[0, 7], [1, 8], [2, 9]]]
I'm trying to get the following result:
data = [[0, 12], [1, 15], [2, 18]]
To add to complexity, it won't always be three sets of data, it may be one set or twelve sets, any number.
Any help is greatly appreciated.
Solution:
data.map(&:to_h).reduce({}) {|memo, h| memo.merge(h) {|_,v1,v2| v1 + v2} }.to_a
Explanation:
Step 1: Convert the data array into array of hashes
data_hash = data.map(&:to_h)
#=> [{0=>1, 1=>2, 2=>3}, {0=>4, 1=>5, 2=>6}, {0=>7, 1=>8, 2=>9}]
Step 2: Reduce the array of hash by merging each hash with one another, while ensuring that values are added together for a given key.
reduced_hash = data_hash.reduce({}) {|memo, h| memo.merge(h) {|_,v1,v2| v1 + v2} }
#=> {0=>12, 1=>15, 2=>18}
We use empty hash {} as initial value of memo, and merge each hash present in data_hash array with it - the block to merge will ensure that when a key is being merged, its values are added up so that eventually we end up with sum of all values of that key
Step 3: Use to_a on the hash to get array result
reduced_hash.to_a
#=> [[0, 12], [1, 15], [2, 18]]