Parse abbreviated numbers in Ruby - ruby

What's the best way in Ruby to turn human-readable abbreviated numbers into actual integers?
Examples:
"1.2M" => 1200000
"477k" => 477000

module SIValue
# http://en.wikipedia.org/wiki/SI_prefix
PREFIX_MAGNITUDES = {
'Y' => 24, 'Z' => 21, 'E' => 18, 'P' => 15, 'T' => 12,
'G' => 9, 'M' => 6, 'k' => 3, 'h' => 2, 'da' => 1,
'd' => -1, 'c' => -2, 'm' => -3, 'μ' => -6, 'n' => -9,
'p' => -12, 'f' => -15, 'a' => -18, 'z' => -21, 'y' => -24
}
def self.from( str )
_, num, prefix = str.match(/^([-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)(#{PREFIX_MAGNITUDES.keys.join('|')})?/o).to_a
if num
prefix ? num.to_f * 10**PREFIX_MAGNITUDES[prefix] : num.to_f
else
0.0
end
end
end
%w[ 1k +3.3m +3.3M 123.123da 0.31h 0.31μ cats ].each do |s|
p [s,SIValue.from(s) ]
end
#=> ["1k", 1000.0]
#=> ["+3.3m", 0.0033]
#=> ["+3.3M", 3300000.0]
#=> ["123.123da", 1231.23]
#=> ["0.31h", 31.0]
#=> ["0.31μ", 3.1e-07]
#=> ["cats", 0.0]

def scale s
a = s.downcase.split /(?=[a-z])/
Integer(a.first.to_f * Hash.new(1).merge('k' => 1024, 'm' => 1024 * 1024)[a[1]] + 0.5)
end
# you may want to extend the hash with more suffix types

Related

I changed my integers to strings, but #gsub only recognizes 0-9

I'm self-learning Ruby, and one assignment is to make a Caesar cipher.
Using #gsub, I've been able to change my letters to integers ('c' => 2), shift them, then change the new integers to strings (2 => "2").
I've hit a wall, and the Ruby documentation isn't helping. When I try to #gsub the strings back to letters ("2" => 'c') it only recognizes 0-9. Everything after that is just a concatenation of those numbers ("12" => 'bc' instead of => 'l').
Why does Ruby do this, and how can I fix it?
Thanks for your help guys.
code: (I know it's sloppy beginner's code; I will try to edit it after it passes)
def convert_to_integer
puts "What would you like to encode?"
words = gets.chomp
words = words.split("")
words.map { |words| words.gsub!(/[a-z]/, 'a' => 0, 'b' => 1, 'c' => 2, 'd' => 3, 'e' => 4, 'f' => 5, 'g' => 6, 'h' => 7, 'i' => 8, 'j' => 9, 'k' => 10, 'l' => 11, 'm' => 12, 'n' => 13, 'o' => 14, 'p' => 15, 'q' => 16, 'r' => 17, 's' => 18, 't' => 19, 'u' => 20, 'v' => 21, 'w' => 22, 'x' => 23, 'y' => 24, 'z' => 25)
}
integer = words.map! { |letter| letter.to_i }
return integer
end
def shift_left(integer, number = 0)
puts "How many letters (to the left) would you like to shift it?"
number = gets.to_i
integer.map! { |n| n - number }
return integer
end
def convert_to_letter(integer)
integer.map! { |integer| integer.to_s }
integer.map! { |n| n.gsub(/[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 , 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]/, '0' => 'a', '1' => 'b', '2' => 'c', '3' => 'd', '4' => 'e', '5' => 'f', '6' => 'g', '7' => 'h', '8' => 'i', '9' => 'j', '10' => 'k', '11' => 'l', '12' => 'm', '13' => 'n', '14' => 'o', '15' => 'p', '16' => 'q', '17' => 'r', '18' => 's', '19' => 't', '20' => 'u', '21' => 'v', '22' => 'w', '23' => 'x', '24' => 'y', '25' => 'z')
}
print integer
end
convert_to_letter(shift_left(convert_to_integer))
You don't need to do a gsub there. gsub is normally used to replace parts of a bigger string. You want to replace the whole thing.
This should do the trick:
def convert_to_letter(integers)
replacements = {0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd', 4 => 'e',
5 => 'f', 6 => 'g', 7 => 'h', 8 => 'i', 9 => 'j', 10 => 'k',
11 => 'l', 12 => 'm', 13 => 'n', 14 => 'o', 15 => 'p', 16 => 'q',
17 => 'r', 18 => 's', 19 => 't', 20 => 'u', 21 => 'v', 22 => 'w',
23 => 'x', 24 => 'y', 25 => 'z'
}
integers.map{|x| replacements[x]}.join
end
Also, be careful with destructive operations (map! here). You may run into undesired side-effects (for example, some arrays will change when you think they shouldn't).
It's easier and faster to use lookups:
#letter_to_number = ('a'..'z').zip(0..25).to_h
#number_to_letter = (0..25).zip('a'..'z').to_h
def convert_to_integers(letters)
letters.map{|l| #letter_to_number[l]}
end
def convert_to_letters(numbers)
numbers.map{|n| #number_to_letter[n]}
end
There's also a shortcut that combines the lookups and combines the methods.
#convert = (('a'..'z').zip(0..25) + (0..25).zip('a'..'z')).to_h
def convert(objects)
objects.map{|o| #convert[o]}
end
That's not how regular expressions work. "12".gsub(/[12]/, '12' => 'm') does not produce "m". That code says to find any occurrence of "1" or "2", and replace it according to the following rule: "12" gets replaced with "m", and, implicitly, anything else gets replaced with nothing. Both the "1" and the "2" are occurrences of "1" or "2", but neither of them are "12", so they both get replaced with nothing. Thus the above results in just the empty string.
In fact gsub and regular expressions are not really ideal for this problem. You could just do this:
def char_to_int(char)
char.ord - 97
end
def int_to_char(int)
(int + 97).chr
end
def caesar(string, shift)
string.split(" ").map do |word|
word.split("").map do |letter|
int_to_char((char_to_int(letter) - shift) % 26)
end.join
end.join(" ")
end

How to get array of arrays with conditions from an array

I have an array containing hashes, like this:
[
{:id => 1, :week => 1, :year => 2014},
{:id => 2, :week => 2, :year => 2014},
{:id => 1, :week => 1, :year => 2015},
{:id => 2, :week => 5, :year => 2015},
]
What i need is a array of arrays, containing each two values:
The first value is a hash from the first array with year 2014, the second is a hash from the first array with year 2015, if it has the same week and id as the first hash.
If there is not hash with equal id and week from 2015, then the second value has to be nil, vice versa the first value is nil.
For the array above, the new array should look like:
[
[{:id => 1, :week => 1, :year => 2014}, {:id => 1, :week => 1, :year => 2015}],
[{:id => 2, :week => 2, :year => 2014}, nil],
[nil, {:id => 2, :week => 5, :year => 2015}],
]
-e-
my approach:
result = []
all_ids.each do |id|
all_weeks.each do |week|
v1 = array.select{ |v| v.id == id && v.week == week && v.year == 2014}
v2 = array.select{ |v| v.id == id && v.week == week && v.year == 2015}
v1 = v1.length == 1 ? v1.first : nil
v2 = v2.length == 1 ? v1.first : nil
result << [v1, v2]
end
end
this doesn't seem to be very efficient as i have to iterate the array multiple times
Use group_by to separate your hashes by week and id, then you just need to pad out the arrays that don't have two values:
array.group_by { |v| [v[:id], v[:week]] }.values.each do |a|
a.insert(2015 - a[0][:year], nil) if a.length == 1
end
Here is a way that is intended to emphasize readability (seriously).
Code
def convert(arr)
a14 = arr.select { |h| h[:year] == 2014 }
a15 = arr - a14
arr = a14.map do |h14|
i15 = a15.index { |h15| h14[:id]==h15[:id] && h14[:week]==h15[:week] }
[ h14, i15.nil? ? nil : a15.delete_at(i15) ]
end
arr.concat([nil].product(a15)) if a15.any?
end
If the value of :year can be other than 2014 or 2015, replace a15 = arr - a14 with:
a15 = arr.select { |h| h[:year] == 2015 }
Note that a15.delete_at(i15) serves a dual-function: it removes the value at index i15 from a15 and returns that value for inclusion in the tuple.
Example
arr = [ {:id => 1, :week => 1, :year => 2014},
{:id => 2, :week => 2, :year => 2014},
{:id => 1, :week => 1, :year => 2015},
{:id => 2, :week => 5, :year => 2015} ]
convert(arr)
#=> [[{:id=>1, :week=>1, :year=>2014}, {:id=>1, :week=>1, :year=>2015}],
# [{:id=>2, :week=>2, :year=>2014}, nil],
# [nil, {:id=>2, :week=>5, :year=>2015}]]

Subtract values in hash from corresponding values in another hash

I'd like to be able to subtract two hashes and get a third hash in Ruby.
The two hashes look like this:
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0
I'd like to be able to call a method on h1 like this:
h1.difference(h2)
and get this hash as a result:
{"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}
I'd like to create a new hash with keys from both Hashes and the values of the new hash to be the value of the key in the first hash minus the value of that key in the second hash. The catch is that I'd like this Hash method to work regardless of the case of the keys. In other words, I'd like "Cat" to match up with "cat".
Here's what I have so far:
class Hash
def difference(another_hash)
(keys + another_hash.keys).map { |key| key.strip }.uniq.inject(Hash.new(0)) { |acc, key| acc[key] = (self[key] - another_hash[key]); acc }.delete_if { |key, value| value == 0 }
end
end
This is OK, but, unfortunately, the result isn't what I want.
Any help would be appreciated.
How about converting the hashes to sets.
require 'set'
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0
p (h1.to_set - h2.to_set)
#=> #<Set: {["Cat", 100], ["Dog", 5], ["Bird", 2]}>
As a recommendation...
I've used something like this in the past:
class Hash
def downcase_keys
Hash[map{ |k,v| [k.downcase, v]}]
end
def difference(other)
Hash[self.to_a - other.to_a]
end
alias :- :difference
end
which lets me do things like:
irb(main):206:0> h1.downcase_keys - h2.downcase_keys
{
"cat" => 100,
"dog" => 5,
"bird" => 2
}
irb(main):207:0> h2.downcase_keys - h1.downcase_keys
{
"cat" => 50,
"dog" => 3,
"bird" => 4,
"mouse" => 75
}
The alias gives you the nice syntax of using - instead of difference, similar to using - for Arrays and Sets. You can still use difference though:
irb(main):210:0> h1.downcase_keys.difference(h2.downcase_keys)
{
"cat" => 100,
"dog" => 5,
"bird" => 2
}
irb(main):211:0> h2.downcase_keys.difference(h1.downcase_keys)
{
"cat" => 50,
"dog" => 3,
"bird" => 4,
"mouse" => 75
}
I always normalize my hash keys, and don't allow variants to leak in. It makes processing the hashes much too difficult when you don't know what the keys are called, so I'd highly recommend doing that as a first step. It's a code-maintenance issue.
Otherwise, you could create a map of the original key names and their normalized names, but you run into problems if your hash contains two unique-case keys, such as 'key' and 'KEY', because normalizing will stomp on one.
Sorry that due to the time limit (I have to take care of my baby boy now), only figured out this stupid but working code:
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0
h3 = {"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}
class Hash
def difference(subtrahend)
diff = {}
self.each_pair do |k1, v1|
flag = false
subtrahend.each_pair do |k2, v2|
if k1.downcase == k2.downcase
flag = true
v_diff = v1 - v2
break if v_diff == 0
v_diff > 0 ? diff[k1] = v_diff : diff[k2] = v_diff
end
end
diff[k1] = v1 unless flag
end
subtrahend.each_pair do |k2, v2|
flag = false
self.each_pair do |k1, v1|
if k1.downcase == k2.downcase
flag = true
break
end
end
diff[k2] = -v2 unless flag
end
return diff
end
end
h1.difference(h2) == h3 ? puts("Pass") : puts("Fail") #=> "Pass"
I got this to the resque https://github.com/junegunn/insensitive_hash
then follow your procedure but slighly tweaked as requirement
require 'insensitive_hash'
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}.insensitive
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}.insensitive
h2.default = 0
class Hash
def difference(another_hash)
(keys + another_hash.keys).map { |key|
key.downcase }.uniq.inject(Hash.new(0)) do |acc, key|
val = self[key] - another_hash[key]
acc[key] = val if val!= 0
acc
end
end
end
h1.difference(h2)
# => {"cat"=>50, "dog"=>2, "bird"=>-2, "mouse"=>-75}
This time I would like to provide another solution: normalized -> store original key value pairs -> grab the original key who has larger value as the key for the difference.
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0
h3 = {"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}
class Hash
def difference(subtrahend)
# create a hash which contains all normalized keys
all_pairs = (self.keys.map{|x| x.downcase} + subtrahend.keys.map{|x| x.downcase}).uniq.inject({}) do |pairs, key|
pairs[key] = []
pairs
end
#=> {"mouse"=>[], "cat"=>[], "snake"=>[], "bird"=>[], "dog"=>[]}
# push original key value pairs into array which is the value of just created hash
[self, subtrahend].each_with_index do |hsh, idx|
hsh.each_pair { |k, v| all_pairs[k.downcase].push([k, v]) }
all_pairs.each_value { |v| v.push([nil, 0]) if v.size == idx }
end
#=> {"mouse"=>[[nil, 0], ["Mouse", 75]], "cat"=>[["Cat", 100], ["cat", 50]], "snake"=>[["Snake", 10], ["Snake", 10]], "bird"=>[["Bird", 2], ["BIRD", 4]], "dog"=>[["Dog", 5], ["dog", 3]]}
results = {}
all_pairs.each_value do |values|
diff = values[0][1] - values[1][1]
# always take the key whose value is larger
if diff > 0
results[values[0][0]] = diff
elsif diff < 0
results[values[1][0]] = diff
end
end
return results
end
end
puts h1.difference(h2).inspect #=> {"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}
h1.difference(h2) == h3 ? puts("Pass") : puts("Fail") #=> "Pass"
According to what you described, this one does a pretty good job. The result is exactly what you've shown (key is not normalized in the final result, but depends on whose value is bigger).

Search an Array of Hashes with a partial Hash in Ruby

Given a hash
z = [{'a' => 1, 'b' => 2}, {'a' => 3, 'b' => 4}, {'a' => 1, 'b' => 4}]
How do I search if the search parameter itself is a hash e.g.
{'a' => 3}
so that I can do something like z.find_by_hash({'a' => 3}) for it to return
{'a' => 3, 'b' => 4}
and also to get a collection of arrays like z.find_by_hash({'a' => 1}) for it to return
[{'a' => 1, 'b' => 2}, {'a' => 1, 'b => 4}]
Thanks
You can do this:
class Array
def find_by_hash(hash)
self.select { |h| h.includes_hash?(hash) }
end
end
class Hash
def includes_hash?(other)
included = true
other.each do |key, value|
included &= self[key] == other[key]
end
included
end
end
This extends Hash by a method to find out if a Hash includes another (with multiple keys and values). Array is extended with the method you wanted, but it's a more generic approach since you can do this:
ary = [ {:a => 1, :b => 3, :c => 5}, {:a => 5, :b => 2, :c => 8} ]
ary.find_by_hash( { :a => 1, :c => 5 } )
Note: You should also consider using Symbols for Hash keys since it is a common practice in Ruby, but my approach does also work with your keys.
z = [{'a' => 1, 'b' => 2}, {'a' => 3, 'b' => 4}, {'a' => 1, 'b' => 4}]
class Array
def search_hash(hash)
key = hash.keys.first
value = hash.values.first
select { |h| h[key] == value }
end
end
z.search_hash({'a' => 3}) #=> [{"a"=>3, "b"=>4}]
or you can type it without curly brackets
z.search_hash('a' => 3)
Basically what you need is something like this:
class Array
def find_by_hash(h)
h.collect_concat do |key, value|
self.select{|h| h[key] == value}
end
end
end
I didn't find an approach in API, so I think we have to implement it of our own.
(by the way, I think #megas' approach is better and more readable)
Code by TDD:
class SearchHashTest < Test::Unit::TestCase
def setup
#array_with_hash_elements = ArrayWithHashElements.new [{'a' => 1, 'b' => 2}, {'a' => 3, 'b' => 4}, {'a' => 1, 'b' => 4}]
end
def test_search_an_array_by_hash_parameter_and_return_single_hash
assert_equal( {'a' => 3, 'b' => 4}, #array_with_hash_elements.search({'a'=>3}) )
end
def test_search_an_array_by_hash_parameter_and_return_an_array
assert_equal( [{'a' => 1, 'b' => 2}, {'a'=> 1, 'b' => 4}], #array_with_hash_elements.search({'a'=>1}))
end
end
implemented code ( just for demo, not production)
class ArrayWithHashElements
def initialize some_array
#elements = some_array
end
def search( query_hash)
puts "search: #{query_hash.inspect}"
result = []
#elements.each do | array_element_in_hash_form|
query_hash.each_pair do | key, value |
if array_element_in_hash_form.has_key?(key) && array_element_in_hash_form[key] == value
puts "adding : #{array_element_in_hash_form.inspect} to result"
result << array_element_in_hash_form
end
end
result
end
return result.size != 1 ? result : result[0]
end
end
result:
sg552#siwei-moto:~/workspace/test$ ruby search_hash_test.rb
Loaded suite search_hash_test
Started
search: {"a"=>1}
adding : {"b"=>2, "a"=>1} to result
adding : {"b"=>4, "a"=>1} to result
.search: {"a"=>3}
adding : {"b"=>4, "a"=>3} to result
.
Finished in 0.000513 seconds.
2 tests, 2 assertions, 0 failures, 0 errors

Replacing values in a byte type NArray in Ruby

I am looking for a way to replace all occurrences of 'A' with 1, 'T' with 2, 'C' with 8, and 'G' with 16 in a byte array. How can this be done?
require "narray"
class NArray
def cast(type)
a = NArray.new(type,*self.shape)
a[] = self
a
end
end
conv = NArray.int(256)
atcg = NArray.to_na('ATCG', NArray::BYTE).cast(NArray::LINT)
conv[atcg] = [1,2,8,16]
seq_str = 'ABCDAGDE'
seq_ary = NArray.to_na(seq_str, NArray::BYTE).cast(NArray::LINT)
p conv[seq_ary]
#=> NArray.int(8):
# [ 1, 0, 8, 0, 1, 16, 0, 0 ]
Is it what you are looking for?
h = {'A' => 1, 'T' => 2, 'C' => 8, 'G' => 16}
a = ['A', 'B', 'C', 'D', 'A', 'G', 'D', 'E']
result = a.map {|c| h.include?(c) ? h[c] : c }

Resources