I'm very new to Ruby and searching for a solution.
Essentially I have a hash in the form of [0 > String, 1 > String] etc.
I want to run a loop which can count the characters in the strings in the hashes, should it reach a limit, the end of the string should be cut at that point and replaced with '...'
Example:
Say I set my character count at 10:
Hello World!
Would shorten to:
Hello Worl...
May be worth noting that this hash is created from an array, as such if it is deemed wiser to do it before the hash conversion, that would also be fine. Any advice is hugely appreciated.
Using ActiveSupport's Truncate Method
If you're willing to mix in methods from the ActiveSupport gem such as String#truncate, this is trivial. For example:
require 'active_support/core_ext/string/filters'
'Hello World!'.truncate 10
#=> "Hello W..."
Note that the #truncate method counts the ellipsis as three characters (one for each period in the ellipsis) towards the total character count. Bump your count by three characters (e.g. 13 instead of 10) if you actually want 10 characters before the ellipsis rather than including it.
I think this is not beautiful, but it does what you're asking:
hash = {
0 => 'hello world',
1 => 'hello'
}
hash.each_pair do |key, value|
p key: key
p value: value.length > 10 ? value[0..9] + '...' : value
end
you could also make a method to truncate the string, like this:
def truncate(string, truncate_after)
return string if string.length < truncate_after
string[0..(truncate_after - 1)] + '...'
end
Related
To convert array to string I used Array#join and got space between the
beginning of the string, first quote mark, and the first word. I do not understand why this is happening.
I resolved with String#strip but I would like to understand
def order(words)
arr_new = []
arr = words.split(" ")
nums = ["1","2","3","4","5","6","7","8","9"]
arr.each do |word|
nums.each do |num|
if word.include? num
arr_new[num.to_i] = word
end
end
end
arr_new.join(" ").strip
end
order("is2 Thi1s T4est 3a")
Without .strip the output is:
" Thi1s is2 3a T4est"
After .strip:
"Thi1s is2 3a T4est"
The reason you're seeing the extra space is because arrays in ruby are 0 indexed, so you have an nil array element because your first insert is a index 1
x = []
x[1] = "test"
This creates an array as such:
[
nil,
"test"
]
If you created an empty array named x and assigned x[10] = "test" you'd have 10 nil values, and the word "test" in your array.
So, your array, before joining, is actually:
[nil, "Thi1s", "is2", "3a", "T4est"]
You have a couple options:
Change your strings to start with zero
Change your assignment to adjust the offset (subtract one)
Use compact before you join (this will remove nils)
Use strip as you noted
I'd suggest compact because it would address a few edge cases (such as "gaps" in your numbers.
More info in the array docs
#Jay's explanation is indeed correct.
I'll simply suggest a cleaner version of your code that doesn't have the same problem.
This assumes that the 1-9 order isn't dynamic. Aka wouldn't work if you wanted to sort by random characters for example.
def order(words)
words.split.sort_by { |word| word[/\d/].to_i }.join ' '
end
Say that we want to count the number of words in a document. I know we can do the following:
text.each_line(){ |line| totalWords = totalWords + line.split.size }
Say, that I just want to add some exceptions, such that, I don't want to count the following as words:
(1) numbers
(2) standalone letters
(3) email addresses
How can we do that?
Thanks.
You can wrap this up pretty neatly:
text.each_line do |line|
total_words += line.split.reject do |word|
word.match(/\A(\d+|\w|\S*\#\S+\.\S+)\z/)
end.length
end
Roughly speaking that defines an approximate email address.
Remember Ruby strongly encourages the use of variables with names like total_words and not totalWords.
assuming you can represent all the exceptions in a single regular expression regex_variable, you could do:
text.each_line(){ |line| totalWords = totalWords + line.split.count {|wrd| wrd !~ regex_variable }
your regular expression could look something like:
regex_variable = /\d.|^[a-z]{1}$|\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
I don't claim to be a regex expert, so you may want to double check that, particularly the email validation part
In addition to the other answers, a little gem hunting came up with this:
WordsCounted Gem
Get the following data from any string or readable file:
Word count
Unique word count
Word density
Character count
Average characters per word
A hash map of words and the number of times they occur
A hash map of words and their lengths
The longest word(s) and its length
The most occurring word(s) and its number of occurrences.
Count invividual strings for occurrences.
A flexible way to exclude words (or anything) from the count. You can pass a string, a regexp, an array, or a lambda.
Customisable criteria. Pass your own regexp rules to split strings if you prefer. The default regexp has two features:
Filters special characters but respects hyphens and apostrophes.
Plays nicely with diacritics (UTF and unicode characters): "São Paulo" is treated as ["São", "Paulo"] and not ["S", "", "o", "Paulo"].
Opens and reads files. Pass in a file path or a url instead of a string.
Have you ever started answering a question and found yourself wandering, exploring interesting, but tangential issues, or concepts you didn't fully understand? That's what happened to me here. Perhaps some of the ideas might prove useful in other settings, if not for the problem at hand.
For readability, we might define some helpers in the class String, but to avoid contamination, I'll use Refinements.
Code
module StringHelpers
refine String do
def count_words
remove_punctuation.split.count { |w|
!(w.is_number? || w.size == 1 || w.is_email_address?) }
end
def remove_punctuation
gsub(/[.!?,;:)](?:\s|$)|(?:^|\s)\(|\-|\n/,' ')
end
def is_number?
self =~ /\A-?\d+(?:\.\d+)?\z/
end
def is_email_address?
include?('#') # for testing only
end
end
end
module CountWords
using StringHelpers
def self.count_words_in_file(fname)
IO.foreach(fname).reduce(0) { |t,l| t+l.count_words }
end
end
Note that using must be in a module (possibly a class). It does not work in main, presumably because that would make the methods available in the class self.class #=> Object, which would defeat the purpose of Refinements. (Readers: please correct me if I'm wrong about the reason using must be in a module.)
Example
Let's first informally check that the helpers are working correctly:
module CheckHelpers
using StringHelpers
s = "You can reach my dog, a 10-year-old golden, at fido#dogs.org."
p s = s.remove_punctuation
#=> "You can reach my dog a 10 year old golden at fido#dogs.org."
p words = s.split
#=> ["You", "can", "reach", "my", "dog", "a", "10",
# "year", "old", "golden", "at", "fido#dogs.org."]
p '123'.is_number? #=> 0
p '-123'.is_number? #=> 0
p '1.23'.is_number? #=> 0
p '123.'.is_number? #=> nil
p "fido#dogs.org".is_email_address? #=> true
p "fido(at)dogs.org".is_email_address? #=> false
p s.count_words #=> 9 (`'a'`, `'10'` and "fido#dogs.org" excluded)
s = "My cat, who has 4 lives remaining, is at abbie(at)felines.org."
p s = s.remove_punctuation
p s.count_words
end
All looks OK. Next, put I'll put some text in a file:
FName = "pets"
text =<<_
My cat, who has 4 lives remaining, is at abbie(at)felines.org.
You can reach my dog, a 10-year-old golden, at fido#dogs.org.
_
File.write(FName, text)
#=> 125
and confirm the file contents:
File.read(FName)
#=> "My cat, who has 4 lives remaining, is at abbie(at)felines.org.\n
# You can reach my dog, a 10-year-old golden, at fido#dogs.org.\n"
Now, count the words:
CountWords.count_words_in_file(FName)
#=> 18 (9 in ech line)
Note that there is at least one problem with the removal of punctuation. It has to do with the hyphen. Any idea what that might be?
Something like...?
def is_countable(word)
return false if word.size < 2
return false if word ~= /^[0-9]+$/
return false if is_an_email_address(word) # you need a gem for this...
return true
end
wordCount = text.split().inject(0) {|count,word| count += 1 if is_countable(word) }
Or, since I am jumping to the conclusion that you can just split your entire text into an array with split(), you might need:
wordCount = 0
text.each_line do |line|
line.split.each{|word| wordCount += 1 if is_countable(word) }
end
I am trying to create a program where the first three characters of a string is repeated a given number of times like this:
foo('Chocolate', 3) # => 'ChoChoCho'
foo('Abc', 3) # => 'AbcAbcAbc'
I know I can use length to count characters, but how do I specify the length of the string to be outputted? Also how can I specify the number of times?
def foo(str, n)
str[0..2] * n
end
You can use something like this.
def print_first_three_x_times(string, x)
#remove everything but the first three chars
string.slice!(3..string.length)
#print x times
x.times{ print string }
end
output:
Hunter#Hunter-PC ~
$ irb
irb(main):008:0> print_first_three_x_times("Hunter",5)
HunHunHunHunHun=> 5
irb(main):009:0>
I have read the lines of a text file into a hash of chars. Now I need to make it so only the alphabetical chars appear, not the special chars and punctuation. I have tried this:
File.open(WORKING_DIR + '/code.txt','r') do |f|
char_count = Hash.new(0) # create a hash where 0 is the default value
f.each_char do |c| # iterate on each character
char_count.has_key?(c)
char_count[c] +=1
end
end
puts char_count.inspect
But when I inspect my hash, all punctuation is still present. How am I using has_key incorrectly?
You definitely seem to be missing some things here. Since you've tagged this as homework, I'm not going to give you an outright solution, but here's some things to look into:
First, each_char iterates through every single character -- not just alphanumerics:
irb(main):001:0> "xyz.abc".each_char { |c| print c, ' ' }
x y z . a b c => "xyz.abc"
So, you will need some sort of test for that.
Second, inside your iterator, you're not actually applying your has_key? test -- you will need to use some logic here to decide when to increment your hash.
What is the preferred way of removing the last n characters from a string?
irb> 'now is the time'[0...-4]
=> "now is the "
If the characters you want to remove are always the same characters, then consider chomp:
'abc123'.chomp('123') # => "abc"
The advantages of chomp are: no counting, and the code more clearly communicates what it is doing.
With no arguments, chomp removes the DOS or Unix line ending, if either is present:
"abc\n".chomp # => "abc"
"abc\r\n".chomp # => "abc"
From the comments, there was a question of the speed of using #chomp versus using a range. Here is a benchmark comparing the two:
require 'benchmark'
S = 'asdfghjkl'
SL = S.length
T = 10_000
A = 1_000.times.map { |n| "#{n}#{S}" }
GC.disable
Benchmark.bmbm do |x|
x.report('chomp') { T.times { A.each { |s| s.chomp(S) } } }
x.report('range') { T.times { A.each { |s| s[0...-SL] } } }
end
Benchmark Results (using CRuby 2.13p242):
Rehearsal -----------------------------------------
chomp 1.540000 0.040000 1.580000 ( 1.587908)
range 1.810000 0.200000 2.010000 ( 2.011846)
-------------------------------- total: 3.590000sec
user system total real
chomp 1.550000 0.070000 1.620000 ( 1.610362)
range 1.970000 0.170000 2.140000 ( 2.146682)
So chomp is faster than using a range, by ~22%.
Ruby 2.5+
As of Ruby 2.5 you can use delete_suffix or delete_suffix! to achieve this in a fast and readable manner.
The docs on the methods are here.
If you know what the suffix is, this is idiomatic (and I'd argue, even more readable than other answers here):
'abc123'.delete_suffix('123') # => "abc"
'abc123'.delete_suffix!('123') # => "abc"
It's even significantly faster (almost 40% with the bang method) than the top answer. Here's the result of the same benchmark:
user system total real
chomp 0.949823 0.001025 0.950848 ( 0.951941)
range 1.874237 0.001472 1.875709 ( 1.876820)
delete_suffix 0.721699 0.000945 0.722644 ( 0.723410)
delete_suffix! 0.650042 0.000714 0.650756 ( 0.651332)
I hope this is useful - note the method doesn't currently accept a regex so if you don't know the suffix it's not viable for the time being. However, as the accepted answer (update: at the time of writing) dictates the same, I thought this might be useful to some people.
str = str[0..-1-n]
Unlike the [0...-n], this handles the case of n=0.
I would suggest chop. I think it has been mentioned in one of the comments but without links or explanations so here's why I think it's better:
It simply removes the last character from a string and you don't have to specify any values for that to happen.
If you need to remove more than one character then chomp is your best bet. This is what the ruby docs have to say about chop:
Returns a new String with the last character removed. If the string
ends with \r\n, both characters are removed. Applying chop to an empty
string returns an empty string. String#chomp is often a safer
alternative, as it leaves the string unchanged if it doesn’t end in a
record separator.
Although this is used mostly to remove separators such as \r\n I've used it to remove the last character from a simple string, for example the s to make the word singular.
name = "my text"
x.times do name.chop! end
Here in the console:
>name = "Nabucodonosor"
=> "Nabucodonosor"
> 7.times do name.chop! end
=> 7
> name
=> "Nabuco"
Dropping the last n characters is the same as keeping the first length - n characters.
Active Support includes String#first and String#last methods which provide a convenient way to keep or drop the first/last n characters:
require 'active_support/core_ext/string/access'
"foobarbaz".first(3) # => "foo"
"foobarbaz".first(-3) # => "foobar"
"foobarbaz".last(3) # => "baz"
"foobarbaz".last(-3) # => "barbaz"
if you are using rails, try:
"my_string".last(2) # => "ng"
[EDITED]
To get the string WITHOUT the last 2 chars:
n = "my_string".size
"my_string"[0..n-3] # => "my_stri"
Note: the last string char is at n-1. So, to remove the last 2, we use n-3.
Check out the slice() method:
http://ruby-doc.org/core-2.5.0/String.html#method-i-slice
You can always use something like
"string".sub!(/.{X}$/,'')
Where X is the number of characters to remove.
Or with assigning/using the result:
myvar = "string"[0..-X]
where X is the number of characters plus one to remove.
If you're ok with creating class methods and want the characters you chop off, try this:
class String
def chop_multiple(amount)
amount.times.inject([self, '']){ |(s, r)| [s.chop, r.prepend(s[-1])] }
end
end
hello, world = "hello world".chop_multiple 5
hello #=> 'hello '
world #=> 'world'
Using regex:
str = 'string'
n = 2 #to remove last n characters
str[/\A.{#{str.size-n}}/] #=> "stri"
x = "my_test"
last_char = x.split('').last