Add to Array Loop - ruby

Ruby code is:
a = []
h = {}
2.times.each do |i|
%w(a b c).each do |x|
h[x] = x + i.to_s
end
a << h
end
the result is:
a = [{"c"=>"c1", "b"=>"b1", "a"=>"a1"}, {"c"=>"c1", "b"=>"b1", "a"=>"a1"}]
but i hope the result is:
a = [{"c"=>"c0", "b"=>"b0", "a"=>"a0"}, {"c"=>"c1", "b"=>"b1", "a"=>"a1"}]
who can help me.thx

After a << h you have to do h = {}. This is because you are assigning a new object to h so that it doesn't override the previous values.

[Complementary answer] Are you familiar with the principles of functional programming?
(0..1).map { |n| Hash[("a".."c").map { |c| [c, "#{c}#{n}"] }] }
#=> {"a"=>"a0", "b"=>"b0", "c"=>"c0"}, {"a"=>"a1", "b"=>"b1", "c"=>"c1"}]

Related

What's wrong with my code?

def encrypt(string)
alphabet = ("a".."b").to_a
result = ""
idx = 0
while idx < string.length
character = string[idx]
if character == " "
result += " "
else
n = alphabet.index(character)
n_plus = (n + 1) % alphabet.length
result += alphabet[n_plus]
end
idx += 1
end
return result
end
puts encrypt("abc")
puts encrypt("xyz")
I'm trying to get "abc" to print out "bcd" and "xyz" to print "yza". I want to advance the letter forward by 1. Can someone point me to the right direction?
All I had to do was change your alphabet array to go from a to z, not a to b, and it works fine.
def encrypt(string)
alphabet = ("a".."z").to_a
result = ""
idx = 0
while idx < string.length
character = string[idx]
if character == " "
result += " "
else
n = alphabet.index(character)
n_plus = (n + 1) % alphabet.length
result += alphabet[n_plus]
end
idx += 1
end
return result
end
puts encrypt("abc")
puts encrypt("xyz")
Another way to solve the issue, that I think is simpler, personally, is to use String#tr:
ALPHA = ('a'..'z').to_a.join #=> "abcdefghijklmnopqrstuvwxyz"
BMQIB = ('a'..'z').to_a.rotate(1).join #=> "bcdefghijklmnopqrstuvwxyza"
def encrypt(str)
str.tr(ALPHA,BMQIB)
end
def decrypt(str)
str.tr(BMQIB,ALPHA)
end
encrypt('pizza') #=> "qjaab"
decrypt('qjaab') #=> "pizza"
Alternatively if you don't want to take up that memory storing the alphabet you could use character codings and then just use arithmetic operations on them to shift the letters:
def encrypt(string)
result = ""
idx = 0
while idx < string.length
result += (string[idx].ord == 32 ? (string[idx].chr) : (string[idx].ord+1).chr)
idx += 1
end
result
end
Other strange thing about ruby is that you do not need to explicitly return something at the end of the method body. It just returns the last thing by default. This is considered good style amongst ruby folks.
Your question has been answered, so here are a couple of more Ruby-like ways of doing that.
Use String#gsub with a hash
CODE_MAP = ('a'..'z').each_with_object({}) { |c,h| h[c] = c < 'z' ? c.next : 'a' }
#=> {"a"=>"b", "b"=>"c",..., "y"=>"z", "z"=>"a"}
DECODE_MAP = CODE_MAP.invert
#=> {"b"=>"a", "c"=>"b",..., "z"=>"y", "a"=>"z"}
def encrypt(word)
word.gsub(/./, CODE_MAP)
end
def decrypt(word)
word.gsub(/./, DECODE_MAP)
end
encrypt('pizza')
#=> "qjaab"
decrypt('qjaab')
#=> "pizza"
Use String#gsub with Array#rotate
LETTERS = ('a'..'z').to_a
#=> ["a", "b", ..., "z"]
def encrypt(word)
word.gsub(/./) { |c| LETTERS.rotate[LETTERS.index(c)] }
end
def decrypt(word)
word.gsub(/./) { |c| LETTERS.rotate(-1)[LETTERS.index(c)] }
end
encrypt('pizza')
#=> "qjaab"
decrypt('qjaab')
#=> "pizza"

How to turn a ruby hash so that the value becomes a key that points to a group of old keys?

