Comp 110 map and the Transform Interface

map and the Transform Interface

This page is going to talk about how we can make use of the generic map function implemented below. Check out the pages on the filter-map-reduce pipeline and generic functions for a review of those concepts!

export let map = <T, U> (list: List<T>, f: Transform<T, U>): List<U> => {
    if (list === null) {
        return null;
    } else {
        return cons(f(first(list)), map(rest(list), f));
    }
};

Let's take this step by step!

map takes in two parameters:

    1. A List of some generic type.

    2. A function of type Transform.

The map function is a higher-order function, meaning it accepts another function as a parameter. This function parameter belongs to the Transform interface. For a review of function types and functional interfaces, click here

The Transform interface is defined as follows: 

interface Transform<T, U> {
    (item: T): U;
}

In English, this means the function passed to map returns some generic type "U" when given an item of the generic type "T." Note here that T and U don't necessarily have to be different types - they can be the same! Defining our Transform interface in this way gives us more flexibility in our mapping algorithms, allowing us to manipulate our data in any number of ways, whether or not we are changing types.

The new mapped List is generated by recursively building a list, with each item we add onto the list being processed by the Transform function, f. Now that we have abstracted the logic of the map algorithm, we can reuse the map function over and over again. Just make sure you import it at the top of your program:

import { map } from "./list-utils";
/* This import statement will only work if you have the list-utils.ts file 
in the same folder you are working in! You can find this in your lecture 13
or lecture 14 folders. */

Let's jump into some examples!

Examples

1. Mapping strings to numbers

let stringLength = (word: string): number => {
    return word.length;
};
let main = async () => {
    let original: List<string> = listify("cat", "broom", "waffle");
    let mapped: List<string> = map(original, stringLength);
    print(mapped);
};

The resulting output will be 3 --> 5 --> 6 --> null. For each element in the original List, the Transform function stringLength is called, which returns the length property of each word. A new "mapped" List is constructed consisting of these manipulated data values.

2. Mapping strings to strings

let pigLatin = (word: string): string => {
    let firstLetter: string = word.substring(0, 1);
    let pigWord = word.substring(1) + firstLetter + "ay"
    return pigWord;
};
let main = async () => {
    let english: List<string> = listify("lets", "go", "heels");
    let pig: List<string> = map(english, pigLatin);
    print(pig);
};

The resulting output will be etslay --> ogay --> eelshay --> null. Each English word is mapped to its Pig Latin translation.

3. Mapping with objects

class Car {
    fuelTankFull: boolean = true;
    mpg: number = 30;
    color: string = "blue";
}
let emptyTanks = (car: Car): Car => {
    car.fuelTankFull = false;
    return car;
};
let toMPG = (car: Car): number => {
    return car.mpg;
};
let main = async () => {
    let car1: Car = new Car();
    car1.mpg = 32;
    let car2: Car = new Car();
    car2.mpg = 15;
    let car3: Car = new Car();
    let original: List<Car> = listify(car1, car2, car3);
    let emptied: List<Car> = map(original, emptyTanks);
    let milesPerGallon: List<number> = map(original, toMPG);
    print(emptied);
    print(milesPerGallon);
};

The resulting "emptied" List is a List of Car objects whose fuelTankFull property has been changed to false.

fuelTankFullmpgcolor
false32blue
false15blue
false30blue
null

The resulting "milesPerGallon" List is a List of numbers - the mpg property of each Car object.

32 --> 15 --> 30 --> null.

Mapping with Arrays

Every array of any generic type T[] has a map method! This map method differs slightly from the map function we may be more used to working with, but the fundamental ideas behind them are exactly the same.

The map method takes in a single parameter: a Transform function. This Transform function follows the exact same rules and requirements as a Transform used with the map function. The map method gives us back a new array that manipulates or modifies each element in some way.

Let's take look at the same examples as above, just using arrays instead.

1. Mapping strings to numbers

let stringLength = (word: string): number => {    
    return word.length;
};
let main = async () => {    
    let original: string[] = ["cat", "broom", "waffle"];    
    let mapped: number[] = original.map(stringLength);    
    print(mapped);
};

The resulting output would be 3,5,6.

2. Mapping strings to strings

let pigLatin = (word: string): string => {    
    let firstLetter: string = word.substring(0, 1);    
    let pigWord = word.substring(1) + firstLetter + "ay"    
    return pigWord;
};
let main = async () => {    
    let english: string[] = ["lets", "go", "heels"];    
    let pig: string[] = english.map(pigLatin);    
    print(pig);
};

The resulting output will be etslay,ogay,eelshay.

3. Mapping with Objects

class Car {    
    fuelTankFull: boolean = true;    
    mpg: number = 30;    
    color: string = "blue";
}
let emptyTanks = (car: Car): Car => {    
    car.fuelTankFull = false;    
    return car;
};
let toMPG = (car: Car): number => {    
    return car.mpg;
};
let main = async () => {    
    let car1: Car = new Car();    
    car1.mpg = 32;    
    let car2: Car = new Car();    
    car2.mpg = 15;    
    let car3: Car = new Car();    
    let original: Car[] = [car1, car2, car3];    
    let emptied: Car[] = original.map(emptyTanks);    
    let milesPerGallon: number[] = original.map(toMPG);    
    print(emptied);    
    print(milesPerGallon);
};

The resulting outputs would be [object Object],[object Object],[object Object] and 32,15,30 until we define a toString() method that the print function we will make use of. An example one we could write is:

toString = (): string => {
    return "Tank full: " + this.fuelTankFull + " & Color: " 
    + this.color + " & MPG: " + this.mpg;
}

In which case the output would be: Tank full: false & Color: blue & MPG: 32,Tank full: false & Color: blue & MPG: 15,Tank full: false & Color: blue & MPG: 30.