I'm dealing with a file that has a bunch of entries like this
2012-07-15 10:16:27 C ?\path\to a filename\ called this file.doc
I want to take a line like this and cut the first 3 fields separated by spaces.
So...
var1 = 2012-07-15
var2 = 10:16:27
var3 = c
I've googled around and I just can not seem to find the right method to use. Thank you for your help!
Ruby's String#split accepts a limit as its second parameter. This will do exactly what you're looking for:
irb(main):005:0> str = "2012-07-15 10:16:27 C ?\path\to a filename\ called this file.doc"
=> "2012-07-15 10:16:27 C ?path\to a filename called this file.doc"
irb(main):006:0> str.split " ", 4
=> ["2012-07-15", "10:16:27", "C", "?path\to a filename called this file.doc"]
You can use destructuring to assign these into local variables, if you want:
one, two, three, rest = str.split " ", 4
The split method will do what you want:
string = '2012-07-15 10:16:27 C ?\path\to a filename\ called this file.doc'
date, time, drive = string.split
Related
print str[i].upcase is not working and i have to capitalize specific letters determined using an index. Can someone help me with this?
def mumble_letters
str = nil
print "Please write a string : "
str = gets.to_str
# puts str.length
while str.length == 1
print "Please write a string : "
str = gets.to_str
end
for i in 0..str.length
print str[i].upcase!
i.times{ print str[i].capitalize}
if i != str.length - 1
print"-"
end
end
end
mumble_letters
the error I get is : undefined method `upcase' for nil:NilClass (NoMethodError)
Did you mean? case
Problem
str[i].upcase! mutates the single character in the Array value into an uppercase character. However, at least on Ruby 2.7.1, it won't actually change the contents of your original String object until you reassign the element back to the String index you want modified. For example:
str[i] = str[i].upcase
However, the approach above won't work with frozen strings, which are fairly common in certain core methods, libraries, and frameworks. As a result, you may encounter the FrozenError exception with the index-assignment approach.
Solution
There's more than one way to solve this, but one way is to:
split your String object into an Array of characters,
modify the letter at the desired indexes,
rejoin the characters into a single String, and then
re-assign the modified String to your original variable.
For example, showing some intermediate steps:
# convert String to Array of characters
str = "foobar"
chars = str.chars
# contents of your chars Array
chars
#=> ["f", "o", "o", "b", "a", "r"]
# - convert char in place at given index in Array
# - don't rely on the return value of the bang method
# to be a letter
# - safe navigation handles various nil-related errors
chars[3]&.upcase!
#=> "B"
# re-join Array of chars into String
chars.join
#=> "fooBar"
# re-assign to original variable
str = chars.join
str
#=> "fooBar"
If you want, you can perform the same operation on multiple indexes of your chars Array before re-joining the elements. That should yield the results you're looking for.
More concisely:
str = "foobar"
chars = str.chars
chars[3]&.upcase!
p str = chars.join
#=> "fooBar"
Personally, I find operating on an Array of characters more intuitive and easier to troubleshoot than making in-place changes through repeated assignments to indexes within the original String. Furthermore, it avoids exceptions raised when trying to modify indexes within a frozen String. However, your design choices may vary.
str[i].upcase returns the upcased letter, but does not modify it in place. Assign it back to the string for it to work.
str = 'abcd'
str[2] = str[2].upcase #=> "C"
str #=> "abCd"
I can see two problems with your code...
First, an empty string has a length of 0 so what you wanted to write is
while str.length == 0
Secondly, when you do...
for i in 0..str.length
You are iterating up to the string length INCLUDING the string length. If the string has five characters, it actually only has valid indexes 0 through 4 but you are iterating 0 through 5. And str[5] doesn't exist so returns nil and you cannot do upcase! on a nil.
To handle that common situation, Ruby has the tripe dot operator
for i in 0...str.length
...which will stop at the integer before the length, which is what you want.
It's also more ruby-eque to do
(0...str.length).each do |i|
I have a string like "a_b_c" or "a_b_c_d" or "a_b_c_d_e". I want to split the string at the last underscore.
**input**
'a_b_c'
**output**
a_b
c
**input**
'a_b_c_d'
**output**
a_b_c
d
I have done the following:
a='a_b_c'
a=a.split('_')
last=a.pop
a.delete(last)
p a.join("_")
p last
and achieved the result, but I don't think this should be done this way. I hope there is some regular expression to achieve this. Is there anyone who can help me with this?
You can use String#rpartition that searches for a given pattern form the right end of the string and splits when it finds it.
'a_b_c_d_e'.rpartition(/_/)
=> ["a_b_c_d", "_", "e"]
s = 'a_b_c_d_e'
parts = s.rpartition(/_/)
[parts.first, parts.last]
=> ["a_b_c_d", "e"]
EDIT: applying advices from the comments:
'a_b_c_d_e'.rpartition('_').values_at(0,2)
=> ["a_b_c_d", "e"]
Do you really need to split? How about just replacing the _ with a space? e.g. using rindex and []=
a[a.rindex('_')] = ' '
I didn't do a benchmark, but split creates a new array, which typically requires more resources, at least in other languages.
EDIT: as the question was edited, its now clear the OP is asking for a list instead of a string output
You can also get values as below,
> a = a.split('_')
> a[0..-2].join('_')
# => "a_b_c_d"
> a[-1]
# => "e"
'a_b_c_d_e'.split /_(?!.*_)/
#=> ["a_b_c_d", "e"]
The negative lookahead (?!.*_) requires that following the match of the underscore there is no other underscore in the string.
Split it with regex:
a.split(/_(?=[^_]+$)/)
Explanation:
matches the character _ with positive Lookahead (?=[^_]+$)
Match a single character not present in the list below [^_]+ and
$ asserts position at the end of the string, or before the line terminator right at the end of the string (if any)
Assuming you know this string follows this format:
str = 'a_b_c_d_e'
# Remainder
str[0...-2] # -> 'a_b_c_d'
# Last symbol
str[-1] # -> 'e'
I've justed started learning to code in Ruby and have hit a snag in my first script. The idea is to translate the English alphabet into morse code.
I have set up a hash for my letters and their corresponding values:
morse_code = {
'a' => '.-',
'b' => '-...',
etc etc
I use the following to iterate through the hash and pull the corresponding values based on input then output it:
print "What would you like to translate: "
code = gets.strip.downcase
morse_code.each do |morse, alpha|
code.gsub!( morse, alpha )
end
puts code
The problem is that my output does not contain spacing so looks like this:
......-...-..----
instead of what I want:
.... . .-.. .-.. --- -
All I've found thus far are relating to adding a whitespace when calling variables inside a string. Below is an example:
Putting space between the output of defined variables in Ruby
Any help on how I can achieve this with my current code or rewrite it accordingly would be appreciated.
What you need is to take the input and map its characters to corresponding values from the morse_code hash, and then join it with spaces:
code = 'abb'
code.each_char.map { |letter| morse_code[letter] }.join(' ')
#=> ".- -... -..."
Reference:
String#each_char
Enumerable#map
Array#join
EDIT:
To make your initial code to work the only thing you lacked is a space, which is easy to add using interpolation:
code = 'abab'
morse_code.each do |morse, alpha|
code.gsub!(morse, "#{alpha} ") # <=============
end
code
#=> ".- -... .- -... "
code.rstrip
#=> ".- -... .- -..."
If you did not know about interpolation - here is how it works:
foo = 'bar'
"#{foo}" #=> "bar"
"hello I am #{foo}" #=> "hello I am bar"
So going back to your case, all the following does
"#{alpha} "
is adding a space after, which you needed. Problem with it, that the resulting string will have an extra space at the end, which we solved with
code.rstrip
I want to replace the last occurrence of a substring in Ruby. What's the easiest way?
For example, in abc123abc123, I want to replace the last abc to ABC. How do I do that?
How about
new_str = old_str.reverse.sub(pattern.reverse, replacement.reverse).reverse
For instance:
irb(main):001:0> old_str = "abc123abc123"
=> "abc123abc123"
irb(main):002:0> pattern="abc"
=> "abc"
irb(main):003:0> replacement="ABC"
=> "ABC"
irb(main):004:0> new_str = old_str.reverse.sub(pattern.reverse, replacement.reverse).reverse
=> "abc123ABC123"
"abc123abc123".gsub(/(.*(abc.*)*)(abc)(.*)/, '\1ABC\4')
#=> "abc123ABC123"
But probably there is a better way...
Edit:
...which Chris kindly provided in the comment below.
So, as * is a greedy operator, the following is enough:
"abc123abc123".gsub(/(.*)(abc)(.*)/, '\1ABC\3')
#=> "abc123ABC123"
Edit2:
There is also a solution which neatly illustrates parallel array assignment in Ruby:
*a, b = "abc123abc123".split('abc', -1)
a.join('abc')+'ABC'+b
#=> "abc123ABC123"
Since Ruby 2.0 we can use \K which removes any text matched before it from the returned match. Combine with a greedy operator and you get this:
'abc123abc123'.sub(/.*\Kabc/, 'ABC')
#=> "abc123ABC123"
This is about 1.4 times faster than using capturing groups as Hirurg103 suggested, but that speed comes at the cost of lowering readability by using a lesser-known pattern.
more info on \K: https://www.regular-expressions.info/keep.html
Here's another possible solution:
>> s = "abc123abc123"
=> "abc123abc123"
>> s[s.rindex('abc')...(s.rindex('abc') + 'abc'.length)] = "ABC"
=> "ABC"
>> s
=> "abc123ABC123"
When searching in huge streams of data, using reverse will definitively* lead to performance issues. I use string.rpartition*:
sub_or_pattern = "!"
replacement = "?"
string = "hello!hello!hello"
array_of_pieces = string.rpartition sub_or_pattern
( array_of_pieces[(array_of_pieces.find_index sub_or_pattern)] = replacement ) rescue nil
p array_of_pieces.join
# "hello!hello?hello"
The same code must work with a string with no occurrences of sub_or_pattern:
string = "hello_hello_hello"
# ...
# "hello_hello_hello"
*rpartition uses rb_str_subseq() internally. I didn't check if that function returns a copy of the string, but I think it preserves the chunk of memory used by that part of the string. reverse uses rb_enc_cr_str_copy_for_substr(), which suggests that copies are done all the time -- although maybe in the future a smarter String class may be implemented (having a flag reversed set to true, and having all of its functions operating backwards when that is set), as of now, it is inefficient.
Moreover, Regex patterns can't be simply reversed. The question only asks for replacing the last occurrence of a sub-string, so, that's OK, but readers in the need of something more robust won't benefit from the most voted answer (as of this writing)
You can achieve this with String#sub and greedy regexp .* like this:
'abc123abc123'.sub(/(.*)abc/, '\1ABC')
simple and efficient:
s = "abc123abc123abc"
p = "123"
s.slice!(s.rindex(p), p.size)
s == "abc123abcabc"
string = "abc123abc123"
pattern = /abc/
replacement = "ABC"
matches = string.scan(pattern).length
index = 0
string.gsub(pattern) do |match|
index += 1
index == matches ? replacement : match
end
#=> abc123ABC123
I've used this handy helper method quite a bit:
def gsub_last(str, source, target)
return str unless str.include?(source)
top, middle, bottom = str.rpartition(source)
"#{top}#{target}#{bottom}"
end
If you want to make it more Rails-y, extend it on the String class itself:
class String
def gsub_last(source, target)
return self unless self.include?(source)
top, middle, bottom = self.rpartition(source)
"#{top}#{target}#{bottom}"
end
end
Then you can just call it directly on any String instance, eg "fooBAR123BAR".gsub_last("BAR", "FOO") == "fooBAR123FOO"
.gsub /abc(?=[^abc]*$)/, 'ABC'
Matches a "abc" and then asserts ((?=) is positive lookahead) that no other characters up to the end of the string are "abc".
In Ruby language, how can I get the number of lines in a string?
There is a lines method for strings which returns an Enumerator. Call count on the enumerator.
str = "Hello\nWorld"
str.lines.count # 2
str = "Hello\nWorld\n" # trailing newline is ignored
str.lines.count # 2
The lines method was introduced in Ruby 1.8.7. If you're using an older version, checkout the answers by #mipadi and #Greg.
One way would be to count the number of line endings (\n or \r\n, depending on the string), the caveat being that if the string does not end in a new line, you'll have to make sure to add one to your count. You could do so with the following:
c = my_string.count("\n")
c += 1 unless c[-1,1] == "\n"
You could also just loop through the string and count the lines:
c = 0
my_string.each { |line| c += 1 }
Continuing with that solution, you could get really fancy and use inject:
c = my_string.each.inject(0) { |count, line| count += 1 }
string".split("\n").size works nicely. I like that it ignores trailing new-lines if they don't contain content.
"Hello\nWorld\n".split("\n") # => ["Hello", "World"]
"hello\nworld\nfoo bar\n\n".split("\n").size # => 3
That might not be what you want, so use lines() as #Anurag suggested instead if you need to honor all new-lines.
"hello\nworld\nfoo bar\n\n".lines.count # => 4
"hello\nworld\nfoo bar\n\n".chomp.split("\n",-1).size # => 4
String#chomp gets rid of an end of line if it exists, and the -1 allows empty strings.
given a file object (here, in rails)
file = File.open(File.join(Rails.root, 'lib', 'file.json'))
file.readlines.count
returns the number of lines
IO#readlines performs a split method on strings (IOStrings in this case) using newlines as the separator
This will not count blank lines:
string.split("\n").select{ |line| line != "" }.size