DEV Community

Erik Smith
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);
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

Demo on Replit.

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

Top comments (3)

Collapse
 
jonrandy profile image
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]"))
Enter fullscreen mode Exit fullscreen mode
Collapse
 
365erik profile image
Erik Smith

Nice. I'm conditioned away from even thinking to useeval() 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!)

Collapse
 
jonrandy profile image
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.

Need a better mental model for async/await?

Check out this classic DEV post on the subject.

β­οΈπŸŽ€ JavaScript Visualized: Promises & Async/Await

async await