javascript

exercises

exercises.js
/**
 * ========================================
 * 12.2 Key Array Methods - Exercises
 * ========================================
 *
 * Practice using essential array methods.
 */

/**
 * EXERCISE 1: Transform Product Data
 *
 * Use map() to transform an array of products into
 * an array of formatted strings: "Name: $Price"
 */

function formatProducts(products) {
  // YOUR CODE HERE:
  // Return array of formatted strings
}

// const products = [
//     { name: 'Apple', price: 1.5 },
//     { name: 'Banana', price: 0.75 },
//     { name: 'Cherry', price: 2.0 }
// ];
// console.log(formatProducts(products));
// ['Apple: $1.50', 'Banana: $0.75', 'Cherry: $2.00']

/*
 * SOLUTION:
 *
 * function formatProducts(products) {
 *     return products.map(p => `${p.name}: $${p.price.toFixed(2)}`);
 * }
 */

/**
 * EXERCISE 2: Extract and Transform
 *
 * Given an array of users, extract emails and convert to lowercase.
 */

function extractEmails(users) {
  // YOUR CODE HERE:
  // Return array of lowercase emails
}

// const users = [
//     { name: 'John', email: 'JOHN@Example.com' },
//     { name: 'Jane', email: 'Jane@EXAMPLE.COM' },
//     { name: 'Bob', email: 'Bob@example.COM' }
// ];
// console.log(extractEmails(users));
// ['john@example.com', 'jane@example.com', 'bob@example.com']

/*
 * SOLUTION:
 *
 * function extractEmails(users) {
 *     return users.map(u => u.email.toLowerCase());
 * }
 */

/**
 * EXERCISE 3: Filter by Multiple Conditions
 *
 * Filter products that are:
 * - In stock (quantity > 0)
 * - In a specific category
 * - Within a price range
 */

function filterProducts(products, category, minPrice, maxPrice) {
  // YOUR CODE HERE:
  // Return filtered products
}

// const products = [
//     { name: 'Laptop', category: 'electronics', price: 999, quantity: 5 },
//     { name: 'Phone', category: 'electronics', price: 699, quantity: 0 },
//     { name: 'Tablet', category: 'electronics', price: 499, quantity: 10 },
//     { name: 'Headphones', category: 'electronics', price: 199, quantity: 15 }
// ];
// console.log(filterProducts(products, 'electronics', 200, 700).map(p => p.name));
// ['Tablet']

/*
 * SOLUTION:
 *
 * function filterProducts(products, category, minPrice, maxPrice) {
 *     return products.filter(p =>
 *         p.quantity > 0 &&
 *         p.category === category &&
 *         p.price >= minPrice &&
 *         p.price <= maxPrice
 *     );
 * }
 */

/**
 * EXERCISE 4: Calculate Order Total
 *
 * Use reduce() to calculate total cost of an order
 * where each item has price and quantity.
 */

function calculateOrderTotal(items) {
  // YOUR CODE HERE:
  // Return total cost
}

// const order = [
//     { name: 'Widget', price: 25, quantity: 4 },
//     { name: 'Gadget', price: 50, quantity: 2 },
//     { name: 'Gizmo', price: 15, quantity: 10 }
// ];
// console.log(calculateOrderTotal(order));  // 350

/*
 * SOLUTION:
 *
 * function calculateOrderTotal(items) {
 *     return items.reduce((total, item) =>
 *         total + (item.price * item.quantity), 0
 *     );
 * }
 */

/**
 * EXERCISE 5: Count by Category
 *
 * Use reduce() to count items in each category.
 */

function countByCategory(items) {
  // YOUR CODE HERE:
  // Return { category: count, ... }
}

// const items = [
//     { name: 'Apple', category: 'fruit' },
//     { name: 'Carrot', category: 'vegetable' },
//     { name: 'Banana', category: 'fruit' },
//     { name: 'Broccoli', category: 'vegetable' },
//     { name: 'Cherry', category: 'fruit' }
// ];
// console.log(countByCategory(items));
// { fruit: 3, vegetable: 2 }

/*
 * SOLUTION:
 *
 * function countByCategory(items) {
 *     return items.reduce((counts, item) => {
 *         counts[item.category] = (counts[item.category] || 0) + 1;
 *         return counts;
 *     }, {});
 * }
 */

/**
 * EXERCISE 6: Find First and Last Match
 *
 * Use find() and findLast() to get first and last
 * active user from an array.
 */

function findActiveUsers(users) {
  // YOUR CODE HERE:
  // Return { first, last }
}

