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.

πŸ‘‹ Hey, my name is Noah and I’m the one who set up this ad. My job is to get you to join DEV, so if you fancy doing me a favor, I’d love for you to create an account.

If you found DEV from searching around, here are a couple of our most popular articles on DEV: