Truncate string when it is too long - ruby

I have two strings:
short_string = "hello world"
long_string = "this is a very long long long .... string" # suppose more than 10000 chars
I want to change the default behavior of print to:
puts short_string
# => "hello world"
puts long_string
# => "this is a very long long....."
The long_string is only partially printed. I tried to change String#to_s, but it didn't work. Does anyone know how to do it like this?
updated
Actually i wanna it works smoothly, that means the following cases also work fine:
> puts very_long_str
> puts [very_long_str]
> puts {:a => very_long_str}
So i think the behavior belongs to String.
Thanks all anyway.

First of all, you need a method to truncate a string, either something like:
def truncate(string, max)
string.length > max ? "#{string[0...max]}..." : string
end
Or by extending String: (it's not recommended to alter core classes, though)
class String
def truncate(max)
length > max ? "#{self[0...max]}..." : self
end
end
Now you can call truncate when printing the string:
puts "short string".truncate
#=> short string
puts "a very, very, very, very long string".truncate
#=> a very, very, very, ...
Or you could just define your own puts:
def puts(string)
super(string.truncate(20))
end
puts "short string"
#=> short string
puts "a very, very, very, very long string"
#=> a very, very, very, ...
Note that Kernel#puts takes a variable number of arguments, you might want to change your puts method accordingly.

This is how Ruby on Rails does it in their String#truncate method as a monkey-patch:
class String
def truncate(truncate_at, options = {})
return dup unless length > truncate_at
options[:omission] ||= '...'
length_with_room_for_omission = truncate_at - options[:omission].length
stop = if options[:separator]
rindex(options[:separator], length_with_room_for_omission) ||
length_with_room_for_omission
else
length_with_room_for_omission
end
"#{self[0...stop]}#{options[:omission]}"
end
end
Then you can use it like this
'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)')
# => "And they f... (continued)"

You can write a wrapper around puts that handles truncation for you:
def pleasant(string, length = 32)
raise 'Pleasant: Length should be greater than 3' unless length > 3
truncated_string = string.to_s
if truncated_string.length > length
truncated_string = truncated_string[0...(length - 3)]
truncated_string += '...'
end
puts truncated_string
truncated_string
end

Truncate naturally
I want to propose a solution that truncates naturally. I fell in love with the String#truncate method offered by Ruby on Rails. It was already mentioned by #Oto Brglez above. Unfortunately I couldn't rewrite it for pure ruby. So I wrote this function.
def truncate(content, max)
if content.length > max
truncated = ""
collector = ""
content = content.split(" ")
content.each do |word|
word = word + " "
collector << word
truncated << word if collector.length < max
end
truncated = truncated.strip.chomp(",").concat("...")
else
truncated = content
end
return truncated
end
Example
Test: I am a sample phrase to show the result of this function.
NOT: I am a sample phrase to show the result of th...
BUT: I am a sample phrase to show the result of...
Note: I'm open for improvements because I'm convinced that there is a shorter solution possible.

You can just use this syntax:
"mystring"[0..MAX_LENGTH]
[5] pry(main)> "hello world"[0..10]
=> "hello world"
[6] pry(main)> "hello world why"[0..10]
=> "hello world"
[7] pry(main)> "hello"[0..10]
=> "hello"
There's no need to check if it actually exceed the maximum length.

Related

How to optimize code removing unwanted characters

