Nermin
Nermin

Reputation: 935

How to match values into separate optional groups with a regex based on character?

3 examples of input:

"13-4h50m2s"
"13-4h2s"
"13-50m"

Preferred code:

const [hours = 0, minutes = 0, seconds = 0] = input.match(some_regexp)

Wanted output {hours,minutes,seconds} for the 3 examples:

{hours: 4, minutes: 50, seconds: 2}
{hours: 4, minutes: 0,  seconds: 2}
{hours: 0, minutes: 50, seconds: 0}

Regex I've tried: /(\d+)?[a-z](\d+)?[a-z]?(\d+)?[a-z]?/. But then the second example doesn't work.

Edit: The input can be expected to be sorted in the right order (h,m,s). And it doesn't matter if the values are stored as strings or numbers.

Edit 2: Sorry for the late updates, actually the 13- part of the input can be ignored as well, but it doesn't make big of a difference and can easily be adjusted anyways.

Upvotes: 0

Views: 78

Answers (3)

Nermin
Nermin

Reputation: 935

So I actually got a working solution I'm satisfied with:

const inputs = [
  '1h2m3s',
  '4h5m',
  '6m',
  '7h8s',
  '9m10s'
]

const re = /(?<hours>\d+(?=h))?[hms]?(?<minutes>\d+(?=m))?[hms]?(?<seconds>\d+(?=s))?/

for (str of inputs) {
  const match = re.exec(str)
  const {hours = 0, minutes = 0, seconds = 0} = match.groups
  console.log({hours,minutes,seconds})
}

Strings and numbers are mixed but that's OK, and it works as long as the order of h,m,s is correct.

The regex

I used optional named capturing groups with lookahead for the respective time-units, and then optional [hms] character sets in between.

Upvotes: 1

trincot
trincot

Reputation: 350310

You could match the digits and suffix, and then use Object.fromEntries to turn that result into an object. Use an object initialiser with spread syntax to get 0 for non-populated properties:

const parse = s =>
    ({h: 0, m: 0, s: 0, ...
        Object.fromEntries(s.match(/\d+[msh]/g).map(m => [m[m.length-1], parseInt(m)]))
    });

let inputs = [
  "13-4h50m2s",
  "13-2s4h",
  "13-50m"
];

console.log(inputs.map(parse));

NB/ This reuses the single letters h, m, s as property names.

Upvotes: 2

Peter Thoeny
Peter Thoeny

Reputation: 7616

This should answer your question:

const labelMap = { h: 'hours', m: 'minutes', s: 'seconds' };
[
  "13-4h50m2s",
  "13-2s4h",
  "13-50m"
].forEach((str) => {
  let obj = { hours: 0, minutes: 0, seconds: 0 };
  str.replace(/(\d+)([hms])/g, (m, p1, p2) => {
    obj[labelMap[p2]] = parseInt(p1, 10);
  });
  console.log(JSON.stringify(obj));
});

Output:

{"hours":4,"minutes":50,"seconds":2}
{"hours":4,"minutes":0,"seconds":2}
{"hours":0,"minutes":50,"seconds":0}

Notes:

  • the labelMap maps from your input unit to the desired output object key
  • use a string replace that has no return, it's simply used to iterate over the pattern we want: /(\d+)([hms])/g
  • the replace function composes the object based on the digit and the unit

Upvotes: 2

Related Questions