iterate an array with conditions such that certain elements can be revisited multiple time

Viewed 48

For the input below:

{
    "vals": [
      {
        "month": "Jan 2022",
        "value": 0,
        "amount": -200,
        "date": "01-02-2022"
      },
      {
        "month": "Feb 2022",
        "value": 0,
        "amount": -200,
        "date": "28-02-2022"
      }
    ],
    "items": [
        {
            "date": "01-02-2022",
            "amount": -200
        },
        {
            "date": "04-02-2022",
            "amount": 100
        },
        {
            "date": "28-02-2022",
            "amount": -200
        },
        {
            "date": "10-03-2022"
            "amount": 250
        },
        {
            "date": "12-03-2022"
            "amount": 50
        }
    ]   
}

I want the below output

{
    "vals": [
      {
        "month": "Jan 2022",
        "value": 37
      },
      {
        "month": "Feb 2022",
        "value": 12
      }
    ]
}

for month "Jan 2022" of vals, we have debit amount on 01-02-2022. So need go through the items arrays from 01-02-2022 till the total sum of amount matches with vals "Jan 2022" amount value and we need to the no of days between i.e. 01-02-2022 and 10-03-2022

for month "Feb 2022" instead of going through items array again from first we should continue where we left for Jan 2022.

    {
        "date": "10-03-2022"
        "amount": 250
    }

of this item 100 is considered for Jan 2022 and 150 is considered for Feb 2022.

so I need to loop items array such that I should continue where I left for an element of vals array.

    {
        "date": "10-03-2022"
        "amount": 250
    }

this element of items array should be considered twice once for Jan 2022 and second for Feb 2022

But

    {
        "date": "04-02-2022"
        "amount": 100
    }

should considered only once as the 100 will not completely serve Jan 2022 amount i.e. -200

I hope I made it clear.

1 Answers

Unfortunately there are some aspects of the problem which I do not understand, but the following should help as it addresses the key need:

I need to loop items array such that I should continue where I left for an element of vals array.

The key to doing this cleanly is to define a helper function which allows us to keep track of where to resume the search:

# input: [ $index, $array ] where $index is null or >= -1
# output: the first index, $i, in the array for which
#   $i > $index and $array[$i].amount == $v , or else null
def value($v): 
   . as [ $index, $array ]
   | if $index == null then null
     else first( range($index + 1; $array|length) | select($array[.].amount == $v) ) // null
     end ;

For computing the number of days between two dates, another helper function will be useful:

# Date format: dd-mm-year
def days($finish; $start):
  [$finish, $start]
  | map(strptime("%d-%m-%Y") | mktime) # seconds
  | (.[0] - .[1]) / 86400 + 0.5 | trunc ;

Now we have simply to build up the result by visiting the elements of .vals:

.items as $items
| reduce .vals[] as $val ({ix: 0, date: .vals[0].date, array: []};
    ([.ix, $items] | value($val.amount)) as $ix
    | .ix = $ix
    | if $ix == null
      then .
      else .date as $date
      | .array += [$val | {month, value: days($items[$ix].date; $date) }] 
      | .date = $date
      end )

As already stated, this is probably not exactly what you want, but should provide a suitable framework for you.

Related