Tuesday, August 18, 2020

Avoiding name clashes when using HoC in React

After watching Michael Jackson's video on HoC vs render props, I noticed that the problems in HoC he mentioned can be avoided, albeit the component authors have to make their component's property names be dynamic. The component's property name(s) have to be explicitly set.

To cut to the chase, here's one implementation: https://codesandbox.io/s/compassionate-curie-i1x7w

App.js
import React from "react";
import "./styles.css";
import { withMouse } from "./Mouse";
import { withCat } from "./Cat";

const withMouseProp = "mouse";
const withCatProp = "spawnDate";

class App extends React.Component {
  render() {
    const mouseProp = this.props[withMouseProp];
    const catProp = this.props[withCatProp];
    const { message } = this.props;

    return (
      <div className="App">
        <h1>{message}</h1>
        <h2>Start editing to see some magic happen!</h2>
        mouse is at {mouseProp.x} {mouseProp.y}
        <br />
        Cat spawn date: {catProp}
      </div>
    );
  }
}

export default withMouse(
  withCat(App, withCatProp, withMouseProp),
  withMouseProp
);
Mouse.js
import React from "react";

export function withMouse(Component, mousePropName) {
  return class extends React.Component {
    state = { x: 0, y: 0 };

    handleMouseMove = (event) =  {
      this.setState({
        x: event.clientX,
        y: event.clientY
      });
    };

    render() {
      const withPropName = { ...this.props, [mousePropName]: this.state };
      return (
        <div onMouseMove={this.handleMouseMove}>
          <Component {...withPropName} />
        </div>
      );
    }
  };
}
Cat.js
import React from "react";

export function withCat(Component, catPropName, mousePropName) {
  return class extends React.Component {
    state = { x: 0, y: 0 };

    componentDidUpdate(prevProps) {
      const mouse = this.props[mousePropName];
      if (
        mouse.x !== prevProps[mousePropName].x ||
        mouse.y !== prevProps[mousePropName].y
      ) {
        this.setState(mouse);
      }
    }

    render() {
      const mouse = this.props[mousePropName];
      const withPropName = {
        ...this.props,
        [catPropName]: new Date().toISOString()
      };
      return (
        <>
          <Component {...withPropName} />
          <div>
            style={{
              position: "absolute",
              left: mouse.x,
              top: mouse.y,
              width: "25px",
              height: "25px",
              backgroundColor: "red"
            }}
          >
            cat
          </div>
        </>
      );
    }
  };
}

The above solution is moot point now, use React hooks instead.

Monday, August 17, 2020

Center

.container {
    min-height: 100vh; 
    
    display: flex;
    justify-content: center;
    align-items: center;
}
Details: scotch.io: Centering things with CSS Flexbox

Sunday, August 16, 2020

Create React App with TypeScript and PnP (no npx)

#!/bin/zsh

