jeudi 1 février 2024

L'empaqueteur / The Packer

Un petit sujet pour nous divertir (mais qui peut aider les utilisateur de l'OMS OneStock 😁). Il s'agit de transformer une liste de valeurs triées et successives en une liste d'intervalles qui représente cette même liste. Par exemple, la liste:

[1, 2, 4, 5, 6, 8, 10, 11, 14, 15, 16] 

sera transformée en:

[
  {
    "from": 1,
    "to": 2
  },
  {
    "from": 4,
    "to": 6
  },
  {
    "from": 8,
    "to": 8
  },
  {
    "from": 10,
    "to": 11
  },
  {
    "from": 14,
    "to": 16
  }
]

Chaque trou provoquant la création d'un nouvel intervalle. La solution est donnée par le script suivant:

%dw 2.0
output application/json
var ressources = [1, 2, 4, 5, 6, 8, 10, 11, 14, 15, 16] 
---
(if (ressources == []) [] else (ressources reduce (v, acc={
    consolidated: [],
    first: null
})->
if (acc.first==null) {
    consolidated: acc.consolidated,
    first: v,
    last: v
}
else if (acc.last+1==v) {
    consolidated: acc.consolidated,
    first: acc.first,
    last: v
}
else {
    consolidated: acc.consolidated + {"from":acc.first, "to":acc.last},
    first: v,
    last: v
})
then (acc)->acc.consolidated + {"from":acc.first, "to":acc.last})

Cet exemple (utilisé moins fréquemment que bien d'autres dans ce blog) est néanmoins très intéressant en tant que modèle pour l'écriture d'une réduction. La réduction (DataWeave "reduce") est une des fonctions les plus polyvalente de DataWeave. L'idée est de construire un résultat (acc comme accumulateur) par le traitement d'une succession de valeurs. Trop souvent les développeurs partent avec deux idées reçues:

  1. L'accumulateur EST le résultat attendu,
  2. A la fin de la réduction, l'accumulateur est un résultat final attendu.

Or, rien ne nous y oblige...

  1. En effet, l'accumulateur peut très bien contenir beaucoup plus que le résultat attendu en cours d'élaboration : il peut aussi contenir des valeurs intermédiaires.
  2. Lorsque la réduction se termine, nous pouvons appliquer à l'accumulateur résultant, une fonction conclusive qui finalisera le résultat attendu et l'extraira de l'accumulateur.
Dans notre cas, l'accumulateur est un objet structuré contenant trois informations:
  1. La liste des intervalles finalisés (consolidated), 
  2. le borne d'ouverture de l'intervalle en cours d'élaboration (first)
  3. la borne de fermeture de l'intervalle en cours d'élaboration (et qui est repoussée par la réduction chaque fois qu'on constate une valeur successive)
La réduction se comporte comme un diagramme d'état, faisant progresser cet "état" (l'accumulateur) en fonction des "évènements" reçues (les valeurs du tableau réduit).

A la fin de la réduction, l'accumulateur n'est pas le résultat attendu (c'est le champ "consolidated" de cet accumulateur qui l'est) et ce résultat n'est pas finalisé (il manque le dernier intervalle). La fonction de conclusion:

then (acc)->acc.consolidated + {"from":acc.first, "to":acc.last})

finit le travail. CQFD.
____________________________________________________________________________

A little subject to keep us entertained (but which may help OMS OneStock users 😁). It involves transforming a list of sorted and successive values into a list of intervals that represents that same list. For example, the list:

[1, 2, 4, 5, 6, 8, 10, 11, 14, 15, 16] 

will be transformed into:

[
  {
    "from": 1,
    "to": 2
  },
  {
    "from": 4,
    "to": 6
  },
  {
    "from": 8,
    "to": 8
  },
  {
    "from": 10,
    "to": 11
  },
  {
    "from": 14,
    "to": 16
  }
]

Each hole creates a new interval. The solution is given by the following script:

%dw 2.0
output application/json
var ressources = [1, 2, 4, 5, 6, 8, 10, 11, 14, 15, 16] 
---
(if (ressources == []) [] else (ressources reduce (v, acc={
    consolidated: [],
    first: null
})->
if (acc.first==null) {
    consolidated: acc.consolidated,
    first: v,
    last: v
}
else if (acc.last+1==v) {
    consolidated: acc.consolidated,
    first: acc.first,
    last: v
}
else {
    consolidated: acc.consolidated + {"from":acc.first, "to":acc.last},
    first: v,
    last: v
})
then (acc)->acc.consolidated + {"from":acc.first, "to":acc.last})

This example (used less frequently than many others in this blog) is nevertheless very interesting as a model for writing a reduce. Reduction (DataWeave "reduce") is one of DataWeave's most versatile functions. The idea is to build a result (acc as accumulator) by processing a succession of values. All too often, developers start with two preconceived ideas:

  1. The accumulator IS the expected result,
  2. At the end of the reduction, the accumulator is an expected final result.

However, nothing obliges us to do this...

  1. In fact, the accumulator may well contain much more than the expected result: it may also contain intermediate values.
  2. When the reduction is complete, we can apply a conclusive function to the resulting accumulator, which will finalize the expected result and extract it from the accumulator.

In our case, the accumulator is a structured object containing three pieces of information:

  1. The list of finalized intervals (consolidated), 
  2. the opening boundary of the interval under construction (first)
  3. the closing limit of the interval under construction (which is pushed back by the reduction each time a successive value is found).

The reduction behaves like a state diagram, progressing this "state" (the accumulator) according to the "events" received (the values in the reduced array).

At the end of the reduction, the accumulator is not the expected result (the "consolidated" field of this accumulator is) and this result is not finalized (the last interval is missing). The conclusive function:

then (acc)->acc.consolidated + {"from":acc.first, "to":acc.last})

ends the job. And there you are !






Aucun commentaire:

Enregistrer un commentaire

Pourquoi ce blog ? / Why this blog?

Mulesoft est un ESB du monde Salesforce utilisé pour construire des flots permettant aux pièces logicielles d'un Système d'Informati...