Barris
Barris

Reputation: 1019

Nested loops in Ruby

I am trying to count the number of similar prefix beginnings to a string in Ruby. e.g; input "ababaa" should output 11;

ababaa = 6
 babaa = 0
  abaa = 3
   baa = 0
    aa = 1
     a = 1

I have got as far as the code below, using a nested loop to go through each of the above as an array, however it looks as though Ruby is currently outputting the count of just the first Array object, "ababaa".

Solved, thanks :)

def string_suffix(string)
num = 0
ary = []
string.length.times do
  ary << string[num..string.length]
  num = num + 1
end
result = 0
ary.each do |x| # ["ababaa", "babaa", "abaa", "baa", "aa", "a"] 
  x.chars.each_with_index do |c,index|
    break unless c == string[index]
      result = result + 1
  end
end
return result
end

I have looked far and wide and still cannot solve the issue, It looks like the (final, nested) array is breaking after the first iteration of the 'ary' Array and just returning that output.

Upvotes: 0

Views: 1973

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110675

As I understand, the problem is this: given a string s, for each i = 0..s.size-1, compute the number of leading characters of s[0..-i-1] that match the corresponding characters (i.e., at same offsets) of s[i..-1], and sum these s.size subtotals.

Here's a Ruby-like way to do that, using Enumerable#reduce (aka inject) and Enumerable#take_while:

str = "ababaa"

arr = str.chars
(0...arr.size).reduce(0) do |tot,i|
  tot + arr[0..-i-1].zip(arr[i..-1]).take_while { |x,y| x == y }.size
end
  #=> 11

The steps:

arr = str.chars
  #=> ["a", "b", "a", "b", "a", "a"] 
r = 0...arr.size
  #=> 0...6 

When the first element of r is passed to the block, the block variables are set to:

tot = 0
i   = 0

The block calculation is therefore as follows:

a = arr[0..-i-1].zip(arr[i..-1])
  #=> arr[0..-1].zip(arr[0..-1])
  #=> arr.zip(arr)
  #=> ["a", "b", "a", "b", "a", "a"].zip(["a", "b", "a", "b", "a", "a"])
  #=> [["a", "a"], ["b", "b"], ["a", "a"], ["b", "b"], ["a", "a"], ["a", "a"]]

b = a.take_while { |x,y| x == y }
  #=> [["a", "a"], ["b", "b"], ["a", "a"], ["b", "b"], ["a", "a"], ["a", "a"]]

tot + b.size
  #=> 0 + 6
  #=> 6

Note that this calculation will always equal arr.size for the first element of arr passed to the block.

When the next element of arr is passed to the block, the block variable i is set to 1. tot, which we just computed, equals 6. The block calculation is therefore:

a = arr[0..-i-1].zip(arr[i..-1])
  #=> arr[0..-2].zip(arr[1..-1])
  #=> ["a", "b", "a", "b", "a"].zip(["b", "a", "b", "a", "a"])
  #=> [["a", "b"], ["b", "a"], ["a", "b"], ["b", "a"], ["a", "a"]] 

b = a.take_while { |x,y| x == y }
  #=> [] 
tot + b.size
  #=> 6 + 0
  #=> 6

The remaining calculations are similar. After all elements of arr have been sent to the block, reduce returns the value of tot.

Upvotes: 0

JHobern
JHobern

Reputation: 864

You are returning the result while you are still in the loop. You need to move result = 0 out of the loop, and move the return result statement outside of the loop too. At the moment the function is going through the first iteration of the loop ("ababaa", for which all characters match), but you want result to equal the sum of all results.

Additionally, instead of doing:

count = 0
x.chars.each do |x|
    if x == string[count]
        count = count + 1
        result = result + 1
    else
        count = count + 1
    end
end

You could use the function each_with_index, to get

x.chars.each_with_index do |c,index|
    if c == string[index]
        result = result + 1
    end
end

However, since you are trying to count how many characters in the substring are a prefix of string, you want to break when you first find a character c that is not equal to string[index], so that you don't end up counting extra characters. The loop then becomes:

x.chars.each_with_index do |c,index|
    if c == string[index]
        result = result + 1
    else
        break
    end
end

Upvotes: 3

Tess
Tess

Reputation: 150

I noticed you are returning the result inside your second loop, at the end. This means that after you've gone through the first item in your array the function returns just the result for the first item. Move your return statement to outside the loop.

Upvotes: 0

Related Questions