Jgoo83
Jgoo83

Reputation: 377

Consecutive letter frequency

I am trying to write code to determine consecutive frequency of letters within a string.

For example:

"aabbcbb" => ["a",2],["b",2],["c", 1], ["b", 2]

My code gives me the first letter frequency but doesn't move on to the next.

def encrypt(str)
  array = []
  count = 0
   str.each_char do |letter|

    if array.empty?
      array << letter
      count += 1
    elsif array.last == letter
      count += 1
    else
      return [array, count]
      array = []
    end
  end
end

Upvotes: 4

Views: 1474

Answers (7)

Jgoo83
Jgoo83

Reputation: 377

def encrypt(str)

  count = 0
  array = []
  str.chars do |letter|

    if array.empty?
      array << letter
      count += 1
    elsif array.last == letter
      count += 1
    else
      puts "[#{array}, #{count}]"
      array.clear
      count = 0
      array << letter
      count += 1
    end
  end
  puts "[#{array}, #{count}]"
end

Upvotes: 0

sawa
sawa

Reputation: 168131

"aabbcbb".chars.slice_when(&:!=).map{|a| [a.first, a.length]}
# => [["a", 2], ["b", 2], ["c", 1], ["b", 2]]

Upvotes: 8

user229044
user229044

Reputation: 239312

There's a simple regular expression-based solution involving back-references:

"aabbbcbb".scan(/((.)\2*)/).map { |m,c| [c, m.length] }
# => [["a", 2], ["b", 3], ["c", 1], ["b", 2]]

But I would prefer the chunk method for clarity (and almost certainly efficiency).


Actually out of curiosity, I wrote a quick benchmark and scan is a little more than four times faster than chunk.map, but I'd still use chunk.map for clarity unless you're actually doing this hundreds of thousands of times:

require 'benchmark'

N = 10000

data = ('a'..'z').map { |c| c * 10 }.join("")

Benchmark.bm do |bm|
  bm.report do
    N.times { data.chars.chunk{ |c| c }.map { |c, a| [c, a.size] } }
  end

  bm.report do
    N.times { data.scan(/((.)\2*)/).map { |m,c| [c, m.size] } }
  end
end
     user     system      total        real
 0.800000   0.010000   0.810000 (  0.803824)
 0.190000   0.000000   0.190000 (  0.192915)

Upvotes: 4

Cary Swoveland
Cary Swoveland

Reputation: 110685

@steenslag gave the answer I would have given, so I'll try something different.

"aabbcbb".each_char.with_object([]) { |c,a| (a.any? && c == a.last.first) ?
  a.last[-1] += 1 : a << [c, 1] }
  #=> [["a", 2], ["b", 2], ["c", 1], ["b", 2]]

Upvotes: 0

Hector Correa
Hector Correa

Reputation: 26690

There are several errors with your implementation, I would try with a hash (rather than an array) and use something like this:

def encrypt(str)

  count = 0
  hash = {}
  str.each_char do |letter|

    if hash.key?(letter)
      hash[letter] += 1
    else
      hash[letter] = 1
    end

  end

  return hash
end

puts encrypt("aabbcbb")

Upvotes: -2

Ajedi32
Ajedi32

Reputation: 48368

You need to build up an array of results, rather than simply stopping at the first one:

def consecutive_frequencies(str)
  str.each_char.reduce([]) do |frequencies_arr, char|
    if frequencies_arr.last && frequencies_arr.last[0] == char
      frequencies_arr.last[1] += 1
    else
      frequencies_arr << [char, 1]
    end

    frequencies_arr
  end
end

Upvotes: 0

steenslag
steenslag

Reputation: 80065

p "aabbcbb".chars.chunk{|c| c}.map{|c, a| [c, a.size]} 
# => [["a", 2], ["b", 2], ["c", 1], ["b", 2]]

Upvotes: 11

Related Questions