How to print a 2D array with fixed column width - arrays

I have an array:
animals = [
["cats", "dogs"],
["verrylongcat", "dog"],
["shortcat", "verrylongdog"],
["cat", "dog"]
]
And I would like to display it nicely. Is there an easy way to make the colums a fixed width so I get something like this:
cats dogs
verrylongcat dog
shortcat verrylongdog
cat dog
animals is just an example, my array could also have 3, or 4 columns or even more.

You are looking for String#ljust:
max_cat_size = animals.map(&:first).max_by(&:size).size
animals.each do |cat, dog|
puts "#{cat.ljust(max_cat_size)} #{dog}"
end
If you want more than one space just add the corresponding amount in the interpolation.
Assuming your array is n × m and not 2 × m:
animal_max_sizes = animals.first.size.times.map do |index|
animals.transpose[index].map(&:to_s).max_by(&:size).size
end
animals.map do |animal_line|
animal_line.each.with_index.reduce('') do |animal_line, (animal, index)|
animal_line + animal.to_s.ljust(animal_max_sizes[index].next)
end
end.each { |animal_line_stringified| puts animal_line_stringified }
Note: The to_ses are used in case your arrays contain nils, numbers, etc.

Another way to do this is with printf-style formatting. If you know you will always have exactly 2 words in each line then you can do this:
#!/usr/bin/env ruby
lines = [
' cats dogs',
' verrylongcat dog',
'shortcat verrylongdog ',
' cat dog ',
]
lines.map(&:strip).each do |line|
puts "%-14s%s" % line.split
end
Outputs:
cats dogs
verrylongcat dog
shortcat verrylongdog
cat dog
If you need to calculate the column width based on the data, then you'd have to do a little more work:
# as #ndn showed:
first_col_width = lines.map(&:split).map(&:first).max_by(&:size).size + 2
lines.map(&:strip).each do |line|
puts "%-#{first_col_width}s%s" % line.split
end

Here's another attempt for a variable numbers of columns. Given this array:
animals = [
['Cats', 'Dogs', 'Fish'],
['Mr. Tinkles', 'Buddy', 'Nemo'],
['Calico', 'Butch', 'Marlin'],
['Ginger', 'Ivy', 'Dory']
]
We can calculate the width of each column via transpose, map, length and max:
widths = animals.transpose.map { |x| x.map(&:length).max }
#=> [11, 5, 6]
Based on this, we can generate a format string that can be passed to sprintf (or its shortcut %):
row_format = widths.map { |w| "%-#{w}s" }.join(' ')
#=> "%-11s %-5s %-6s"
%s denotes a string argument, 11, 5 and 6 are our widths and - left-justifies the result.
Let's try it:
row_format % animals[0] #=> "Cats Dogs Fish "
row_format % animals[1] #=> "Mr. Tinkles Buddy Nemo "
row_format % animals[2] #=> "Calico Butch Marlin"
That looks good, we should use a loop and wrap everything it in a method:
def print_table(array)
widths = array.transpose.map { |x| x.map(&:length).max }
row_format = widths.map { |w| "%-#{w}s" }.join(' ')
array.each do |row_values|
puts row_format % row_values
end
end
print_table(animals)
Output:
Cats Dogs Fish
Mr. Tinkles Buddy Nemo
Calico Butch Marlin
Ginger Ivy Dory
More complex formatting
With a little tweaking, you can also output a MySQL style table:
def print_mysql_table(array)
widths = array.transpose.map { |x| x.map(&:length).max }
row_format = '|%s|' % widths.map { |w| " %-#{w}s " }.join('|')
separator = '+%s+' % widths.map { |w| '-' * (w+2) }.join('+')
header, *rows = array
puts separator
puts row_format % header
puts separator
rows.each do |row_values|
puts row_format % row_values
end
puts separator
end
print_mysql_table(animals)
Output:
+-------------+-------+--------+
| Cats | Dogs | Fish |
+-------------+-------+--------+
| Mr. Tinkles | Buddy | Nemo |
| Calico | Butch | Marlin |
| Ginger | Ivy | Dory |
+-------------+-------+--------+

Related

How to iterate over an array a certain amount of times?

