A guide to organizing elements in Javascript to calculate the Cartesian product in Javascript

I encountered a situation where I have an object structured like this:

   [
    {attributeGroupId:2, attributeId: 11, name: 'Diamond'},
    {attributeGroupId:1, attributeId: 9, name: '916'},
    {attributeGroupId:1, attributeId: 1, name: '24K'},
    {attributeGroupId:2, attributeId: 12, name: 'Square'}
]

The expected output should be:

[
    {attributeGroupId:2, attributeId: 11, name: 'Diamond'},
    {attributeGroupId:2, attributeId: 12, name: 'Square'}
]

,

[
    {attributeGroupId:1, attributeId: 9, name: '916'},
    {attributeGroupId:1, attributeId: 1, name: '24K'}
]

This allows me to perform the cartesian product as demonstrated below:

[
     {attributeId: 11-9, name: 'Diamond-916'}, 
     {attributeId: 11-1, name: 'Diamond-24K'},
     {attributeId: 12-9, name: 'Square-916'}, 
     {attributeId: 12-1, name: 'Square-24K'},
]

Now, the goal is to maintain this functionality in a versatile manner since the number of attributeGroupId values can vary during runtime.

I propose that dividing the array into smaller arrays based on attributeGroupId would serve as the initial step.

Answer №1

If you need to create a simple filter function, you can use the following code snippet:

function basicFilter(data, id) {
  return data.filter((item) => item.id === id);
}

console.log(basicFilter(data, 1)) //returns elements with id = 1
console.log(basicFilter(data, 2)) //returns elements with id = 2

This code presents a more general solution that returns an array of filtered arrays:

function advancedFilter(data) {
  const mem = {};
  data.forEach((item) => {
    if (mem[item.id]) {
      mem[item.id].push(item);
    } else {
      mem[item.id] = [item];
    }
  })
  return Object.values(mem);
}

Update based on received feedback: For scenarios where attribute order is irrelevant, this code snippet provides a way to obtain the "cartesian concatenation" of multiple arrays:

function concatArrays(arrays) {
  if (arrays.length <= 1 ) return arrays[0];
  const first = arrays[0];
  const second = concatArrays(arrays.slice(1));
  const result = [];
   first.forEach(( itemFirst ) => {
      second.forEach( (itemSecond) => {
        result.push({id: `${itemFirst.id}-${itemSecond.id}`, name: `${itemFirst.name}-${itemSecond.name}`})
      });
   });
   return result;
}

By using the following call:

console.log(concatArrays(advancedFilter(data)));

You will receive the expected output, even though the strings may be concatenated differently:

[
  {
    "id":"9-11",
    "name":"916-Diamond"
  },
  {
    "id":"9-12",
    "name":"916-Square"
  },
  {
    "id":"1-11",
    "name":"24K-Diamond"
  },
  {
    "id":"1-12",
    "name":"24K-Square"
  }
]

Answer №2

In my opinion, breaking down this logic into smaller pieces would be more beneficial. Initially, creating a function that can generate a cartesian product for an array of arrays would prove to be valuable. Let's create a utility function for that purpose. Additionally, it is common to split an array into subarrays based on a shared feature. So, let's also create a function for that.

Subsequently, we can develop a basic main function that groups the IDs by attributeGroupId, and then applies the cartesian product to each item in the resulting array:

[
  {attributeGroupId: 2, attributeId: 11, name: "Diamond"},
  {attributeGroupId: 1, attributeId: 9, name: "916"}
]

We can then construct a new object by combining the properties of attributeId and name.

const cartesian = ([xs, ...xss]) =>
  xs == undefined ? [[]] : xs .flatMap (x => cartesian (xss) .map (ys => [x, ...ys]))

const group = (fn, k) => (xs) => Object .values (xs .reduce (
  (a, x) => ((k = 'x' + fn (x)), (a [k] = a [k] || []), (a [k] .push (x)), a), {}
))

const regroupAtts = (xs) => 
  cartesian (group (x => x .attributeGroupId) (xs))
    .map (xs => ({
      attributeId: xs .map (x => x .attributeId) .join ('-'),
      name: xs .map (x => x.name) .join ('-')
    }))

