Is there a way to implement depth-first retrieval in rxjs using the expand method?

Currently, I am engaged in a project utilizing Angular 7, Typescript, and RxJS 6.3.3. In this project, I am working with RxJS Observables and relevant operators to handle hierarchical collections of objects obtained from an http server that interfaces with a database.

In order to create a depth-first search algorithm, I initially attempted to utilize the expand operator while employing concatMap to maintain order. Unfortunately, this approach proved ineffective.

You can find a simplified example here: https://stackblitz.com/edit/rxjs-vf4zem

The console output generated by this example is as follows:

dfs no delay: [1,8,9,22,23,24,2,4,7,3]
dfs with delay: [1,2,3,4,7,8,9,22,23,24]

(The sequence of the second output line may vary depending on the extent of the simulated data retrieval delay meant to mimic fetching data from the http server.)

With reference to the dataset provided in the example, my intention is to consistently obtain the first line of output: a representation following a depth-first order. The crucial function featured in the example is:

const dfs = (getter: Getter) => rootIds.pipe(
  concatMap(ids => from(ids)),
  expand(id =>
    getter(id).pipe(
      concatMap(children => from(children))
    )
  ),
  toArray()
);

Is there an effective method to enforce depth-first processing? Can the use of expand guarantee such an outcome, or is it simply an inadequate strategy for transforming hierarchical data into a flattened, depth-first array?

Answer №1

I believe the question raises an interesting point about the need for an additional data structure for parallel fetch operations to compose results efficiently.

Although a recursive reconstruction using expand is intriguing, I have opted for a sequential approach:

sequentialDFS(getChildren: Getter, ids: number[]): Observable<number[]> {
  return of(ids).pipe(
    expand(([head, ...rest]) =>
      // working with ids in left-to-right order
      getChildren(head).pipe(
        switchMap(subChildren => {
          const acc = [...subChildren, ...rest ];
          return acc.length
            ? of(acc)
            : EMPTY;
        })
      )
    ),

    // collecting the heads {{{
    map(([head])=>head),
    toArray()
    // }}}
  );
}

* The getChildren method has been slightly modified to return Observable<number[]> instead of Observable<number>.

https://stackblitz.com/edit/rxjs-rf8d1j

This solution does not address parallel fetching directly but rather presents a sequential alternative that I found enjoyable to implement.

Answer №2

After experimenting a bit and manually implementing recursion instead of relying on the expand method, I developed the following solution (also updated in the stackblitz link provided above):

class Test {
  // This approach does not maintain depth-first order; it may vary based on timing.
  brokenDFS(getChildren: Getter, ids: number[]): Observable<number[]> {
    return of(ids).pipe(
      concatMap(ids => from(ids)),
      expand(id => getChildren(id)),
      toArray()
    );
  }

  workingDFS(getChildren: Getter, ids: number[]): Observable<number[]> {
    return from(ids).pipe(
      concatMap(id => this.parentAndChildren(getChildren, id)),
      toArray()
    );
  }

  private parentAndChildren(getChildren: Getter, id: number): Observable<number> {
    return of(id).pipe(
      concat(
        getChildren(id).pipe(
          map(child => this.parentAndChildren(getChildren, child)),
          concatAll()
        )
      ),
    );
  }

}

const getter = getChildrenWithDelay;
const rootIds = [1, 17, 20];
const test = new Test();
test.brokenDFS(getter, rootIds).subscribe(data => console.log(`Broken: ${data}`));
test.workingDFS(getter, rootIds).subscribe(data => console.log(`Working: ${data}`));

The output results (noting that the "Broken" output can vary due to timing):

Broken: 1,17,20,21,2,6,18,7,13,14,3,4,19,15,16,5,8,9,10,11,12
Working: 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21

(I also adjusted the tree values from the original post to ensure the correct DFS is an array ranging from 1 to 21).

Although the workingDFS function produces the desired result, it operates significantly slower than brokenDFS, as each HTTP request must wait for preceding ones to finish. On the other hand, the brokenDFS version allows multiple requests to run concurrently (albeit not in the correct order).

Considering potential alternatives using RxJS, I might need to modify my methods by including sorting information alongside the objects of interest, executing many or all requests simultaneously, and subsequently combining everything in the correct sequence.

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

What purpose does the # symbol serve in Angular when interpolating values?

Here is an example provided from the following link: https://angular.io/guide/lifecycle-hooks export class PeekABoo implements OnInit { constructor(private logger: LoggerService) { } // Implementing OnInit's `ngOnInit` method ngOnInit() { this ...

What are the various methods of specifying function types in TypeScript?

I've noticed that in Typescript you can easily declare a function type using the declare keyword. For example: declare function test1(name: string): true const t1 = test1('t') // true Alternatively, you can also use the arrow notation: c ...

Connecting Multiple Relationships with Many-To-Many Matches