// const users = [
//     { id: 1, name: 'John', active: false },
//     { id: 2, name: 'Jane', active: true },
//     { id: 3, name: 'Bob', active: false },
//     { id: 4, name: 'Alice', active: true },
//     { id: 5, name: 'Charlie', active: true }
// ];
// console.log(findActiveUsers(users));
// { first: { id: 2, name: 'Jane', ... }, last: { id: 5, name: 'Charlie', ... } }

/*
 * SOLUTION:
 *
 * function findActiveUsers(users) {
 *     return {
 *         first: users.find(u => u.active),
 *         last: users.findLast(u => u.active)
 *     };
 * }
 */

/**
 * EXERCISE 7: Validate Data
 *
 * Check if all items pass validation and if any have errors.
 */

function validateData(items) {
  // YOUR CODE HERE:
  // Return { allValid, hasErrors }
  // Valid: has name (non-empty) and quantity >= 0
}

// const items = [
//     { name: 'Widget', quantity: 5 },
//     { name: 'Gadget', quantity: 0 },
//     { name: '', quantity: 10 }
// ];
// console.log(validateData(items));
// { allValid: false, hasErrors: true }

/*
 * SOLUTION:
 *
 * function validateData(items) {
 *     const isValid = item => item.name && item.name.trim() !== '' && item.quantity >= 0;
 *     return {
 *         allValid: items.every(isValid),
 *         hasErrors: items.some(item => !isValid(item))
 *     };
 * }
 */

/**
 * EXERCISE 8: Flatten Nested Data
 *
 * Extract all tags from posts that have nested tag arrays.
 */

function extractAllTags(posts) {
  // YOUR CODE HERE:
  // Return flat array of unique tags
}

// const posts = [
//     { title: 'Post 1', tags: ['javascript', 'programming'] },
//     { title: 'Post 2', tags: ['javascript', 'web'] },
//     { title: 'Post 3', tags: ['programming', 'tutorial'] }
// ];
// console.log(extractAllTags(posts));
// ['javascript', 'programming', 'web', 'tutorial'] (unique)

/*
 * SOLUTION:
 *
 * function extractAllTags(posts) {
 *     return [...new Set(posts.flatMap(post => post.tags))];
 * }
 */

/**
 * EXERCISE 9: Sort Complex Data
 *
 * Sort products by:
 * 1. Category (alphabetically)
 * 2. Price (ascending)
 * 3. Name (alphabetically)
 */

function sortProducts(products) {
  // YOUR CODE HERE:
  // Return sorted copy
}

// const products = [
//     { name: 'Widget A', category: 'tools', price: 30 },
//     { name: 'Gadget B', category: 'electronics', price: 50 },
//     { name: 'Widget B', category: 'tools', price: 30 },
//     { name: 'Gadget A', category: 'electronics', price: 40 }
// ];
// console.log(sortProducts(products).map(p => p.name));
// ['Gadget A', 'Gadget B', 'Widget A', 'Widget B']

/*
 * SOLUTION:
 *
 * function sortProducts(products) {
 *     return [...products].sort((a, b) => {
 *         if (a.category !== b.category) {
 *             return a.category.localeCompare(b.category);
 *         }
 *         if (a.price !== b.price) {
 *             return a.price - b.price;
 *         }
 *         return a.name.localeCompare(b.name);
 *     });
 * }
 */

/**
 * EXERCISE 10: Non-Mutating Update
 *
 * Use ES2023 methods (toSorted, with, toSpliced) to:
 * - Update price at specific index
 * - Sort by price
 * - Remove item at index
 * Without mutating original array.
 */

function processImmutably(products, indexToUpdate, newPrice, indexToRemove) {
  // YOUR CODE HERE:
  // Return { updated, sorted, removed }
}

// const products = [
//     { name: 'A', price: 30 },
//     { name: 'B', price: 20 },
//     { name: 'C', price: 40 }
// ];
// const result = processImmutably(products, 1, 25, 2);
// console.log(result.updated);  // Price at index 1 is now 25
// console.log(result.sorted);   // Sorted by price
// console.log(result.removed);  // Item at index 2 removed

/*
 * SOLUTION:
 *
 * function processImmutably(products, indexToUpdate, newPrice, indexToRemove) {
 *     return {
 *         updated: products.with(indexToUpdate, {
 *             ...products[indexToUpdate],
 *             price: newPrice
 *         }),
 *         sorted: products.toSorted((a, b) => a.price - b.price),
 *         removed: products.toSpliced(indexToRemove, 1)
 *     };
 * }
 */

/**
 * EXERCISE 11: Chain Methods for Analysis
 *
 * Analyze orders:
 * 1. Filter completed orders
 * 2. Get orders over $100
 * 3. Extract customer names
 * 4. Remove duplicates
 * 5. Sort alphabetically
 */

function analyzeOrders(orders) {
  // YOUR CODE HERE:
  // Return sorted, unique customer names of completed orders over $100
}

