在Ruby数组中计算元素的连续出现次数

10

考虑以下数组:

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

我希望得到一个显示每个元素按顺序重复次数的结果。因此,最终可能会得到以下内容:

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

结果的结构并不是那么重要...如果需要,可能还有其他的数据类型。


5
为什么它被打上了“functional-programming”的标签? - Swanand
1
因为这是一个使用高阶函数解决的问题(在这种情况下,是由fold实现的函数http://en.m.wikipedia.org/wiki/Fold_(higher-order_function))。 - John Bachir
为什么问题应该暗示解决方法? - Mladen Jablanović
1
@JohnBachir 这是一个可以用高阶函数解决的问题。它也可以通过许多其他技术来解决。 - Ben
1
澄清:问题提出者怀疑这个问题通常可以通过高阶函数或其衍生物来简洁地解决,因此她希望吸引那些在这个领域有经验的 Stack Overflow 用户的注意。根据下面呈现的答案,她的怀疑似乎是正确的(尽管也许下面的答案性质受到了她最初标记的影响,所以承认这不是确凿的数据)。 - John Bachir
4个回答

28

为此目的,1.9 版本引入了 Enumerable#chunk 方法:

x.chunk{|y| y}.map{|y, ys| [y, ys.length]}

虽然我不确定chunk的目的是否是这样。 - Swanand
在 map 中解包参数:x.chunk { |y| y }.map { |y, ys| [y, ys.length] } - tokland
+1. 我之前没有想到可以这样使用“chunk”,但这确实是正确的方法。我撤回以前的建议。 - Matt Sanders
@Swanand:当然,chunk 的基本目的就是这样,将连续的元素分组(然后您可以决定如何处理成对的元素/组)。这就像 Python 的 itertools.groupby:[(y, len(list(ys))) for (y, ys) in itertools.groupby(x)] - tokland

1

这不是一个通用的解决方案,但如果你只需要匹配单个字符,可以像这样完成:

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

只有当项目是单个字符字符串时,此方法才有效。 x = ['cat', 'elephant', 'elephant', 'dog', 'eagle', 'eagle'] 将失败。 - Steve Wilhelm
@SteveWilhelm:Enumerable#Chunk 在你的例子中也可以使用。请查看下面pguardiario的答案。 - Swanand

1

这是一行解决方案。逻辑与Matt建议的相同,尽管在x前面使用nil也可以正常工作:

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

4
仅仅因为一个方法可以写在一行代码里并不意味着这个方法应该被写成一行。你需要用4-5行注释来解释这个方法的运作方式。难道不是把这些行用在简单、明确和美丽的 Ruby 代码上更好吗? - bloudermilk
我同意,我的解决方案可能会从将其展开成几行中受益。但是,我并不真的喜欢你提出的那个需要显式先前值和计数数组的解决方案。它看起来一点也不像 Ruby。 - KL-7
你是绝对正确的。我的答案可能会受益于更好的 ArrayEnumerable 方法(除了 #chunk)。但归根结底,这段代码简单、易读且有效。我们并不总是有扫描 ruby-doc 的特权,但当我们拥有时,就像重构我这样的代码 :) - bloudermilk

0

这是我的方法:

# 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

我应该指出,这不会遭受Matt Sanders所描述的同样问题,因为我们在迭代的第一次时就非常注意。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接