if [ $# -eq 0 ] ; then
	echo "Pass the name of project"
	exit 1
fi

yarn set version berry

NAME="$1"

yarn dlx create-react-app $NAME --template typescript 

cd $NAME
yarn dlx @yarnpkg/pnpify --sdk vscode

TO_DECLARE="declare module '@testing-library/react';"
printf $TO_DECLARE >> src/react-app-env.d.ts

code .
The above is same as https://www.anicehumble.com/2020/08/create-react-app-with-typescript-and-pnp.html 

The previous one does not create hidden directory(.yarn) and file (.yarnrc.yml) on the parent directory of the project, while the script above do.

The advantage of the script above is almost everything works out of the box, but you just have to tolerate the creation of .yarn directory and .yarnrc.yml file on the directory where you run the create-react-app

Looks like it is safe to run "yarn set version berry" once on the home directory if you don't have any project that is dependent on yarn version 1. Setting yarn to berry on home folder would make yarn use the version 2 globally. With that said, the "yarn set version berry" above can be removed from the script.

If you set the yarn to version 2 globally and you need to go back to version 1, just delete the .yarn directory and .yarnrc file, yarn will run as version 1 again.

Saturday, August 15, 2020

Create React App with TypeScript and PnP

#!/bin/zsh

if [ $# -eq 0 ] ; then
	echo "Pass the name of project"
	exit 1
fi

NAME="$1"

npx create-react-app $NAME --template typescript
cd $NAME

yarn set version berry

yarn remove typescript
yarn add -D typescript
yarn dlx @yarnpkg/pnpify --sdk vscode

TO_DECLARE="declare module '@testing-library/react';"
printf $TO_DECLARE >> src/react-app-env.d.ts

TO_IGNORE="
.yarn/*
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*
"
printf $TO_IGNORE >> .gitignore

code .
https://reactjs.org/docs/create-a-new-react-app.html
https://next.yarnpkg.com/getting-started/install#updating-to-the-latest-versions
https://stackoverflow.com/questions/54954337/is-it-possible-to-use-yarn-pnp-with-typescript-vscode
https://create-react-app.dev/docs/adding-typescript/
https://yarnpkg.com/advanced/qa#which-files-should-be-gitignored


Another way: https://www.anicehumble.com/2020/08/create-react-app-with-typescript-and-pnp-no-npx.html

npm is installing...

Create Angular with authentication project on macOS

#!/bin/zsh

if [ $# -eq 0 ] ; then
	echo "Pass the name of project"
	exit 1
fi

NAME="$1"

mkdir $NAME
cd $NAME
dotnet new sln --name $NAME
dotnet new angular -o $NAME -au Individual
dotnet sln $NAME.sln add $NAME/$NAME.csproj
open $NAME.sln

Thursday, August 6, 2020

Basic drag and drop (no back-end)

let dropArea;

document.addEventListener('DOMContentLoaded', () => {
    dropArea = document.getElementById('drop-area');

    ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => dropArea.addEventListener(eventName, preventDefaults, false));
    ['dragenter', 'dragover'].forEach(eventName => dropArea.addEventListener(eventName, highlight, false));
    ['dragleave', 'drop'].forEach(eventName => dropArea.addEventListener(eventName, unhighlight, false));

    dropArea.addEventListener('drop', handleDrop, false);
}, false);





function preventDefaults(e) {
    e.preventDefault();
    e.stopPropagation();
}



function highlight(e) {
    dropArea.classList.add('highlight');
}

function unhighlight(e) {
    dropArea.classList.remove('highlight')
}


function handleDrop(e) {
    const dt = e.dataTransfer;
    const files = dt.files;

    handleFiles(files);
}


function handleFiles(files) {
    // console.log(files);
    // alert(Array.isArray(files)); // false
    // alert(Array.isArray(Object.values(files))); // true
    // alert(Array.isArray([...files])); // true

    // this won't work:
    //      files.forEach(uploadFile);

    [...files].forEach(uploadFile);
}

function uploadFile(file) {
    alert('uploading ' + file.name);
}
<head>


    <script src='index.js'></script>

    <style>
        html {
            box-sizing: border-box;
        }

        *,
        *:before,
        *:after {
            box-sizing: inherit;
        }

        * {
            margin: 0;
            padding: 0;
        }

        #drop-area {
            border: 2px dashed #ccc;
            border-radius: 20px;
            width: 480px;
            font-family: sans-serif;
            margin: 100px auto;
            padding: 20px;
        }

        #drop-area.highlight {
            border-color: purple;
        }

        p {
            margin-top: 0;
        }

        .my-form {
            margin-bottom: 10px; 
        }

        #gallery {
            margin-top: 10px;
        }

        #gallery img {
            width: 150px;
            margin-bottom: 10px;
            margin-right: 10px;
            vertical-align: middle;
        }

        .button {
            display: inline-block;
            padding: 10px;
            background: #ccc;
            cursor: pointer;
            border-radius: 5px;
            border: 1px solid #ccc;
        }

        #fileElem {
            display: none;
        }
    </style>

</head>

<body>

    <div id="drop-area">
        <div id="my-form">
            <p>Upload multiple files with the file dialog or by dragging and dropping images onto the dashed region</p>
            <input type="file" id="fileElem" multiple="multiple" accept="image/*" onchange="handleFiles(this.files)">
            <label for="fileElem" class="button">Select some files</label>
        </div>
    </div>


</body>

Wednesday, August 5, 2020

FileList.forEach

We can't use forEach on FileList data structure. FileList is an object, not an array. To convert it to array, we have to use Object.values, or use spread operator:
function handleFiles(files) {
    console.log(files);
    alert(Array.isArray(files)); // false
    alert(Array.isArray(Object.values(files))); // true
    alert(Array.isArray([...files])); // true

    // this won't work:
    //      files.forEach(uploadFile);

    [...files].forEach(uploadFile);
}

function uploadFile(file) {
    alert('uploading ' + file.name);
}

Tuesday, July 28, 2020

Cash Register

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/javascript-algorithms-and-data-structures-projects/cash-register

JavaScript Algorithms and Data Structures Projects: Cash Register


Design a cash register drawer function checkCashRegister() that accepts purchase price as the first argument (price), payment as the second argument (cash), and cash-in-drawer (cid) as the third argument.

cid is a 2D array listing available currency.

The checkCashRegister() function should always return an object with a status key and a change key.

Return {status: "INSUFFICIENT_FUNDS", change: []} if cash-in-drawer is less than the change due, or if you cannot return the exact change.

Return {status: "CLOSED", change: [...]} with cash-in-drawer as the value for the key change if it is equal to the change due.

Otherwise, return {status: "OPEN", change: [...]}, with the change due in coins and bills, sorted in highest to lowest order, as the value of the change key.

Currency Unit Amount
Penny $0.01 (PENNY)
Nickel $0.05 (NICKEL)
Dime $0.1 (DIME)
Quarter $0.25 (QUARTER)
Dollar $1 (ONE)
Five Dollars $5 (FIVE)
Ten Dollars $10 (TEN)
Twenty Dollars $20 (TWENTY)
One-hundred Dollars $100 (ONE HUNDRED)


See below for an example of a cash-in-drawer array:
[
["PENNY", 1.01],
["NICKEL", 2.05],
["DIME", 3.1],
["QUARTER", 4.25],
["ONE", 90],
["FIVE", 55],
["TEN", 20],
["TWENTY", 60],
["ONE HUNDRED", 100]
]

Duration: 1 hour. See video: https://www.youtube.com/watch?v=rx7IUYq1wkU
const UNIT_VALUES = Object.freeze({
    'ONE HUNDRED': 100,
    TWENTY: 20,
    TEN: 10,
    FIVE: 5,
    ONE: 1,
    QUARTER: 0.25,
    DIME: 0.1,
    NICKEL: 0.05,
    PENNY: 0.01
});
 
 
function monify(obj) {
    if (typeof obj === 'number') {
        return Number(obj.toFixed(2).replace('.', ''));
    } else if (obj === undefined || obj === null || typeof obj === 'string' || obj instanceof Date) {
        return obj;
    } else if (Array.isArray(obj)) {
        return obj.map(el => monify(el));
    } else if (typeof obj === 'object') {
        return Object.entries(obj).reduce((acc, [key, value]) => ({ ...acc, [key]: monify(value) }), {});
    } else {
      throw new Error('Invalid Data', obj);
    }
}
 
console.log(monify(new Date()));
 
function floatify(obj) {
    if (typeof obj === 'number') {
        return Number(obj) / 100;
    } else if (obj === undefined || obj === null || typeof obj === 'string'|| obj instanceof Date) {
        return obj;
    } else if (Array.isArray(obj)) {
        return obj.map(el => floatify(el));
    } else if (typeof obj === 'object') {
        return Object.entries(obj).reduce((acc, [key, value]) => ({ ...acc, [key]: floatify(value) }), {});
    } else {
        throw new Error('Invalid Data', obj);
    }
}
 
function checkCashRegister(argPrice, argCash, argCid, argUnitValues = UNIT_VALUES) {
    const {
        argPrice: price,
        argCash: cash,
        argCid: cid,
        argUnitValues: UNIT_VALUES
    } = monify({ argPrice, argCash, argCid: [...argCid], argUnitValues });
 
    // From here onwards, we can pretend that we are still operating on original values, courtesy of monify function.
    // All values are all scaled to 100 times for us, to prevent inaccurate representation of cents in floating point number.
    // No need to change the code's logic.
    
    // If we are not using monify, floating point fraction produces inaccurate result, e.g.,
    // console.log(100 - 3.26 - 60);
    // 		produces incorrect result: 36.739999999999995
    // Correct result is: 36.74
 
    cid.sort(([aUnit], [bUnit]) => UNIT_VALUES[bUnit] - UNIT_VALUES[aUnit]);
 
    const change = cash - price;
    let uncollectedChange = change;
    const collectedChange = [];
    for (const [drawerUnit, drawerAmount] of cid) {
        const unitValue = UNIT_VALUES[drawerUnit];
 
        if (unitValue > change) {
            collectedChange.push([drawerUnit, 0]);
        } else {
            if (uncollectedChange >= drawerAmount) {
                collectedChange.push([drawerUnit, drawerAmount]);
                uncollectedChange -= drawerAmount;
            } else {
                const toDeduct = Math.floor(uncollectedChange / unitValue) * unitValue;
                collectedChange.push([drawerUnit, toDeduct]);
                uncollectedChange -= toDeduct;
            }
        }
    }
 

    if (uncollectedChange > 0) {
        return { status: 'INSUFFICIENT_FUNDS', change: [] };
    }
 
    const totalDrawerAmount = cid.reduce((sum, [, drawerAmount]) => sum + drawerAmount, 0);
    const status = totalDrawerAmount - change === 0 ? 'CLOSED' : 'OPEN';
 
    return floatify({
        status,
        change: collectedChange
            .filter(([, ccValue]) => status === 'OPEN' && ccValue > 0 || status === 'CLOSED')
            .sort(([aUnit, aValue], [bUnit, bValue]) => bValue - aValue || UNIT_VALUES[aUnit] - UNIT_VALUES[bUnit])
    });
}
 
[
    checkCashRegister(19.5, 20, [["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.1], ["QUARTER", 4.25], ["ONE", 90], ["FIVE", 55], ["TEN", 20], ["TWENTY", 60], ["ONE HUNDRED", 100]]),
    checkCashRegister(3.26, 100, [["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.1], ["QUARTER", 4.25], ["ONE", 90], ["FIVE", 55], ["TEN", 20], ["TWENTY", 60], ["ONE HUNDRED", 100]]),
    checkCashRegister(19.5, 20, [["PENNY", 0.01], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 0], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]]),
    checkCashRegister(19.5, 20, [["PENNY", 0.01], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 1], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]]),
    checkCashRegister(19.5, 20, [["PENNY", 0.5], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 0], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]]),
].forEach(result => console.log(result));

Output:
{ status: 'OPEN', change: [ [ 'QUARTER', 0.5 ] ] }
{ status: 'OPEN',
  change: 
   [ [ 'TWENTY', 60 ],
     [ 'TEN', 20 ],
     [ 'FIVE', 15 ],
     [ 'ONE', 1 ],
     [ 'QUARTER', 0.5 ],
     [ 'DIME', 0.2 ],
     [ 'PENNY', 0.04 ] ] }
{ status: 'INSUFFICIENT_FUNDS', change: [] }
{ status: 'INSUFFICIENT_FUNDS', change: [] }
{ status: 'CLOSED',
  change: 
   [ [ 'PENNY', 0.5 ],
     [ 'NICKEL', 0 ],
     [ 'DIME', 0 ],
     [ 'QUARTER', 0 ],
     [ 'ONE', 0 ],
     [ 'FIVE', 0 ],
     [ 'TEN', 0 ],
     [ 'TWENTY', 0 ],
     [ 'ONE HUNDRED', 0 ] ] }

Monday, July 27, 2020

Caesars Cipher

JavaScript Algorithms and Data Structures Projects: Caesars CipherPassed


One of the simplest and most widely known ciphers is a Caesar cipher, also known as a shift cipher. In a shift cipher the meanings of the letters are shifted by some set amount.

A common modern use is the ROT13 cipher, where the values of the letters are shifted by 13 places. Thus 'A' ↔ 'N', 'B' ↔ 'O' and so on.

Write a function which takes a ROT13 encoded string as input and returns a decoded string.

All letters will be uppercase. Do not transform any non-alphabetic character (i.e. spaces, punctuation), but do pass them on.


const rot13 = str =>
  String.fromCharCode(...str.split('').map(l => 
    /\w/.test(l) ? 
      (l.charCodeAt(0) - 'A'.charCodeAt(0) + 13) % 26 + 'A'.charCodeAt(0)
    :
      l.charCodeAt(0)
  ));

[
    rot13("SERR PBQR PNZC") ,
    rot13("SERR CVMMN!") ,
    rot13("SERR YBIR?") ,
    rot13("GUR DHVPX OEBJA SBK WHZCF BIRE GUR YNML QBT.")
].forEach(result => console.log(result));

FREE CODE CAMP
FREE PIZZA!
FREE LOVE?
THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.

Roman Numeral Converter

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/javascript-algorithms-and-data-structures-projects/roman-numeral-converter

const ROMAN_NUMERALS = Object.freeze({
    1000: 'M',
    900: 'CM',
    500: 'D',
    400: 'CD',
    100: 'C',
    90: 'XC',
    50: 'L',
    40: 'XL',
    10: 'X',
    9: 'IX',
    5: 'V',
    4: 'IV',
    1: 'I'
});

function convertToRoman(num) {
    const digit = Math.max(...Object.keys(ROMAN_NUMERALS).filter(key => num >= key));
    if (num === digit) {
        return ROMAN_NUMERALS[digit];
    }
    return ROMAN_NUMERALS[digit] + convertToRoman(num - digit);
}

[
    2,
    3,
    4,
    5,
    9,
    12,
    16,
    29,
    44,
    45,
    68,
    83, 
    97,
    99,
    400,
    500,
    501,
    649,
    798,
    891,
    1000,
    1004,
    1006,
    1023, 
    2014,
    3999
].forEach(arg => console.log(arg, convertToRoman(arg)));

Output:
2 II
3 III
4 IV
5 V
9 IX
12 XII
16 XVI
29 XXIX
44 XLIV
45 XLV
68 LXVIII
83 LXXXIII
97 XCVII
99 XCIX
400 CD
500 D
501 DI
649 DCXLIX
798 DCCXCVIII
891 DCCCXCI
1000 M
1004 MIV
1006 MVI
1023 MXXIII
2014 MMXIV
3999 MMMCMXCIX

Another approach:
const ROMAN_NUMERALS = Object.freeze({
    M: 1000,
    CM: 900,
    D: 500,
    CD: 400,
    C: 100,
    XC: 90,
    L: 50,
    XL: 40,
    X: 10,
    IX: 9,
    V: 5,
    IV: 4,
    I: 1
})


function convertToRoman(num) {
    let rn = '';
    for (const [rnLetter, rnValue] of Object.entries(ROMAN_NUMERALS)) {
        const q = Math.trunc(num / rnValue);
        rn += rnLetter.repeat(q);
        num -= q * rnValue;
    }
    return rn;
}


[
    2,
    3,
    4,
    5,
    9,
    12,
    16,
    29,
    44,
    45,
    68,
    83, 
    97,
    99,
    400,
    500,
    501,
    649,
    798,
    891,
    1000,
    1004,
    1006,
    1023, 
    2014,
    3999
].forEach(arg => console.log(arg, convertToRoman(arg)));


Output:
2 II
3 III
4 IV
5 V
9 IX
12 XII
16 XVI
29 XXIX
44 XLIV
45 XLV
68 LXVIII
83 LXXXIII
97 XCVII
99 XCIX
400 CD
500 D
501 DI
649 DCXLIX
798 DCCXCVIII
891 DCCCXCI
1000 M
1004 MIV
1006 MVI
1023 MXXIII
2014 MMXIV
3999 MMMCMXCIX

Palindrome Checker

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/javascript-algorithms-and-data-structures-projects/palindrome-checker

JavaScript Algorithms and Data Structures Projects: Palindrome Checker
Return true if the given string is a palindrome. Otherwise, return false.

A palindrome is a word or sentence that's spelled the same way both forward and backward, ignoring punctuation, case, and spacing.

Note
You'll need to remove all non-alphanumeric characters (punctuation, spaces and symbols) and turn everything into the same case (lower or upper case) in order to check for palindromes.

We'll pass strings with varying formats, such as "racecar", "RaceCar", and "race CAR" among others.

We'll also pass strings with special symbols, such as "2A3*3a2", "2A3 3a2", and "2_A3*3#A2".


const removeAllNonNumeric = str => str.replace(/[\W_]/g, '');

function palindrome(str) {
  const clean = removeAllNonNumeric(str).toLowerCase();
  const len = clean.length;
  const mid = len / 2;

  for (let start = 0, end = len - 1; start < mid; ++start, --end) {
      if (clean[start] !== clean[end]) {
          return false;
      }
  }
  return true;
}

[
    "eye",
    "eye",
    "_eye",
    "race car",
    "not a palindrome",
    "A man, a plan, a canal. Panama",
    "never odd or even",
    "nope",
    "almostomla",
    "My age is 0, 0 si ega ym.",
    "1 eye for of 1 eye.",
    "0_0 (: /-\\ :) 0-0",
    "five|\\_/|four",
].forEach(arg => console.log(arg, ':', palindrome(arg)));



Output:
eye : true
eye : true
_eye : true
race car : true
not a palindrome : false
A man, a plan, a canal. Panama : true
never odd or even : true
nope : false
almostomla : false
My age is 0, 0 si ega ym. : true
1 eye for of 1 eye. : false
0_0 (: /-\ :) 0-0 : true
five|\_/|four : false

Saturday, July 25, 2020

Everything Be True

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/everything-be-true

Intermediate Algorithm Scripting: Everything Be True


Check if the predicate (second argument) is truthy on all elements of a collection (first argument).

In other words, you are given an array collection of objects. The predicate pre will be an object property and you need to return true if its value is truthy. Otherwise, return false.

In JavaScript, truthy values are values that translate to true when evaluated in a Boolean context.

Remember, you can access object properties through either dot notation or [] notation.


truthCheck([{"user": "Tinky-Winky", "sex": "male"}, {"user": "Dipsy", "sex": "male"}, {"user": "Laa-Laa", "sex": "female"}, {"user": "Po", "sex": "female"}], "sex") should return true.

truthCheck([{"user": "Tinky-Winky", "sex": "male"}, {"user": "Dipsy"}, {"user": "Laa-Laa", "sex": "female"}, {"user": "Po", "sex": "female"}], "sex") should return false.

truthCheck([{"user": "Tinky-Winky", "sex": "male", "age": 0}, {"user": "Dipsy", "sex": "male", "age": 3}, {"user": "Laa-Laa", "sex": "female", "age": 5}, {"user": "Po", "sex": "female", "age": 4}], "age") should return false.

truthCheck([{"name": "Pete", "onBoat": true}, {"name": "Repeat", "onBoat": true}, {"name": "FastForward", "onBoat": null}], "onBoat") should return false

truthCheck([{"name": "Pete", "onBoat": true}, {"name": "Repeat", "onBoat": true, "alias": "Repete"}, {"name": "FastForward", "onBoat": true}], "onBoat") should return true

truthCheck([{"single": "yes"}], "single") should return true

truthCheck([{"single": ""}, {"single": "double"}], "single") should return false

truthCheck([{"single": "double"}, {"single": undefined}], "single") should return false

truthCheck([{"single": "double"}, {"single": NaN}], "single") should return false

const truthCheck = (collection, pre) => collection.every(el => el[pre]);

[
    truthCheck([{"user": "Tinky-Winky", "sex": "male"}, {"user": "Dipsy", "sex": "male"}, {"user": "Laa-Laa", "sex": "female"}, {"user": "Po", "sex": "female"}], "sex"),
    truthCheck([{"user": "Tinky-Winky", "sex": "male"}, {"user": "Dipsy"}, {"user": "Laa-Laa", "sex": "female"}, {"user": "Po", "sex": "female"}], "sex") ,
    truthCheck([{"user": "Tinky-Winky", "sex": "male", "age": 0}, {"user": "Dipsy", "sex": "male", "age": 3}, {"user": "Laa-Laa", "sex": "female", "age": 5}, {"user": "Po", "sex": "female", "age": 4}], "age") ,
    truthCheck([{"name": "Pete", "onBoat": true}, {"name": "Repeat", "onBoat": true}, {"name": "FastForward", "onBoat": null}], "onBoat") ,
    truthCheck([{"name": "Pete", "onBoat": true}, {"name": "Repeat", "onBoat": true, "alias": "Repete"}, {"name": "FastForward", "onBoat": true}], "onBoat") ,
    truthCheck([{"single": "yes"}], "single"),
    truthCheck([{"single": ""}, {"single": "double"}], "single") ,
    truthCheck([{"single": "double"}, {"single": undefined}], "single") ,
    truthCheck([{"single": "double"}, {"single": NaN}], "single") ,
].forEach(result => console.log(result));


Output:
true
false
false
false
true
true
false
false
false

Map the Debris

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/map-the-debris

Intermediate Algorithm Scripting: Map the Debris


Return a new array that transforms the elements' average altitude into their orbital periods (in seconds).

The array will contain objects in the format {name: 'name', avgAlt: avgAlt}.

You can read about orbital periods on Wikipedia.

The values should be rounded to the nearest whole number. The body being orbited is Earth.

The radius of the earth is 6367.4447 kilometers, and the GM value of earth is 398600.4418 km3s-2.


orbitalPeriod([{name : "sputnik", avgAlt : 35873.5553}]) should return [{name: "sputnik", orbitalPeriod: 86400}].
Passed

orbitalPeriod([{name: "iss", avgAlt: 413.6}, {name: "hubble", avgAlt: 556.7}, {name: "moon", avgAlt: 378632.553}]) should return [{name : "iss", orbitalPeriod: 5557}, {name: "hubble", orbitalPeriod: 5734}, {name: "moon", orbitalPeriod: 2377399}].


function orbitalPeriod(arr) {
  var GM = 398600.4418;
  var earthRadius = 6367.4447;
  
  return arr.map(({name, avgAlt}) => ({
      name,
      orbitalPeriod: Math.round(
          2 * Math.PI * 
          Math.sqrt(
              Math.pow(earthRadius + avgAlt, 3) 
              / 
              GM
          )
      )
  }));
}

[
    orbitalPeriod([{name : "sputnik", avgAlt : 35873.5553}]),
    orbitalPeriod([
        {name: "iss", avgAlt: 413.6}, 
        {name: "hubble", avgAlt: 556.7}, 
        {name: "moon", avgAlt: 378632.553}
    ])
].forEach(result => console.log(result));


Convert HTML Entities

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/convert-html-entities


Intermediate Algorithm Scripting: Convert HTML Entities

Convert the characters &, <, >, " (double quote), and ' (apostrophe), in a string to their corresponding HTML entities.


convertHTML("Dolce & Gabbana") should return "Dolce &amp; Gabbana".

convertHTML("Hamburgers < Pizza < Tacos") should return "Hamburgers &lt; Pizza &lt; Tacos". convertHTML("Sixty > twelve") should return "Sixty > twelve".

convertHTML('Stuff in "quotation marks"') should return "Stuff in "quotation marks"".

convertHTML("Schindler's List") should return "Schindler's List".

convertHTML("<>") should return "<>".

convertHTML("abc") should return "abc"


const HTML_ENTITIES = Object.freeze({
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&apos;'
});


function convertHTML(str) {
  return str.replace(/[&<>"']/g, result => HTML_ENTITIES[result]);
}

[
  'Dolce & Gabbana',
  'Hamburgers < Pizza < Tacos',
  'Sixty > twelve',
  'Stuff in "quotation marks"',
  "Schindler's List",
  '<>',
  'abc'
].forEach(param => console.log(convertHTML(param)));


Output:
Dolce &amp; Gabbana
Hamburgers &lt; Pizza &lt; Tacos
Sixty &gt; twelve
Stuff in &quot;quotation marks&quot;
Schindler&apos;s List
<>
abc

Friday, July 24, 2020

Missing letters

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/missing-letters

Intermediate Algorithm Scripting: Missing letters


Find the missing letter in the passed letter range and return it.

If all letters are present in the range, return undefined.


fearNotLetter("abce") should return "d".

fearNotLetter("abcdefghjklmno") should return "i".

fearNotLetter("stvwx") should return "u".

fearNotLetter("bcdf") should return "e".

fearNotLetter("abcdefghijklmnopqrstuvwxyz") should return undefined.


const fearNotLetter = str => 
  str.split('').reduce((a, b, i, letters) => 
      b.charCodeAt(0) - a.charCodeAt(0) === 1 ?
          (i < letters.length - 1 ? b : undefined)
      :
          (letters.splice(i), String.fromCharCode(a.charCodeAt(0) + 1))   
  );

[
    "abce",
    'abcdefghjklmno',
    'stvwx',
    'bcdf',
    'abcdefghijklmnopqrstuvwxyz'
].forEach(param => console.log(fearNotLetter(param)));

Output:
d
i
u
e
undefined

Sorted Union

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/sorted-union

Intermediate Algorithm Scripting: Sorted Union


Write a function that takes two or more arrays and returns a new array of unique values in the order of the original provided arrays.

In other words, all values present from all arrays should be included in their original order, but with no duplicates in the final array.

The unique numbers should be sorted by their original order, but the final array should not be sorted in numerical order.

Check the assertion tests for examples.


uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1]) should return [1, 3, 2, 5, 4].

uniteUnique([1, 2, 3], [5, 2, 1]) should return [1, 2, 3, 5].

uniteUnique([1, 2, 3], [5, 2, 1, 4], [2, 1], [6, 7, 8]) should return [1, 2, 3, 5, 4, 6, 7, 8].


const uniteUnique = (...arrList) => arrList.reduce((a, b) => [...a, ...b.filter(el => !a.includes(el))]);

[
  uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1]),
  uniteUnique([1, 2, 3], [5, 2, 1]),
  uniteUnique([1, 2, 3], [5, 2, 1, 4], [2, 1], [6, 7, 8])
].forEach(result => console.log(result));


Output:
[ 1, 3, 2, 5, 4 ]
[ 1, 2, 3, 5 ]
[ 1, 2, 3, 5, 4, 6, 7, 8 ]

Thursday, July 23, 2020

Arguments Optional

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/arguments-optional

Intermediate Algorithm Scripting: Arguments Optional


Create a function that sums two arguments together. If only one argument is provided, then return a function that expects one argument and returns the sum.

For example, addTogether(2, 3) should return 5, and addTogether(2) should return a function.

Calling this returned function with a single argument will then return the sum:

var sumTwoAnd = addTogether(2);

sumTwoAnd(3) returns 5.

If either argument isn't a valid number, return undefined.


addTogether(2, 3) should return 5.

addTogether(23, 30) should return 53.

addTogether(5)(7) should return 12.

addTogether("http://bit.ly/IqT6zt") should return undefined.

addTogether(2, "3") should return undefined.

addTogether(2)([3]) should return undefined.


function addTogether(a, b) {
    if (typeof a === 'number') {
        if (typeof b === 'number') {
          return a + b;
        } else if (b === undefined) {
          return toAdd => addTogether(a, toAdd);
        } 
    }
}

[
    addTogether(2, 3),
    addTogether(23, 30),
    addTogether(5)(7),
    addTogether(2, "3"),
    addTogether(2)([3])
].forEach(result => console.log(result));

Output:
5
53
12
undefined
undefined

Wherefore art thou

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/wherefore-art-thou

Intermediate Algorithm Scripting: Wherefore art thou

Make a function that looks through an array of objects (first argument) and returns an array of all objects that have matching name and value pairs (second argument). Each name and value pair of the source object has to be present in the object from the collection if it is to be included in the returned array.

For example, if the first argument is [{ first: "Romeo", last: "Montague" }, { first: "Mercutio", last: null }, { first: "Tybalt", last: "Capulet" }], and the second argument is { last: "Capulet" }, then you must return the third object from the array (the first argument), because it contains the name and its value, that was passed on as the second argument.


whatIsInAName([{ first: "Romeo", last: "Montague" }, { first: "Mercutio", last: null }, { first: "Tybalt", last: "Capulet" }], { last: "Capulet" }) should return [{ first: "Tybalt", last: "Capulet" }].

whatIsInAName([{ "apple": 1 }, { "apple": 1 }, { "apple": 1, "bat": 2 }], { "apple": 1 }) should return [{ "apple": 1 }, { "apple": 1 }, { "apple": 1, "bat": 2 }].

whatIsInAName([{ "apple": 1, "bat": 2 }, { "bat": 2 }, { "apple": 1, "bat": 2, "cookie": 2 }], { "apple": 1, "bat": 2 }) should return [{ "apple": 1, "bat": 2 }, { "apple": 1, "bat": 2, "cookie": 2 }].

whatIsInAName([{ "apple": 1, "bat": 2 }, { "apple": 1 }, { "apple": 1, "bat": 2, "cookie": 2 }], { "apple": 1, "cookie": 2 }) should return [{ "apple": 1, "bat": 2, "cookie": 2 }].

whatIsInAName([{ "apple": 1, "bat": 2 }, { "apple": 1 }, { "apple": 1, "bat": 2, "cookie": 2 }, { "bat":2 }], { "apple": 1, "bat": 2 }) should return [{ "apple": 1, "bat": 2 }, { "apple": 1, "bat": 2, "cookie":2 }].

whatIsInAName([{"a": 1, "b": 2, "c": 3}], {"a": 1, "b": 9999, "c": 3}) should return []


const whatIsInAName = (collection, source) =>
  collection.filter(
    el => Object.entries(source).every(([key, value]) => el[key] == value)
  );


[
  whatIsInAName([{ first: "Romeo", last: "Montague" }, { first: "Mercutio", last: null }, { first: "Tybalt", last: "Capulet" }], { last: "Capulet" }),
  whatIsInAName([{ "apple": 1 }, { "apple": 1 }, { "apple": 1, "bat": 2 }], { "apple": 1 }),
  whatIsInAName([{ "apple": 1, "bat": 2 }, { "bat": 2 }, { "apple": 1, "bat": 2, "cookie": 2 }], { "apple": 1, "bat": 2 }),
  whatIsInAName([{ "apple": 1, "bat": 2 }, { "apple": 1 }, { "apple": 1, "bat": 2, "cookie": 2 }], { "apple": 1, "cookie": 2 }),
  whatIsInAName([{ "apple": 1, "bat": 2 }, { "apple": 1 }, { "apple": 1, "bat": 2, "cookie": 2 }, { "bat":2 }], { "apple": 1, "bat": 2 }),
  whatIsInAName([{"a": 1, "b": 2, "c": 3}], {"a": 1, "b": 9999, "c": 3})
].forEach(result => console.log(result));

Output:
[ { first: 'Tybalt', last: 'Capulet' } ]
[ { apple: 1 }, { apple: 1 }, { apple: 1, bat: 2 } ]
[ { apple: 1, bat: 2 }, { apple: 1, bat: 2, cookie: 2 } ]
[ { apple: 1, bat: 2, cookie: 2 } ]
[ { apple: 1, bat: 2 }, { apple: 1, bat: 2, cookie: 2 } ]
[]

Pig Latin

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/pig-latin

Intermediate Algorithm Scripting: Pig Latin

Pig Latin is a way of altering English Words. The rules are as follows:

- If a word begins with a consonant, take the first consonant or consonant cluster, move it to the end of the word, and add "ay" to it.

- If a word begins with a vowel, just add "way" at the end.


translatePigLatin("california") should return "aliforniacay".

translatePigLatin("paragraphs") should return "aragraphspay".

translatePigLatin("glove") should return "oveglay".

translatePigLatin("algorithm") should return "algorithmway".

translatePigLatin("eight") should return "eightway".

Should handle words where the first vowel comes in the middle of the word. translatePigLatin("schwartz") should return "artzschway".

Should handle words without vowels. translatePigLatin("rhythm") should return "rhythmay".

function translatePigLatin(str) {
  return str.replace(/^(((?![aeiou])[a-z])+)(\w*)/,  '$3$1') 
      + ('aeiou'.split('').includes(str[0]) ? 'w' : '') +  'ay';
}

[
  'california',
  'paragraphs',
  'glove',
  'algorithm',
  'eight',
  'schwartz',
  'rhythm',
  'c2alifornia'
].forEach(word => console.log(translatePigLatin(word)));

Output:
aliforniacay
aragraphspay
oveglay
algorithmway
eightway
artzschway
rhythmay
2aliforniacay

Spinal Tap Case

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/spinal-tap-case

Intermediate Algorithm Scripting: Spinal Tap Case

Convert a string to spinal case. Spinal case is all-lowercase-words-joined-by-dashes.

spinalCase("This Is Spinal Tap") should return "this-is-spinal-tap".

spinalCase("thisIsSpinalTap") should return "this-is-spinal-tap".

spinalCase("The_Andy_Griffith_Show") should return "the-andy-griffith-show".

spinalCase("Teletubbies say Eh-oh") should return "teletubbies-say-eh-oh".

spinalCase("AllThe-small Things") should return "all-the-small-things".

const spinalCase = str => 
   str.match(/([A-Z]|[a-z])[a-z]*/g).map(word => word.toLowerCase()).join('-');

[
  'This Is Spinal Tap',
  'thisIsSpinalTap',
  'The_Andy_Griffith_Show',
  'Teletubbies say Eh-oh',
  'AllThe-small Things'
].forEach(param => console.log(spinalCase(param)));


Output:
this-is-spinal-tap
this-is-spinal-tap
the-andy-griffith-show
teletubbies-say-eh-oh
all-the-small-things

Binary Agents

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/binary-agents

Intermediate Algorithm Scripting: Binary Agents


Return an English translated sentence of the passed binary string.

The binary string will be space separated.

function es5_without_apply_binaryAgent(str) {
    return str.split(' ').map(function(letterBin) { 
        return String.fromCharCode(parseInt(letterBin, 2)); 
    })
    .join('');
}

function es5_with_apply_binaryAgent(str) {
    return String.fromCharCode.apply(
        null,
        str.split(' ').map(function(letterBin) { 
            return parseInt(letterBin, 2); 
        })
    );
}

// es6 arrow function and spread (apply's syntactic sugar) operator (...)
const binaryAgent = str => String.fromCharCode(...str.split(' ').map(bin => parseInt(bin, 2)));

console.log(
  binaryAgent("01000001 01110010 01100101 01101110 00100111 01110100 00100000 01100010 01101111 01101110 01100110 01101001 01110010 01100101 01110011 00100000 01100110 01110101 01101110 00100001 00111111")
);

console.log(
  binaryAgent("01001001 00100000 01101100 01101111 01110110 01100101 00100000 01000110 01110010 01100101 01100101 01000011 01101111 01100100 01100101 01000011 01100001 01101101 01110000 00100001")
);


Output:
Aren't bonfires fun!?
I love FreeCodeCamp!

Diff Two Arrays

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/diff-two-arrays

Intermediate Algorithm Scripting: Diff Two Arrays

Compare two arrays and return a new array with any items only found in one of the two given arrays, but not both. In other words, return the symmetric difference of the two arrays.

Note
You can return the array with its elements in any order.

diffArray([1, 2, 3, 5], [1, 2, 3, 4, 5]) should return an array.

["diorite", "andesite", "grass", "dirt", "pink wool", "dead shrub"], ["diorite", "andesite", "grass", "dirt", "dead shrub"] should return ["pink wool"].

["diorite", "andesite", "grass", "dirt", "pink wool", "dead shrub"], ["diorite", "andesite", "grass", "dirt", "dead shrub"] should return an array with one item.

["andesite", "grass", "dirt", "pink wool", "dead shrub"], ["diorite", "andesite", "grass", "dirt", "dead shrub"] should return ["diorite", "pink wool"].

["andesite", "grass", "dirt", "pink wool", "dead shrub"], ["diorite", "andesite", "grass", "dirt", "dead shrub"] should return an array with two items.

["andesite", "grass", "dirt", "dead shrub"], ["andesite", "grass", "dirt", "dead shrub"] should return [].

["andesite", "grass", "dirt", "dead shrub"], ["andesite", "grass", "dirt", "dead shrub"] should return an empty array.

[1, 2, 3, 5], [1, 2, 3, 4, 5] should return [4].

[1, 2, 3, 5], [1, 2, 3, 4, 5] should return an array with one item.

[1, "calf", 3, "piglet"], [1, "calf", 3, 4] should return ["piglet", 4].

[1, "calf", 3, "piglet"], [1, "calf", 3, 4] should return an array with two items.

[], ["snuffleupagus", "cookie monster", "elmo"] should return ["snuffleupagus", "cookie monster", "elmo"].

[], ["snuffleupagus", "cookie monster", "elmo"] should return an array with three items.

[1, "calf", 3, "piglet"], [7, "filly"] should return [1, "calf", 3, "piglet", 7, "filly"].

[1, "calf", 3, "piglet"], [7, "filly"] should return an array with six items.


Array.prototype.notIn = function(arr) {
  return this.filter(el => !arr.includes(el));
}

const diffArray = (arr1, arr2) => [...arr1.notIn(arr2), ...arr2.notIn(arr1)];

console.log(diffArray([1, 2, 3, 5], [1, 2, 3, 4, 5]));

console.log();
[
  [["diorite", "andesite", "grass", "dirt", "pink wool", "dead shrub"], ["diorite", "andesite", "grass", "dirt", "dead shrub"]],
  [["andesite", "grass", "dirt", "pink wool", "dead shrub"], ["diorite", "andesite", "grass", "dirt", "dead shrub"]],
  [["andesite", "grass", "dirt", "dead shrub"], ["andesite", "grass", "dirt", "dead shrub"]],
  [[1, 2, 3, 5], [1, 2, 3, 4, 5]],
  [[1, "calf", 3, "piglet"], [1, "calf", 3, 4]],
  [[], ["snuffleupagus", "cookie monster", "elmo"]],
  [[1, "calf", 3, "piglet"], [7, "filly"]]
].forEach(params => console.log(diffArray(...params)));


Output:
[ 4 ]

[ 'pink wool' ]
[ 'pink wool', 'diorite' ]
[]
[ 4 ]
[ 'piglet', 4 ]
[ 'snuffleupagus', 'cookie monster', 'elmo' ]
[ 1, 'calf', 3, 'piglet', 7, 'filly' ]

Sum all numbers in a range

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/sum-all-numbers-in-a-range


Intermediate Algorithm Scripting: Sum All Numbers in a Range

We'll pass you an array of two numbers. Return the sum of those two numbers plus the sum of all the numbers between them. The lowest number will not always come first.

For example, sumAll([4,1]) should return 10 because sum of all the numbers between 1 and 4 (both inclusive) is 10.


sumAll([1, 4]) should return a number.

sumAll([1, 4]) should return 10.

sumAll([4, 1]) should return 10.

sumAll([5, 10]) should return 45.

sumAll([10, 5]) should return 45.


const sumRange = n => (n * (n+1)) / 2;
 
const sumAll = arr => sumRange(Math.max(...arr)) - sumRange(Math.min(...arr) - 1);
 
[
    [1, 4],
    [4, 1],
    [5, 10],
    [10, 5]
].forEach(param => console.log(sumAll(param)));

10
10
45
45

Steamroller

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/steamroller

Intermediate Algorithm Scripting: Steamroller

Flatten a nested array. You must account for varying levels of nesting.


const steamrollArray = arr => 
    arr.reduce((acc, el) => acc.concat(Array.isArray(el) ? steamrollArray(el) : el), []);
    
[
  steamrollArray([[["a"]], [["b"]]]),
  steamrollArray([1, [2], [3, [[4]]]]),
  steamrollArray([1, [], [3, [[4]]]]),
  steamrollArray([1, {}, [3, [[4]]]]) 
].forEach(result => console.log(result));

Output:
[ 'a', 'b' ]
[ 1, 2, 3, 4 ]
[ 1, 3, 4 ]
[ 1, {}, 3, 4 ]

Drop it

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/drop-it

Intermediate Algorithm Scripting: Drop it

Given the array arr, iterate through and remove each element starting from the first element (the 0 index) until the function func returns true when the iterated element is passed through it.

Then return the rest of the array once the condition is satisfied, otherwise, arr should be returned as an empty array.

dropElements([1, 2, 3, 4], function(n) {return n >= 3;}) should return [3, 4].

dropElements([0, 1, 0, 1], function(n) {return n === 1;}) should return [1, 0, 1].

dropElements([1, 2, 3], function(n) {return n > 0;}) should return [1, 2, 3].

dropElements([1, 2, 3, 4], function(n) {return n > 5;}) should return [].

dropElements([1, 2, 3, 7, 4], function(n) {return n > 3;}) should return [7, 4].

dropElements([1, 2, 3, 9, 2], function(n) {return n > 2;}) should return [3, 9, 2].


Array.prototype.indexOfCondition = function (func) {
    for (let i = 0; i < this.length; ++i) {
        if (func(this[i])) {
            return i;
        }
    }

    // it's better to use undefined instead of -1.
    // see slice code below what we can do with undefined
    return undefined;
};

const dropElements = (arr, func) => arr.slice(arr.indexOfCondition(func) ?? arr.length);

[
    dropElements([1, 2, 3, 4], n => n >= 3),
    dropElements([0, 1, 0, 1], n => n === 1),
    dropElements([1, 2, 3], n => n > 0),
    dropElements([1, 2, 3, 4], n => n > 5),
    dropElements([1, 2, 3, 7, 4], n => n > 3),
    dropElements([1, 2, 3, 9, 2], n => n > 2)
].forEach(result => console.log(result));

Output:
[ 3, 4 ]
[ 1, 0, 1 ]
[ 1, 2, 3 ]
[]
[ 7, 4 ]
[ 3, 9, 2 ]

Smallest common multiple

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/smallest-common-multiple

Materials used:
https://en.wikipedia.org/wiki/Euclidean_algorithm
https://en.wikipedia.org/wiki/Least_common_multiple

Intermediate Algorithm Scripting: Smallest Common Multiple

Find the smallest common multiple of the provided parameters that can be evenly divided by both, as well as by all sequential numbers in the range between these parameters.

The range will be an array of two numbers that will not necessarily be in numerical order.

For example, if given 1 and 3, find the smallest common multiple of both 1 and 3 that is also evenly divisible by all numbers between 1 and 3. The answer here would be 6.

smallestCommons([1, 5]) should return a number.

smallestCommons([1, 5]) should return 60.

smallestCommons([5, 1]) should return 60.

smallestCommons([2, 10]) should return 2520.

smallestCommons([1, 13]) should return 360360.

smallestCommons([23, 18]) should return 6056820.


// if this causes stack overflow, search stackoverflow for better implementation.
// this would work too even if hi and lo are swapped.
const recGcd = (hi, lo) => lo === 0 ? hi : recGcd(lo, hi % lo);

// If your small code has if, has loop, or return keyword,
// don't use arrow function, just use plain old function instead.
// In fact, the arrow function will become longer due to const keyword use.
// And you need to use const, as no one modifies the behavior of arrow functions.

// function gcd(a, b) {
// let gcd = (a, b) => {
// const gcd = (a, b) => {

// this won't cause stack overflow
// this would work too even if hi and lo are swapped.
function gcd(hi, lo) {
    while (hi % lo !== 0) {
        [hi, lo] = [lo, hi % lo];
    } 
    return lo;
}

const lcm = (a, b) => a * b / gcd(a, b);

function smallestCommons(arr) {
    const lo = Math.min(...arr);
    const hi = Math.max(...arr);

    let lastLcm = lo;
    for (let i = lastLcm; i < hi; ++i) {
      lastLcm = lcm(lastLcm, i + 1);
    }
    return lastLcm;
}

console.log(gcd(252, 105));
console.log();
[
    [1, 5],
    [5, 1],
    [2, 10],
    [1, 13],
    [23, 18]
].forEach(arr => console.log(smallestCommons(arr)));

Output:
21

60
60
2520
360360
6056820

Wednesday, July 22, 2020

Sum All Primes

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/sum-all-primes

Intermediate Algorithm Scripting: Sum All Primes


A prime number is a whole number greater than 1 with exactly two divisors: 1 and itself. For example, 2 is a prime number because it is only divisible by 1 and 2. In contrast, 4 is not prime since it is divisible by 1, 2 and 4.

Rewrite sumPrimes so it returns the sum of all prime numbers that are less than or equal to num.

sumPrimes(10) should return a number.

sumPrimes(10) should return 17.

sumPrimes(977) should return 73156.


function* primesGenerator(qualifierFunc) {
    const primes = [2];
    yield primes[0];
    for (let odd = 3; ; odd += 2) {
        if (qualifierFunc && !qualifierFunc(odd)) {
            return;
        }
 
        if (primes.some(n => odd % n === 0)) {
            continue;
        }

        yield odd;
        primes.push(odd);
    }
}


function sumPrimes(num) {
    return Array.from(primesGenerator(n => n <= num)).reduce((a, b) => a + b);
}

console.log(sumPrimes(10));
console.log(sumPrimes(977));


Output:
17
73156

Making a fibonacci generator that can be consumed in functional manner

From https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/sum-all-odd-fibonacci-numbers

Given a positive integer num, return the sum of all odd Fibonacci numbers that are less than or equal to num.

The first two numbers in the Fibonacci sequence are 1 and 1. Every additional number in the sequence is the sum of the two previous numbers. The first six numbers of the Fibonacci sequence are 1, 1, 2, 3, 5 and 8.

For example, sumFibs(10) should return 10 because all odd Fibonacci numbers less than or equal to 10 are 1, 1, 3, and 5.

sumFibs(1) should return a number.

sumFibs(1000) should return 1785.

sumFibs(4000000) should return 4613732.

sumFibs(4) should return 5.

sumFibs(75024) should return 60696.

sumFibs(75025) should return 135721.

function* fibGenerator(qualifierFunc) {
    let a = 0;
    let b = 1;

    for (;;) {
        if (qualifierFunc && !qualifierFunc(a)) {
            return;
        } 

        yield a;

        [a, b] = [b, a + b];
    }
}

We can solve it this way:
let sum = 0;
let value, done;
const iter = fibGenerator(n => n <= 1000);
while (!({value,done} = iter.next()).done) {
    sum += value % 2 == 1 ? value : 0;
}  

console.log('fib sum', sum);

Output:
fib sum 1785

Or we can do it in functional way:
console.log('fib sum', 
    Array.from(fibGenerator(n => n <= 1000), v => v % 2 === 1 ? v : 0).reduce((a, b) => a + b)
);

Output:
fib sum 1785

One thing that stands out the most on functional programming is that most of the time you don't have to use assignment operations to perform a task. That's one of the hallmarks of functional programming.

Lazy-evaluating iterators with Array.from

function* fibGenerator(qualifierFunc) {
    let a = 0;
    let b = 1;

    for (;;) {
        if (qualifierFunc && !qualifierFunc(a)) {
            return;
        }
        console.log('gen fib', a);

        yield a;
        const n = a;
        a += b;
        b = n;
    }
}

Array.from(fibGenerator(n => n <= 13), v => console.log('hey', v));

console.log('----');

Array.from(fibGenerator(n => n <= 13)).map(v => console.log('hey', v));


gen fib 0
hey 0
gen fib 1
hey 1
gen fib 1
hey 1
gen fib 2
hey 2
gen fib 3
hey 3
gen fib 5
hey 5
gen fib 8
hey 8
gen fib 13
hey 13
----
gen fib 0
gen fib 1
gen fib 1
gen fib 2
gen fib 3
gen fib 5
gen fib 8
gen fib 13
hey 0
hey 1
hey 1
hey 2
hey 3
hey 5
hey 8
hey 13

The first Array.from is better as it does not allocate memory. Don't defeat the purpose of iterator's lazy-evaluating capability.

A robust search and replace, using regular expression (I think*)

This is a robust implementation of https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/search-and-replace

function myReplace(str, before, after) {
    return str.replace(
        new RegExp(`(?<=\\s|,)${escapeRegExp(before)}(?!\\w)`, 'gi'), 
              x => (x[0] === x[0].toUpperCase() ? after[0].toUpperCase() : after[0]) + after.slice(1)
              // this would work too:
              // x => after[0][`to${x[0] === x[0].toUpperCase() ? 'Upper' : 'Lower'}Case`]() + after.slice(1)
    );
}

// bobince's version
function escapeRegExp(string) {
    return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}


const source = `The net profit of ASP.NET is nice. It's a money-magnet platform
A good safety net. Net is what spider-man makes. .NET is nice
.NET is super cool
The netizens are correct. .net, .NET are all the same thing
Some basket,net,basketball net,toys
This a good Net I would say
That is a good net
Net satisfaction is at all-time high
Good net!`;
 
console.log(myReplace(source, "net", "gross"));   
console.log();
console.log(myReplace(source, ".NET", "_DOT_NET_")); 
console.log();
console.log(myReplace(source, "ASP.NET", "ASP Classic"));   

Output:
The gross profit of ASP.NET is nice. It's a money-magnet platform
A good safety gross. Gross is what spider-man makes. .NET is nice
.NET is super cool
The netizens are correct. .net, .NET are all the same thing
Some basket,gross,basketball gross,toys
This a good Gross I would say
That is a good gross
Gross satisfaction is at all-time high
Good gross!

The net profit of ASP.NET is nice. It's a money-magnet platform
A good safety net. Net is what spider-man makes. _DOT_NET_ is nice
_DOT_NET_ is super cool
The netizens are correct. _DOT_NET_, _DOT_NET_ are all the same thing
Some basket,net,basketball net,toys
This a good Net I would say
That is a good net
Net satisfaction is at all-time high
Good net!

The net profit of ASP Classic is nice. It's a money-magnet platform
A good safety net. Net is what spider-man makes. .NET is nice
.NET is super cool
The netizens are correct. .net, .NET are all the same thing
Some basket,net,basketball net,toys
This a good Net I would say
That is a good net
Net satisfaction is at all-time high
Good net!

Tuesday, July 21, 2020

Seduced by reduce

It's hard to resist functional programming.

Functional programming helps us eliminate side-effects in code.

reduce function is one of those functions that let the developer adhere to functional programming paradigm.

However, there's no built-in way to break early from reduce's iteration. Why we need to break early? Optimization of course.

There's a way to break early from reduce's iteration, empty out the array so it won't have anything to iterate on next. splice can do the job of emptying out the array.

splice can introduce side-effects if the code is working on source input, or if there's another statement that depends on splice's source array.

With that said, in the code below there's no side effects since there's no way that splice can alter the input argument (str), and the str argument is not the source array of splice. There's no side-effects too as there's no succeeding statement that depends on slice's source array after the slice below executes.

const fearNotLetter = str => 
    str.split('').reduce((currentLetter, nextToCurrentLetter, currentLetterIndex, letters) => 
        currentLetter.charCodeAt(0) + 1 === nextToCurrentLetter.charCodeAt(0) ?
            (currentLetterIndex < str.length - 1 ? nextToCurrentLetter : undefined)
        :  
            (letters.splice(currentLetterIndex), String.fromCharCode(currentLetter.charCodeAt(0) + 1))
    );


console.log(fearNotLetter("abce"));
console.log(fearNotLetter("abcdefghjklmno"));
console.log(fearNotLetter("stvwx"));
console.log(fearNotLetter("bcdf"));
console.log(fearNotLetter("abcdefghijklmnopqrstuvwxyz"));
Output:
d
i
u
e
undefined
Puzzle from: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/missing-letters

When life gives you lemons, make functions

// You can't do this in Math.min and Math.max functions:
const arr = [20,10,30,40];

console.log(Math.min(arr));
console.log(Math.max(arr));

// You have to do this:
console.log(Math.min(20,10,30,40));
console.log(Math.max(20,10,30,40));

Output:
NaN
NaN
10
40

Whoever design the Math.min and Math.max functions must be thinking that devs know in advance how many items the user will create.

Here's how to pass array to Math.min and Math.max:
const arr = [20,10,30,40];

console.log(Math.min.apply(null, arr));
console.log(Math.max.apply(null, arr));

Output:
10
40

That's tedious if you need to do it often. Generate functions that accepts array out of Math.min and Math.max.
const getThe = [Math.min, Math.max].reduce((fns, f) => ({
    ...fns,
    [f.name]: Function.apply.bind(f, null)
}), {});



const arr = [20,10,30,40];

console.log(getThe.min(arr));
console.log(getThe.max(arr));


Output:
10
40

When life gives you syntactic sugar? Well, gulp them!
const arr = [20,10,30,40];

console.log(Math.min(...arr));
console.log(Math.max(...arr));

Output:
10
40

Monday, July 20, 2020

JavaScript, prototype, constructor. Things you will not attempt in other languages

function Foo() {}

// this is used to simulate OOP's static method 
Foo.baz = function () {
    console.log('baz');
};
// this is used to simulate OOP's instance method
Foo.prototype.bar = function () {
    console.log('bar');
};

console.log('fighter');
Foo.fighter = function () {
    this.baz();
    // this.bar(); // TypeError: this.bar is not a function

    this.prototype.bar();
    this.prototype.constructor.baz();
};
Foo.fighter();

console.log('warrior');
const great = new Foo();
great.warrior = function () {
    this.bar();
    // this.baz(); // TypeError: this.baz is not a function

    this.constructor.baz();
    this.constructor.prototype.bar();
};
great.warrior();

fighter
baz
bar
baz
warrior
bar
baz
bar

Saturday, July 18, 2020

Removing side effect

If the first statement modifies the variable that the second statement relies on, the code produces side effects or errors.

On functional programming, the original data must not be modified. You should not produce an output by modifying an input.

Hence (courtesy of freeCodeCamp), the following would produce side effect:

https://jsfiddle.net/c79nqfh5/2/
var Window = function(tabs) {
  this.tabs = tabs; // We keep a record of the array inside the object
};

// When you close a tab
Window.prototype.tabClose = function (index) {

  // Only change code below this line

  var tabsBeforeIndex = this.tabs.splice(0, index); // Get the tabs before the tab
  var tabsAfterIndex = this.tabs.splice(index + 1); // Get the tabs after the tab
  this.tabs = tabsBeforeIndex.concat(tabsAfterIndex); // Join them together


  // Only change code above this line

  return this;
};

var videoWindow = new Window(['Netflix', 'YouTube', 'Vimeo', 'Vine']); // Entertainment sites
console.log(videoWindow.tabClose(2).tabs); 

Output:
["Netflix", "YouTube"]

The output is incorrect, splice modifies the this.tabs state that tabsAfterIndex's expression relies on.

One way to solve the problem is to create a copy for this.tabs.

https://jsfiddle.net/c79nqfh5/3/
var tabsBeforeIndex = [...this.tabs].splice(0, index); // Get the tabs before the tab
    var tabsAfterIndex = [...this.tabs].splice(index + 1); // Get the tabs after the tab
    this.tabs = tabsBeforeIndex.concat(tabsAfterIndex); // Join them together

Output:
["Netflix", "YouTube", "Vine"]

But we should not do that if we can use a function that don't destroy the variable it operates on. In this case, we can use the slice function instead of splice.

https://jsfiddle.net/c79nqfh5/5/
var tabsBeforeIndex = this.tabs.slice(0, index);
  var tabsAfterIndex = this.tabs.slice(index + 1);
  this.tabs = tabsBeforeIndex.concat(tabsAfterIndex);

Output:
["Netflix", "YouTube", "Vine"]


If using ES6 or TypeScript, it can be improved further:
    this.tabs = [...this.tabs.slice(0, index), ...this.tabs.slice(index + 1)];

Wednesday, July 15, 2020

Simple inheritance

function Animal(theName) {
  this.animalName = theName;
  console.log('The animal constructor is called, the name is ' + theName);
}


Animal.prototype = {
  constructor: Animal,
  eat: function() {
    console.log("nom nom nom");
  }
};

function Dog(theName) { 
  console.log('dog constructor called');
  Animal.call(this, theName)

}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
  console.log(`Dog named ${this.animalName} is barking`);
}