// const orders = [
//     { id: 1, customer: 'John', total: 150, status: 'completed' },
//     { id: 2, customer: 'Jane', total: 50, status: 'completed' },
//     { id: 3, customer: 'John', total: 200, status: 'completed' },
//     { id: 4, customer: 'Bob', total: 120, status: 'pending' },
//     { id: 5, customer: 'Alice', total: 180, status: 'completed' }
// ];
// console.log(analyzeOrders(orders));  // ['Alice', 'John']

/*
 * SOLUTION:
 *
 * function analyzeOrders(orders) {
 *     return [...new Set(
 *         orders
 *             .filter(o => o.status === 'completed')
 *             .filter(o => o.total > 100)
 *             .map(o => o.customer)
 *     )].sort();
 * }
 */

/**
 * EXERCISE 12: Reduce to Object
 *
 * Convert array of [key, value] pairs to object,
 * handling duplicate keys by keeping the last value.
 */

function pairsToObject(pairs) {
  // YOUR CODE HERE:
  // Return object from pairs
}

// const pairs = [
//     ['name', 'John'],
//     ['age', 25],
//     ['name', 'Jane'],  // Duplicate - should override
//     ['city', 'NYC']
// ];
// console.log(pairsToObject(pairs));
// { name: 'Jane', age: 25, city: 'NYC' }

/*
 * SOLUTION:
 *
 * function pairsToObject(pairs) {
 *     return pairs.reduce((obj, [key, value]) => {
 *         obj[key] = value;
 *         return obj;
 *     }, {});
 * }
 */

/**
 * EXERCISE 13: Find Index with Complex Condition
 *
 * Find the index of the first product that:
 * - Is in stock
 * - Has price under specified max
 * - Has name containing search term
 */

function findProductIndex(products, maxPrice, searchTerm) {
  // YOUR CODE HERE:
  // Return index or -1 if not found
}

// const products = [
//     { name: 'Laptop Pro', price: 1500, inStock: true },
//     { name: 'Laptop Basic', price: 800, inStock: false },
//     { name: 'Laptop Mini', price: 600, inStock: true },
//     { name: 'Desktop Pro', price: 900, inStock: true }
// ];
// console.log(findProductIndex(products, 1000, 'Laptop'));  // 2

/*
 * SOLUTION:
 *
 * function findProductIndex(products, maxPrice, searchTerm) {
 *     return products.findIndex(p =>
 *         p.inStock &&
 *         p.price < maxPrice &&
 *         p.name.toLowerCase().includes(searchTerm.toLowerCase())
 *     );
 * }
 */

/**
 * EXERCISE 14: Group and Sum
 *
 * Group sales by product and calculate total quantity and revenue.
 */

function summarizeSales(sales) {
  // YOUR CODE HERE:
  // Return { product: { quantity, revenue }, ... }
}

// const sales = [
//     { product: 'Widget', quantity: 5, price: 10 },
//     { product: 'Gadget', quantity: 3, price: 20 },
//     { product: 'Widget', quantity: 2, price: 10 },
//     { product: 'Gadget', quantity: 1, price: 20 }
// ];
// console.log(summarizeSales(sales));
// {
//     Widget: { quantity: 7, revenue: 70 },
//     Gadget: { quantity: 4, revenue: 80 }
// }

/*
 * SOLUTION:
 *
 * function summarizeSales(sales) {
 *     return sales.reduce((summary, sale) => {
 *         if (!summary[sale.product]) {
 *             summary[sale.product] = { quantity: 0, revenue: 0 };
 *         }
 *         summary[sale.product].quantity += sale.quantity;
 *         summary[sale.product].revenue += sale.quantity * sale.price;
 *         return summary;
 *     }, {});
 * }
 */

/**
 * EXERCISE 15: Implement Custom flatMap
 *
 * Create your own flatMap function without using the built-in.
 */

function customFlatMap(arr, callback) {
  // YOUR CODE HERE:
  // Return flattened mapped array
}

// const nums = [1, 2, 3];
// const result = customFlatMap(nums, n => [n, n * 2]);
// console.log(result);  // [1, 2, 2, 4, 3, 6]

/*
 * SOLUTION:
 *
 * function customFlatMap(arr, callback) {
 *     return arr.reduce((result, item, index) => {
 *         return result.concat(callback(item, index, arr));
 *     }, []);
 * }
 *
 * // Or using map and flat:
 * function customFlatMap(arr, callback) {
 *     return arr.map(callback).reduce((a, b) => a.concat(b), []);
 * }
 */

/**
 * EXERCISE 16: Map with Index-Based Logic
 *
 * Create a function that applies different transformations
 * based on element index:
 * - Even indices: double the value
 * - Odd indices: square the value
 */

