Calculate sum of objects for each unique object property in Ruby - ruby

I was helping with an answer in this question and it sparked a question of my own.
Pie is an object that has a pieces array made of of PiePiece objects.
Each PiePiece has a flavor attribute
How do I create a hash that looks like this:
# flavor => number of pieces
{
:cherry => 3
:apple => 1
:strawberry => 2
}
This works, but I think it could be improved
def inventory
hash = {}
pieces.each do |p|
hash[p.flavor] ||= 0
hash[p.flavor] += 1
end
hash
end
Any ideas?

def inventory
Hash[pieces.group_by(&:flavor).map{|f,p| [f, p.size]}]
end

Related

Selecting data from structured data, but the elegant functional way

Apparently, my ability to think functional withered over time. I have problems to select a sub-dataset from a dataset. I can solve the problem the hacky imperative style, but I believe, there is a sweet functional solution, which I am unfortunately not able to find.
Consider this data structure (tried to not simplify it beyond usability):
class C
attr_reader :attrC
def initialize(base)
#attrC = { "c1" => base+10 , "c2" => base+20, "c3" => base+30}
end
end
class B
attr_reader :attrB
##counter = 0
def initialize
#attrB = Hash.new
#attrB["b#{##counter}"] = C.new(##counter)
##counter += 1
end
end
class A
attr_reader :attrA
def initialize
#attrA = { "a1" => B.new, "a2" => B.new, "a3" => B.new}
end
end
which is created as a = A.new. The complete data set then would be
#<A: #attrA={"a1"=>#<B: #attrB={"b0"=>#<C: #attrC={"c1"=>10, "c2"=>20, "c3"=>30}>}>,
"a2"=>#<B: #attrB={"b1"=>#<C: #attrC={"c1"=>11, "c2"=>21, "c3"=>31}>}>,
"a3"=>#<B: #attrB={"b2"=>#<C: #attrC={"c1"=>12, "c2"=>22, "c3"=>32}>}>}>
which is subject to a selection. I want to retrieve only those instances of B where attrB's key is "b2".
My hacky way would is:
result = Array.new
A.new.attrA.each do |_,va|
result << va.attrB.select { |kb,_| kb == "b2" }
end
p result.reject { |a| a.empty?} [0]
which results in exactly what I intended:
{"b2"=>#<C: #attrC={"c1"=>12, "c2"=>22, "c3"=>32}>}
but I believe there would be a one-liner using map, fold, zip and reduce.
If you want a one-liner:
a.attrA.values.select { |b| b.attrB.keys == %w(b2) }
This returns instances of B. In your question, you're getting attrB values rather than instances of B. If that's what you want, there's this ugly reduce:
a.attrA.values.reduce([]) { |memo, b| memo << b.attrB if b.attrB.keys == %w(b2) ; memo }
I'm not sure what you're trying to do here, though?

Sort hash by unique value

I'm using the cinch IRC bot framework to create an irc bot that intelligently kicks a user from the channel. In order to do that, I need to know the bot's user modes and compare it to the user's user modes. There's 4 modes that matter: q, a, o, h.
Basically: owners (q) > admins (a) > operators (o) > half-ops (h) > *. Users can have multiple usermodes. Someone can have the modes qao, but I don't care if the user is an owner and an admin, I only care that the user is an owner.
Cinch provides a hash of all known users in the channel and their usermodes. For example:
Users{"bot" => {"a", "o"}, "fred" => {"q", "o"}, "mike" => "o", "larry" => "v"}
What I'm looking to do is, as concisely as possible, take some logic that can interpret when the bot is, for example, "ao", fred is "qo" and mike is "o" and then say "OK. I'm an admin and mike is an operator, so I can kick mike, but fred is an owner so I can't kick fred."
My idea of implementation is messy (involves a lot of if then else looping..) and I know there has to be a better way. In addition, I'm not really sure how to iterate through a key value and ignore the values at a certain point. I feel as though my idea would iterate, come across an "ao" and set as an admin, then reset as an operator which would not be what I need.
Thanks for the help.
#I, #carolclarinet has the right idea, and you probably don't need any more than that. However, I would like to mention a somewhat more elaborate way of dealing with the problem, which you might find useful in related applications. First, suppose your hash looks like this:
h = {"bot" => ["a", "o"], "fred" => ["q", "o"], "mike" => ["o"], "larry" => ["h"]}
This is just what you have, except I've made all the single hash values arrays. Obviously, it would be simple to get it into this form. Below I show how you can redefine the value arrays as being instances of a new class that I've called UserModes, that is a subclass of Array. By doing that, you can compare the value arrays in a very natural way:
h["bot"] # => ["a", "o"]
h["fred"] # => ["q", "o"]
h["bot"].can_kick? h["fred"] # => false
h["mike"] # => ["o"]
h["bot"].can_kick? h["mike"] # => true
h["larry"] # => ["h"]
h["bot"].can_kick? h["larry"] # => true
kickees[]
h.each {|k,v| kickees << k if k!="bot" && h["bot"].can_kick?(h[k])} # final () req'd
# kickees => ["mike", "larry"]
If you add the other methods:
h["bot"] < h["fred"] # => true
h["bot"] >= h["fred"] # => false
h["bot"] == h["mike"] # => false
h["bot"] > h["mike"] # => true
h["bot"] <= h["larry"] # => false
h["bot"] >= h["larry"] # => true
Here's the class definition:
class UserModes < Array
# Change > to >= in in can_kick? if I've misunderstood the kicking rule
def can_kick?(other) rating(self) > rating(other) end
# Add any or all of the following if useful to you:
def <=>(other) rating(self) <=> rating(other) end
def ==(other) rating(self) == rating(other) end
def <(other) rating(self) < rating(other) end
def >(other) rating(self) > rating(other) end
def <=(other) rating(self) <= rating(other) end
def >=(other) rating(self) >= rating(other) end
private
def rating a
case
when (a.include? ?q) then 3
when (a.include? ?a) then 2
when (a.include? ?o) then 1
when (a.include? ?v) then 0
else
# raise exception
end
end
end
You see that all the public methods make use of the private method rating, which converts an array to a numeric score, along the lines of #carolclarinet's answer. You can convert each hash value from an instance of Array to an instance of UserModes like this:
h.each_key {|k| h[k] = UserModes.new(h[k])}
We can confirm this works as intended:
h["fred"].class => UserModes
You can treat the values h as ordinary arrays, but you now also have the method can_kick? and, if you want, several others. Some of those (< <= >= >) are not defined for Array objects; others (<=> ==) must be defined to override methods of the same name in Array.
I would think about it like taking the "greatest" usermode the bot has and comparing it to the "greatest" usermode that the user you'd like to kick is. If the bot's is greater than or equal to the other user's, then the bot can kick the user.
In that case, you'd need a method that takes a list of usermodes and could return a numerical value corresponding to the greatest one. One implementation could be:
def greatest_usermode(array_of_usermodes)
if array_of_usermodes.include?("q")
4
elsif array_of_usermodes.include?("a")
3
elsif array_of_usermodes.include?("o")
2
elsif array_of_usermodes.include?("h")
1
else
0
end
end
Then you could define a method that just uses the mathematical >= operator to compare the numbers returned from this method for the bot and the user in question.
There are many other ways this could be done, but this is a pretty simple and straightforward way to do it.

avoid keys duplication to get a random hash key

I need to pick a hash entry at random, so I do
h = {1 => 'one', 2 => 'two', 3 => 'three'}
k = h.keys.sample
result = h[k]
Since h.keys creates a new array I do not like it. Is there a way to avoid creating a new array every time?
This will not generate another array. On average hash_random_value will iterate halfway through the given hash to produce a random value.
def hash_random_value(h)
i = rand(h.length)
h.each_with_index do |(_, v), i2|
return v if i == i2
end
end
h = {1 => 'one', 2 => 'two', 3 => 'three'}
hash_random_value(h)
This being said, you should optimize only when you are certain you need to do that. The only way you can know is to profile your code, otherwise you are most likely doing premature optimisation. I.e. complicating your code and increasing the chance of introducing bugs--sometimes even decreasing the performance of your program. Your original solution is much easier to understand than mine, and it is immediately obvious that it is correct.
I'd like to first reiterate what most people are saying: this probably doesn't matter.
Second, I'll point out that it sure seems like you want a random value, not a random key. Maybe that's just because your example snippet of code doesn't show what you're really doing.
If you very frequently need a random value, and very infrequently update the Hash, I'd recommend caching the values any time the Hash is modified and then taking a random value from the cache. One way to do that might be like this:
class RandomValueHash < Hash
def []=(k, v)
super(k, v)
#values = self.values
end
def sample_value
#values ||= self.values
#values.sample
end
end
rvh = RandomValueHash[{1 => 'one', 2 => 'two', 3 => 'three'}]
rvh.sample_value
# => "one"
rvh[4] = 'four'
rvh[5] = 'five'
rvh.sample_value
# => "four"
Of course, if you really do want a random key rather than value, the exact same concept applies. Either way, this avoids recreating the Array every time you get a value; it only creates it when necessary.
If you need to make the random sample a lot, and need it to be efficient, then perhaps a Ruby Hash is not the right data structure or storage for your problem. Even a wrapper class that maintained Hash and Array attributes together might work well - if for instance for every write to the hash you needed to read 20 random samples.
Whether or not that works for you not only depends on the ratio of reading and writing, it also relates to the logical structure of your problem data (as opposed to how you've chosen to represent it in your solution).
But before you set off on re-thinking your problem, you need to have a practical need for higher performance in the affected code. The hash would need to be pretty large in order to have a noticeable cost to fetching its keys. h.keys takes about 250ms when the hash has 1 million entries on my laptop.
How about...
h = {1 => 'one', 2 => 'two', 3 => 'three'}
k = h.keys
...
result = h[k.sample]
You can do the result = h[k.sample] times as often as you like, and it won't be regenerating the k array. However, you should regenerate k any time h changes.
ADDENDUM: I'm throwing in benchmark code for several of the proposed solutions. Enjoy.
#!/usr/bin/env ruby
require 'benchmark'
NUM_ITERATIONS = 1_000_000
def hash_random_value(h)
i = rand(h.length)
h.each_with_index do |(_, v), i2|
return v if i == i2
end
end
class RandomValueHash < Hash
def []=(k, v)
super(k, v)
#values = self.values
end
def sample_value
#values ||= self.values
#values.sample
end
end
Benchmark.bmbm do |b|
h = {1 => 'one', 2 => 'two', 3 => 'three'}
b.report("original proposal") do
NUM_ITERATIONS.times {k = h.keys.sample; result = h[k]}
end
b.report("hash_random_value") do
NUM_ITERATIONS.times {result = hash_random_value(h)}
end
b.report("manual keyset") do
k = h.keys
NUM_ITERATIONS.times {result = h[k.sample]}
end
rvh = RandomValueHash[{1 => 'one', 2 => 'two', 3 => 'three'}]
b.report("RandomValueHash") do
NUM_ITERATIONS.times {result = rvh.sample_value}
end
end
Not really. Hashes don't have an index so you either convert them to an Array and pick a random index or you Enumerate your hash for a random number of times. You should benchmark which method is fastest but I doubt you can avoid creating a new object.
If you don't care about your object you could shift it's keys for a random number of times but then you'd cerate Arrays for return values.
Unless you have a gigantic hash, this is a pointless concern. Ruby is no efficiency powerhouse, and if you're that worried about this, you should be using C(++).
something like this:
h.each_with_index.reduce(nil) {|m, ((_, v), i)|
rand(i + 1) == 0 ? v : m
}

Ruby hash with multiple keys pointing to the same value

I am looking for a way to have, I would say synonym keys in the hash.
I want multiple keys to point to the same value, so I can read/write a value through any of these keys.
As example, it should work like that (let say :foo and :bar are synonyms)
hash[:foo] = "foo"
hash[:bar] = "bar"
puts hash[:foo] # => "bar"
Update 1
Let me add couple of details. The main reason why I need these synonyms, because I receive keys from external source, which I can't control, but multiple keys could actually be associated with the same value.
Rethink Your Data Structure
Depending on how you want to access your data, you can make either the keys or the values synonyms by making them an array. Either way, you'll need to do more work to parse the synonyms than the definitional word they share.
Keys as Definitions
For example, you could use the keys as the definition for your synonyms.
# Create your synonyms.
hash = {}
hash['foo'] = %w[foo bar]
hash
# => {"foo"=>["foo", "bar"]}
# Update the "definition" of your synonyms.
hash['baz'] = hash.delete('foo')
hash
# => {"baz"=>["foo", "bar"]}
Values as Definitions
You could also invert this structure and make your keys arrays of synonyms instead. For example:
hash = {["foo", "bar"]=>"foo"}
hash[hash.rassoc('foo').first] = 'baz'
=> {["foo", "bar"]=>"baz"}
You could subclass hash and override [] and []=.
class AliasedHash < Hash
def initialize(*args)
super
#aliases = {}
end
def alias(from,to)
#aliases[from] = to
self
end
def [](key)
super(alias_of(key))
end
def []=(key,value)
super(alias_of(key), value)
end
private
def alias_of(key)
#aliases.fetch(key,key)
end
end
ah = AliasedHash.new.alias(:bar,:foo)
ah[:foo] = 123
ah[:bar] # => 123
ah[:bar] = 456
ah[:foo] # => 456
What you can do is completely possible as long as you assign the same object to both keys.
variable_a = 'a'
hash = {foo: variable_a, bar: variable_a}
puts hash[:foo] #=> 'a'
hash[:bar].succ!
puts hash[:foo] #=> 'b'
This works because hash[:foo] and hash[:bar] both refer to the same instance of the letter a via variable_a. This however wouldn't work if you used the assignment hash = {foo: 'a', bar: 'a'} because in that case :foo and :bar refer to different instance variables.
The answer to your original post is:
hash[:foo] = hash[:bar]
and
hash[:foo].__id__ == hash[:bar].__id__it
will hold true as long as the value is a reference value (String, Array ...) .
The answer to your Update 1 could be:
input.reduce({ :k => {}, :v => {} }) { |t, (k, v)|
t[:k][t[:v][v] || k] = v;
t[:v][v] = k;
t
}[:k]
where «input» is an abstract enumerator (or array) of your input data as it comes [key, value]+, «:k» your result, and «:v» an inverted hash that serves the purpose of finding a key if its value is already present.

How to declare a two-dimensional array in Ruby

I want a twodimensional array in Ruby, that I can access for example like this:
if #array[x][y] == "1" then #array[x][y] = "0"
The problem is: I don't know the initial sizes of the array dimensions and i grow the array (with the << operator).
How do I declare it as an instance variable, so I get no error like this?
undefined method `[]' for nil:NilClass (NoMethodError)
QUESTION UPDATED:
#array = Array.new {Array.new}
now works for me, so the comment from Matt below is correct!
I just found out the reason why I received the error was because I iterated over the array like this:
for i in 0..#array.length
for j in 0..#array[0].length
#array[i][j] ...
which was obviously wrong and produced the error. It has to be like this:
for i in 0..#array.length-1
for j in 0..#array[0].length-1
#array[i][j] ...
A simple implementation for a sparse 2-dimensional array using nested Hashes,
class SparseArray
attr_reader :hash
def initialize
#hash = {}
end
def [](key)
hash[key] ||= {}
end
def rows
hash.length
end
alias_method :length, :rows
end
Usage:
sparse_array = SparseArray.new
sparse_array[1][2] = 3
sparse_array[1][2] #=> 3
p sparse_array.hash
#=> {1=>{2=>3}}
#
# dimensions
#
sparse_array.length #=> 1
sparse_array.rows #=> 1
sparse_array[0].length #=> 0
sparse_array[1].length #=> 1
Matt's comment on your question is totally correct. However, based on the fact that you've tagged this "conways-game-of-life", it looks like you are trying to initialize a two dimensional array and then use this in calculations for the game. If you wanted to do this in Ruby, one way to do this would be:
a = Array.new(my_x_size) { |i| Array.new(my_y_size) { |i| 0 }}
which will create a my_x_size * my_y_size array filled with zeros.
What this code does is to create a new Array of your x size, then initialize each element of that array to be another Array of your y size, and initialize each element of each second array with 0's.
Ruby's Array doesn't give you this functionality.
Either you manually do it:
(#array[x] ||= [])[y] = 42
Or you use hashes:
#hash = Hash.new{|h, k| h[k] = []}
#hash[42][3] = 42
#hash # => {42 => [nil, nil, nil, 42]}

Resources