// Only change code below this line


const beagle = new Dog('spot');
console.log(Dog.prototype.isPrototypeOf(beagle));
console.log(Animal.prototype.isPrototypeOf(Dog.prototype));
console.log(Animal.prototype.isPrototypeOf(beagle));
console.log(beagle.constructor === Dog);
console.log(beagle.constructor === Animal);

beagle.bark();

dog constructor called
The animal constructor is called, the name is spot
true
true
true
true
false
Dog named spot is barking

Friday, May 29, 2020

this is new

Beware of sharing this.

Here's how to check if your this is new, or when you are accidentally using a shared this.

function Abbey() {
    this.name = 'you know my name';
    console.log('Abbey() called');
    this.jude = function () {
        console.log("Abbey's jude() called");
    }
}

Abbey.prototype.great = function () {
    console.log('great');
};


function jude() {
    console.log('root jude() called');
}


function hey() {
    console.log('Hey() called');
    try {
        this.jude();
    } catch (ex) {
        console.log("I'm not running in node/browser's REPL. Can't resolve this.jude()");
        console.log(ex);
    }
}


new Abbey();
hey();
console.log('\nthis is messy\n');
Abbey(); // forgot the new keyword
hey();


console.log('\n\nnew Abbey():');
// a's this is new. not shared this
var a = new Abbey();
console.log(a);
a.great();

