I'm doing a project which requires removal of extra letters in a word.
If a letter occurs three or more times consecutively, we condense it to one letter
-Happyyyyyy -> Happy
-awwwsum -> awsum
-cooool -> col
I'm using Ruby 1.8.7 to do this. How do I go about this?
Here's how you do it:
result = subject.gsub(
/(.) # Match a single character, capture it in group 1
\1{2,} # Match the same character 2 or more times, as many as possible/x,
'\1') # Replace with the one captured character
Result:
> subject = "happyyyy daaaaays!!!"
=> "happyyyy daaaaays!!!"
> result = subject.gsub(/(.)\1{2,}/, '\1')
=> "happy days!"
Related
I have this code running inside a buffer (used to unescape a JS string in Ruby):
elsif hex_substring =~ /^\\u[0-9a-fA-F]{1,4}/
hex_substring.scan(/^((\\u[\da-fA-F]{4}){1,})/) do |match|
hex_byte = match[0]
buffer << JSON.load(%Q("#{hex_byte}"))
hex_index += hex_byte.length
end
...
I have a concern that the scan() is matching a bit too much:
hex_substring.scan(/^((\\u[\da-fA-F]{4}){1,})/)
# => [["\\ud83c\\udfec", "\\udfec"]]
I am using only "\\ud83c\\udfec", not "\\udfec".
Is there a way in Ruby or in regex to grab only the first part?
You should use a single grouping construct here, the one to match 1 or more occurrences of four hex chars, and omit the inner capturing group that resulted in an extra item in the resulting array:
.scan(/^(?:\\u[\da-fA-F]{4})+/)
Note that + is a simpler and shorter way to write {1,} (one or more occurrences).
Details
^ - start of string
(?: - start of a non-capturing group (what it matches won't be added to the final scan result):
\\u - a \u substring
[\da-fA-F]{4} - four hex chars
)+ - 1 or more occurrences (of the group pattern sequence).
The following code from a book is supposed to transfer "FOO92OBAR" to "FOO92_O_BAR":
gsub(/([a-z\d])([A-Z])/, '\1_\2')
Can anyone explain how this works?
([a-z\d]) looks for a lowercase letter (a-z) or a number (\d means a digit). The () around the whole thing assign the result to regex subgroup 1.
([A-Z]) then looks for an uppercase letter, assigning the result to group 2. So the whole thing looks for a lowercase-or-digit followed by an uppercase letter. The second part, '\1_\2', means "regex group 1 followed by regex group 2"
gsub replaces every time it sees a lowercase-or-digit followed by an uppercase letter with (the first thing)_(the second thing).
So actually FOO92OBAR will be FOO92_OBAR.
For FOO92OBAR to become FOO92_O_BAR, the replace part should be '\1_\2_' (since only the O is the second part.. BAR is not matched, so not replaced at all).
It works using regular expressions.
The two parameters of gsub are the match expression and the replacement. Because the match /([a-z\d])([A-Z])/ contains groups (identified by (...)), then you can reference a match in the replacement using \ID where the ID is the number of the group, starting from 1.
That said, the code gsub(/([a-z\d])([A-Z])/, '\1_\2')
# take any combination of
([a-z\d])([A-Z])
# which means any combinations of a (1) lower-case char or (2) digit
([a-z\d])
# followed by an (1) upper case letter
([A-Z])
# if any, replace it with
\1_\2
# that represents the first group
\1
# followed by _
# followed by the second group
\2
Please note that your example will generate FOO92_OBAR, not FOO92_O_BAR
2.1.5 :001 > string = "FOO92OBAR"
=> "FOO92OBAR"
2.1.5 :002 > string.gsub(/([a-z\d])([A-Z])/, '\1_\2')
=> "FOO92_OBAR"
The explanation is because there is only one case of a "lower-case char or digit" (and that is a digit) followed by an upper case char.
2.1.5 :003 > string.scan(/([a-z\d])([A-Z])/)
=> [["2", "O"]]
Regular expressions are case sensitive by default.
I'm trying to learn ruby and having a hard time figuring out what each individual part of this code is doing. Specifically, how does the global subbing determine whether two sequential numbers are both one of these values [13579] and how does it add a dash (-) in between them?
def DashInsert(num)
num_str = num.to_s
num_str.gsub(/([13579])(?=[13579])/, '\1-')
end
num_str.gsub(/([13579])(?=[13579])/, '\1-')
() called capturing group, which captures the characters matched by the pattern present inside the capturing group. So the pattern present inside the capturing group is [13579] which matches a single digit from the given set of digits. That corresponding digit was captured and stored inside index 1.
(?=[13579]) Positive lookahead which asserts that the match must be followed by the character or string matched by the pattern inside the lookahead. Replacement will occur only if this condition is satisfied.
\1 refers the characters which are present inside the group index 1.
Example:
> "13".gsub(/([13579])(?=[13579])/, '\1-')
=> "1-3"
You may start with some random tests:
def DashInsert(num)
num_str = num.to_s
num_str.gsub(/([13579])(?=[13579])/, '\1-')
end
10.times{
x = rand(10000)
puts "%6i: %6s" % [x,DashInsert(x)]
}
Example:
9633: 963-3
7774: 7-7-74
6826: 6826
7386: 7-386
2145: 2145
7806: 7806
9499: 949-9
4117: 41-1-7
4920: 4920
14: 14
And now to check the regex.
([13579]) take any odd number and remember it (it can be used later with \1
(?=[13579]) Check if the next number is also odd, but don't take it (it still remains in the string)
'\1-' Output the first odd num and ab a - to it.
In other word:
Puts a - between each two odds numbers.
I have a very long string of around 2000 chars. The string is a join of segments with first two chars of each segment as the segment indicator.
Eg- '11xxxxx 12yyyy 14ddddd gghgfbddc 0876686589 SANDRA COLINS 201 STMONK CA'
Now I want to extract the segment with indicator 14.
I achieved this using:
str.split(' ').each do |substr|
if substr.starts_with?('14')
key = substr.slice(2,5).to_i
break
end
end
I feel there should be a better way to do this. I am not able to find a more direct and one line solution for string matching in ruby. Please someone suggest a better approach.
It's not entirely clear what you're looking for, because your example string shows letters, but your title says digits. Either way, this is a good task for a regular expression.
foo = '12yyyy 014dddd 14ddddd gghgfbddc'
bar = '12yyyy 014dddd 1499999 gghgfbddc'
baz = '12yyyy 014dddd 14a9B9z gghgfbddc'
foo[/\b14[a-zA-Z]+/] # => "14ddddd"
bar[/\b14\d+/] # => "1499999"
baz[/\b14\w+/] # => "14a9B9z"
foo[/\b14\S+/] # => "14ddddd"
bar[/\b14\S+/] # => "1499999"
baz[/\b14\S+/] # => "14a9B9z"
In the patterns:
\b means word-break, so the pattern has to start at a transition between spaces or punctuation.
[a-zA-Z]+ means one or more letters.
\d+ means one or more digits.
\w+ means one or more of letters, digits and '_'. That is equivalent to the character set [a-zA-Z0-9_]+.
\S+ means non-whitespace, which is useful if you want everything up to a space.
Which of those is appropriate for your use-case is really up to you to decide.
I would like to insert a <wbr> tag every 5 characters.
Input: s = 'HelloWorld-Hello guys'
Expected outcome: Hello<wbr>World<wbr>-Hell<wbr>o guys
s = 'HelloWorld-Hello guys'
s.scan(/.{5}|.+/).join("<wbr>")
Explanation:
Scan groups all matches of the regexp into an array. The .{5} matches any 5 characters. If there are characters left at the end of the string, they will be matched by the .+. Join the array with your string
There are several options to do this. If you just want to insert a delimiter string you can use scan followed by join as follows:
s = '12345678901234567'
puts s.scan(/.{1,5}/).join(":")
# 12345:67890:12345:67
.{1,5} matches between 1 and 5 of "any" character, but since it's greedy, it will take 5 if it can. The allowance for taking less is to accomodate the last match, where there may not be enough leftovers.
Another option is to use gsub, which allows for more flexible substitutions:
puts s.gsub(/.{1,5}/, '<\0>')
# <12345><67890><12345><67>
\0 is a backreference to what group 0 matched, i.e. the whole match. So substituting with <\0> effectively puts whatever the regex matched in literal brackets.
If whitespaces are not to be counted, then instead of ., you want to match \s*\S (i.e. a non whitespace, possibly preceded by whitespaces).
s = '123 4 567 890 1 2 3 456 7 '
puts s.gsub(/(\s*\S){1,5}/, '[\0]')
# [123 4 5][67 890][ 1 2 3 45][6 7]
Attachments
Source code and output on ideone.com
References
regular-expressions.info
Finite Repetition, Greediness
Character classes
Grouping and Backreferences
Dot Matches (Almost) Any Character
Here is a solution that is adapted from the answer to a recent question:
class String
def in_groups_of(n, sep = ' ')
chars.each_slice(n).map(&:join).join(sep)
end
end
p 'HelloWorld-Hello guys'.in_groups_of(5,'<wbr>')
# "Hello<wbr>World<wbr>-Hell<wbr>o guy<wbr>s"
The result differs from your example in that the space counts as a character, leaving the final s in a group of its own. Was your example flawed, or do you mean to exclude spaces (whitespace in general?) from the character count?
To only count non-whitespace (“sticking” trailing whitespace to the last non-whitespace, leaving whitespace-only strings alone):
# count "hard coded" into regexp
s.scan(/(?:\s*\S(?:\s+\z)?){1,5}|\s+\z/).join('<wbr>')
# parametric count
s.scan(/\s*\S(?:\s+\z)?|\s+\z/).each_slice(5).map(&:join).join('<wbr>')