Javascript - Filtering out all map elements if a map with the same type already exists in a list

Viewed 55

I have a value events of a type Map<number, List<Event>>, I am flattening this value like this:

const flatEvents = events.valueSeq().flatMap(event => event.values()).toList()

The value flatEvents looks like this after flattening:

[
    {
        "eventTime": "2022-03-18T08:04:00Z",
        "type": "deviated",
        "id": 841,
        "createdAt": "2022-03-18T08:04:43.975332Z",
        "createdBy": "47"
    },
    {
        "eventTime": "2022-03-15T11:11:38.220966Z",
        "type": "created",
        "id": 826,
        "createdAt": "2022-03-15T11:11:38.220966Z",
        "createdBy": "47"
    },
    {
        "eventTime": "2022-03-15T11:11:38.220966Z",
        "id": 827,
        "type": "created",
        "createdAt": "2022-03-15T11:11:38.220966Z",
        "createdBy": "47"
    },
    {
        "eventTime": "2022-03-15T11:11:38.220966Z",
        "type": "created",
        "id": 828,
        "createdAt": "2022-03-15T11:11:38.220966Z",
        "createdBy": "47"
    }
]

But, I would also like to filter out all the extra elements that have type = created, so that I end up with a list that has only one element where type == created:

[
    {
        "eventTime": "2022-03-18T08:04:00Z",
        "type": "deviated",
        "id": 841,
        "createdAt": "2022-03-18T08:04:43.975332Z",
        "createdBy": "47"
    },
    {
        "eventTime": "2022-03-15T11:11:38.220966Z",
        "type": "created",
        "id": 826,
        "createdAt": "2022-03-15T11:11:38.220966Z",
        "createdBy": "47"
    }
]

I have tried to do it like this:

const flatEvents = events.valueSeq()
    .flatMap(event => event.values()).toList()
    .filter((v, _, list) =>
      list.find(v => v.get('type') === 'created'))

But, that is returning the same list as before. I have also tried this:

const flatEvents = events.valueSeq()
    .flatMap(event => event.values()).toList()
    .filter((_,i,flattenedEvents) =>
      flattenedEvents.findIndex(consignmentEvent => (consignmentEvent.get('type') === 'created')) === i
    )

That filters out extra elements with the type === 'created', but also other elements, so I end up with only this:

[
    {
        "eventTime": "2022-03-18T08:04:00Z",
        "type": "deviated",
        "id": 841,
        "createdAt": "2022-03-18T08:04:43.975332Z",
        "createdBy": "47"
    },
 ]

How can I achieve this?

3 Answers

You can use filter and include a local counter variable that starts with 0, but is incremented when a "created" type is encountered. This can be used for the actual filtering:

let flatEvents = [{"eventTime": "2022-03-18T08:04:00Z","type": "deviated","id": 841,"createdAt": "2022-03-18T08:04:43.975332Z","createdBy": "47"},{"eventTime": "2022-03-15T11:11:38.220966Z","type": "created","id": 826,"createdAt": "2022-03-15T11:11:38.220966Z","createdBy": "47"},{"eventTime": "2022-03-15T11:11:38.220966Z","id": 827,"type": "created","createdAt": "2022-03-15T11:11:38.220966Z","createdBy": "47"},{"eventTime": "2022-03-15T11:11:38.220966Z","type": "created","id": 828,"createdAt": "2022-03-15T11:11:38.220966Z","createdBy": "47"}];

flatEvents = flatEvents.filter(
   (count => 
        event => event.type != "created" || !count++
   )(0)
); 

console.log(flatEvents);

You would chain this filter() call after your toList() call.

Removing duplicates only concerns events with type = 'created', therefore, the following:

const result = flatEvents.reduce((prev, curr) => {
    return prev.find(e => e.type === "created") ?
        prev :
        [...prev, curr]
}, []
)

We are reducing the array to another array (i.e. filtering but with access to prev which in this case are the events iterated over so far). For each curr event we check if there is already an event with type=== "created" in prev and return accordingly.


Or if you want it attached to your previous operations:

const flatEvents = events
    .valueSeq()
    .flatMap(event => event.values())
    .toList()
    .reduce((prev, curr) =>
        prev.find(e => e.type === "created") ?
            prev :
            [...prev, curr]
        , []
    )

Output
Only one (first) event with type = "created" is kept. However duplicates are allowed for other types:

[
  {
    eventTime: '2022-03-18T08:04:00Z',
    type: 'deviated',
    id: 841,
    createdAt: '2022-03-18T08:04:43.975332Z',
    createdBy: '47'
  },
  {
    eventTime: '2022-03-15T11:11:38.220966Z',
    type: 'created',
    id: 826,
    createdAt: '2022-03-15T11:11:38.220966Z',
    createdBy: '47'
  }
]

For O(n) plus readability you can always go safe and use a for loop:

const filterEvents = (events) => {
    let result = [];
    let foundCreatedType = false;
    for (let event of events) {
        if (event.type === "created" && !foundCreatedType) {
            result.push(event)
            foundCreatedType = true;
        } else {
            if (event.type !== "created") result.push(event)
        }
    }
    return result;
}

So after you used your toList() just apply filterEvents on the result

Related