javascript

examples

examples.js
// ============================================
// 17.4 Iterators and Iterables - Examples
// ============================================

// --------------------------------------------
// 1. Built-in Iterables
// --------------------------------------------

// String iteration
const str = 'Hello';
for (const char of str) {
  console.log(char); // 'H', 'e', 'l', 'l', 'o'
}

// Array iteration
const arr = [1, 2, 3];
for (const item of arr) {
  console.log(item); // 1, 2, 3
}

// Map iteration
const map = new Map([
  ['a', 1],
  ['b', 2],
]);
for (const [key, value] of map) {
  console.log(key, value); // 'a' 1, 'b' 2
}

// Set iteration
const set = new Set([1, 2, 3]);
for (const item of set) {
  console.log(item); // 1, 2, 3
}

// --------------------------------------------
// 2. Manual Iterator Usage
// --------------------------------------------

const numbers = [10, 20, 30];

// Get the iterator
const iterator = numbers[Symbol.iterator]();

// Call next() manually
console.log(iterator.next()); // { value: 10, done: false }
console.log(iterator.next()); // { value: 20, done: false }
console.log(iterator.next()); // { value: 30, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

// --------------------------------------------
// 3. Custom Iterable Object
// --------------------------------------------

const range = {
  start: 1,
  end: 5,

  [Symbol.iterator]() {
    let current = this.start;
    const last = this.end;

    return {
      next() {
        if (current <= last) {
          return { value: current++, done: false };
        }
        return { done: true };
      },
    };
  },
};

console.log('Range iteration:');
for (const num of range) {
  console.log(num); // 1, 2, 3, 4, 5
}

// Works with spread
console.log([...range]); // [1, 2, 3, 4, 5]

// Works with destructuring
const [first, second] = range;
console.log(first, second); // 1, 2

// Works with Array.from
console.log(Array.from(range)); // [1, 2, 3, 4, 5]

// --------------------------------------------
// 4. Iterable Class
// --------------------------------------------

class Range {
  constructor(start, end, step = 1) {
    this.start = start;
    this.end = end;
    this.step = step;
  }

  [Symbol.iterator]() {
    let current = this.start;
    const end = this.end;
    const step = this.step;

    return {
      next() {
        if (current <= end) {
          const value = current;
          current += step;
          return { value, done: false };
        }
        return { done: true };
      },
    };
  }
}

const evens = new Range(0, 10, 2);
console.log([...evens]); // [0, 2, 4, 6, 8, 10]

// --------------------------------------------
// 5. Iterator with Return Method
// --------------------------------------------

function createResourceIterator(data) {
  let index = 0;
  console.log('Resource opened');

  return {
    [Symbol.iterator]() {
      return this;
    },

    next() {
      if (index < data.length) {
        return { value: data[index++], done: false };
      }
      return { done: true };
    },

    return() {
      console.log('Resource closed (cleanup)');
      return { done: true };
    },
  };
}

const resource = createResourceIterator([1, 2, 3, 4, 5]);

for (const item of resource) {
  console.log(item);
  if (item === 3) break; // Triggers return()
}
// Output: Resource opened, 1, 2, 3, Resource closed (cleanup)

// --------------------------------------------
// 6. Infinite Iterator
// --------------------------------------------

const fibonacci = {
  [Symbol.iterator]() {
    let prev = 0,
      curr = 1;

    return {
      next() {
        const value = curr;
        [prev, curr] = [curr, prev + curr];
        return { value, done: false };
      },
    };
  },
};

// Take first 10 fibonacci numbers
const fibArray = [];
for (const num of fibonacci) {
  if (fibArray.length >= 10) break;
  fibArray.push(num);
}
console.log(fibArray); // [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

// --------------------------------------------
// 7. Iterator Helper Functions
// --------------------------------------------

// Take first N items from any iterable
function* take(iterable, n) {
  let count = 0;
  for (const item of iterable) {
    if (count >= n) return;
    yield item;
    count++;
  }
}

console.log([...take([1, 2, 3, 4, 5], 3)]); // [1, 2, 3]

// Filter iterable
function* filter(iterable, predicate) {
  for (const item of iterable) {
    if (predicate(item)) {
      yield item;
    }
  }
}

console.log([...filter([1, 2, 3, 4, 5], (x) => x % 2 === 0)]); // [2, 4]

// Map iterable
function* map(iterable, fn) {
  for (const item of iterable) {
    yield fn(item);
  }
}

console.log([...map([1, 2, 3], (x) => x * 2)]); // [2, 4, 6]

// --------------------------------------------
// 8. Combining Iterators
// --------------------------------------------

function* concat(...iterables) {
  for (const iterable of iterables) {
    yield* iterable;
  }
}

