I was looking into javascript generators and iterators and was wondering if there is a way to write a generator function to return the value at the current position --- without of course having to call next() or to remember the returned value from the last next() call.
More specific, my failed attempt:
function* iterable(arr) {
this.index = 0;
this.arr = arr;
while(this.index < this.arr.length) {
yield this.arr[this.index++];
}
}
iterable.prototype.current = function () {
return this.arr[this.index];
}
const i = iterable([0, 1, 2]);
console.log(i.current()); // TypeError: Cannot read property 'undefined' of undefined
The desired functionality could be implemented using a class like this (I'm aware of the fact that the return values from the iterator would be objects like { value: 1, done: false }):
class iterableClass {
constructor(arr) {
this.index = 0;
this.arr = arr;
}
get(i) {
return this.index < arr.length ? this.arr[this.index] : false;
}
next() {
const val = this.get(this.index);
this.index++;
return val;
}
current() {
return this.get(this.index);
}
}
const i = iterableClass([0, 1, 2]);
console.log(i.current()); // 0
While I could just work with the class (or even a plain old function), I was wondering if this could be done with a generator/iterator or maybe there's an even better option.
The problem with your generator function is that a) it doesn't start running when you call it, it just creates the generator (this.arr and this.index won't be initialised until the first call to next()) and b) there is no way to access the generator object from inside the function like you tried with this.
Instead, you would want
function iterable(arr) {
const gen = Object.assign(function* () {
while (gen.index < gen.arr.length) {
yield gen.arr[gen.index++];
}
}(), {
arr,
index: 0,
current() {
return gen.arr[gen.index];
},
});
return gen;
}
Alternatively, instead of using generator syntax you can also directly implement the Iterator interface:
function iterable(arr) {
return {
arr,
index: 0,
current() { return this.arr[this.index]; },
next() {
const done = !(this.index < this.arr.length);
return { done, value: done ? undefined : this.arr[this.index++] };
},
[Symbol.iterator]() { return this; },
};
}
(which you could of course write as a class as well)
There seem to be multiple interpretations of this question. My understanding is that you want an iterator that provides a way to access the most recently-retrieved value, as shown by the last line in your final code block:
console.log(i.current()); // 0
Doing that isn't part of the iterator interface and isn't provided by generator functions. You could provide an iterator wrapper that did it, and then use that on the generator from the generator function (although you don't need a generator for what you're doing, the standard array iterator does it), see comments:
// Get the Iterator prototype, which has no global name
const itPrototype = Object.getPrototypeOf(
Object.getPrototypeOf([][Symbol.iterator]())
);
function currentWrapper(source) {
// Allow source to be an iterable or an iterator
if (Symbol.iterator in source) {
source = source[Symbol.iterator]();
}
// Create our wrapper iterator
const it = Object.create(itPrototype);
// Remember the last value we saw from `next`
let current = null;
// The iterator method
it.next = () => {
return current = source.next();
};
// Our additional methods
it.current = () => current && current.value;
it.currentResult = () => ({...current});
return it;
}
This has the advantage of being reusable and generic, not tied to a specific iterable.
Live Example:
// Get the Iterator prototype, which has no global name
const itPrototype = Object.getPrototypeOf(
Object.getPrototypeOf([][Symbol.iterator]())
);
function currentWrapper(source) {
// Allow source to be an iterable or an iterator
if (Symbol.iterator in source) {
source = source[Symbol.iterator]();
}
// Create our wrapper iterator
const it = Object.create(itPrototype);
// Remember the last value we saw from `next`
let current = null;
// The iterator method
it.next = () => {
return current = source.next();
};
// Our additional methods
it.current = () => current && current.value;
it.currentResult = () => ({...current});
return it;
}
// Something to iterate over
const a = [1, 2, 3];
// Example use #1: Using `current`
const it = currentWrapper(a[Symbol.iterator]());
console.log("current", it.current()); // undefined
console.log("next", it.next()); // {value: 1, done: false}
console.log("current", it.current()); // 1
console.log("currentResult", it.currentResult()); // {value: 1, done: false}
// Example use #2: Just normal use of an iterator
for (const value of currentWrapper(a)) {
console.log(value);
}
.as-console-wrapper {
max-height: 100% !important;
}
I focussed on the current bit and not the index bit because I think of iterables as streams rather than arrays, but I suppose it would be easy enough to add index. The slightly-tricky part is when the iterator has finished, do you increment the index when next is called or not? The below doesn't:
// Get the Iterator prototype, which has no global name
const itPrototype = Object.getPrototypeOf(
Object.getPrototypeOf([][Symbol.iterator]())
);
function currentWrapper(source) {
// Allow source to be an iterable or an iterator
if (Symbol.iterator in source) {
source = source[Symbol.iterator]();
}
// Create our wrapper iterator
const it = Object.create(itPrototype);
// Remember the last value we saw from `next` and the current "index"
let current = null;
let index = -1;
// The iterator method
it.next = () => {
// Don't increase the index if "done" (tricky bit)
if (!current || !current.done) {
++index;
}
return current = source.next();
};
// Our additional methods
it.current = () => current && current.value;
it.currentResult = () => ({...current});
it.currentIndex = () => index;
return it;
}
// Something to iterate over
const a = [1, 2, 3];
// Example use #1: Using `current`
const it = currentWrapper(a[Symbol.iterator]());
console.log("current", it.current()); // undefined
console.log("next", it.next()); // {value: 1, done: false}
console.log("current", it.current()); // 1
console.log("currentResult", it.currentResult()); // {value: 1, done: false}
console.log("currentIndex", it.currentIndex()); // 0
console.log("next", it.next()); // {value: 2, done: false}
console.log("current", it.current()); // 2
console.log("currentResult", it.currentResult()); // {value: 2, done: false}
console.log("currentIndex", it.currentIndex()); // 1
// Example use #2: Just normal use of an iterator
for (const value of currentWrapper(a)) {
console.log(value);
}
.as-console-wrapper {
max-height: 100% !important;
}
Why not use a function from MDN Iterators and generators, where just the return part is replaced by the value instead of an object with value and done property
function makeIterator(array) {
var nextIndex = 0,
lastValue;
return {
next: function() {
return lastValue = nextIndex < array.length ? array[nextIndex++] : undefined;
},
last: function () {
return lastValue;
}
};
}
var it = makeIterator(['yo', 'ya']);
console.log(it.next());
console.log(it.next());
console.log(it.last());
console.log(it.next());
Related
new Set(getObjects())
getObjects returns an iterable object. Every iteration a new object is created. But Set gets and adds all of new objects immediately.
How to make it so only when a new object is needed, a new object is gotten and added?
I tried Proxy but no handler fires when calling a function with receiver as this.
const getEntitySet = (nativeEntityCollection) => {
const cache = new Set;
let src = {
[Symbol.iterator]: ((itr) => {
return () => itr;
})(nativeEntityCollection[Symbol.iterator]()),
ref: nativeEntityCollection
};
let addAll = () => {
for (const entity of src) {
cache.add(entity)
}
done()
};
let generate = function*() {
yield* cache;
for (const entity of src) {
cache.add(entity);
yield entity;
}
done()
};
const done = () => {
src = null;
addAll = null;
generate = Reflect.get(cache, Symbol.iterator).bind(cache)
};
const generateRef = () => generate();
return new Proxy(cache, {
get(set, prop) {
if (prop === Symbol.iterator) {
return generateRef;
}
addAll?.();
return Reflect.get(cache, prop);
}
});
};
The receiver issue you mention can be reproduced when using other methods on the proxy object than Symbol.iterator, like has.
This can be fixed by using bind (as you already did elsewhere):
return Reflect.get(cache, prop).bind(cache);
This will fix that issue.
Other remarks
An access to a method will trigger addAll, which is a pity, as it is not certain that this access will really need that to happen. For instance, if we just did const f = myproxy.has, then there is actually no need to greedily consume the iterable.
With the proxy pattern you would have to return a replacing function for such method access, and only have addAll called within that returned function. I would suggest to use an inheritance pattern instead of the proxy pattern. This seems more suitable for implementing the desired behaviour.
In the case of the has method, we could even consider to partially consume the iterable up to the point that the target element is found, as any further consumption of that iterable could never change the outcome of this method call.
Implementation
Here is an alternative "subclassing" implementation you could consider:
class LazySet extends Set {
#iterator
constructor(iterable) {
super();
this.#iterator = iterable[Symbol.iterator]();
this.#iterator.return = () => ({}); // Disable undesired return behaviour
}
* values() {
yield* super.values();
if (!this.#iterator) return;
let size = super.size;
for (const value of this.#iterator) {
super.add(value); // lazy
if (size === super.size) continue; // duplicate
yield value;
size++;
}
this.#iterator = null; // release reference
}
* entries() {
for (const value of this.values()) yield [value, value];
}
* keys() {
yield* this.values();
}
* [Symbol.iterator]() {
yield* this.values();
}
forEach(cb, thisArg) {
for (const value of this.values()) {
cb.call(thisArg, value, this);
}
}
has(needle) {
if (super.has(needle)) return true;
if (!this.#iterator) return false;
for (const value of this.#iterator) {
super.add(value); // lazy
if (value === needle) return true;
}
this.#iterator = null;
return false;
}
delete(needle) {
for (const _ of this.values()) {}; // consume iterator
return super.delete(needle);
}
get size() {
for (const _ of this.values()) {}; // consume iterator
return super.size;
}
}
// Demo
function* generator() {
for (let i = 0; i < 10; i++) {
console.log(` (about to yield ${i})`);
yield i;
}
}
let set = new LazySet(generator());
console.log("Does set have 3?:");
console.log(set.has(3));
console.log("-----------------");
console.log("first 5 values in set:");
let i = 0;
for (const value of set) {
console.log(value);
if (++i === 5) break;
}
console.log("-----------------");
console.log("All values in set:");
console.log(...set);
console.log("size:", set.size);
I need to iterate the next value from an array of an array in every function call.
forex: If I have an array of array
const Arr = [ ['1','2', '3'], ['11','12', '13'], ['11','22', '33'],]
and I have a function
getNumber(id: number): string {
let n;
for(const i in Arr) {
for(const j in Arr[i]{
n = j;
}
}
return n;
}
and I here I need to call the function multiple times but every Time It should return the next number
let i = 4;
for(let j=0; j<=i; j++) {
const g = {
number: this.getNumber(i); //this i means id
}
}
after the calling function, it will return like
'1'
'2'
'3'
'11'
'12'
please help me to solve this
You can use a generator to get the next value everytime you call the function.
To turn the multidimensional array into a flat array you can use the Array.flat() method.
const Arr = [ ['1','2', '3'], ['11','12', '13'], ['11','22', '33'],];
function* getNumber(arr) {
for(const num of arr) {
yield num;
}
}
const gen = getNumber(Arr.flat());
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);
You can use the build-in Array Iterator that uses a generator like the one shown in the answer from #Reyno. It's just simpler and you don't have to write so much code since it's already written for you.
const arr = [[1],[2],[3],[4]];
const it = arr.flat()[Symbol.iterator]();
it.next() // { value: 1, done: false }
it.next() // { value: 2, done: false }
it.next() // { value: 3, done: false }
it.next() // { value: 4, done: false }
it.next() // { value: undefined, done: true }
Also if you don't want to use generators and iterators since they are from ES6, you can just write a function:
function getIterator(arr) {
var counter = 0;
return function next() {
if (counter === arr.length) { return undefined; }
return arr[counter++];
};
}
var arr = [1, 2, 3, 4];
var next = getIterator(arr);
next(); // 1
next(); // 2
next(); // 3
next(); // 4
next(); // undefined
next(); // undefined
arr.push(5);
next(); // 5
which by the way can be even a better solution even in ES6+ since you will be able to push elements inside the array and still be able to iterate over the new ones. The iterator/generator solution closes after reaching the end of the array so you must create a new one in order to iterate over again.
I have a deeply nested object and I want to manipulate a value of it and reassign it again. Is there a shorthand way for this other than writing it all out again or assigning it to a variable:
createStops[idx]['place']['create'][stop][key][value] = createStops[idx]['place']['create'][stop][key][value].toString()
looks ugly doesn't it? Something like:
createStops[idx]['place']['create'][stop][key][value].toStringAndReassign()
but JS built in.
Edit: In my case it is a number, if it's for your case too please check out #MarkMeyer answer.
No, there isn't.
Assigning a new value requires an assignment.
Strings are immutable, so you can't convert an existing value into a string in-place.
Given a value that's a number, if you just want it to be a string, you can coerce to a string with an assignment operator:
let o = {
akey: {
akey:{
value: 15
}
}
}
o.akey.akey.value += ''
console.log(o)
No,
Going to the same index is needed to store the value
Although it is not possible as mentioned by #Quentin you can define a custom getter in your object like:
var foo = {
a: 5,
b: 6,
get c () {
return this.b.toString()+' text'
}
};
console.log(foo.c);
You're not reassigning the value as you are semantically formatting your values. In order to format your value you are mutating your initial object. If you do not pretend to modify an object for formatting purposes that will work just fine.
You do not have integrated functions to use like that, but you could use of some utilitary functions of your own to help you manage assignements and make it less verbal.
SPOIL : The final use look like
// call the function to do +1 at the specified key
executeFunctionAtKey(
// The object to change the value on
createStops,
// The path
`${idx}.place.create.${stop}.${key}.${value}`,
// The thing to do
(x) => x + 1,
);
const createStops = {
idx: {
place: {
create: {
stop: {
key: {
value: 5,
},
},
},
},
},
};
const idx = 'idx';
const stop = 'stop';
const key = 'key';
const value = 'value';
// Function that go to the specified key and
// execute a function on it.
// The new value is the result of the func
// You can do your toString there, or anything else
function executeFunctionAtKey(obj, path, func) {
const keys = path.split('.');
if (keys.length === 1) {
obj[path] = func(obj[key]);
return obj;
}
const lastPtr = keys.slice(0, keys.length - 1).reduce((tmp, x) => tmp[x], obj);
lastPtr[keys[keys.length - 1]] = func(lastPtr[keys[keys.length - 1]]);
return obj;
}
// call the function to do +1 at the specified key
executeFunctionAtKey(
// The object to change the value on
createStops,
// The path
`${idx}.place.create.${stop}.${key}.${value}`,
// The thing to do
(x) => x + 1,
);
console.log(createStops);
with the toString example from Number to String
const createStops = {
idx: {
place: {
create: {
stop: {
key: {
value: 5,
},
},
},
},
},
};
const idx = 'idx';
const stop = 'stop';
const key = 'key';
const value = 'value';
// Function that go to the specified key and
// execute a function on it.
// The new value is the result of the func
// You can do your toString there, or anything else
function executeFunctionAtKey(obj, path, func) {
const keys = path.split('.');
if (keys.length === 1) {
obj[path] = func(obj[key]);
return obj;
}
const lastPtr = keys.slice(0, keys.length - 1).reduce((tmp, x) => tmp[x], obj);
lastPtr[keys[keys.length - 1]] = func(lastPtr[keys[keys.length - 1]]);
return obj;
}
// call the function to do +1 at the specified key
executeFunctionAtKey(
// The object to change the value on
createStops,
// The path
`${idx}.place.create.${stop}.${key}.${value}`,
// The thing to do
(x) => x.toString(),
);
console.log(createStops);
Theoretically you could build a function that takes an object, a path and the property to set it to.
This will reduce the readability of your code, so i would advice using ordinary assignment. But if you need it check out the snippet below:
//
function setProp(object, path, val) {
var parts = path.split("/").filter(function (p) { return p.length > 0; });
var pathIndex = 0;
var currentTarget = object;
while (pathIndex < parts.length - 1) {
currentTarget = currentTarget[parts[pathIndex]];
pathIndex++;
}
if (val instanceof Function) {
currentTarget[parts[pathIndex]] = val(currentTarget[parts[pathIndex]]);
}
else {
currentTarget[parts[pathIndex]] = val;
}
return object;
}
var createStops = {
idx: {
place: {
create: {
stop: {
key: {
value: 5
}
}
}
}
}
};
function toString(p) { return p.toString(); }
console.log(JSON.stringify(createStops, null, 4));
setProp(createStops, 'idx/place/create/stop/key/value', toString);
console.log(JSON.stringify(createStops, null, 4));
UPDATE 1
Allowed passing functions and used OP JSON structure for snippet
I am trying to understand Ecmascript 6 iterators and trying to create a data structure which behaves much like native Arrays.
for (let i of [1,2,3]) console.log(i); //Iterate over data set itself
will output 1,2,3
for (let i of [1,2,3].keys()) console.log(i); //Iterate over a custom iterator from a method
will output 0,1,2, and
var a = [1,2,3];
var keys = [...a.keys()];
will contain [0,1,2] as expected.
Therefore,
console.log([1,2,3].keys().next());
will output Object {value: 0, done: false}
Now I created a new type of data and tried to make it behave the same way.
var myDogs = function(dogs) {
this.dogs = dogs;
this[Symbol.iterator] = () => {
let i = -1;
return {
next() {
i++;
var dog = Object.keys(dogs)[i];
if (!dog) return {done:true};
return {value:{ dog, hungry:dogs[dog] }, done:false};
}
};
};
this.dogsNames = () => {
return {
[Symbol.iterator]() {
let i = -1;
return {
next() {
i++;
var dog = Object.keys(dogs)[i];
if (!dog) return {done:true};
return {value: dog, done:false};
}
};
}
}
}
};
var dogs = new myDogs({ buddy: true, hasso: false });
This works as expected (a custom iterator - thank you):
var dogHungryMap = [...dogs];
dogHungryMap == [{ dog: 'buddy', hungry: true }, { dog: 'hasso': hungry: false }]
The iterator dogsNames() works almost as expected. This is OK:
var names = [...dogs.dogsNames()];
names == ["buddy", "hasso"]
But this does not:
dogs.dogsNames().next()
VM19728:2 Uncaught TypeError: dogs.dogsNames(...).next is not a function(…)
Why and how can I reproduce the behavior of native arrays?
Because dogsNames() returns an object which has an iterator as a key. So you can use for...of over dogsNames, but to access next() directly you need to access the iterator function declared on the object like so:
dogsNames()[Symbol.iterator]().next()
Babel REPL Example
And for completeness, the full code for sharing next() function:
var myDogs = function(dogs) {
this.dogs = dogs;
let i = -1;
var iter = {
next() {
i++;
var dog = Object.keys(dogs)[i];
if (!dog) return {done:true};
return {value:{ dog, hungry:dogs[dog] }, done:false};
}
}
this[Symbol.iterator] = () => iter;
this.next = iter.next;
};
var dogList = new myDogs({
dog1: "no",
dog2: "yes"
});
for(const x of dogList) console.log(x);
console.log(dogList.next());
console.log(dogList.next());
dogNames has to return an iterator (something that implements a next method) that is also an iterable (something that implements a Symbol.iterator method) (and returns itself). Every built-in iterator does that.
A quick test verifies that:
var it = [].keys();
it[Symbol.iterator]() === it // true
So you could change your code to
this.dogsNames = () => {
let i = -1;
return {
[Symbol.iterator]() {
return this;
},
next() {
i++;
var dog = Object.keys(dogs)[i]; // should probably also be put in the outer function
if (!dog) return {done:true};
return {value: dog, done:false};
},
};
};
I have some iterator that pulls a value from an array.
There is a sequence
[0, 1, 2, 3, 4]
which can be accessed by an iterator it.next()
I want to pull whole value of the sequence using the iterator.
Perhaps, the half-way code would be
var seq =[];
var i = 0;
while (some_condition)
{
seq[i] = it.next();
i++;
}
I usually use
if (!foo) bar;
to check the value existence, but in this case, it does not work.
What is the smartest pattern to implement this?
You're supposed to use for..of with generators:
for(var x of it)
seq.push(x)
If you want to use next, check the done property:
while(1) {
x = it.next()
if(x.done)
break
seq.push(x.value)
}
If you implement your own iterator, I'd recommend to keep up with standards and use the harmony protocol, for example:
function rangeIter(min, max) {
var done = false;
return {
next: function() {
if(done)
throw Error('Generator has already finished');
var value;
if(min >= max) {
done = true;
value = undefined;
} else {
value = min++;
}
return {done:done, value:value};
}
}
}
This code behaves just like a "real" iterator would:
it = rangeIter(3, 7)
console.log(it.next()) // { done: false, value: 3 }
console.log(it.next()) // { done: false, value: 4 }
console.log(it.next()) // { done: false, value: 5 }
console.log(it.next()) // { done: false, value: 6 }
console.log(it.next()) // { done: true, value: undefined }
console.log(it.next()) // Error: Generator has already finished
This is another more more java like solution which should work with all versions of javascript:
function fakeIterator(array){
var i = 0;
return {
next: function(){
i += 1;
return array[i - 1];
},
hasNext: function(){
return i < array.length;
}
};
}
Then this should work:
var myIterator = fakeIterator([0, 4, 6]);
while(myIterator.hasNext()){
console.log('presenting: ' + myIterator.next());
}
// =>
// presenting: 0
// presenting: 4
// presenting: 6