"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

Refresh the child component whenever there is a change in the parent's state

As of now, I am in the process of developing a MultiCheckbox Component which looks like this: Checkbox.tsx import './Checkbox.scss'; import React, {ChangeEvent, Component} from 'react'; /* * Description of Checkbox properties */ in ...

The incorrect initial state is causing issues in the Zustand state management on the Next.js server side

While utilizing zustand as a global state manager, I encountered an issue where the persisted states were not being logged correctly in the server side of nextjs pages. The log would only show the default values (which are null) and not the updated state v ...

Ensuring the correctness of environment variables in Next.js using Zod

After spending the entire day trying to figure it out, I realize that the solution may be simpler than expected. I am currently using the well-known zod library to validate my environment variables and transform data. However, I keep encountering a persis ...

Substitute terms in a sentence according to the guidelines

Looking to transform strings based on specific rules? "Hello {{firstName}}, this is {{senderName}}." Consider the following rules: rules = { firstName: "Alex", senderName: "Tracy" } The expected output would be: "Hello Alex, this is Tracy." If yo ...

Is there a way to operate both websocket and http methods concurrently on a server in Typescript?

I have a question regarding running a websocket server with the route "ws://localhost:port" using the 'ws' library. It requires the app instance of 'express' and also features http routes such as "http://localhost:port/auth/login". I am ...

Having trouble verifying the selection option in Angular 6

I've been trying to implement Select option validation in Angular 6, but neither Aria-required nor required seem to be effective. The requirement is for it to display a message or show a RED border similar to HTML forms. Here's the HTML snippet ...

Is it compatible to use Typescript version 2.4.2 with Ionic version 3.8.0?

Is it compatible to use Typescript 2.4.2 with Ionic 3.8.0? $ ionic info cli packages: (C:***\AppData\Roaming\npm\node_modules) @ionic/cli-utils : 1.18.0 ionic (Ionic CLI) : 3.18.0 global packages: cordova (Cordova CLI) : not insta ...

Will the component re-render before executing the next lines when using setState or dispatch with the useReducer hook?

Upon using the useState and useReducer hooks, I encountered an issue where any code lines following the state update function (setState, dispatch) would be executed in the subsequent re-rendering, with the previous state intact before the update. Essential ...

Enhance the appearance of a custom checkbox component in Angular

I developed a customized toggle switch for my application and integrated it into various sections. Recently, I decided to rework it as a component. However, I am encountering an issue where the toggle switch button does not update in the view (it remains t ...

Tips for changing a created Word file with Docxtemplater into a PDF format

Hey there! I am currently in the process of building a website with Angular.js and have successfully managed to generate a word document from user input. Everything was working fine until I encountered an issue. I now need to provide a way for users to pr ...

Transforming two child arrays within an object into a single array using Ramda

I am looking to transform an object into an array. The object I have is structured like this: const data = { t1: [ {"a": 1, "a1": 2}, {"b": 3, "b1": 4}, {"c": 5, "c1": 6} ], t2: [ {" ...

Requires the refreshing of an Angular component without altering any @Input properties

Currently delving into the world of Angular (along with Typescript). I've put together a small application consisting of two components. This app is designed to help track work hours (yes, I am aware there are commercial products available for this pu ...

Strategies for redirecting search queries when adding a new path

Issue I am facing a challenge with pushing a new path to the URI while maintaining existing search queries. For example: Current URL: https://example.com/foo?bar=123&foobar=123 When I use history.push('newPath'), I end up with https://exa ...

Is it possible for a React selector to retrieve a particular data type?

As a newcomer to React and Typescript, I am currently exploring whether a selector can be configured to return a custom type. Below is a basic selector that returns a user of type Map<string, any>: selectors/user.ts import { createSelector } from ...

The issue arises when attempting to invoke a method from a global mixin in a Vue3 TypeScript component

I've been working on this challenge for the past week, and I would appreciate any help or insights from those who may have experience in this area. Currently, I am in the process of converting vue2-based code to vue3 for a new project. Instead of usi ...

Using AngularJS with CDN: A beginner's guide

If I need to create an app using AngularJS with Cordova in Visual Studio, do I need anything else besides the Google CDN for AngularJS? <!doctype html> <html ng-app> <head> <title>My Angular App</title> <script s ...

Triggering two function calls upon submission and then waiting for the useEffect hook to execute

Currently, I am facing a challenge with form validation that needs to be triggered on submit. The issue arises as some of the validation logic is located in a separate child component and is triggered through a useEffect dependency from the parent componen ...

The rendering of the Angular 2 D3 tree is not functioning properly

Attempting to transition a tree created with d3 (v3) in vanilla JavaScript into an Angular2 component has been challenging for me. The issue lies in displaying it correctly within the component. Below is the code snippet from tree.component.ts: import { ...

Can you surpass the type declarations of a module at the local level?

Is there a way to change the appearance of a specific typescript module for those importing it? I have webpack rules that modify the exports of this module during transpile time, which is why I want to override its appearance. In my custom.d.ts file, I h ...

The 'HTMLDivElement' type does not include the property 'prepend' in Typescript

When working with a typescript method, the following code snippet is used: some_existing_div.prepend(some_new_div) This may result in an error message: [ts] Property 'prepend' does not exist on type 'HTMLDivElement'. However, despi ...