Dividing an array into categories with typescript/javascript

Here is the initial structure I have:

    products = [
       {
         'id': 1
         'name: 'test'
       },
       {
         'id': 2
         'name: 'test'
       },
       {
         'id': 3
         'name: 'test'
       }
       ... etc, etc
    ]

This is what I need it to look like after restructuring:

    products = [
       row1: [
        {
         'id': 1
         'name: 'test'
        },
        {
         'id': 2
         'name: 'test'
        },
        {
         'id': 3
         'name: 'test'
        },
        {
         'id': 4
         'name: 'test'
        }
       ]
    row2: [
       {
         'id': 5
         'name: 'test'
        },
        {
         'id': 6
         'name: 'test'
        },
        {
         'id': 7
         'name: 'test'
        },
        {
         'id': 8
         'name: 'test'
        }
       ]
       row3: [.. etc, etc
    ]

The challenge lies in dynamically setting the number of objects in each group based on a variable (in this example, the variable would be 4).

Is there a way to accomplish this using Typescript/Javascript? It's been quite frustrating!

Thank you

Answer №1

When it comes to organizing data, the concept of chunking is key. This method differs from grouping, as it disregards the content of each item in favor of structuring them into chunks efficiently. While some suggest using Object.groupBy() or Array.prototype.reduce() to group items, a more efficient approach is to utilize chunking.

Option A: Simplified Chunking/Translation Approach

The most expedient option involves iterating through the array by chunk size and extracting subsets along the way:

function chunkAndTranslate(array, chunkSize) { 
  // Create an object for storing named properties such as row1, row2, ...rowN
  const output = {}, 
  // Store array.length
  arrayLength = array.length;
  // Loop variables
  let arrayIndex = 0, chunkOrdinal = 1;
  // Loop over chunks
  while (arrayIndex < arrayLength) {
    // Use slice() to extract a chunk. Note the incrementing/assignment operations.
    output[`row${chunkOrdinal++}`] = array.slice(arrayIndex, arrayIndex += chunkSize);
  }
  return output;
}
// Testing with a simple demo array
console.table(chunkAndTranslate([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 4));
<script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script><style>.as-console-wrapper{display:block}</style><script>console.config({timeStamps:false,maximize:true})</script>

This method offers distinct advantages over using reduce() and Object.groupBy(). It:

  • Proceeds by chunk size rather than unit-by-unit, reducing the number of iterations required.
  • Eliminates the need for extensive logic or calculations for every index, as individual item content is ignored.
  • Declares each rowN property only once, without constant validation checks.
  • Utilizes the built-in
    Array.prototype.<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice" rel="nofollow noreferrer">slice()</a>
    function for selecting subsets instead of adding items individually and resizing the chunk array repeatedly.

Option B: Pre-chunk followed by Reduction

An alternate approach involves separating chunking and translation processes. By implementing a reusable Array.prototype.chunk() method and then applying

Array.prototype.<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce" rel="nofollow noreferrer">reduce()</a>
on the resulting two-dimensional array, we can enhance efficiency beyond Option A under certain input conditions:

if (!Array.prototype.hasOwnProperty("chunk")) {
  Object.defineProperty(Array.prototype, "chunk", {
    enumerable: false,
    value: function(size) {
      // Cache array.length
      const length = this.length;
      // Initialize output array size to avoid pushing/resizing
      const output = new Array(Math.ceil(length / size));
      // Loop variables
      let seekIndex = 0,
        outputIndex = 0;
      // Iterate over chunks
      while (seekIndex < length) {
        // Extract a chunk using slice(). Note the incrementing/assignment operations.
        output[outputIndex++] = this.slice(seekIndex, seekIndex += size);
      }
      // Return the chunks
      return output;
    }
  });
}

console.table(
  // Sample test using a basic array
  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  // Split into chunks
  .chunk(4)
  // Reduce for desired object structure
  .reduce((output, chunk, index) => {
    output[`row${index + 1}`] = chunk;
    return output;
  }, {})
);
<script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script><style>.as-console-wrapper{display:block}</style><script>console.config({timeStamps:false,maximize:true})</script>

A jsbench comparison with chunk size of 25 and input length of 99 showcases performance disparities between options A, B, and other approaches presented in different answers. Interestingly, the supposedly superior reduce answer actually proves least effective due to inefficiencies when dealing with items disregarding content:

Solution Ops/s Relative Speed Compared to Fastest
Option A: while + slice 1,300,000 fastest
Option B: chunk → reduce 1,100,000 21% slower
Object.groupBy 71,000 95% slower
reduce 57,000 96% slower

Experiment with varied input data or chunk sizes via the provided test link.

Answer №2

Utilize the power of Array.reduce()

To organize your products into groups, you can use the .reduce() method on the products array:

var products = [
   { 'id': 1, name: 'test' },
   { 'id': 2, name: 'test' },
   { 'id': 3, name: 'test' },
   { 'id': 4, name: 'test' },
   { 'id': 5, name: 'test'  },
   { 'id': 6, name: 'test' }
]

var numberOfObjects = 4 // <-- specify number of objects in each group

var groupedProducts = products.reduce((acc, elem, index) => {
  var rowNum = Math.floor(index/numberOfObjects) + 1
  acc[`row${rowNum}`] = acc[`row${rowNum}`] || []
  acc[`row${rowNum}`].push(elem)
  return acc
}, {})

console.log(groupedProducts)

Answer №3

An innovative implementation of Object.groupBy is now available in pre-release versions of major browsers: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy

This could potentially become the preferred method for solving this issue in the time to come.

Here is an example usage (not tested and may not work unless using a prelease version...):

  const products = [
       {
         'id': 1,
         'name': 'test1'
       },
       {
         'id': 2,
         'name': 'test2'
       },
       {
         'id': 3,
         'name': 'test3'
       },
       {
         'id': 4,
         'name': 'test4'
       },
       {
         'id': 5,
         'name': 'test5'
       },
    ]
    
const columnsPerRow = 3;    

const table = Object.groupBy(products,(p,index)=>{
  return `row${Math.floor(index/columnsPerRow)}`
  })
  
console.log(table)

Answer №4

Lodash groupBy function is quite useful as it allows you to group entries in an array based on a specified iterator function. The logic behind the grouping can be achieved by tracking the index of each iteration and increasing the group count when the modulo operator yields zero.

For instance, consider this example:


const { groupBy } = require("lodash");

const products = [
  { id: "1", name: "test" },
  { id: "2", name: "test" },
  { id: "3", name: "test" },
  { id: "4", name: "test" },
  { id: "5", name: "test" },
  { id: "6", name: "test" },
  { id: "7", name: "test" },
  { id: "8", name: "test" },
];
const myGroupingFunction = (val) => {
  ++index;
  const groupLabel = "row" + groupCount;
  if (index % groupSize === 0) {
    ++groupCount;
  }
  return groupLabel;
};

const groupSize = 2;
let groupCount = 1;
let index = 0;
const groupedEntries = groupBy(products, myGroupingFunction);

console.log("GroupedEntries: ", groupedEntries);


// GroupedEntries:  {
//  row1: [ { id: '1', name: 'test' }, { id: '2', name: 'test' } ],
//  row2: [ { id: '3', name: 'test' }, { id: '4', name: 'test' } ],
//  row3: [ { id: '5', name: 'test' }, { id: '6', name: 'test' } ],
//  row4: [ { id: '7', name: 'test' }, { id: '8', name: 'test' } ]
//}

This method will loop through a list, categorizing the items into evenly sized groups determined by the groupSize variable, maintaining their original sequence.

If preferred, the grouping number could also be calculated based on object properties within the array. In this case, I opted for incrementing an index instead.

https://lodash.com/docs/4.17.15#groupBy

Answer №5

This code snippet will provide the precise solution you seek:

const groupedProducts = {}
products.map((product, idx) => {
  const rowKey = `row${Math.floor(idx / 4) + 1}`
  if (!groupedProducts[rowKey]) groupedProducts[rowKey] = []
  return groupedProducts[rowKey].push(product)
})

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

The periodLookup array does not have a defined value for periodStr. Why is this error being caught?

Here is a method that I am working with: set_period_help_text: function(periodInput){ var metric = MonitorMetric.getSelectedMetric(); var periodStr = $('select[name=metric_period]').val(); var datapoints = Number(periodIn ...

Glistening: sending reactiveValues to conditionalPanel

Is it possible to pass a reactiveValues to the condition of a conditionalPanel? If so, what is the correct method? Below is my attempt in the UI.R file for the conditionalPanel: conditionalPanel(condition = "values.cond == 0", etc. I have defined values ...

How can we ensure that Protractor's ElementArrayFinder 'each' function pauses until the current action has finished before moving on to the next iteration?

Currently, I am facing an issue while trying to utilize an 'each' loop in my Angular 8 app's end-to-end tests using protractor. Within my page object, I have created a method that returns an ElementArrayFinder. public getCards(): ElementArr ...

JSON Serialization Results Include a Property for Counting

In my C# project, I'm facing an issue with serializing an array of objects into JSON. The type of array I'm dealing with is Object[], not Array<Object>. Currently, the serialization process is handled automatically by a JsonMediaTypeFormatt ...

Is there a way to update a useState in TabPanel without causing a re-render?

For the past few months, I've been immersing myself in React and MUI. Recently, I encountered a problem that has me stumped. The Goal: I receive data from a backend and need to display it for users to edit before sending the changes back to the serv ...

retrieving data from Redis with the help of Node.js

I'm having trouble with the code snippet below. Despite setting values for the left_label and right_label variables in Redis, they always seem to return as "true". I suspect this is due to the client.get function returning true when successful. How ca ...

Maintain the webpage content even after submitting a form with a file attachment on the same page

I am in the process of creating a secure webpage exclusively for our members. This page will allow members to access their personal data stored in the database and upload files as needed. One concern I have is how to return to the member page with all the ...

Having trouble with a JavaScript element not responding to clicks when displayed inline?

Currently, I am utilizing the "Hotel Datepicker" script found at this website: My goal is to have the datepicker always displayed inline, rather than closing after selecting dates. I attempted to add a function that triggers whenever the datepicker close ...

Creating a Universal Resolver in Angular (2+) - A Step-by-Step Guide

I have a vision to develop an ultra-generic API Resolver for my application. The goal is to have all "GET" requests, with potential extension to other verbs in the future, utilize this resolver. I aim to pass the URL and request verb to the resolver, allow ...

What is the best way to determine the value of a variable specific to my scenario?

Using the Angular framework, I am trying to determine if the data variable updates when a button is clicked. Here is an example: <button ng-click='change()'>{{data}}</button> I want to verify if the data changes or log the data var ...

The CSS selectors wrapped in jQuery vary between Chrome and Internet Explorer 11

When utilizing a jquery wrapped css selector such as $($("#selector").find('input[type="textarea"])'), I am able to input values into the textarea in Chrome using $($("#selector").find('input[type="textarea"]).val(someValue)') but encou ...

Why Mixin Class inference is not supported in Typescript

I encountered an issue in my code: The error message 'Property 'debug' does not exist on type 'HardToDebugUser'.' was displayed. It seems like Typescript failed to infer the mixin class correctly. Can you please explain this t ...

What is the best way to handle parsing a JSON object in a single response?

My challenge lies in parsing/converting a GET response to a POJO. Below, I have the User class and the Controller class. In the getUsers() method, I successfully parse the request using ParameterizedTypeReference with the wrapper class. However, I face di ...

Upon clicking a button, I aim to retrieve the data from the rows that the user has checked. I am currently unsure of how to iterate through the rows in my mat-table

My goal is to iterate through the column of my mat-table to identify the rows that have been checked, and then store the data of those rows in an array. <td mat-cell *matCellDef="let row"> <mat-checkbox (click)="$event.stopPropagation()" (c ...

Is it true that all events in JavaScript go through capturing and bubbling phases?

My current project involves binding one eventListener to an <audio> element for the play event and another eventListener to its parent element for the same event. I've observed that the callback for the child element always gets executed, while ...

Vuejs fails to properly transmit data

When I change the image in an image field, the new image data appears correctly before sending it to the back-end. However, after sending the data, the values are empty! Code Commented save_changes() { /* eslint-disable */ if (!this.validateForm) ...

The createElement function in Vue.js does not always return a fully formed component

There is a scenario where I need to create a functional component for a specific purpose. The task at hand involves taking all the children elements and adding some additional props to them, but only to those that are custom components of type ChildCompone ...

The NativeAppEventEmitter does not return any value

I've been grappling with obtaining a logged in user access token for quite some time. I initially faced challenges with it in JavaScript, so I switched to Objective-C and managed to succeed. Following that, I referred to this RN guide: https://facebo ...

Error: Found an unconventional token "e" in JSON data at position 1 during parsing in JSON.parse function

Is there a way to retrieve a string value by calling an API in Angular? Here is my approach: Service file - public getRespondDashboardUrl(): Observable<any> { return this.http.get(`ref/RespondDashboardUrl`); } Component File respondDashboar ...

Getting the Request Body Content in Express Middleware

Currently, I am in the process of developing a small API logger to use as an Express middleware. This logger is designed to gather data from both the request and response objects, then store this information in a JSON file on disk for later reference. Her ...