Dele opp array i grupper ved bruk av typescript/javascript


Jeg har dette:

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

Jeg trenger å omstrukturere det til dette:

    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
    ]

Det vanskelige er at antallet objekter i hver gruppe settes ved hjelp av en variabel (i dette eksemplet ville variabelen være 4).

Hvordan kan jeg oppnå dette ved å bruke Typescript/Javascript? Det gjør meg gal!

Takk

Svar #1

Det du egentlig ber om er chunking, i motsetning til gruppering. Noen av de andre svarene prøver å bruke Object.groupBy() eller Array.prototype.reduce() for å behandle hvert element og gruppere dem, noe som er ineffektivt. Chunking, i motsetning til gruppering, tar ingen hensyn til innholdet i elementene; derfor trenger vi ikke å gå gjennom (og behandle) hvert enkelt element i arrayen.

Alternativ A: Kombinert inndelings-/oversettelsesmetode

Det raskeste alternativet er en enkel løkke som går gjennom arrayet med trinn på chunk-størrelse, og skjærer ut delmengder mens vi går:

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>

Dette har noen fordeler over forslagene reduce() og Object.groupBy(). Denne:

  • Steg etter størrelsen på bitene i stedet for 1; så det utfører langt færre iterasjoner.
  • Har ikke behov for gjentatt, komparativ logikk eller beregninger (ingen divisjon eller avrunding nedover) for hver indeks. Innholdet i elementet er irrelevant; derfor bryr vi oss ikke om å behandle hvert enkelt element.
  • Definerer hver rowN-egenskap én gang og trenger aldri å sjekke om den allerede eksisterer.
  • Bruker den native Array.prototype.slice()-metoden for å velge våre delsett i stedet for å legge til individuelle elementer ett om gangen og gjentatte ganger endre størrelsen på chunk-arrayet.

Alternativ B: Del opp først, deretter reduser

Alternativt kan vi separere oppdelings- og oversettelsestrinnene. Vi vil lage en mer gjenbrukbar Array.prototype.chunk()-metode, slik det er vanlig i andre språk og biblioteker*. Vi vil deretter bruke Array.prototype.reduce() på den resulterende, kortere, todimensjonale arrayen. Dette reduserer mange av svakhetene ved å bruke reduce alene og blir faktisk raskere enn Alternativ A ved visse terskler for lengden på input-arrayen og chunk-størrelsen:

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>

* Du vil normalt implementere en chunking-metode som dette som en generator, siden chunking vanligvis brukes til å behandle store datasett, og derfor kanskje du ikke ønsker å materialisere et helt nytt array som inneholder alle chunks. For enkelhets skyld, og fordi generatoren medfører ekstra overhead, har vi laget et enkelt output-array med chunks. Når det er sagt, her er en generator-implementasjon hvis du vil ta en titt. Når Iterator.prototype.reduce() og lignende metoder får bred støtte, vil generatorer være enda mer attraktive.

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>

Her er en jsbench, som bruker en chunk-størrelse på 25 og en inngangslengde på 99, som sammenligner alternativ A og B sammen med løsninger gitt av andre svar. Det aksepterte reduce-svaret presterer faktisk dårligst. Det er tydelige ytelsesstraffer for å behandle individuelle elementer når grupperingen ikke tar hensyn til innholdet i elementene:

LøsningOperasjoner/sek (høyere er bedre)I forhold til raskest
Alternativ A: mens + skive1 300 000raskest
Alternativ B: dele opp → redusere1 100 00021 % tregere
Objekt.grupperEtter71 00095 % tregere
redusere57 00096 % tregere

Føl deg fri til å forkere testen for å prøve forskjellige inndata eller batche størrelser.

Svar #2

Bruk Array.reduce()

Du kan kjøre en .reduce()-metode på produktlisten din, slik:

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)

Relaterte artikler