Reputation: 263
Say I have an array of strings like this:
array = ["foo", "(bar)", "baaz", "quux", "herp", "(derp)"]
And I need to join items starting with "(" with the previous item to get output like this:
["foo (bar)", "baaz", "quux", "herp (derp)"]
I guess it has to be something like get indices of array items that match /^\(/
and then iterate over original array in a block, joining items at index-1..index
, and deleting at index
Upvotes: 3
Views: 1571
Reputation: 110685
Here's another way, using Enumerable#chunk. I assume that the first character of the first element of the array is not (
, but the method could of course be modified if that assumption is not correct.
Code
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
Examples
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"]
Explanation
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)"]
Upvotes: 2
Reputation: 110685
I cannot improve upon @sawa's answer, but I can offer an alternative approach that some readers may find useful at another time in another place:
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)"]
This is what's happening.
arr = []
enum = array.each
#=> #<Enumerator: ["foo", "(bar)", "baaz", "quux", "herp", "(derp)"]:each>
Let's now step through the loop until a StopIteration
exception is raised:
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
The StopIteration
exception is handled by Kernel#loop by breaking the loop.
arr #=> ["foo (bar)", "baaz", "quux", "herp (derp)"]
Upvotes: 2
Reputation: 37409
Looking at it from another direction - joining the whole string, then splitting it along spaces which do not have (
after them:
array.join(' ').split(/ (?!\()/)
# => ["foo (bar)", "baaz", "quux", "herp (derp)"]
Upvotes: 5
Reputation: 168101
array.slice_before{|s| !s.start_with?("(")}.map{|a| a.join(" ")}
# => ["foo (bar)", "baaz", "quux", "herp (derp)"]
Upvotes: 5