Ruby: How can I dynamically build a hash from user input - ruby

I have a hash like this below
h = {a: 1, b: 2, c: 3, d: 4.....z: 26}
now user input 1 then I will fetch first 5 if user input 2 then 6 to next 5 means 6 to 11
How can I acheieve this by a best way

h = {a: 1, b: 2, c: 3, d: 4.....z: 26}
user_input = 1
Hash[h.to_a[((user_input - 1) * 5 )..( (user_input * 5) - 1)]]
#=> {:a=>1, :b=>2, :c=>3, :d=>4, :e=>5}

I assume the question concerns an arbitrary hash.
Code
Three choices:
#1
def select_em(h, which_block_5)
select_range = (5*which_block_5 - 4)..(5*which_block_5)
h.select.with_index(1) { |_,i| select_range.cover?(i) }
end
#2
def select_em(h, which_block_5)
select_array = Array.new(5*(which_block_5-1),false) +
Array.new(5,true) +
Array.new(h.size-5*(which_block_5),false)
h.select { select_array.shift }
end
Note
select_array = Array.new(5*(which_block_5-1),false) +
Array.new(5,true) +
Array.new(26-5*(which_block_5),false)
#=> [false, false, false, false, false, true, true, true, true, true,
# false, false, false, false, false, false, false, false, false,
# false, false, false, false, false, false, false]
#3
def select_em(h, which_block_5)
start = 5*which_block_5 - 4
stop = start + 4
h.select.with_index(1) { |_,i| (i==start..i==stop) ? true : false }
end
This method uses Ruby's flip-flop operator.
All of these methods use Hash#select (which returns a hash), not Enumerable#select (which returns an array).
Examples
h = {:a=>1, :b=>2, :c=>3, :d=>4, :e=>5, :f=>6, :g=>7, :cat=>"meow", :dog=>"woof",
: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=>26}
select_em(h, 1)
#=> {:a=>1, :b=>2, :c=>3, :d=>4, :e=>5}
select_em(h, 2)
#=> {:f=>6, :g=>7, :cat=>"meow", :dog=>"woof", :h=>8}
select_em(h, 3)
#=> {:i=>9, :j=>10, :k=>11, :l=>12, :m=>13}
select_em(h, 4)
#=> {:n=>14, :o=>15, :p=>16, :q=>17, :r=>18}

Here is what i would do:
char_ary = ('a'..'z').to_a
start_idx = (input - 1) * 5
subary = char_ary.slice(start_idx, 5)
subary.inject({}) do |h, c|
h[c.to_sym] = char_ary.index(c) + 1
h
end
This doesn't need a hash to be defined with all the alphabets.

So you can simply use slice array function to split out things.
alphabets = [*('a'..'z')].collect.with_index{ |key,index| { key => index+1 } }
user_input = 1 # Capture user input here.
part_of_array = alphabets[( (user_input - 1) * 5 )..( (user_input * 5) - 1)]
Converting into simple one hash use the below code.
part_of_array = eval(alphabets[( (user_input - 1) * 5 )..( (user_input * 5) - 1)].to_s.gsub("{","").gsub("}",""))
Let me know if you have any issues.

Related

Converting a boolean sequence to an integer

I have four boolean variables v0, v1, v2, v3 and want to get the integer expressed by them, taking the v's as binary digits, and their values false as 0 and true as 1, in other words:
8 * v3 + 4 * v2 + 2 * v1 + v0
What is the best way to cast them to such integers? Can this be done directly in a vector?
Just iterate. No need of power operation.
[false, true, false, true] .inject(0){|n, b| n * 2 + (b ? 1 : 0)} # => 5
[false, false, false, false].inject(0){|n, b| n * 2 + (b ? 1 : 0)} # => 0
[false, false, false, true] .inject(0){|n, b| n * 2 + (b ? 1 : 0)} # => 1
[true, false, false, true] .inject(0){|n, b| n * 2 + (b ? 1 : 0)} # => 9
Just create a custom method:
def bool_to_int(bool)
bool ? 1 : 0
end
8*bool_to_int(v3) + 4*bool_to_int(v2) + 2*bool_to_int(v1) + bool_to_int(v0)
You can of course use an array and apply the function call to all the values in the list.
ary = [false, true, true, false]
exp = 0
ary.inject(0) do |total, value|
total += bool_to_int(value) * (2**exp)
exp += 1
total
end
This is more concise. The first item in the array is the exponent, the second is the sum.
ary = [false, true, true, false]
ary.inject([0,0]) do |(exp, total), value|
[exp + 1, total + bool_to_int(value) * (2**exp)]
end
As pointed out in the comments, you can also use <<
ary = [false, true, true, false]
ary.inject([0,0]) do |(exp, total), value|
[exp + 1, total + (bool_to_int(value) << exp)]
end
In this certain example you could add to_i method directly to true and false:
def false.to_i
0
end
def true.to_i
1
end
def int_from_boolean_array(array)
sum = 0
array.each_with_index do |el, index|
sum += el.to_i * (2**index)
end
sum
end
int_from_boolean_array([false, true, false, true])
It works because true (same for false) is just simple object in ruby and thus you could extend it. Also you could write the same in slightly different way:
class TrueClass
def to_i
1
end
end
class FalseClass
def to_i
0
end
end
First approach works because there is always only one instance of TrueClass and FalseClass in the system.
You can monkey-patch TrueClass and FalseClass to respond to * and + methods. Here is a working solution largely based on discussion in this topic - In Ruby, how does coerce() actually work?
# Changes to `TrueClass` and `FalseClass`
class TrueClass
def *(i)
i * 1
end
def +(i)
i + 1
end
def coerce(something)
[self, something]
end
end
class FalseClass
def *(i)
i * 0
end
def +(i)
i + 0
end
def coerce(something)
[self,something]
end
end
# Sample Runs
v3,v2,v1,v0 = true,false,true,true
p v3*8 + v2*4 + v1*2 + v0
#=> 11
p 8*v3 + 4*v2 + 2*v1 + v0
#=> 11
p 8*true + 4*false + 2*false + true
#=> 9

