Skip to the code
cf.

Fibonacci generator with take

Write a generator function fibonacci(). It yields the Fibonacci numbers forever: 0, 1, and then each number is the sum of the two before it. Every call returns a new generator that starts again from 0.

Write a generator function take(iterable, n) too. It yields the first n values of iterable, then stops, or stops sooner if iterable runs out. iterable can be any iterable: an array, a string, a Set, or an endless generator like fibonacci(). When n is 0, it yields nothing.

take is lazy. Calling it pulls nothing from iterable; each value is pulled when it's asked for, and it never pulls more than n values.

When take stops before iterable runs out, it closes the iterable's iterator (calls its return() method), so a generator's finally blocks run.

const fib = fibonacci();
fib.next(); // { value: 0, done: false }
fib.next(); // { value: 1, done: false }
fib.next(); // { value: 1, done: false }
fib.next(); // { value: 2, done: false }
 
[...take(fibonacci(), 8)]; // [0, 1, 1, 2, 3, 5, 8, 13]
[...take("hello", 3)];     // ["h", "e", "l"]
[...take([1, 2], 5)];      // [1, 2]
[...take([1, 2], 0)];      // []

What the tests check

  1. fibonacci() yields 0, 1, and then the sum of the two before
    const fib = fibonacci();
    const first = [];
    for (let i = 0; i < 10; i++) first.push(fib.next().value);
    expect(first).toEqual([0, 1, 1, 2, 3, 5, 8, 13, 21, 34]);
    
  2. fibonacci() never runs out
    const fib = fibonacci();
    let result;
    for (let i = 0; i < 50; i++) result = fib.next();
    expect(result).toEqual({ value: 7778742049, done: false });
    
  3. each fibonacci() call starts again from 0
    const first = fibonacci();
    first.next();
    first.next();
    first.next();
    const second = fibonacci();
    expect(second.next().value).toBe(0);
    expect(first.next().value).toBe(2);
    
  4. take yields the first n values of an endless generator
    expect([...take(fibonacci(), 8)]).toEqual([0, 1, 1, 2, 3, 5, 8, 13]);
    
  5. take works with any iterable and stops when it runs out
    expect([...take("hello", 3)]).toEqual(["h", "e", "l"]);
    expect([...take(new Set([1, 2, 3]), 2)]).toEqual([1, 2]);
    expect([...take([1, 2], 5)]).toEqual([1, 2]);
    
  6. take pulls nothing until asked, then exactly n values
    let pulls = 0;
    const upToTen = {
      [Symbol.iterator]() {
        return { next: () => (pulls < 10 ? { value: ++pulls, done: false } : { value: undefined, done: true }) };
      },
    };
    const firstThree = take(upToTen, 3);
    expect(pulls).toBe(0);
    expect([...firstThree]).toEqual([1, 2, 3]);
    expect(pulls).toBe(3);
    
  7. take(iterable, 0) yields nothing and pulls nothing
    let pulls = 0;
    const upToTen = {
      [Symbol.iterator]() {
        return { next: () => (pulls < 10 ? { value: ++pulls, done: false } : { value: undefined, done: true }) };
      },
    };
    expect([...take(upToTen, 0)]).toEqual([]);
    expect(pulls).toBe(0);
    
  8. take closes the iterable when it stops early
    let closed = false;
    function* letters() {
      try {
        yield "a";
        yield "b";
        yield "c";
      } finally {
        closed = true;
      }
    }
    expect([...take(letters(), 2)]).toEqual(["a", "b"]);
    expect(closed).toBe(true);
    

Learn more

solution.jsSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).