Typescript iteration: Exploring an iterable through multiple traversals

What is a practical approach to handling multiple traversals over an Iterable in Typescript?

Typically, an Iterable can only be traversed once before it exhausts all its elements. For instance, an IterableIterator behaves this way. To traverse a sequence multiple times, one common solution is to first replicate it into an array.

If the Iterable already supports multiple traversals, like in the case of arrays, making a copy is unnecessary duplication. To optimize this process, it may be beneficial to develop a function called

possiblyCopyForMultipleTraversals()
that generates a copy only when required. A more cautious implementation could identify standard data structures like Arrays and Sets to skip unnecessary copying. Could such a generic function be created?

Answer №1

This method may not be flawless, but it follows the concept of "creating your own":

You have the option to design a MultipleIterable interface that only exposes a property indicating whether the object represents an Iterable with an iterator method that generates a new iterator each time it is called:

export interface MultipleIterable<T> extends Iterable<T> {
  multipleIterable: true;
}

In addition, you can define a function that checks if an Iterable is also a MultipleIterable:

function isMultableIterable<T>(iterable: Iterable<T>): iterable is MultipleIterable<T> {
  return (iterable) && ((iterable as any).multipleIterable === true);
}

If desired, you can extend Array's prototype to indicate that arrays are MultipleIterable:

declare global {
  interface Array<T> {
    multipleIterable: true;
  }
}
Array.prototype.multipleIterable = true;

A similar approach could be taken for modifying the prototypes of other built-in iterables exhibiting this behavior.


Next, create a ReplayableIterable<T> class where the constructor accepts any Iterable<T> and wraps it so that its iterators always start fresh:

class ReplayableIterable<T> implements MultipleIterable<T> {

  multipleIterable = true as true;

  [Symbol.iterator](): Iterator<T> {
    let cur = 0;
    let iterable = this;
    return {
      next(): IteratorResult<T> {
        while (cur >= iterable.iteratorResults.length) {
          iterable.iteratorResults.push(iterable.iterator.next());
        }
        const ret: IteratorResult<T> = iterable.iteratorResults[cur];
        cur++;
        return ret;
      }
    }
  }

  private iterator: Iterator<T>;
  private iteratorResults: Array<IteratorResult<T>>;

  constructor(iterable: Iterable<T>) {
    this.iterator = iterable[Symbol.iterator]();
    this.iteratorResults = [];
  }

}

The idea is to use the passed-in iterable solely to obtain one iterator, storing the results in an array during processing. The iterators from ReplayableIterable will only consult the actual iterator if they deplete the array contents. This allows for lazy copying of the iterator - beneficial when dealing with large or infinite iterables to avoid unnecessary memory and time consumption.

Finally, provide a function to convert any Iterable into a MultipleIterable, either by returning the unaltered parameter (if it's already a

MultipleIterable</code) or by constructing a <code>ReplayableIterable
based on it:

export function toMultipleIterable<T>(iterable: Iterable<T>): MultipleIterable<T> {
  return isMultableIterable(iterable) ? iterable : new ReplayableIterable(iterable);
}

Hopefully, this explanation proves helpful. Best of luck!

Answer №2

Perhaps taking this approach would be beneficial:

type FIterable<T> = (() => Iterable<T>) | Iterable<T>;
export function* run<T = any>(...its: FIterable<T>[]) {
    while (true) {
        for (const it of its) {
            if (typeof it === 'function') {
                yield* it(); // although this throws a type error, it still compiles :)
            } else {
                yield* it;
            }
        }
    }
}

// Example:
export const map: Map<number, number> = new Map([[1, 2], [3, 4]]);
const a = run(() => map.keys(), [5, 6, 7], new Set([8, 9]));
for (const _ of Array(10)) {
    console.log(a.next());
};

When working with JS/TS generators, one can iterate over generators using yield*. The issue with iterating through a single way iterable can be resolved using functions, rather than creating another iterable.

In this particular example, only the map cannot be repeated.

Here is a snippet from my tsconfig.json file:

{
    "compilerOptions": {
        "module": "UMD",
        "target": "es5",
        "lib": [
            "es2015",
            "es2015.iterable",
            "dom"
        ]
    },
    "files": [
        "app.ts"
    ]
}

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

Move your cursor over a specific element to trigger an effect on another element that is not directly next to or related to it

In my current project, which is built with Angular, I am looking for a way to achieve a specific effect without using jQuery. Specifically, I would like the text inside a div element with the class title to have underline styling when hovering over an im ...

When attempting to open an Angular modal window that contains a Radio Button group, an error may occur with the message "ExpressionChanged

I am brand new to Angular and have been trying to grasp the concept of lifecycle hooks, but it seems like I'm missing something. In my current project, there is a Radio Button Group nested inside a modal window. This modal is triggered by a button cl ...

Experience the power of TypeScript in a serverless environment as you transform your code from

