Laszlo
Laszlo

Reputation: 372

Javascript flatten a generator in lambda

I have solved the sum-of-multiples small puzzle like this:

function sum(multiples, limit_in) {
  return [...multiples.map(n => [...range(n, limit_in, n)])
    .reduce((numbers, array) => {
      array.forEach(value => numbers.add(value));
      return numbers;
    }, new Set())
    .values()]
    .reduce((sum, n) => sum + n, 0);
}

function* range(start, stop, step = 1) {
  if (stop == null) {
    stop = start;
    start = 0;
  }

  for (let i = start; step > 0 ? i < stop : i > stop; i += step) {
    yield i;
  }
}

const result = sum([5, 6, 8], 150);
console.log(result); // The result is 4419

Second version was something like this:

function sum(multiples, limit_in) {
  return [...multiples.flatMap(n => [...range(n, limit_in, n)])
    .reduce((numbers, value) => {
      numbers.add(value);
      return numbers;
    }, new Set())
    .values()]
    .reduce((sum, n) => sum + n, 0);
}

function* range(start, stop, step = 1) {
  if (stop == null) {
    stop = start;
    start = 0;
  }

  for (let i = start; step > 0 ? i < stop : i > stop; i += step) {
    yield i;
  }
}

const result = sum([5, 6, 8], 150);
console.log(result); // Result 4419

The question is it possible to make it more lazy? Is there a way to flatten more the generator (function*): range(n, limit_in, n) and not construct the array [...range(n, limit_in, n)]?

Upvotes: 2

Views: 205

Answers (2)

gog
gog

Reputation: 11347

Javascript standard library is not good at dealing with iterators. If you want a completely lazy, memory-savvy solution, you'll have to define your own set of generic iteration primitives, for example:

function* range(start, stop, step = 1) {
    if (stop == null) {
        stop = start;
        start = 0;
    }

    for (let i = start; step > 0 ? i < stop : i > stop; i += step) {
        yield i;
    }
}

function* map(it, fn) {
    for (let x of it)
        yield fn(x)
}

function* chain(iters) {
    for (let it of iters)
        yield* it
}

function* uniq(it) {
    let s = new Set
    for (let x of it) {
        if (!s.has(x)) {
            s.add(x)
            yield x
        }
    }
}

function sum(it) {
    let s = 0
    for (let x of it)
        s += x
    return s
}


let solution = (multiples, limit_in) => 
    sum(
        uniq(
            chain(
                map(
                    multiples, 
                    n => range(n, limit_in, n)))))




const result = solution([5, 6, 8], 150);
console.log(result); // The result is 4419

Upvotes: 2

trincot
trincot

Reputation: 351118

You could write another helper function that allows to reduce the values originating from an iterable:

function sum(multiples, limit_in) {
    return iterReduce(
        multiples.reduce((numbers, n) => 
            iterReduce(
                range(n, limit_in, n),
                (numbers, n) => numbers.add(n),
                numbers
            ),
            new Set
        ),
        (sum, n) => sum + n,
        0
    );
}

function iterReduce(iterable, callback, acc) {
    const it = iterable[Symbol.iterator]();
    if (arguments.length < 3) acc = it.next();
    for (const val of it) {
        acc = callback(acc, val);
    }
    return acc;
}

function* range(start, stop, step = 1) {
  if (stop == null) {
    stop = start;
    start = 0;
  }

  for (let i = start; step > 0 ? i < stop : i > stop; i += step) {
    yield i;
  }
}

const result = sum([5, 6, 8], 150);
console.log(result); // The result is 4419

Upvotes: 1

Related Questions