Utilizing TypeScript Variadic Tuple Types for an efficient Cartesian Product function

With the introduction of Variadic Tuple Types in TypeScript 4.0, a new type construct that allows for interesting possibilities such as concantenation functions has been made available. An example from the documentation illustrates this:

type Arr = readonly any[];

function concat<T extends Arr, U extends Arr>(arr1: T, arr2: U): [...T, ...U] {
  return [...arr1, ...arr2];
}

This raises the question of whether Variadic Tuple Types can be leveraged to type a function that calculates the Cartesian Product. The goal would be for the function to infer the types of its arguments and produce an appropriate return type. For instance, providing [number[], string[]] as input should yield a result of type [number, string][]. While there are various implementations of Cartesian Product functions available, none are strictly typed. One example is shown below:

const cartesian =
  (...a) => a.reduce((a, b) => a.flatMap(d => b.map(e => [d, e].flat())));

The current implementation I am using does not make use of Variadic Tuple Types and instead requires explicit type casting as seen here:

const cartesian = <T extends any[]>(...arr: any[][]): T[] =>
  arr.reduce<T[]>(
    (a, b) => a.flatMap<T>(c => b.map<T>(d => [...c, d] as T)),
    [[]] as T
  );

const product = cartesian<[number, string]>([1, 2, 3], ['a', 'b', 'c']);

Seeking a solution without relying on explicit type casting, I believe that Variadic Tuple Types could provide a more elegant approach for typing a Cartesian Product function.

Question

How can Variadic Tuple Types be utilized to automatically infer types for a Cartesian Product function?

Answer №1

Variadic Tuple Types do not necessarily enhance the way we can type this specific code snippet. The ability to type this has actually been achievable since the support for mapping tuples was introduced in version 3.1 of TypeScript (although it may have been possible before but not as elegantly).

While a type assertion is still required in the actual implementation, the call site will automatically infer the argument types and generate the correct return type:

type MapCartesian<T extends any[][]> = {
  [P in keyof T]: T[P] extends Array<infer U> ? U : never
}
const cartesian = <T extends any[][]>(...arr: T): MapCartesian<T>[] =>
  arr.reduce(
    (a, b) => a.flatMap(c => b.map(d => [...c, d])),
    [[]] 
  ) as MapCartesian<T>[];

const product = cartesian([1, 2, 3], ['a', 'b', 'c']);

Check out the Playground Link

Answer №2

If we are dealing with a cartesian product, it is advisable to utilize Sets instead of arrays for both input and output.

function cartesian<X, Y>(setX: Set<X>, setY: Set<Y>): Set<[X, Y]> {
    const result = new Set<[X, Y]>();
    setX.forEach(x => setY.forEach(y => result.add([x, y])));
    return result;
}

const product = cartesian(new Set([1, 2, 3]), new Set(['a', 'b', 'c']));