map_values() for Ruby hashes?

I miss a Hash method in Ruby to transform/map only the values of the hash.
h = { 1 => [9,2,3,4], 2 => [6], 3 => [5,7,1] }
h.map_values { |v| v.size }
#=> { 1 => 4, 2 => 1, 3 => 3 }
How do you archive this in Ruby?
Update: I'm looking for an implementation of map_values().
# more examples
h.map_values { |v| v.reduce(0, :+) }
#=> { 1 => 18, 2 => 6, 3 => 13 }
h.map_values(&:min)
#=> { 1 => 2, 2 => 6, 3 => 1 }
Ruby 2.4 introduced the methods Hash#transform_values and Hash#transform_values! with the desired behavoir.
h = { 1=>[9, 2, 3, 4], 2=>[6], 3=>[5, 7, 1] }
h.transform_values { |e| e.size }
#=> {1=>4, 2=>1, 3=>3}
You can monkey-patch the hash class, like this
class Hash
def map_values
map { |k, v|
[k, yield(v)]
}.to_h
end
end
p ({1 => [1,1,1,1], 2 => [2], 3 => [3,3,3]}.map_values { |e| e.size })
You can also use Hash#update for this:
h = { 1 => [9, 2, 3, 4], 2 => [6], 3 => [5, 7, 1] }
h.update(h) { |_, v| v.size }
#=> { 1 => 4, 2 => 1, 3 => 3 }
It replaces all values that have duplicate keys in one hash with that of another, or, if a block is given, with the result of calling the block. You can pass the original hash as the argument to ensure all values are replaced.
Note that this modifies the hash in place! If you want to preserve the original hash, dup it first:
h.dup.update(h) { |_, v| v.size }
#=> { 1 => 4, 2 => 1, 3 => 3 }
h
#=> { 1 => [9, 2, 3, 4], 2 => [6], 3 => [5, 7, 1] }
This will do the trick for you
h = { 1 => [1,1,1,1], 2 => [2], 3 => [3,3,3] }
h.map {|k,v| [k, v.size] }.to_h
No map, just each
h = { 1 => [1,1,1,1], 2 => [2], 3 => [3,3,3] }
h.each{|k,v| h[k] = v.size}
You can achieve this by:
h.map { |a, b| [a, b.size] }.to_h
#=> {1=>4, 2=>1, 3=>3}
Here is one more way to achieve it:
h = { 1 => [1,1,1,1], 2 => [2], 3 => [3,3,3] }
p h.keys.zip(h.values.map(&:size)).to_h
#=> {1=>4, 2=>1, 3=>3}
There's an implementation of this method in the DeepEnumerable library: https://github.com/dgopstein/deep_enumerable/
It's called shallow_map_values:
>> require 'deep_enumerable'
>> h = { 1 => [9,2,3,4], 2 => [6], 3 => [5,7,1] }
>> h.shallow_map_values { |v| v.size }
=> {1=>4, 2=>1, 3=>3}

Ruby: reuse value in a block without assigning it to variable (write object method on the fly)

