13

Something similar to question Convert ES6 Iterable to Array. But I only want first N items. Is there any built-in for me to do so? Or how can I achieve this more elegantly?

let N = 100;
function *Z() { for (let i = 0; ; i++) yield i; }

// This wont work
// Array.from(Z()).slice(0, N);
// [...Z()].slice(0, N)

// This works, but a built-in may be preferred
let a = [], t = Z(); for (let i = 0; i < N; i++) a.push(t.next().value);
tsh
  • 4,263
  • 5
  • 28
  • 47
  • Just make it a function. They are exactly for situations like this where you don't want to repeat (sometimes inelegant) logic. – ExPixel Nov 29 '17 at 01:59

4 Answers4

8

To get the first n values of an iterator, you could use one of:

Array.from({length: n}, function(){ return this.next().value; }, iterator);
Array.from({length: n}, (i => () => i.next().value)(iterator));

To get the iterator of an arbitrary iterable, use:

const iterator = iterable[Symbol.iterator]();

In your case, given a generator function Z:

Array.from({length: 3}, function(){ return this.next().value; }, Z());

If you need this functionality more often, you could create a generator function:

function* take(iterable, length) {
  const iterator = iterable[Symbol.iterator]();
  while (length-- > 0) yield iterator.next().value;
}

// Example:
const set = new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
console.log(...take(set, 3));
le_m
  • 19,302
  • 9
  • 64
  • 74
3

There is no built in method to take only a certain number of items from an iterable (ala something like take()). Although your snippet can be somewhat improved with a for of loop, which is specifically meant to work with iterables, eg:

let a = []; let i = 0; for (let x of Z()) { a.push(x); if (++i === N) break; }

Which might be better since your original snippet would continue looping even if there are not N items in the iterable.

CRice
  • 29,968
  • 4
  • 57
  • 70
  • `for..of` loop causes a lot of overhead though, according to the `AirBnB` linting guide – Ayush Gupta Nov 29 '17 at 03:53
  • 1
    @AyushGupta Any evidence about the overhead? I think this answer is reasonable as far as there is no such built-in works for the given situation. – tsh Nov 29 '17 at 04:10
  • It seems the performace was improved with some V8 engine updated, but seems prettty low a few versions ago – Ayush Gupta Nov 29 '17 at 04:17
  • 1
    @AyushGupta Your testcase is targeted on Array, not Iterable. That's not same. – tsh Nov 29 '17 at 05:08
  • I had [created a jsperf](https://jsperf.com/iterate-n-element/1), it seems that using `for of` is a bit slow in Chrome, but same performance in Firefox (both 52 and 57). Not knowing why. – tsh Nov 29 '17 at 05:40
0

A bit shorter and less efficient with .map, and a bit safer with custom function:

function *Z() { for (let i = 0; i < 5; ) yield i++; }

function buffer(t, n = -1, a = [], c) { 
    while (n-- && (c = t.next(), !c.done)) a.push(c.value); return a; }

const l = console.log, t = Z()

l( [...Array(3)].map(v => t.next().value) )

l( buffer(t) )
Slai
  • 22,144
  • 5
  • 45
  • 53
0

how can I achieve this more elegantly?

One possible elegant solution, using iter-ops library:

import {pipe, take} from 'iter-ops';

const i = pipe(
    Z(), // your generator result
    take(N) // take up to N values
); //=> Iterable<number>

const arr = [...i]; // your resulting array

P.S. I'm the author of the library.

vitaly-t
  • 24,279
  • 15
  • 116
  • 138
  • I want to know why you design interface like this? Why not something like `Iter(Z()).take(N)` `Iter::take(n: number) -> Iter` while `Iter implements Iterable`? – tsh Dec 15 '21 at 01:39
  • @tsh Because chaining like you showed requires use of a synthetic type (wrapper), which creates type compatibility and integration concerns, whereas my approach works with JavaScript native types only - it takes an iterable, and outputs an iterable, and it also performs better this way. – vitaly-t Dec 15 '21 at 02:01