Lazy lists with transformations, using generators & iterators

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
4
down vote

favorite
2












I built this very-basic lazy list (I'll add more methods as I need them). You provide it an array, a generator or any iterator. It creates a lazy list, which lets you run a pipeline of transformations in a lazy manner, meaning that they'll only be applied as you pull values out of the list.



Here's the class:



class List 
static range(start, end)
return new List(function* ()
while (start <= end)
yield start++;

);


constructor(source)
if (typeof source == 'function')
this.generator = source;
else
this.generator = function* ()
yield* source;
;



filter(predicate)
return new List(function* ()
for (const item of this)
if (predicate(item))
yield item;


.bind(this));


map(mapper)
return new List(function* ()
for (const item of this)
yield mapper(item);

.bind(this));


take(size)
return new List(function* ()
for (const item of this)
if (size--)
yield item;
else
break;


.bind(this));


*[Symbol.iterator] ()
yield* this.generator();


toArray()
return [...this];




Here's a simple example of how to use it:



List.range(1, 10 ** 9)
.filter(number => number % 10 == 0)
.map(number => 'Item ' + number)
.take(5)
.toArray();


Trying the same with a regular array:



Array.from( length: 10 ** 9 , (v, i) => i + 1)
.filter(number => number % 10 == 0)
.map(number => 'Item ' + number)
.slice(0, 5);


...will run out of memory before it completes.







share|improve this question

















  • 1




    Sidenote: not having generator arrow functions saddens me.
    – Joseph Silber
    Mar 23 at 12:36











  • Currently it lacks the very basic list constructor, the cons itself ((:) in Haskell). Though can be implemented with a function each time, it probably makes sense to make it specific.
    – bipll
    Mar 23 at 12:42










  • I am curious to see how the reoccurring return new List(function* () code can be reduced. One could probably dynamically define filter, map, take from an array of the inner generator functions. Not sure if this is clean, though, and one loses all the typings (when using TypeScript).
    – ComFreek
    Mar 23 at 12:48











  • @ComFreek - I don't think that's possible, since you must yield, and the only way to do that is within a generator function. Also, I'm not sure what you mean by "One could probably dynamically define filter, map, take from an array of the inner generator functions". There's no array, which is the whole point here. The values are all flowing down one by one.
    – Joseph Silber
    Mar 23 at 12:50











  • See pastebin.com/70nPzuzp.
    – ComFreek
    Mar 23 at 12:59
















up vote
4
down vote

favorite
2












I built this very-basic lazy list (I'll add more methods as I need them). You provide it an array, a generator or any iterator. It creates a lazy list, which lets you run a pipeline of transformations in a lazy manner, meaning that they'll only be applied as you pull values out of the list.



Here's the class:



class List 
static range(start, end)
return new List(function* ()
while (start <= end)
yield start++;

);


constructor(source)
if (typeof source == 'function')
this.generator = source;
else
this.generator = function* ()
yield* source;
;



filter(predicate)
return new List(function* ()
for (const item of this)
if (predicate(item))
yield item;


.bind(this));


map(mapper)
return new List(function* ()
for (const item of this)
yield mapper(item);

.bind(this));


take(size)
return new List(function* ()
for (const item of this)
if (size--)
yield item;
else
break;


.bind(this));


*[Symbol.iterator] ()
yield* this.generator();


toArray()
return [...this];




Here's a simple example of how to use it:



List.range(1, 10 ** 9)
.filter(number => number % 10 == 0)
.map(number => 'Item ' + number)
.take(5)
.toArray();


Trying the same with a regular array:



Array.from( length: 10 ** 9 , (v, i) => i + 1)
.filter(number => number % 10 == 0)
.map(number => 'Item ' + number)
.slice(0, 5);


...will run out of memory before it completes.







share|improve this question

















  • 1




    Sidenote: not having generator arrow functions saddens me.
    – Joseph Silber
    Mar 23 at 12:36











  • Currently it lacks the very basic list constructor, the cons itself ((:) in Haskell). Though can be implemented with a function each time, it probably makes sense to make it specific.
    – bipll
    Mar 23 at 12:42










  • I am curious to see how the reoccurring return new List(function* () code can be reduced. One could probably dynamically define filter, map, take from an array of the inner generator functions. Not sure if this is clean, though, and one loses all the typings (when using TypeScript).
    – ComFreek
    Mar 23 at 12:48











  • @ComFreek - I don't think that's possible, since you must yield, and the only way to do that is within a generator function. Also, I'm not sure what you mean by "One could probably dynamically define filter, map, take from an array of the inner generator functions". There's no array, which is the whole point here. The values are all flowing down one by one.
    – Joseph Silber
    Mar 23 at 12:50











  • See pastebin.com/70nPzuzp.
    – ComFreek
    Mar 23 at 12:59












up vote
4
down vote

favorite
2









up vote
4
down vote

favorite
2






2





I built this very-basic lazy list (I'll add more methods as I need them). You provide it an array, a generator or any iterator. It creates a lazy list, which lets you run a pipeline of transformations in a lazy manner, meaning that they'll only be applied as you pull values out of the list.



Here's the class:



class List 
static range(start, end)
return new List(function* ()
while (start <= end)
yield start++;

);


constructor(source)
if (typeof source == 'function')
this.generator = source;
else
this.generator = function* ()
yield* source;
;



filter(predicate)
return new List(function* ()
for (const item of this)
if (predicate(item))
yield item;


.bind(this));


map(mapper)
return new List(function* ()
for (const item of this)
yield mapper(item);

.bind(this));


take(size)
return new List(function* ()
for (const item of this)
if (size--)
yield item;
else
break;


.bind(this));


*[Symbol.iterator] ()
yield* this.generator();


toArray()
return [...this];




Here's a simple example of how to use it:



List.range(1, 10 ** 9)
.filter(number => number % 10 == 0)
.map(number => 'Item ' + number)
.take(5)
.toArray();


Trying the same with a regular array:



Array.from( length: 10 ** 9 , (v, i) => i + 1)
.filter(number => number % 10 == 0)
.map(number => 'Item ' + number)
.slice(0, 5);


...will run out of memory before it completes.







share|improve this question













I built this very-basic lazy list (I'll add more methods as I need them). You provide it an array, a generator or any iterator. It creates a lazy list, which lets you run a pipeline of transformations in a lazy manner, meaning that they'll only be applied as you pull values out of the list.



Here's the class:



class List 
static range(start, end)
return new List(function* ()
while (start <= end)
yield start++;

);


constructor(source)
if (typeof source == 'function')
this.generator = source;
else
this.generator = function* ()
yield* source;
;



filter(predicate)
return new List(function* ()
for (const item of this)
if (predicate(item))
yield item;


.bind(this));


map(mapper)
return new List(function* ()
for (const item of this)
yield mapper(item);

.bind(this));


take(size)
return new List(function* ()
for (const item of this)
if (size--)
yield item;
else
break;


.bind(this));


*[Symbol.iterator] ()
yield* this.generator();


toArray()
return [...this];




Here's a simple example of how to use it:



List.range(1, 10 ** 9)
.filter(number => number % 10 == 0)
.map(number => 'Item ' + number)
.take(5)
.toArray();


Trying the same with a regular array:



Array.from( length: 10 ** 9 , (v, i) => i + 1)
.filter(number => number % 10 == 0)
.map(number => 'Item ' + number)
.slice(0, 5);


...will run out of memory before it completes.









share|improve this question












share|improve this question




share|improve this question








edited Mar 25 at 2:18
























asked Mar 23 at 12:33









Joseph Silber

928411




928411







  • 1




    Sidenote: not having generator arrow functions saddens me.
    – Joseph Silber
    Mar 23 at 12:36











  • Currently it lacks the very basic list constructor, the cons itself ((:) in Haskell). Though can be implemented with a function each time, it probably makes sense to make it specific.
    – bipll
    Mar 23 at 12:42










  • I am curious to see how the reoccurring return new List(function* () code can be reduced. One could probably dynamically define filter, map, take from an array of the inner generator functions. Not sure if this is clean, though, and one loses all the typings (when using TypeScript).
    – ComFreek
    Mar 23 at 12:48











  • @ComFreek - I don't think that's possible, since you must yield, and the only way to do that is within a generator function. Also, I'm not sure what you mean by "One could probably dynamically define filter, map, take from an array of the inner generator functions". There's no array, which is the whole point here. The values are all flowing down one by one.
    – Joseph Silber
    Mar 23 at 12:50











  • See pastebin.com/70nPzuzp.
    – ComFreek
    Mar 23 at 12:59












  • 1




    Sidenote: not having generator arrow functions saddens me.
    – Joseph Silber
    Mar 23 at 12:36











  • Currently it lacks the very basic list constructor, the cons itself ((:) in Haskell). Though can be implemented with a function each time, it probably makes sense to make it specific.
    – bipll
    Mar 23 at 12:42










  • I am curious to see how the reoccurring return new List(function* () code can be reduced. One could probably dynamically define filter, map, take from an array of the inner generator functions. Not sure if this is clean, though, and one loses all the typings (when using TypeScript).
    – ComFreek
    Mar 23 at 12:48











  • @ComFreek - I don't think that's possible, since you must yield, and the only way to do that is within a generator function. Also, I'm not sure what you mean by "One could probably dynamically define filter, map, take from an array of the inner generator functions". There's no array, which is the whole point here. The values are all flowing down one by one.
    – Joseph Silber
    Mar 23 at 12:50











  • See pastebin.com/70nPzuzp.
    – ComFreek
    Mar 23 at 12:59







1




1




Sidenote: not having generator arrow functions saddens me.
– Joseph Silber
Mar 23 at 12:36





Sidenote: not having generator arrow functions saddens me.
– Joseph Silber
Mar 23 at 12:36













Currently it lacks the very basic list constructor, the cons itself ((:) in Haskell). Though can be implemented with a function each time, it probably makes sense to make it specific.
– bipll
Mar 23 at 12:42




Currently it lacks the very basic list constructor, the cons itself ((:) in Haskell). Though can be implemented with a function each time, it probably makes sense to make it specific.
– bipll
Mar 23 at 12:42












I am curious to see how the reoccurring return new List(function* () code can be reduced. One could probably dynamically define filter, map, take from an array of the inner generator functions. Not sure if this is clean, though, and one loses all the typings (when using TypeScript).
– ComFreek
Mar 23 at 12:48





I am curious to see how the reoccurring return new List(function* () code can be reduced. One could probably dynamically define filter, map, take from an array of the inner generator functions. Not sure if this is clean, though, and one loses all the typings (when using TypeScript).
– ComFreek
Mar 23 at 12:48













@ComFreek - I don't think that's possible, since you must yield, and the only way to do that is within a generator function. Also, I'm not sure what you mean by "One could probably dynamically define filter, map, take from an array of the inner generator functions". There's no array, which is the whole point here. The values are all flowing down one by one.
– Joseph Silber
Mar 23 at 12:50





@ComFreek - I don't think that's possible, since you must yield, and the only way to do that is within a generator function. Also, I'm not sure what you mean by "One could probably dynamically define filter, map, take from an array of the inner generator functions". There's no array, which is the whole point here. The values are all flowing down one by one.
– Joseph Silber
Mar 23 at 12:50













See pastebin.com/70nPzuzp.
– ComFreek
Mar 23 at 12:59




See pastebin.com/70nPzuzp.
– ComFreek
Mar 23 at 12:59










1 Answer
1






active

oldest

votes

















up vote
2
down vote













I can not find fault with your code apart from the use of class of which i am not a fan.



So I will just present an alternative syntax that gives a little extra encapsulated protection. Its usage is slightly different. List is instantiated via a factory and closure holds the generator list. I also freeze each instance of List to further protect the state.



It also does not have to mess about with const list = this which from your side-note comment is a bit of an annoyance.



const lazyList = (() => 

const range = (start, end) => List(function* ()
while(start <= end) yield start++
);
function List(source)
var list;
if (typeof source === "function") list = source
else list = function* () yield* source

return Object.freeze(
filter(pred)
return List(function* ()
for (const item of list()) pred(item) ? yield item : 0
);
,
map(map)
return List(function* ()
for (const item of list()) yield map(item)
);
,
take(count)
return List(function* ()
for (const item of list())
if (count--) yield item
else break

);
,
toArray() return [...list()] ,
*[Symbol.iterator] () yield* list()
);

return Object.freeze(range, List);

)();





share|improve this answer





















  • I wouldn't call using classes a "fault". It's ok for you to prefer a more functional, closure-based approach, but I vastly prefer sticking all my methods on the prototype so that I don't have to recreate them for every single list created.
    – Joseph Silber
    Mar 25 at 2:16











  • Also, I got rid of all the const list = this by binding the functions to the current object.
    – Joseph Silber
    Mar 25 at 2:16










Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f190301%2flazy-lists-with-transformations-using-generators-iterators%23new-answer', 'question_page');

);

Post as a guest






























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
2
down vote













I can not find fault with your code apart from the use of class of which i am not a fan.



So I will just present an alternative syntax that gives a little extra encapsulated protection. Its usage is slightly different. List is instantiated via a factory and closure holds the generator list. I also freeze each instance of List to further protect the state.



It also does not have to mess about with const list = this which from your side-note comment is a bit of an annoyance.



const lazyList = (() => 

const range = (start, end) => List(function* ()
while(start <= end) yield start++
);
function List(source)
var list;
if (typeof source === "function") list = source
else list = function* () yield* source

return Object.freeze(
filter(pred)
return List(function* ()
for (const item of list()) pred(item) ? yield item : 0
);
,
map(map)
return List(function* ()
for (const item of list()) yield map(item)
);
,
take(count)
return List(function* ()
for (const item of list())
if (count--) yield item
else break

);
,
toArray() return [...list()] ,
*[Symbol.iterator] () yield* list()
);

return Object.freeze(range, List);

)();





share|improve this answer





















  • I wouldn't call using classes a "fault". It's ok for you to prefer a more functional, closure-based approach, but I vastly prefer sticking all my methods on the prototype so that I don't have to recreate them for every single list created.
    – Joseph Silber
    Mar 25 at 2:16











  • Also, I got rid of all the const list = this by binding the functions to the current object.
    – Joseph Silber
    Mar 25 at 2:16














up vote
2
down vote













I can not find fault with your code apart from the use of class of which i am not a fan.



So I will just present an alternative syntax that gives a little extra encapsulated protection. Its usage is slightly different. List is instantiated via a factory and closure holds the generator list. I also freeze each instance of List to further protect the state.



It also does not have to mess about with const list = this which from your side-note comment is a bit of an annoyance.



const lazyList = (() => 

const range = (start, end) => List(function* ()
while(start <= end) yield start++
);
function List(source)
var list;
if (typeof source === "function") list = source
else list = function* () yield* source

return Object.freeze(
filter(pred)
return List(function* ()
for (const item of list()) pred(item) ? yield item : 0
);
,
map(map)
return List(function* ()
for (const item of list()) yield map(item)
);
,
take(count)
return List(function* ()
for (const item of list())
if (count--) yield item
else break

);
,
toArray() return [...list()] ,
*[Symbol.iterator] () yield* list()
);

return Object.freeze(range, List);

)();





share|improve this answer





















  • I wouldn't call using classes a "fault". It's ok for you to prefer a more functional, closure-based approach, but I vastly prefer sticking all my methods on the prototype so that I don't have to recreate them for every single list created.
    – Joseph Silber
    Mar 25 at 2:16











  • Also, I got rid of all the const list = this by binding the functions to the current object.
    – Joseph Silber
    Mar 25 at 2:16












up vote
2
down vote










up vote
2
down vote









I can not find fault with your code apart from the use of class of which i am not a fan.



So I will just present an alternative syntax that gives a little extra encapsulated protection. Its usage is slightly different. List is instantiated via a factory and closure holds the generator list. I also freeze each instance of List to further protect the state.



It also does not have to mess about with const list = this which from your side-note comment is a bit of an annoyance.



const lazyList = (() => 

const range = (start, end) => List(function* ()
while(start <= end) yield start++
);
function List(source)
var list;
if (typeof source === "function") list = source
else list = function* () yield* source

return Object.freeze(
filter(pred)
return List(function* ()
for (const item of list()) pred(item) ? yield item : 0
);
,
map(map)
return List(function* ()
for (const item of list()) yield map(item)
);
,
take(count)
return List(function* ()
for (const item of list())
if (count--) yield item
else break

);
,
toArray() return [...list()] ,
*[Symbol.iterator] () yield* list()
);

return Object.freeze(range, List);

)();





share|improve this answer













I can not find fault with your code apart from the use of class of which i am not a fan.



So I will just present an alternative syntax that gives a little extra encapsulated protection. Its usage is slightly different. List is instantiated via a factory and closure holds the generator list. I also freeze each instance of List to further protect the state.



It also does not have to mess about with const list = this which from your side-note comment is a bit of an annoyance.



const lazyList = (() => 

const range = (start, end) => List(function* ()
while(start <= end) yield start++
);
function List(source)
var list;
if (typeof source === "function") list = source
else list = function* () yield* source

return Object.freeze(
filter(pred)
return List(function* ()
for (const item of list()) pred(item) ? yield item : 0
);
,
map(map)
return List(function* ()
for (const item of list()) yield map(item)
);
,
take(count)
return List(function* ()
for (const item of list())
if (count--) yield item
else break

);
,
toArray() return [...list()] ,
*[Symbol.iterator] () yield* list()
);

return Object.freeze(range, List);

)();






share|improve this answer













share|improve this answer



share|improve this answer











answered Mar 23 at 22:47









Blindman67

5,3611320




5,3611320











  • I wouldn't call using classes a "fault". It's ok for you to prefer a more functional, closure-based approach, but I vastly prefer sticking all my methods on the prototype so that I don't have to recreate them for every single list created.
    – Joseph Silber
    Mar 25 at 2:16











  • Also, I got rid of all the const list = this by binding the functions to the current object.
    – Joseph Silber
    Mar 25 at 2:16
















  • I wouldn't call using classes a "fault". It's ok for you to prefer a more functional, closure-based approach, but I vastly prefer sticking all my methods on the prototype so that I don't have to recreate them for every single list created.
    – Joseph Silber
    Mar 25 at 2:16











  • Also, I got rid of all the const list = this by binding the functions to the current object.
    – Joseph Silber
    Mar 25 at 2:16















I wouldn't call using classes a "fault". It's ok for you to prefer a more functional, closure-based approach, but I vastly prefer sticking all my methods on the prototype so that I don't have to recreate them for every single list created.
– Joseph Silber
Mar 25 at 2:16





I wouldn't call using classes a "fault". It's ok for you to prefer a more functional, closure-based approach, but I vastly prefer sticking all my methods on the prototype so that I don't have to recreate them for every single list created.
– Joseph Silber
Mar 25 at 2:16













Also, I got rid of all the const list = this by binding the functions to the current object.
– Joseph Silber
Mar 25 at 2:16




Also, I got rid of all the const list = this by binding the functions to the current object.
– Joseph Silber
Mar 25 at 2:16












 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f190301%2flazy-lists-with-transformations-using-generators-iterators%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Greedy Best First Search implementation in Rust

Function to Return a JSON Like Objects Using VBA Collections and Arrays

C++11 CLH Lock Implementation