1

I have a array of string

test= ["ChangeServer<br/>Test: 3-7<br/>PinCode:DFSFSDFB04008<br/>ShipCode:DFADFSDFSDM-000D3<br/>SomeCode:sdfsdf", "werwerwe", "adfsdfsd", 
"sdfsdfsdfsd<br/>Test: 9<br/>PinCode:ADFSDF4NS0<br/>ShipCode:FADFSDFD-9ZM170<br/>"]

I want to grab the number after Test: which in the above array of string are 3, 4, 5, 6, 7 ( range 3-7) and 9

Desired output:

["3","4","5","6","7","9"]

What I tried so far

test.join.scan(/(?<=Test: )[0-9]+/)
=> ["3", "7"]

How to deal with range?

Second test case:

    test= ["ChangeServer<br/>Test: 3-7<br/>PinCode:DFSFSDFB04008<br/>ShipCode:DFADFSDFSDM-000D3<br/>SomeCode:sdfsdf", "werwerwe", "adfsdfsd", 
"sdfsdfsdfsd<br/>Test: 9<br/>PinCode:ADFSDF4NS0<br/>ShipCode:FADFSDFD-9ZM170<br/>", "sdfsdfsdfsd<br/>Test: 15-18<br/>PinCode:ADFSDF4NS0<br/>ShipCode:FADFSDFD-9ZM170<br/>"]

Desired output:

["3","4","5","6","7","9","15","16","17","18"]
3
  • Are the numbers always single-digit? Commented Jun 28, 2017 at 16:35
  • No, i may be double or triple. Commented Jun 28, 2017 at 16:42
  • 2
    Do it step by step: 1) extract "3-7" and "9", 2) Figure out how to convert "3-7" to ['3','4','5','6','7'], 3) combine the results Commented Jun 28, 2017 at 17:01

3 Answers 3

5

There are a lot of ways you could solve this. I'd probably do it this way:

test.flat_map do |s|
  _, m, n = *s.match(/Test:\s*(\d+)(?:-(\d+))?/)
  m ? (m..n||m).to_a : []
end

See it in action on repl.it: https://repl.it/JFwT/13

Or, more succinctly:

test.flat_map {|s| s.match(/Test:\s*(\d+)(?:-(\d+))?/) { $1..($2||$1) }.to_a }

https://repl.it/JFwT/11

Sign up to request clarification or add additional context in comments.

6 Comments

Interesting and neat solution! BTW, you could get the exact desired output (strings instead of numbers) removing to_i.
Since the OP wants an array of strings, you could shorten it to m.upto(n || m).to_a
@Gerry, Stefan: Thanks for your suggestions; I've updated my answer.
I really enjoyed this answer especially the "succinct" version. So just for fun Benchmarks available at repl.it/JFwT/16
@engineersmnky It was fun to write. I'm not surprised by the benchmark result. Array#join is pretty well-optimized.
|
4

You could create a new Range for each range found (i.e N-N) using the splat operator (i.e. *) and combine the results, like this 1:

test.join.scan(/(?<=Test: )[0-9-]+/)
         .flat_map { |r| Range.new(*r.split('-').values_at(0, -1)).to_a }
#=> ["3", "4", "5", "6", "7", "9"]

This will work for both examples.

1 Notice the the added - next to 0-9 in the regex.

Is the a way where we can include both Test: 1 (with space between Test: and 1) and Test:1 (without space between Test: and 1)?

Yes, update your regex (change where space is placed) and add an additional map to get rid of those spaces:

test.join
    .scan(/(?<=Test:)[ 0-9-]+/)
    .map(&:strip)
    .flat_map { |r| Range.new(*r.split('-').values_at(0, -1)).to_a }

And here's shortened option using two captures in the regex, as suggested by Jordan.

test.join
    .scan(/Test:\s*(\d+)(?:-(\d+))?/)
    .flat_map { |m,n| (m..n||m).to_a }

10 Comments

Thank you but there is extra number 8 which shouldn't be there.
Is the a way where we can include both Test: 1(with space between Test: and 1) and Test:1 (without space between Test: and 1)?
@iceツ I also thought of that, but Range.new need 2..3 arguments, so won't work for all cases.
flat_map { |r| Range.new(*r.split('-').values_at(0, -1)).to_a } would work for both, a-b notation and single values.
@Gerry I'm pretty happy with my answer without join, so you're welcome to it.
|
0

Just out of curiosity:

test.
  join.
  scan(/(?<=Test: )[\d-]+/).
  map { |e| e.gsub(/\A\d+\Z/) { |m| "#{m}..#{m}" }.gsub('-', '..') }.
  map(&method(:eval)).
  flat_map(&:to_a)

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.