John
John

Reputation: 1323

How to expand a string in Ruby based on some condition?

I have a string a5bc2cdf3. I want to expand it to aaaaabcbccdfcdfcdf.

In the string is a5, so the resulting string should contain 5 consecutive "a"s, "bc2" results in "bc" appearing 2 times consecutively, and cdf should repeat 3 times.

If input is a5bc2cdf3, and output is aaaaabcbccdfcdfcdf how can I do this in a Ruby method?

def get_character("compressed_string",index)
  expanded_string = calculate_expanded_string(compressed_string)
  required_char = expanded_string(char_at, index_number(for eg 3))
end

def calculate_expanded_string(compressed_string)
  return expanded
end

Upvotes: 1

Views: 370

Answers (4)

the Tin Man
the Tin Man

Reputation: 160631

I'd use:

str = "a5bc2cdf3"
str.split(/(\d+)/).each_slice(2).map { |s, c| s * c.to_i }.join # => "aaaaabcbccdfcdfcdf"

Here's how it breaks down:

str.split(/(\d+)/) # => ["a", "5", "bc", "2", "cdf", "3"]

This works because split will return the value being split on if it's in a regex group: /(\d+)/.

str.split(/(\d+)/).each_slice(2).to_a  # => [["a", "5"], ["bc", "2"], ["cdf", "3"]]

The resulting array can be broken into the string to be repeated and its associated count using each_slice(2).

str.split(/(\d+)/).each_slice(2).map { |s, c| s * c.to_i } # => ["aaaaa", "bcbc", "cdfcdfcdf"]

That array of arrays can then be processed in a map that uses String's * to repeat the characters.

And finally join concatenates all the resulting expanded strings back into a single string.

Upvotes: 0

Wiktor Stribiżew
Wiktor Stribiżew

Reputation: 627607

You may use a regex like

.gsub(/([a-zA-Z]+)(\d+)/){$1*$2.to_i}

See the Ruby online demo

The /([a-zA-Z]+)(\d+)/ will match stubstrings with 1+ letters (([a-zA-Z]+)) and 1+ digits ((\d+)) and will capture them into 2 groups that are later used inside a block to return the string you need.

Note that instead of [a-zA-Z] you might consider using \p{L} that can match any letters.

You want to break out of gsub once the specified index is reached in the original "compressed" string. It is still possible, see this Ruby demo:

s = 'a5bc2cdf3'                # input string
index = 5                      # break index
result = ""                    # expanded string
s.gsub!(/([a-zA-Z]+)(\d+)/){   # regex replacement
    result << $1*$2.to_i       # add to the resulting string
    break if Regexp.last_match.end(0) >= index  # Break if the current match end index is bigger or equal to index
}
puts result[index]            # Show the result
# => b

For brevity, you may replace Regexp.last_match with $~.

Upvotes: 3

Sagar Pandya
Sagar Pandya

Reputation: 9508

Just another way. I prefer Wiktor's method by a long way.

def stringy str, index
  lets, nums = str.split(/\d+/), str.split(/[a-z]+/)[1..-1].map(&:to_i)
  ostr = lets.zip(nums).map { |l,n| l*n }.join
  ostr[index]
end

str = 'a5bc2cdf3'
p stringy str, 5 #=> "b"

Upvotes: 0

Michael Sievers
Michael Sievers

Reputation: 46

I would propose to use scan to move over the compressed string, using a simple RegEx which detects groups of non-decimal characters followed by their count as decimal /([^\d]+)(\d+)/.

def get_character(compressed_string, index)
  result = nil

  compressed_string.scan(/([^\d]+)(\d+)/).inject(0) do |total_length, (chars, count)|
    decoded_string = chars * count.to_i
    total_length += decoded_string.length

    if index < total_length
      result = decoded_string[-(total_length - index)]
      break
    else
      total_length
    end
  end

  result
end

Knowing the current (total) length, one can break out of the loop if the current expanded string includes the requested index. The string is never decoded entirely.

This code gives the following results

get_character("a5bc2cdf3", 5)  # => "b"
get_character("a5bc2cdf3", 10) # => "d"
get_character("a5bc2cdf3", 20) # => nil

Upvotes: 2

Related Questions