Output:

dev@Developers-iMac % node y.js
Abbey() called
Hey() called
I'm not running in node/browser's REPL. Can't resolve this.jude()
TypeError: this.jude is not a function
    at hey (/Users/dev/Desktop/y.js:22:14)
    at Object. (/Users/dev/Desktop/y.js:31:1)
    at Module._compile (internal/modules/cjs/loader.js:959:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:995:10)
    at Module.load (internal/modules/cjs/loader.js:815:32)
    at Function.Module._load (internal/modules/cjs/loader.js:727:14)
    at Function.Module.runMain (internal/modules/cjs/loader.js:1047:10)
    at internal/main/run_main_module.js:17:11

this is messy

Abbey() called
Hey() called
Abbey's jude() called


new Abbey():
Abbey() called
Abbey { name: 'you know my name', jude: [Function] }
great

It's better to attach the instance's method to .prototype instead of attaching it to this. Aside from that it prevents accidentally using a shared this, it also unclutters the instance's data.

As seen from the output, the jude method become part of the data, whereas the great method doesn't.


The this can accidentally call other code when the code is run in node/browser's REPL. The following is the output of the same exact code above when it is run in REPL instead. Note that root jude is called.

Reduce this confusion

Experimented from: https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback/20279485#20279485


When reusing an instance's method from another object (see great and anotherGreat below), access it directly from the name of the function that holds the prototype, e.g., Beatles

