Search code examples
dataweavemule4

How to separate an array in dynamics arrays to not exceed the value indicate with DWL


I try to explain you, I have an Array of Objects:

Input:

[
    { "number": "000001", "price": 14.99, "qty": 18, "line": "3" },
    { "number": "000003", "price": 24.0, "qty": 18, "line": "3" },
    { "number": "000004", "price": 18.0, "qty": 18, "line": "3" }
]

This payload is an example. The array can have more or less "qty" or objects with a similar schema. I am trying to separate the objects dynamically into a new array of objects. The validations are:

  1. I need to append in a new array all the objects that do not pass the sum > 700. This operation is (price * qty).

  2. If we follow the above example:

    • The first object will be appended as it is because the sum of (14.99 * 18) = 269.82 does not exceed 700.
    • The next object "000003" has a sum (24 * 18) = 432. If we add the first and the second, it gives a total of 701.82. In this case, we need to reduce the qty by 1, resulting in:
    [
        { "number": "000001", "price": 14.99, "qty": 18, "line": "3" },
        { "number": "000003", "price": 24.0, "qty": 17, "line": "3" }
    ]
    
  3. The next objects inside the array follow the same logic:

    [
        { "number": "000003", "price": 24.0, "qty": 1, "line": "3" },
        { "number": "000004", "price": 18.0, "qty": 18, "line": "3" }
    ]
    

Output:

[
    {
        "object": 1,
        "total": 677.82,
        "items": [
            { "number": "000001", "price": 14.99, "qty": 18, "line": "3" },
            { "number": "000003", "price": 24.0, "qty": 17, "line": "3" }
        ]
    },
    {
        "object": 2,
        "total": 348.00,
        "items": [
            { "number": "000003", "price": 24.0, "qty": 1, "line": "3" },
            { "number": "000004", "price": 18.0, "qty": 18, "line": "3" }
        ]
    }
]

Summary of the result:

  • "object" is an iterator or index.
  • "total" is the sum (price * qty) for all elements in "items".
  • "items" is the result I explained before.

Note: This is an example. If I increase maxTotal to 1100, the result will be the same because it does not exceed maxTotal. But if I reduce maxTotal to 200, the final result will be divided into 5 similar objects.

This is my DWL code, and I don't know how to finish it. I have the idea, but I can't complete it:

%dw 2.0
import * from dw::core::Arrays
output application/json

var inputArray = [
    { "number": "000001", "price": 14.99, "qty": 18, "line": "3" },
    { "number": "000003", "price": 24.0, "qty": 18, "line": "3" },
    { "number": "000004", "price": 18.0, "qty": 18, "line": "3" }
]

var maxTotal = 700

var listOfItems = flatten(inputArray map (itm, idx) -> do {
    var result = 1 to itm.qty as Number map (i) -> ({
        "number": itm.number,
        "price": itm.price,
        "qty": 1,
        "line": itm.line
    })
    ---
    result
})

var divideTo = ((sum(listOfItems.price) / maxTotal) as String {format: "0", roundMode: "UP"}) as Number

---
1 to divideTo map (itm, idx) -> do {
    var total = 0
    var items = {}
    ---
    {
        object: (idx + 1),
        total: total, 
        items: items
    }
}

Explanation of the code:

  • The variable "listOfItems" divides the objects by qty, meaning [ "number": "000001", "number": "000001"... n ].
  • In the variable "divideTo", I get the value of the total objects in the new array.
  • Finally, I'm stopping at the final code.

If you have a better idea to complete the code, I would appreciate your help.


Solution

  • I have came up with a slightly different approach which is explained below. I am using the term "group" to denote each item in the expected output array (as they are basically a group of input items)

    1. Iterate through the input payload using reduce.
    2. For each item, attempt to add it to the current group in runningResults.
    3. If the item cannot fully fit, split it, add the eligible part, and start a new group for the remaining part.
    4. Finally, map through the results to add the object index for each group.
    %dw 2.0
    import update from dw::util::Values
    output application/json
    
    // To add the current item to the interim results
    fun addToResults(runningResults, currentItem) = do {
        var currentItemTotal = itemTotal(currentItem)
        var lastGroupTotal = runningResults[-1].total
        // Split the item depending on the remaining space in the last group of the runningResults
        var splittedItem = splitItem(currentItem, maxTotal - lastGroupTotal)
        ---
        (
            // Add the eligible portion of the item to the last group if there is quantity left
            if(splittedItem.eligible.qty > 0) 
                runningResults updateLastWithItem splittedItem.eligible
            else runningResults
        )
        then ((
            // If there is any remaining portion of the item, start a new group
            if(splittedItem.remaining.qty > 0)
                ($ << defaultGroup) addToResults splittedItem.remaining
            else $
        ))
    }
    
    fun itemTotal(item) = item.qty * item.price
    
    // Splits an item into an eligible and a remaining portion based on the remainingTotal in the last group of runningResults
    fun splitItem(item, remainingTotal) = do {
        var eligibleQty = 
            if(remainingTotal >= maxTotal and item.price > maxTotal) 1 // to handle the case when price of item can exceed maximum total allowed for group
            else min([floor(remainingTotal / item.price), item.qty]) // used min function to avoid exceeding the maximum available quantity. (which can be the case for the first object in result)
        ---
        {
            eligible: item update "qty" with eligibleQty,
            remaining: item update "qty" with $ - eligibleQty
        }
    }
    
    // Updates the last group with a new item, updating the total and adding the item in items array
    fun updateLastWithItem(runningResults, itemToAdd) = 
        runningResults update (sizeOf(runningResults) - 1) with (
            $ update {
                case .total -> ($ + itemTotal(itemToAdd))
                case .items -> $ << itemToAdd
            }
        )
    
    // Default structure for each group in the result
    var defaultGroup = {total: 0, items: []}
    var maxTotal = 700
    ---
    payload reduce ((item, results = [defaultGroup]) -> 
        results addToResults item
    ) map {
        // Add an object index to each group
        object: $$ + 1, 
        ($)
    }
    
    

    Note

    1. It does not provide the "minimum size possible" output. Which means if it is possible to fit everything in array of size 2 then it might produce output with array of size 3 or 4. It will be more complicated to produce such result, but since it was not the requirement I have kept it simple and time efficient.