I wrote this ruby code for a challenge. The code removes all non digit characters from a string and then groups the digits in threes if the digit length is of modulus 3, if it is not the last set of digits are grouped in two's.
Examples
"5551231234"
would be grouped like this: ["555", "123", "12", "34"]
.
"18883319"
would be grouped like this: ["188", "833", "19"]
.
"123456"
would be ["123", "456"]
.
"1234567891011"
would be ["123", "456", "789", "10", "11"]
.
I have come up with this logic using regex to solve the challenge but I am being asked if there is a way to make the logic shorter.
def format(s)
return s.gsub(/[^0-9]/, '')
.scan(/(?=\d{5})(\d{3})|(?=\d{3}$)(\d{3})|(\d{1,2})/)
.join('-')
.gsub(/- /, '-')
.gsub(/^-|-$/, '')
end
The result from the scan gives a lot of whitespaces so after the join operation, I am left with either a double dash or triple dashes so I used this .gsub(/- /, '-')
to fix that. I also noticed sometimes there is a dash at the begin or the end of the string, so I used .gsub(/^-|-$/, '')
to fix that too
Any Ideas?
CodePudding user response:
Inspired by @steenslag's recommendation. (There are quite a few other ways to achieve the same with varying levels of verbosity and esotericism)
Here is how I would go about it:
def format_str(str)
numbers = str.delete("^0-9").scan(/.{1,3}/)
# there are a number of ways this operation can be performed
numbers.concat(numbers.pop(2).join.scan(/../)) if numbers.last.length == 1
numbers.join('-')
end
Breakdown:
numbers = str.delete("^0-9")
- delete any non numeric character from the string.scan(/.{1,3}/)
- scan them into groups of 1 to 3 charactersnumbers.concat(numbers.pop(2).join.scan(/../)) if numbers.last.length == 1
- If the last element has a length of 1 then remove the last 2 elements join them and then scan them into groups of 2 and add these groups back to the Arraynumbers.join('-')
- join the numbers with a hyphen to return a formatted String
Example:
require 'securerandom'
10.times do
s = SecureRandom.hex
puts "Original: #{s} => Formatted: #{format_str(s)}"
end
# Original: fd1bbce41b1c784ce6ad5303d868bbe9 => Formatted: 141-178-465-303-86-89
# Original: af04bd4d4d6beb5a0412a692d5d3d42d => Formatted: 044-465-041-269-253-42
# Original: 9a1833a43cbef51c3f3c21baa66fe996 => Formatted: 918-334-351-332-166-996
# Original: 4104ae13c998cec896997b9919bdafb3 => Formatted: 410-413-998-896-997-991-93
# Original: 0eb49065472240ba32b3c029f897b30d => Formatted: 049-065-472-240-323-029-897-30
# Original: 4c68f9f68e8f6132c0ed5b966d639cf4 => Formatted: 468-968-861-320-596-663-94
# Original: 65987ee04aea8fb533dbe38c0fea7d63 => Formatted: 659-870-485-333-807-63
# Original: aa8aaf1cf59b52c9ad7db6d4b1ae0cbb => Formatted: 815-952-976-410
# Original: 8eb6b457059f91fd06ccbac272db8f4e => Formatted: 864-570-599-106-272-84
# Original: 1c65825ed59dcdc6ec18af969938ea57 => Formatted: 165-825-596-189-699-38-57
That being said to modify your existing code this will work as well:
def format_str(str)
str
.delete("^0-9")
.scan(/(?=\d{5})\d{3}|(?=\d{3}$)\d{3}|\d{2}/)
.join('-')
end
CodePudding user response:
- Slice the string in chunks of max 3 digits. (
s.scan(/.{1,3}/
) - Check if the last chunk has only 1 character. If so, take the last char of the chunk before and prepend it to the last.
- Glue the chunks together using
join(" ")
CodePudding user response:
Without changing your code too much and without adjusting your actual regex, I might suggest replacing scan
with split
in order to avoid all the extra nil values; replacing gsub
with tr
which is much faster; and then using reject(&:empty?)
to loop through and remove an blank array elements before joining with whatever character you want:
string = "12345fg\n\t\t\t 67"
string.tr("^0-9", "")
.split(/(?=\d{5})(\d{3})|(?=\d{3}$)(\d{3})|(\d{1,2})/)
.reject(&:empty?)
.join("-")
#=> 123-45-67
CodePudding user response:
An approach:
def foo(s)
s.gsub(/\D/, '').scan(/\d{1,3}/).each_with_object([]) do |x, arr|
if x.size == 3 || arr == []
arr << x
else
y = arr.last
arr[-1] = y[0...-1]
arr << "#{y[-1]}#{x}"
end
end
end
Remove all non-digits characters, then scan for 1 to 3 digit chunks. Iterate over them. If it's the first time through or the chunk is three digits, add it to the array. If it isn't, take the last digit from the previous chunk and prepend it to the current chunk and add that to the array.
Alternatively, without generating a new array.
def foo(s)
s.gsub(/\D/, '').scan(/\d{1,3}/).instance_eval do |y|
y.each_with_index do |x, i|
if x.size == 1
y[i] = "#{y[i-1][-1]}#{x}"
y[i-1] = y[i-1][0...-1]
end
end
end
end