const atts = [{attributeGroupId: 2, attributeId: 11, name: 'Diamond'}, {attributeGroupId: 1, attributeId: 9, name: '916'}, {attributeGroupId: 1, attributeId: 1, name: '24K'}, {attributeGroupId: 2, attributeId: 12, name: 'Square'}]
const atts2 = [{attributeGroupId: 2, attributeId: 11, name: 'Diamond'}, {attributeGroupId: 1, attributeId: 9, name: '916'}, {attributeGroupId: 3, attributeId: 101, name: 'foo'}, {attributeGroupId: 1, attributeId: 1, name: '24K'}, {attributeGroupId: 3, attributeId: 102, name: 'bar'}, {attributeGroupId: 2, attributeId: 12, name: 'Square'}, {attributeGroupId: 3, attributeId: 103, name: 'baz'}]


console .log ('Original:', regroupAtts (atts))
console .log ('With third group added:', regroupAtts (atts2))
.as-console-wrapper {max-height: 100% !important; top: 0}

To demonstrate scalability, a third attribute with entries named "foo", "bar", and "baz" has been included. This showcases how the functionality extends to accommodate additional attributes. However, it is crucial to consider combinatorial explosions when adding numerous attributes, although the code is structured to manage such scenarios.

An intriguing concept derived from this is the potential parameterization of names such as "attributeGroupId", "attributeId", and "name". This could be achieved through the following method:

const regroup = (key, props) => (xs) => 
  cartesian (group (x => x [key]) (xs))
    .map (xs => Object .fromEntries (
      props .map (prop => [prop, xs .map (x => x [prop]) .join ('-')])
    ))

const regroupAtts = regroup ('attributeGroupId', ['attributeId', 'name']) 

const cartesian = ([xs, ...xss]) =>
  xs == undefined ? [[]] : xs .flatMap (x => cartesian (xss) .map (ys => [x, ...ys]))

const group = (fn, k) => (xs) => Object .values (xs .reduce (
  (a, x) => ((k = 'x' + fn (x)), (a [k] = a[k] || []), (a[k] .push (x)), a), {}
))

const regroup = (key, props) => (xs) => 
  cartesian (group (x => x [key]) (xs))
    .map (xs => Object .fromEntries (
      props .map (prop => [prop, xs .map (x => x [prop]) .join ('-')])
    ))

const regroupAtts = regroup ('attributeGroupId', ['attributeId', 'name']) 


const atts = [{attributeGroupId: 2, attributeId: 11, name: 'Diamond'}, {attributeGroupId: 1, attributeId: 9, name: '916'}, {attributeGroupId: 1, attributeId: 1, name: '24K'}, {attributeGroupId: 2, attributeId: 12, name: 'Square'}]
const atts2 = [{attributeGroupId: 2, attributeId: 11, name: 'Diamond'}, {attributeGroupId: 1, attributeId: 9, name: '916'}, {attributeGroupId: 3, attributeId: 101, name: 'foo'}, {attributeGroupId: 1, attributeId: 1, name: '24K'}, {attributeGroupId: 3, attributeId: 102, name: 'bar'}, {attributeGroupId: 2, attributeId: 12, name: 'Square'}, {attributeGroupId: 3, attributeId: 103, name: 'baz'}]

console .log ('Original:', regroupAtts (atts))
console .log ('With third group added:', regroupAtts (atts2))
.as-console-wrapper {max-height: 100% !important; top: 0}

This approach allows us to utilize reusable functions like cartesian and group, which are commonly used. These functions have been borrowed from my previous responses, although I made some modifications to the group function by appending the string 'x' before key generation. This adjustment ensures that the keys (attributeGroupIds) are ordered as per your preferences. By exploring this variation, I am assessing whether it could replace the standard function in my toolkit.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Minimizing the gap between icon and label text