function transformByIndex(numbers) {
  // YOUR CODE HERE:
  // Return transformed array
}

// console.log(transformByIndex([1, 2, 3, 4, 5]));
// [2, 4, 6, 16, 10] (indices: 0:*2, 1:^2, 2:*2, 3:^2, 4:*2)

/*
 * SOLUTION:
 *
 * function transformByIndex(numbers) {
 *     return numbers.map((n, i) =>
 *         i % 2 === 0 ? n * 2 : n ** 2
 *     );
 * }
 */

/**
 * EXERCISE 17: Filter and Count
 *
 * Return both the filtered items and the count of filtered vs total.
 */

function filterWithStats(items, predicate) {
  // YOUR CODE HERE:
  // Return { filtered, matched, total, percentage }
}

// const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// const result = filterWithStats(numbers, n => n % 2 === 0);
// console.log(result);
// { filtered: [2, 4, 6, 8, 10], matched: 5, total: 10, percentage: 50 }

/*
 * SOLUTION:
 *
 * function filterWithStats(items, predicate) {
 *     const filtered = items.filter(predicate);
 *     return {
 *         filtered,
 *         matched: filtered.length,
 *         total: items.length,
 *         percentage: (filtered.length / items.length) * 100
 *     };
 * }
 */

/**
 * EXERCISE 18: Reduce with Running Total
 *
 * Use reduce to create array of running totals (cumulative sum).
 */

function runningTotal(numbers) {
  // YOUR CODE HERE:
  // Return array of cumulative sums
}

// console.log(runningTotal([1, 2, 3, 4, 5]));
// [1, 3, 6, 10, 15]

/*
 * SOLUTION:
 *
 * function runningTotal(numbers) {
 *     return numbers.reduce((result, num) => {
 *         const previousSum = result.length > 0 ? result[result.length - 1] : 0;
 *         result.push(previousSum + num);
 *         return result;
 *     }, []);
 * }
 *
 * // Alternative using scan pattern:
 * function runningTotal(numbers) {
 *     let sum = 0;
 *     return numbers.map(n => sum += n);
 * }
 */

/**
 * EXERCISE 19: Combine Every and Some
 *
 * Create a validation function that checks:
 * - all required fields are present
 * - at least one optional field is present
 */

function validateForm(data, required, optional) {
  // YOUR CODE HERE:
  // Return { allRequired, anyOptional, isValid }
}

// const data = { name: 'John', email: 'john@test.com', phone: '' };
// const result = validateForm(
//     data,
//     ['name', 'email'],
//     ['phone', 'address', 'bio']
// );
// console.log(result);
// { allRequired: true, anyOptional: false, isValid: false }

/*
 * SOLUTION:
 *
 * function validateForm(data, required, optional) {
 *     const hasValue = field => data[field] && data[field].trim() !== '';
 *     const allRequired = required.every(hasValue);
 *     const anyOptional = optional.some(hasValue);
 *     return {
 *         allRequired,
 *         anyOptional,
 *         isValid: allRequired && anyOptional
 *     };
 * }
 */

/**
 * EXERCISE 20: Chain Everything
 *
 * Process transaction data:
 * 1. Filter only successful transactions
 * 2. Group by type (income/expense)
 * 3. Calculate total for each type
 * 4. Return net balance
 */

function processTransactions(transactions) {
  // YOUR CODE HERE:
  // Return { income, expense, balance }
}

// const transactions = [
//     { type: 'income', amount: 1000, status: 'success' },
//     { type: 'expense', amount: 500, status: 'success' },
//     { type: 'income', amount: 200, status: 'failed' },
//     { type: 'expense', amount: 300, status: 'success' },
//     { type: 'income', amount: 400, status: 'success' }
// ];
// console.log(processTransactions(transactions));
// { income: 1400, expense: 800, balance: 600 }

/*
 * SOLUTION:
 *
 * function processTransactions(transactions) {
 *     const successful = transactions.filter(t => t.status === 'success');
 *
 *     const income = successful
 *         .filter(t => t.type === 'income')
 *         .reduce((sum, t) => sum + t.amount, 0);
 *
 *     const expense = successful
 *         .filter(t => t.type === 'expense')
 *         .reduce((sum, t) => sum + t.amount, 0);
 *
 *     return { income, expense, balance: income - expense };
 * }
 *
 * // Single reduce alternative:
 * function processTransactions(transactions) {
 *     return transactions
 *         .filter(t => t.status === 'success')
 *         .reduce((result, t) => {
 *             result[t.type] += t.amount;
 *             result.balance += t.type === 'income' ? t.amount : -t.amount;
 *             return result;
 *         }, { income: 0, expense: 0, balance: 0 });
 * }
 */

console.log('Key array methods exercises loaded!');
Exercises - JavaScript Tutorial | DeepML