When trying to multiply with a method I get this output ஸ - ruby-on-rails

Ruby Noob here - Trying to make a very simple appointment booking form that outputs a confirmation and the amount of time the appointment will take. I've gotten the concatenation working on the output but I keep getting this character (ஸ) where the amount of time should be. Below is my ruby document and the output.
print "Whats your name?"
name = gets.to_s
print "What is the address for your listing?"
appointment_address = gets
print "Square footage?"
sq_ft = gets.to_i
print "listing price"
listing_price = gets
# PHOTOGRAPHERS
def tps
tps = 3.to_i
end
def ryan(sq_ft,tps)
p sq_ft.to_i * tps.to_i
end
appointment_confirmation = 'Hey, '<< name.to_s.strip << '! Your appointment at ' << appointment_address.to_s.strip << ' will take us about ' << sq_ft*tps << ' to complete.'
p appointment_confirmation.strip
Output:
Hey, Alex! Your appointment at 102 Alex will take us about ஸ to complete.

When you add an integer to a string, the integer is converted into its corresponding character code. Cast your integer to a string:
' will take us about ' << (sq_ft*tps).to_s << ' to com ...'

You are appending an integer to a string. The integer is treated as character code.
The following prints our the letters A-Z because 65 is the code of "A" and 90 the code of "Z".
s = ""
(65..90).each do |ascii_code|
s << ascii_code
end
puts s
Either convert the amount to a string or (and this is my preferred way) use string interpolation:
"Hey, #{name} the duration is #{sq_ft*tps}"
Where #{} can be used to interpolate ruby values into a string. This only works with double quoted strings.

Related

How to resolve this error erb is throwing when passing a currency formatted string

