1

I have a string:

"a_b_c_d_e"

I would like to build a list of substrings that result from removing everything after a single "_" from the string. The resulting list would look like:

['a_b_c_d', 'a_b_c', 'a_b', 'a']

What is the most rubyish way to achieve this?

4 Answers 4

8
s = "a_b_c_d_e"
a = []
s.scan("_"){a << $`}                                                            #`
a # => ["a", "a_b", "a_b_c", "a_b_c_d"]
Sign up to request clarification or add additional context in comments.

7 Comments

clever use of comment to fix syntax highlight problem.
Also a clever answer, though not sure it's most Rubyish. Seems kinda Perlish to me :) Regardless, upvoted.
The best answer here.
Educational, as always.
@Dickie, see "Special global variables" at Regexp: "$` contains string before match". Changing the block to {puts $`; a << $`} causes the following to be printed: "a", "a_b", "a_b_c", "a_b_c_d".
|
3

You can split the string on the underscore character into an Array. Then discard the last element of the array and collect the remaining elements in another array joined by underscores. Like this:

str = "a_b_c_d_e"
str_ary = str.split("_") # will yield ["a","b","c","d","e"]
str_ary.pop # throw out the last element in str_ary
result_ary = [] # an empty array where you will collect your results
until str_ary.empty?
  result_ary << str_ary.join("_") #collect the remaining elements of str_ary joined by underscores
  str_ary.pop
end

# result_ary = ["a_b_c_d","a_b_c","a_b","a"]

Hope this helps.

1 Comment

EDITED: need to pop one element before entering the loop and then pop again as the last instruction in the loop. Previous solution pushed an empty string into result_ary
3

I am not sure about “most rubyish”, my solutions would be:

str = 'a_b_c_d_e'

(items = str.split('_')).map.with_index do |_, i| 
  items.take(i + 1).join('_')
end.reverse
########################################################
(items = str.split('_')).size.downto(1).map do |e|
  items.take(e).join('_')
end
########################################################
str.split('_').inject([]) do |memo, l| 
  memo << [memo.last, l].compact.join('_') 
end.reverse
########################################################
([items]*items.size).map.with_index(&:take).map do |e| 
  e.join('_')
end.reject(&:empty?).reverse

My fave:

([str]*str.count('_')).map.with_index do |s, i| 
  s[/\A([^_]+_){#{i + 1}}/][0...-1]
end.reverse

2 Comments

I like your last approach. However, I don't quite understand everything about the regex you've used. Can you please explain it better?
i+1 times match blah_. Results in blah_blah_blah_. I don’t like chop function on string, that’s why I take everything but last symbol manually then.
1

Ruby ships with a module for abbreviation.

require "abbrev"

puts ["a_b_c_d_e".tr("_","")].abbrev.keys[1..-1].map{|a| a.chars*"_"}
# => ["a_b_c_d", "a_b_c", "a_b", "a"]

It works on an Array with words - just one in this case. Most work is removing and re-placing the underscores.

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.