I am doing a coding bootcamp and into week-2 and have learnt loop, methods, a bit of classes. I have come across with this coding challenge.
I can put a frame around the sentence I put in def method. I would like to know how I can put a frame around user input words. I have tried, but am getting an error.
Ultimately, what I want to achieve is to delete words = %w(This restaurant has an excellent menu - sushi ramen okonomiyaki.) from def method.
Thank you!!
puts "Welcome to frame with words."
puts "Enter your favourite quote or sentence or any word you like"
words = gets.chomp.to_s
def my_favourite words=[]
words = %w(This restaurant has an excellent menu - sushi ramen okonomiyaki.)
longest = 0
words.each {|word| longest = word.length if longest < word.length }
(0..longest+3).each {print "*"}
print "\n"
words.each do |word|
print "* "
print word
(0..longest-word.length).each { print " " }
print"*\n"
end
(0..longest+3).each {print"*" }
return
end
my_favourite
The type of output I want is below.
* This *
* restaurant *
* has *
* an *
* excellent *
* menu *
* - *
* sushi *
* ramen *
* okonomiyaki. *
**************** ```
Suppose
str = gets.chomp
#=> "This restaurant has an excellent menu - sushi ramen."
then
words = str.split
#=> ["This", "restaurant", "has", "an", "excellent", "menu", "-",
# "sushi", "ramen."]
width = words.max_by(&:size).size
#=> 10
top_bot = '*' * (width+4)
#=> "**************"
puts top_bot
words.each { |word| puts "* %-#{width}s *" % word }
puts top_bot
displays:
**************
* This *
* restaurant *
* has *
* an *
* excellent *
* menu *
* - *
* sushi *
* ramen. *
**************
words.max_by(&:size) is shorthand for:
words.max_by { |word| word.size }
#=> "restaurant"
See the doc Kernel#sprint for an explanation of the formatting codes in "* %-#{width}s *" % word. After #{width} is substitute out this becomes "* %-10s *" % word. s specifies that word is a string. 10 means it should occupy a field of width 10. - means that the string should be left-adjusted in the field. The field of width 10 is preceded by "* " and followed by " *", forming a string of width 14.
This could instead be (equivalently) written:
word = "balloon"
sprintf("* %-#{width}s *", word)
#=> "* balloon *"
See also String#split, Enumerable#max_by and String#*.
Related
I have a class MoneyBox with two fields (wallet and cent). I need to overload operations +, -, and * with these fields. How to correctly implement the overloading of these operators with two parameters?
class MoneyBox
attr_accessor :wallet, :cent
def initialize(wallet, cent)
#wallet = wallet
#cent = cent
end
def + (obj, obj_cent)
self.wallet = self.wallet + obj.wallet
self.cent = self.cent + obj_cent.cent
return self.wallet, self.cent
end
def - (obj, obj_cent)
self.wallet = self.wallet - obj.wallet
self.cent = self.cent - obj_cent.cent
return self.wallet, self.cent
end
def * (obj,obj_cent)
self.wallet = self.wallet * obj.wallet
self.cent = self.cent * obj_cent
return self.wallet, self.cent
end
end
It should be something like this:
Cash1 = MoneyBox.new(500, 30)
Cash2 = MoneyBox.new(100, 15)
puts " D1 = #{D1 = (Cash1-(Cash2*2))}" # 500,30 - 100,15*2 = 300,0
You already have a class that encapsulates wallet / cent pairs and the operations are also performed pair-wise. Why not take advantage of it and make +, - and * take a (single) MoneyBox instance as their argument and return the result as another MoneyBox instance, e.g.: (arithmetic operators shouldn't modify their operands)
class MoneyBox
attr_accessor :wallet, :cent
def initialize(wallet, cent)
#wallet = wallet
#cent = cent
end
def +(other)
MoneyBox.new(wallet + other.wallet, cent + other.cent)
end
def -(other)
MoneyBox.new(wallet - other.wallet, cent - other.cent)
end
def *(other)
MoneyBox.new(wallet * other.wallet, cent * other.cent)
end
def to_s
"#{wallet},#{cent}"
end
end
Example usage:
cash1 = MoneyBox.new(500, 30)
cash2 = MoneyBox.new(100, 15)
puts "#{cash1} + #{cash2} = #{cash1 + cash2}"
# 500,30 + 100,15 = 600,45
puts "#{cash1} - #{cash2} = #{cash1 - cash2}"
# 500,30 - 100,15 = 400,15
puts "#{cash1} * #{cash2} = #{cash1 * cash2}"
# 500,30 * 100,15 = 50000,45
To multiply both wallet and cents by 2 you'd use a MoneyBox.new(2, 2) instance:
puts "#{cash1} - #{cash2} * 2 = #{cash1 - cash2 * MoneyBox.new(2, 2)}"
# 500,30 - 100,15 * 2 = 300,0
Note that operator precedence still applies, so the result is evaluated as cash1 - (cash2 * Money.new(2, 2)).
If you want to multiply by integers directly, i.e. without explicitly creating that MoneyBox.new(2, 2) instance, you could move that logic into * by adding a conditional, e.g:
def *(other)
case other
when Integer
MoneyBox.new(wallet * other, cent * other)
when MoneyBox
MoneyBox.new(wallet * other.wallet, cent * other.cent)
else
raise ArgumentError, "expected Integer or MoneyBox, got #{other.class}"
end
end
Which gives you:
cash1 = MoneyBox.new(500, 30)
cash2 = MoneyBox.new(100, 15)
puts "#{cash1} - #{cash2} * 2 = #{cash1 - cash2 * 2}"
# 500,30 - 100,15 * 2 = 300,0
Note that this only defines MoneyBox#* and doesn't alter Integer#*, so 2 * cash2 does not work by default:
2 * cash2
# TypeError: MoneyBox can't be coerced into Integer
That's because you're calling * on 2 and Integer doesn't know how to deal with a MoneyBox instance. This could be fixed by implementing coerce in MoneyBox: (something that only works for numerics)
def coerce(other)
[self, other]
end
Which effectively turns 2 * cash2 into cash2 * 2.
BTW if you always call * with an integer and there's no need to actually multiply two MoneyBox instances, it would of course get much simpler:
def *(factor)
MoneyBox.new(wallet * factor, cent * factor)
end
You can't use the typical x + y syntax if you're going to overload the operator to take two arguments.
Instead, you have to use .+, see the following:
class Foo
def initialize(val)
#val = val
end
def +(a,b)
#val + a + b
end
end
foo = Foo.new(1)
foo.+(2,3)
# => 6
Your example usage does not match the method definition. You want to use your objects in the way to write expressions like i.e. Cash2 * 2). Since Cash2 is a constant of class MoneyBox, the definition for the multiplication by a scalar would be something like
def * (factor)
self.class.new(wallet, cent * factor)
end
This is a shortcut of
def * (factor)
MoneyBox.new(self.wallet, self.cent * factor)
end
This is a super basic ruby question. Just learning ruby and I'm trying to build out a box where it finds the longest word in the string and makes the box that width with spaces on either side and then centers all the other text i've passed in.
so far i have this;
def box(str)
arr = str.split
wordlength = arr.max_by(&:length).length
width = wordlength + 4
width.times{print "*"}
puts "\n"
arr.each {|i| puts "* #{i} *" }
width.times{print "*"}
end
but the above prints out:
***********
* texting *
* stuff *
* test *
* text *
***********
i'd like it to print something like the below
***********
* texting *
* stuff *
* test *
* text *
***********
thanks
Here, this code works:
def box(str)
arr = str.split
wordlength = arr.max_by(&:length).length
width = wordlength + 4
width.times{print "*"}
puts "\n"
arr.each do |i|
current_length = i.length
puts "* #{fill_space(current_length, wordlength, 'pre', i)}#{i}#{fill_space(current_length, wordlength, 'post')} *"
end
width.times{print "*"}
end
def fill_space(current_length, max_length, where, current='')
spaces_to_fill = max_length - current_length
if where == 'pre'
str = ' ' * (spaces_to_fill / 2)
elsif spaces_to_fill % 2 > 0
str = ' ' * (spaces_to_fill / 2 + 1)
else
str = ' ' * (spaces_to_fill / 2)
end
end
The problem was,that you didn't calculate how many " " you should insert into your current row. In the fill_space function I calculate exactly that, and I call this function for each row that should be printed. Also, if there are odd words this function adds the additional space in the end of the row.
I didn't change much of your box function but feel free to insert the hint that Keith gave you
I'm having some trouble trying to find an appropriate method for string substitution. I would like to replace every character in a string 'except' for a selection of words or set of string (provided in an array). I know there's a gsub method, but I guess what I'm trying to achieve is its reverse. For example...
My string: "Part of this string needs to be substituted"
Keywords: ["this string", "substituted"]
Desired output: "**** ** this string ***** ** ** substituted"
ps. It's my first question ever, so your help will be greatly appreciated!
Here's a different approach. First, do the reverse of what you ultimately want: redact what you want to keep. Then compare this redacted string to your original character by character, and if the characters are the same, redact, and if they are not, keep the original.
class String
# Returns a string with all words except those passed in as keepers
# redacted.
#
# "Part of this string needs to be substituted".gsub_except(["this string", "substituted"], '*')
# # => "**** ** this string ***** ** ** substituted"
def gsub_except keep, mark
reverse_keep = self.dup
keep.each_with_object(Hash.new(0)) { |e, a| a[e] = mark * e.length }
.each { |word, redacted| reverse_keep.gsub! word, redacted }
reverse_keep.chars.zip(self.chars).map do |redacted, original|
redacted == original && original != ' ' ? mark : original
end.join
end
end
You can use something like:
str="Part of this string needs to be substituted"
keep = ["this","string", "substituted"]
str.split(" ").map{|word| keep.include?(word) ? word : word.split("").map{|w| "*"}.join}.join(" ")
but this will work only to keep words, not phrases.
This might be a little more understandable than my last answer:
s = "Part of this string needs to be substituted"
k = ["this string", "substituted"]
tmp = s
for(key in k) {
tmp = tmp.replace(k[key], function(x){ return "*".repeat(x.length)})
}
res = s.split("")
for(charIdx in s) {
if(tmp[charIdx] != "*" && tmp[charIdx] != " ") {
res[charIdx] = "*"
} else {
res[charIdx] = s.charAt(charIdx)
}
}
var finalResult = res.join("")
Explanation:
This goes off of my previous idea about using where the keywords are in order to replace portions of the string with stars. First off:
For each of the keywords we replace it with stars, of the same length as it. So:
s.replace("this string", function(x){
return "*".repeat(x.length)
}
replaces the portion of s that matches "this string" with x.length *'s
We do this for each key, for completeness, you should make sure that the replace is global and not just the first match found. /this string/g, I didn't do it in the answer, but I think you should be able to figure out how to use new RegExp by yourself.
Next up, we split a copy of the original string into an array. If you're a visual person, it should make sense to think of this as a weird sort of character addition:
"Part of this string needs to be substituted"
"Part of *********** needs to be substituted" +
---------------------------------------------
**** ** this string ***** ** ** ***********
is what we're going for. So if our tmp variable has stars, then we want to bring over the original string, and otherwise we want to replace the character with a *
This is easily done with an if statement. And to make it like your example in the question, we also bring over the original character if it's a space. Lastly, we join the array back into a string via .join("") so that you can work with a string again.
Makes sense?
You can use the following approach: collect the substrings that you need to turn into asterisks, and then perform this replacement:
str="Part of this string needs to be substituted"
arr = ["this string", "substituted"]
arr_to_remove = str.split(Regexp.new("\\b(?:" + arr.map { |x| Regexp.escape(x) }.join('|') + ")\\b|\\s+")).reject { |s| s.empty? }
arr_to_remove.each do |s|
str = str.gsub(s, "*" * s.length)
end
puts str
Output of the demo program:
**** ** this string ***** ** ** substituted
str = "Part of this string needs to be substituted"
keywords = ["this string", "substituted"]
pattern = /(#{keywords.join('|')})/
str.split(pattern).map {|i| keywords.include?(i) ? i : i.gsub(/\S/,"*")}.join
#=> "**** ** this string ***** ** ** substituted"
A more readable version of the same code
str = "Part of this string needs to be substituted"
keywords = ["this string", "substituted"]
#Use regexp pattern to split string around keywords.
pattern = /(#{keywords.join('|')})/ #pattern => /(this string|substituted)/
str = str.split(pattern) #=> ["Part of ", "this string", " needs to be ", "substituted"]
redacted = str.map do |i|
if keywords.include?(i)
i
else
i.gsub(/\S/,"*") # replace all non-whitespace characters with "*"
end
end
# redacted => ["**** **", "this string", "***** ** **", "substituted"]
redacted.join
You can do that using the form of String#split that uses a regex with a capture group.
Code
def sub_some(str, keywords)
str.split(/(#{keywords.join('|')})/)
.map {|s| keywords.include?(s) ? s : s.gsub(/./) {|c| (c==' ') ? c : '*'}}
.join
end
Example
str = "Part of this string needs to be substituted"
keywords = ["this string", "substituted"]
sub_some(str, keywords)
#=> "**** ** this string ***** ** ** substituted"
Explanation
r = /(#{keywords.join('|')})/
#=> /(this string|substituted)/
a = str.split(r)
#=> ["Part of ", "this string", " needs to be ", "substituted"]
e = a.map
#=> #<Enumerator: ["Part of ", "this string", " needs to be ",
# "substituted"]:map>
s = e.next
#=> "Part of "
keywords.include?(s) ? s : s.gsub(/./) { |c| (c==' ') ? c : '*' }
#=> s.gsub(/./) { |c| (c==' ') ? c : '*' }
#=> "Part of "gsub(/./) { |c| (c==' ') ? c : '*' }
#=> "**** ** "
s = e.next
keywords.include?(s) ? s : s.gsub(/./) { |c| (c==' ') ? c : '*' }
#=> "this string"
keywords.include?(s) ? s : s.gsub(/./) { |c| (c==' ') ? c : '*' }
#=> s
#=> "this string"
and so on... Lastly,
["**** ** ", "this string", " ***** ** ** ", "substituted"].join('|')
#=> "**** ** this string ***** ** ** substituted"
Note that, prior to v.1.9.3, Enumerable#map did not return an enumerator when no block is given. The calculations are the same, however.
My question is about how to convert array elements to string in ruby 1.9 without getting the brackets and quotation marks. I've got an array (DB extract), from which I want to use to create a periodic report.
myArray = ["Apple", "Pear", "Banana", "2", "15", "12"]
In ruby 1.8 I had the following line
reportStr = "In the first quarter we sold " + myArray[3].to_s + " " + myArray[0].to_s + "(s)."
puts reportStr
Which produced the (wanted) output
In the first quarter we sold 2 Apple(s).
The same two lines in ruby 1.9 produce (not wanted)
In the first quarter we sold ["2"] ["Apple"] (s).
After reading in the documentation
Ruby 1.9.3 doc#Array#slice
I thought I could produce code like
reportStr = "In the first quarter we sold " + myArray[3] + " " + myArray[0] + "(s)."
puts reportStr
which returns a runtime error
/home/test/example.rb:450:in `+': can't convert Array into String (TypeError)
My current solution is to remove brackets and quotation marks with a temporary string, like
tempStr0 = myArray[0].to_s
myLength = tempStr0.length
tempStr0 = tempStr0[2..myLength-3]
tempStr3 = myArray[3].to_s
myLength = tempStr3.length
tempStr3 = tempStr3[2..myLength-3]
reportStr = "In the first quarter we sold " + tempStr3 + " " + tempStr0 + "(s)."
puts reportStr
which in general works.
However, what would be a more elegant "ruby" way how to do that?
You can use the .join method.
For example:
my_array = ["Apple", "Pear", "Banana"]
my_array.join(', ') # returns string separating array elements with arg to `join`
=> Apple, Pear, Banana
Use interpolation instead of concatenation:
reportStr = "In the first quarter we sold #{myArray[3]} #{myArray[0]}(s)."
It's more idiomatic, more efficient, requires less typing and automatically calls to_s for you.
And if you need to do this for more than one fruit the best way is to transform the array and the use the each statement.
myArray = ["Apple", "Pear", "Banana", "2", "1", "12"]
num_of_products = 3
tranformed = myArray.each_slice(num_of_products).to_a.transpose
p tranformed #=> [["Apple", "2"], ["Pear", "1"], ["Banana", "12"]]
tranformed.each do |fruit, amount|
puts "In the first quarter we sold #{amount} #{fruit}#{amount=='1' ? '':'s'}."
end
#=>
#In the first quarter we sold 2 Apples.
#In the first quarter we sold 1 Pear.
#In the first quarter we sold 12 Bananas.
You can think of this as arrayToString()
array = array * " "
E.g.,
myArray = ["One.","_1_?! Really?!","Yes!"]
=> "One.","_1_?! Really?!","Yes!"
myArray = myArray * " "
=> "One. _1_?! Really?! Yes."
I know in ruby you can search and replace a string using gsub and a regular expression.
However is there away to apply an expression to the result of the search and replace before it is replaced.
For example in the code below though you can use the matched string with \0 you cannot apply expressions to it (eg \0.to_i * 10) and doing so results in an error:
shopping_list = <<LIST
3 apples
500g flour
1 ham
LIST
new_list = shopping_list.gsub(/\d+/m, \0.to_i * 10)
puts new_list #syntax error, unexpected $undefined
It seems to only work with a string literal:
shopping_list = <<LIST
3 apples
500g flour
1 ham
LIST
new_list = shopping_list.gsub(/\d+/m, '\0 string and replace')
puts new_list
Is this what you want?
shopping_list = <<LIST
3 apples
500g flour
1 ham
LIST
new_list = shopping_list.gsub(/\d+/m) do |m|
m.to_i * 10
end
puts new_list
# >> 30 apples
# >> 5000g flour
# >> 10 ham
Documentation: String#gsub.