1

I have list of indexes:

var remove_list = [ [ 7, 12 ], [ 12, 14 ] ];

and list of tokens of S-Expression from Scheme lisp.

var tokens = [
  '(',  'let', '(',   '(',
  'x',  '10',  ')',   '#;',
  '(',  'foo', 'bar', ')',
  '#;', 'xxx', ')',   '(',
  '*',  'x',   'x',   ')',
  ')'
];

what is the best way to remove all items from tokens. (it suppose to remove inline commands #;(foo bar) ad #;xxx as specified in R7RS).

I can use this:

for (let remove of remove_list) {
    tokens.splice(...remove);
}

because after first call to splice indexes will change. What is simplest way to remove all specified ranges from array?

And for context I have this function that should remove inline comments, I've splitted calculating the indexes from removing them from array, is this good approach?

function strip_s_comments(tokens) {
    var s_count = 0;
    var s_start = null;
    var remove_list = [];
    for (let i = 0; i < tokens.length; ++i) {
        const token = tokens[i];
        if (token === '#;') {
            if (['(', '['].includes(tokens[i + 1])) {
                s_count = 1;
                s_start = i;
            } else {
                remove_list.push([i, i + 2]);
            }
            i += 1;
            continue;
        }
        if (s_start !== null) {
            if ([')', ']'].includes(token)) {
                s_count--;
            } else if (['(', '['].includes(token)) {
                s_count++;
            }
            if (s_count === 0) {
                remove_list.push([s_start, i + 1]);
                s_start = null;
            }
        }
    }
    for (let remove of remove_list) {
        tokens.splice(...remove);
    }
    return tokens;
}
jcubic
  • 61,973
  • 54
  • 229
  • 402
  • 2
    If the ranges are not overlapping and ordered by start index, simply iterate over the array in reverse order so that the tokens towards the end are removed first. But if you are already iterating over the tokens to detect comments, why not just "drop" the tokens that are part of the comment? – Felix Kling Jun 09 '20 at 08:41

2 Answers2

2

You have two options:

  1. Work in reverse order, or

  2. Keep track of how much you've removed and take that into account with later indexes

Here's an example of #1:

const reverse = remove_list.sort(([a], [b]) => b - a);
for (const [begin, end] of reverse) {
    tokens.splice(begin, end - begin);
}

var remove_list = [ [ 7, 12 ], [ 12, 14 ] ];

var tokens = [
  '(',  'let', '(',   '(',
  'x',  '10',  ')',   '#;',
  '(',  'foo', 'bar', ')',
  '#;', 'xxx', ')',   '(',
  '*',  'x',   'x',   ')',
  ')'
];

const reverse = remove_list.sort(([a], [b]) => b - a);
for (const [begin, end] of reverse) {
    tokens.splice(begin, end - begin);
}

console.log(tokens);

Here's an example of #2:

let removed = 0;
for (const [begin, end] of remove_list) {
    removed += tokens.splice(begin - removed, end - begin).length;
}

var remove_list = [ [ 7, 12 ], [ 12, 14 ] ];

var tokens = [
  '(',  'let', '(',   '(',
  'x',  '10',  ')',   '#;',
  '(',  'foo', 'bar', ')',
  '#;', 'xxx', ')',   '(',
  '*',  'x',   'x',   ')',
  ')'
];

let removed = 0;
for (const [begin, end] of remove_list) {
    removed += tokens.splice(begin - removed, end - begin).length;
}

console.log(tokens);
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
1

Traverse from end to start of list and use copyWithin.

function remove(dat, list) {
  const arr = [...dat];
  const is_remove = index =>
    list.some(([start, end]) => index >= start && index <= end);

  for (let i = arr.length - 1; i > -1; i--) {
    if (is_remove(i)) {
      arr.copyWithin(i, i + 1).pop();
    }
  }
  return arr;
}

function remove2(dat, list) {
  const arr = [...dat];
  let new_len = arr.length;
  for (let i = list.length - 1; i > -1; i--) {
    const [start, end] = list[i];
    arr.copyWithin(start, end + 1);
    new_len -= end - start + 1;
  }
  arr.length = new_len;
  return arr;
}

// Alternate way
const remove3 = (arr, list) =>
  arr.filter((_, i) => !list.some((rg) => i >= rg[0] && i <= rg[1]));


var remove_list = [ [ 7, 11 ], [ 12, 14 ] ];
var tokens = [
  '(',  'let', '(',   '(',
  'x',  '10',  ')',   '#;',
  '(',  'foo', 'bar', ')',
  '#;', 'xxx', ')',   '(',
  '*',  'x',   'x',   ')',
  ')'
];

console.log(remove(tokens,remove_list).join(''));
console.log(remove2(tokens,remove_list).join(''));
console.log(remove3(tokens,remove_list).join(''));
Siva K V
  • 10,561
  • 2
  • 16
  • 29
  • is_remove function is not needed all ranges are remove indexes, they are always correct. – jcubic Jun 09 '20 at 08:56
  • @jcubic, Actually is_remove is will return `false` when index is not in remove_list right? I just checked `is_remove(6)` will return false and where as `is_remove(7)` will return true? does that make sense? – Siva K V Jun 09 '20 at 09:16
  • @jcubic, you are right. is_remove method is not required. Just updated answer with variation of going over with remove_list. Thank you :) – Siva K V Jun 09 '20 at 09:49
  • Using copyWithin is clever but it's not obvious at first what the code do. Thanks anyway. – jcubic Jun 09 '20 at 12:26