I have a hash like this:
t={"4nM"=>"Triangle", "I40"=>"Triangle", "123"=>"Square"}
And I want to turn it into a hash like:
{"Triangle" => ["4nM", "I40"], "Square" => ["123"]}
What is the best way to do this?
I start with group_by but then the code gets to be a bit convoluted....
This is what I did:
t.group_by { |k, v| v }.map { |type, group| {type => group.flatten.reject { |x| x == type } } }
h = { "4nM"=>"Triangle", "I40"=>"Triangle", "123"=>"Square" }
h.each_with_object({}) { |(k,v),h| (h[v] ||= []) << k }
#=> {"Triangle"=>["4nM", "I40"], "Square"=>["123"]}
The expression
(h[v] ||= []) << k
expands to
(h[v] = h[v] || []) << k
If h has a key v, h[k] will be truthy, so the expression above reduces to
(h[v] = h[v]) << k
and then
h[v] << k
If h does not have a key v, h[k] #=> nil, so the expression above reduces to
(h[v] = []) << k
resulting in
h[v] #=> [k]
Alternatively, we could write
h.each_with_object(Hash.new { |h,k| h[k] = [] }) { |(k,v),h| h[v] << k }
#=> {"Triangle"=>["4nM", "I40"], "Square"=>["123"]}
See Hash::new for an explanation of the use of a block for returning the default values of keys that are not present in the hash.
This is the shortest I could write :
t.group_by(&:last).map{|k,v|[k,v.map(&:first)]}.to_h
Still 4 characters longer than #Cary Swoveland's answer.
Note that in Rails, Hash#transform_values makes it a bit easier :
t.group_by{|_,v| v }.transform_values{|v| v.map(&:first) }
You can cut it down a little bit by doing this
t.group_by {|k,v| v}.map{|k,v| {k => v.map(&:first)}}
but your original implementation was already pretty concise.
t={"4nM"=>"Triangle", "I40"=>"Triangle", "123"=>"Square"}
h = Hash.new{[]}
t.each{|k,v| h[v] <<= k}

Ruby parallel sorting

I am trying to create a multithreaded version of a sorting algorithm. I do not understand why this algorithm always returns just Array[1] instead of the full array.
class Array
def quick_sort
return self if self.length <= 1
pivot = self[0]
if block_given?
less, greater_equals = self[1..-1].partition { yield(x, pivot) }
else
less, greater_equals = self[1..-1].partition { |x| x < pivot }
end
l = []
g = []
Process.fork {l = less.quick_sort }
Process.fork {g = greater_equals.quick_sort}
Process.waitall
return l + [pivot] + g
end
end
The local variables l and g are not passed beyond Process.fork. They are only valid within that block. For example,
Process.fork{a = 2}
Process.wait
a #=> NameError: undefined local variable or method `a' for main:Object
In your code, the l and g assignments done before Process.fork are still valid when you call return l + [pivot] + g.
By the way, if you had intended l and g to be passed from Process.fork, then your initialization of these variables prior to Process.fork is meaningless.
From you examples it looks like you are trying to use Process where you actually want to use a thread.
Process: no shared resources with itś caller (Parent)
Thread: shares memory with its Parent
Your example would work if you replaced the Process.fork with Threads:
l = []
g = []
left_thread = Thread.new {l = less.quick_sort }
right_thread = Thread.new {g = greater_equals.quick_sort}
left_thread.join
right_thread.join
return l. + [pivot] + g

Filter arrays with bitmask or other array in Ruby

I was wondering if there was an Array method in Ruby that allows to filter an array based on another array or a bitmask.
Here is an example and a quick implementation for illustration purposes:
class Array
def filter(f)
res = []
if f.is_a? Integer
(0...self.size).each do |i|
res << self[i] unless f[i].nil? || 2**i & f == 0
end
else
(0...self.size).each do |i|
res << self[i] unless f[i].nil? || f[i] == 0
end
end
return res
end
end
Example:
%w(a b c).filter([1, 0, 1]) ==> ['a', 'c']
%w(a b c).filter(4) ==> ['c']
%w(a b c).filter([1]) ==> ['a']
Thanks!
In ruby 1.9 Fixnum#[] gives you bit values at a particular position, so it will work for both integers and arrays. I'm thinking something like this:
class Array
def filter f
select.with_index { |e,i| f[i] == 1 }
end
end
%w(a b c).filter([1, 0, 1]) #=> ['a', 'c']
%w(a b c).filter(4) #=> ['c']
%w(a b c).filter(5) #=> ['a', c']
%w(a b c).filter([1]) #=> ['a']
class Array
def filter(f)
f = f.to_s(2).split("").map(&:to_i) unless Array === f
reverse.reject.with_index{|_, i| f[-i].to_i.zero?}
end
end

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]}

Resources