Array Transformations Deep Dive
This lesson explores advanced patterns for array transformation and data manipulation.
Understanding reduce()
reduce() is the most powerful array method - map and filter can be implemented with it:
javascript1// reduce signature2array.reduce((accumulator, currentValue, index, array) => {3 // return new accumulator4}, initialValue);56// The accumulator "accumulates" results through each iteration
Building Common Operations with reduce
javascript1// Implement map with reduce2function map(arr, fn) {3 return arr.reduce((acc, item, i) => {4 acc.push(fn(item, i));5 return acc;6 }, []);7}89// Implement filter with reduce10function filter(arr, predicate) {11 return arr.reduce((acc, item) => {12 if (predicate(item)) acc.push(item);13 return acc;14 }, []);15}1617// Implement find with reduce18function find(arr, predicate) {19 return arr.reduce((found, item) => {20 if (found !== undefined) return found;21 return predicate(item) ? item : undefined;22 }, undefined);23}
Advanced reduce Patterns
Object Transformation
javascript1// Array to object (by key)2const users = [3 { id: 1, name: "Alice" },4 { id: 2, name: "Bob" }5];67const usersById = users.reduce((acc, user) => {8 acc[user.id] = user;9 return acc;10}, {});11// { 1: { id: 1, name: "Alice" }, 2: { id: 2, name: "Bob" } }1213// Alternative using Object.fromEntries14const usersById2 = Object.fromEntries(15 users.map(u => [u.id, u])16);
Grouping and Categorizing
javascript1const transactions = [2 { type: "income", amount: 1000 },3 { type: "expense", amount: 500 },4 { type: "income", amount: 2000 },5 { type: "expense", amount: 300 }6];78// Group by type9const grouped = transactions.reduce((acc, t) => {10 acc[t.type] = acc[t.type] || [];11 acc[t.type].push(t);12 return acc;13}, {});1415// Sum by type16const totals = transactions.reduce((acc, t) => {17 acc[t.type] = (acc[t.type] || 0) + t.amount;18 return acc;19}, {});20// { income: 3000, expense: 800 }2122// ES2024: Object.groupBy (coming soon to all browsers)23const grouped2 = Object.groupBy(transactions, t => t.type);
Flattening with reduce
javascript1// Flatten one level2const flatten = arr => arr.reduce(3 (flat, item) => flat.concat(item),4 []5);67// Deep flatten8const deepFlatten = arr => arr.reduce(9 (flat, item) => flat.concat(10 Array.isArray(item) ? deepFlatten(item) : item11 ),12 []13);1415deepFlatten([1, [2, [3, [4]]]]); // [1, 2, 3, 4]
Running Calculations
javascript1// Running total2const amounts = [100, 50, 200, 75];3const runningTotal = amounts.reduce((acc, amount) => {4 const lastTotal = acc.length ? acc[acc.length - 1] : 0;5 acc.push(lastTotal + amount);6 return acc;7}, []);8// [100, 150, 350, 425]910// Average with reduce11const average = amounts.reduce((acc, val, i, arr) => {12 acc += val;13 if (i === arr.length - 1) return acc / arr.length;14 return acc;15}, 0);
Immutable Array Operations
Avoid mutating original arrays:
javascript1// Immutable push2const addItem = (arr, item) => [...arr, item];34// Immutable unshift5const prependItem = (arr, item) => [item, ...arr];67// Immutable remove by index8const removeAt = (arr, index) => [9 ...arr.slice(0, index),10 ...arr.slice(index + 1)11];1213// Immutable update by index14const updateAt = (arr, index, newValue) => [15 ...arr.slice(0, index),16 newValue,17 ...arr.slice(index + 1)18];1920// Immutable insert at index21const insertAt = (arr, index, item) => [22 ...arr.slice(0, index),23 item,24 ...arr.slice(index)25];2627// Immutable sort28const sortedCopy = arr => [...arr].sort();
Transforming Nested Data
javascript1const data = {2 users: [3 { id: 1, orders: [{ total: 100 }, { total: 200 }] },4 { id: 2, orders: [{ total: 150 }] }5 ]6};78// Get all order totals (flatten nested)9const allTotals = data.users.flatMap(user =>10 user.orders.map(order => order.total)11);12// [100, 200, 150]1314// Transform nested structure15const transformed = {16 ...data,17 users: data.users.map(user => ({18 ...user,19 totalSpent: user.orders.reduce((sum, o) => sum + o.total, 0)20 }))21};2223// Deep update (immutable)24const updateUserOrder = (data, userId, orderIndex, newTotal) => ({25 ...data,26 users: data.users.map(user =>27 user.id !== userId ? user : {28 ...user,29 orders: user.orders.map((order, i) =>30 i !== orderIndex ? order : { ...order, total: newTotal }31 )32 }33 )34});
Pipeline Patterns
Creating reusable transformation pipelines:
javascript1// Pipe function2const pipe = (...fns) => (value) =>3 fns.reduce((acc, fn) => fn(acc), value);45// Reusable transformations6const filterActive = users => users.filter(u => u.active);7const sortByName = users => [...users].sort((a, b) =>8 a.name.localeCompare(b.name)9);10const take = n => arr => arr.slice(0, n);11const pluck = key => arr => arr.map(item => item[key]);1213// Compose pipeline14const getTopActiveNames = pipe(15 filterActive,16 sortByName,17 take(5),18 pluck('name')19);2021const users = [/* ... */];22const result = getTopActiveNames(users);
Performance Considerations
javascript1// BAD: Multiple iterations2const result = arr3 .filter(x => x > 0)4 .map(x => x * 2)5 .filter(x => x < 100);67// BETTER: Single pass with reduce8const result2 = arr.reduce((acc, x) => {9 if (x > 0) {10 const doubled = x * 2;11 if (doubled < 100) {12 acc.push(doubled);13 }14 }15 return acc;16}, []);1718// Even for small arrays, reduce is often cleaner when19// you need multiple operations that depend on each other
Early Exit Patterns
javascript1// some() and every() exit early2arr.some(x => x > 10); // Stops at first true3arr.every(x => x > 0); // Stops at first false45// find() exits early6arr.find(x => x > 10); // Stops at first match78// for...of allows break9function findFirst(arr, predicate) {10 for (const item of arr) {11 if (predicate(item)) return item;12 }13 return undefined;14}1516// reduce() cannot exit early - consider for loop for large arrays17// when you need early termination
Common Gotchas
javascript1// 1. Forgetting initial value in reduce2[].reduce((a, b) => a + b); // TypeError!3[].reduce((a, b) => a + b, 0); // 0 (correct)45// 2. Mutating in map/filter (don't do this!)6const users = [{ active: false }];7const activated = users.map(u => {8 u.active = true; // BAD: mutates original!9 return u;10});11// Instead:12const activated2 = users.map(u => ({ ...u, active: true }));1314// 3. Using indexOf with objects15[{ id: 1 }].indexOf({ id: 1 }); // -1 (different references)16[{ id: 1 }].findIndex(x => x.id === 1); // 0 (correct)1718// 4. sort() mutates and has string default19const nums = [10, 2, 1];20const sorted = nums.sort(); // nums is also mutated!21// Always: [...nums].sort((a, b) => a - b)2223// 5. flat() depth defaults to 124[[1], [[2]]].flat(); // [1, [2]] - only one level!
Utility Functions
Reusable array utilities:
javascript1// Unique values2const unique = arr => [...new Set(arr)];34// Unique by key5const uniqueBy = (arr, key) => {6 const seen = new Set();7 return arr.filter(item => {8 const val = item[key];9 if (seen.has(val)) return false;10 seen.add(val);11 return true;12 });13};1415// Intersection16const intersection = (a, b) => a.filter(x => b.includes(x));1718// Difference19const difference = (a, b) => a.filter(x => !b.includes(x));2021// Zip arrays22const zip = (...arrays) => {23 const maxLength = Math.max(...arrays.map(a => a.length));24 return Array.from({ length: maxLength }, (_, i) =>25 arrays.map(arr => arr[i])26 );27};2829zip([1, 2], ['a', 'b'], [true, false]);30// [[1, 'a', true], [2, 'b', false]]3132// Shuffle33const shuffle = arr => {34 const copy = [...arr];35 for (let i = copy.length - 1; i > 0; i--) {36 const j = Math.floor(Math.random() * (i + 1));37 [copy[i], copy[j]] = [copy[j], copy[i]];38 }39 return copy;40};
Summary
| Pattern | When to Use |
|---|---|
| reduce() to object | Converting array to lookup map |
| reduce() grouping | Categorizing by property |
| flatMap() | Map + flatten in one step |
| Immutable updates | When you shouldn't mutate |
| Pipelines | Reusable transformation chains |
| Early exit loops | Large arrays with early termination |
Key takeaways:
reduce()is powerful but often overkill - prefer simpler methods when possible- Always consider immutability
- Chain methods for readable transformations
- Be aware of performance for large arrays