How to iterate over a generator with indexes? - javascript

With arrays in javascript, getting the current index for iteration is easy. You can either use forEach and the index is the second entry, or use for...of and .entries() and array unpacking.
But generators have no .entries() method. How do I get the current index for a generator in my for...of loop?
I basically want:
function* myGen(){
let i = 0;
while(true) {
i+=1;
yield i;
}
}
for(let [j, index] of myGen().entries()) { //<-- I want .entries() but for a Generator
//...
}
//Running the above produces TypeError: myGen(...).entries(...) is not a function or its return value is not iterable

It is not advisable to add things to a built-in prototype, but if you really want your code to work like that (calling .entries() on any generator), then you could proceed as follows:
const Generator = Object.getPrototypeOf(function* () {});
Generator.prototype.entries = function * () {
let i = 0;
for (let value of this) {
yield [i++, value];
}
}
// Demo
function* myGen(){
let i = 64;
while(i < 70) {
i+=1;
yield String.fromCharCode(i);
}
}
for(let [j, index] of myGen().entries()) { //<-- Now you have .entries() on a Generator
console.log(j, index);
}
It is more prudent however to define a utility function.
const GeneratorUtils = {
* entriesOf(iter) {
let i = 0;
for (let value of iter) {
yield [i++, value];
}
}
};
// Demo
function* myGen(){
let i = 64;
while(i < 70) {
i+=1;
yield String.fromCharCode(i);
}
}
for(let [j, index] of GeneratorUtils.entriesOf(myGen())) {
console.log(j, index);
}

There's no built-in way to do it - the generator will have to yield something that contains the index. For example:
function* myGen(){
let index = 0;
while(index < 10) {
const item = 'foo' + index;
yield { item, index };
index++;
}
}
for(const { item, index } of myGen()) {
console.log('item: ' + item);
console.log('index: ' + index);
}
If you can't modify a generator that you want to also get the index of, you can put it inside another generator that does keep track of the index (or you could just increment on every iteration outside):
function* unmodifiableGen(){
// index is private, is not being yielded
let index = 0;
while(index < 10) {
yield Math.random();
index++;
}
}
function* generatorCounter(gen) {
// this index *will* be yielded:
let index = 0;
for (const item of gen()) {
yield { item, index };
index++;
}
}
for(const { item, index } of generatorCounter(unmodifiableGen)) {
console.log('item: ' + item);
console.log('index: ' + index);
}

But generators have no .entries() method. How do I get the current
index for a generator in my for...of loop?
You can utilize spread element preceding generator function call within an array literal and .entries() method of Array.prototype
function* myGen() {
let i = 0;
while (i < 10) {
i += 1;
yield i;
}
}
for (const [index, value] of [...myGen()].entries()) {
console.log(index, value);
}

