Skip to content

Instantly share code, notes, and snippets.

@deepakshrma
Last active October 16, 2021 11:03
Show Gist options
  • Save deepakshrma/8edd876dc7e5593587d7ee4e877959cc to your computer and use it in GitHub Desktop.
Save deepakshrma/8edd876dc7e5593587d7ee4e877959cc to your computer and use it in GitHub Desktop.
even-odd-generator.js
const fill = function* (start = 0, end = 100, filter = (_) => true, mapper = (x) => x) {
while (start < end) {
if (filter(start))
yield start;
mapper(start++);
}
};
const isEven = (num) => num % 2 === 0;
const isOdd = (num) => num % 2 !== 0;
const evens = [...fill(0, 100, isEven)];
const odds = [...fill(0, 100, isOdd)];
const evensSquares = [...fill(0, 100, isEven, (x) => x * x)];
console.log(evens); //[0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98]
console.log(odds); //[1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49,51,53,55,57,59,61,63,65,67,69,71,73,75,77,79,81,83,85,87,89,91,93,95,97,99]
console.log(evensSquares); //[0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98]
const fillNaturalNumbers = (start, end) => fill(start, end + 1);
const naturals = [...fillNaturalNumbers(1, 100)];
console.log(naturals);//[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100]
/**
* Original author @christiantakle
*/
const identity = <A>(a: A) => a;
const constTrue = <A>(a: A) => true;
interface FillInput<A> {
range: [start: number, end: number];
filter: (a: number) => boolean;
mapper: (a: number) => A;
}
function* fill<A>({
range: [start, end],
filter = constTrue,
mapper,
}: FillInput<A>): Generator<A, void, undefined> {
while (start < end) {
if (filter(start)) yield mapper(start);
start++;
}
}
const isEven = (num: number) => num % 2 === 0;
const isOdd = (num: number) => num % 2 !== 0;
const evens = [
...fill({
range: [1, 101],
filter: isEven,
mapper: identity,
}),
];
console.log(evens);
console.log([
...fill({
range: [1, 101],
filter: isEven,
mapper: (x) => x * x,
}),
]);
@christiantakle
Copy link

christiantakle commented Oct 16, 2021

context: https://www.linkedin.com/feed/update/urn:li:activity:6855002687952576512

  • Update range input to reflect authors code.
  • fix mapper fn so its result is yielded instead of discarded
  • (not strictly needed) add input to filter default fn just to be explicit that it always take an argument
  • (not strictly needed) use generator function syntax for terseness
function* fill(start = 0, end = 100, filter = (x) => true, mapper = (x) => x) {
  while (start < end) {
    if (filter(start)) yield mapper(start);
    start++
  }
}

const isEven = (num) => num % 2 === 0;
const isOdd = (num) => num % 2 !== 0;

const evens = [...fill(1, 101, isEven)];
const odds = [...fill(1, 101, isOdd)];
const evensSquares = [...fill(1, 101, isEven, (x) => x * x)];

results

// > evens
// [
//    2,  4,  6,  8, 10,  12, 14, 16, 18, 20, 22,
//   24, 26, 28, 30, 32,  34, 36, 38, 40, 42, 44,
//   46, 48, 50, 52, 54,  56, 58, 60, 62, 64, 66,
//   68, 70, 72, 74, 76,  78, 80, 82, 84, 86, 88,
//   90, 92, 94, 96, 98, 100
// ]

// > odds
// [
//    1,  3,  5,  7,  9, 11, 13, 15, 17, 19, 21,
//   23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43,
//   45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65,
//   67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87,
//   89, 91, 93, 95, 97, 99
// ]
// > evens

// > evensSquares
// [
//       4,   16,   36,   64,  100,  144,  196,
//     256,  324,  400,  484,  576,  676,  784,
//     900, 1024, 1156, 1296, 1444, 1600, 1764,
//    1936, 2116, 2304, 2500, 2704, 2916, 3136,
//    3364, 3600, 3844, 4096, 4356, 4624, 4900,
//    5184, 5476, 5776, 6084, 6400, 6724, 7056,
//    7396, 7744, 8100, 8464, 8836, 9216, 9604,
//   10000
// ]