array = [apple, orange]
number = 4
desired output:
apple
orange
apple
orange
So far, I have:
array.each do |x|
puts x
end
I'm just not sure how to iterate over the array 4 times.
array = ["apple", "orange"]
iter_count = 4
array.cycle.take(iter_count).each { |x|
puts x
}
array.cycle gives us an infinite enumerable that repeats the elements of array. Then we take the first iter_count elements from it and iterate over that.
Enumerable has a ton of goodies that perform neat tasks like this. Once you familiarize yourself with the module, you'll find you can do a lot of array- and stream- oriented processes much more easily.
ar = ["apple", "orange"]
n = 4
n.times { ar.each{|a| p a} }
array = ["apple", "orange"]
numOfIteration=4
for i in 0..numOfIteration-1
puts array[i%array.size]
end
A fun way to achieve this:
4.times { |n| p array[n % array.count] }
Definitely not the best: every iteration we are counting the number of elements in array and also processing that n is dividable by the number of elements. It's also not very readable, as there is some cognitive processing required to understand the statement.
A nicer way to achieve this:
print(arr.cycle.take(4).join("\n"))
apple
orange
apple
orange
another non-idiomatic for loop, 3 dots removes need for explicit subtraction
array = ['apple', 'orange']
number = 4
for i in 0...number
puts array[i % array.size]
end
and some silliness with lambdas and recursion :D
array = ['apple', 'orange']
number = 4
loop = lambda do |list, count|
return if count == number
puts list[count % list.size]
loop.(list, count + 1)
end
loop.(array, 0)

How to Iterate through multiple strings in an array

I have an array of strings.
a = "Apple Banana oranges grapes. free free free phones. deals deals time.black white grey"
b = a.split(/\./)
I want to check if a word "free" or "deals" is present in each string, and count and store it to a new array.
c = 0
d = 0
g = Array.new
t = Array.new
b.each do |i|
if /free/.match(i)
t.push i
c = c + 1
elsif /deals/.match(i)
t.push i
d = d + 1
else
g.push i
end
end
p d
p c
p g
p t
But it doesn't show the exact count. Is there any other way to parse a string inside an array?
With a little variable renaming, and some trickery of scan instead of match, this might work?
a = "Apple Banana oranges grapes. free free free phones. deals deals time.black white grey"
sentences = a.split(/\./) # Split on `.`
# Initialize variables
free_count = 0
deal_count = 0
no_match = []
matches = []
sentences.each do |i| # Go through each sentence
if (m = i.scan(/free/)) && m.any? # Try matching "free", and check if there is any matches
matches << i # Add match to matches array
free_count += m.length # Count up amounts of `free` spotted.
elsif (m = i.scan(/deals/)) && m.any? # Try matching "deals", and check if there is any matches
matches << i # Add match to matches array
deal_count += m.length # Count up amounts of `deals` spotted.
else
no_match << i # Count up for nothing spotted
end
end
p free_count #=> 3
p deal_count #=> 2
p no_match #=> ["Apple Banana oranges grapes", "black white grey"]
p matches #=> [" free free free phones", " deals deals time"]
Your approach counts the sentences, not the occurrences of a word in the matched sentences. Use c += i.split.count "free" to count the actual occurrences of the word "free". Avoid using single-letter variable names unless the meaning is clear.
However, this all seems like a bit of extra lifting; you can perform counts and select/reject items matching a pattern using builtin array methods:
a = "Apple Banana oranges grapes. free free free phones. deals deals time.black white grey"
p a.split(".").grep /\bfree\b|\bdeals\b/
p a.split(".").reject {|e| e =~ /\bfree\b|\bdeals\b/}
p a.split.count "free"
p a.split.count "deals"
Output:
[" free free free phones", " deals deals time"]
["Apple Banana oranges grapes", "black white grey"]
3
2
Try it!
Possibly a solution that follows your original code:
a = "Apple Banana oranges grapes. free free free phones. deals deals time.black white grey"
b = a.split(/\W+/)
c = 0
d = 0
g = Array.new
t = Array.new
b.each do |i|
if i == "free"
t.push i
c = c + 1
elsif i == "deals"
t.push i
d = d + 1
else
g.push i
end
end
puts "Original words: #{b}"
puts "Counts of 'free': #{c}"
puts "Counts of 'deals': #{d}"
puts "Array of matches: #{t}"
puts "Array of non matches: #{g}"
And the output is:
# Original words: ["Apple", "Banana", "oranges", "grapes", "free", "free", "free", "phones", "deals", "deals", "time", "black", "white", "grey"]
# Counts of 'free': 3
# Counts of 'deals': 2
# Array of matches: ["free", "free", "free", "deals", "deals"]
# Array of non matches: ["Apple", "Banana", "oranges", "grapes", "phones", "time", "black", "white", "grey"]
An example of counting using Ruby tools:
counts = {}
a.split(/\W+/).tap { |b| b.uniq.map { |w| counts[w.downcase] = b.count(w) } }
counts #=> {"apple"=>1, "banana"=>1, "oranges"=>1, "grapes"=>1, "free"=>3, "phones"=>1, "deals"=>2, "time"=>1, "black"=>1, "white"=>1, "grey"=>1}
Then you can access data, for example:
counts.keys #=> ["apple", "banana", "oranges", "grapes", "free", "phones", "deals", "time", "black", "white", "grey"]
counts['free'] #=> 3
counts['deals'] #=> 2
counts.select { |_, v| v > 1} #=> {"free"=>3, "deals"=>2}

