Lazy lists with transformations, using generators & iterators
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
4
down vote
favorite
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.
javascript ecmascript-6 iterator lazy
 |Â
show 1 more comment
up vote
4
down vote
favorite
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.
javascript ecmascript-6 iterator lazy
1
Sidenote: not having generator arrow functions saddens me.
â Joseph Silber
Mar 23 at 12:36
Currently it lacks the very basic list constructor, thecons
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 reoccurringreturn new List(function* ()
code can be reduced. One could probably dynamically definefilter
,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 mustyield
, 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 definefilter
,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
 |Â
show 1 more comment
up vote
4
down vote
favorite
up vote
4
down vote
favorite
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.
javascript ecmascript-6 iterator lazy
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.
javascript ecmascript-6 iterator lazy
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, thecons
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 reoccurringreturn new List(function* ()
code can be reduced. One could probably dynamically definefilter
,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 mustyield
, 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 definefilter
,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
 |Â
show 1 more comment
1
Sidenote: not having generator arrow functions saddens me.
â Joseph Silber
Mar 23 at 12:36
Currently it lacks the very basic list constructor, thecons
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 reoccurringreturn new List(function* ()
code can be reduced. One could probably dynamically definefilter
,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 mustyield
, 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 definefilter
,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
 |Â
show 1 more comment
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);
)();
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 theconst list = this
by binding the functions to the current object.
â Joseph Silber
Mar 25 at 2:16
add a comment |Â
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);
)();
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 theconst list = this
by binding the functions to the current object.
â Joseph Silber
Mar 25 at 2:16
add a comment |Â
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);
)();
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 theconst list = this
by binding the functions to the current object.
â Joseph Silber
Mar 25 at 2:16
add a comment |Â
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);
)();
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);
)();
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 theconst list = this
by binding the functions to the current object.
â Joseph Silber
Mar 25 at 2:16
add a comment |Â
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 theconst 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
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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 definefilter
,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 definefilter
,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