There are several situations where I'd like to apply a block to a certain value and use the value inside this block, to use the enumerator coding style to every element.
If such method would be called decompose, it would look like:
result = [3, 4, 7, 8].decompose{ |array| array[2] + array[3] } # result = 15
# OR
result = {:key1 => 'value', :key2 => true}.decompose{ |hash| hash[:key1] if hash[:key2] } # result = 'value'
# OR
[min, max] = [3, 4, 7, 8].decompose{ |array| [array.min, array.max] } # [min, max] = [3, 8]
# OR
result = 100.decompose{ |int| (int - 1) * (int + 1) / (int * int) } # result = 1
# OR
result = 'Paris'.decompose{ |str| str.replace('a', '') + str[0] } # result = 'PrisP'
The method simply yields self to the block, returning the block's result. I don't think it exists, but you can implement it yourself:
class Object
def decompose
yield self
end
end
[3, 4, 7, 8].decompose{ |array| array[2] + array[3] }
#=> 15
{:key1 => 'value', :key2 => true}.decompose{ |hash| hash[:key1] if hash[:key2] }
#=> "value"
[3, 4, 7, 8].decompose{ |array| [array.min, array.max] }
#=> [3, 8]
It actually exists (I could not believe it didn't).
It is called BasicObject#instance_eval. Here's the doc: http://apidock.com/ruby/BasicObject/instance_eval
Available since Ruby 1.9 as this post explains: What's the difference between Object and BasicObject in Ruby?

Return variable rather than the value

I am curious about a feature of the .each method.
a = 1
b = 2
[a,b].each do |x|
puts x
end
Is there a way for ruby to return the variable "a" rather than the value 1?
It doesn't return 1, it returns [1, 2], the each method returns what it iterated over.
> a = 1
=> 1
> b = 2
=> 2
> r = [a, b].each { |x| puts x }
1
2
=> [1, 2]
> p r.inspect
"[1, 2]"
If you're asking if you can "go backwards" from the array value, or the variable inside the iteration block, I don't see how. If you were iterating over a map with key/value pairs, yes.
> m = { a: 1, b: 2}
=> {:a=>1, :b=>2}
> m.each { |k, v| p "#{k} = #{v}" }
"a = 1"
"b = 2"

can't convert Array into Integer

I'm trying to iterate through an array, #chem_species = ["H2", "S", "O4"] and multiply a constant times the amount of constants present: H = 1.01 * 2, S = 32.1 * 1 and so on. The constants are of course defined within the class, before the instance method.
The code I've constructed to do this does not function:
def fw
x = #chem_species.map { |chem| chem.scan(/[A-Z]/)}
y = #chem_species.map { |chem| chem.scan({/\d+/)}
#mm = x[0] * y[0]
end
yields -> TypeError: can't convert Array into Integer
Any suggestions on how to better code this? Thank you for your insight in advance.
How about doing it all in one scan & map? The String#scan method always returns an array of the strings it matched. Look at this:
irb> "H2".scan /[A-Z]+|\d+/i
=> ["H", "2"]
So just apply that to all of your #chem_species using map:
irb> #chem_species.map! { |chem| chem.scan /[A-Z]+|\d+/i }
=> [["H", "2"], ["S"], ["O", "4"]]
OK, now map over #chem_species, converting each element symbol to the value of its constant, and each coefficient to an integer:
irb> H = 1.01
irb> S = 32.01
irb> O = 15.99
irb> #chem_species.map { |(elem, coeff)| self.class.const_get(elem) * (coeff || 1).to_i }
=> [2.02, 32.01, 63.96]
There's your molar masses!
By the way, I suggest you look up the molar masses in a single hash constant instead of multiple constants for each element. Like this:
MASSES = { :H => 1.01, :S => 32.01, :O => 15.99 }
Then that last map would go like:
#chem_species.map { |(elem, coeff)| MASSES[elem.to_sym] * (coeff || 1).to_i }
You have a syntax error in your code: Maybe it should be:
def fw
x = #chem_species.map { |chem| chem.scan(/[A-Z]/)}
y = #chem_species.map { |chem| chem.scan(/\d+/)}
#mm = x[0] * y[0]
end
Have you looked at the output of #chem_species.map { |chem| chem.scan(/[A-Z]/)} (or the second one for that matter)? It's giving you an array of arrays, so if you really wanted to stick with this approach you'd have to do x[0][0].
Instead of mapping, do each
#chem_species.each { |c| c.scan(/[A-Z]/) }
Edit: just realized that that didn't work at all how I had thought it did, my apologies on a silly answer :P
Here's a way to multiply the values once you have them. The * operator won't work on arrays.
x = [ 4, 5, 6 ]
y = [ 7, 8, 9 ]
res = []
x.zip(y) { |a,b| res.push(a*b) }
res.inject(0) { |sum, v| sum += v}
# sum => 122
Or, cutting out the middle man:
x = [ 4, 5, 6 ]
y = [ 7, 8, 9 ]
res = 0
x.zip(y) { |a,b| res += (a*b) }
# res => 122
(one-liners alert, off-topic alert)
you can parse the formula directly:
"H2SO4".scan(/([A-Z][a-z]*)(\d*)/)
# -> [["H", "2"], ["S", ""], ["O", "4"]]
calculate partial sums:
aw = { 'H' => 1.01, 'S' => 32.07, 'O' => 16.00 }
"H2SO4".scan(/([A-Z][a-z]*)(\d*)/).collect{|e,x| aw[e] * (x==""?1:x).to_i}
# -> [2.02, 32.07, 64.0]
total sum:
"H2SO4".scan(/([A-Z][a-z]*)(\d*)/).collect{|e,x| aw[e] * (x==""?1:x).to_i}.inject{|s,x| s+x}
# -> 98.09

Resources