0

Is there a way to get the index of the last matching element in MongoDB? I'm speaking of an equivalent to JavaScript's Array.prototype.lastIndexOf. I know about $indexOfArray, but I'm looking for the last index, not the first one.

My use case

I've got a collection whose schema looks like this:

{
  // ...
  images: [
    {
      identifier: String,
      imageId: ObjectId
    }
  ]
  // ...
}

I have a record whose got duplicated elements:

{
  // ...
  images: [
    {
      identifier: '0',
      imageId: ObjectId('objectId0')
    },
    {
      identifier: '1',
      imageId: ObjectId('objectId1')
    },
    {
      identifier: '2',
      imageId: ObjectId('objectId2')
    },
    {
      identifier: '0', // <-- duplicated!
      imageId: ObjectId('objectId3')
    },
    // many more elements...
    {
      identifier: '0', // last occurence
      imageId: ObjectId('objectIdN+0')
    },
    {
      identifier: '1',
      imageId: ObjectId('objectIdN+1')
    },
    {
      identifier: '2',
      imageId: ObjectId('objectIdN+2')
    }
  ]
}

I want to remove all the elements before the last occurence of images.identifier: '0'.

Is it possible at all without the usage of JavaScript?

Amit Beckenstein
  • 1,220
  • 12
  • 20
  • You can use an aggregation pipeline to get all the duplicate IDS except the last one using $limit & $skip. Once you have the IDs you can delete them one using ObjectID. – Hiren Nov 02 '20 at 14:19
  • @Hiren how exactly do I get all the number of elements up until the last duplicate, though? I will need to pass a very specific value to `$limit` – Amit Beckenstein Nov 02 '20 at 14:26
  • Here are some ideas from which you can think further: [Find duplicate urls in mongodb](https://stackoverflow.com/questions/61062508/find-duplicate-urls-in-mongodb/61072540#61072540). – prasad_ Nov 02 '20 at 14:29
  • Do you like to remove "all behind `images.identifier: '0'`" or "all duplicated identifier"? – Wernfried Domscheit Nov 02 '20 at 15:02

1 Answers1

1

Try this one:

db.collection.aggregate([
   {
      $set: {
         images: {
            $reduce: {
               input: "$images",
               initialValue: [],
               in: {
                  $cond: {
                     if: { $eq: ["$$this.identifier", "0"] },
                     then: ["$$this"], // reset array to current value
                     else: { $concatArrays: ["$$value", ["$$this"]] } // append values
                  }
               }
            }
         }
      }
   }
])
Wernfried Domscheit
  • 54,457
  • 9
  • 76
  • 110
  • Won't this result in duplicates as well? – Amit Beckenstein Nov 02 '20 at 15:03
  • No it does not. It looks for `images.identifier: '0'` and everything what appeared before in the array is removed. That's how I understood your requirement. – Wernfried Domscheit Nov 02 '20 at 15:05
  • It actually works! I don't see how or why? Can you maybe be more verbose? Sorry for the hassle – Amit Beckenstein Nov 02 '20 at 15:07
  • @Wernfriend Docmscheit nevermind, I think I understand. Cool, thanks! – Amit Beckenstein Nov 02 '20 at 15:19
  • Well then `$reduce` is not that easy to understand in my opinion: It goes over each elements of the array. If `identifier: '0'` is found then array is reset to current element. Otherwise the current element is appended to the array. – Wernfried Domscheit Nov 02 '20 at 16:34
  • Well the `$reduce` is not that easy to understand in my opinion: It goes over each elements of the array. If `identifier: '0'` is found then array is reset to current element. Otherwise the current element is appended to the array. Note, the order of elements matters! – Wernfried Domscheit Nov 02 '20 at 16:43
  • yeah I know what `$reduce` (and reduce functions in general) do. I just didn't get how the duplicated elements were disposed, but it's pretty clear after all in the `$cond`. It's just that the comments were confusing at first. – Amit Beckenstein Nov 02 '20 at 18:20