I need to get a new Ruby hash based on an existing hash but with one element removed and without affecting the original hash. I'm sure it's really simple, and I'm too much of a Ruby newbie to spot it.
For example, if I have plugh={:bar=>"bar", :baz=>"baz"} I want to be able to do something like xyzzy=plugh.some_magic_goes_here(:baz) and get xyzzy set to {:bar=>"bar"} without affecting plughin any way. How do I do it?
If you are not using active support, you can do the following:
xyzzy = plugh.reject { |k, _| k == :baz }
If you're using Rails (or at least ActiveSupport), then except is what you want:
xyzzy = plugh.except(:baz)
If you're not using Rails, the docs include the source code as well:
def except(*keys)
dup.except!(*keys)
end
def except!(*keys)
keys.each { |key| delete(key) }
self
end
Related
I started working through some sample problems on Test-First, and had worked out a solution which passed all the RSpec tests using Ruby 1.8.7. I just upgraded my OS, and Ruby upgraded as well; my code no longer passes the RSpec test. Can anyone help me understand why this is not working anymore?
My code
def entries
#d
end
the error message
Failures:
1) Dictionary can add whole entries with keyword and definition
Failure/Error: #d.entries.should == {'fish' => 'aquatic animal'}
expected: {"fish"=>"aquatic animal"}
got: {["fish"]=>["aquatic animal"]} (using ==)
Diff:
## -1,2 +1,2 ##
-"fish" => "aquatic animal"
+["fish"] => ["aquatic animal"]
#
I can't figure out what to change about the formatting. (One of the RSpec tests is that the #d must be empty when created, so when I try modifying the #d by putting in explicit formatting it also fails, but I'm imagining that there's a straightforward type issue here I'm not understanding.)
Update: More code
class Dictionary
def initialize d = {}
#d = d
end
def entries
#d
end
def keywords
#d.keys.sort
end
def add words
n_key = words.keys
n_val = words.values
#d[n_key] = n_val
end
end
It looks like you're trying to do some kind of mass assignment by adding several words at once, but that's not the way to do it.
A Ruby Hash can have anything as a key, and this includes arrays of things. It's not like JavaScript where it will automatically cast to string, or other languages that have the same sort of conversion to a specific dictionary key type. In Ruby any object will do.
So your add words method should be:
def add words
words.each do |word, value|
#d[word] = value
end
end
As a note using names like #d is really bad form. Try and be more specific about what that is, or you risk confusing people endlessly. Programs filled with things like #d, x and S are awful to debug and maintain. Better to be clear if a bit verbose than terse and ambiguous.
Secondly, it's not clear how your Dictionary class is all that different from Hash itself. Maybe you could make it a subclass and save yourself some trouble. For example:
class Dictionary < Hash
def keywords
keys.sort
end
def add words
merge!(words)
end
end
In general terms it's always best to use the core Ruby classes to do what you want, then build out from there. Re-inventing the wheel leads to incompatibility and frustration. The built-in Hash class has a whole bunch of utility methods that are very handy for doing data transformation, conversion and iteration, things you're losing by creating your own opaque wrapper class.
The merge! method in particular adds data to an existing Hash, which is exactly what you want.
I had a block of code that looked like this:
returning({}) do |hash|
attributes.each { |key, value|
hash[key.underscore] = value
}
end
Rewriting this to not use the returning magic fixed this method breaking with Ruby 1.9.3. Did this really fix something, or am I just missing something obvious?
Thanks.
I personally never heard of returning. But your snippet can be rewritten with more standard methods.
attributes.each_with_object({}) do |(key, value), memo|
memo[key.underscore] = value
end
The method returning was never a standard method in Ruby; it was supplied in Rails' ActiveSupport in earlier versions of Rails. It is an implementation of the K-combinator commonly nicknamed Kestrel. The Ruby core library now supplies an equivalent method Object#tap. Just substitute the word tap for returning in your snippet and it will work as you expected.
However, for what you're trying to do, it is simply unnecessary. Use map and Hash::[], for a much simpler expression:
Hash[ attributes.map { |k, v| [k.underscore, v] } ]
So I have this ruby code I grabbed from wikipedia and I modified a bit:
#trie = Hash.new()
def build(str)
node = #trie
str.each_char { |ch|
cur = ch
prev_node = node
node = node[cur]
if node == nil
prev_node[cur] = Hash.new()
node = prev_node[cur]
end
}
end
build('dogs')
puts #trie.inspect
I first ran this on console irb, and each time I output node, it just keeps giving me an empty hash each time {}, but when I actually invoke that function build with parameter 'dogs' string, it actually does work, and outputs {"d"=>{"o"=>{"g"=>{"s"=>{}}}}}, which is totally correct.
This is probably more of a Ruby question than the actual question about how the algorithm works. I don't really have adequate Ruby knowledge to decipher what is going on there I guess.
You're probably getting lost inside that mess of code which takes an approach that seems a better fit for C++ than for Ruby. Here's the same thing in a more concise format that uses a special case Hash for storage:
class Trie < Hash
def initialize
# Ensure that this is not a special Hash by disallowing
# initialization options.
super
end
def build(string)
string.chars.inject(self) do |h, char|
h[char] ||= { }
end
end
end
It works exactly the same but doesn't have nearly the same mess with pointers and such:
trie = Trie.new
trie.build('dogs')
puts trie.inspect
Ruby's Enumerable module is full of amazingly useful methods like inject which is precisely what you want for a situation like this.
I think you are just using irb incorrectly. You should type the whole function in, then run it, and see if you get correct results. If it doesn't work, how about you post your entire IRB session here.
Also here is a simplified version of your code:
def build(str)
node = #trie
str.each_char do |ch|
node = (node[ch] ||= {})
end
# not sure what the return value should be
end
So I have a class like this:
def Word
end
and im looping thru an array like this
array.each do |value|
end
And inside that loop I want to instantiate an object, with a handle of the var
value = Word.new
Im sure there is an easy way to do this - I just dont know what it is!
Thanks!
To assign things to a dynamic variable name, you need to use something like eval:
array.each do |value|
eval "#{value} = Word.new"
end
but check this is what you want - you should avoid using eval to solve things that really require different data structures, since it's hard to debug errors created with eval, and can easily cause undesired behaviour. For example, what you might really want is a hash of words and associated objects, for example
words = {}
array.each do |value|
words[value] = Word.new
end
which won't pollute your namespace with tons of Word objects.
Depending on the data structure you want to work with, you could also do this:
# will give you an array:
words = array.map { |value| Word.new(value) }
# will give you a hash (as in Peter's example)
words = array.inject({}) { |hash, value| hash.merge value => Word.new }
# same as above, but more efficient, using monkey-lib (gem install monkey-lib)
words = array.construct_hash { |value| [value, Word.new ] }
I have an application that I am using Active Record to access a database. I'm trying to take the information and put it into a hash for later use.
Here is basically what I am doing.
require 'active_support'
#emailhash = Hash.new
emails = Email.find(:all)
emails.each do |email|
email.attributes.keys.each do |#column|
#emailhash[email.ticketno] || Hash.new
#emailhash[email.ticketno] = email.#column
end
end
The line that doesn't work is:
#emailhash[email.ticketno] = email.#column
Is there any way that I can do this properly? Basically my goal is to build a hash off of the values that are stored in the table columns, any input is appreciated.
Ruby programmers usually indent 2
Your code was squishing all of the emails into one hash entry, rather than an entry per email.
If you want to call a method dynamically, use Object.send.
#emailhash[email.ticketno] || Hash.new doesn't do anything.
Something like this might do it:
require 'active_support'
#emailhash = {}
Email.find(:all).each do |email|
#mailhash[email.ticketno] = {}
email.attributes.keys.each do |key|
#emailhash[email.ticketno][key] = email.send(key)
end
end
The key piece is "send", which calls the method identified by a string or symbol.
You cannot have an iterator variable starting with an #. Try something like this:
require 'active_support'
#emailhash = Hash.new
emails = Email.find(:all)
emails.each do |email|
#emailhash[email.ticketno] = email.attributes.keys.collect{|key| key.column}
end
In addition to blinry's comment, the line
#emailhash[email.ticketno] || Hash.new
looks suspicious. Are you sure you don't want
#emailhash[email.ticketno] ||= Hash.new
?
Besides the previous accurate observations, I would like to add the following:
Point 1.
Is important to state that #ivars may not work on formal function parameters... This said, I think it is invalid to have:
collection.each { |#not_valid| }
Is also a bad practice to have #ivars inside blocks, you won't know for sure in which context this block will be executed in (As you many know, the self reference inside that block may be different than the self reference outside it).
Point 2.
Another point that you should have in mind is that if you don't assign the result of a (||) operator this won't do any modification at all (just will be a time waster), however you could use:
mail_hash[email.ticketno] = mail_hash[email.ticketno] || Hash.new
That can be easily rewritten to:
mail_hash[email.ticketno] ||= Hash.new
Point 3.
Even if email.attributes.keys is a cheap instruction, is not free... I would suggest to have that outside the iteration block (given that the keys will always be the same for each record, given we are not using Document Databases).
Final Result
require 'active_support'
mails = Email.all
#mailshash = mails.inject(Hash.new) do |hsh, mail|
# mail.attributes is already a representation of the
# email record in a hash
hsh[mail.ticketno] = mail.attributes
hsh
end