Thursday, May 28, 2020

Make your function .apply.bind-friendly

The this.nice(); code below, though you can invoke your non-instance methods that way, does not mean you should.


Most of the time, Function.apply.bind's second parameter is null for utility functions. Utility functions are those functions that belongs to class, not to the instance of the class.

The .nice won't get invoked when using .apply.bind. Worse yet, when using function-based class (pre-ES6), this points to global this, so if there's a global nice function, it would get called instead.




Though ES6 has first-class support for class, and null will be assigned to this when invoking .apply.bind on class's static method, thus preventing miscalled/hijacked function, it's not a good practice to invoke static (non-instance) methods from a this reference.





Just call the function directly from the parent function/class:






Do note that TypeScript transpilation won't assign null to this when using Function.apply.bind against the generated class. this still receives global this when .apply's first parameter is passed with null value.

The transpilation can't prevent miscalled/hijacked function.






Note that miscalled/hijacked function happens only on node's REPL or browser's console tool. It does not happen when invoked directly via node:

Wednesday, May 27, 2020

Confuse yourself less when using .bind

.apply.bind, .call.bind

To make a shortcut for Math.max.apply(null, [5,8,6])

Use this:
var maxArray = Function.apply.bind(Math.max, null);

var maxValue = maxArray([5,8,6]);


