最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Splitting array into groups using typescriptjavascript - Stack Overflow

programmeradmin4浏览0评论

I have this:

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

I need to restructure it to this:

    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 hard part is that the number of objects in each group is set using a variable (in this example the variable would be 4).

How can I achieve this using Typescript/Javascript? Its driving me mad!

Thanks

I have this:

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

I need to restructure it to this:

    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 hard part is that the number of objects in each group is set using a variable (in this example the variable would be 4).

How can I achieve this using Typescript/Javascript? Its driving me mad!

Thanks

Share Improve this question edited Feb 3, 2020 at 8:49 DBoi asked Jan 31, 2020 at 16:28 DBoiDBoi 6872 gold badges20 silver badges36 bronze badges 4
  • 1 Does this answer your question? Split array into chunks – Roberto Zvjerković Commented Jan 31, 2020 at 16:29
  • What have you tried so far? – evolutionxbox Commented Jan 31, 2020 at 16:30
  • I have tried this but i need the nested arrays to have names (row1, row2, etc). How can I add the array names? – DBoi Commented Jan 31, 2020 at 16:32
  • You would need an object for that, I added an answer on how you can implement it. – Yalung Tang Commented Jan 31, 2020 at 16:54
Add a ment  | 

5 Answers 5

Reset to default 9

What you're really asking for is chunking, as distinct from grouping. Some of the other answers are trying to use Object.groupBy() or Array.prototype.reduce() to process each item and group them, which is inefficient. Chunking, as distinct from grouping, has no regard for item content; so, we don't need to loop over (and process) each individual element of the array.

Option A: Combined Chunking/Translation Method

The fastest option is a simple loop which steps through the array by chunk-size, slicing subsets as we go:

function chunkAndTranslate(array, chunkSize) { 
  // Create a plain object for housing our named properties: row1, row2, ...rowN
  const output = {}, 
  // Cache array.length
  arrayLength = array.length;
  // Loop variables
  let arrayIndex = 0, chunkOrdinal = 1;
  // Loop over chunks
  while (arrayIndex < arrayLength) {
    // Use slice() to get a chunk. Note the incrementing/assignment operations.
    output[`row${chunkOrdinal++}`] = array.slice(arrayIndex, arrayIndex += chunkSize);
  }
  return output;
}
// Testing with a simplified 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 has a few advantages over the reduce() and Object.groupBy() suggestions. This one:

  • Steps by chunk-size rather than 1; so, it performs far fewer iterations.
  • Has no need for repeated, parative logic or calculations (no division or flooring) for every index. The item content is irrelevant; so, we don't bother processing each individual item.
  • Defines each rowN property once and never has to check whether it already exists.
  • Uses the native Array.prototype.slice() method to select our subsets rather than pushing individual items one at a time and repeatedly resizing the chunk array.

Option B: Pre-chunk then Reduce

Alternatively, we can separate the chunking and translation steps. We'll create a more reusable Array.prototype.chunk() method, as is mon in other languages and libraries*. We'll then use Array.prototype.reduce() on the resultant, shorter, two-dimensional array. This mitigates many of the weaknesses of using reduce on its own and actually bees faster than Option A at certain thresholds of input array length and chunk size:

if (!Array.prototype.hasOwnProperty("chunk")) {
  Object.defineProperty(Array.prototype, "chunk", {
    enumerable: false,
    value: function(size) {
      // Cache array.length
      const length = this.length;
      // Pre-size output array so we don't have to push/resize
      const output = new Array(Math.ceil(length / size));
      // Loop variables
      let seekIndex = 0,
        outputIndex = 0;
      // Loop over chunks
      while (seekIndex < length) {
        // Use slice() to get a chunk. Note the incrementing/assignment operations.
        output[outputIndex++] = this.slice(seekIndex, seekIndex += size);
      }
      // Return our chunks
      return output;
    }
  });
}

console.table(
  // Testing with a simplified demo array
  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  // pre-chunk
  .chunk(4)
  // Reduce to translate into the desired object
  .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>

* You'd normally implement a chunking method like this as a generator, since chunking is generally used to process large sets and, as such, you may not want to materialize an entirely new array containing all chunks. For simplicity, and because the generator incurs additional overhead, we've created a simple output array of chunks. That said, here's a generator implementation if you'd like to take a look. Once Iterator.prototype.reduce() and similar methods get broad support, generators will be even more attractive.

if (!Array.prototype.hasOwnProperty("chunk")) {
  Object.defineProperty(Array.prototype, "chunk", {
    enumerable: false,
    value: function*(size) {
      // Cache array.length
      const length = this.length;
      // Loop variable
      let seekIndex = 0;
      // Loop over chunks
      while (seekIndex < length) {
        // Use slice() to yield a chunk. Note the incrementing/assignment operations.
        yield this.slice(seekIndex, seekIndex += size);
      }
    }
  });
}

console.table(
  // Testing with a simplified demo array
  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  // pre-chunk with our generator
  .chunk(4)
  // Iterator.prototype.reduce() to translate into the desired object
  // check browser support for this function
  .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>

Here's a jsbench, using a chunk size of 25 and an input length of 99, paring options A and B along with solutions provided by other answers. The accepted reduce answer actually performs the worst. There are clear performance penalties for processing individual elements when the grouping has no regard for item content:

Solution Ops/s (higher is better) Relative 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

Feel free to fork the test to try different input data or chunk sizes.

Use Array.reduce()

You can run a .reduce() method on your products array, like so:

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 // <-- decides 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)

There's an experimental implementation for Object.groupBy that's supported in pre-release versions of most browsers: https://developer.mozilla/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy

Hopefully this will be the go-to way to solve this in the future.

Example usage (untested and shouldn't 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)

Lodash groupBy is handy. It takes an array, and an iterator function and groups the entries in the array accordingly. The grouping logic can easily be done by counting the index for each iteration, and increment the group count on when the modulo (remainder) operator returns zero.

Using your 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 will iterate through a list, group the entries in equally sized groups according to the groupSize variable, in the order they appear in the list.

If you want, you can also calculate the group number based on object values in the list. I'm incrementing an index instead.

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

This should return exactly what you are looking for:

const filteredProducts = {}
products.map((product, i) => {
  const row = `row${Math.floor(i / 4) + 1}`
  if (!filteredProducts[row]) filteredProducts[row] = []
  return filteredProducts[row].push(product)
})
发布评论

评论列表(0)

  1. 暂无评论