Ruby array with an extra state - ruby

I'm trying to go through an array and add a second dimension for true and false values in ruby.
For example. I will be pushing on arrays to another array where it would be:
a = [[1,2,3,4],[5]]
I would like to go through each array inside of "a" and be able to mark a state of true or false for each individual value. Similar to a map from java.
Any ideas? Thanks.

You're better off starting with this:
a = [{ 1 => false, 2 => false, 3 => false, 4 => false }, { 5 => false }]
Then you can just flip the booleans as needed. Otherwise you will have to pollute your code with a bunch of tests to see if you have a Fixnum (1, 2, ...) or a Hash ({1 => true}) before you can test the flag's value.
Hashes in Ruby 1.9 are ordered so you wouldn't lose your ordering by switching to hashes.
You can convert your array to this form with one of these:
a = a.map { |x| Hash[x.zip([false] * x.length)] }
# or
a = a.map { |x| x.each_with_object({}) { |i,h| h[i] = false } }
And if using nil to mean "unvisited" makes more sense than starting with false then:
a = a.map { |x| Hash[x.zip([nil] * x.length)] }
# or
a = a.map { |x| x.each_with_object({}) { |i,h| h[i] = nil } }
Some useful references:
Hash[]
each_with_object
zip
Array *

If what you are trying to do is simply tag specific elements in the member arrays with boolean values, it is just a simple matter of doing the following:
current_value = a[i][j]
a[i][j] = [current_value, true_or_false]
For example if you have
a = [[1,2,3,4],[5]]
Then if you say
a[0][2] = [a[0,2],true]
then a becomes
a = [[1,2,[3,true],4],[5]]
You can roll this into a method
def tag_array_element(a, i, j, boolean_value)
a[i][j] = [a[i][j], boolean_value]
end
You might want to enhance this a little so you don't tag a specific element twice. :) To do so, just check if a[i][j] is already an array.

Change x % 2 == 0 for the actual operation you want for the mapping:
>> xss = [[1,2,3,4],[5]]
>> xss.map { |xs| xs.map { |x| {x => x % 2} } }
#=> [[{1=>false}, {2=>true}, {3=>false}, {4=>true}], [{5=>false}]]

Related

Count keys inside of hash that have specific value

I have a Hash that looks like:
day = {
:morning => true,
:afternoon => false,
:evening => true,
:night => true
}
I'd like to get the percent of values that == true
I'm current doing
sum = day.count.to_f
adherence_percent = (day.select{|k, v| v == true }.count / sum) * 100
:> 66.6666666
Is there a more elegant/efficient way I can do this?
You can do this using count and last
day.count(&:last).fdiv(day.size)
Demonstration
If you know there are only true/false values in there
day.values.count(&:itself)
otherwise
day.values.count { |item| item == true }
usage:
sum = day.count.to_f
adherence_percent = day.values.count(&:itself) / sum * 100

How to change a value in an array via a hash?

I want to change the value of an array via a hash, for example:
arr = ['g','g','e','z']
positions = {1 => arr[0], 2 => arr[1]}
positions[1] = "ee"
Problem is that the one that changed is hash and not array. When I do p arr It still outputs ['g','g','e','z']. Is there a way around this?
You're going to need to add another line of code to do what you want:
arr = ['g','g','e','z']
positions = {1 => arr[0], 2 => arr[1]}
positions[1] = "ee"
arr[0] = positions[1]
Another option would be to make a method that automatically updated the array for you, something like this:
def update_hash_and_array(hash, array, val, index)
# Assume that index is not zero indexed like you have
hash[index] = val
array[index - 1] = val
end
update_hash_and_array(positions, arr, "ee", 1) # Does what you want
This is possible to code into your hash using procs.
arr = ['g','g','e','z']
positions = {1 => -> (val) { arr[0] = val } }
positions[1].('hello')
# arr => ['hello', 'g', 'e', 'z']
You can generalize this a bit if you want to generate a hash that can modify any array.
def remap_arr(arr, idx)
(idx...arr.length+idx).zip(arr.map.with_index{|_,i| -> (val) {arr[i] = val}}).to_h
end
arr = [1,2,3,4,5,6]
positions = remap_arr(arr, 1)
positions[2].('hello')
# arr => [1,'hello',3,4,5,6]
positions[6].('goodbye')
# arr => [1,'hello',3,4,5,'goodbye']
But I'm hoping this is just a thought experiment, there is no reason to change the way array indexing behavior works to start from 1 rather than 0. In such cases, you would normally just want to offset the index you have to match the proper array indexing (starting at zero). If that is not sufficient, it's a sign you need a different data structure.
#!/usr/bin/env ruby
a = %w(q w e)
h = {
1 => a[0]
}
puts a[0].object_id # 70114787518660
puts h[1].object_id # 70114787518660
puts a[0] === h[1] # true
# It is a NEW object of a string. Look at their object_ids.
# That why you can not change value in an array via a hash.
h[1] = 'Z'
puts a[0].object_id # 70114787518660
puts h[1].object_id # 70114574058580
puts a[0] === h[1] # false
h[2] = a
puts a.object_id # 70308472111520
puts h[2].object_id # 70308472111520
puts h[2] === a # true
puts a[0] === h[2][0] # true
# Here we can change value in the array via the hash.
# Why?
# Because 'h[2]' and 'a' are associated with the same object '%w(q w e)'.
# We will change the VALUE without creating a new object.
h[2][0] = 'X'
puts a[0] # X
puts h[2][0] # X
puts a[0] === h[2][0] # true

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

