Pergunta

Need help with this code on counting chars in a sequence.

This is what I want:

word("aaabbcbbaaa") == [["a", 3], ["b", 2], ["c", 1], ["b", 2], ["a", 3]]
word("aaaaaaaaaa") == [["a", 10]]
word("") == []

Here is my code:

def word(str)
words=str.split("")
count = Hash.new(0)

words.map {|char| count[char] +=1 }

return count
end

I got word("aaabbcbbaaa") => [["a", 6], ["b", 4], ["c", 1]], which is not what I want. I want to count each sequence. I prefer a none regex solution. Thanks.

Foi útil?

Solução

Split string by chars, then group chunks by char, then count chars in chunks:

def word str
  str
  .chars
  .chunk{ |e| e }
  .map{|(e,ar)| [e, ar.length] }
end

p word "aaabbcbbaaa"
p word("aaaaaaaaaa")
p word ""

Result:

[["a", 3], ["b", 2], ["c", 1], ["b", 2], ["a", 3]]
[["a", 10]]
[]

Outras dicas

If you don't want to use a regex, you may just have to do something like:

def word(str)
  last, n, result = str.chars.first, 0, []
  str.chars.each do |char|
    if char != last
      result << [last, n]
      last, n = char, 1
    else
      n += 1
    end
  end
  result << [last, n]
end

I'd like to use some higher-order function to make this more concise, but there's no appropriate one in the Ruby standard library. Enumerable#partition almost does it, but not quite.

I'd do the following. Note that each_char is a newer method (Ruby 1.9?) that might not be available on your version, so stick with words=str.split("") in that case.

def word(str)
  return [] if str.length == 0
  seq_count = []
  last_char = nil
  count = 0
  str.each_char do |char|
    if last_char == char
      count += 1
    else
      seq_count << [last_char, count] unless last_char.nil?
      count = 1
    end
    last_char = char
  end
  seq_count << [last_char, count]
end

[52] pry(main)> word("hello")
=> [["h", 1], ["e", 1], ["l", 2], ["o", 1]]

[54] pry(main)> word("aaabbcbbaaa")
=> [["a", 3], ["b", 2], ["c", 1], ["b", 2], ["a", 3]]

[57] pry(main)> word("")
=> []

Another non-regexp-version.

x = "aaabbcbbaaa"

def word(str)
  str.squeeze.reverse.chars.each_with_object([]) do |char, list|
    count = 0
    count += 1 until str.chomp!(char).nil?
    list << [char, count]
  end
end

p word(x) #=> [["a", 3], ["b", 2], ["c", 1], ["b", 2], ["a", 3]]

If the world were without regex and chunk:

def word(str)
  a = str.chars
  b = []
  loop do
    return b if a.empty?
    c = a.slice_before {|e| e != a.first}.first
    b << [c.first, c.size]
    a = a[c.size..-1]    
  end
end

word "aaabbcbbaaa" # => [["a", 3], ["b", 2], ["c", 1], ["b", 2], ["a", 3]]
word "aaa"         # => [["a",3]]
word ""            # => []

Here's another way. Initially I tried to find a solution that didn't require conversion of the string to an array of its characters. I couldn't come up with anything decent until I saw @hirolau 's answer, which I modified:

def word(str)
  list = []
  char = str[-1]
  loop do
    return list if str.empty?
    count = 0
    count += 1 until str.chomp!(char).nil?
    list.unshift [char, count]
    char = str[-1]
  end
end

You can use this pattern with scan:

"aaabbcbbaaa".scan(/((.)\2*)/)

and after count the number of char for all group 1

example:

"aaabbcbbaaaa".scan(/((.)\2*)/).map do |x,y| [y, x.length] end
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top