Search code examples
javascriptiteratorslice

Slice operator for iterator


I'm looking for a way to slice an iterator, I couldn't find a proper way to do it using an existing standard method/function.

Here is what I want to do:

// Suppose I have this generator:
function* range(n){
  for (let i = 0; i < n; i += 1){
    yield i;
  }
}

// This is obviously not desirable as we create a large array:
console.log(Array.from(range(100)).slice(2, 5));
// -> [ 2, 3, 4 ]

// What I'm looking for is an existing function that would be equivalent to this: 
function* islice(iterable, start, end){
  if (!end){
    end = start;
    start = 0;
  }
  dropfirsts(iterable, start);
  yield* firsts(iterable, end-start);
}

function dropfirsts(iterable, n){
  for (let i of range(n)){ 
    if (iterable.next().done) return;
  }
}

function* firsts(iterable, n){
  for (let j of range(n)){
    const item = iterable.next();
    if (item.done) return;
    yield item.value;
  }
}

console.log(Array.from(islice(range(10), 2, 5)));
// -> [ 2, 3, 4 ]

Note that I'm not looking for a complete implementation of an islice function, instead I'm looking for a equivalent to this inside the standard library.


Solution

  • As of ECMAScript 2025, you have drop and take, and so you can now write:

    function* naturals() { // An infinite generator as example
        for (let i = 0; true; i++) yield i;
    }
    
    const islice = (iterable, start=0, end=Infinity) =>
                   Iterator.from(iterable).take(end).drop(start);
    
    console.log(...islice(naturals(), 2, 5));  // -> 2, 3, 4

    The Iterator.from call is not needed if iterable is an iterator that is an instance of Iterator (which is the case with the iterators you get from native JS functions or from generators), but this way it also works for iterables that do not implement the iterator protocol, or iterators that don't inherit from Iterator.