I've got an hash that looks like this
{"1-5 lbs"=>107342.43999999999, "31+ lbs"=>39838.58000000001, "21-30 lbs"=>19036.41, "11-20 lbs"=>39350.95, "6-10 lbs"=>41401.880000000005}
And I'd like to sort it so it looks like this
{"1-5 lbs"=>107342.43999999999, "6-10 lbs"=>41401.880000000005, "11-20 lbs"=>39350.95, "21-30 lbs"=>19036.41, "31+ lbs"=>39838.58000000001 }
The logic is being stored in an instance variable #weight_ranges
You'll need to get into regular expressions to get the value of the first number of each
range.
Hash[(#weight_ranges.sort_by {|key, value| key.scan(/\d+/)[0].to_i})]
To break it down further:
# Sort the weight ranges by the first series of digits found in the key
x = #weight_ranges.sort_by {|key, value| key.scan(/\d+/)[0].to_i}
# Convert each inner Array to a key, value pair in a Hash
x = Hash[x]
You depicted the data as a hash. For a sequence you'll need an array of pairs. Something like this will do it:
list_of_pairs = #weight_ranges.keys.sort_by(&:to_i).map {|k| [k, #weight_ranges[k]]}
This exploits a happy coincidence that to_i stops at the first non-digit it sees.
Correction
I have just learned that in 1.9 Ruby hashes are ordered! So it's easy to adapt:
Hash[#weight_ranges.sort_by{|k,v| k.to_i}]
I'll leave both ideas here, since the first one is still right for Ruby < 1.9.
Related
I'm newbie in ruby on rails, trying to rewrite rails project in PHP, can someone explain me please what next lines of code do?
def payment_types_billing
I18n.t("customer.payment_types_billing").inject({}){|memo,(k,v)|
memo[k.to_s.to_i] = v; memo}.
merge({
PaymentType::ACCOUNTCREDIT => I18n.t("customer.payment_types_billing.#{PaymentType::ACCOUNTCREDIT}",
:amount => number_to_currency(get_account_credit))
})
end
I don't understand part after .inject, if someone can just explain that part in human language, I will be very thankful. :)
These methods work in conjunction. By calling payment_types, the following will occur:
First it grabs a section of the localization yaml (likely in config/locales/en.yml). For more on internationalization/localization, see this!
I18n.t("customer.payment_types_billing")
Then it runs an inject block on the resulting enumerable (in this case a hash), with the intent of returning a newly-formed result (see about .inject here)
.inject({}){|memo,(k,v)| memo[k.to_s.to_i] = v; memo}
The result of this block appears to be a hash whose keys were the keys of the retrieved hash, converted to integers (without knowing the data being accessed, I can't know how this is meant to function).
Addendum:
I suspect the purpose of the above block is to assign integer keys to a new hash (something that is impossible otherwise). Seeing the later steps with the invert, this would mean that the final printed hash will have integer values, not strings.
It then adds two new key value pairs to the hash:
.merge({PaymentType::ACCOUNTCREDIT => I18n.t("customer.payment_types_billing.#{PaymentType::ACCOUNTCREDIT}", :amount => number_to_currency(get_account_credit))})
The first pair has a key equal to ACCOUNTCREDIT, with another value retrieved from the YAML. The second is the key :amount, with the value of "get_account_credit" (presumably a method with a decimal output) converted to currency for the current region.
As we reach the actual content of the payment_types method, the results from above (the newly-formed hash) is a block with a delete condition. If get_account_credit is returning a non-positive number, the ACCOUNTCREDIT keyed pair is deleted
.delete_if {|key, value| (key == PaymentType::ACCOUNTCREDIT && get_account_credit <= 0) }
Finally, the hash is inverted (the keys become the values, and the values become the keys):
.invert
Hi I have an array that i created using push like this
arr.push(h, s.power)
PS: h and s.power both are variables but depends on condition I applied
which ends up something like this
[22,"0.014",22,"0.01",22,"0.01",22,"0.082",22,"0.0002",22,"0.02822",22,"0.0042822",22,"0.041662",21,"0.0042822",21,"0.11107"]
but now I want to create new array for each new value like 22, 21 but I can not access it with many combinations I tried such as arr[22], with arr.map
You should consider using a Hash instead. See ruby hash documentation here.
So instead of pushing h and s.power into an array, you would add them to the hash like this:
my_hash[h] ||= []
my_hash[h].push(s.power)
The first line makes sure you have an array in the hash for the latest value of h. The second adds s.power to that array.
If you run this code repeatedly, you will end up with one array for each unique value of h which you can access like this:
my_hash[22] # <= returns the array of s.power values for h=22
my_hash[21] # <= returns the array of s.power values for h=21
If I understand your question correctly, this should be a clean way to do what you want.
My goal is to determine whether there is a blank in a hash like this:
{"departure_time_slots"=>{"date"=>[""], "time"=>{"start"=>[""], "end"=>[""]}}}
The strings are nested arbitrary times. I do not want to check each of them manually. It would be nice if I can extract all the strings regardless of their depth. Is there a simple way to do that?
Here's an example of how you can extract the values. However you will still have the problem of matching the values to the keys. You can't really have both at the same time.
# Extend the Hash class
class Hash
def recursive_values
self.values.collect { |v|
v.is_a?(Hash) ? v.recursive_values : v
}.flatten
end
end
Usage:
h = {"departure_time_slots"=>{"date"=>[""], "time"=>{"start"=>[""], "end"=>[""]}}}
h.recursive_values
=> ["", "", ""]
It will be better if you will use sth like that:
departure_time_slots = {:date => Time.new, :time_start => nil, :time_end => nil}
And when you use keys in Hash it is good practise to using Symbols for keys. (http://www.ruby-doc.org/core-1.9.3/Symbol.html)
No not possible. Because they are totally present in different scope with respect to each other.
For e.g.. keys start and end is totally unknown and masked from the departure_time_slots object in the example above.
One round abut way could be, getting all the values of the hashmap which are of type hashmap again and obtaining their keys recusively.
Fetch keys of departure_time_slots and then from the value list of that map, find all the keys from every value, if that were to be a hashmap. Other than that, I don't think there is another way.
P.S. On side note, see if u can modify your structure to an array where elements can also be arrays, and try and use flatten concept of arrays. :P
I have a hash that will render my html differently based on a particular variable. The variable is within the hash. So I am wondering how I can pass a hash value to a group by. to sort the rest heres what I am trying, maybe this will explain it better than me wording it.
<% grouped = svcs.group_by { |svc| svc[val[:sorttype]] } %>
val is a multidimensional hash. the first 2 key value pairs sorttype and one other are simple key and value, the 3rd piece (svcs) contains the equivilent of a 2D hash. Which if I manually type the type of sort I want to apply to it for the group by it works ie:
<% grouped = svcs.group_by { |svc| svc[:service_name] } %>
in PHP i know in a similar instance I can pass a variable of some sort to something like this and have it work. I assume such is the case here. However Im not sure how to put the variable in. Cause all the ways Ive tried don't work
It depends a little.
Rails' has a HashWithIndifferentAccess that will not distinguish between string and symbol keys; if you're using one of those, it should work as-is.
If it's not, it depends what the val entries are--if they're strings, convert to a symbol using to_sym, e.g., svc[val[:sorttype].to_sym].
I'm building an xml document from a hash. The xml attributes need to be in order. How can this be accomplished?
hash.to_xml
Ruby 1.8's hash aren't in insertion order. With ruby 1.9, they will be.
However rails offers an alternative to that, the class OrderedHash.
my_hash = ActiveSupport::OrderedHash.new
my_hash[:key] = 'value'
my_hash[:second_key] = 'second value'
This hash is in fact an array of that format :
[[:key, 'value'], [:second_key, 'second value']]
The entries remains in the order you inserted them.
And you can access them like with any other hash.
h = Hash[:x,123,:a,553,:d,949,:e,5321]
=> {:e=>5321, :x=>123, :a=>553, :d=>949}
h.sort { |x,y| x[0].to_s <=> y[0].to_s }
=> [[:a, 553], [:d, 949], [:e, 5321], [:x, 123]]
The usual ways of sorting a hash is by key or value. Have a look here:
hash.sort
More complex sorts can be accomplised however by utilizing the spaceship operator
What order did you want them to be in? You shouldn't expect them to be in insertion order. From the docs for Hash:
The order in which you traverse a hash
by either key or value may seem
arbitrary, and will generally not be
in the insertion order.
If you need them to be in a specific order you can determine just from the keys/values (e.g. order the attribute names alphabetically), you'll need to apply that ordering explicitly.
This piece of code I've just made for i18n-js might help you out as it convert Hash to ActiveSupport::OrderedHash if needed then sort it's key by natural order.
http://seb.box.re/2010/1/15/deep-hash-ordering-with-ruby-1-8