如何在ruby中根据条件连接一些(不是全部)数组元素



假设我有一个这样的字符串数组:

array = ["foo", "(bar)", "baaz", "quux", "herp", "(derp)"]

我需要将以"("开头的项与前一项连接起来,以获得如下输出:

["foo (bar)", "baaz", "quux", "herp (derp)"]

我猜它必须是类似于获得与/^(/匹配的数组项的索引,然后在块中迭代原始数组,在index-1..index处连接项,并在index处删除

array.slice_before{|s| !s.start_with?("(")}.map{|a| a.join(" ")}
# => ["foo (bar)", "baaz", "quux", "herp (derp)"]

从另一个方向看——连接整个字符串,然后沿着后面没有(的空格拆分它:

array.join(' ').split(/ (?!()/)
# => ["foo (bar)", "baaz", "quux", "herp (derp)"]

我无法改进@sawa的答案,但我可以提供另一种方法,一些读者可能会在其他时间和其他地方发现有用的方法:

array = ["foo", "(bar)", "baaz", "quux", "herp", "(derp)"]
arr = []
enum = array.each
loop do
  arr << enum.next
  next_up = enum.peek
  if next_up[0] == ?(
    arr[-1] += (" " + next_up)
    enum.next
  end
end
arr #=> ["foo (bar)", "baaz", "quux", "herp (derp)"]

事情就是这样。

arr = []
enum = array.each
  #=> #<Enumerator: ["foo", "(bar)", "baaz", "quux", "herp", "(derp)"]:each>

现在让我们遍历循环,直到引发StopIteration异常:

s = enum.next                #=> "foo"
arr << s                     #=> ["foo"]
next_up = enum.peek          #=> "(bar)"
next_up[0] == ?(             #=> true
  arr[-1] += (" " + next_up) #=> "foo (bar)"
    arr                      #=> ["foo (bar)"]
  enum.next                  #=> "(bar)" (discard)
s = enum.next                #=> "baaz"
arr << s                     #=> ["foo (bar)", "baaz"]
next_up = enum.peek          #=> "quux"
next_up[0] == ?(             #=> false
s = enum.next                #=> "quux"
arr << s                     #=> ["foo (bar)", "baaz", "quux"]
next_up = enum.peek          #=> "herp"
next_up[0] == ?(             #=> false
s = enum.next                #=> "herp"
arr << s                     #=> ["foo (bar)", "baaz", "quux", "herp"]
next_up = enum.peek          #=> "(derp)"
next_up[0] == ?(             #=> true
  arr[-1] += (" " + next_up) #=> "herp (derp)"
    arr                      #=> ["foo (bar)", "baaz", "quux", "herp (derp)"]
  enum.next                  #=> "(derp)" (discard)
s = enum.next                #=> StopIteration: iteration reached an end

StopIteration异常由Kernel#loop通过打破循环来处理。

arr                          #=> ["foo (bar)", "baaz", "quux", "herp (derp)"]

这里有另一种方法,使用Enumerable#chunk。我假设数组的第一个元素的第一个字符不是(,但是如果这个假设不正确,当然可以修改方法。

def doit(array)  
  array.chunk { |s| s[0] == ?( }
       .map(&:last)
       .each_slice(2)
       .map { |arr| (arr.size == 2) ? [arr.first[0..-2],
                      [arr.first.last, *arr.last].join(' ')] : arr }
       .flatten
end

array = ["foo", "(bar)", "baaz", "quux", "herp", "(derp)"]
doit(array) #=> ["foo (bar)", "baaz", "quux", "herp (derp)"]
array = ["foo", "(bar)", "(anther bar)", "quux"]    
doit(array) #=> ["foo (bar) (anther bar)", "quux"]

array = ["foo", "(bar)", "baaz", "quux", "herp", "(derp)"]
enum1 = array.chunk { |s| s[0] == ?( }
  #=> #<Enumerator: #<Enumerator::Generator:0x00000101142ce0>:each>
enum1.to_a # elements to be enumerated (for information only)
  #=> [[false, ["foo"]], [true, ["(bar)"]],
  #    [false, ["baaz", "quux", "herp"]], [true, ["(derp)"]]]
a = enum1.map(&:last)
  #=> [["foo"], ["(bar)"], ["baaz", "quux", "herp"], ["(derp)"]]
enum2 = a.each_slice(2)
  #=> #<Enumerator: [["foo"], ["(bar)"], ["baaz", "quux", "herp"],
  #                  ["(derp)"]]:each_slice(2)>
enum2.to_a # elements to be enumerated (for information only)
  #=> [[["foo"], ["(bar)"]], [["baaz", "quux", "herp"], ["(derp)"]]]
c = enum2.map { |arr| (arr.size==2) ? [arr.first[0..-2],
                        [arr.first.last, *arr.last].join(' ')] : arr }
  #=> [[[], "foo (bar)"], [["baaz", "quux"], "herp (derp)"]]
c.flatten
  #=> ["foo (bar)", "baaz", "quux", "herp (derp)"]

最新更新