This code is designed for a problem where the users computer has a bug where every time he/she hits the backspace button it displays a '<' symbol. The created program should fix this and output the intended string considering that '<' represents a backspace. The input string can be up to 10^6 characters long, and it only will include lowercase letters and '<'.
My code seems to be executing correctly but, when I submit it, the website says it exceeded the time limit for test 5/25. The amount of time given is 1 second. Also, if there are only '<' symbols it should produce no output.
For example,
"hellooo<< my name is matthe<<"
would output
"hello my name is matt"
and
"ssadfas<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
would output nothing, etc.
Here is the code:
input = gets.chomp
while input[/[[:lower:]]</]
input.gsub!(/[[:lower:]]</, "")
end
input.gsub!(/</, "")
puts"#{input}"
In the code above I stay in the while loop if there are any instances where a lowercase letter is in front of a '<'. Anywhere a lowercase letter is followed by a '<' it is replaced with nothing. Once the while loop is exited if there are any '<' symbols left, they are replaced with nothing. Then the final string is displayed.
I created a test which I think is worst case scenario for my code:
input = ("a" + "<" + "a")*10000000
#input = gets.chomp
while input[/[[:lower:]]</]
input.gsub!(/[[:lower:]]</, "")
end
input.gsub!(/</, "")
puts"#{input}"
I made the program stop between the creation of the string and the execution of the while loop and then ran it completely to be able to eyeball if it was taking longer than a second. It seemed to take much longer than 1 second.
How can it be modified to be faster or is there a much better way to do this?
Your approach is good but you get better performance if you adapt the regular expression.
Cary, I hope you don't mind I take your excellent solution also in the benchmark ?
Benchmark done on a MRI ruby 2.3.0p0 (2015-12-25 revision 53290) [x64-mingw32]
I use .dup on my sample string to make sure none of the methods changes the input sample.
require 'benchmark'
input = ""
10_000_000.times{input << ['a','<'].sample}
def original_method inp
while inp[/[[:lower:]]</]
inp.gsub!(/[[:lower:]]</, "")
end
inp.gsub(/</, "")
end
def better_method inp
tuple = /[^<]</
while inp[tuple]
inp.gsub!(inp[tuple], "")
end
inp.gsub(/</, "")
end
def backspace str
bs_count = 0
str.reverse.each_char.with_object([]) do |s, arr|
if s == '<'
bs_count += 1
else
bs_count.zero? ? arr.unshift(s) : bs_count -= 1
end
end.join
end
puts original_method(input.dup).length
puts better_method(input.dup).length
puts backspace(input.dup).length
Benchmark.bm do |x|
x.report("original_method") { original_method(input.dup) }
x.report("backspace ") { backspace(input.dup) }
x.report("better_method ") { better_method(input.dup) }
end
gives
3640
3640
3640
user system total real
original_method 3.494000 0.016000 3.510000 ( 3.510709)
backspace 1.872000 0.000000 1.872000 ( 1.862550)
better_method 1.155000 0.031000 1.186000 ( 1.187495)
def backspace(str)
bs_count = 0
str.reverse.each_char.with_object([]) do |s, arr|
if s == '<'
bs_count += 1
else
bs_count.zero? ? arr.unshift(s) : bs_count -= 1
end
end.join
end
backspace "Now is the<< tim<e fo<<<r every<<<one to chill ou<<<<t"
#=> "Now is t tier evone to chilt"

Does anyone see the error in this simple Ruby function?