update:

  while (start++ <= end) { 
  //to
  while (start++ < end) {

Failed to copy my final code to this gist see
https://gist.github.com/deepakshrma/8edd876dc7e5593587d7ee4e877959cc#gistcomment-3929111 and
https://gist.github.com/deepakshrma/8edd876dc7e5593587d7ee4e877959cc#gistcomment-3929113

update 2

Changed the code to use the original range handling of @deepakshrma its more logical and update the range input to reflect the change to match the authors output.

update 3

Changed the comment to reflect our dialog. If readers want to see typescript implementations you can see them here

@deepakshrma
Copy link
Author

@christiantakle , Thanks But comment seems un-relevant to the code.

  1. If you need to print natural numbers, u can write curry. Given sample works like array fill Array(n).fill(1)
const fillNaturalNumbers = (start: number, end: number) => fill(start, end + 1)
const naturals = [...fillNaturalNumbers(1, 100)];
  1. filter is default version, no used of index. The default filter will always true.
  2. This code is not meant for typescript, Typescript version can be written as below
const fill = (start = 0,
  end = 100,
  filter = (_: number) => true,
  mapper: (index: number) => any = (x) => x) => {
  return {
    [Symbol.iterator]: function* () {
      while (start < end) {
        if (filter(start)) yield start;
        mapper(start++);
      }
    },
  };
};

@deepakshrma
Copy link
Author

deepakshrma commented Oct 16, 2021

@christiantakle and your code is buggy:(1,100) includes 101....

function* fill(start = 0, end = 100, filter = (x) => true, mapper = (x) => x) {
while (start++ <= end) {
if (filter(start)) yield mapper(start);
}
}

console.log(JSON.stringify([...fill()]));// [1,2,3,4,5,6,7,8,9,10,11,12,13,
//14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83
//,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101]

@christiantakle
Copy link

@deepakshrma haha yeah I was writing in my editor and didn't copy the final code. Its indeed a bug its due to

while (start++ <= end) {
// should be
while (start++ < end) {

And great job catching things this its the thing that happening from writing and not running.

You can see my output is correct but the code is wrong.

@deepakshrma
Copy link
Author

@christiantakle : haha! no worries.. Reviews make code better ;)

@christiantakle
Copy link

christiantakle commented Oct 16, 2021

Regards to:

https://gist.github.com/deepakshrma/8edd876dc7e5593587d7ee4e877959cc#gistcomment-3929109

  1. If you need to print natural numbers, u can write curry. Given sample works like array fill Array(n).fill(1)

Yes you are correct. It would better to just update the range input to your function rather than reimplement. Only saw it because the output was different from the author.

Sidenote:
I think that is partial function application and not currying. Most of the time the difference doesn't matter πŸ˜„

Simple example of a curried function.

const f = (x) => (y) => (z) => x + y + z
  1. filter is default version, no used of index. The default filter will always true.
    I do understand what you mean and why you did it. Just seeing it because I write typescript and typed language most of the time so just mentioning for completeness.

And as you said it was never meant to be anything but javascript. I just think your code is a good basis to teach more things from.

Dumb example using a curried function to make a constTrue function.

const constant = <A,B>(a:A) => (b:B) => A
const constTrue = constant(true)

Library in which both of the above exist
https://gcanti.github.io/fp-ts/modules/function.ts.html#consttrue

  1. This code is not meant for typescript, Typescript version can be written as below
    Very nice! πŸ‘

I would change its to an object argument since you cannot use that function with a mapper and have the default filter function

const constTrue = <A>(a: A) => true;

interface FillInput {
  range: [start: number, end: number];
  filter: (a: number) => boolean;
  mapper?: (a: number) => number;
}

function* fill({
  range: [start, end],
  filter = constTrue,
  mapper,
}: FillInput): Generator<number, void, undefined> {
  while (start < end) {
    if (filter(start)) yield mapper?.(start) ?? start;
    start++;
  }
}

const isEven = (num: number): boolean => num % 2 === 0;
const isOdd = (num: number): boolean => num % 2 !== 0;
const evens = [
  ...fill({
    range: [1, 101],
    filter: isEven,
  }),
];

console.log(evens);

console.log([
  ...fill({
    range: [1, 101],
    filter: isEven,
    mapper: (x) => x * x,
  }),
]);

Hopefully I copy paste the correct code this time πŸ˜†

update

improved working in sidenote

update 2

Changed the typescript code to use the original range handling of @deepakshrma its more logical and update the range input to reflect the change.

update 3

removed generics doesn't work for mapper

update 4

add return type to fill fn

@christiantakle
Copy link

christiantakle commented Oct 16, 2021

Re-add generics but it forces you to provided a mapper fn on every call

const identity = <A>(a: A) => a;
const constTrue = <A>(a: A) => true;

interface FillInput<A> {
  range: [start: number, end: number];
  filter: (a: number) => boolean;
  mapper: (a: number) => A;
}

function* fill<A>({
  range: [start, end],
  filter = constTrue,
  mapper,
}: FillInput<A>): Generator<A, void, undefined> {
  while (start < end) {
    if (filter(start)) yield mapper(start);
    start++;
  }
}

const isEven = (num: number): boolean => num % 2 === 0;
const isOdd = (num: number): boolean => num % 2 !== 0;
const evens = [
  ...fill({
    range: [1, 101],
    filter: isEven,
    mapper: identity,
  }),
];

console.log(evens);

console.log([
  ...fill({
    range: [1, 101],
    filter: isEven,
    mapper: (x) => "changes the output type",
  }),
]);

@deepakshrma
Copy link
Author

I tried something similar to write, However, the default to mapper still issue.

Type 'number' is not assignable to type 'A'.

If that could be solve.

@christiantakle
Copy link

christiantakle commented Oct 16, 2021

@deepakshrma I've updated my first comment with information from our dialog. I removed the generics from the example where mapper is optional you cannot typecheck it unless you use any or unknown neither which is ideal.

I've also added an example with generics but you have to supply a mapper function to use it to make the type checker happy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment