jq

Assigning incrementing numbers in order-preserving way


Using jq, I want to assign an incrementing number num to each element in the order they appear in the input, but keep the same number for elements that share a ref value with other elements.

So considering this input,

[
  {"ref": "e13bb9", "name": "One"},
  {"ref": "11b8cc", "name": "Two"},
  {"ref": "e13bb9", "name": "Three"}
]

I would want the following output:

[
  {"ref": "e13bb9", "name": "One", "num": 0},
  {"ref": "11b8cc", "name": "Two", "num": 1},
  {"ref": "e13bb9", "name": "Three", "num": 0}
]

In other words, the element with "name" == "One" should have "num": 0 (since it is the first element), then the element with "name" == "Two" should have "num": 1 (since it is the second element, and its ref value has not occurred before), then the element with "name" == "Three" should again have "num": 0 (since it has the same ref as the element with "name" == "One", and should thus share its num).

I tried to use group_by(.ref) to group the elements with the same ref together, but that does not preserve the order in which they occur in the input; in other words, "name" == "Two" came before "name" == "One" and "name" == "Three".


Solution

  • Keep a lookup table for the .ref values, and determine the next value by its size (length):

    reduce .[] as $i ({};
      .num |= (.[$i.ref] //= length) | .arr += [$i + {num: .num[$i.ref]}]
    ) | .arr
    
    [
      {
        "ref": "e13bb9",
        "name": "One",
        "num": 0
      },
      {
        "ref": "11b8cc",
        "name": "Two",
        "num": 1
      },
      {
        "ref": "e13bb9",
        "name": "Three",
        "num": 0
      }
    ]
    

    Demo