简体   繁体   中英

How can I improve the performance of this small Ruby function?

I am currently doing a Ruby challenge and get the error Terminated due to timeout for some testcases where the string input is very long (10.000+ characters).

How can I improve my code?

Ruby challenge description

You are given a string containing characters A and B only. Your task is to change it into a string such that there are no matching adjacent characters. To do this, you are allowed to delete zero or more characters in the string.

Your task is to find the minimum number of required deletions.

For example, given the string s = AABAAB , remove A an at positions 0 and 3 to make s = ABAB in 2 deletions.

My function

def alternatingCharacters(s)
    counter = 0
    s.chars.each_with_index { |char, idx| counter += 1 if s.chars[idx + 1] == char }
    return counter
end

Thank you!

This could be faster returning the count:

str.size - str.chars.chunk_while{ |a, b| a == b }.to_a.size

The second part uses String#chars method in conjunction with Enumerable#chunk_while . This way the second part groups in subarrays:

'aababbabbaab'.chars.chunk_while{ |a, b| a == b}.to_a
#=> [["a", "a"], ["b"], ["a"], ["b", "b"], ["a"], ["b", "b"], ["a", "a"], ["b"]]

Trivial if you can use squeeze :

str.length - str.squeeze.length

Otherwise, you could try a regular expression that matches those A (or B ) that are preceded by another A (or B ):

str.enum_for(:scan, /(?<=A)A|(?<=B)B/).count

Using enum_for avoids the creation of the intermediate array.

The main issue with:

 s.chars.each_with_index { |char, idx| counter += 1 if s.chars[idx + 1] == char }

Is the fact that you don't save chars into a variable. s.chars will rip apart the string into an array of characters. The first s.chars call outside the loop is fine. However there is no reason to do this for each character in s . This means if you have a string of 10.000 characters, you'll instantiate 10.001 arrays of size 10.000.

Re-using the characters array will give you a huge performance boost:

require 'benchmark'

s  = ''
options = %w[A B]
10_000.times { s << options.sample }
Benchmark.bm do |x|
  x.report do
    counter = 0
    s.chars.each_with_index { |char, idx| counter += 1 if s.chars[idx + 1] == char }
    #           create a character array for each iteration ^
  end

  x.report do
    counter = 0
    chars = s.chars # <- only create a character array once
    chars.each_with_index { |char, idx| counter += 1 if chars[idx + 1] == char }
  end
end
       user     system      total        real
   8.279767   0.000001   8.279768 (  8.279655)
   0.002188   0.000003   0.002191 (  0.002191)

You could also make use of enumerator methods like each_cons and count to simplify the code, this doesn't increase performance cost a lot, but makes the code a lot more readable.

Benchmark.bm do |x|
  x.report do
    counter = 0
    chars = s.chars
    chars.each_with_index { |char, idx| counter += 1 if chars[idx + 1] == char }
  end

  x.report do
    s.each_char.each_cons(2).count { |a, b| a == b }
    #  ^ using each_char instead of chars to avoid
    #    instantiating a character array
  end
end
       user     system      total        real
   0.002923   0.000000   0.002923 (  0.002920)
   0.003995   0.000000   0.003995 (  0.003994)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM