Javascript match numbers by newline or comma

Viewed 79

I have a text file containing a mixture of characters, empty lines, and the values I want to pluck out - numbers which are separated either by linebreaks and, for backwards compatibility, as comma separated values.

# Here are some numbers such as 34 and 53
34
53
124

# Here's a number
45
44

# And a final number
72


# And here's a legacy set of comma separated numbers
23,24,25

So far I've managed to pluck the numbers out by reading the file in and using a match to split them out to an array:

input.match(/^([0-9]+[^\r\n]?)+$/gm)

// ["34", "53", "124", "45", "44", "72", "23,24,25"]

Unfortunately I'm struggling to get the regex to split the comma separated numbers as well. What I want to achieve is getting that final set of numbers to split into their own values:

["34", "53", "124", "45", "44", "72", "23", "24", "25"]

Dropping a comma in alongside the linebreaks in the regex doesn't do the trick, and I think I've reached the limits of my regex knowledge, so any help appreciated in working this one out!

Regex101 example: https://regex101.com/r/rmpmuD/1

5 Answers

First let's simplify your RegExp. All you really need to do is match numbers and commas from the start of a line.

let nums = input.match(/^\d[\d,]*/gm)

That gives us something like:

["1", "2", "3", "4, 5, 6"];

Now let's map the array to a new one, splitting any values that contain commas into a separate, sub-array.

nums = nums.map(match => match.split(','));

Nearly there. That gives us:

["1", "2", "3", ["4", "5", "6"]];

Now all we need to do is flatten the array, so the sub-array is subsumed into the parent array.

nums = nums.flat();

...et voila.

["1", "2", "3", "4", "5", "6"];

An alternative solution could be using lookaround assertions to assert optional repetitions of digits followed by a comma before and after the current number from the start till the end of the line.

(?<=^(?:\d+,)*)\d+(?=(?:,\d+)*$)

See the browser support for lookbehind

Regex demo

const regex = /(?<=^(?:\d+,)*)\d+(?=(?:,\d+)*$)/gm;
const str = `34
53
124

45
44

72


23,24,25`;
console.log(str.match(regex));


Another way could be matching the number with an optional repeating part for a comma and a number for the valid format of the line

^\d+(?:,\d+)*$

Regex demo

Then use flatMap on the result of match:

const regex = /^\d+(?:,\d+)*$/gm;
const str = `34
53
124

45
44

72


23,24,25`;
let result = str.match(regex).flatMap(i => i.split(','));
console.log(result);

How about this approach with one single regex

Online Demo

var input = `# Here are some numbers such as 34 and 53
34
53
124

# Here's a number
45
44

# And a final number
72


# And here's a legacy set of comma separated numbers
23,24,25`;


var num = input.match(/^([0-9]+[^\r\n]?)|(?<=,)([0-9]+[^\r\n]?)/gm)
console.log(num)

I like regexes. But this looks like an instance where a pure regex solution is not the best thing to do, particularly if you are parsing a long file.

In fact, it looks like you could easily be parsing your file line by line (and use a regex on each line, sure). This approach will enable efficient processing (ignoring) of comments, without making the regex more complex, as well as streaming, and should be easier to read (admittedly, anything is easier to read than a regex for most people).

So, first you split the lines. This is quick and dirty but you could stream it with a bit more code:

const lines = input.split('\n')

Then, use a generator to process the lines. This makes your code more flexible, as the input can be any iterable, not just an array:

function *processLines(lines) {
  for(const line of lines) {
    const useful = line.match(/^\d+(?:,\s*\d+)*/)
    if (useful) yield *useful[0].split(/,\s*/)
  }
}

I have used a simple for… of but you can replace it with for await… of to accommodate a true stream. It will still work with an array too.

And here is your list of numbers (but you could process them lazily if you wanted instead of spreading the generator into an array) :

const numbers = [...processLines(lines)]

Have you tried just \d+ ? (\d is equivalent to [0-9])

Related