I'm using a module within my Rails App to perform some actions and render a html file and save it to S3. So far so good, apart from the fact that I need to pass a currency variable to to be rendered and erb is throwing this error:
undefined method `/' for "3,395,000":String
Here's my code:
options = {
...
price: Money.new(#case.cash_price / 100.to_i, "DKK").format.to_s.html_safe,
...
}
And here's my module:
def generate_html(options)
require 'erb'
erb_file = "templates/banners/widesky.html.erb"
erb_str = File.read(erb_file)
...
#price = options[:price]
...
renderer = ERB.new(erb_str)
result = renderer.result(binding)
FileUtils.mkdir_p('temp') unless File.directory?('temp')
File.open('temp/index.html', 'w') do |f|
f.write(result)
end
'temp/index.html'
end
And I tried formatting the currency in different ways, but I always get the same error. Any ideas why?
EDIT
#case.cash_price originally is an Integer. I want to convert it to a string with commas (hence using Money to format it). The problem seems to be that erb doesn't like the formatted result and throw the above error.
If for some reason you cannot use any gem/helper, let's reinvent the wheel!
def to_currency(price_in_cents, currency=nil, decimal_separator = '.', thousand_separator = ',')
price_in_cents.to_s.rjust(3,'0').reverse.insert(2,decimal_separator).gsub(/(\d{3})(?=\d)/, '\1'+thousand_separator).reverse+(currency ? " #{currency}" : '')
end
puts to_currency(123456789, 'DKK')
puts to_currency(123456, '€', ',', ' ')
puts to_currency(1)
It outputs :
1,234,567.89 DKK
1 234,56 €
0.01
Note that price_in_cents should be either a String that looks like an Integer ("123456789") or an Integer (123456789), but not a preformatted String ("123,456.78") or a Float (1.23).
Finally, the resulting String is as unsafe as price_in_cents :
to_currency("unsafe_codejs")
=> "unsafe_code.js"
You don't have to specify html_safe on the result anyway, because nothing would be escaped in "1,234,567.89 DKK".
Original answer :
If cash_price is a String with commas, you need to remove the commas first, then convert it to a float, then divide by 100, and then convert the result to an Integer.
cash_price.to_s is to avoid getting errors if cash_price does come as a Numeric.
price: Money.new((#case.cash_price.to_s.delete(',').to_f/100).to_i, "DKK").format.to_s.html_safe
#case.cash_price is a string so you can't perform any mathematical operations on it. You would need to convert the value to an integer (3395000) rather than a comma delimited string as you currently have ('3,395,000').
A side note, 100.to_i is redundant as 100 is already an integer, unless you wanted to convert the equation to an integer, which would need brackets (#case.cash_price / 100).to_i.

Determining length of array of strings ignoring commas/periods (letters only)

I'm trying to find the best way to determine the letter count in an array of strings. I'm splitting the string, and then looping every word, then splitting letters and looping those letters.
When I get to the point where I determine the length, the problem I have is that it's counting commas and periods too. Thus, the length in terms of letters only is inaccurate.
I know this may be a lot shorter with regex, but I'm not well versed on that yet. My code is passing most tests, but I'm stuck where it counts commas.
E.g. 'You,' should be string.length = "3"
Sample code:
def abbr(str)
new_words = []
str.split.each do |word|
new_word = []
word.split("-").each do |w| # it has to be able to handle hyphenated words as well
letters = w.split('')
if letters.length >= 4
first_letter = letters.shift
last_letter = letters.pop
new_word << "#{first_letter}#{letters.count}#{last_letter}"
else
new_word << w
end
end
new_words << new_word.join('-')
end
new_words.join(' ')
I tried doing gsub before looping the words, but that wouldn't work because I don't want to completely remove the commas. I just don't need them to be counted.
Any enlightenment is appreciated.
arr = ["Now is the time for y'all Rubiests",
"to come to the aid of your bowling team."]
arr.join.size
#=> 74
Without a regex
def abbr(arr)
str = arr.join
str.size - str.delete([*('a'..'z')].join + [*('A'..'Z')].join).size
end
abbr arr
#=> 58
Here and below, arr.join converts the array to a single string.
With a regex
R = /
[^a-z] # match a character that is not a lower-case letter
/ix # case-insenstive (i) and free-spacing regex definition (x) modes
def abbr(arr)
arr.join.gsub(R,'').size
end
abbr arr
#=> 58
You could of course write:
arr.join.gsub(/[^a-z]/i,'').size
#=> 58
Try this:
def abbr(str)
str.gsub /\b\w+\b/ do |word|
if word.length >= 4
"#{word[0]}#{word.length - 2}#{word[-1]}"
else
word
end
end
end
The regex in the gsub call says "one or more word characters preceded and followed by a word boundary". The block passed to gsub operates on each word, the return from the block is the replacement for the 'word' match in gsub.
You can check for each character that whether its ascii value lies in 97-122 or 65-90.When this condition is fulfilled increment a local variable that will give you total length of string without any number or any special character or any white space.
You can use something like that (short version):
a.map { |x| x.chars.reject { |char| [' ', ',', '.'].include? char } }
Long version with explanation:
a = ['a, ', 'bbb', 'c c, .'] # Initial array of strings
excluded_chars = [' ', ',', '.'] # Chars you don't want to be counted
a.map do |str| # Iterate through all strings in array
str.chars.reject do |char| # Split each string to the array of chars
excluded_chars.include? char # and reject excluded_chars from it
end.size # This returns [["a"], ["b", "b", "b"], ["c", "c"]]
end # so, we take #size of each array to get size of the string
# Result: [1, 3, 2]

Truncate sentences in rails?

I've got a helper that I'm using to truncate strings in Rails, and it works great when I truncate sentences that end in periods. How should I modify the code to also truncate sentences when they end in question marks or exclamation points?
def smart_truncate(s, opts = {})
opts = {:words => 12}.merge(opts)
if opts[:sentences]
return s.split(/\.(\s|$)+/).reject{ |s| s.strip.empty? }[0, opts[:sentences]].map{|s| s.strip}.join('. ') + '...'
end
a = s.split(/\s/) # or /[ ]+/ to only split on spaces
n = opts[:words]
a[0...n].join(' ') + (a.size > n ? '... (more)' : '')
end
Thanks!!!
You have the truncate method
'Once upon a time in a world far far away'.truncate(27, separator: /\s/, ommission: "....")
which will return "Once upon a time in a..."
And if you need to truncate by number of words instead then use the newly introduced truncate_words (since Rails 4.2.2)
'And they found that many people were sleeping better.'.truncate_words(5, omission: '... (continued)')
which returns
"And they found that many... (continued)"

Ruby: truncating a long string contained in another string

Let string_a = "I'm a string but this aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa is long."
How is it possible to detect the aaaa...aaa (the part without spaces that is very long) part and truncate it so that the result looks like (the rest of the string should look the same):
puts string_a # => "I'm a string but this aaa....aaa is long."
I use this method to truncate:
def truncate_string(string_x)
splits = string_x.scan(/(.{0,9})(.*?)(.{0,9}$)/)[0]
splits[1] = "..." if splits[1].length > 3
splits.join
end
So running:
puts truncate_string("I'm a very long long long string") # => "I'm a ver...ng string"
The problem is detecting the 'aaaa...aaaa' and apply truncate_string to it.
First part of the solution? Detecting strings that are longer than N using regex?
What about something like
string.gsub(/\S{10,}/) { |x| "#{x[0..3]}...#{x[-3..-1]}" }
where 10 is the maximum length of a word?
how do you like this?
s = "I'm a string but this aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa is long."
s.gsub(/(.* \w{3})\w{5,}(\w{3}.*)/, '\1...\2')
=> "I'm a string but this aaa...aaa is long."

What's the fastest way to check if a word from one string is in another string?

I have a string of words; let's call them bad:
bad = "foo bar baz"
I can keep this string as a whitespace separated string, or as a list:
bad = bad.split(" ");
If I have another string, like so:
str = "This is my first foo string"
What's the fasted way to check if any word from the bad string is within my comparison string, and what's the fastest way to remove said word if it's found?
#Find if a word is there
bad.split(" ").each do |word|
found = str.include?(word)
end
#Remove the word
bad.split(" ").each do |word|
str.gsub!(/#{word}/, "")
end
If the list of bad words gets huge, a hash is a lot faster:
require 'benchmark'
bad = ('aaa'..'zzz').to_a # 17576 words
str= "What's the fasted way to check if any word from the bad string is within my "
str += "comparison string, and what's the fastest way to remove said word if it's "
str += "found"
str *= 10
badex = /\b(#{bad.join('|')})\b/i
bad_hash = {}
bad.each{|w| bad_hash[w] = true}
n = 10
Benchmark.bm(10) do |x|
x.report('regex:') {n.times do
str.gsub(badex,'').squeeze(' ')
end}
x.report('hash:') {n.times do
str.gsub(/\b\w+\b/){|word| bad_hash[word] ? '': word}.squeeze(' ')
end}
end
user system total real
regex: 10.485000 0.000000 10.485000 ( 13.312500)
hash: 0.000000 0.000000 0.000000 ( 0.000000)
bad = "foo bar baz"
=> "foo bar baz"
str = "This is my first foo string"
=> "This is my first foo string"
(str.split(' ') - bad.split(' ')).join(' ')
=> "This is my first string"
All the solutions have problems with catching the bad words if the case does not match. The regex solution is easiest to fix by adding the ignore-case flag:
badex = /\b(#{bad.split.join('|')})\b/i
In addition, using "String".include?(" String ") will lead to boundary problems with the first and last words in the string or strings where the target words have punctuation or are hyphenated. Testing for those situations will result in a lot of other code being needed. Because of that I think the regex solution is the best one. It is not the fastest but it is going to be more flexible right out of the box, and, if the other algorithms are tweaked to handle case folding and compound-words the regex solution might pull ahead.
#!/usr/bin/ruby
require 'benchmark'
bad = 'foo bar baz comparison'
badex = /\b(#{bad.split.join('|')})\b/i
str = "What's the fasted way to check if any word from the bad string is within my comparison string, and what's the fastest way to remove said word if it's found?" * 10
n = 10_000
Benchmark.bm(20) do |x|
x.report('regex:') do
n.times { str.gsub(badex,'').gsub(' ',' ') }
end
x.report('regex with squeeze:') do
n.times{ str.gsub(badex,'').squeeze(' ') }
end
x.report('array subtraction') do
n.times { (str.split(' ') - bad.split(' ')).join(' ') }
end
end
I made the str variable a lot longer, to make the routines work a bit harder.
user system total real
regex: 0.740000 0.010000 0.750000 ( 0.752846)
regex with squeeze: 0.570000 0.000000 0.570000 ( 0.581304)
array subtraction 1.430000 0.010000 1.440000 ( 1.449578)
Doh!, I'm too used to how other languages handle their benchmarks. Now I got it working and looking better!
Just a little comment about what it looks like the OP is trying to do: Black-listed word removal is easy to fool, and a pain to keep maintained. L33t-sp34k makes it trivial to sneek words through. Depending on the application, people will consider it a game to find ways to push offensive words past the filtering. The best solution I found when I was asked to work on this, was to create a generator that would create all the variations on a word and dump them into a database where some process could check as soon as possible, rather than in real time. A million small strings being checked can take a while if you are searching through a long list of offensive words; I'm sure we could come up with quite a list of things that someone would find offensive, but that's an exercise for a different day.
I haven't seen anything similar in Ruby to Perl's Regexp::Assemble, but that was a good way to go after this sort of problem. You can pass an array of words, plus options for case-folding and word-boundaries, and it will spit out a regex pattern that will match all the words, with their commonalities considered to result in the smallest pattern that will match all words in the list. The problem after that is locating which word in the original string matched the hits found by the pattern, so they can be removed. Differences in word case and hits within compound-words makes that replacement more interesting.
And we won't even go into words that are benign or offensive depending on the context.
I added a bit more comprehensive test for the array-subtraction benchmark, to fit how it would need to work in a real piece of code. The if clause is specified in the answer, this now reflects it:
#!/usr/bin/env ruby
require 'benchmark'
bad = 'foo bar baz comparison'
badex = /\b(#{bad.split.join('|')})\b/i
str = "What's the fasted way to check if any word from the bad string is within my comparison string, and what's the fastest way to remove said word if it's found?" * 10
str_split = str.split
bad_split = bad.split
n = 10_000
Benchmark.bm(20) do |x|
x.report('regex') do
n.times { str.gsub(badex,'').gsub(' ',' ') }
end
x.report('regex with squeeze') do
n.times{ str.gsub(badex,'').squeeze(' ') }
end
x.report('bad.any?') do
n.times {
if (bad_split.any? { |bw| str.include?(bw) })
(str_split - bad_split).join(' ')
end
}
end
x.report('array subtraction') do
n.times { (str_split - bad_split).join(' ') }
end
end
with two test runs:
ruby test.rb
user system total real
regex 1.000000 0.010000 1.010000 ( 1.001093)
regex with squeeze 0.870000 0.000000 0.870000 ( 0.873224)
bad.any? 1.760000 0.000000 1.760000 ( 1.762195)
array subtraction 1.350000 0.000000 1.350000 ( 1.346043)
ruby test.rb
user system total real
regex 1.000000 0.010000 1.010000 ( 1.004365)
regex with squeeze 0.870000 0.000000 0.870000 ( 0.868525)
bad.any? 1.770000 0.000000 1.770000 ( 1.775567)
array subtraction 1.360000 0.000000 1.360000 ( 1.359100)
I usually make a point of not optimizing without measurements, but here's a wag:
To make it fast, you should iterate through each string once. You want to avoid a loop with bad count * str count inner compares. So, you could build a big regexp and gsub with it.
(adding foo variants to test word boundary works)
str = "This is my first foo fooo ofoo string"
=> "This is my first foo fooo ofoo string"
badex = /\b(#{bad.split.join('|')})\b/
=> /\b(foo|bar|baz)\b/
str.gsub(badex,'').gsub(' ',' ')
=> "This is my first fooo ofoo string"
Of course the huge resulting regexp might be as slow as the implied nested iteration in my other answer. Only way to know is to measure.
bad = %w(foo bar baz)
str = "This is my first foo string"
# find the first word in the list
found = bad.find {|word| str.include?(word)}
# remove it
str[found] = '' ;# str => "This is my first string"
I'd benchmark this:
bad = "foo bar baz".split(' ')
str = "This is my first foo string".split(' ')
# 1. What's the fasted way to check if any word from the bad string is within my comparison string
p bad.any? { |bw| str.include?(bw) }
# 2. What's the fastest way to remove said word if it's found?
p (str - bad).join(' ')
any? will quick checking as soon as it sees a match. If you can order your bad words by their probability, you can save some cycles.
Here's one that will check for words and phrases.
def checkContent(str)
bad = ["foo", "bar", "this place sucks", "or whatever"]
# may be best to map and singularize everything as well.
# maybe add some regex to catch those pesky, "How i make $69 dollars each second online..."
# maybe apply some comparison stuff to check for weird characters in those pesky, "How i m4ke $69 $ollars an hour"
bad_hash = {}
bad_phrase_hash = {}
bad.map(&:downcase).each do |word|
words = word.split().map(&:downcase)
if words.length > 1
words.each do |inner|
if bad_hash.key?(inner)
if bad_hash[inner].is_a?(Hash) && !bad_hash[inner].key?(words.length)
bad_hash[inner][words.length] = true
elsif bad_hash[inner] === 1
bad_hash[inner] = {1=>true,words.length => true}
end
else
bad_hash[inner] = {words.length => true}
end
end
bad_phrase_hash[word] = true
else
bad_hash[word] = 1
end
end
string = str.split().map(&:downcase)
string.each_with_index do |word,index|
if bad_hash.key?(word)
if bad_hash[word].is_a?(Hash)
if bad_hash[word].key?(1)
return false
else
bad_hash[word].keys.sort.each do |length|
value = string[index...(index + length)].join(" ")
if bad_phrase_hash.key?(value)
return false
end
end
end
else
return false
end
end
end
return true
end
The include? method is what you need. The ruby String specificacion says:
str.include?( string ) -> true or false
Returns true if str contains the given string or character.
"hello".include? "lo" -> true
"hello".include? "ol" -> false
"hello".include? ?h -> true
Note that it has O(n) and what you purposed is O(n^2)

Resources