Erik Smith

Posted on

# Listified Tokens, or Somebody's JavaScript Homework

A friend asked me how I might solve following problem:

Given a string in the form `"(1, 2, 3), (4, 5, 6), (7, 8, 9)"`, convert it to a multidimensional array of the form `[[1, 2, 3], [4, 5, 6], [7, 8, 9]]`.

For my first pass, I did some fun shenanigans with `Array.prototype.reduce()` and a couple of regular expressions like this...

``````const listified = `(1, 2, 3), (4, 5, 6), (7, 8, 9), (5, junk, 100, 2eggs)`
.match(/\((.*?)\)/g)
.reduce(
(a, c) => [
...a,
[
...c
.match(/([0-9]+)(?=[ ,\)])/g)
.map((el) => !isNaN(el) && parseInt(el)),
],
],
[]
);

console.log(listified);
``````

Demo on Replit.

While it looks cool and looking cool is my favorite thing about modern JavaScript, this approach does have something of a problem for calling loops within loops, so here is a more efficient approach that walks a pointer across the string and gathers up the numbers it finds into work sets...

``````// Convert strings of the form `"(1, 2, 3), (4, 5, 6), (7, 8, 9)"` into
// multidimensional arrays of the form `[[1, 2, 3], [4, 5, 6], [7,8,9]]`.

const listifiedTokens = (str) => {
let data = [];
let ws = [];
let x;

for (c of str) {
// Taking pains to prevent type-coercsion.
if (!isNaN(c)) {
x = x ? `\${x}\${c}` : c;
}

// Start a new work set and overwrite
// any existing work set.
if (c === "(") {
ws = [];
}

// ')' and ',' terminate a list entry,
// and x must be a number before we parse.
if ([")", ","].includes(c) && !isNaN(x)) {
ws = [...ws, parseInt(x, 10)];
}

// Report the work set.
if (c === ")") {
data = [...data, ws];
}

// Whenever c is NaN, we flush x
// because this only happens at the end
// of a valid list or when the list item
// contains an unsupported value.
if (isNaN(c)) {
x = undefined;
}
}

return data;
};

const str = `(1, 2, 3), (4, 5, 6), (7, 8, 8, 9), (100, 2egg, 5, bananas)`;

console.log(listifiedTokens(str));
``````

Demo on Replit.

It's not nearly as cool looking, but it's probably better in the long run.

Jon Randy ποΈ

If it is true that the strings are always as shown, you could do this... although if that is not true - this method is dangerous:

``````const listifiedTokens = s => eval(`[\${s}]`.replace(/\((.+?)\)/g,"[\$1]"))
``````

Erik Smith

Nice. I'm conditioned away from even thinking to use`eval()` for anything after consciously avoiding it for so many years. (And from working in large production environments, I'm paranoid that incoming data isn't guaranteed to be what the docs says it will be!)

Jon Randy ποΈ

Yep, but the above is a perfectly valid solution to the coding problem as specified. Probably worth mentioning the potential pitfalls if giving this answer in an interview setting though.

DEV Community