10

Given some array such as the following:

x = ['a', 'b', 'b', 'c', 'a', 'a', 'a']

I want to end up with something that shows how many times each element repeats sequentially. So maybe I end up with the following:

[['a', 1], ['b', 2], ['c', 1], ['a', 3]]

The structure of the results isn't that important... could be some other data types of needed.

5
  • 5
    Why is it tagged with functional-programming? Commented Nov 17, 2011 at 8:13
  • 1
    Because it is a problem that is solved with a higher order function (in this case, a function implemented by fold en.m.wikipedia.org/wiki/Fold_(higher-order_function) ) Commented Nov 17, 2011 at 8:22
  • ^ Why should the question imply the solution method? Commented Nov 17, 2011 at 13:28
  • 1
    @JohnBachir It is a problem that can be solved with a higher order function. It can also be solved by many other techniques. Commented Nov 18, 2011 at 1:18
  • 1
    clarification: the question-asker suspected that the problem is one that is commonly solved concisely by higher-order functions or their derivatives, and so she wanted to attract the attention of Stack Overflow users who have experience in this space. Judging by the answers presented below, her suspicion seems to have been correct (although perhaps the nature of the answers below were influenced by her initial tagging, so admittedly it's not conclusive data). Commented Nov 18, 2011 at 1:34

4 Answers 4

28

1.9 has Enumerable#chunk for just this purpose:

x.chunk{|y| y}.map{|y, ys| [y, ys.length]}
Sign up to request clarification or add additional context in comments.

4 Comments

Although I am not sure if that is the purpose behind chunk.
unpack the arguments in the map: x.chunk { |y| y }.map { |y, ys| [y, ys.length] }
+1. I hadn't thought of using chunk this way, but this is the way to go. Removing my previous suggestion.
@Swanand: sure, chunk's basic purpose is exactly this, grouping consecutive elements (and then you decide what to do with the pairs element/group). It's like Python's itertools.groupby: [(y, len(list(ys))) for (y, ys) in itertools.groupby(x)]
1

This is not a general solution, but if you only need to match single characters, it can be done like this:

x.join.scan(/(\w)(\1*)/).map{|x| [x[0], x.join.length]}

2 Comments

This only works if items are single characters strings. x = ['cat', 'elephant', 'elephant', 'dog', 'eagle', 'eagle'] would fail.
@SteveWilhelm: Enumerable#Chunk would work on your example too. Please look at pguardiario's answer below.
1

Here's one line solution. The logic same as Matt suggested, though, works fine with nil's in front of x:

x.each_with_object([]) { |e, r| r[-1] && r[-1][0] == e ? r[-1][-1] +=1 : r << [e, 1] }

3 Comments

Just because a method can be written in a single line doesn't mean a method should be written in a single line. You're going to need 4-5 lines of comments just to explain how this works. Wouldn't those lines be put to better use with simple, explicit, and beautiful ruby code?
I agree, my solution might benefit from unfolding it into several lines. Though, I don't really like the one you suggested with explicit previous value and counts array. It doesn't look like ruby at all.
You're absolutely right. My answer probably could have benefitted from a better method from Array or Enumerable (beside #chunk). At the end of the day though, the code is simple, readable and effective. We don't always have the privilege of scanning ruby-doc, but when we do, we're refactoring code like mine :)
0

Here's my approach:

# Starting array
arr = [nil, nil, "a", "b", "b", "c", "a", "a", "a"]

# Array to hold final values as requested
counts = []

# Array of previous `count` element
previous = nil

arr.each do |letter|
  # If this letter matches the last one we checked, increment count
  if previous and previous[0] == letter
    previous[1] += 1

  # Otherwise push a new array for letter/count
  else
    previous = [letter, 1]
    counts.push previous
  end
end

I should note that this doesn't suffer from the same problem that Matt Sanders describes, since we're mindful of our first time through the iteration.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.