This function is supposed to take a string and return the characters in reverse order.
def reverse(string)
reversedString = "";
i = string.length - 1
while i >= 0
reversedString = reversedString + string[i]
i -= 1
end
puts reversedString
end
however all the tests return false:
puts(
'reverse("abc") == "cba": ' + (reverse("abc") == "cba").to_s
)
puts(
'reverse("a") == "a": ' + (reverse("a") == "a").to_s
)
puts(
'reverse("") == "": ' + (reverse("") == "").to_s
)
Does anyone see what the problem is?
Try to use the default String class reverse method like this:
"Hello World".reverse
"Hello World".reverse!
Check Ruby's String class API at https://ruby-doc.org/core-2.4.0/String.html
If you want to make your custom method, you could use a map like this:
string = String.new
"Hello World".chars.each { | c | string.prepend c }
The problem is your function isn't returning its result, it's printing it. It needs to return reversedString.
As a rule of thumb, functions should return their result. Another function should format and print it.
def reverse(string)
reversedString = "";
i = string.length - 1
while i >= 0
reversedString = reversedString + string[i]
i -= 1
end
return reversedString
end
Note: This was probably an exercise, but Ruby already has String#reverse.
It's good that you're writing tests, but the way you're writing them it's hard to tell what went wrong. Look into a Ruby testing framework like MiniTest.
require "minitest/autorun"
class TestReverse < Minitest::Test
def test_reverse
assert_equal "cba", reverse("abc")
assert_equal "a", reverse("a")
assert_equal "", reverse("")
end
end
That would have told you that your function is returning nil.
1) Failure:
TestReverse#test_reverse [test.rb:16]:
Expected: "cba"
Actual: nil
To make this more Ruby-like yet avoid using the built-in String#reverse method you'd do this:
def reverse(string)
string.chars.reverse.join('')
end
Remember that in Ruby the result of the last operation is automatically the return value of the method. In your case the last operation is puts which always returns nil, eating your value. You want to pass it through.
Try to design methods with a simple mandate, that is, this function should focus on doing one job and one job only: reversing a string. Displaying it is beyond that mandate, so that's a job for another method, like perhaps the caller.
To avoid calling any sort of reverse method at all:
def reverse(string)
result = ''
length = string.length
length.times do |i|
result << string[length - 1 - i]
end
result
end
You can often avoid for almost completely and while frequently if you use things like times or ranges (0..n) to iterate over.
puts prints and returns nil, so the whole method returns nil. If, for debugging reasons , you want to inspect what your method is returning, use p which returns it's argument (reversedString in this case).
def reverse(string)
reversedString = ""
i = string.length - 1
while i >= 0
reversedString = reversedString + string[i]
i -= 1
end
p reversedString # !!!
end
And all 3 tests return true
If I was going to do this, I'd probably take advantage of an array:
ary = 'foo bar baz'.chars
reversed_ary = []
ary.size.times do
reversed_ary << ary.pop
end
reversed_ary.join # => "zab rab oof"
pop removes the last character from the array and returns it, so basically it's walking backwards through ary, nibbling at the end and pushing each character onto the end of reversed_ary, effectively reversing the array.
Alternately it could be done using a string:
ary = 'foo bar baz'.chars
reversed_str = ''
ary.size.times do
reversed_str << ary.pop
end
reversed_str # => "zab rab oof"
or:
reversed_str += ary.pop
I just saw that #tadman did a similar thing with the string. His would run more quickly but this is more readable, at least to my eyes.

Method to reverse string only if it has less than four letters

I need to write a ruby method that reverses a string only if it has less than four characters.
# Write a method that reverses
# a string ONLY if it's shorter than
# 4 letters.
# Otherwise, the string is
# returned as-is.
# (Hint: strings have
# a built-in .length method!)
# conditional_reverse("yo")
# => "oy"
# conditional_reverse("hello")
# => "hello"
Here is the code I came up with.
def conditional_reverse(string)
good = string.length
if good < 4
puts string.reverse
else
puts string
end
puts conditional_reverse("cat")
end
When I run it in repl I get the following response
:conditional_reverse
I have no idea what i'm doing wrong.
just put puts conditional_reverse("cat") out side our def
def conditional_reverse(string)
good = string.length
if good < 4
puts string.reverse
else
puts string
end
end
conditional_reverse("cat")
You are callind your method in its definition. Avoid it if you are not writing a recursive method.
def conditional_reverse(s)
s.length < 4 ? s.reverse : s
end
The answer provided by #Ursus is perfect, but in case you want to go with your way the change you have to do is this;
def conditional_reverse(string)
good = string.length
if good < 4
puts string.reverse
else
puts string
end
end
puts conditional_reverse("cat")
What the others said, plus...
You get that response from irb because in recent versions of Ruby, a method definition returns the method name as a symbol.
Also, your problem specifies that the string should be reversed, not that the string should be output reversed; so you should remove the puts calls and just manipulate the string.
For the benefit of your readers, I recommend being specific with your names. good = string.length could be changed to needs_reversing = string.length < 4, for example.