Browse array of hashes to remove string included in other string in Ruby

I have an array of hashes built like this:
grapes_matched << { part: part, grape: grape_match }
I would like to:
keep the current array sorting
remove items in array if grape_match.name (Active Record item) includes another grape_match.name shorter.
For example, imagine my array of hashes is:
{ part:"toto", grape: AR Grape with name: "Cabernet" }
{ part:"titi", grape: AR Grape with name: "Cabernet Sauvignon" }
{ part:"tutu", grape: AR Grape with name: "Merlot" }
As the second "Cabernet Sauvignon" includes the first "Cabernet", I want to remove the first of the array.
If possible, I would like to not build another array and keep my array of hashes without changing the structure (not like the code below).
I have something very ugly at the time:
grapes_matched.each do |grape_matched|
temp_grape = grape_matched[:grape]
temp_grape_name = I18n.transliterate(temp_grape.name).downcase
# does the temp grape name is included in one of previous grapes
# first grape
grapes_founds << temp_grape if grapes_founds.length == 0
# other grapes
grapes_founds.each do |grape_found|
grapes_founds << temp_grape if !I18n.transliterate(grape_found.name).downcase.include? temp_grape_name
end
end
I'm quite sure this can be done with less lines of code in Ruby and keeping the initial array of hashes.
Thanks in advance.
My objective is to implement a reasonably-efficient algorithm.
Let's first simplify and rearrange the array somewhat.
grapes = [{ part:"toto", grape: "Cabernet" },
{ part:"tutu", grape: "Merlot" },
{ part:"titi", grape: "Cabernet Sauvignon" }]
We can then do the following to obtain the desired array in a reasonably-efficient manner.
grapes.each_with_index.
sort_by { |g,_i| -g[:grape].size }.
each_with_object([]) { |(g,i),a| a << [g,i] unless a.any? { |f,_i|
f[:grape].include?(g[:grape]) } }.
sort_by(&:last).
map(&:first)
#=> [{:part=>"tutu", :grape=>"Merlot"},
# {:part=>"titi", :grape=>"Cabernet Sauvignon"}]
The steps are as follows.
Add an index to each hash so that their original order in grapes can be determined later.
e = grapes.each_with_index
#=> #<Enumerator: [{:part=>"toto", :grape=>"Cabernet"},
# {:part=>"tutu", :grape=>"Merlot"},
# {:part=>"titi", :grape=>"Cabernet Sauvignon"}]:each_with_index>
Sort the hash/index pairs by decreasing size of g[:grape].
b = e.sort_by { |g,_i| -g[:grape].size }
#=> [[{:part=>"titi", :grape=>"Cabernet Sauvignon"}, 2],
# [{:part=>"toto", :grape=>"Cabernet"}, 0],
# [{:part=>"tutu", :grape=>"Merlot"}, 1]]
Add each hash/index pair [g,i] to an initially-empty array a, unless f[:grape] includes g[:grape] for a hash f already in a.
c = b.each_with_object([]) { |(g,i),a| a << [g,i] unless a.any? { |f,_i|
f[:grape].include?(g[:grape]) } }
#=> [[{:part=>"titi", :grape=>"Cabernet Sauvignon"}, 2],
# [{:part=>"tutu", :grape=>"Merlot"}, 1]]
To obtain the desired order of the hashes in c, sort them by their indexes in the original array grapes (which has no effect for this example).
d = c.sort_by(&:last)
#=> [[{:part=>"tutu", :grape=>"Merlot"}, 1],
# [{:part=>"titi", :grape=>"Cabernet Sauvignon"}, 2]]
Remove the indices.
d.map(&:first)
#=> [{:part=>"tutu", :grape=>"Merlot"},
# {:part=>"titi", :grape=>"Cabernet Sauvignon"}]
Depending on requirements, it may be preferable to replace f[:grape].include?(g[:grape]) with f[:grape].begin_with?(g[:grape]) || f[:grape].end_with?(g[:grape]).
A simple benchmark comparing #Max' solution with mine follows.
def max_way(grapes_matched)
grapes_matched.select do |grape_matched|
grapes_matched.none? { |gm| gm[:grape] != grape_matched[:grape] &&
grape_matched[:grape].include?(gm[:grape]) }
end
end
def cary_way(grapes)
grapes.each_with_index.
sort_by { |g,_i| -g[:grape].size }.
each_with_object([]) { |(g,i),a| a << [g,i] unless a.any? { |f,_i|
f[:grape].include?(g[:grape]) } }.
sort_by(&:last).
map(&:first)
end
ALPHA = ('a'..'z').to_a
def rnd5
' '.gsub(' ') { ALPHA.sample }
end
def grapes(n, m)
n.times.each_with_object([]) do |i,a|
s1, s2 = rnd5, rnd5
a << { grape: "%s %s" % [s1, s2] }
a << { grape: i.even? ? s1 : s2 } if i < m
end.shuffle
end
require 'fruity'
def bench(n, m)
(grapes_matched = grapes(n, m)).size
compare do
Max { max_way(grapes_matched) }
Cary { cary_way(grapes_matched) }
end
end
bench 95, 5
Running each test once. Test will take about 1 second.
Cary is faster than Max by 3x ± 1.0
bench 950, 50
Running each test once. Test will take about 13 seconds.
Cary is faster than Max by 3x ± 1.0
bench 950, 500
Running each test once. Test will take about 23 seconds.
Cary is faster than Max by 4x ± 0.1
It can be much shorter:
grapes_founds = grapes_matched.select do |grape_matched|
grapes_matched.none? { |gm| gm[:grape] != grape_matched[:grape] && grape_matched[:grape].include?(gm[:grape]) }
end
In English: select all grapes for which no other grape has a different name which is included in this grape's name.
It's not entirely clear to me what your data structure is and how your strings are normalized so you might have to massage this into exactly the right form.

