/*
  Resources:
  - https://javascript.plainenglish.io/10-reduce-techniques-worth-mastering-97dd9b9a9e90
  - https://andepaulj.medium.com/javascript-reduce-79aab078da23
  - https://javascript.plainenglish.io/5-use-cases-for-reduce-in-javascript-61ed243b8fef
  - https://code.tutsplus.com/5-real-life-uses-for-the-javascript-reduce-method--cms-39096a
*/

const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const count = fruits.reduce((accumulator, currentValue) => {
  accumulator[currentValue] = (accumulator[currentValue] || 0) + 1;
  return accumulator;
}, {});
console.log(count); // Output: { apple: 3, banana: 2, orange: 1 }


/* Counting Occurrences */
const fruits = [ 'Banana', 'Orange', 'Apple', 'Orange', 'Pear', 'Banana']
const occurrences = fruits.reduce((acc, currFruit) => {
    return {...acc, [currFruit]: (acc[currFruit] || 0) + 1 }
}, {})
console.log(occurrences)
/* 
{ 
Apple: 1, 
Banana: 2, 
Orange: 2, 
Pear: 1 
} 
*/

// OR
const reduceOccurrences = manyNumbers.reduce((acc, cur) => {
  acc[cur] ? acc[cur]++ : acc[cur] = 1
  return acc
}, {})

// OR via Map()
const count = (array) =>
    array.reduce(
        (acc, it) => (acc.set(it, (acc.get(it) || 0) + 1), acc),
        new Map()
    );
const array = [1, 2, 1, 2, -1, 0, "0", 10, "10"];
console.log(count(array));




/* Flatten a List of Arrays */
const arrOfArrs = [
    ['aaron', 'ake', 'anna', 'aje'],
    ['becky', 'ben', 'bright'],
    ['cara', 'chris'],
    ['david', 'daniel', 'danielle', 'djenue'],
]
const flattened = arrOfArrs.reduce((acc, array) => acc.concat(array))
console.log(flattened)
// ["aaron", "ake", "anna", "aje", "becky", "ben", "bright", "cara", "chris", "david", "daniel", // "danielle", "djenue"]

// OR
const array = [1, [2, [3, [4, [5]]]]];
const flat = (arrayNumbers) =>
    arrayNumbers.reduce(
        (acc, it) => acc.concat(Array.isArray(it) ? flat(it) : it),
        []
    );
const flatArray = flat(array);
console.log(flatArray); // [ 1, 2, 3, 4, 5 ]



/* Getting Max and Min Values */
const students = [
    { name: "Kingsley", score: 70 },
    { name: "Jack", score: 80 },
    { name: "Joe", score: 63 },
    { name: "Beth", score: 75 },
    { name: "Kareem", score: 59 },
    { name: "Sarah", score: 93 }
]
const max = students.reduce((acc, student) => {
    if(acc === null || student.score > acc) 
        return student.score
    return acc
}, null)
console.log(max) // Prints 93

// OR
const getMax = (array) => array.reduce((max, num) => (max > num ? max : num));
const getMin = (array) => array.reduce((max, num) => (max < num ? max : num));



/* Converting Between Types */ 
const studentsArray = [
    { name: "Kingsley", score: 70, position: "1st" },
    { name: "Jack", score: 80, position: "2nd" },
    { name: "Joe", score: 63, position: "3rd" },
    { name: "Beth", score: 75, position: "4rd" },
    { name: "Kareem", score: 59, position: "5th" },
    { name: "Sarah", score: 93, position: "6th" }
]
const studentObj = studentsArray.reduce((acc, student) => {
	return {...acc, [student.name]: student.position}
}, {})
console.log(studentObj)
/* 
{ 
Beth: "4rd", 
Jack: "2nd", 
Joe: "3rd", 
Kareem: "5th", 
Kingsley: "1st", 
Sarah: "6th" 
} 
*/



/* Grouping objects by a property */
const result = [
    {subject: 'Physics', marks: 41},
    {subject: 'Chemistry', marks: 59},
    {subject: 'Pure Maths', marks: 36},
    {subject: 'Applied Maths', marks: 90},
    {subject: 'English', marks: 64},
];

let initialValue = {
    pass: [], 
    fail: []
}

const groupedResult = result.reduce((accumulator, current) => {
    (current.marks>=50) ? accumulator.pass.push(current) : accumulator.fail.push(current);
    return accumulator;
}, initialValue);

console.log(groupedResult);
/*
{
 pass: [
  { subject: ‘Chemistry’, marks: 59 },
  { subject: ‘Applied Maths’, marks: 90 },
  { subject: ‘English’, marks: 64 }
 ],
 fail: [
  { subject: ‘Physics’, marks: 41 },
  { subject: ‘Pure Maths’, marks: 36 }
 ]
}
*/




/* Reduce is a Higher-Order Function*/
const plusTwoReducer = (acc, cur) => {
  acc.push(cur + 2)
  return acc
}

const plusSixReducer = (acc, cur) => {
  acc.push(cur + 6)
  return acc
}

numbers.reduce(plusTwoReducer, [])
numbers.reduce(plusSixReducer, [])