A slightly different approach might be to make myGen() a regular function that returns an object adhering to the iterator protocol rather than a generator. Then you can just give it an entries() method. It will work a little differently than a generator (you can't call next() on it directly). But it be self-contained and should work as expected in situations where an iterator is expected:
function myGen(start, stop){
return {
[Symbol.iterator]: function* () {
while(start < stop){
yield start++
}
},
entries: function* entries (){
let i = 0
for (n of this){
yield [i++, n]
}
}
}
}
let g = myGen(10, 20)
// works like a regular iterator:
console.log([...g])
// but you can also call entries():
g = myGen(2, 9)
for ([i, n] of g.entries()){
console.log(`index: ${i}, value: ${n}`)
}

Related

Algorithm to find all possible arrays of max size L that sum up to N or less

I want to find all possible arrays -of non-negative numbers- that sum up to -at most- N in JavaScript:
function findArrays(maxSize, maxSum){}
Example input: findArrays(3, 10)
Some acceptable outputs: (not writing all as it would be too long)
[[0], [0,0,0], [10,0,0], [1,9], [1,2,3] /*, ... */]
What I tried so far:
I know it looks like homework but it's not :) I can think of a solution that simply generates all (size*maxSum) possible arrays of acceptable sizes and then iterate through them to check if sum is greater than maxSum. However, I think this solution is very bad in terms of performance as maxSum gets bigger. I'm looking for a more efficient implementation but I just don't know where to start.
My "bad" solution
function getNextArray(r,maxVal){
for(var i=r.length-1;i>=0;i--){
if(r[i]<maxVal){
r[i]++;
if(i<r.length-1){
r[i+1]=0;
}
break;
}
}
return r;
}
function getAllArraysOfSize(size, maxVal){
var arrays=[],r=[],i;
for(i=0;i<size;i++){
r[i]=0;
}
while(r.reduce((a, b) => a + b, 0) < (maxVal*size)){
r = getNextArray(r.slice(),maxVal);
arrays.push(r);
}
return arrays;
};
function findArrays(maxSize, maxSum){
var allArrays=[],arraysOfFixedSize=[],acceptableArrays=[],i,j;
for(i=1; i<=maxSize; i++){
arraysOfFixedSize=getAllArraysOfSize(i,maxSum);
for(j=0; j<arraysOfFixedSize.length; j++){
allArrays.push(arraysOfFixedSize[j]);
}
}
for(i=0; i<allArrays.length; i++){
if(allArrays[i].reduce((a, b) => a + b, 0) <= maxSum){
acceptableArrays.push(allArrays[i]);
}
}
return acceptableArrays;
};
You can use recursion and a generator. The number of outputs grows quickly for higher valued arguments, so I keep them low here:
function * findArrays(maxSize, maxSum) {
let arr = [];
function * recur(maxSum) {
let k = arr.length;
yield [...arr]; // or: if (k) yield [...arr]
if (k === maxSize) return;
for (let i = 0; i <= maxSum; i++) {
arr[k] = i;
yield * recur(maxSum - i);
}
arr.length = k;
}
yield * recur(maxSum);
}
// demo
for (let arr of findArrays(2, 4))
console.log(JSON.stringify(arr));
NB: this also produces the empty array, which makes sense. If you want to avoid this, then just check that you don't yield an empty array.
If you prefer working with plain functions instead of generators, then translate the innermost yield expression to a push unto a result array, as follows:
function findArrays(maxSize, maxSum) {
let arr = [];
let result = []; // <--- will collect all the subarrays
function recur(maxSum) {
let k = arr.length;
result.push([...arr]);
if (k === maxSize) return;
for (let i = 0; i <= maxSum; i++) {
arr[k] = i;
recur(maxSum - i);
}
arr.length = k;
}
recur(maxSum);
return result;
}
// demo
for (let arr of findArrays(2, 4))
console.log(JSON.stringify(arr));
i hope this is helpful
const data = [[0],[0,0,0],[10,0,0],[1,9],[1,2,3]];
function findArrays(maxSize, maxSum){
return data.reduce(
(acc, value) => {
if (value.length <= maxSize) {
const tempValue = value;
const sum = tempValue.reduce((acc, val) => val >= 0 ? acc + val : 0, 0);
if (sum <= maxSum && sum > 0) acc.push(value);
}
return acc
}, []
)
}
console.log(findArrays(3, 10));

How to carry with result on recursive operation

If I can't pass an integer by reference how I can carry with the result on this function:
function operation(k, n, result) {
if (k == 0) {
console.log(n);
result += n;
return result;
} else {
//the result variable its back to the old value when the recursions goes back
for (var i = 1; i <= n; i++) {
operation(k - 1, i, result);
}
}
}
console.log('resultado = ', operation(2, 3, 0));
I need to accumulate n value on result when k=0,but result goes back to older values.I'm not sure about how to make it
In JavaScript arguments are passed by value: the result parameter is a separate variable in your recursive function's execution context.
As #Pointy remarked in comments you need to assign the return value back to result. The idea is to return the new value.
That would look like this:
function operation(k, n, result) {
if (k == 0) {
result += n;
} else {
for (var i = 1; i <= n; i++) {
result = operation(k - 1, i, result);
}
}
return result;
}
console.log('resultado = ', operation(2, 3, 0));
Although this works, the more natural way to implement recursion, is to let the recursive call produce a result that is independent from the larger context it is running in: it should not have to know the "result so far". Instead it should return the result as if it was executing the top-level call. It is up to the caller to accumulate the result further.
So with that pattern, you would not pass a result as argument at all:
function operation(k, n) {
if (k == 0) return n;
let result = 0; // we start from scratch
for (var i = 1; i <= n; i++) {
result += operation(k - 1, i); // note the addition here!
}
return result;
}
console.log('resultado = ', operation(2, 3));

issue with rewriting the reduce method js

I am trying to rewrite the reduce method.
I have a couple of things I am unsure of. Firstly, is that it gives the wrong result, which I cannot figure out why. Secondly, if I don't assign the reducez function to a const or a let there is an error.
The following code gives an error
Cannot read property 'reducuez' of undefined
Does anyone know why this is.
[1, 2, 3].reducuez((a, b) => {
return a + b;
});
And here is the actual code where I am trying to write the reduce method.
Array.prototype.reducuez = function(callback) {
let initialValue = 0;
for (let i = 0; i < this.length; i++) {
initialValue += callback(this[i], initialValue)
}
return initialValue;
}
const y = [1, 2, 3].reducuez((a, b) => {
return a + b;
});
console.log(y); // this is 11, should be 6
initialValue += callback(this[i], initialValue)
should be
initialValue = callback(this[i], initialValue)
because when you append values after every callback the the value of initalValue is added twice once in callback and once due to +=
Array.prototype.reducuez = function(callback) {
let initialValue = 0;
for (let i = 0; i < this.length; i++) {
initialValue = callback(this[i], initialValue)
}
return initialValue;
}
const y = [1, 2, 3].reducuez((a, b) => {
return a + b;
});
console.log(y); // this is 11, should be 6

How to yield multiple promises in for loop with vo.js?

Based on this example - vo/examples/9-pipeline-composition.js, how would I return yield a promise for each iteration of this for loop?
At the moment the loop runs once and yields a single promise.
function * get (urls) {
for (var i = urls.length - 1; i >= 0; i--) {
console.log(urls[i])
return yield http.get(urls[i])
}
}
function status (res, params) {
return res.status
}
let scrape = vo(get(['http://standupjack.com', 'https://google.com']), status)
vo([ scrape ])
.then(out => console.log('out', out))
.catch(e => console.error(e))
When u do the return inside the for loop, the loop breaks and returns the result and the loop wont move forward. You can call a function inside the for loop which handles the result and not return it.
function * get (urls) {
for (var i = urls.length - 1; i >= 0; i--) {
console.log(urls[i])
let result = yield http.get(urls[i])
yield handleResult(result)
}
}
Orelse u can push each of the results in an array and return all of then together at the end
function * get (urls) {
let resArr = []
for (var i = urls.length - 1; i >= 0; i--) {
console.log(urls[i])
let result = yield http.get(urls[i])
resArr.push(result)
}
return resArr
}

Identifying the next item in current for of loop iteration Typescript

In my TypeScript project, I want to compare the values in an Array with the next value. If I am doing this in JavaScript I would do something like this:
//JS
for (var i = 0; i < codeBlocks.length; i++) {
var j = i + 1;
if (codeBlocks[i] > codeBlocks[j]) {return true;}
return false;
}
However, I really like the Typescript for of syntax as it is much more readable.
//TS
for (let codeBlock of codeBlocks) {
//logic
}
Is there a way of describing the "next iteration" value in the typescript for...of loop?
You can use entries()
for (var [index, codeBlock] of codeBlocks.entries())
{
// your code you can use index
}
DEMO
Example snippet:
var test = ['a','b','c']
for (var [index, cur] of test.entries())
{
var j = index + 1;
console.log("Current value: "+cur+" Next value: "+test[j])
}
If you want the index of an element , use the foreach function:
codeBlocks.forEach((item, index) => {
console.log(item); // item in the array
console.log(index); // item index in the array
});
You can use same syntax for (var i = 0; i < codeBlocks.length; i++) in TypeScript. But your cycle will run only once.
No. That's not the purpose of for .. of. You can read more about it on MDN.
You must use the for (var i = 0; i < codeBlocks.length; i++) syntax for that.
first Your javascript code is wrong, the last element will compare with undefined
maybe you should give a look to reduce function https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
Here is a generator which generates pairs of elements:
function *pairs(array) {
let prev = array.shift();
for (v of array) {
yield [prev, v];
prev = v;
}
}
for (let pair of pairs([1,2,3])) { console.log(pair); }
If you want to also generate a last pair with the last value and undefined, then just add a yield [prev, undefined]; at the end of the function.

Categories