"Efficient ways to calculate the total sum of an array of objects based on a specific property

I currently have a straightforward method that calculates the total sum of an object array based on one of the properties.

const calculateSum = <T extends object, K extends keyof T>(array: T[], property : K) : number =>{
  
  let total = 0;
  if (property)
  {
      total = array.reduce((acc, item) => {
      return acc + (+item[property]);
    }, 0);

  }
  return total;
}

Is there a way to refactor this method so that it can also accept an array of numbers and simply calculate the total in that case without requiring a property to be passed?

Answer №1

Essentially, this code represents two distinct functionalities:

function calculateObjSum<K extends PropertyKey>(
    arr: (Record<string, any> & Partial<Record<K, number>>)[],
    property: K
): number {
    const _arr: Partial<Record<K, number>>[] = arr;
    return _arr.reduce((a, v) => a + (v[property] ?? 0), 0)
}
const s1 = calculateObjSum([{ a: "abc", b: 123 }, { a: "def", b: 456 }], "b");
console.log(s1) // 579

function calculateNumSum(arr: number[]): number {
    return arr.reduce((a, v) => a + v, 0)
}
const s2 = calculateNumSum([789, 123, 456]);
console.log(s2) // 1368

The calculateObjSum() function ensures type safety by specifically allowing inputs where the elements of arr possess number properties at the specified property key. Conversely, calculateNumSum() simply adds up an array of numbers.


If you desire to combine these into a single function, it requires setting up multiple call signatures and implementations for TypeScript verification and runtime operation. For instance, an overloaded function can be written as follows:

// Call Signatures
function calculateSum<K extends PropertyKey>(
    arr: (Record<string, any> & Partial<Record<K, number>>)[],
    property: K
): number;
function calculateSum(arr: number[]): number;

// Implementation
function calculateSum(arr: any[], property?: PropertyKey) {
    if (typeof property === "string") {
        return arr.reduce((a, v) => a + (v[property] ?? 0), 0);
    }
    return arr.reduce((a, v) => a + v, 0);
}

const s1 = calculateSum([{ a: "abc", b: 123 }, { a: "def", b: 456 }], "b");
console.log(s1) // 579

const s2 = calculateSum([789, 123, 456]);
console.log(s2) // 1368

This implementation operates as expected, determining whether property is a string or undefined, thus executing either calculateObjSum or calculateNumSum.

Note that this approach is less type-safe compared to individual functions since TypeScript may struggle to validate compliance with each call signature distinctly. This increases the complexity both for the compiler and implementer, potentially impacting the caller too. Ultimately, choosing between a unified calculateSum function and separate calculateObjSum and

calculateNumSum</code functions is subjective. In my view, having distinct functions with singular responsibilities is preferable for clarity over merging them into one container. This consolidation makes analysis more difficult for compilers and implementers alike, although the decision rests with personal preference.</p>
<hr />
<p>Alternatively, one could consider both these functions as specific scenarios of a broader function accepting a variadic range of property keys indicating a path within array elements. For instance, <code>calculateSum(arr)
necessitates number elements in arr, while calculateSum(arr, "a") involves {a?: number} elements, and
calculateSum(arr, "a", "b", "c")
pertains to {a?: {b?: {c?: number}}} elements. Such a comprehensive function could be structured like this:

type DeepNumberDict<K extends PropertyKey[]> =
    K extends [infer K0 extends PropertyKey, ...infer KR extends PropertyKey[]] ?
    Record<string, any> & { [P in K0]?: DeepNumberDict<KR> } : number;

function calculateSum<K extends PropertyKey[]>(
    arr: DeepNumberDict<K>[], ...keys: K
): number {
    return arr.reduce((a, v) => a + ((keys.reduce((a, k) => (a ?? {})[k], v as any)) ?? 0), 0);
}

Testing this functionality demonstrates its effectiveness:

const s1 = calculateSum([{ a: "abc", b: 123 }, { a: "def", b: 456 }], "b");
console.log(s1) // 579

const s2 = calculateSum([789, 123, 456]);
console.log(s2) // 1368

const s3 = calculateSum([{ a: { b: { c: 1 } } }, { a: { b: { c: 2 } } }], "a", "b", "c");
console.log(s3) // 3

However, the operational intricacies hinder TypeScript validation due to potential need for type assertions or any, and comprehension of the DeepNumberDict utility type might present challenges for casual users. Despite this, from a conceptual perspective, it embodies a cohesive entity.

Playground link to code

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Guide for launching Electron on a local host server during development and for production builds

I have a project using Next.js + Electron + Typescript. I used the npx create-next-app --example with-electron-typescript command to generate the initial code. When I run npm run dev (which actually runs npm run build-electron && electron . ), the ...

Testing Jasmine with objects that contain optional properties

