How to get the most common class among elements - ruby

How can I get the most common data type (i.e. class) among the elements of an array? For example, for this array:
array = [nil, "string", 1, 3, 0.234, 25, "hot potato"]
Integer should be returned since it's the most common class.

array.group_by(&:class).max_by{|k, v| v.length}.first
# => Integer

array.each_with_object(Hash.new(0)) { |e,h| h[e.class] += 1 }.
max_by(&:last).
first
#=> Integer
the first step being
array.each_with_object(Hash.new(0)) { |e,h| h[e.class] += 1 }
#=> {NilClass=>1, String=>2, Integer=>3, Float=>1}

Following can also work,
array.inject(Hash.new(0)) { |h,v| h[v.class] += 1; h }.max_by(&:last).first

Related

Iterate over an array and initialize multiple variables in one line in Ruby

I am trying to iterate over an array and count the number of positive, negative and zeros in an array. Right now I am doing it like this
arr = [1, -1, 0, 2, 3, -2, -5]
pos = arr.select { |i| i > 0 }.count
neg = arr.select { |i| i < 0 }.count
zero = arr.select { |i| i == 0 }.count
puts pos
puts neg
puts zero
But is there any way where I can do this in one line? Something like this?
pos, neg, zero = arr.select { |i| i > 0; i < 0; i == 0; }.count
Use inject and the <=> operator:
neg, zero, pos = arr.inject([0,0,0]) { |a,b| a[(b<=>0)+1] += 1; a }
Alternatively, as #HolgerJust mentioned:
neg, zero, pos = arr.each_with_object([0,0,0]) { |a,b| b[(a<=>0)+1] += 1 }
is slightly longer but doesn't have the extra ; a in the block.
Inspired by #steenslag's use of tally:
neg, zero, pos = arr.map { |x| x<=>0 }.tally.values_at(-1,0,1)
If you use a counting hash the code is short and the results are returned in a hash, which may be convenient.
arr = [1, -1, 0, 2, 3, -2, -5, 4]
You could write
arr.each_with_object(Hash.new(0)) { |n,h| h[n<=>0] += 1 }
#=> {1=>4, -1=>3, 0=>1}
or perhaps you would prefer
labels = { -1=>:neg, 0=>:zero, 1=>:pos }
arr.each_with_object(Hash.new(0)) { |n,h| h[labels[n<=>0]] += 1 }
#=> {:pos=>4, :neg=>3, :zero=>1}
the last line of which could alternatively be written
arr.each_with_object({}) { |n,h| h[labels[n<=>0]] = (h[labels[n<=>0]] ||= 0) + 1 }
See Hash::new, specifically the (second) form that takes an argument called the default value (here zero), and no block. If a hash is defined h = Hash.new(0), then if h has no key k, h[k] returns 0 (and h is not changed).
arr = [1, -1, 0, 2, 3, -2, -5]
neg, zero, pos = arr.map{|n| n <=> 0}.tally.values_at(-1, 0, 1)
Using the new tally method.
As others have already said, you should just use inject and count using the <=> operator. If you plan to use similar logic frequently, you could monkey patch a #tally_by method into Enumerable like so:
class Enumerable
def my_tally(*keys, &proc)
proc ||= -> e {e} # Default identity proc
h = keys.empty? ? Hash.new(0) : Hash[keys.map{|k|[k, 0]}]
inject(h){|a, e| a[proc.call(e)] += 1; a}
end
end
This allows you to write:
neg, zero, pos = arr.my_tally(-1, 0, 1){|e| e <=> 0}
While this is certainly more upfront code than the others, it may be nice to have if you find yourself using similar logic frequently. You could also just make this a regular method somewhere if you don't like monkey-patching.

How to sum values in an array with different hash

