How to loop nested arrays in Ruby - ruby

VERY new to Ruby and coding in general. I'm trying to loop through two dimensional arrays but can't figure it out. Here's what I have:
--Use a loop to print out each person on separate lines with their alter egos.
--Bruce Wayne, a.k.a. Batman
people = [
["Bruce", "Wayne", "Batman"],
["Selina", "Kyle", "Catwoman"],
["Barbara", "Gordon", "Oracle"],
["Terry", "McGinnis", "Batman Beyond"]
]
index = people[0][0]
first_name = people[0][0]
last_name = people[0][1]
hero_name = people[0][2]
4.times do
puts first_name + " " + last_name + "," " " + "a.k.a" " " + hero_name
index = index + 1
end
It does print the first line but then raises an error:
Bruce Wayne, a.k.a Batman
# `+': no implicit conversion of Integer into String (TypeError)

In ruby we don’t use loops by index, like for and family; instead we iterate on collections:
people =
[["Bruce", "Wayne", "Batman"],
["Selina", "Kyle", "Catwoman"],
["Barbara", "Gordon", "Oracle"],
["Terry", "McGinnis", "Batman Beyond"]]
people.each do |first, last, nick|
puts "#{first} #{last}, a.k.a #{nick}"
end
or
people.each do |first_last_nick|
*first_last, nick = first_last_nick
puts [first_last.join(' '), nick].join(', a.k.a ')
end

Your code produces error because you assign a String to index
index = people[0][0]
and then you use it to count with
index = index + 1
You could have used
index = 0
and
index += 1
A more Rubyesque way would be to enumerate the array and print it like this
people.each do |person|
puts "#{person.first} #{person[1]}, a.k.a #{person.last}"
end
Which gives
Bruce Wayne, a.k.a Batman
Selina Kyle, a.k.a Catwoman
Barbara Gordon, a.k.a Oracle
Terry McGinnis, a.k.a Batman Beyond
Storing the parts in a variable improves readability but lenghtens the code which in turn diminishes readability, the choice is yours..
As an alternative you could name the indices or decompose like mudasobwa suggests.
Firstname, Lastname, Nickname = 0, 1, 2
people.each do |person|
puts "#{person[Firstname]} #{person[Lastname]}, a.k.a #{person[Nickname]}"
end

For your code to work:
4.times do |character|
puts people[character][0] + " " + people[character][1] + "," " " + "a.k.a" " " + people[character][2]
end
But iterating in ruby is done as answered by others.
This is a version using a block {} instead:
people = [["Bruce", "Wayne", "Batman"], ["Selina", "Kyle", "Catwoman"], ["Barbara", "Gordon", "Oracle"], ["Terry", "McGinnis", "Batman Beyond"]]
people.each { |character| puts "#{character [0]}, a.k.a #{character [1]} #{character [2]}" }
#=> Bruce, a.k.a Wayne Batman
#=> Selina, a.k.a Kyle Catwoman
#=> Barbara, a.k.a Gordon Oracle
#=> Terry, a.k.a McGinnis Batman Beyond
In general to loop through nested arrays:
people.each do |character|
character.each do |name|
puts name
end
end

Related

How to run a loop on an array, combine the results and set them to a variable

I have this array: ['John', 'Michael', 'Siri']. How can I run an each loop on them and add a text to each "part" of the array and at the end "combine" the result of the loops and set/assign the result of all each to a variable?
By that I mean, I do:
array = ['John', 'Michael', 'Siri']
array.each do |a|
text = "#{a} here"
# Results would need be =>
# John is here
# Michael is here
# Siri is here
#new_string = text # => Which would need to be "John is here Michael is here Siri is here"
end
I have done the code above, but #new_string becomes only Siri is here and if I move the #string out of the loop, like below, it becomes John is here, so basically it takes only one of them and "assigns" it to #new_string.
array = ['John', 'Michael', 'Siri']
array.each do |a|
#text = "#{a} here"
end
#new_string = #text
I tested with [0]+[1]+[2] and it kind of worked, but the problem is that I would not know the size of my array. It can be 2 items or it can be 100 items.
This way
array = ["John", "Michael", "Siri"]
your_variable = array.map { |name| "#{name} is here" }.join(" ")
It's basically a transformation, you want to add something to each element of a collection (use map for that). Lastly, join them up.
Can be done by
array.map { |x| x + ' is here' }.join(' ')
More concisely:
%w{ John Michael Siri}.collect{|s| s+" is here"}.join(" ")
Given the array = ['John', 'Michael', 'Siri'], the problem with your code is that the variable has a scope inside of the loop so it is not accessible after the loop ends.
The solution is to declare the variable before.
#new_string = '' # initialize outside the loop
array.each do |a|
text = "#{a} here "
#new_string += text # note +=
end
#new_string #=> "John here Michael here Siri here "
For the second code, the problem is the same:
#new_string = '' # initialize outside the loop
array = ['John', 'Michael', 'Siri']
array.each do |a|
#new_string += "#{a} here " # note +=
end
#new_string #=> "John here Michael here Siri here "
As you can see string ends with a space, to avoid it populate an array then join as showed in previous answers:
#new_string = [] # initialize outside the loop
array = ['John', 'Michael', 'Siri']
array.each do |a|
#new_string << "#{a} here" # note +=
end
p #new_string = #new_string.join(' ') #=> "John here Michael here Siri here"
Side note:
# comments in ruby starts with #