This question was asked on stackoverflow:
Why it's important to pass null for bind as argument?


Without the null on bind:
var maxArray = Function.apply.bind(Math.max);


When maxArray([5,8,6]) is called, it is invoked like:

It's invoking the .apply on Math.max as:
Math.max.apply([5,8,6]);

The parameter [5,8,6] is assigned to this reference.

So both maxArray([5,8,6]) and Math.max.apply([5,8,6]) are basically invoking the Math.max without any parameters, and the [5,8,6] assigned to this reference.
Math.max();

We will see how .apply.bind works on our own object later and see its effect on this reference.

Both results to -Infinity:
dev@Developers-iMac % node
Welcome to Node.js v12.14.0.
Type ".help" for more information.
> var maxArray = Function.apply.bind(Math.max);
undefined
> maxArray([5,8,6]);
-Infinity
> Math.max.apply();
-Infinity
> Math.max();
-Infinity
>


We can invoke the maxArray properly like the following, but it somehow defeats the purpose of automagically passing the null on first parameter of Math.max.apply(null, [5,8,6]) expression
dev@Developers-iMac % node
Welcome to Node.js v12.14.0.
Type ".help" for more information.
> var maxArray = Function.apply.bind(Math.max);
undefined
> maxArray(null, [5,8,6]);
8


