Home > Blockchain >  Shorten Code Logic For String Manipulation
Shorten Code Logic For String Manipulation

Time:12-29

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 characters
  • numbers.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 Array
  • numbers.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
  •  Tags:  
  • ruby
  • Related