Replace different sets of characters with different sets of numbers using regex

I am trying to replace characters in a string with a shift in the ord by some number. I am thinking the best way to do this is with regex, but running into some problems.
This is the flawed code I do have
def cipher(coded_message)
coded_message=coded_message.downcase.split("")
new_message=[]
coded_message.each do |x|
x=x.gsub(/[a-d][e-z]/, '\1x.ord+22\2x.ord-4')
new_message<<x
end
p new_message.join
end
I know that my problem is with the regex and probably the replacement text, but not sure where to go on this one. Any help would be appreciated.
Ok so I took a different approach to solving your problem. Here is a solution which doesn't involve a regex, and is very flexible.
def cipher(coded_message)
new_message=[]
coded_message.downcase.each_char do |x|
case x
when ("a".."d")
new_message << (x.ord+22).chr
when ("e".."z")
new_message << (x.ord-4).chr
end
end
new_message.join
end
cipher("Code this string")
#=> "ykzapdeoopnejc"
Not much point coding a message if you can't decode it:
#code_key = 123.times.with_object({}) do |i,h|
c = i.chr
h[c] =
case c
when /[a-dA-D]/
(i+22).chr
when /[e-zE-Z]/
(i-4).chr
else
c
end
end
#decode_key = #code_key.invert
def code(message)
#code_key.values_at(*message.chars).join
end
def decode(message)
#decode_key.values_at(*message.chars).join
end
message = "Is 42 an important number?"
coded_message = code(message) # => "Eo 42 wj eilknpwjp jqixan?"
decoded_message = decode(coded_message) # => "Is 42 an important number?"

Repeat Method to Give "string" an "x" amounts of times

I'm trying to write a method that will take two arguments, one for the string, and the other the number of times it will be repeated. here is the code of i have:
def repeat(text,c=2)
c.times do print text end
end
repeat ("hi")
problem here is, I want to have the result to be "hi hi"
i tried "puts" but that starts a new line...
[ print text " + " text ] doesn't work as well...
thanks for the help!
Your question is unclear. If all you want is to print the text repeated n times, use String#*
def repeat(text, n=2)
print text * n
end
Your example result says you want "hi hi" implying you would like spaces between each repetition. The most concise way to accomplish that is to use Array#*
def repeat(text, n=2)
print [text] * n * ' '
end
Simply multiply the string by the number, Ruby is smart enough to know what you mean ;)
pry(main)> "abcabcabc" * 3
=> "abcabcabcabcabcabcabcabcabc"
Or you could do something like:
def repeat(text, c=2)
print c.times.collect { text }.join(' ')
end
Enumerator#cycle returns an enumerator:
puts ['hi'].cycle(3).to_a.join(' ')
# => hi hi hi
Breaking down the code:
['hi'] creates an array containing a string
cycle(3) creates an enumerator from the array that repeats the elements 3 times
.to_a creates an array from the enumerator so that the join method of Array can create the final output string.
def repeat(text, c=2)
print Array.new(c, text).join(' ')
end
I am new to ruby, but I thought this solution worked well for me and I came up with it myself.
def repeat(word, i=2)
word + (" #{word}" * (i-1))
end
You can try this:
def repeat(text, c=2)
print ((text + ' ')*c).strip
end
def repeat(text, c=2)
print ([text]*c).join(' ')
end
Perhaps easier to read. Unless, is there any reason to use the .collect method instead?
I don't see the point in creating an array (with or without collect()) and then calling join(). This works too:
def repeat(text, c=2)
c.times { |i| print text; print ' ' unless i+1 == c }
end
Although, it is a little more verbose (which is arguably un-ruby like) it does less work (which maybe makes more sense).

Resources