I have a React form that I need help with. The issue is that I want to reduce the space between the list icon and the label. Here is the CSS I am using: .form__container { display: flex; flex-wrap: wrap; } .form__container input { color: rgb(115, 0, ...

Tips for iterating through an array of object literals and combining values with matching IDs

Looking for a more efficient way to iterate through an array of object literals and combine values in objects with duplicate IDs. Is there a smarter approach than utilizing multiple nested for loops? Here is the sample data provided: { "theList": [ ...

Automatically fill in a text box with previously saved information

Does anyone have suggestions on how to create this type of textbox or what it is called? (View original image here) ...

Can the value of a variable be passed as seen in the JavaScript code snippet?

I'm wondering if I'm on the right track with generating random colors when a button is clicked. Here's my code: randomColor = "#" + Math.floor(Math.random() * 16777215).toString(16); // --- more code --- changeHeaderColor() { con ...

What is the process for assigning a value to the body in a div element?

Within a div, there is a body element structured like this: <div id = "TextBox1"> <iframe id = "TextBox1_1"> #document <html> <head></head> <body></body> </html> </iframe> </div> I have attempte ...

Consolidate test outcomes from various nodes in the stack into a single graph

Nowadays, web applications consist of a variety of different technology stacks. Developers utilize languages like Ruby, Python, PHP, JavaScript, and C for the backend, as well as frameworks such as AngularJS and EmberJS for MVC/client-side code. To ensure ...

Vuejs allows objects to trigger the execution of methods through elements

My goal is to utilize a function in order to individually set the content of table cells. In this specific scenario, I aim to enclose the status with the <strong> - Tag (I refrain from modifying the template directly because it is stored within a com ...

personalize auto-suggestions in AngularJS

Here is a link to a custom search implementation using autocomplete in angularjs. Is it possible to modify this so that the matching starts from the first character? HTML <div ng-app='MyModule'> <div ng-controller='DefaultCtrl& ...

Using AngularJS to hide elements within a nested dictionary structure

My dictionary structure is as follows: var data = { a: [1, 2, 3, 4, 5], b: [ [1, 2], [3, 4], [5, 6] ] }; Currently, I am using ng-hide to hide an element if the value 2 exists in data->a. Here's how it's implemented: <i ...

Ways to break down a collection of multiple arrays

Looking to transform an array that consists of multiple arrays into a format suitable for an external API. For example: [ [44.5,43.2,45.1] , [42, 41.2, 48.1] ] transforming into [ [44.5,42], [43.2,41.2] , [45.1, 48.1] ] My current code attempts this ...

Decreased Performance in Vue Threejs with Larger JSON Data Sets

I am currently upgrading a legacy Three.js project from Angular 1.5 to Vue 2.6. The project is used for visualizing objects in JSON file format and I'm experiencing a drop in frame rate, going from ~60FPS in Angular to ~12FPS in Vue when loading large ...

What are some creative ways to enhance closePath() in canvas styling?

Currently, I am faced with the challenge of styling the closePath() function on my canvas. Specifically, I would like to apply different stroke styles to each line segment. For now, let's focus on changing colors for each line. In the example below, y ...

jQuery: What's the Difference Between Triggering a Click and Calling a Function?

Imagine having certain actions assigned to a link using .click or .live, and wanting to repeat the same action later without duplicating code. What would be the right approach and why? Option 1: $('#link').click(function(){ //do stuff }); //c ...

Unable to utilize Bower due to a node.js malfunction

Currently facing an issue while attempting to utilize bower for installing all necessary components for my website project. Each time I make an attempt, the following error presents itself: TypeError: Object #<Object> has no method 'toLowerCase ...

Troubleshooting jsPDF problem with multi-page content in React and HTML: Converting HTML to PDF

I need to convert HTML content in my React application into a PDF file. My current approach involves taking an HTML container and executing the following code snippet: await htmlToImage .toPng(node) .then((dataUrl) => { ...

Choosing a String and Performing a Double Click in Selenium with Java

My textbox is disabled, and it includes the following attributes: <div id="writingactivityId2" class="boxSize ng-pristine ng-untouched ng-valid ng-valid-required redactor_editor writingActivityDisabled" ng-focus="editing()" redactor="" readonly="" ng- ...

The GET request is returning a null array instead of the expected array contents

I am new to this, but I am trying to understand why my GET request is returning an empty array even though I am sure that the Mongo database collection is not empty. Each word form in the WordForm collection has a "lexicalform" key that refers to a Lexicon ...

Deselect radio option

I am attempting to create a Vue instance with a group of radio buttons. My aim is that when a user clicks on a checked radio button, it will become unchecked. However, I have not been successful in accomplishing this using Vue so far. Below is the code I h ...

What is the best way to organize text within messages?

Hey there! I have a messaging app with arrays of messages. [{"id":4, "user_id":1, "messageable_id":3, "messageable_type":"conversation", "text":"Text 1", "action":null, "target_id":null, "created_at":"2019-06-17 15:47:55", "updated_at":"2019-06-17 15:47:5 ...

What is the best way to remove headers and footers programmatically in print pages on Safari using JavaScript?

Struggling with eliminating the header and footer on print pages in Safari using JavaScript? While disabling the header and footer manually can be done through print settings in most browsers, my aim is to automate this process with code to ensure that use ...