I want to sum the total values of the same items in an array.
I have an array as
[{"a"=>1},{"b"=>2},{"c"=>3},{"a"=>2},{"b"=>4}]
I want to get the result as
[{"a"=>3},{"b"=>6},{"c"=>3}]
Which method can do it?
if:
array = [{"a"=>1},{"b"=>2},{"c"=>3},{"a"=>2},{"b"=>4}]
then you can do:
array.inject(Hash.new{|h,k| h[k] = 0})
{ |h, a| k, v = a.flatten; h[k] += v; h }.
map{|arr| Hash[*arr] }
#=> [{"a"=>3}, {"b"=>6}, {"c"=>3}]
or:
array.each_with_object(Hash.new{|h,k| h[k] = 0})
{ |a, h| k, v = a.flatten; h[k] += v }.
map{|arr| Hash[*arr] }
#=> [{"a"=>3}, {"b"=>6}, {"c"=>3}]
It can be done as follows
array.group_by { |h| h.keys.first }.
values.
map {|x| x.reduce({}) { |h1, h2| h1.merge(h2) { |_, o, n| o + n } }
#=> [{"a"=>3}, {"b"=>6}, {"c"=>3}]
Every time you want to transform a collection in not a one-to-one way, it's job for #reduce. For one-to-one transformations we use #map.
array.reduce({}) { |h, acc| acc.merge(h) {|_k, o, n| o+n } }.zip.map(&:to_h)
# => [{"b"=>6}, {"a"=>3}, {"c"=>3}]
Here we use reduce with the initial value {}, which is passed to the block as the acc parameter, and then we use #merge with manual "conflicts resolution". It means that the block is called only when the key we're trying to merge is already present in the method receiver, acc. After that we break the hash into an array of hashes.
There are many ways to do this. It is instructive to see a few, even some that may be unusual and/or not especially efficient.
Here is another way:
arr = [{"a"=>1},{"b"=>2},{"c"=>3},{"a"=>2},{"b"=>4}]
arr.flat_map(&:keys)
.uniq
.map { |k| { k=>arr.reduce(0) { |t,g| t + (g.key?(k) ? g[k] : 0) } } }
#=> [{"a"=>3}, {"b"=>6}, {"c"=>3}]
Since nil.to_i => 0, we could instead write reduce's block as:
{ |t,g| t+g[k].to_i }

ruby adding range elements in each block

How can I add range elements in each block together. E.g.:
a = [[1..10, 13..20, 21..24], [34..39, 42..45], [50..55]]
outcome:
a = [[9+7+3],[5+3],[5]]
a = [20, 8, 5]
You can access the first and last elements of a range by the corresponding first and last methods. inject(:+) sums up the partial distances of all the ranges belonging to the same group.
a.map { |ran­ges| range­s.map { |rang­e| range­.last - range­.first }.inj­ect(:+) }
=> [19, 8, 5]
Or, even shorter, as suggested by tokland using Ruby 2.0:
a.map { |ran­ges| range­s.map(&:size).reduce(0, :+) }
Ruby 2.0:
a.map { |ranges| ranges.map { |r| r.size - 1 } .reduce(0, :+) }
Range class has a method called #size. Thus we can do as :
a.map { |ranges| ranges.inject(0) { |sum,rng| sum + rng.size - 1 } }

Ruby array with an extra state

I'm trying to go through an array and add a second dimension for true and false values in ruby.
For example. I will be pushing on arrays to another array where it would be:
a = [[1,2,3,4],[5]]
I would like to go through each array inside of "a" and be able to mark a state of true or false for each individual value. Similar to a map from java.
Any ideas? Thanks.
You're better off starting with this:
a = [{ 1 => false, 2 => false, 3 => false, 4 => false }, { 5 => false }]
Then you can just flip the booleans as needed. Otherwise you will have to pollute your code with a bunch of tests to see if you have a Fixnum (1, 2, ...) or a Hash ({1 => true}) before you can test the flag's value.
Hashes in Ruby 1.9 are ordered so you wouldn't lose your ordering by switching to hashes.
You can convert your array to this form with one of these:
a = a.map { |x| Hash[x.zip([false] * x.length)] }
# or
a = a.map { |x| x.each_with_object({}) { |i,h| h[i] = false } }
And if using nil to mean "unvisited" makes more sense than starting with false then:
a = a.map { |x| Hash[x.zip([nil] * x.length)] }
# or
a = a.map { |x| x.each_with_object({}) { |i,h| h[i] = nil } }
Some useful references:
Hash[]
each_with_object
zip
Array *
If what you are trying to do is simply tag specific elements in the member arrays with boolean values, it is just a simple matter of doing the following:
current_value = a[i][j]
a[i][j] = [current_value, true_or_false]
For example if you have
a = [[1,2,3,4],[5]]
Then if you say
a[0][2] = [a[0,2],true]
then a becomes
a = [[1,2,[3,true],4],[5]]
You can roll this into a method
def tag_array_element(a, i, j, boolean_value)
a[i][j] = [a[i][j], boolean_value]
end
You might want to enhance this a little so you don't tag a specific element twice. :) To do so, just check if a[i][j] is already an array.
Change x % 2 == 0 for the actual operation you want for the mapping:
>> xss = [[1,2,3,4],[5]]
>> xss.map { |xs| xs.map { |x| {x => x % 2} } }
#=> [[{1=>false}, {2=>true}, {3=>false}, {4=>true}], [{5=>false}]]

Ruby: How to either set a variable to 0 or, if it is already set, increment by 1

I know of the ||= operator, but don't think it'll help me here...trying to create an array that counts the number of "types" among an array of objects.
array.each do |c|
newarray[c.type] = newarray[c.type] ? newarray[c.type]+1 ? 0
end
Is there a more graceful way to do this?
types = Hash.new(-1) # It feels like this should be 0, but to be
# equivalent to your example it needs to be -1
array.each do |c|
types[c.type] += 1
end
Use the Array#fetch method for which you can provide a default value if the index doesn't exist:
array.each do |c|
newarray[c.type] = newarray.fetch(c.type, -1) + 1
end
array.each do |c|
newarray[c.type] = 1 + (newarray[c.type] || -1)
end
Alternatively
array.each do |c|
newarray[c.type] ||= -1
newarray[c.type] += 1
end
||= does help:
types = {}
array.each do |c|
types[c.class] ||= 0
types[c.class] += 1
end
Your variable newarray is named oddly, since in Ruby and most other languages, arrays are indexed by integers, not random Objects like Class. It's more likely this is a Hash.
Also, you should be using c.class, instead of c.type, which is deprecated.
Finally, since you're creating a Hash, you can use inject like so:
newarray = array.inject( {} ) do |h,c|
h[c.class] = h.key?(c.class) ? h[c.class]+1 : 0
h
end
Or, for a one-liner:
newarray = array.inject( {} ) { |h,c| h[c.class] = h.key?(c.class) ? h[c.class]+1 : 0 ; h }
As you can see, this gives the desired results:
irb(main):001:0> array = [1, {}, 42, [], Object.new(), [1, 2, 3]]
=> [1, {}, 42, [], #<Object:0x287030>, [1, 2, 3]]
irb(main):002:0> newarray = array.inject( {} ) { |h,c| h[c.class] = h.key?(c.class) ? h[c.class]+1 : 0 ; h }
=> {Object=>0, Hash=>0, Array=>1, Fixnum=>1}
In Ruby 1.8.7 or later you can use group_by and then turn each list of elements into count - 1, and make a hash from the array returned by map.
Hash[array.group_by(&:class).map { |k,v| [k, v.size-1] }]

Resources