I have some JavaScript code that needs to be converted to TypeScript. Currently, I have two files: API_Responses.js const Responses = { _200(data = {}) { return { statusCode: 200, body: JSON.stringify(data), }; } }; module.export ...

Calculate the minimum, maximum, and average values within an array containing nested elements

I want to calculate the min, max, and average values for nested data that already have these values precalculated. Essentially, I'm looking for the average of averages, min of min, and max of max. I have a large dataset that includes the min, max, an ...

When working on one of my subdomains, I require the ability to distribute cookies between the two

Currently, I am involved in a project that utilizes a sub-domain under a parent domain called "my-project.parent.com", which unfortunately I do not have access to. Additionally, there is another project operating under the main domain "parent.com" with t ...

Using Webpack 4 and React Router, when trying to navigate to a sub path,

I'm currently working on setting up a router for my page, but I've encountered a problem. import * as React from 'react'; import {Route, Router, Switch, Redirect} from 'react-router-dom'; import { createBrowserHistory } from ...

Typescript Event Handling in React Select

I am facing an issue with my handleSelect function: const handlerSelectBank = (event: React.ChangeEvent<{ value: unknown }>) => { setState({ ...state, buttonDisabled: false, selectedBank: event }); }; Upon execution, I encountered ...

The Art of Validating Configurations Using io-ts

I have recently switched to using io-ts instead of runtypes in a fresh project. In terms of configuration validation, my approach involves creating an object that specifies the types for each part of the config; const configTypeMap = { jwtSecret: t.str ...

What could be causing the problem between typescript and Prisma ORM as I attempt to locate a distinct item?

I'm encountering a problem with using the prisma .findUnique() function. Even though my code doesn't display any compilation errors, attempting to access a product page triggers a runtime error. PrismaClientValidationError: Invalid `prisma.produ ...

Is there a way to set the initial value of an input's ngmodel variable using only HTML?

Is there a way to assign an initial value of "1" to the variable named "some" using ngModel during initialization? *Update: I am specifically interested in how to achieve this using HTML only component.html : <input type="text" value="1" name="some" ...

Reacting to each change event, Angular dynamically loads new autocomplete options

I am facing an issue with my form where users need to select a company using mat-select-search. Upon selection, an API call is made with the selected company ID to fetch users from that company for the autocomplete feature in recipient fields. The process ...

ngx-datatable Retrieve the most recent clicked row using multi-click mode

Currently, I am attempting to retrieve the most recent 'clicked' row from ngx-datatable. Here is what I have in my code: <ngx-datatable [rows]="rows" [selected]="selected" [selectionType]="'multiClick'" (select)='on ...

Dividing component files using TypeScript

Our team has embarked on a new project using Vue 3 for the front end. We have opted to incorporate TypeScript and are interested in implementing a file-separation approach. While researching, we came across this article in the documentation which provides ...

Leveraging a sophisticated .d.ts file known as chrome-app.d.ts

Understanding and using .d.ts files can be straightforward for most, like jQuery.d.ts. However, I recently encountered chrome-app.d.ts and found it a bit puzzling on how to import this typings file. In chrome-app.d.ts, there are various module definitions ...

Troubles with compiling Typescript arise when employing ESM

I'm currently working on a Typescript UI project that has multiple issues that I could really use some help with: Encountering 'xyz' property does not exist error src/index.ts:6:13 - error TS2339: Property 'xyz' does not exist o ...

Looking to create universal React component wrappers?

I am working with a set of functional components that share a common set of properties, for example: const A = ({ x, y, z }) = {...} const B = ({ x, y, z }) = {...} For these components, I have predefined configurations: const styles { A: { ty ...

In Angular 11, the error message "Type 'Object' cannot be assigned to type 'NgIterable<any> | null | undefined'" is appearing

Struggling to grasp the concepts of Angular and TypeScript for the first time, particularly puzzled by why this code snippet is not considered valid! http.service.ts export class HttpService { constructor(private http: HttpClient) { } getBeer() { ...

Transfer the unique field name to a universal assistant

Consider the code snippet provided in this playground: class A { private z = 0 } type K = "z" type ValidKeys = A[K] extends any ? K : never The type ValidKeys compiles correctly and matches K only when K represents a subset of keys from A. It ...

Flag is activated to retrieve the data from the @Input source

@Input() config= []; flag = false; I need to change the flag to true only when I receive data in the config from the @input. Where should I do this? The data in the config is delayed and I am unable to access it in ngOnInit but can get it in ngOnChanges. ...

The initial processing step for the export namespace is to utilize the `@babel/plugin-proposal-export-namespace-from` plugin. Previous attempts to resolve this issue have been

I encountered a problem with my application and found two related questions on the same topic. However, due to a lack of reputation, I am unable to comment or ask questions there. That's why I'm reaching out here... Recently, I integrated Redux ...