Add the last index of an array with a unique seperator

I am trying to get the last character of an array to join with it's own character. I'm having trouble trying to figure this out on my own I'm still unfamiliar with built in methods on ruby. Here's where I'm at so far:
def list(names)
#last = names.last
joined = names.map(&:values).flatten.join(", ")
#joined.pop
#joined << last.join(" &")
end
What I want to do is for the last index I want to join it with it's own character. I've tried doing this for hours but I keep getting errors. If anyone can point me in the right direction on this I would greatly appreciate it.
My target goal for an output would be
list([{name: 'Bart'},{name: 'Lisa'},{name: 'Garry'}])
to output:
"Bart, Lisa & Gary"
I suggest creating the string with all names separated by commas (e.g., "Bart, Lisa, Garry") and then replacing the last comma with " &". Here are two ways to do that.
Code
def list1(names)
all_commas(names).tap { |s| s[s.rindex(',')] = ' &' }
end
def list2(names)
all_commas(names).sub(/,(?=[^,]+\z)/, ' &')
end
def all_commas(names)
names.map(&:values).join(', ')
end
Example
names = [{ name: 'Bart' }, { name: 'Lisa' } , { name: 'Garry' }]
list1 names
#=> "Bart, Lisa & Garry"
list2 names
#=> "Bart, Lisa & Garry"
Explanation
The steps are as follows.
For all_commas:
a = names.map(&:values)
#=> [["Bart"], ["Lisa"], ["Garry"]]
a.join(', ')
#=> "Bart, Lisa, Garry"
For list1
s = all_commas(names)
#=> "Bart, Lisa, Garry"
i = s.rindex(',')
#=> 10
s[i] = ' &'
#=> " &"
s #=> "Bart, Lisa & Garry"
tap's block returns s
For list2
a = all_commas(names)
#=> "Bart, Lisa, Garry"
a.sub(/,(?=[^,]+\z)/, ' &')
# => "Bart, Lisa & Garry"
The regular expression, which employs a positive lookahead, reads, "match a comma, followed by one or more characters other than comma, followed by the end of the string".
Here's a solution that yields your desired output, given your input:
def list(names)
*head, tail = names.map(&:values)
[head.join(", "), tail].join(" & ")
end
Enjoy!
Here's a solution with the Oxford comma, which I prefer :)
def list(name_list)
*names, last = name_list.flat_map(&:values)
oxford_comma = names.size > 1 ? ", " : ""
names.join(", ") + oxford_comma + "& #{last}"
end
(Note: if this is Rails, Array#to_sentence does this automatically.)

Append strings to array if found in paragraph using `.match` in Ruby

I'm attempting to search a paragraph for each word in an array, and then output a new array with only the words that could be found.
But I've been unable to get the desired output format so far.
paragraph = "Japan is a stratovolcanic archipelago of 6,852 islands.
The four largest are Honshu, Hokkaido, Kyushu and Shikoku, which make up about ninety-seven percent of Japan's land area.
The country is divided into 47 prefectures in eight regions."
words_to_find = %w[ Japan archipelago fishing country ]
words_found = []
words_to_find.each do |w|
paragraph.match(/#{w}/) ? words_found << w : nil
end
puts words_found
Currently the output I'm getting is a vertical list of printed words.
Japan
archipelago
country
But I would like something like, ['Japan', 'archipelago', 'country'].
I don't have much experience matching text in a paragraph and am not sure what I'm doing wrong here. Could anyone give some guidance?
this is because you are using puts to print the elements of the array . appending "\n" to the end of every element "word":
#!/usr/bin/env ruby
def run_me
paragraph = "Japan is a stratovolcanic archipelago of 6,852 islands.
the four largest are Honshu, Hokkaido, Kyushu and Shikoku, which make up about ninety-seven percent of Japan's land area.
the country is divided into 47 prefectures in eight regions."
words_to_find = %w[ Japan archipelago fishing country ]
find_words_from_a_text_file paragraph , words_to_find
end
def find_words_from_a_text_file( paragraph , *words_to_find )
words_found = []
words_to_find.each do |w|
paragraph.match(/#{w}/) ? words_found << w : nil
end
# print array with enum .
words_found.each { |x| puts "with enum and puts : : #{x}" }
# or just use "print , which does not add anew line"
print "with print :"; print words_found "\n"
# or with p
p words_found
end
run_me
outputs :
za:ruby_dir za$ ./fooscript.rb
with enum and puts : : ["Japan", "archipelago", "fishing", "country"]
with print :[["Japan", "archipelago", "fishing", "country"]]
Here are a couple of ways to do that. Both are case-indifferent.
Use a regular expression
r = /
\b # Match a word break
#{ Regexp.union(words_to_find) } # Match any word in words_to_find
\b # Match a word break
/xi # Free-spacing regex definition mode (x)
# and case-indifferent (i)
#=> /
# \b # Match a word break
# (?-mix:Japan|archipelago|fishing|country) # Match any word in words_to_find
# \b # Match a word break
# /ix # Free-spacing regex definition mode (x)
# and case-indifferent (i)
paragraph.scan(r).uniq(&:itself)
#=> ["Japan", "archipelago", "country"]
Intersect two arrays
words_to_find_hash = words_to_find.each_with_object({}) { |w,h| h[w.downcase] = w }
#=> {"japan"=>"Japan", "archipelago"=>"archipelago", "fishing"=>"fishing",
"country"=>"country"}
words_to_find_hash.values_at(*paragraph.delete(".;:,?'").
downcase.
split.
uniq & words_to_find_hash.keys)
#=> ["Japan", "archipelago", "country"]

Resources