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