'Link between two JavaScript objects and then create condition

I have this set of data :

[
  {
    CIRPIC: 'SAL',
    ALLPIC: [7, 8, 9, 10]
  },
  {
    CIRPIC: 'SUC',
    ALLPIC: [5, 6, 7]
  },
  {
    CIRPIC: 'LI3',
    ALLPIC: [10, 13]
  },
  {
    CIRPIC: 'MAT',
    ALLPIC: [20, 19, 17, 18, 21, 13]
  },
]

And this one :

[
  {
    CODMAT: '86        ',
    ALLLST: 5,
    CIRCUITS: ['SUC', 'SAL']
  },
  {
    CODMAT: '83        ',
    ALLLST: 20,
    CIRCUITS: ['MAT', 'LI3']
  }
]

I would like to link the two, with these conditions : From 1 to 21, loop through CIRCUITS and match the ALLPIC values : if there is a link, 1, if not, 0, like this :

{
  CODMAT: '86'
  ALLST: 5
  CIRCUITS: ['SUC', 'SAL']
  ALLEES: [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
}

(all the 1 are because the ALLPIC are in CIRPIC 'SAL' or 'SUC')



Solution 1:[1]

Solution

Here one approach using bigint which have been introduced into JavaScript with ES2020.

const allCircuits = [
  {
    CIRPIC: "SAL",
    ALLPIC: [7, 8, 9, 10],
  },
  {
    CIRPIC: "SUC",
    ALLPIC: [5, 6, 7],
  },
  {
    CIRPIC: "LI3",
    ALLPIC: [10, 13],
  },
  {
    CIRPIC: "MAT",
    ALLPIC: [20, 19, 17, 18, 21, 13],
  },
];

// create a structure for fast lookups of circuits and their set bits
const circuits = new Map();
allCircuits.forEach(({ CIRPIC, ALLPIC }) =>
  circuits.set(CIRPIC, transform(ALLPIC))
);

/**
 * Sets the k-th bit of a given bigint.
 * @param {bigint} x 
 * @param {number} k 
 * @returns 
 */
function setKthBit(x, k) {
  return (1n << BigInt(k - 1)) | x;
}

/**
 * Create a bigint which contains 1-bits at all specified positions within the array.
 * e.g. [1, 3] returns the bigint which in binary is 0b101
 * @param {Array<number>} array 
 * @returns bigint with the 1-bits set at positions specified in array
 */
function transform(array) {
  return array.reduce(
    (prev, no) => setKthBit(prev, no),
    0n
  );
}

const input = [
  {
    CODMAT: "86        ",
    ALLLST: 5,
    CIRCUITS: ["SUC", "SAL"],
  },
  {
    CODMAT: "83        ",
    ALLLST: 20,
    CIRCUITS: ["MAT", "LI3"],
  },
];

const output = input.map((item) => ({
  // copy all items
  ...item,
  // remove unnecessary whitespace
  CODMAT: item.CODMAT.trim(),
  // OR the bits which is effectively merging the arrays e.g. 0101 | 1001 => 1101
  ALLEES: item.CIRCUITS.reduce(
    (merged, currentCircuit) => merged | circuits.get(currentCircuit),
    0n
  )
  // get string in base 2 (binary format => "01010001")
    .toString(2)
  // string might be shorter than 21, so pad the rest with 0 bits
    .padStart(21, "0")
  // split so we get an array containing the individual bits
    .split("")
  // reverse the array as bits get bigger going from left to right and we need it from right to left
    .reverse(),
}));

console.log(output);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Explanation

This approach uses big manipulation to achieve the wanted result. It is also possible without bit manipulation but this is I think a quite neat and fast approach.

We start by creating a lookup table i. e. implemented using a Map mapping each circuit to it's position of 1s. This is to make sure we can quickly lookup the bits for a given circuit later.

We do, however not store the array which indicates the position of 1s but use an actual integer value which has a 1-bit set at the specified positions. We do that using the method transform().

Here an example with lots of output which shows how the transformation works. Keep in mind that in binary the least significant bits are on the right, but your output has the least significant bits on the left therefore the bit represenation is the inverse of your representation.

/**
 * Sets the k-th bit of a given bigint.
 * @param {bigint} x 
 * @param {number} k 
 * @returns 
 */
function setKthBit(x, k) {
  return (1n << BigInt(k - 1)) | x;
}

/**
 * Create a bigint which contains 1-bits at all specified positions within the array.
 * e.g. [1, 3] returns the bigint which in binary is 0b101
 * @param {Array<number>} array 
 * @returns bigint with the 1-bits set at positions specified in array
 */
 function transform(array) {
  return array.reduce(
    (prev, no) => setKthBit(prev, no),
    0n
  );
}

const bitsToSet = [20, 19, 17, 18, 21, 13];
  
const transformed = transform(bitsToSet)
console.log("bits set at position: ", bitsToSet);
console.log("decimal value:", transformed.toString(10));
console.log("hexadecimal value:", transformed.toString(16));
console.log("binary value:", transformed.toString(2));
console.log(`Bit | Position | Reversed
----|----------|----------`)
const bitArray = transformed.toString(2).split("");
bitArray.forEach((bit, position) => console.log(`  ${bit} | ${position}        | ${bitArray.length - position}`))
.as-console-wrapper { max-height: 100% !important; top: 0; }

Now we can start with the actual mapping using map() because we want to have a single result for every input item. We create a copy of the input for all properties except two:

  • CODMAT
  • ALLEES

We need to remove the whitespace in CODMAT using trim() and of course we need the array of 1s and 0s.

We can get that using the lookup table from before and reduce(). We loop through the circuits and for each circuit we obtain the integer which describes the circuit from our Map and use the bit-operator | to OR those bits. And we OR all circuits which means we are effectively merging all the 1-bits of all specified circuits.

Now we have the result as an bigint but we need the actual 1s and 0s. To get that use bigint#toString(2). Now padStart() the result with 0 as there are no leading zeroes for bigint, then split() the result to get an array, and reverse() the array to get the final output.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1