Advanced Array Methods
在此附上 Elie Schoppik 講師的 slides
陣列迭代
在 JavaScript 中,callback 是一種常見的函式,這篇文章想要對 JS 中幾種常見迭代 (iterate)陣列的方式做個初步的介紹。
主要函式有以下幾種,使用它們可以使程式碼變得更加整潔、清楚!
forEach
map
filter
some
every
reduce
forEach
執行步驟:
- 迭代整個陣列
- 對每一個值(value)執行一次 callback
- 回傳
undefined
運行機制:
function forEach(arr, callback) { for (let i = 0; i < arr.length; ++i) { callback(arr[i], i, arr); } }
以下讓我們透過例子來深入了解 forEach
的奧妙!
Write a function called
doubleValues
which accepts an array and returns a new array with all the values in the array passed to the function doubled.Examples:
doubleValues([1, 2, 3]); // [2, 4, 6] doubleValues([5, 1, 2, 3, 10]); // [10, 2, 4, 6, 20]
Solution:
function doubleValues(arr) { let newArr = []; arr.forEach((val) => newArr.push(val * 2)); return newArr; }
Write a function called
onlyEvenValues
which accepts an array and returns a new array with only the even values in the array passed to the function.Examples:
onlyEvenValues([1, 2, 3]); // [2] onlyEvenValues([5, 1, 2, 3, 10]); // [2, 10]
Solution:
function onlyEvenValues(arr) { let newArr = []; arr.forEach((val) => { if (val % 2 === 0) { newArr.push(val); } }); return newArr; }
Write a function called
showFirstAndLast
which accepts an array of strings and returns a new array with only the first and last character of each string.Examples:
showFirstAndLast(["colt", "matt", "tim", "udemy"]); // ['ct', 'mt', 'tm', 'uy'] showFirstAndLast(["hi", "goodbye", "smile"]); // ['hi', 'ge', 'se']
Solution:
function showFirstAndLast(arr) { let newArr = []; arr.forEach((val) => newArr.push(val[0] + val[val.length - 1])); return newArr; }
Write a function called
addKeyAndValue
which accepts an array of objects, a key, and a value and returns the array passed to the function with the new key and value added for each object.Examples:
addKeyAndValue( [{ name: "Elie" }, { name: "Colt" }], "title", "instructor" ); // [{ name: 'Elie', title: 'instructor' }, // { name: 'Colt', title: 'instructor' }]
Solution:
function addKeyAndValue(arr, key, value) { arr.forEach((val) => (val[key] = value)); return arr; }
Write a function called
vowelCount
which accepts a string and returns an object with the keys as the vowel and the values as the number of times the vowel appears in the string. This function should be case insensitive so a lowercase letter and uppercase letter should count.Examples:
vowelCount("Elie"); // { e: 2, i: 1 } vowelCount("Colt"); // { o: 1 } vowelCount("hmmm"); // {}; vowelCount("I Am awesome and so are you"); // { i: 1, a: 4, e: 3, o: 3, u: 1 }
Solution:
function vowelCount(str) { let obj = {}; let vowels = "aeiou"; str .toLowerCase() .split("") .forEach((val) => { if (vowels.indexOf(val) !== -1) { if (obj[val]) { ++obj[val]; } else { obj[val] = 1; } } }); return obj; }
map
執行步驟:
- 創建一個新的陣列(newArr)
- 迭代原本的陣列(arr)
- 對每一個值(value)執行一次 callback
- 將 callback 函式回傳的結果加入步驟一所建立的陣列(newArr)
- 回傳 newArr
運行機制:
function map(arr, callback) { let newArr; for (let i = 0; i < arr.length; ++i) { newArr.push(callback(arr[i], i, arr)); } return newArr; }
我們可以發現,透過 map,我們不需要再自行建立陣列,這會使的程式碼變得非常潔簡,由 其是搭配 1-line arrow function 時!
一樣透過以下幾個例子,讓大家細細品嘗 map
比 forEach
強大的地方!
Write a function called
doubleValues
which accepts an array and returns a new array with all the values in the array passed to the function doubled.Examples:
doubleValues([1, 2, 3]); // [2, 4, 6] doubleValues([1, -2, -3]); // [2, -4, -6]
Solution:
function doubleValues(arr) { return arr.map((val) => val * 2); }
Write a function called
valTimesIndex
which accepts an array and returns a new array with each value multiplied by the index it is currently at in the array.Examples:
valTimesIndex([1, 2, 3]); // [0, 2, 6] valTimesIndex([1, -2, -3]); // [0, -2, -6]
Solution:
function valTimesIndex(arr) { return arr.map((val, i) => val * i); }
Write a function called
extractKey
which accepts an array of objects and some key and returns a new array with the value of that key in each object.Examples:
extractKey([{ name: "Elie" }, { name: "Colt" }], "name"); // ['Elie', 'Colt']
Solution:
function extractKey(arr, key) { return arr.map((val) => val[key]); }
Write a function called
extractFullName
which accepts an array of objects and returns a new array with the value of the key with a name of ‘first’ and the value of a key with the name of ’last’ in each object, concatenated together with a space.Examples:
extractFullName([ { first: "Elie", last: "Schoppik" }, { first: "Colt", last: "Steele" }, ]); // ['Elie Schoppik', 'Colt Steele']
Solution:
function extractFullName(arr) { return arr.map((val) => `${val.first} ${val.last}`); }
filter
執行步驟:
- 創建一個新的陣列(newArr)
- 迭代原本的陣列(arr)
- 對每一個值(value)執行一次 callback
- 若 callback 回傳
true
,將 value 加入步驟一所建立的陣列(newArr) - 若 callback 回傳
false
,continue
- 若 callback 回傳
- 回傳 newArr
運行機制:
function filter(arr, callback) { let newArr; for (let i = 0; i < arr.length; ++i) { if (callback(arr[i], i, arr)) { newArr.push(arr[i]); } } return newArr; }
例子:
Write a function called
filterByValue
which accepts an array of objects and a key and returns a new array with all the objects that contain that key.Examples:
filterByValue( [ { first: "Elie", last: "Schoppik" }, { first: "Colt", last: "Steele", isCatOwner: true }, ], "isCatOwner" ); // [{ first: 'Colt', last: 'Steele', isCatOwner: true }]
Solution:
function filterByValue(arr, key) { return arr.filter((val) => val[key]); }
Write a function called
find
which accepts an array and a value and returns the first element in the array that has the same value as the second parameter or undefined if the value is not found in the array.Examples:
find([1, 2, 3, 4, 5], 3); // 3 find([1, 2, 3, 4, 5], 10); // undefined
Solution:
function find(arr, searchValue) { return arr.filter((val) => val === searchValue)[0]; }
Write a function called
findInObj
which accepts an array of objects, a key, and some value to search for and returns the first found value in the array.Examples:
findInObj( [ { first: "Elie", last: "Schoppik" }, { first: "Tim", last: "Garcia", isCatOwner: true }, { first: "Colt", last: "Steele", isCatOwner: true }, ], "isCatOwner", true ); // { first: 'Tim', last: 'Garcia', isCatOwner: true}
Solution:
function findInObj(arr, key, searchValue) { return arr.filter((val) => val[key] === searchValue)[0]; }
Write a function called
removeVowels
which accepts a string and returns a new string with all of the vowels (both uppercased and lowercased) removed. Every character in the new string should be lowercased.Examples:
removeVowels("Elie"); // 'l' removeVowels("TIM"); // 'tm' removeVowels("ZZZZZZ"); // 'zzzzzz'
Solution:
function removeVowels(str) { let vowels = "aeiou"; return str .toLowerCase() .split("") .filter((val) => vowels.indexOf(val) === -1) .join(""); }
Write a function called
doubleOddNumbers
which accepts an array and returns a new array with all of the odd numbers doubled (HINT - you can use map and fitler to double and then filter the odd numbers).Examples:
doubleOddNumbers([1, 2, 3, 4, 5]); // [2, 6, 10] doubleOddNumbers([4, 4, 4, 4, 4]); // []
Solution:
function doubleOddNumbers(arr) { return arr.filter((val) => val % 2 === 1).map((val) => val * 2); }
some
執行步驟:
- 迭代原本的陣列(arr)
- 對每一個值(value)執行一次 callback
- 若至少有一個 callback 回傳
true
,則回傳true
- 否則,回傳
false
- 若至少有一個 callback 回傳
運行機制:
function some(arr, callback) { for (let i = 0; i < arr.length; ++i) { if (callback(arr[i], i, arr)) { return true; } } return false; }
every
執行步驟:
- 迭代原本的陣列(arr)
- 對每一個值(value)執行一次 callback
- 若至少有一個 callback 回傳
false
,則回傳fale
- 否則,回傳
true
- 若至少有一個 callback 回傳
運行機制:
function every(arr, callback) { for (let i = 0; i < arr.length; ++i) { if (callback(arr[i], i, arr) === false) { return false; } } return true; }
以下我們一樣透過例子來看如何實作 some
和 every
:
Write a function called
hasOddNumber
which accepts an array and returnstrue
if the array contains at least one odd number, otherwise it returnsfalse
.Examples:
hasOddNumber([1, 2, 2, 2, 2, 2, 4]); // true hasOddNumber([2, 2, 2, 2, 2, 4]); // false
Solution:
function hasOddNumber(arr) { return arr.some((val) => val % 2 === 1); }
Write a function called
hasAZero
which accepts a number and returnstrue
if that number contains at least one zero. Otherwise, the function should returnfalse
.Examples:
hasAZero(3332123213101232321); // true hasAZero(1212121); // false
Solution:
function hasAZero(num) { return num .toString() .split("") .some((val) => val === "0"); }
Write a function called
hasOnlyOddNumbers
which accepts an array and returnstrue
if every single number in the array is odd. If any of the values in the array are not odd, the function should returnfalse
.Examples:
hasOnlyOddNumbers([1, 3, 5, 7]); // false hasOnlyOddNumbers([1, 2, 3, 5, 7]); // false
Solution:
function hasOnlyOddNumbers(arr) { return arr.every((val) => val % 2 === 1); }
Write a function called
hasNoDuplicates
which accepts an array and returnstrue
if there are no duplicate values (more than one element in the array that has the same value as another). If there are any duplicates, the function should returnfalse
.Examples:
hasNoDuplicates([1, 2, 3, 1]); // false hasNoDuplicates([1, 2, 3]); // true
Solution:
function hasNoDuplicates(arr) { return arr.every((val) => arr.indexOf(val) === arr.lastIndexOf(val)); }
Write a function called
hasCertainKey
which accepts an array of objects and a key, and returnstrue
if every single object in the array contains that key. Otherwise it should returnfalse
.Examples:
const arr = [ { title: "Instructor", first: "Elie", last: "Schoppik" }, { title: "Instructor", first: "Tim", last: "Garcia", isCatOwner: true }, { title: "Instructor", first: "Matt", last: "Lane" }, { title: "Instructor", first: "Colt", last: "Steele", isCatOwner: true }, ]; hasCertainKey(arr, "first"); // true hasCertainKey(arr, "isCatOwner"); // false
Solution:
function hasCertainKey(arr, key) { return arr.every((val) => key in val); }
Write a function called
hasCertainValue
which accepts an array of objects and a key, and a value, and returnstrue
if every single object in the array contains that value for the specific key. Otherwise it should returnfalse
.Examples:
let arr = [ { title: "Instructor", first: "Elie", last: "Schoppik" }, { title: "Instructor", first: "Tim", last: "Garcia", isCatOwner: true }, { title: "Instructor", first: "Matt", last: "Lane" }, { title: "Instructor", first: "Colt", last: "Steele", isCatOwner: true }, ]; hasCertainValue(arr, "title", "Instructor"); // true hasCertainValue(arr, "first", "Elie"); // false
Solution:
function hasCertainValue(arr, key, searchValue) { return arr.every((val) => val[key] === searchValue); }
reduce
執行步驟
- 參數:
- 1.(必要)callback 函式
- 1.(可有可無)第二個參數(optional parameter)
- 迭代原本的陣列(
arr
) - 對每一個值(value)執行一次 callback
- 若有提供 optional parameter 的話,callback 的第一個參數便是該 optional parameter
- 否則,則是
arr[0]
,也就是第 0 個 value - 通常我們稱呼 callback 的第一個參數為 ‘accumulator’,常用縮寫為 acc
- 每次從 callback 回傳的值,變成為新的 accumulator!
- 參數:
本文最後介紹的 reduce
和前面任何一個函式執行方式有著很大的差異,需要透過不停的
練習才能熟能生巧,因為難度較高,讓我們先看幾個範例:
無 optional parameter,最後回傳結果為
6
。const arr = [1, 2, 3]; arr.reduce((acc, next) => acc + next);
accumulator nextValue returned value 1 2 3 3 3 6 有 optional parameter,最後回傳結果為
16
。const arr = [1, 2, 3]; arr.reduce((acc, next) => acc + next, 10);
accumulator nextValue returned value 10 1 11 11 2 13 13 3 16
從範例中可以發現,透過 reduce
來加總整個陣列是非常方便的。
以下再多看幾題練習題吧!
Write a function called
extractValue
which accepts an array of objects and a key and returns a new array with the value of each object at the key.Examples:
const arr = [{ name: "Elie" }, { name: "Colt" }]; extractValue(arr, "name"); // ['Elie', 'Colt']
Solution:
function extractValue(arr, key) { return arr.reduce((acc, next) => { acc.push(next[key]); return acc; }, []); }
Write a function called
vowelCount
which accepts a string and returns an object with the keys as the vowel and the values as the number of times the vowel appears in the string. This function should be case insensitive so a lowercase letter and uppercase letter should countExamples:
vowelCount("Elie"); // { e: 2, i: 1 } vowelCount("Colt"); // { o: 1 } vowelCount("hmmm"); // {}; vowelCount("I Am awesome and so are you"); // { i: 1, a: 4, e: 3, o: 3, u: 1 }
Solution:
function vowelCount(str) { const vowels = "aeiou"; return str .toLowerCase() .split("") .reduce((acc, next) => { if (vowels.indexOf(next) !== -1) { if (acc[next]) { ++acc[next]; } else { acc[next] = 1; } } return acc; }, {}); }
Write a function called
addKeyAndValue
which accepts an array of objects, a key, and a value and returns the array passed to the function with the new key and value added for each object.Examples:
addKeyAndValue( [{ name: "Elie" }, { name: "Colt" }], "title", "instructor" ); // [{ name: 'Elie', title: 'instructor' }, // { name: 'Colt', title: 'instructor' }]
Solution:
function addKeyAndValue(arr, key, value) { return arr.reduce((acc, next, i) => { acc[i][key] = value; return acc; }, arr); }
Write a function called
partition
which accepts an array and a callback and returns an array with two arrays inside of it. The partition function should run the callback function on each value in the array and if the result of the callback function at that specific value is true, the value should be placed in the first subarray. If the result of the callback function at that specific value is false, the value should be placed in the second subarray.Examples:
function isEven(val) { return val % 2 === 0; } const arr = [1, 2, 3, 4, 5, 6, 7, 8]; partition(arr, isEven); // [[2, 4, 6, 8], [1, 3, 5, 7]]; function isLongerThanThreeCharacters(val) { return val.length > 3; } const names = ["Elie", "Colt", "Tim", "Matt"]; partition(names, isLongerThanThreeCharacters); // [['Elie', 'Colt', 'Matt'], ['Tim']]
Solution:
function partition(arr, callback) { return arr.reduce( (acc, next, i) => { if (callback(next)) { acc[0].push(next); } else { acc[1].push(next); } return acc; }, [[], []] ); }
以上就是幾個重要常見的 Array Methods!