EDIT (after Nicky's comment):

In an attempt to create a more generalized function signature that can handle any number of sets, I initially struggled to transition from arrays to sets:

declare function cartesian<A extends Array<Array<any>>>(
    ...args: A): { [K in keyof A]: A[K] extends Array<any> ? A[K][number] : never }[];
const product = cartesian([1, 2, 3], ['a', 'b', 'c'], [true, false]); 

However, upon carefully reviewing @Titian Cernicova-Dragomir's answer which demonstrated elegant type inference, I adopted his approach using sets:

declare function cartesian<A extends Array<Set<any>>>(
    ...args: A): Set<{ [K in keyof A]: A[K] extends Set<infer T> ? T : never }>;
const product = cartesian(new Set([1, 2, 3]), new Set(['a', 'b', 'c']), 
    new Set([true, false])); 

Answer №3

According to Titian, there is no real need for Variadic Tuple Types in this context. A more concise solution can be achieved by using the generic T to represent the inner types of each input array. It's also recommended to use unknown instead of any for safety.

const cartesian = <T extends unknown[]>(...a: { [K in keyof T]: T[K][] }) =>
    a.reduce<T[]>(
        (b, c) => b.flatMap((d) => c.map((e) => [...d, e] as T)),
        [[]] as unknown as T[]
    );

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

Receiving an eslint error while trying to integrate Stripe pricing table into a React web application

If you're looking to incorporate a Stripe documentation code snippet for adding a stripe-pricing-table element, here's what they suggest: import * as React from 'react'; // If you're using TypeScript, don't forget to include ...

What is the correct way to write an asynchronous Express middleware function in Typescript?

Can anyone help me figure out how to correctly define a return value for an express middleware that utilizes async/await? I've been experimenting with different approaches but haven't found success yet. Additionally, I'm attempting to exten ...

Arrange the columns in Angular Material Table in various directions

Is there a way to sort all columns in an Angular material table by descending order, while keeping the active column sorted in ascending order? I have been trying to achieve this using the code below: @ViewChild(MatSort) sort: MatSort; <table matSort ...

Trouble loading Styled Components in React Typescript with Webpack configuration

Hey there! I'm diving into the world of styled components for the first time, and I'm a bit lost on where I might have slipped up. I've got my webpack all sorted out and my .babelrc file in place. As I was going through the Styled Component ...

What is the error message "Cannot assign type 'IArguments' to argument"?

Currently employing a workaround that is unfortunately necessary. I have to suppress specific console errors that are essentially harmless. export const removeConsoleErrors = () => { const cloneConsoleError = console.error; const suppressedWarnings ...

Setting up the environment for Angular 7 within a TFS build pipeline

I've been attempting to customize the environment in my tfs build pipeline, but it keeps defaulting to the dev environment. Oddly enough, the 'ng serve' command is working perfectly fine. Below are the version details of my application: An ...

Switch up the data format of a JSON file that is brought into TypeScript

When bringing in a JSON file into a TypeScript project with the resolveJsonModule option activated, the TypeScript compiler can automatically determine the type of the imported JSON file. However, I find this type to be too specific and I would like to rep ...

Retrieve the data from the mat-checkbox

My goal is to retrieve a value from a mat-checkbox, but the issue is that we only get boolean expression instead of the string value. Here's an example snippet of what I'm looking for: <mat-checkbox formControlName="cb2" <strong&g ...

Adding client-side scripts to a web page in a Node.js environment

Currently, I am embarking on a project involving ts, node, and express. My primary query is whether there exists a method to incorporate typescript files into HTML/ejs that can be executed on the client side (allowing access to document e.t.c., similar to ...

What steps do I need to take in order to activate scrolling in a Modal using Material-UI

Can a Modal be designed to work like a Dialog with the scroll set to 'paper'? I have a large amount of text to show in the Modal, but it exceeds the browser window's size without any scrolling option. ...

Unable to utilize the web-assembly Rust implementation due to the error stating 'Cannot access '__wbindgen_throw' before initialization'

Looking to integrate some web-assembly into my project, I started off by testing if my webpack setup was functioning properly and able to utilize my .wasm modules. Here's a snippet of what I came up with: #[wasm_bindgen] pub fn return_char() -> cha ...

Issue with displaying decimal places in Nivo HeatMap

While utilizing Nivo HeatMap, I have observed that the y value always requires a number. Even if I attempt to include decimal places (.00), it will still trim the trailing zeros and display the value without them. The expected format of the data is as foll ...

What is the method for handling an array in Angular when all of them are successfully passed?

tasks: [ {failed: true, remarks: "",task: {'name': 'task1'}}, {failed: true, remarks: "",task: {'name': 'task2'}}, ] Is there a way to determine the overall status of all tasks based on their suc ...

Interacting between Angular Child and Parent components

I am facing an issue where I am trying to emit an event from a child component and display it in the parent HTML, but it doesn't seem to be working. Below is my code: ParentComponent.ts @Component({ selector: 'app-parent', templateUrl: ...

Translate Typescript into Javascript for use in React applications

Can anyone assist me in converting this Typescript code to Javascript? function ImageMagnifier({ src, width, height, magnifierHeight = 100, magnifieWidth = 100, zoomLevel = 1.5 }: { src: string; width?: string; height?: string; magnifie ...

Every time I execute my program, it consistently displays a 500 error message when using both the POST and GET

I'm seeking assistance with mvvm as I am new to it. Can anyone help me in displaying details based on the selected date? Upon running my code, I encounter a 500 error with both the post and get methods. Schedule.cshtml <div class="col-lg-8" ng-ap ...

Can someone give me a thorough clarification on exporting and importing in NodeJS/Typescript?

I am inquiring about the functionality of exports and imports in NodeJS with TypeScript. My current setup includes: NodeJS All written in Typescript TSLint for linting Typings for type definitions I have been experimenting with exports/imports instead o ...

No data found in req.query object in ExpressJS

When I use http.post to send data from Angular to NodeJS, the req.query always comes back empty for me. Here is my server.js setup: const express = require('express'); const cors = require('cors'); const bodyParser = require('body ...

Service consuming in Angular 2 using Stomp protocol

Why am I seeing responseBody as undefined, but I am able to see the subscribe response in msg_body? What could be causing this issue with responseBody? let stomp_subscription = this._stompService.subscribe('/topic/queue'); stomp_subscription.ma ...

What is the most effective method of testing with jest to verify that a TypeScript Enum list contains all the expected string values?

Recently, I created a list of enums: export enum Hobbies { Paint = 'PAINT', Run = 'RUN', Bike = 'BIKE', Dance = 'DANCE' } My goal is to iterate through this list using Jest and verify that all the string ...