Reducing multiple variables in Ruby - ruby

AFAIK when reducing an array we can only output once variable at the end like so:
(0..10).reduce(0) do |sum, value|
sum + value
end
What if I have an array of hash objects, can I reduce the array and output multiple variables something like:
({:grade => 100, :sex => 'female'}, {:grade => 90, :sex => 'male'}).reduce(0, 0, 0) do |sum_of_grades, sum_of_male, sum_of_female, value|
sum_of_grades = sum_of_grades + value[:grade]
sum_of_male += 1 if value[:sex] == 'male'
sum_of_female +=1 if value[:sex] == 'female
end

Aggregate multiple results in a hash or any other suitable object:
a.reduce({:sum_of_grades => 0, :sum_of_male => 0, :sum_of_female => 0}) do |result, value|
result[:sum_of_grades] += value[:grade]
result[:sum_of_male] += 1 if value[:sex] == 'male'
result[:sum_of_female] += 1 if value[:sex] == 'female'
result
end

Related

How do I keep count of all the elements inside hashes which are inside an array?

Basically, I have an array which contains 3 hashes. I want to count and return each key and value inside the hashes, which include any duplicates. The code is below, I have done the first draft of the code as you can see for yourself below.
my_array = [{:name => "blake"}, {:name => "blake"}, {:name => "ashley"}]
#Count the number of times each element appears inside the hash
#so the output should have the number of times the :names, "blake" and "ashley" element appears
#EXPECTED OUTPUT: :name = 3, "blake" = 2, "ashley" = 1
def getOccurances(array)
array.group_by{|v| v[:name]}.map{|k,v| {name: k, count: v.length}}
end
getOccurances(my_array)
#ACTUAL OUTPUT: {:name => "blake", :count => 2}, {:name => "ashley", :count => 1}
You can map each Hash to an Array of [key, val] pairs, then flatten and each occurrence:
[{:name => "blake"}, {:name => "blake"}, {:name => "ashley"}].
map(&:to_a).flatten.
reduce(Hash.new { 0 }) {|o, v| o[v] += 1; o }
The argument to reduce is a Hash initialized with a block, so the default value of uninitialized keys is 0; we simply iterate through the flattened entries and accumulate a count of values.
my_array.each_with_object(Hash.new(0)) { |g,h| h[g[:name]] += 1 }.
map { |k,v| { name: k, count: v } }
#=> [{:name=>"blake", :count=>2}, {:name=>"ashley", :count=>1}]
Note:
my_array.each_with_object(Hash.new(0)) { |g,h| h[g[:name]] += 1 }
#=> {"blake"=>2, "ashley"=>1}

How to recreate .select()?

