Dr. Mza
Dr. Mza

Reputation: 171

Calculate weight and skip a specific element Mongodb - Aggregation Framework

I have a collection with array elements containing A,B or C values. I have to calculate a weight of each element value. The logic of this weight is sample :

This is how my collection looking like :

{
    "_id" : ObjectId("5a535c48a4d86ed94a7e8618"),
    "myArray" : [ 
        {
            "value" : "C"  
        }, 
        {
            "value" : "A   
        }, 
        {
            "value" : "C" 
        }, 
        {
            "value" : "B"  
        },
        {
            "value" : "A"  
        },
        {
            "value" : "A" 
        }
    ]
}



{
    "_id" : ObjectId("5a535c48a4d86ed94a7e8619"),
    "myArray" : [ 
        {
            "value" : "A"  
        }, 
        {
            "value" : "C"  
        }, 
        {
            "value" : "B" 
        }, 
        { 
            "value" : "C" 
        },
        { 
            "value" : "C" 
        }
    ]
}

I did some aggregation to make it happen but I can here just to affect 1 to the last (different to C) and (if the last is C I give 1 to the last one -1) so I have to fixe the case of having C in the last and last-1 and last -2 .. last-n so I have to fix it to affect 1 to the last one different to C

db.col.aggregate([{'$addFields':{
  'myArray_temp':{
    '$switch':{
      'branches':[{
        'case':{'$and':[{'$gt':[{'$size':'$myArray'},1]},{'$eq':[{'$arrayElemAt':['$myArray.value',-1]},'C']}]},
        'then':{'$concatArrays':[
            {'$map':{
              'input':{'$slice':['$myArray',{'$subtract':[{'$size':'$myArray'},2]}]},
              'as':'val',
              'in':{'value':'$$val.value','weight':0  }
            }},
            [{'value':{'$arrayElemAt':['$myArray.value',-2]},'weight':1}],
            [{'value':{'$arrayElemAt':['$myArray.value',-1]},'weight':0}]
          ]}
       },
       {
        'case':{'$eq':[{'$size':'$myArray'},1]},
        'then':{'$concatArrays':[
          [{'value':{'$arrayElemAt':['$myArray.value',0]},'weight':1}]
        ]}
       },
       {
        'case':{'$and':[{'$gt':[{'$size':'$myArray'},1]},{'$ne':[{'$arrayElemAt':['$myArray.value',-1]},'C']}]},
        'then':{'$concatArrays':[
            {'$map':{
              'input':{'$slice':['$myArray',{'$subtract':[{'$size':'$myArray'},1]}]},
              'as':'val',
              'in':{'value':'$$val.value','weight':0  }
            }},
            [{'value':{'$arrayElemAt':['$myArray.value',-1]},'weight':1}]
          ]}
       }
       ],
    'default':{'$concatArrays':[
          [{'value':{'$arrayElemAt':['$myArray.value',0]},'weight':1}]
        ]}

       }
  }
}}])

The results should be :

{
    "_id" : ObjectId("5a535c48a4d86ed94a7e8618"),
    "myArray" : [ 
        {
            "value" : "C",
            "weight": 0 
        }, 
        {
            "value" : "A" ,
            "weight": 0  
        }, 
        {
            "value" : "C",
            "weight": 0  
        }, 
        {
            "value" : "B" ,
            "weight": 0  
        },
        {
            "value" : "A",
            "weight": 0   
        },
        {
            "value" : "A",
            "weight": 1  
        }
    ]
}

{
    "_id" : ObjectId("5a535c48a4d86ed94a7e8619"),
    "total" : 4.5,
    "myArray" : [ 
        {
            "value" : "A",
            "weight": 0  
        },
        {
            "value" : "C",
            "weight": 0  
        }, 
        {
            "value" : "B" ,
            "weight": 1 // here we give 1 to the last differente to C 
        }, 
        { 
            "value" : "C" ,
            "weight": 0  // my code affect 1 here cause it find C in the last and affect to the last-1 element.
        },
        { 
            "value" : "C" ,
            "weight": 0 
        }
    ]
}

We have to skip all the last (C) elements and give the 1 weight to the last not-C element.

Thank you in advance!

Upvotes: 1

Views: 383

Answers (1)

mickl
mickl

Reputation: 49975

Longest aggregation in my career but seems to be working:

db.collection.aggregate([
    { 
        $addFields : {
            size: { $size: "$myArray" },
            reversed: { 
                $reverseArray: "$myArray"  
            }
        }
    },
    {
        $addFields: {
            otherThanC: {
                $filter: {
                    input: "$reversed",
                    as: "item",
                    cond: { $ne: [ "$$item.value", "C" ] }
                }
            }
        }
    },
    {
        $addFields: {
            firstOtherThanCIndex : { 
                $indexOfArray: [ "$reversed", { $arrayElemAt: [ "$otherThanC", 0 ] } ] 
            }
        }
    },
    {
        $unwind: {
            path: "$reversed",
            includeArrayIndex: "arrayIndex"
        }
    },
    {
        $addFields: {
            weight: {
                $switch: {
                  branches: [
                     { case: { $eq: [ "$size", 1 ] }, then: 1 },
                     { case: { $and: [ { $eq: [ "$arrayIndex", 0 ] }, { $eq: [ { $size: "$otherThanC" }, 0 ] } ] } , then: 1},
                     { case: { $eq: [ "$arrayIndex", "$firstOtherThanCIndex" ] }, then: 1 }
                  ],
                  default: 0
                }
            }
        }
    },
    {
        $group: {
            _id: "$_id",
            myArrayReversed: { 
                $push: {
                    value: "$reversed.value",
                    weight: "$weight"
                } 
            }
        }
    },
    {
        $project: {
            _id: 1,
            myArray: { $reverseArray: "$myArrayReversed" }
        }
    }
])

Brief description of each pipeline stage:

  • We need to add two extra fields: $size of and array and second array with reversed items
  • Second and third steps are to find first (last) item not equal to C using $filter and $arrayElemAt which returns first matching index
  • Then we can $unwind our reversed array using special syntax which adds index of array when unwinding
  • That is the moment when we can calculate weight using $switch - simply setting 1 if array has one element or indexes are matching and zero otherwise
  • Then we just need to reshape the data: grouping back by _id and reversing the array

Upvotes: 1

Related Questions