Hello and welcome! Today, we're exploring practical data manipulation techniques in TypeScript. We'll utilize TypeScript arrays to represent our data streams and perform projection, filtering, and aggregation operations. Our operations will be neatly encapsulated within a TypeScript class for clean, structured code with built-in type annotations.
Data manipulation is akin to sculpting, but for data. We shape our dataset to derive the desired structure. TypeScript arrays with type annotations give us clarity and safety. Our manipulation operations will be part of a TypeScript class. Let's prepare our toolbox:
TypeScript1type DataElement = { 2 name: string; 3 age: number; 4 profession: string; 5 salary: number 6}; 7 8class DataStream { 9 data: DataElement[]; 10 11 constructor(data: DataElement[]) { 12 this.data = data; 13 } 14}
Our first stop is data projection, akin to capturing a photo of our desired features. Imagine we have data about people. If we're only interested in names and ages, we can project our data to focus solely on these details. We'll extend our DataStream
class with a projectData
method, leveraging TypeScript's type system:
TypeScript1projectData(keys: string[]): DataStream { 2 const projectedData = this.data.map((d) => { 3 const newObj: Partial<DataElement> = {}; 4 keys.forEach((key) => { 5 newObj[key] = d[key]; 6 }); 7 return newObj; 8 }); 9 return new DataStream(projectedData as DataElement[]); 10}
The projectData
method in the DataStream
class creates a new DataStream
instance that contains only the specified keys from each element in the original data array. It accepts an array of keys
as input. The method iterates over each element in the data and constructs a new object, newObj
, for each element. Using Partial<DataElement>
for newObj
allows these objects to only include some properties of DataElement
, making them optional. In the method, the forEach
loop assigns the value of each specified key to newObj
, ensuring that only desired fields are retained. The newly constructed projectedData
, which consists of partially constructed DataElement
objects, is then used to instantiate and return a new DataStream
.
Next, we practice data filtering, which is akin to cherry-picking preferred data entries. We'll extend our DataStream
class with a filterData
method that uses a "test" function to filter data:
TypeScript1filterData(testFunc: (element: DataElement) => boolean): DataStream { 2 const filteredData = this.data.filter(testFunc); 3 return new DataStream(filteredData); 4}
The filterData
method in the DataStream
class generates a new DataStream
instance with elements that meet a specified condition, determined by the testFunc
function. This method accepts testFunc
as a parameter, which returns a boolean to indicate whether each element should be included in the filtered dataset. It then uses the filter
method, applying the condition to each element in the data array, and creates a new DataStream
instance with the elements that pass the condition.
Finally, we come to data aggregation, where we condense data into a summary. We'll add an aggregateData
method to our DataStream
class, utilizing TypeScript's strong typing for the aggregation function:
TypeScript1aggregateData(key: string, aggFunc: (values: number[]) => number): number { 2 const values = this.data.map((d) => d[key as string] as number); 3 return aggFunc(values); 4}
The aggregateData
method in the DataStream
class is designed to create a summary statistic of a particular field within the data elements. It takes a key
string and a custom aggFunc
aggregation function as parameters. The method iterates over the data
array and extracts the values associated with the provided key
, using d[key as string] as number
to ensure that the resulting values are treated as numbers. These values are then passed to the aggFunc
, which processes the array of numbers to produce a single numerical result. This method allows for versatile aggregation operations, such as calculating averages, sums, or any other custom aggregation defined by the input function.
Now, let's combine projection, filtering, and aggregation to witness the power of these techniques. We adjust our example to demonstrate this flow, ensuring type safety using TypeScript annotations throughout:
- Data Projection: Choose only the desired fields.
- Data Filtering: Filter based on specified conditions.
- Data Aggregation: Summarize the filtered data.
TypeScript1type DataElement = { 2 name: string; 3 age: number; 4 profession: string; 5 salary: number 6}; 7 8class DataStream { 9 data: DataElement[]; 10 11 constructor(data: DataElement[]) { 12 this.data = data; 13 } 14 15 projectData(keys: string[]): DataStream { 16 const projectedData = this.data.map((d) => { 17 const newObj: Partial<DataElement> = {}; 18 keys.forEach((key) => { 19 newObj[key] = d[key]; 20 }); 21 return newObj; 22 }); 23 return new DataStream(projectedData as DataElement[]); 24 } 25 26 filterData(testFunc: (element: DataElement) => boolean): DataStream { 27 const filteredData = this.data.filter(testFunc); 28 return new DataStream(filteredData); 29 } 30 31 aggregateData(key: string, aggFunc: (values: number[]) => number): number { 32 const values = this.data.map((d) => d[key as string] as number); 33 return aggFunc(values); 34 } 35} 36 37// Example usage 38const ds = new DataStream([ 39 { name: 'Alice', age: 25, profession: 'Engineer', salary: 70000 }, 40 { name: 'Bob', age: 30, profession: 'Doctor', salary: 120000 }, 41 { name: 'Carol', age: 35, profession: 'Artist', salary: 50000 }, 42 { name: 'David', age: 40, profession: 'Engineer', salary: 90000 }, 43]); 44 45const projectedDs = ds.projectData(['name', 'age', 'salary']); 46const filteredDs = projectedDs.filterData((x: DataElement) => x.age > 30); 47const averageSalary = filteredDs.aggregateData('salary', (salaries) => salaries.reduce((a, b) => a + b, 0) / salaries.length); 48console.log(averageSalary); // Outputs: 70000.0
Here:
- Projection: We focus on
name
,age
, andsalary
fields.projectData
returns aDataStream
, allowing operation chaining. - Filtering: We filter individuals aged over 30, and
filterData
returns aDataStream
. - Aggregation: We calculate the average salary for filtered data, demonstrating TypeScript's power and conciseness.
Excellent work! You've now mastered data projection, filtering, and aggregation with TypeScript arrays. By leveraging type annotations, you achieve clear and safe code. You've also learned to encapsulate these operations in a TypeScript
class, creating efficient, reusable code structures. Now, continue your practice with exercises that challenge your newfound skills. Ready for more data fun? Let's dive in!