In the IData interface, there are optional properties available. interface IData { prop1: string, prop2?: string } setObj(){ prop1 = 'abc'; prop2 = 'xyz'; let obj1 : IData = { prop1: this.prop1, ...

Which is the better choice for simply invoking a service method - subscribe or toPromise?

When implementing the search method below, I simply assign the value of BehaviourSubject in the service. However, I am unsure whether it is possible to execute this operation without using either subscribe() or toPromise() after the .pipe() block in the ...

Having trouble installing memlab using the npm package

Recently, I made an attempt to install the memlab library from Meta's GitHub. Initially, when I installed it without using the -g flag, the installation was successful. However, I encountered an issue where I could not execute any of the memlab comman ...

Tips for assigning types from an interface object in TypeScript

Here is the code snippet I'm dealing with interface deviceInfo { Data: { model: string; year: number; }; } const gadget: deviceInfo.Data; I encountered a warning in vscode that indicates there ...

Enhancing interface properties with type safety in a function declaration using Typescript

Consider the following scenario: interface Steps { stepOne?: boolean; stepTwo?: boolean; stepThree?: boolean; } let steps: Steps = {}; function markStepDone (step: ???) { steps[step] = true; } markStepDone('anything'); Is there a wa ...

Setting null for HttpParams during the call

I am encountering an issue with HttpParams and HttpHeaders after upgrading my project from Angular 7 to Angular 8. The problem arises when I make a call to the API, as the parameters are not being added. Any assistance in resolving this matter would be gre ...

Leverage the power of TypeScript by enabling the noImplicitAny flag when working

Currently, I am looking to activate the noImplicitAny flag in my compiler. My main issue lies with utilizing lodash/fp as there are no typings available at this moment. Due to this, the compiler is generating errors due to the absence of a definition file ...

What is the process for incorporating a custom action in TestCafe with TypeScript?

I've encountered an issue while trying to implement a custom action. When running tests with the new custom action, I keep receiving an error stating that my custom action is not recognized as a function. TypeError: testcafe_1.t.customActions.selectFr ...

Highlighting DOM elements that have recently been modified in Angular

Is there a simple way to change the style of an element when a bound property value changes, without storing a dedicated property in the component? The elements I want to highlight are input form elements: <tr field label="Lieu dépôt"> ...

Looking for a more efficient approach to writing React styles for color?

Desire I am interested in customizing colors within Material UI components. Moreover, I aim to develop a React Component that allows for dynamic color switching through Props. Challenge The current approach using withStyles results in redundant and lengt ...

Tips for successfully implementing a redirect in getStaticProps without encountering any TypeScript errors

Issue While using Next.js with TypeScript, an error occurs when attempting to return a redirect in case the data is missing. Solution Attempted import { GetStaticProps } from "next"; import fs from "fs/promises"; import path from " ...

Is it possible to regulate the type of a class that has not yet been instantiated?

Is there a method in typescript to restrict the type of an uninstantiated class? I am looking to specify that only classes which inherit from Repository can be accepted by the addRepository method without actually creating an instance of the class (its co ...

Looking for guidance on converting JS code to TypeScript? Let's tackle this TS test together!

I am facing the challenge of encapsulating a very complex SDK into a layer of code. I have attempted to utilize union and index types for this task, and below is a demo that I have created. How can I implement the bar method in TypeScript to pass the conso ...

What is the best way to determine the type of `rootReducer`?

My project is set up with a combination of React, Redux, Immutable.js, and TypeScript. As I worked on implementing it, I made an effort to declare types wherever possible which led me to discover an interesting issue. A code example illustrating the proble ...

Starting up a pre-existing Angular project on your local machine

I am completely new to Angular and facing difficulties running an existing project on my machine. Despite conducting numerous tests and following various articles, I still cannot get the project to run. Here is the layout of my project files: https://i.s ...

Angular 2 repeatedly pushes elements into an array during ngDoCheck

I need assistance with updating my 'filelistArray' array. It is currently being populated with duplicate items whenever content is available in the 'this.uploadCopy.queue' array, which happens multiple times. However, I want to update ...

Pass information captured from Mat Dialog up to the main component

Looking for a way to pass true/false boolean data from a dialog box into the parent component without just console logging the result? You want to store it in a variable in the parent component for further use. Any suggestions on how to achieve this? This ...

Angular 2 is throwing an error stating that the argument 'ElementRef' cannot be assigned to the parameter 'ViewContainerRef'

I'm developing an Angular 2 application with angular-cli, but when I include the following constructor, I encounter the following error: Error Argument of type 'ElementRef' is not assignable to parameter of type 'ViewContainerRef&apos ...

Develop a library of components using TypeScript and Less files

I'm currently in the process of creating a React UI library that will consist of various components such as Buttons, Inputs, textareas, etc. This library, which I've temporarily named mylib, will be reused across multiple projects including one c ...