DEV Community

Kasra Khosravi
Kasra Khosravi

Posted on • Updated on

Build Your Own Map

Photo by Capturing the human heart. on Unsplash.

Mapping is the process of converting an array of elements into a new one using a mapper function. It forms a many-to-many association and you are expected to get the same length of data that you provided in the first place.

Look at below illustration. It uses a mapping process with a declarative approach. We are not dictating how to do the mapping but what to do with it. We just provide the array of [🥔, 🌽, 🥚] and the mapper function cook and let the map handle iterating over them and applying the mapper function on each one. In the end, it will return the new array [🍟, 🍿, 🍳].


Tip: I completely understand that software interviews can be a bit scary, so my hope is to give you clear ideas about the interview process and offer you practical advice on how to do well at each step.

Map

This course can be very helpful for you to get an overview of all the common interview steps that companies go through to hire a developer. Sign up for SkillShare, Get two months of free trial and Join me on this journey


Map

When it comes to mapping, we have a few options (both with declarative and imperative approaches).


For Loop

We can use a simple for loop to iterate over array elements:

let items = [1, 2, 3, 4, 5];
let double = (item) => item * 2;
const result = [];

for (let i = 0; i < items.length; i++) {
 result.push(double(items[i]));
}


console.log(result);
// Result: [2, 4, 6, 8, 10]
Enter fullscreen mode Exit fullscreen mode

As you can see, the job of index tracking, initializing, and pushing the results to an array is on us. This is a clear example of imperative programming, which tells the computer how we want to achieve something using a step-by-step process.


forEach

Another option is forEach, which iterates over each element in the array:

let items = [1, 2, 3, 4, 5];
let double = item => item * 2;
const result = [];

items.forEach(item => {
    const doubledItem = double(item);
    result.push(doubledItem);
});


console.log(result);
// Result: [2, 4, 6, 8, 10]
Enter fullscreen mode Exit fullscreen mode

This feels a bit better, right? We no longer have to keep track of element indexes. But we can agree that mutating an item outside the scope of the function, in this case result, is not ideal. It would be great if we could abstract this even further.
The native JavaScript map is a better alternative.


Native JS Map

Let's use JavaScript's native map method. All we need is an array of data and a mapper function. map will get an array and iterate over each element in it while applying a mapper function on them. In the end, this will return a converted array with the same length.

let items = [1, 2, 3, 4, 5];
let double = (item) => item * 2;

const result = items.map(double);

console.log(result);
// Result: [2, 4, 6, 8, 10]
Enter fullscreen mode Exit fullscreen mode

This is much better compared to the alternatives like forEach or for loop in terms of readability. However, performance is a very critical component in making a decision about which option to choose.


Building a Mapping Function

But now to the fun part. Did you know that building a mapping function is not that difficult? Let's see this in action.

Own map function (for loop version)

In this example, we are abstracting away index tracking and initializing a starting array. All we need to pass is a mapper function and an array of items, and we are good to go.

let items = [1, 2, 3, 4, 5];
let double = (item) => item * 2;

// Loop Version of Map
let MapLoop = (fn, arr) => {
    const mappedArr = [];
    for (let i = 0; i < arr.length; i++) {
        let mapped = fn(arr[i]);
        mappedArr.push(mapped);
    }
    return mappedArr;
};

console.log(MapLoop(double, items));
// Result: [2, 4, 6, 8, 10]
Enter fullscreen mode Exit fullscreen mode

Own map function (recursive version)

Building a recursive version of a map function is an interesting one. But how does it work?

We still pass both the mapper function and array to the function, but we use ES6 destructuring assignment to break the array into two params called head and tail.

With this approach, we want to take a step-by-step approach and perform the mapper function on each of the array elements recursively. In the process, we use spread syntax to concatenate the result of each MapRecursive call with the result of mapped fn(head).

This continues until head becomes undefined, meaning there are no more elements in the array. That is when we bail from the recursive function shown on line 8 and then start returning the new transformed array.

let items = [1, 2, 3, 4, 5];
let double = (item) => item * 2;

// Recursive Version of Map
let MapRecursive = (fn, [head, ...tail]) => {
    // bailout
    if (head === undefined) {
       return [];
    }
    return[fn(head), ...MapRecursive(fn, tail)];
};

console.log(MapRecursive(double, items));
// Step 1: head: 1, tail: [2,3,4,5], newArray: [2, ...MapRecursive(double, [2,3,4,5])]
// Step 2: head: 2, tail: [3,4,5], newArray: [2,4, ...MapRecursive(double, [3,4,5])]
// Step 3: head: 3, tail: [4,5], newArray: [2,4,6, ...MapRecursive(double, [4,5])]
// Step 4: head: 4, tail: [5], newArray: [2,4,6,8 ...MapRecursive(double, [5])]
// Step 5: head: 5, tail: [], newArray: [2,4,6,8,10 ...MapRecursive(double, [])]
// Step 6: head: undefined -> return newArray: [2,4,6,8,10]
Enter fullscreen mode Exit fullscreen mode

Own map function (generator version)

You can also build a map function using a generator function. This is not the ideal way for handling mapping and does not give you the same result as the previous examples since generator functions return an iterator object. It is merely educational and to see how a similar concept can be applied in generator functions as well.

You can see in the comments section below what the end result of calling MapGenerator looks like:

let items = [1, 2, 3, 4, 5];
let double = (item) => item * 2;

// Generator version of Map
let MapGenerator = function * (fn, arr) {
    for (let x of arr) {
        yield fn(x);
    }
};

const result = MapGenerator(double, items);

console.log(result.next());
// Object {value: 2, done: false}
console.log(result.next());
// Object {value: 4, done: false}
console.log(result.next());
// Object {value: 6, done: false}
console.log(result.next());
// Object {value: 8, done: false}
console.log(result.next());
// Object {value: 10, done: false}
console.log(result.next());
// Object {value: undefined, done: true}
Enter fullscreen mode Exit fullscreen mode

Tip: I completely understand that software interviews can be a bit scary, so my hope is to give you clear ideas about the interview process and offer you practical advice on how to do well at each step.

Map

This course can be very helpful for you to get an overview of all the common interview steps that companies go through to hire a developer. Sign up for SkillShare, Get two months of free trial and Join me on this journey


References
https://www.freecodecamp.org/news/implement-array-map-with-recursion-35976d0325b2/
https://www.digitalocean.com/community/tutorials/list-processing-with-map-filter-and-reduce

Top comments (0)