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;
}
1
  • 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? Commented Jun 9, 2020 at 8:41

2 Answers 2

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);

Sign up to request clarification or add additional context in comments.

Comments

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(''));

4 Comments

is_remove function is not needed all ranges are remove indexes, they are always correct.
@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?
@jcubic, you are right. is_remove method is not required. Just updated answer with variation of going over with remove_list. Thank you :)
Using copyWithin is clever but it's not obvious at first what the code do. Thanks anyway.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.