const combined = concat([1, 2], [3, 4], [5, 6]);
console.log([...combined]); // [1, 2, 3, 4, 5, 6]

function* zip(...iterables) {
  const iterators = iterables.map((it) => it[Symbol.iterator]());

  while (true) {
    const results = iterators.map((it) => it.next());

    if (results.some((r) => r.done)) return;

    yield results.map((r) => r.value);
  }
}

const zipped = zip([1, 2, 3], ['a', 'b', 'c']);
console.log([...zipped]); // [[1, 'a'], [2, 'b'], [3, 'c']]

// --------------------------------------------
// 9. Two-Way Iterator (Iterable Iterator)
// --------------------------------------------

class Counter {
  constructor(max) {
    this.max = max;
    this.current = 0;
  }

  [Symbol.iterator]() {
    return this;
  }

  next() {
    if (this.current < this.max) {
      return { value: this.current++, done: false };
    }
    return { done: true };
  }

  // Reset for re-iteration
  reset() {
    this.current = 0;
    return this;
  }
}

const counter = new Counter(3);
console.log([...counter]); // [0, 1, 2]
console.log([...counter]); // [] - exhausted!

counter.reset();
console.log([...counter]); // [0, 1, 2] - works again

// --------------------------------------------
// 10. Linked List Iterator
// --------------------------------------------

class LinkedList {
  constructor() {
    this.head = null;
    this.tail = null;
  }

  append(value) {
    const node = { value, next: null };
    if (!this.head) {
      this.head = this.tail = node;
    } else {
      this.tail.next = node;
      this.tail = node;
    }
    return this;
  }

  [Symbol.iterator]() {
    let current = this.head;

    return {
      next() {
        if (current) {
          const value = current.value;
          current = current.next;
          return { value, done: false };
        }
        return { done: true };
      },
    };
  }
}

const list = new LinkedList().append('a').append('b').append('c');

console.log([...list]); // ['a', 'b', 'c']

for (const item of list) {
  console.log(item); // 'a', 'b', 'c'
}

// --------------------------------------------
// 11. Tree Iterator (DFS)
// --------------------------------------------

class TreeNode {
  constructor(value, children = []) {
    this.value = value;
    this.children = children;
  }

  *[Symbol.iterator]() {
    yield this.value;
    for (const child of this.children) {
      yield* child;
    }
  }
}

const tree = new TreeNode('root', [
  new TreeNode('child1', [
    new TreeNode('grandchild1'),
    new TreeNode('grandchild2'),
  ]),
  new TreeNode('child2'),
]);

console.log([...tree]);
// ['root', 'child1', 'grandchild1', 'grandchild2', 'child2']

// --------------------------------------------
// 12. Lazy Evaluation with Iterators
// --------------------------------------------

function* numbersFrom(start) {
  let n = start;
  while (true) {
    yield n++;
  }
}

function* takeWhile(iterable, predicate) {
  for (const item of iterable) {
    if (!predicate(item)) return;
    yield item;
  }
}

// Get numbers from 1 while less than 10
const nums = takeWhile(numbersFrom(1), (n) => n < 10);
console.log([...nums]); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

// --------------------------------------------
// 13. Object.entries/keys/values Iteration
// --------------------------------------------

const person = { name: 'Alice', age: 30, city: 'NYC' };

// Object.entries returns iterable
for (const [key, value] of Object.entries(person)) {
  console.log(`${key}: ${value}`);
}

// Object.keys returns iterable
for (const key of Object.keys(person)) {
  console.log(key);
}

// Object.values returns iterable
for (const value of Object.values(person)) {
  console.log(value);
}

// --------------------------------------------
// 14. Making Any Object Iterable
// --------------------------------------------

const obj = {
  data: ['a', 'b', 'c'],

  [Symbol.iterator]() {
    return this.data[Symbol.iterator]();
  },
};

console.log([...obj]); // ['a', 'b', 'c']

// --------------------------------------------
// 15. Checking if Something is Iterable
// --------------------------------------------

function isIterable(obj) {
  return obj != null && typeof obj[Symbol.iterator] === 'function';
}

console.log(isIterable([1, 2, 3])); // true
console.log(isIterable('hello')); // true
console.log(isIterable(new Map())); // true
console.log(isIterable({ a: 1 })); // false
console.log(isIterable(123)); // false
console.log(isIterable(null)); // false

// Make plain object iterable
function makeIterable(obj) {
  obj[Symbol.iterator] = function* () {
    for (const key of Object.keys(this)) {
      yield [key, this[key]];
    }
  };
  return obj;
}

const iterableObj = makeIterable({ x: 1, y: 2 });
console.log([...iterableObj]); // [['x', 1], ['y', 2]]
Examples - JavaScript Tutorial | DeepML