0

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.

Barris
  • 969
  • 13
  • 29
  • 1
    Did you intend an assignment here: `if x = string[count]`? Because `=` is assignment, not check for equality. So the `if` expression will be `true` any time the value of `string[count]` is truthy, which is any time it's non-zero. – lurker Aug 27 '15 at 18:11
  • No I did not see that, thanks. The result is still the same, bringing back 6 – Barris Aug 27 '15 at 18:17
  • 1
    You're also zeroing out `result` each time through your `ary.each` loop. Put `result = 0` *before* `ary.each...`. `num` doesn't seem to be used for anything after the first loop, so `num = 0` is not needed. – lurker Aug 27 '15 at 18:22
  • Yes thanks, I've just seen that also. Of course the output is still 6. – Barris Aug 27 '15 at 18:29
  • You don't output anything except the result of the last iteration. If you want to output results for each item in the array then either (a) output it in the loop, or (b) collect the results and output them after the iteration is complete. – Dave Newton Aug 27 '15 at 18:32
  • OK I'll give up the last two clues (:)): (1) your `return result` is inside your outer loop so it returns too early. It needs to be after the next `end`. And (2) where you have `else count = count + 1` that should be a `break` because at that point you know you've stopped matching and should move on to the next substring (you'll count too many matches when there aren't any more). Fix those two things, then you'll get 11. – lurker Aug 27 '15 at 18:34
  • I don't understand your question. What do you mean by "suffix beginnings", considering that "suffix" refers to the end of a word. What do you mean by "similar"? Please explain why `'ababaa'` is `6` and `'babaa'` is zero. I may just be dense, as others seem to understand what you mean. – Cary Swoveland Aug 27 '15 at 19:14
  • Hi Cary, see the first example, sorry, 'prefix' is probably more accurate, but the count takes the number of matching characters until there is not a match, at which point the loops breaks and moves onto the next iteration, but one character removed from the beginning. – Barris Aug 27 '15 at 20:05
  • BTW, `string_suffix` is a pretty bad name. I would expect a method named `string_suffix` to, well, return the suffix of a string, not the sum of the length of the common prefixes of all suffixes. – Jörg W Mittag Aug 27 '15 at 20:13
  • Let's take `'ababaa'` as an example. Starting with the first character, `'a'`, we (as you stated) count "the number of matching characters until there is not a match". That would be zero. We now do the same for `'babaa'`, then `'abaa'`, then `'baa'`, all of which are are zero. Then `'aa'` gives `1` and, lastly, `'a'` gives zero, for a total of `1`, but you say it should be `6`. Please explain how you got `6` (a question I asked previously that you did not answer). – Cary Swoveland Aug 28 '15 at 00:20
  • No. 'babaa' is zero, 'abaa' is 3, 'baa' is 0, 'aa' is 1, as is 'a'. Go back and read the question. – Barris Aug 30 '15 at 08:24
  • I read the question several times before posting my comment. "the number of similar prefix beginnings" is meaningless. It's possible that the answer lies in your code, but I should not have to read it to understand the question. You still have not answered my question: "Please explain how you got `6`. Aside: when you reply to a comment you must include `@JohnDoe`, otherwise Mr. Doe will not be notified that you left a comment intended for him. – Cary Swoveland Aug 30 '15 at 16:21
  • Okay @CarySwoveland, (hope I got that right). In hindsight a better phrasing is probably; 'The number of matching prefix characters between the iteration and the original string, where in every iteration a first character is removed' – Barris Aug 30 '15 at 20:10
  • So to iterate through the original string with an (initially) exact copy of the string (the substring) to count how many of the characters match until they diverge. BUT for every iteration a first character is removed from the substring. When two characters don't match the loop breaks. Therefore to use the above example we use 'ababaa', the first result is 6, because the loop doesn't break and all of the characters match. The second is zero because the the first iteration breaks, 'a' != 'b'. 'abaa' returns 3 because it counts 3 matches before the characters do not match. – Barris Aug 30 '15 at 20:13
  • Thanks, I believe I now understand. If `str` is the original string, you first count the number of matches between corresponding characters of `str[0..-2]` and `str[1..-1]` (until a non-match occurs), then of `str[0..-3]` and `str[2..-1]` and so on, and then total those number of matches. – Cary Swoveland Aug 30 '15 at 21:35

3 Answers3

3

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
JHobern
  • 866
  • 1
  • 13
  • 20
  • Brilliant, thank you, although for some reason in my program your second suggestion is returning 13. – Barris Aug 27 '15 at 20:02
  • See my edited comment for the alteration that I have used. – Barris Aug 27 '15 at 20:02
  • That's because the logic you are using is slightly wrong, as explained by lurker on a comment on your question. You need to break if c != string[index], because then we know that no more of x is a prefix of string. I'll add it to my answer for you. – JHobern Aug 27 '15 at 20:08
  • Ah! yes of course, the loop will continue to iterate unless it is broken. Thanks again :) – Barris Aug 27 '15 at 20:13
0

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.

Tess
  • 140
  • 10
0

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.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100