Ruby: iterating over a hash and formatting the display

Say I have a hash like so:
top_billed = { ghostbusters: 'Bill Murray', star_wars: 'Harrison Ford' }
What would be the best way to format it in a nice, human-readable way?
For example if you called a method on the hash and it displayed the hash as a capitalized list, minus underscores.
"Ghostbusters: Bill Murray
Star Wars: Harrison Ford
I guess iterating over the array and using gsub to remove underscores then capitalizing might work, but I was wondering whether there was anything more elegant.
Thanks
Manually:
top_billed.each do |k, v|
puts k.to_s.gsub("_", " ") + ": " + v
end
if you are using Rails or ActiveSupport, you can also use the "humanize" method (on a String).
Here is a recursive solution:
top_billed = { ghostbusters: { my_name: 'Bill Murray', my_age: 29 }, star_wars: { my_name: 'Harrison Ford' }, something_esle: 'Its Name'}
def print_well(key, value, indent)
key = key.to_s.split('_').map(&:capitalize).join(' ')
if Hash === value
puts "#{key}: "
value.each do |k, v|
print_well k, v, indent + 1
end
else
puts "#{' '*indent}#{key}: #{value}"
end
end
def print_hash hash, indent=0
hash.each do |key, value|
print_well key, value, indent
end
end
print_hash top_billed

How to replace text in a ruby string