This is the result if we pass three parameters to Function.apply.bind:
dev@Developers-iMac % node
Welcome to Node.js v12.14.0.
Type ".help" for more information.
> var maxArray = Function.apply.bind(Math.max, null, [10, 40, 30]);
undefined
> maxArray()
40
> maxArray([50,60])
40
>

As for the .call, it is the same as .apply, as both them always require an object to be passed on their first parameter, which will then gets assigned to this reference, usually null is used as reference for this for utility functions. .apply is more flexible than .call, as .apply can grow its parameter, whereas .call's parameters are fixed at invocation.

Guess the output of Math.max.call(8,7,6)?

dev@Developers-iMac % node
Welcome to Node.js v12.14.0.
Type ".help" for more information.
> Math.max(8,7,6)
8
> Math.max.call(8,7,6)
7
> Math.max.apply(8,7,6)
Thrown:
TypeError: CreateListFromArrayLike called on non-object
> Math.max.apply([8,7,6])
-Infinity
> Math.max.apply([8,7,6],[2,4,3])
4
> Math.max.apply(null,[2,4,3])
4
> Math.max.call(null,8,7,6)
8

Here are other observations:




Why .apply.bind requires two parameters:




As we can see, if we don't pass an object reference to first parameter of .bind and we pass only one parameter, that passed parameter gets assigned to *this* reference, as seen on theThis property on line 7, likewise on this[0] of line 8. Then the actual parameter of the method something receives nothing (undefined).