My database consists of the following entities: @Entity class User { @ManyToMany(type => Group) @JoinTable() groups: Group[]; } @Entity class MediaObject { @ManyToMany(type => Group) @JoinTable() groups: Group[]; } @Entity ...

Resolving the name error: Importing definition files from node_modules/@types in Angular

After acquiring this set of definitions from a node_modules/@types file, I encountered an issue trying to import it into my js file. I went ahead and executed npm install @types/p5 before including it in my tsconfig.json as follows: "types": [ "n ...

Utilizing headers in apollo watchQuery results in undefined data

Utilizing the apollo-angular package for querying our backend using graphQL has been smooth sailing. However, things take a turn when I attempt to include headers in the query. I am specifically adding a custom header to extract flattened data from the ba ...

Callback for dispatching a union type

I am currently in the process of developing a versatile function that will be used for creating callback actions. However, I am facing some uncertainty on how to handle union types in this particular scenario. The function is designed to take a type as inp ...

What causes BehaviorSubjects in Angular RXJS to emit multiple values?

click here to see the image descriptionUsing BehaviorSubject for sharing data between components in my app has led to a performance problem caused by multiple emissions of the same value. For example, when I make an HTTP call to fetch a team object from th ...

What is the best way to incorporate node_module during the iOS build process in Ionic 2?

Looking to implement an autosize module for automatic resizing of an ion-textarea. Module: Following the installation instructions, I tested it in the browser (ionic serve) and on my iPhone (ionic build ios => run with xcode). Browser: The module wor ...

Issues with Angular Form Builder's Form Control not updating when invalid, causing errors with ngIf statements

I am encountering an issue where the error division fails to appear when the validator is invalid. The problem seems to be originating from this line: <div class = "danger" *ngIf="firstName?.invalid && (firstName?.dirty || firstName?.touched) ...

The listener for @ok is not being activated when using jest-test-utils with b-modal in vue-bootstrap

I have implemented the vue-bootstrap b-modal feature with the @ok="save" hook Here is a snippet of mycomponent.vue: <template> <div> <b-button @click="add">open modal</b-button> <b-modal static lazy id="modal-detail" ...

The element is implicitly given an 'any' type due to the fact that a string expression cannot be used to index the following type: { "1" : { "key": string}; "2" : { "key": string};}

I have a JSON file containing IDs as keys like this: "1" : { "key": "value"}, "2" : { "key": "value"}, In my class, I import this JSON file as a data object and then use the ID passed to a method ...

Angular Material Popup - Interactive Map from AGM

I am in the process of developing a material dialog to collect user location information using AGM (angular google maps). I have successfully implemented a map on my main page, but when the dialog is opened, it only shows a blank space instead of the map. ...

Unspecified OrbitControls Compatibility Issue between Angular2 and Three.js

I'm running into issues trying to set up OrbitControls in my Angular2 project. I managed to display a scene with a box, but I'm struggling to move the camera. Upon investigation, I found that my OrbitComponent, responsible for defining orbit con ...

Issue with updating state in child component preventing addition to state

Recently, I made the switch to TypeScript in my NextJS project using Create T3 App. One of the components in my app involves updating the state after a Prisma mutation is performed. I attempted to pass the setItems (which was initialized with useState) to ...

Angular child component displaying of table data is not looping properly

Currently, I am using Angular 13 along with Bootstrap 3 to develop an application that consists of two main components: form-component (dedicated to inputting user details) and list-component (designed to showcase all data in a table format). Within the HT ...

Having trouble retrieving XML response using angular 8 HttpClient

I'm encountering an issue where the server is sending me an XML response, but I'm unable to retrieve it using HttpClient in Angular 8. Oddly enough, it works fine in Postman. I've attempted various solutions such as setting the responseType ...

Utilize @ngrx/store to merge various reducers from feature modules

I'm currently immersed in developing a test app to delve deeper into the @ngrx/store framework. Within this project, I've set up a module called TrainingModule that aims to store various exercises and related information. The code is functioning ...

Change the value of the material slide toggle according to the user's response to the JavaScript 'confirm' dialogue

I am currently working on implementing an Angular Material Slide Toggle feature. I want to display a confirmation message if the user tries to switch the toggle from true to false, ensuring they really intend to do this. If the user chooses to cancel, I&ap ...

What could be causing my NextJS application to not recognize the _document.tsx file?

Seeking assistance in understanding why my _document.tsx is not loading properly within my nextJS application. My Attempts So Far I have been diligently following the NextJS documentation for creating a custom _document.js. Despite my efforts, I am unable ...

Launching Nest.js application from Visual Studio Code

Currently experimenting with a new framework called which seems promising as it integrates TypeScript into Node, similar to Angular. I'm using the starter template from https://github.com/kamilmysliwiec/nest-typescript-starter. I can start it withou ...