I am trying to write a very simple method in Ruby which takes a string and an array of words and checks if the string contains any of the words and if it does it replaces them with their uppercase.
I made an attempt but its not great due to my level of Ruby skills.
def(my_words,my_sentence)
#split the sentence up into an array of words
my_sentence_words = my_sentence.split(/\W+/)
#nested loop that checks the words array for each brand
my_sentence_words.each do |i|
my_words.each do |i|
#if it finds a brand in the words and sets them to be uppercase
if my_words[i] == my_sentence_words[i]
my_sentence_words[i] == my_sentence_words[i].up.case
end
end
end
#put the words array into one string
words.each do |i|
new_sentence = ("" + my_sentence_words[i]) + " "
end
end
I am getting: can't convert string into integer error
def convert(mywords,sentence)
regex = /#{mywords.join("|")}/i
sentence.gsub(regex) { |m| m.upcase }
end
convert(%W{ john james jane }, "I like jane but prefer john")
#=> "I like JANE but prefer JOHN"
This will work better. It loops through the brands, searches for each, and replaces with the uppercase version.
brands = %w(sony toshiba)
sentence = "This is a sony. This is a toshiba."
brands.each do |brand|
sentence.gsub!(/#{brand}/i, brand.upcase)
end
Results in the string.
"This is a SONY. This is a TOSHIBA."
For those who like Ruby foo!
sentence.gsub!(/#{brands.join('|')}/i) { |b| b.upcase }
And in a function
def capitalize_brands(brands, sentence)
sentence.gsub(/#{brands.join('|')}/i) { |b| b.upcase }
end
You get this error because i doesn't start from 0 as you expected, in each method i is an element of array, and has string type, it's a first word from your sentence:
my_sentence_words = ["word"]
my_sentence_words.each do |i|
puts i.length #=> 4
puts i.type #=> String
puts i #=> word
end
So you try to call my_sentence_words[word] instead of my_sentence_words[0]. You can try method each_index that passes index of element instead of element itself`:
def check(str, *arr)
upstr = str.split(' ')
upstr.eachindex do |i| #=> i is index
arr.each_index do |j|
upstr[i].upcase! if upstr[i] == arr[j]
end
end
upstr
end
check("This is my sentence", "day", "is", "goal", "may", "my")
#=>["This", "IS", "MY", "sentence"]

Split a string into parts in efficient way

My code is here
str = "Early in his first term in office, Obama signed into law economic stimulus legislation in response"
arr= str.split(" ")
set_element= arr.each_cons(2).to_a
sub_str = set_element.map {|i| i.join(' ')}
If i have a big string like very big string then this process take 6.50 sec
because i want to this type of result
sub_str= ["Early in", "in his", "his first", "first term", "term in", "in office,", "office, Obama", "Obama signed", "signed into", "into law", "law economic", "economic stimulus", "stimulus legislation", "legislation in", "in response"]
Is it possible any another way with efficient way
Use scan instead of split and you can get your word pairs directly.
s.scan(/\S+(?:\s+\S+)?/)
EDIT: Just to assure myself that this was relatively efficient, I made a little micro-benchmark. Here's results for the answers seen to date:
ruby 1.9.3p125 (2012-02-16 revision 34643) [x86_64-linux]
10 times on string of size 2284879
user system total real
original 4.180000 0.070000 4.250000 ( 4.272856)
sergio 2.090000 0.000000 2.090000 ( 2.102469)
dbenhur 1.050000 0.000000 1.050000 ( 1.042167)
set_element = arr.each_cons(2).to_a
The line above creates a ton of temporary objects that you don't need. Try this, should be faster:
str = "Early in his first term in office, Obama signed into law economic stimulus legislation in response"
arr = str.split(" ")
sub_str = arr.each_with_object([]).with_index do |(el, memo), idx|
if idx % 2 == 0
memo << el
else
memo.last << ' ' << el
end
end
sub_str # => ["Early in", "his first", "term in", "office, Obama", "signed into", "law economic", "stimulus legislation", "in response"]
You can try this. one step less :)
arr= str.scan(/\S+/)
s = []
arr.each_with_index { |x, i| s << (x + " " + arr[i + 1]) if arr[i+1] }

How to get words frequency in efficient way with ruby?

Sample input:
"I was 09809 home -- Yes! yes! You was"
and output:
{ 'yes' => 2, 'was' => 2, 'i' => 1, 'home' => 1, 'you' => 1 }
My code that does not work:
def get_words_f(myStr)
myStr=myStr.downcase.scan(/\w/).to_s;
h = Hash.new(0)
myStr.split.each do |w|
h[w] += 1
end
return h.to_a;
end
print get_words_f('I was 09809 home -- Yes! yes! You was');
This works but I am kinda new to Ruby too. There might be a better solution.
def count_words(string)
words = string.split(' ')
frequency = Hash.new(0)
words.each { |word| frequency[word.downcase] += 1 }
return frequency
end
Instead of .split(' '), you could also do .scan(/\w+/); however, .scan(/\w+/) would separate aren and t in "aren't", while .split(' ') won't.
Output of your example code:
print count_words('I was 09809 home -- Yes! yes! You was');
#{"i"=>1, "was"=>2, "09809"=>1, "home"=>1, "yes"=>2, "you"=>1}
def count_words(string)
string.scan(/\w+/).reduce(Hash.new(0)){|res,w| res[w.downcase]+=1;res}
end
Second variant:
def count_words(string)
string.scan(/\w+/).each_with_object(Hash.new(0)){|w,h| h[w.downcase]+=1}
end
def count_words(string)
Hash[
string.scan(/[a-zA-Z]+/)
.group_by{|word| word.downcase}
.map{|word, words|[word, words.size]}
]
end
puts count_words 'I was 09809 home -- Yes! yes! You was'
This code will ask you for input and then find the word frequency for you:
puts "enter some text man"
text = gets.chomp
words = text.split(" ")
frequencies = Hash.new(0)
words.each { |word| frequencies[word.downcase] += 1 }
frequencies = frequencies.sort_by {|a, b| b}
frequencies.reverse!
frequencies.each do |word, frequency|
puts word + " " + frequency.to_s
end
This works, and ignores the numbers:
def get_words(my_str)
my_str = my_str.scan(/\w+/)
h = Hash.new(0)
my_str.each do |s|
s = s.downcase
if s !~ /^[0-9]*\.?[0-9]+$/
h[s] += 1
end
end
return h
end
print get_words('I was there 1000 !')
puts '\n'
You can look at my code that splits the text into words. The basic code would look as follows:
sentence = "Ala ma kota za 5zł i 10$."
splitter = SRX::Polish::WordSplitter.new(sentence)
histogram = Hash.new(0)
splitter.each do |word,type|
histogram[word.downcase] += 1 if type == :word
end
p histogram
You should be careful if you wish to work with languages other than English, since in Ruby 1.9 the downcase won't work as you expected for letters such as 'Ł'.
class String
def frequency
self.scan(/[a-zA-Z]+/).each.with_object(Hash.new(0)) do |word, hash|
hash[word.downcase] += 1
end
end
end
puts "I was 09809 home -- Yes! yes! You was".frequency

Resources