Tuesday, May 5, 2020

flex is flexible, but it's not griddable

<div id="band">
    <div style="background-color: red">John</div>
    <div style="background-color: green">Paul</div>
    <div style="background-color: blue">George</div>
    <div style="background-color: yellow">Ringo</div>
</div>


<style>
    body {
        background-color: slategray;

        display: flex;
        justify-content: center;
        align-items: center;
    }

    #band {
        background-color: white;

        margin: 0 auto;
        border-style: solid;
        border-width: 2px;

        max-width: 740px;
        width: 100%;

        display: flex;
        /* justify-content: flex-start; */ /* flex-start is the default justify-content */
        flex-wrap: wrap;
    }

    #band > div {
        width: 200px;
        height: 200px;

        /* center all the names inside each box */
        display: flex;
        justify-content: center;
        align-items: center;
    }
</style>


Its output is:




If we change the justify-content to center:
display: flex;
justify-content: center;
flex-wrap: wrap;


Not only the first row's items are centered, the last item will be centered as well, which is not we want:



If we want to wrap extra items at leftmost and keep the whole grid of items at the center, we need to use grid instead of flex, and replace flex-wrap: wrap to repeat autofit and minmax. To wit:
display: grid;
justify-content: center;
grid-template-columns: repeat(auto-fit, minmax(200px, max-content));

The output is correct:



On the above display grid and grid-template-columns settings, if we change the justify-content to end:
display: grid;
justify-content: end; /* flex-end works as well, but we will just be confusing ourselves with that, we are using grid not flex */
grid-template-columns: repeat(auto-fit, minmax(200px, max-content));

The output is:


To contrast with flex-end of display: flex:
display: flex;
justify-content: flex-end;
flex-wrap: wrap;

The output is:


Same settings above, but with five items:



Let's use grid again with justify-content of end:
display: grid;
justify-content: end;
grid-template-columns: repeat(auto-fit, minmax(200px, max-content));

Its output:



This flex's flex-start:
display: flex;
justify-content: flex-start;
flex-wrap: wrap;

And grid's start:
display: grid;
justify-content: start;
grid-template-columns: repeat(auto-fit, minmax(200px, max-content));

Have same output:

Thursday, April 30, 2020

Don't use label display block for radio and checkbox

<div class="form-group">
  <label id="recommend-label" class="label">Would you recommend freeCodeCamp to a friend?</label>
  
    <label><input type="radio" name="recommend" value="definitely">Definitely the greatest tutorial on earth</label>
  
  
    <label><input type="radio" name="recommend" value="maybe">Maybe</label>
  
  
    <label><input type="radio" name="recommend" value="not-sure">Not sure</label>
</div>

<style>
.form-group label:not(.label) {
  display: block;
  background: lightgreen;
  margin: 2px;  
}
</style>


The problem with label display block approach is that it makes the trailing spaces after the label clickable too.



Demo: https://jsfiddle.net/qadxjghy/


To make the label's trailing spaces not clickable, enclosed the label in div. And then change the input's enclosing label display to inline-flex

<div class="form-group">
  <label id="recommend-label" class="label">Would you recommend freeCodeCamp to a friend?</label>
  
    <div>
      <label><input type="radio" name="recommend" value="definitely">Definitely the greatest tutorial on earth</label>
    </div>
  
    <div>
      <label><input type="radio" name="recommend" value="maybe">Maybe</label>
    </div>
  
    <div>
      <label><input type="radio" name="recommend" value="not-sure">Not sure</label>
    </div>

</div>

.form-group label:not(.label) {
  display: inline-flex;
  background: lightgreen;
  margin: 2px;  
}

Output:



Demo: https://jsfiddle.net/qadxjghy/1/


display: inline-block can be used as well:



inline-flex is better, so we can use align-items: baseline to align the baseline of text to the baseline of the radio/checkbox buttons:



display attribute can be removed as well, but you can't set things like margin:



Friday, March 20, 2020

Angular Service

Simpler way to make Angular service available to component, just add providedIn: 'root'

@Injectable({providedIn: 'root'})
export class ProductService {


Then on consuming component:
@Component({
    selector: 'pm-products',
    templateUrl: './product-list.component.html'
    // no need to add provider
})
export class ProductListComponent {
    constructor(private _productService: ProductService)
}


To make it more explicit:

@Injectable()
export class ProductService {

Then on consuming component:
@Component({
    selector: 'pm-products',
    templateUrl: './product-list.component.html'
    providers: [ProductService] // explicitly add the service here
})
export class ProductListComponent {
    constructor(private _productService: ProductService)
}