frequency of objects in an array using Ruby

If i had a list of balls each of which has a color property. how can i cleanly get the list of balls with the most frequent color.
[m1,m2,m3,m4]
say,
m1.color = blue
m2.color = blue
m3.color = red
m4.color = blue
[m1,m2,m4] is the list of balls with the most frequent color
My Approach is to do:
[m1,m2,m3,m4].group_by{|ball| ball.color}.each do |samecolor|
my_items = samecolor.count
end
where count is defined as
class Array
def count
k =Hash.new(0)
self.each{|x|k[x]+=1}
k
end
end
my_items will be a hash of frequencies foreach same color group. My implementation could be buggy and i feel there must be a better and more smarter way.
any ideas please?
You found group_by but missed max_by
max_color, max_balls = [m1,m2,m3,m4].group_by {|b| b.color}.max_by {|color, balls| balls.length}
Your code isn't bad, but it is inefficient. If I were you I would seek a solution that iterates through your array only once, like this:
balls = [m1, m2, m3, m4]
most_idx = nil
groups = balls.inject({}) do |hsh, ball|
hsh[ball.color] = [] if hsh[ball.color].nil?
hsh[ball.color] << ball
most_idx = ball.color if hsh[most_idx].nil? || hsh[ball.color].size > hsh[most_idx].size
hsh
end
groups[most_idx] # => [m1,m2,m4]
This does basically the same thing as group_by, but at the same time it counts up the groups and keeps a record of which group is largest (most_idx).
How about:
color,balls = [m1,m2,m3,m4].group_by { |b| b.color }.max_by(&:size)
Here's how I'd do it. The basic idea uses inject to accumulate the values into a hash, and comes from "12 - Building a Histogram" in "The Ruby Cookbook".
#!/usr/bin/env ruby
class M
attr_reader :color
def initialize(c)
#color = c
end
end
m1 = M.new('blue')
m2 = M.new('blue')
m3 = M.new('red')
m4 = M.new('blue')
hash = [m1.color, m2.color, m3.color, m4.color].inject(Hash.new(0)){ |h, x| h[x] += 1; h } # => {"blue"=>3, "red"=>1}
hash = [m1, m2, m3, m4].inject(Hash.new(0)){ |h, x| h[x.color] += 1; h } # => {"blue"=>3, "red"=>1}
There are two different ways to do it, depending on how much knowledge you want the inject() to know about your objects.
this produces a reverse sorted list of balls by frequency
balls.group_by { |b| b.color }
.map { |k, v| [k, v.size] }
.sort_by { |k, count| -count}
two parts, I'll use your strange balls example but will also include my own rails example
ary = [m1,m2,m3,m4]
colors = ary.each.map(&:color) #or ary.each.map {|t| t.color }
Hash[colors.group_by(&:w).map {|w, ws| [w, ws.length] }]
#=> {"blue" => 3, "red" => 1 }
my ActiveRecord example
stocks = Sp500Stock.all
Hash[stocks.group_by(&:sector).map {|w, s| [w, s.length] }].sort_by { |k,v| v }
#=> {"Health Care" => 36, etc]
myhash = {}
mylist.each do |ball|
if myhash[ball.color]
myhash[ball.color] += 1
else
myhash[ball.color] = 1
end
end
puts myhash.sort{|a,b| b[1] <=> a[1]}

Ruby: How to either set a variable to 0 or, if it is already set, increment by 1

I know of the ||= operator, but don't think it'll help me here...trying to create an array that counts the number of "types" among an array of objects.
array.each do |c|
newarray[c.type] = newarray[c.type] ? newarray[c.type]+1 ? 0
end
Is there a more graceful way to do this?
types = Hash.new(-1) # It feels like this should be 0, but to be
# equivalent to your example it needs to be -1
array.each do |c|
types[c.type] += 1
end
Use the Array#fetch method for which you can provide a default value if the index doesn't exist:
array.each do |c|
newarray[c.type] = newarray.fetch(c.type, -1) + 1
end
array.each do |c|
newarray[c.type] = 1 + (newarray[c.type] || -1)
end
Alternatively
array.each do |c|
newarray[c.type] ||= -1
newarray[c.type] += 1
end
||= does help:
types = {}
array.each do |c|
types[c.class] ||= 0
types[c.class] += 1
end
Your variable newarray is named oddly, since in Ruby and most other languages, arrays are indexed by integers, not random Objects like Class. It's more likely this is a Hash.
Also, you should be using c.class, instead of c.type, which is deprecated.
Finally, since you're creating a Hash, you can use inject like so:
newarray = array.inject( {} ) do |h,c|
h[c.class] = h.key?(c.class) ? h[c.class]+1 : 0
h
end
Or, for a one-liner:
newarray = array.inject( {} ) { |h,c| h[c.class] = h.key?(c.class) ? h[c.class]+1 : 0 ; h }
As you can see, this gives the desired results:
irb(main):001:0> array = [1, {}, 42, [], Object.new(), [1, 2, 3]]
=> [1, {}, 42, [], #<Object:0x287030>, [1, 2, 3]]
irb(main):002:0> newarray = array.inject( {} ) { |h,c| h[c.class] = h.key?(c.class) ? h[c.class]+1 : 0 ; h }
=> {Object=>0, Hash=>0, Array=>1, Fixnum=>1}
In Ruby 1.8.7 or later you can use group_by and then turn each list of elements into count - 1, and make a hash from the array returned by map.
Hash[array.group_by(&:class).map { |k,v| [k, v.size-1] }]

Resources