Generating a Structure for Aggregation

When I had a moment to think about this, I ran back home to perl and worked this out:

use Modern::Perl;

use Moose::Autobox;
use JSON;

my $encoder = JSON->new->pretty;

my $input = [ { 4 => 10 }, { 7 => 9 }, { 90 => 7 }, { 1 => 8 } ];

my $stack = [];

foreach my $item ( reverse @{$input} ) {

  while ( my ( $key, $value ) = each %{$item} ) {
    my $rec = {
      '$cond' => [
        { '$eq' => [ '$user_id', int($key) ] },
        $value
      ]
    };

    if ( $stack->length == 0 ) {
      $rec->{'$cond'}->push( 0 );
    } else {
      my $last = $stack->pop;
      $rec->{'$cond'}->push( $last );
    }

    $stack->push( $rec );
  }

}

say $encoder->encode( $stack->[0] );

So the process was blindingly simple.

  1. Go through each item in the array and get the key and value for the entry

  2. Create a new “document” that has in array argument to the “$cond” key just two of required three entries. These are the values assigned to test the “$user_id” and the returned “weight” value.

  3. Test the length of the outside variable for stack, and if it was empty (first time through) then push the value of 0 as seen in the last nested element to the end of the “$cond” key in the document.

  4. If there was something already there (length > 0) then take that value and push it as the third value in the “$cond” key for the document.

  5. Put that document back as the value of stack and repeat for the next item

So there are a few things in the listing such as reversing the order of the input, which isn’t required but produces a natural order in the nested output. Also, my choice for that outside “stack” was an array because the test operators seemed simple. But it really is just a singular value that keeps getting re-used, augmented and replaced.

Also the JSON printing is just there to show the output. All that is really wanted is the resulting value of stack to be merged into the structure.

Then I converted the logic to ruby, as was the language used by the OP from where I got the inspiration for how to generate this nested structure:

require 'json'

input = [ { 4 => 10 }, { 7 => 9 }, { 90 => 7 }, { 1 => 8 } ]

stack = []

input.reverse_each {|item|

  item.each {|key,value|
    rec = {
      '$cond' => [
        { '$eq' => [ '$user_id', key ] },
        value
      ]
    }

    if ( stack.length == 0 )
      rec['$cond'].push( 0 )
    else
      last = stack.pop
      rec['$cond'].push( last )
    end

    stack.push( rec )
  }

}

puts JSON.pretty_generate(stack[0])

And then eventually into the final form to generate the pipeline that the OP wanted:

require 'json'

userWeights = [ { 4 => 10 }, { 7 => 9 }, { 90 => 7}, { 1 => 8 } ]

stack = []

userWeights.reverse_each {|item|

  item.each {|key,value|
    rec = {
      '$cond' => [
        { '$eq' => [ '$user_id', key ] },
        value
      ]
    }

    if ( stack.length == 0 )
      rec['$cond'].push( 0 )
    else
      last = stack.pop
      rec['$cond'].push( last )
    end

    stack.push( rec )
  }

}

pipeline = [
    { '$project' => {
        'user_id' => 1,
        'content' => 1,
        'date' => 1,
        'weight' => stack[0]
    }},
    { '$sort' => { 'weight' => -1, 'date' => -1 } }
]

puts JSON.pretty_generate( pipeline )

So that was a way to generate a structure to be passed into aggregate in order to apply “weights” that are specific to a user_id and sort the results in the collection.

Leave a Comment