My Raw Notes for the Week of 11-29-2021
Tags: javascript, funcprogramming, scope, higherorderfunctions
Published: 2021-12-06 01:56:53

```
ENGLISH:

Scope:
- Gandolfo's quick verbal definition: "The area where a variable has access to some value."
- The MDN: https://developer.mozilla.org/en-US/docs/Glossary/Scope
- There are *global* variables and *local* variables.
  - A global variable is available throughout the entire codebase.
  It can be declared with the regular keywords ('let', 'const', 'var'),
  or with no keyword (but this is not a good idea),
  or by attaching it to the 'window' object, e.g., 'window.globalVar'.
  - local variables are inside of a function or code block.
- The keyword 'let' (unlike 'var') creates block scope.
  - A 'block' is basically anything inside of two curly braces.
  - If a variable is declared and defined inside of curly braces,
  but is declared with 'var' instead of 'let', it does NOT create block scope.

Higher-Order Functions and Callback Functions:
- These are the core concepts of 'functional programming'.
  - A higher-order function can...
  ...take a function as an input (argument)
      For example:
      element.addEventListener('change', () => {
        console.log('Our evidence is updated')
      })
  ...return a function as the output
      For example:
      const newClue = (name) => {
        const length = name.length
        return (weapon) => {
          let clue = length + weapon.length
          return !!(clue % 1)
        }
      }
  - A callback function is just a function we pass to a function.
      For example:
      const ifElse = (condition, isTrue, isFalse) => {
        return condition ? isTrue() : isFalse()
      }
      const logTrue = () => { console.log(true) } // true // FYI this function logs 'true' but returns 'undefined'
      const logFalse = () => { console.log(false) }
    console.log(ifElse(true, logTrue, logFalse)) // undefined
  - Simple example of passing arguments:
    - In ES5:
        var increment = function(n) { return n + 1 }
        var square = function(n) { return n * n }
        var doMathSoIDontHaveTo = function(n, func) { return func(n) }
        console.log(doMathSoIDontHaveTo(5, square)) // 25
        console.log(doMathSoIDontHaveTo(4, increment)) // 5
    - In ES6:
        const increment = n => n + 1
        const square = n => n * n 
        const doMathSoIDontHaveTo = (n, func) => func(n)
        console.log(doMathSoIDontHaveTo(5, square)) // 25
        console.log(doMathSoIDontHaveTo(4, increment)) // 5
        // But remember it is probably cleaner / more readable to include the curly braces and 'return' keyword,
        // like this '{ return n + 1 }', instead of just 'n + 1', for example.
```
```javascript
CODE:

// JS
// Scope exercise
let ACTUAL = null

let fn = function() {
  let innerCounter
  if (innerCounter) { // i.e., innerCounter === true, i.e., innerCounter has a truthy value, i.e., innerCounter has been defined
    innerCounter = innerCounter + 1
    ACTUAL = innerCounter
  } else {
    innerCounter = 10
    innerCounter = innerCounter + 1
    ACTUAL = innerCounter
  }
}

console.log(ACTUAL) // null
fn()
console.log(ACTUAL) // 11
fn()
console.log(ACTUAL) // 11
// The point is that the line just above does NOT log 12 and that the reason is:
// *** Every time a function is called, a brand new function scope is created. ***

//-------------------------------------------------------------------

// JS
// Scope exercise
let ACTUAL = null
let outerCounter = 10

let fn = function() {
  outerCounter = outerCounter + 1
  ACTUAL = outerCounter
}

console.log(ACTUAL) // null
fn()
console.log(ACTUAL) // 11
fn()
console.log(ACTUAL) // 12
// The point is that the line just above does NOT log 11 again and that the reason is:
// *** Between multiple calls of a function,
// that function retains access to variables in an outer scope,
// and the modification of those variables sustains between calls to the function. ***
// *** If you are confused about how exactly this works,
// remember that once the value of 'outerCounter' in the function is updated,
// 'outerCounter' in the outer scope is updated. ***

//-------------------------------------------------------------------

// JS
// Passing two functions ('fn1' and 'fn2') to a function ('ifElse')
// and calling one of the two functions ('fn1' in the below example)
// with an argument (the string 'Hey')
const ifElse = (condition, isTrue, isFalse, greeting) => {
  return condition ? isTrue(greeting) : isFalse(greeting)
}

const fn1 = (x) => { console.log(`${x}, it IS true!`) } // Hey, it IS true!
const fn2 = (x) => { console.log(`${x}, it is NOT true!`) }

console.log(ifElse(true, fn1, fn2, 'Hey')) // undefined

//-------------------------------------------------------------------

// JS
// The 'reduce' Method
const arr = [1, 2, 3, 4, 5]
const sum = arr.reduce((acc, curVal, curIndex, array) => {
// fCC paraphrase: The accumulator (represented by the 'acc' parameter in the above line)
// gets assigned the return value of the callback function from the previous iteration. I.e., 'acc'
// could also be 'previousValue', because it holds what the previous iteration returned.
  console.log(curIndex)
  // to see result of above line, view in console
  console.log(array)
  // to see result of above line, view in console
  return acc + curVal  
}, 0)
// fCC paraphrase: 'reduce' has a second parameter (the '0' in the above line)
// which takes an initial value for the accumulator. I.e., the second parameter of 'reduce' is the
// initial value (often set to '0'), which just determines what the accumulator used in the first
// iteration is. If this second parameter is not used, then the first iteration is skipped and the
// second iteration gets passed the first element of the array as the accumulator.
console.log(sum) // 15

//-------------------------------------------------------------------

// JS
// Using the 'map' method
const arr = [1, 2, 3, 4, 5]
const newArr = arr.map(num => {
  return num + 2 
}, 0)
console.log(newArr) // [3, 4, 5, 6, 7]

// Using the 'reduce' method to do the same thing
const arr = [1, 2, 3, 4, 5]
const newArr = arr.reduce((acc, curVal) => {
  acc.push(curVal + 2)
  return acc
}, [])
console.log(newArr) // [3, 4, 5, 6, 7]


// Using the 'filter' method
const arr = [1, 2, 3, 4, 5]
const newArr = arr.filter(num => {
  return num > 2 
})
console.log(newArr) // [3, 4, 5]

// Using the 'reduce' method to do the same thing
const arr = [1, 2, 3, 4, 5]
const newArr = arr.reduce((acc, curVal) => {
  if (curVal > 2) {
    acc.push(curVal)
  }
  return acc
}, [])
console.log(newArr) // [3, 4, 5]

//-------------------------------------------------------------------

// JS
// Example from fCC article: https://www.freecodecamp.org/news/reduce-f47a7da511a9/
const euros = [29.76, 41.85, 46.5]

const average = euros.reduce((total, amount, index, array) => {
  total += amount
  if (index === array.length - 1) {
    return total/array.length
  } else { 
    return total
  }
}) // note that not having the second parameter (often '0') here or having it here makes no difference in this example

console.log(average) // 39.37

//-------------------------------------------------------------------

// JS
// Example from fCC article: https://www.freecodecamp.org/news/reduce-f47a7da511a9/
// "Creating a Tally with the Reduce Method In JavaScript​
// Use it when: You have a collection of items and you want to know how many of each
// item are in the collection."

// less concise version
const fruitBasket = ['banana', 'cherry', 'orange', 'apple', 'cherry', 'orange', 'apple', 'banana', 'cherry', 'orange', 'fig']
const count = fruitBasket.reduce((tally, fruit) => {
  if (!tally[fruit]) { // i.e., tally[fruit] === undefined
    tally[fruit] = 1
  } else {
    tally[fruit]++
  }
  return tally
}, {})
console.log(count) // {banana: 2, cherry: 3, orange: 3, apple: 2, fig: 1}

// more concise version
const fruitBasket = ['banana', 'cherry', 'orange', 'apple', 'cherry', 'orange', 'apple', 'banana', 'cherry', 'orange', 'fig']
const count = fruitBasket.reduce((tally, fruit) => {
  tally[fruit] = (tally[fruit] || 0) + 1 
  return tally
}, {})
console.log(count) // {banana: 2, cherry: 3, orange: 3, apple: 2, fig: 1}

//-------------------------------------------------------------------

// JS
// Example from fCC
const users = [
  { name: 'John', age: 34 },
  { name: 'Amy', age: 20 },
  { name: 'camperCat', age: 10 }
]

const usersObj = users.reduce((obj, user) => {
  obj[user.name] = user.age
  return obj
}, {})

console.log(usersObj) // {John: 34, Amy: 20, camperCat: 10}

//-------------------------------------------------------------------

// JS
// fCC challenge: Use 'reduce' to find the average IMDB rating of the movies directed by Christopher Nolan.
const watchList = [
  {
    "Title": "Inception",
    "Year": "2010",
    "Rated": "PG-13",
    "Released": "16 Jul 2010",
    "Runtime": "148 min",
    "Genre": "Action, Adventure, Crime",
    "Director": "Christopher Nolan",
    "Writer": "Christopher Nolan",
    "Actors": "Leonardo DiCaprio, Joseph Gordon-Levitt, Elliot Page, Tom Hardy",
    "Plot": "A thief, who steals corporate secrets through use of dream-sharing technology, is given the inverse task of planting an idea into the mind of a CEO.",
    "Language": "English, Japanese, French",
    "Country": "USA, UK",
    "Awards": "Won 4 Oscars. Another 143 wins & 198 nominations.",
    "Poster": "http://ia.media-imdb.com/images/M/MV5BMjAxMzY3NjcxNF5BMl5BanBnXkFtZTcwNTI5OTM0Mw@@._V1_SX300.jpg",
    "Metascore": "74",
    "imdbRating": "8.8",
    "imdbVotes": "1,446,708",
    "imdbID": "tt1375666",
    "Type": "movie",
    "Response": "True"
  },
  {
    "Title": "Interstellar",
    "Year": "2014",
    "Rated": "PG-13",
    "Released": "07 Nov 2014",
    "Runtime": "169 min",
    "Genre": "Adventure, Drama, Sci-Fi",
    "Director": "Christopher Nolan",
    "Writer": "Jonathan Nolan, Christopher Nolan",
    "Actors": "Ellen Burstyn, Matthew McConaughey, Mackenzie Foy, John Lithgow",
    "Plot": "A team of explorers travel through a wormhole in space in an attempt to ensure humanity's survival.",
    "Language": "English",
    "Country": "USA, UK",
    "Awards": "Won 1 Oscar. Another 39 wins & 132 nominations.",
    "Poster": "http://ia.media-imdb.com/images/M/MV5BMjIxNTU4MzY4MF5BMl5BanBnXkFtZTgwMzM4ODI3MjE@._V1_SX300.jpg",
    "Metascore": "74",
    "imdbRating": "8.6",
    "imdbVotes": "910,366",
    "imdbID": "tt0816692",
    "Type": "movie",
    "Response": "True"
  },
  {
    "Title": "The Dark Knight",
    "Year": "2008",
    "Rated": "PG-13",
    "Released": "18 Jul 2008",
    "Runtime": "152 min",
    "Genre": "Action, Adventure, Crime",
    "Director": "Christopher Nolan",
    "Writer": "Jonathan Nolan (screenplay), Christopher Nolan (screenplay), Christopher Nolan (story), David S. Goyer (story), Bob Kane (characters)",
    "Actors": "Christian Bale, Heath Ledger, Aaron Eckhart, Michael Caine",
    "Plot": "When the menace known as the Joker wreaks havoc and chaos on the people of Gotham, the caped crusader must come to terms with one of the greatest psychological tests of his ability to fight injustice.",
    "Language": "English, Mandarin",
    "Country": "USA, UK",
    "Awards": "Won 2 Oscars. Another 146 wins & 142 nominations.",
    "Poster": "http://ia.media-imdb.com/images/M/MV5BMTMxNTMwODM0NF5BMl5BanBnXkFtZTcwODAyMTk2Mw@@._V1_SX300.jpg",
    "Metascore": "82",
    "imdbRating": "9.0",
    "imdbVotes": "1,652,832",
    "imdbID": "tt0468569",
    "Type": "movie",
    "Response": "True"
  },
  {
    "Title": "Batman Begins",
    "Year": "2005",
    "Rated": "PG-13",
    "Released": "15 Jun 2005",
    "Runtime": "140 min",
    "Genre": "Action, Adventure",
    "Director": "Christopher Nolan",
    "Writer": "Bob Kane (characters), David S. Goyer (story), Christopher Nolan (screenplay), David S. Goyer (screenplay)",
    "Actors": "Christian Bale, Michael Caine, Liam Neeson, Katie Holmes",
    "Plot": "After training with his mentor, Batman begins his fight to free crime-ridden Gotham City from the corruption that Scarecrow and the League of Shadows have cast upon it.",
    "Language": "English, Urdu, Mandarin",
    "Country": "USA, UK",
    "Awards": "Nominated for 1 Oscar. Another 15 wins & 66 nominations.",
    "Poster": "http://ia.media-imdb.com/images/M/MV5BNTM3OTc0MzM2OV5BMl5BanBnXkFtZTYwNzUwMTI3._V1_SX300.jpg",
    "Metascore": "70",
    "imdbRating": "8.3",
    "imdbVotes": "972,584",
    "imdbID": "tt0372784",
    "Type": "movie",
    "Response": "True"
  },
  {
    "Title": "Avatar",
    "Year": "2009",
    "Rated": "PG-13",
    "Released": "18 Dec 2009",
    "Runtime": "162 min",
    "Genre": "Action, Adventure, Fantasy",
    "Director": "James Cameron",
    "Writer": "James Cameron",
    "Actors": "Sam Worthington, Zoe Saldana, Sigourney Weaver, Stephen Lang",
    "Plot": "A paraplegic marine dispatched to the moon Pandora on a unique mission becomes torn between following his orders and protecting the world he feels is his home.",
    "Language": "English, Spanish",
    "Country": "USA, UK",
    "Awards": "Won 3 Oscars. Another 80 wins & 121 nominations.",
    "Poster": "http://ia.media-imdb.com/images/M/MV5BMTYwOTEwNjAzMl5BMl5BanBnXkFtZTcwODc5MTUwMw@@._V1_SX300.jpg",
    "Metascore": "83",
    "imdbRating": "7.9",
    "imdbVotes": "876,575",
    "imdbID": "tt0499549",
    "Type": "movie",
    "Response": "True"
  }
]

function getRating(theWatchList) {
  const avgRating = theWatchList
    .filter(movieObj => { return movieObj.Director === 'Christopher Nolan' })
    .map(movieObj => { return +movieObj.imdbRating })
    .reduce((ratingsSum, rating) => { return ratingsSum += rating }, 0) /
    theWatchList.filter(movieObj => { return movieObj.Director === 'Christopher Nolan' }).length
  return avgRating
}

console.log(getRating(watchList)) // 8.675

//-------------------------------------------------------------------

// JS
// fCC challenge: The function should return a new array containing the squares of only the
// positive integers (decimal numbers are not integers) when an array of real numbers is
// passed to it. An example of an array of real numbers is [-3, 4.8, 5, 3, -3.2].
const squareList = arr => {
  const newArr = arr
    .filter(num => { return num > 0 && Number.isInteger(num) })
    .map(num => { return num * num })
  return newArr
}
console.log(squareList([-3, 4.8, 5, 3, -3.2])) // [25, 9]

// the above could also be shortened to this
const squareList = arr => {
  return arr
    .filter(num => { return num > 0 && Number.isInteger(num) })
    .map(num => { return num * num })
}
console.log(squareList([-3, 4.8, 5, 3, -3.2])) // [25, 9]

// doing with 'reduce' instead of both 'filter' and 'map'
const squareList = arr => {
  return arr.reduce((sqrIntegers, num) => {
    return Number.isInteger(num) && num > 0 ? sqrIntegers.concat(num * num) : sqrIntegers
  }, [])
}
console.log(squareList([-3, 4.8, 5, 3, -3.2])) // [25, 9]

//-------------------------------------------------------------------

// JS
// To fully understand the code below, really walk through it.
// CREATING the 'reduce' function
const reduce = function(list, callback, initialValue) {
  let accumulator = initialValue
  for (let i = 0; i < list.length; i++) {
    // The reason for the below 'if' condition and statement is in case there is no initialValue
    // passed as an argument to the 'reduce' function call.
    if (i === 0 && accumulator === undefined) {
    // Remember that, in the above line, 'i === 0' means the INDEX is 0, NOT that the VALUE at i is 0.
      accumulator = list[0]
    } else {
      accumulator = callback(accumulator, list[i])
      // Note that in the callback function call just above we're leaving out 'current index' and
      // 'array' parameters just because we're only doing the basic functionality here.
    }
  }  
  return accumulator
}

// USING the 'reduce' function created above
console.log(reduce([1, 2, 3], (acc, curVal) => {return acc += curVal}, 0)) // 6
```