Array Transformation
Overview
Array Transformation involves converting arrays from one form to another through various operations like mapping, filtering, reducing, and applying functions. These transformations are fundamental to functional programming and data processing.
Common transformations:
- Map - Transform each element
- Filter - Select elements by condition
- Reduce - Aggregate to single value
- FlatMap - Map and flatten
- GroupBy - Group by property
- Chunk - Split into groups
Array transformation is particularly useful for:
- Data preprocessing
- Format conversion
- Aggregation
- Functional programming
- Data pipeline operations
Pattern 1: Map Transformation
Transform each element using a function.
function mapArray<T, U>(arr: T[], fn: (item: T, index: number) => U): U[] {
const result: U[] = [];
for (let i = 0; i < arr.length; i++) {
result.push(fn(arr[i], i));
}
return result;
}
// Examples
const doubled = mapArray([1, 2, 3], x => x * 2); // [2, 4, 6]
const squared = mapArray([1, 2, 3], x => x * x); // [1, 4, 9]
const withIndex = mapArray(['a', 'b', 'c'], (x, i) => `${i}:${x}`); // ['0:a', '1:b', '2:c']Time: O(n), Space: O(n)
Pattern 2: Filter Transformation
Select elements satisfying a condition.
function filterArray<T>(arr: T[], predicate: (item: T, index: number) => boolean): T[] {
const result: T[] = [];
for (let i = 0; i < arr.length; i++) {
if (predicate(arr[i], i)) {
result.push(arr[i]);
}
}
return result;
}
// Examples
const evens = filterArray([1, 2, 3, 4, 5], x => x % 2 === 0); // [2, 4]
const positives = filterArray([-1, 2, -3, 4], x => x > 0); // [2, 4]Time: O(n), Space: O(n)
Pattern 3: Reduce Transformation
Aggregate array to single value.
function reduceArray<T, U>(
arr: T[],
reducer: (acc: U, item: T, index: number) => U,
initial: U
): U {
let accumulator = initial;
for (let i = 0; i < arr.length; i++) {
accumulator = reducer(accumulator, arr[i], i);
}
return accumulator;
}
// Examples
const sum = reduceArray([1, 2, 3], (acc, x) => acc + x, 0); // 6
const product = reduceArray([1, 2, 3], (acc, x) => acc * x, 1); // 6
const max = reduceArray([1, 5, 3], (acc, x) => Math.max(acc, x), -Infinity); // 5Time: O(n), Space: O(1)
Pattern 4: FlatMap Transformation
Map and flatten in one operation.
function flatMapArray<T, U>(
arr: T[],
fn: (item: T, index: number) => U[]
): U[] {
const result: U[] = [];
for (let i = 0; i < arr.length; i++) {
result.push(...fn(arr[i], i));
}
return result;
}
// Examples
const expanded = flatMapArray([1, 2, 3], x => [x, x * 2]); // [1, 2, 2, 4, 3, 6]
const words = flatMapArray(['hello world', 'foo bar'], s => s.split(' ')); // ['hello', 'world', 'foo', 'bar']Time: O(n m), Space: O(n m)
Pattern 5: GroupBy Transformation
Group elements by a key function.
function groupBy<T, K>(
arr: T[],
keyFn: (item: T) => K
): Map<K, T[]> {
const groups = new Map<K, T[]>();
for (const item of arr) {
const key = keyFn(item);
if (!groups.has(key)) {
groups.set(key, []);
}
groups.get(key)!.push(item);
}
return groups;
}
// Examples
const byLength = groupBy(['a', 'bb', 'ccc', 'dd'], s => s.length);
// Map { 1 => ['a'], 2 => ['bb', 'dd'], 3 => ['ccc'] }
const byParity = groupBy([1, 2, 3, 4, 5], x => x % 2 === 0 ? 'even' : 'odd');
// Map { 'odd' => [1, 3, 5], 'even' => [2, 4] }Time: O(n), Space: O(n)
Pattern 6: Chunk Transformation
Split array into chunks of specified size.
function chunkArray<T>(arr: T[], size: number): T[][] {
const chunks: T[][] = [];
for (let i = 0; i < arr.length; i += size) {
chunks.push(arr.slice(i, i + size));
}
return chunks;
}
// Examples
const chunks = chunkArray([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4], [5]]
const groups = chunkArray(['a', 'b', 'c', 'd'], 3); // [['a', 'b', 'c'], ['d']]Time: O(n), Space: O(n)
Pattern 7: Partition Transformation
Split array into two based on predicate.
function partitionArray<T>(
arr: T[],
predicate: (item: T) => boolean
): [T[], T[]] {
const truthy: T[] = [];
const falsy: T[] = [];
for (const item of arr) {
if (predicate(item)) {
truthy.push(item);
} else {
falsy.push(item);
}
}
return [truthy, falsy];
}
// Examples
const [evens, odds] = partitionArray([1, 2, 3, 4, 5], x => x % 2 === 0);
// evens = [2, 4], odds = [1, 3, 5]Time: O(n), Space: O(n)
Pattern 8: Zip Transformation
Combine arrays element-wise.
function zipArrays<T, U>(arr1: T[], arr2: U[]): [T, U][] {
const result: [T, U][] = [];
const minLen = Math.min(arr1.length, arr2.length);
for (let i = 0; i < minLen; i++) {
result.push([arr1[i], arr2[i]]);
}
return result;
}
// Zip multiple arrays
function zipMultiple<T>(...arrays: T[][]): T[][] {
const result: T[][] = [];
const minLen = Math.min(...arrays.map(arr => arr.length));
for (let i = 0; i < minLen; i++) {
result.push(arrays.map(arr => arr[i]));
}
return result;
}
// Examples
const zipped = zipArrays([1, 2, 3], ['a', 'b', 'c']); // [[1, 'a'], [2, 'b'], [3, 'c']]Time: O(n), Space: O(n)
Pattern 9: Unzip Transformation
Separate zipped array back into arrays.
function unzipArrays<T, U>(zipped: [T, U][]): [T[], U[]] {
const arr1: T[] = [];
const arr2: U[] = [];
for (const [a, b] of zipped) {
arr1.push(a);
arr2.push(b);
}
return [arr1, arr2];
}
// Unzip multiple
function unzipMultiple<T>(zipped: T[][]): T[][] {
if (zipped.length === 0) return [];
const result: T[][] = Array(zipped[0].length).fill(null).map(() => []);
for (const tuple of zipped) {
for (let i = 0; i < tuple.length; i++) {
result[i].push(tuple[i]);
}
}
return result;
}Time: O(n m), Space: O(n m)
Pattern 10: Window Transformation
Apply function to sliding windows.
function windowArray<T, U>(
arr: T[],
size: number,
fn: (window: T[]) => U
): U[] {
const result: U[] = [];
for (let i = 0; i <= arr.length - size; i++) {
const window = arr.slice(i, i + size);
result.push(fn(window));
}
return result;
}
// Examples
const movingAvg = windowArray([1, 2, 3, 4, 5], 3, window => {
return window.reduce((a, b) => a + b, 0) / window.length;
}); // [2, 3, 4]
const maxInWindow = windowArray([1, 3, 2, 5, 4], 3, window => Math.max(...window));
// [3, 5, 5]Time: O(n * k), Space: O(n)
Pattern 11: Transpose Transformation
Transpose 2D array (swap rows and columns).
function transposeArray<T>(matrix: T[][]): T[][] {
if (matrix.length === 0) return [];
const rows = matrix.length;
const cols = matrix[0].length;
const result: T[][] = Array(cols).fill(null).map(() => Array(rows));
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
result[j][i] = matrix[i][j];
}
}
return result;
}
// Example
const transposed = transposeArray([[1, 2], [3, 4], [5, 6]]);
// [[1, 3, 5], [2, 4, 6]]Time: O(rows cols), Space: O(rows cols)
Pattern 12: Pipeline Transformation
Chain multiple transformations.
class ArrayPipeline<T> {
private data: T[];
constructor(data: T[]) {
this.data = [...data];
}
map<U>(fn: (item: T) => U): ArrayPipeline<U> {
return new ArrayPipeline(this.data.map(fn));
}
filter(predicate: (item: T) => boolean): ArrayPipeline<T> {
return new ArrayPipeline(this.data.filter(predicate));
}
reduce<U>(reducer: (acc: U, item: T) => U, initial: U): U {
return this.data.reduce(reducer, initial);
}
flatMap<U>(fn: (item: T) => U[]): ArrayPipeline<U> {
return new ArrayPipeline(this.data.flatMap(fn));
}
chunk(size: number): ArrayPipeline<T[]> {
const chunks: T[][] = [];
for (let i = 0; i < this.data.length; i += size) {
chunks.push(this.data.slice(i, i + size));
}
return new ArrayPipeline(chunks);
}
toArray(): T[] {
return [...this.data];
}
}
// Usage
const result = new ArrayPipeline([1, 2, 3, 4, 5, 6])
.filter(x => x % 2 === 0)
.map(x => x * 2)
.chunk(2)
.toArray();
// [[4, 8], [12]]Time: Depends on operations, Space: O(n)
When to Use Array Transformations
Use array transformations when:
- Need to process data in functional style
- Pipeline operations required
- Data preprocessing needed
- Format conversion necessary
- Aggregation or grouping required
- Functional programming patterns
Key principles:
- Prefer immutable operations
- Chain transformations for readability
- Consider performance for large arrays
- Use appropriate transformation for task
Template (Map-Filter-Reduce Pipeline)
function transformPipeline<T, U>(
arr: T[],
mapFn: (item: T) => U,
filterFn: (item: U) => boolean,
reduceFn: (acc: number, item: U) => number,
initial: number
): number {
return arr
.map(mapFn)
.filter(filterFn)
.reduce(reduceFn, initial);
}Template (Composable Transformations)
function composeTransforms<T>(
arr: T[],
...transforms: Array<(arr: T[]) => T[]>
): T[] {
return transforms.reduce((acc, transform) => transform(acc), arr);
}Time and Space Complexity Summary
- Map: O(n) time, O(n) space
- Filter: O(n) time, O(n) space
- Reduce: O(n) time, O(1) space
- FlatMap: O(n m) time, O(n m) space
- GroupBy: O(n) time, O(n) space
- Chunk: O(n) time, O(n) space
- Zip: O(n) time, O(n) space
- Transpose: O(rows cols) time, O(rows cols) space
Practice Tips
- Choose right transformation - Map for element-wise, filter for selection, reduce for aggregation
- Chain operations - Build pipelines for complex transformations
- Immutable - Prefer creating new arrays over mutating
- Performance - Consider time/space for large arrays
- Readability - Use descriptive function names
Common Mistakes
- Mutating original - Not creating new array
- Wrong transformation - Using map when need filter
- Index errors - Off-by-one in transformations
- Memory overhead - Creating unnecessary intermediate arrays
- Type errors - Not handling type conversions
Related Concepts
- Functional Programming - Core paradigm
- Array Methods - Built-in transformations
- Data Processing - Real-world applications
- Pipeline Pattern - Chaining operations
Array transformation is fundamental to data processing. Master these patterns, and you'll efficiently transform and process arrays in functional programming style in coding interviews and real-world applications.