I'm trying to recreate the #select method. So far, I have array portion working, but when I try to call #my_select on a Hash, I get an empty Hash.
FYI, for starters, I had to recreate my own `#each' method. Here's that.
module Enumerable
def my_each
i = 0
while i < self.size
yield(self[i])
i += 1
end
self
end
Now, here's the #my_select method I created:
def my_select
if self.instance_of?(Array)
ret = []
self.my_each do |item|
ret << item if yield(item)
end
ret
elsif self.instance_of?(Hash)
ret = {}
self.my_each do |key, value|
if yield(key,value)
ret[key] = value
end
end
ret
end
end
end
...my input/output...
> h = { "a" => 100, "b" => 200, "c" => 300 }
> h.select { |k,v| k == "a"}
=> {"a"=>100}
> h.my_select { |k,v| k == "a"}
=> {}
Maybe you could change my_each to handle Hash as well?
def my_each
if self.instance_of?(Array)
i = 0
while i < self.size
yield(self[i])
i += 1
end
self
elsif self.instance_of?(Hash)
i = 0
arr = self.to_a
while i < arr.size
yield(arr[i][0], arr[i][1])
i += 1
end
self
end
end

Given an array, returns a hash

Write a method, which given an array, returns a hash whose keys are words in the array and whose values are the number of times each word appears.
arr=["A", "man", "a", "plan", "a", "canal","Panama"]
# => {'a' => 3, 'man' => 1, 'canal' => 1, 'panama' => 1, 'plan' => 1}
How do I achieve that? Here's my code:
hash={}
arr.each do |i|
hash.each do |c,v|
hash[c]=v+1
end
end
hash = arr.inject({}) do |hash, element|
element.downcase!
hash[element] ||= 0
hash[element] += 1
hash
end
arr.inject­(Hash.new(­0)){|h,k| k.dow­ncase!; h[k] += 1; h}
arr = ["A", "man", "a", "plan", "a", "canal","Panama"]
r = {}
arr.each { |e| e.downcase!; r[e] = arr.count(e) if r[e].nil? }
Output
p r
#==> {"a"=>3, "man"=>1, "plan"=>1, "canal"=>1, "panama"=>1}

Ruby methods. Switch methods

Just wondering why
def move
world_switch(#pos_X += 1, #pos_X -= 1, #pos_Y += 1, #pos_Y -= 1)
end
def world_switch(do_on_north, do_on_south, do_on_east, do_on_west)
case #facing # => 'NORTH'
when 'NORTH'
puts do_on_north # => 1
do_on_north
when 'SOUTH'
do_on_south
when 'EAST'
do_on_east
when 'WEST'
do_on_west
end
end
Calling world_switch:
robot = Robot.new(0, 0, 'NORTH')
robot.move
puts robot.instance_variable_get("#pos_X") #=> 0
results in changing nothing, I would like to increase or decrease instance variable #pos_X or #pos_Y
This is my initialize method
def initialize(pos_X, pos_Y, facing)
#pos_X, #pos_Y, #facing = pos_X, pos_Y, facing
end
and that's how I create an instance of the class robot = Robot.new(0, 0, 'NORTH')
All help will be appreciated
The explanation for the current behaviour is as Chowlett described, but did you intend for your #pos_X += 1, #pos_X -= 1 etc in move to be blocks of code and then for exactly one of these to be called from world_switch depending on which way the robot is facing?
If so, move needs to be declared like this
def move
world_switch(Proc.new { #pos_X += 1 }, Proc.new { #pos_X -= 1 },
Proc.new { #pos_Y += 1 }, Proc.new { #pos_Y -= 1 })
end
and then in world_switch you can do something like
case #facing # => 'NORTH'
when 'NORTH'
do_on_north.call
when 'SOUTH'
do_on_south.call
...
It does nothing because of the way you call world_switch. Ruby will evaluate each of the expressions you're passing as parameters before the call.
So, you call move with (say), #pos_X and #pos_Y both equal to 0. The Ruby does:
#pos_X += 1 # => #pos_X = 1; param 1 will be 1
#pos_X -= 1 # => #pos_X = 0; param 2 will be 0
#pos_Y += 1 # => #pos_Y = 1; param 3 will be 1
#pos_Y -= 1 # => #pos_Y = 0; param 4 will be 0
world_switch(1, 0, 1, 0)
Then world_switch switches based on #facing, and simply returns the value of the appropriate parameter. It doesn't change the instance variables at all.
I'm not sure I explained that all that clearly. Let me know if you need clarification.
Adding to Chowlett's and mikej's answers (which nicely explain why your code isn't working). You could try something like this:
class Player
def initialize(position)
#position = position
end
def move(direction)
case direction
when :north
#position[:x] += 1
when :south
#position[:x] -= 1
when :east
#position[:y] -= 1
when :west
#position[:y] += 1
end
end
end
player = Player.new({:x => 0, :y => 0})
player.move(:north)
puts player.inspect
# => "#<Player:0x16c7ef8 #position={:x=>1, :y=>0}>"

Ruby: Deleting all instances of a particular key from hash of hashes

I have a hash like
h = {1 => {"inner" => 45}, 2 => {"inner" => 46}, "inner" => 47}
How do I delete every pair that contains the key "inner"?
You can see that some of the "inner" pairs appear directly in h while others appear in pairs in h
Note that I only want to delete the "inner" pairs, so if I call my mass delete method on the above hash, I should get
h = {1 => {}, 2 => {}}
Since these pairs don't have a key == "inner"
Really, this is what reject! is for:
def f! x
x.reject!{|k,v| 'inner' == k} if x.is_a? Hash
x.each{|k,v| f! x[k]}
end
def f x
x.inject({}) do |m, (k, v)|
v = f v if v.is_a? Hash # note, arbitrarily recursive
m[k] = v unless k == 'inner'
m
end
end
p f h
Update: slightly improved...
def f x
x.is_a?(Hash) ? x.inject({}) do |m, (k, v)|
m[k] = f v unless k == 'inner'
m
end : x
end
def except_nested(x,key)
case x
when Hash then x = x.inject({}) {|m, (k, v)| m[k] = except_nested(v,key) unless k == key ; m }
when Array then x.map! {|e| except_nested(e,key)}
end
x
end
Here is what I came up with:
class Hash
def deep_reject_key!(key)
keys.each {|k| delete(k) if k == key || self[k] == self[key] }
values.each {|v| v.deep_reject_key!(key) if v.is_a? Hash }
self
end
end
Works for a Hash or a HashWithIndifferentAccess
> x = {'1' => 'cat', '2' => { '1' => 'dog', '2' => 'elephant' }}
=> {"1"=>"cat", "2"=>{"1"=>"dog", "2"=>"elephant"}}
> y = x.with_indifferent_access
=> {"1"=>"cat", "2"=>{"1"=>"dog", "2"=>"elephant"}}
> x.deep_reject_key!(:"1")
=> {"1"=>"cat", "2"=>{"1"=>"dog", "2"=>"elephant"}}
> x.deep_reject_key!("1")
=> {"2"=>{"2"=>"elephant"}}
> y.deep_reject_key!(:"1")
=> {"2"=>{"2"=>"elephant"}}
Similar answer but it is a whitelist type approach. For ruby 1.9+
# recursive remove keys
def deep_simplify_record(hash, keep)
hash.keep_if do |key, value|
if keep.include?(key)
deep_simplify_record(value, keep) if value.is_a?(Hash)
true
end
end
end
hash = {:a => 1, :b => 2, :c => {:a => 1, :b => 2, :c => {:a => 1, :b => 2, :c => 4}} }
deep_simplify_record(hash, [:b, :c])
# => {:b=>2, :c=>{:b=>2, :c=>{:b=>2, :c=>4}}}
Also here are some other methods which I like to use for hashes.
https://gist.github.com/earlonrails/2048705

Resources