Iteration and array - ruby

How do I iterate 9 times and produce three arrays like this:
1 a
2 b
3 c
["a","b","c"]
4 d
5 e
6 f
["d","e","f"]
7 g
8 h
9 i
["g","h","i"] ?
-------------------------------------
1.upto(9) do
xxx = gets.chomp
wn << xxx
if wn.length ==3
puts wn.inspect
end
end
------------------------------------
I get the following output:
a
b
c
["a", "b", "c"]
d
e
f
g
h
i
Not the results I hoped for :(

A simple solution:
a1 = []
a2 = []
a3 = []
1.upto(9) do |i|
if a1.empty? || a1.size < 3
a1 << gets.chomp!
elsif a2.size < 3
a2 << gets.chomp!
else
a3 << gets.chomp!
end
end
puts a1
puts a2
puts a3
Create the 3 arrays, iterate 9 times, create conditions to populate them.

Do you have to iterate? you could always break your string by length, like so:
"abcdefghi".scan(/.{3}/).map{|i| i.split('')} # => [["a", "b", "c"], ["d", "e", "f"], ["g", "h", "i"]]
If you really must iterate:
1.upto(9) do
xxx = gets.chomp
wn << xxx
if wn.length % 3 == 0
puts wn.inspect
end
end

I think nested loops are a clean solution for you.
a = [[],[],[]]
3.times do |i|
3.times { |j| a[i][j] = gets.strip }
puts a[i].inspect
end

Related

Reconstructing the shortest path in a graph in BFS

I am implementing a function in ruby using BFS, and I would like to know how to print the shortest path in an undirected graph, between a start value and end value
Graph
In this example the graph has ten vertices. Each node represents a vertex and it has an array that stores adjacent nodes (edges).
$ irb
graph.to_s
1. 1 -> [2 3 4 5 8 9 10]
2. 2 -> [1 4 5 6 7 8 10]
3. 3 -> [1 4 6]
4. 4 -> [1 2 3 6 7 8 9 10]
5. 5 -> [1 2 8 9 10]
6. 6 -> [2 3 4 7 8 9 10]
7. 7 -> [2 4 6 8 9]
8. 8 -> [1 2 4 5 6 7 9 10]
9. 9 -> [1 4 5 6 7 8]
10. 10 -> [1 2 4 5 6 8]
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Expected Output
2,1,9 or 2,4,9, etc.
BFS
def bfs_shortest_path(graph, start=2, search=9)
if graph.nodes[start].nil? || graph.nodes[search].nil?
return nil
end
visited = Set.new
search_queue = Queue.new
search_queue.enq(start)
while !search_queue.empty? do
current_node_key = search_queue.deq
current_node = graph.nodes[current_node_key]
visited.add(current_node_key)
if current_node.value == search
return current_node # I would like to return the PATH Eg. 2,1,9
end
adjacent_nodes_array = current_node.adjacent_nodes.map{|x| x.value}
adjacent_nodes_array.each do |value|
if !visited.include?(value)
search_queue.enq(value)
graph.nodes[value].concat_to_path(current_node.path_from_start)
end
end
end
end
Node
class Node
attr_reader :value
attr_reader :adjacent_nodes
def initialize(value)
#value = value
#adjacent_nodes = []
end
def add_edge(node)
#adjacent_nodes.push(node)
end
def to_s
"#{#value} -> [#{#adjacent_nodes.map(&:value).sort.join(" ")}]"
end
end
Graph
class Graph
attr_reader :nodes
def initialize
#nodes = {}
end
def add_node(node)
#nodes[node.value] = node
end
def add_edge(node1,node2)
if #nodes[node1.value].adjacent_nodes.map(&:value).include? (node2.value)
puts "#{node1.value} and #{node2.value} already have an edge"
elsif node1.value == node2.value
puts "node1.value == node2.value"
else
#nodes[node1.value].add_edge(#nodes[node2.value])
#nodes[node2.value].add_edge(#nodes[node1.value])
end
end
def to_s
#nodes.keys.sort.each_with_index do |key,index|
puts "#{index + 1}. #{#nodes[key].to_s}"
end
end
end
Generating a Graph
def generate_random_graph
g = Graph.new
[*1..10].shuffle.each do |node_value|
g.add_node(Node.new(node_value))
end
40.times do
key1 = g.nodes.keys.sample
key2 = g.nodes.keys.sample
g.add_edge(g.nodes[key1],g.nodes[key2])
end
return g
end
Test
graph = generate_random_graph
graph.to_s
bfs_shortest_path(graph,2,9)
Thanks for the comments
Working Version
class Node
attr_reader :value
attr_reader :adjacent_nodes
attr_reader :path_from_start
def initialize(value)
#value = value
#adjacent_nodes = []
#path_from_start = []
end
def add_edge(node)
#adjacent_nodes.push(node)
end
def add_to_path(value)
#path_from_start.push(value)
end
def concat_to_path(value_array)
#path_from_start.concat(value_array)
end
def to_s
"#{#value} -> [#{#adjacent_nodes.map(&:value).sort.join(" ")}]"
end
end
class Graph
attr_reader :nodes
def initialize
#nodes = {}
end
def add_node(node)
#nodes[node.value] = node
end
def add_edge(node1,node2)
if #nodes[node1.value].adjacent_nodes.map(&:value).include? (node2.value)
puts "#{node1.value} and #{node2.value} already have an edge"
elsif node1.value == node2.value
puts "node1.value == node2.value"
else
#nodes[node1.value].add_edge(#nodes[node2.value])
#nodes[node2.value].add_edge(#nodes[node1.value])
end
end
def to_s
#nodes.keys.sort.each_with_index do |key,index|
puts "#{index + 1}. #{#nodes[key].to_s}"
end
end
end
def generate_random_graph
g = Graph.new
[*1..10].shuffle.each do |node_value|
g.add_node(Node.new(node_value))
end
40.times do
key1 = g.nodes.keys.sample
key2 = g.nodes.keys.sample
g.add_edge(g.nodes[key1],g.nodes[key2])
end
return g
end
def bfs(graph, start_node_value=2, search_value=9)
if graph.nodes[start_node_value].nil? || graph.nodes[search_value].nil?
return nil
end
visited = Set.new
search_queue = Queue.new
search_queue.enq(graph.nodes[start_node_value])
while !search_queue.empty? do
current_node = search_queue.deq
visited.add(current_node)
if current_node.value == search_value
return current_node
end
current_node.adjacent_nodes.each do |node|
if !visited.include?(graph.nodes[node.value])
search_queue.enq(graph.nodes[node.value])
end
end
end
end
def bfs_shortest_path(graph, start=2, search=9)
if graph.nodes[start].nil? || graph.nodes[search].nil?
return nil
end
visited = Set.new
visited.add(start)
search_queue = Queue.new
search_queue.enq(start)
while !search_queue.empty? do
current_node_key = search_queue.deq
current_node = graph.nodes[current_node_key]
current_node.add_to_path(current_node.value)
if current_node.value == search
return current_node.path_from_start
end
adjacent_nodes_array = current_node.adjacent_nodes.map{|x| x.value}
adjacent_nodes_array.each do |value|
if !visited.include?(value)
search_queue.enq(value)
visited.add(value)
graph.nodes[value].concat_to_path(current_node.path_from_start)
end
end
end
end
def test_graph
graph = generate_random_graph
graph.to_s
bfs_shortest_path(graph,2,9)
end
To keep a history of where you've been so you can reconstruct a path, instead of a visited set, use a hash. The hash tracks which node is the predecessor of each visited node. When you push a neighbor onto the queue, add the current node we're moving away from as the parent/predecessor by came_from[neighbor] = current.
This hash also works for eliminating cycles as visited does.
When you reach the goal, you can rebuild the path by repeatedly keying into this came_from hash until you run out of predecessors. Reverse the array (linear time) and return it as the final path.
Here's a minimal, runnable example you can adapt to your class structure:
def reconstruct_path(tail, came_from)
path = []
while !tail.nil?
path << tail
tail = came_from[tail]
end
path.reverse
end
def bfs(graph, start, goal)
q = Queue.new
q.enq(start)
came_from = {start => nil}
while !q.empty?
curr = q.deq
if graph.key? curr
return reconstruct_path(goal, came_from) if curr == goal
graph[curr].each do |neighbor|
if !came_from.key?(neighbor)
came_from[neighbor] = curr
q.enq(neighbor)
end
end
end
end
end
graph = {
"A" => ["B", "C"],
"B" => ["A", "F"],
"C" => ["D"],
"D" => ["E", "F"],
"E" => [],
"F" => []
}
=begin
+----+
v |
A--->B--->F
| ^
V |
C--->D----+
|
v
E
=end
p bfs(graph, "A", "F") # => ["A", "B", "F"]
p bfs(graph, "A", "E") # => ["A", "C", "D", "E"]
p bfs(graph, "B", "E") # => ["B", "A", "C", "D", "E"]

Ruby. Shuffle the array so that there are no adjacent elements with the same value

An array of hashes is given (10 elements at least):
arr = [{letter: "a", number: "1"}, {letter: "a", number: "3"}, {letter: "b", number: "4"}, {letter: "b", number: "1"}, ..., {letter: "e", number: "2"} ]
The task is to shuffle the array so that there are no adjacent elements with the same 'letter' value.
So, the result should be like the following:
[{letter: "c", number: "4"}, {letter: "a", number: "1"}, {letter: "e", number: "2"}, {letter: "b", number: "1"}, ..., {letter: "a", number: "3"} ]
What is the simplest way to do that?
=== UPDATE ===
The number of repeated letters in the array is precisely known - it's 20% of the array length.
So, the array looks like the following:
[
{letter: "a", number: "1"}, {letter: "a", number: "3"},
{letter: "b", number: "4"}, {letter: "b", number: "1"},
{letter: "c", number: "7"}, {letter: "c", number: "3"},
{letter: "d", number: "6"}, {letter: "d", number: "4"},
{letter: "e", number: "5"}, {letter: "e", number: "2"}
]
Or, its simplified version:
["a", "a", "b", "b", "c", "c", "d", "d", "e", "e"]
Or, for example, there is a simplified array containing 15 elements:
["a", "a", "a", "b", "b", "b", "c", "c", "c", "d", "d", "d", "e", "e", "e"]
The simplest way (without any random):
# Calculate letter frequency
freq = arr.group_by { |h| h[:letter] }.map { |k, v| [k, v.size] }.to_h
# Then check that the most frequent element occurs less that arr.size / 2
center = (arr.size + 1) / 2
if freq.values.max > center
# Impossible
end
# Sort array by frequency to have most frequent first.
sarr = arr.sort_by { |h| freq[h[:letter]] }.reverse
sarr[0..center-1].zip(sarr[center..-1]).flatten.compact
Your problem is a special case of this question. See my answer for the detailed explanation how this works.
We even don't need to sort by letter frequency. It's for corner cases like "abbcccc". We can solve them in another way:
# Works with correct data: most frequent letter occurs <= center times
def f(arr)
arr = arr.sort
center = (arr.size + 1) / 2
arr = arr[0..center-1].zip(arr[center..-1]).flatten.compact
double = (1..arr.size-1).find { |i| arr[i] == arr[i-1] }
double ? arr.rotate(double) : arr # fix for the corner cases
end
puts f(%w[a a a a b b c].shuffle).join
# ababaca
puts f(%w[a a b b b b c].shuffle).join
# bcbabab
puts f(%w[a b b c c c c].shuffle).join
# cacbcbc
The only non-linear part of the algorithm is arr.sort. But as you can see by the link above, we even don't need the sorting. We need letters counts, which could be found in linear time. Therefore, we can reduce the algorithm to O(n).
The number of repeated letters in the array is precisely known - it's 20% of the array length.
With this update, the algorithm is simplified to (as there are no corner cases):
sarr = arr.sort_by { |h| h[:letter] }
center = (arr.size + 1) / 2
sarr[0..center-1].zip(sarr[center..-1]).flatten.compact
The simple and maybe the less effective way could be the brute force.
So on a simplified version of the array, one can do:
ary = %w(a a c c b a s)
loop do
break if ary.shuffle!.slice_when { |a, b| a == b }.to_a.size == 1
end
Some check should be added to assure that a solution exists, to avoid infinite loop.
Other (better?) way is to shuffle then find the permutation (no infinite loop) which satisfy the condition:
ary.shuffle!
ary.permutation.find { |a| a.slice_when { |a, b| a == b }.to_a.size == 1 }
If a solution does not exist, it returns nil.
Run the the benchmark:
def looping
ary = %w(a a c c b a s)
loop do
break if ary.shuffle!.slice_when { |a, b| a == b }.to_a.size == 1
end
ary
end
def shuffle_permute
ary = %w(a a c c b a s)
ary.shuffle!
ary.permutation.lazy.find { |a| a.slice_when { |a, b| a == b }.to_a.size == 1 }
end
require 'benchmark'
n = 500
Benchmark.bm do |x|
x.report { looping }
x.report { shuffle_permute }
end
Code
def reorder(arr)
groups = arr.group_by { |h| h[:letter] }
return nil if 2 * groups.map { |_,v| v.size }.max > arr.size + 1
max_key = groups.max_by { |_,a| a.size }.first
letters = ([max_key] + (groups.keys - [max_key])).cycle
ordered = []
while ordered.size < arr.size
k = letters.next
ordered << groups[k].pop unless groups[k].empty?
end
ordered
end
nilis returned if it is not possible to rearrange the elements in such a way that g[:letter] != h[:letter] for all pairs of consecutive elements g and h.
Note that this method has near linear computational complexity, O(arr.size), "near" because hash lookups are not quite constant time.
If desired, one could call the method with arr randomized: reorder(arr.shuffle).
Example
arr = [
{ letter: "a" }, { letter: "e" }, { letter: "b" }, { letter: "b" },
{ letter: "e" }, { letter: "a" }, { letter: "a" }, { letter: "f" }
]
reorder(arr)
#=> [{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"f"},
# {:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"a"}]
Proof
The assertion is that if the line
return nil if 2 * groups.map { |_,v| v.size }.max > arr.size + 1
were removed from the method the array returned by the method would have the property that for all pairs of successive elements, g, h, g[:letter] != h[:letter] if and only if
2 * groups.map { |_,v| v.size }.max <= arr.size + 1
The proof has two parts.
The above inequality holds if the method produces a valid array
Compute
max_key = groups.max_by { |_,a| a.size }.first
max_key_freq = groups.map { |_,v| v.size }.max
and assume a valid array is returned. There must be at least one element other than max_key between each successive value of max_key in that array. The number of elements of arr other than max_key must therefore be at least max_key_freq - 1, so that
max_key_freq + max_key_freq - 1 <= arr.size
Hence,
2 * max_key_freq <= arr.size + 1
which is the same as:
2 * groups.map { |_,v| v.size }.max <= arr.size + 1
The above inequality does not hold if the method produces an invalid array
Suppose ordered is returned and it contains successive elements g and h for which both g[:letter] and h[:letter] equal the same letter l.
Because of the way ordered is constructed:
groups[k] must be empty for all keys k in groups for which k != l;
f[:letter] must equal l for all elements of ordered following g (if there are any); and
l must be the first key enumerated by keys, which is a letter that appears with a frequency that is not less than that of any other letter. l has frequency groups.map { |_,v| v.size }.max.
If n = groups.keys.size there must be a non-negative integer k (loosely, the number of rounds of allocations for all keys of groups) such that the number of elements h of arr for which h[:letter] != l equals k*n and the number of elements h of arr for which h[:letter] == l is k*n + 2 + m, where m >= 0. The size of arr is therefore 2*k*n + 2 + m.
In that case,
2 * groups.map { |_,v| v.size }.max > arr.size + 1
-> 2 * (k*n + 2 + m) > (k*n + 2 + m + k*n) + 1
-> 2*k*n + 4 + 2*m > 2*k*n + 3 + m
-> (4-3) + m > 0
-> true
Explanation
For the example,
groups = arr.group_by { |h| h[:letter] }
#=> {"a"=>[{:letter=>"a"}, {:letter=>"a"}, {:letter=>"a"}],
# "e"=>[{:letter=>"e"}, {:letter=>"e"}],
# "b"=>[{:letter=>"b"}, {:letter=>"b"}],
# "f"=>[{:letter=>"f"}]}
The following tells us that a solution exists.
2 * groups.map { |_,v| v.size }.max > arr.size + 1
#=> 2 * [3, 2, 2, 1].max > 8 + 1
#=> 2 * 3 > 9
#=> 6 > 9
#=> false
Next create an enumerator letters.
max_key = groups.max_by { |_,a| a.size }.first
#=> "a"
letters = ([max_key] + (groups.keys - [max_key])).cycle
#=> #<Enumerator: ["a", "e", "b", "f"]:cycle>
The elements of letters are generated as follows.
letters.next #=> "a"
letters.next #=> "e"
letters.next #=> "b"
letters.next #=> "f"
letters.next #=> "a"
letters.next #=> "e"
... ad infinititum
See Array#cycle.
I can best explain the remaining calculations by salting the method with puts statements before running the method. Note that arr.size #=> 8.
def reorder(arr)
groups = arr.group_by { |h| h[:letter] }
puts "groups = #{groups}"
return nil if 2 * groups.map { |_,v| v.size }.max > arr.size + 1
max_key = groups.max_by { |_,a| a.size }.first
letters = ([max_key] + (groups.keys - [max_key])).cycle
ordered = []
while ordered.size < arr.size
puts "\nordered.size = #{ordered.size} < #{arr.size} = #{ordered.size < arr.size}"
k = letters.next
puts "k = #{k}"
puts "groups[#{k}].empty? = #{groups[k].empty?}"
ordered << groups[k].pop unless groups[k].empty?
puts "ordered = #{ordered}"
puts "groups = #{groups}"
end
ordered
end
reorder(arr)
#=> [{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"f"},
# {:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"a"}]
The following is displayed.
groups = {"a"=>[{:letter=>"a"}, {:letter=>"a"}, {:letter=>"a"}],
"e"=>[{:letter=>"e"}, {:letter=>"e"}],
"b"=>[{:letter=>"b"}, {:letter=>"b"}],
"f"=>[{:letter=>"f"}]}
ordered.size = 0 < 8 = true
k = a
groups[a].empty? = false
ordered = [{:letter=>"a"}]
groups = {"a"=>[{:letter=>"a"}, {:letter=>"a"}],
"e"=>[{:letter=>"e"}, {:letter=>"e"}],
"b"=>[{:letter=>"b"}, {:letter=>"b"}],
"f"=>[{:letter=>"f"}]}
ordered.size = 1 < 8 = true
k = e
groups[e].empty? = false
ordered = [{:letter=>"a"}, {:letter=>"e"}]
groups = {"a"=>[{:letter=>"a"}, {:letter=>"a"}],
"e"=>[{:letter=>"e"}],
"b"=>[{:letter=>"b"}, {:letter=>"b"}],
"f"=>[{:letter=>"f"}]}
ordered.size = 2 < 8 = true
k = b
groups[b].empty? = false
ordered = [{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}]
groups = {"a"=>[{:letter=>"a"}, {:letter=>"a"}],
"e"=>[{:letter=>"e"}],
"b"=>[{:letter=>"b"}],
"f"=>[{:letter=>"f"}]}
ordered.size = 3 < 8 = true
k = f
groups[f].empty? = false
ordered = [{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"f"}]
groups = {"a"=>[{:letter=>"a"}, {:letter=>"a"}],
"e"=>[{:letter=>"e"}], "b"=>[{:letter=>"b"}],
"f"=>[]}
ordered.size = 4 < 8 = true
k = a
groups[a].empty? = false
ordered = [{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"f"},
{:letter=>"a"}]
groups = {"a"=>[{:letter=>"a"}],
"e"=>[{:letter=>"e"}],
"b"=>[{:letter=>"b"}],
"f"=>[]}
ordered.size = 5 < 8 = true
k = e
groups[e].empty? = false
ordered = [{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"f"},
{:letter=>"a"}, {:letter=>"e"}]
groups = {"a"=>[{:letter=>"a"}],
"e"=>[],
"b"=>[{:letter=>"b"}],
"f"=>[]}
ordered.size = 6 < 8 = true
k = b
groups[b].empty? = false
ordered = [{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"f"},
{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}]
groups = {"a"=>[{:letter=>"a"}], "e"=>[], "b"=>[], "f"=>[]}
ordered.size = 7 < 8 = true
k = f
groups[f].empty? = true
ordered = [{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"f"},
{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}]
groups = {"a"=>[{:letter=>"a"}], "e"=>[], "b"=>[], "f"=>[]}
ordered.size = 7 < 8 = true
k = a
groups[a].empty? = false
ordered = [{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"f"},
{:letter=>"a"}, {:letter=>"e"}, {:letter=>"b"}, {:letter=>"a"}]
groups = {"a"=>[], "e"=>[], "b"=>[], "f"=>[]}
Refering to the revised question, if
arr = ["a", "a", "b", "b", "c", "c", "d", "d", "e", "e"]
one could simply write:
arr.each_slice(arr.index { |s| s != arr.first }.to_a.transpose.flatten
#=> ["a", "b", "c", "d", "e", "a", "b", "c", "d", "e"]
or
arr.each_slice(arr.count(arr.first)).to_a.transpose.flatten
This sounds a lot like backtracking.
I would build the "shuffled" array from left to right.
Assume that there are N elements in the array. Say that at some point during the algorithm, you have already the first k elements arranged to fulfil the condition.
Now you pick from the remaining (N-k) elements the first one, which you can append to your result array, without breaking the condition.
If you can find one, you repeat the process recursively, now having an result array of (k+1) elements.
If you can not find one, you return a failure indicator and let the caller (i.e. the previous recursion) try another choice.

Assigning values to words based on position of letters in alphabet

I have an array of names. Each letter gets a value of 1 to 26 for the alphabet. Then the letter is multiplied by its place in the list. I came up with the following algorithm:
score = 0
names.each_with_index do |name, index|
temp = 0
letters = name.to_s.scan(/(.)/)
letters.each do |letter|
temp += 1 if letter.to_s.match(/A/)
temp += 2 if letter.to_s.match(/B/)
temp += 3 if letter.to_s.match(/C/)
temp += 4 if letter.to_s.match(/D/)
temp += 5 if letter.to_s.match(/E/)
temp += 6 if letter.to_s.match(/F/)
temp += 7 if letter.to_s.match(/G/)
temp += 8 if letter.to_s.match(/H/)
temp += 9 if letter.to_s.match(/I/)
temp += 10 if letter.to_s.match(/J/)
temp += 11 if letter.to_s.match(/K/)
temp += 12 if letter.to_s.match(/L/)
temp += 13 if letter.to_s.match(/M/)
temp += 14 if letter.to_s.match(/N/)
temp += 15 if letter.to_s.match(/O/)
temp += 16 if letter.to_s.match(/P/)
temp += 17 if letter.to_s.match(/Q/)
temp += 18 if letter.to_s.match(/R/)
temp += 19 if letter.to_s.match(/S/)
temp += 20 if letter.to_s.match(/T/)
temp += 21 if letter.to_s.match(/U/)
temp += 22 if letter.to_s.match(/V/)
temp += 23 if letter.to_s.match(/W/)
temp += 24 if letter.to_s.match(/X/)
temp += 25 if letter.to_s.match(/Y/)
temp += 26 if letter.to_s.match(/Z/)
end
score += (index+1) * temp
end
puts score
This is quite slow code. I hope someone can explain me a better and faster way to accomplish this task.
This is how I would do it.
Assumptions
the only characters are upper- and lower-case letters and whitespace.
after converting all characters of each element of the array to lower case, you want the letter offsets from a for each word to be summed and then multiplied by the (base 1) position of the word in the array.
Code
def totals_by_name(names)
names.each.with_index(1).with_object({}) { |(name,i),tots|
tots[name] = i*(name.downcase.each_char.reduce(0) { |tot,c|
tot + c.ord - 'a'.ord + 1 }) }
end
def total(names)
totals_by_name(names).values.reduce(:+)
end
Example
names = %w{ Joanne Jackie Joe Jethro Jack Jill }
#=> ["Joanne", "Jackie", "Joe", "Jethro", "Jack", "Jill"]
total(names)
#=> 914
Explanation
For the method totals_by_name and the array names above:
e0 = names.each
#=> #<Enumerator: ["Joanne", "Jackie", "Joe", "Jethro", "Jack", "Jill"]:each>
We can see the values this enumerator will pass on by converting it to an array:
e0.to_a
#=> ["Joanne", "Jackie", "Joe", "Jethro", "Jack", "Jill"]
Continuing,
e1 = e0.with_index(1)
#=> #<Enumerator: #<Enumerator: ["Joanne", "Jackie", "Joe", "Jethro",
# "Jack", "Jill"]:each>:with_index(1)>
e1.to_a
#=> [["Joanne", 1], ["Jackie", 2], ["Joe", 3], ["Jethro", 4], ["Jack", 5], ["Jill", 6]]
e2 = e1.with_object({})
#=> #<Enumerator: #<Enumerator: #<Enumerator: ["Joanne", "Jackie", "Joe",
# "Jethro", "Jack", "Jill"]:each>:with_index(1)>:with_object({})>
e2.to_a
#=> [[["Joanne", 1], {}], [["Jackie", 2], {}], [["Joe", 3], {}],
# [["Jethro", 4], {}], [["Jack", 5], {}], [["Jill", 6], {}]]
We can think of e2 and e3 as compound enumerators.
Enumerator#each passes elements of e2 to the block and assigns values to the block variables. We can use Enumerator#next to see what happens:
(name,i),tots = e2.next
#=> [["Joanne", 1], {}]
name #=> "Joanne"
i #=> 1
tots #=> {}
The block calculation is:
e3 = name.downcase.each_char
#=> #<Enumerator: "joanne":each_char>
e3.to_a # have a look
#=> ["j", "o", "a", "n", "n", "e"]
e3.reduce(0) { |tot,c| tot + c.ord - 'a'.ord + 1 }
#=> 59
For
c = "j"
this block calculation is:
tot + c.ord - 'a'.ord + 1
#=> 0 + 106 - 97 + 1
#=> 10
Therefore:
tots[name] = i*(name.downcase.each_char.reduce(0) { |tot,c|
tot + c.ord - 'a'.ord + 1 })
#=> tots["Joanna"] = 1*(59)
tots
#=> {"Joanne"=>59}
Values for other names are calculated similarly. The method tots is straightforward.
I would most likely go with:
class String
def points
each_char.map.with_index do |char, index|
(char.downcase.ord - 96) * (index + 1)
end.inject(0, :+)
end
end
'Hello'.points #=> 177

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

How do I generate a matrix?

I have the following data:
gene strain
A1 S1
A1 S4
A1 S8
A2 S5
A2 S4
A2 S9
A3 S4
A3 S1
A3 S10
I need to produce a matrix that has the genes vs strains, I.E., I need to show which genes are present in which strains, so the matrix will look like this:
S1 S4 S5 S8 S9 S10
A1
A2
A3
Can anyone guide me through the best and quickest way to do this in Ruby? I have the array of strains and genes.
There are many ways you could represent the gene-strain matrix you need. The best way will depend on what you want to do with the matrix. Do you want to compare which strains are present in different genes? Or compare which genes have a given strain? Do you just want to be able to look up whether a given gene has a given strain?
One simple way would be a Hash whose keys are Sets:
require 'set'
h = Hash.new { |h,k| h[k] = Set.new }
# assuming you already have the data in an array of arrays...
data.each do |gene,strain|
h[gene] << strain
end
If you only want to print a matrix out on the screen, here is a little script to do so:
require 'set'
genes, strains = Set.new, Set.new
h = Hash.new { |h,k| h[k] = Set.new }
# again assuming you already have the data in an array of arrays
data.each { |g,s| h[g] << s; genes << g; strains << s }
genes, strains = genes.sort, strains.sort
FIELD_WIDTH = 5
BLANK = " "*FIELD_WIDTH
X = "X" + (" " * (FIELD_WIDTH - 1))
def print_fixed_width(str)
str = str[0,FIELD_WIDTH]
print str
print " "*(FIELD_WIDTH-str.length)
end
# now print the matrix
print BLANK
strains.each { |s| print_fixed_width(s) }
puts
genes.each do |g|
print_fixed_width(g)
strains.each { |s| h[g].include?(s) ? print X : print BLANK }
puts
end
Please post more details on what you want to do with the matrix and I will provide a more appropriate option if necessary.
You can represent this in a 2d array:
arr = [[1,1],[1,4],[1,8],[2,5],[2,4],[2,9],[3,4],[3,1],[3,10]]
quick and dirty table:
s = " 1234567890\n"
(1..3).each do |i|
s << i.to_s << ' '
(1..10).each do |j|
s << ( arr.include?( [i,j] ) ? 'x' : ' ' )
end
s << "\n"
end
puts s
1234567890
1 x x x
2 xx x
3 x x x
If you " need to check which genes are present in which strains", then a Hash would be sufficient:
str = <<DOC
A1 S1
A1 S4
A1 S8
A2 S5
A2 S4
A2 S9
A3 S4
A3 S1
A3 S10
DOC
ar = str.lines.map{|line| line.split(/\s+/) } #string to array of arrays
genes_from_strain = Hash.new{|h,k| h[k]=[] } #This hash will give an empty array if key is not present
ar.each{|pair| genes_from_strain[pair.last] << pair.first }
p genes_from_strain['S1'] #=>["A1", "A3"]

Resources