1

Suppose an array of objects:

arr1 = [
  { 'catId': 1, 'name': 'A' },
  { 'catId': 2, 'name': 'B' },
  { 'catId': 3, 'name': 'C' },
  { 'catId': 2, 'name': 'D' },
  { 'catId': 1, 'name': 'E' },
  { 'catId': 1, 'name': 'F' },
  { 'catId': 3, 'name': 'G' },
  { 'catId': 3, 'name': 'H' },
  { 'catId': 2, 'name': 'I' },
  { 'catId': 1, 'name': 'J' }
]

How can I randomly choose two Item of catId=1 and each Items from remaining category.

Required

arr2 = [
  { 'catId': 1, 'name': 'A' },
  { 'catId': 1, 'name': 'F' },
  { 'catId': 2, 'name': 'I' },
  { 'catId': 3, 'name': 'G' }
]
Alexander van Oostenrijk
  • 4,644
  • 3
  • 23
  • 37
Rrptm
  • 329
  • 2
  • 12
  • 1
    What have you tried so far? – Balastrong Aug 19 '21 at 17:37
  • Your `catId` keys are missing a closing quote. – Mr. Polywhirl Aug 19 '21 at 17:39
  • 1
    When you have a problem like this it helps to break it down in sub-problems. One naïve approach would be to obtain two arrays from the main array: one with catId = 1, and another array with items with catId != 1. [Array.prototype.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) comes to mind to help you accomplish this task. Then to get the random elements with catId = 1 [Math.random()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) can help you with that operating on the length of catId arr – Hanlet Escaño Aug 19 '21 at 17:44

2 Answers2

0

This is a very simple and naïve approach like I explained on my comment, but it gets the job done:

var arr1 = [
  { 'catId': 1, 'name': 'A' },
  { 'catId': 2, 'name': 'B' },
  { 'catId': 3, 'name': 'C' },
  { 'catId': 2, 'name': 'D' },
  { 'catId': 1, 'name': 'E' },
  { 'catId': 1, 'name': 'F' },
  { 'catId': 3, 'name': 'G' },
  { 'catId': 3, 'name': 'H' },
  { 'catId': 2, 'name': 'I' },
  { 'catId': 1, 'name': 'J' }
];

//shuffles an array
function shuffle(arr) {
    return arr.sort(() => Math.random() - 0.5);
}

//gets items with catId = 1, and shuffles them
var cat1 = shuffle(arr1.filter(function(item) {
    return item.catId == 1;
}));

var otherCat = [];
//pushes items in the otherCat array that aren't catId = 1, and not duplicate category
for (var i = 0; i < arr1.length; i++) {
    //if not catId = 1 and not already in array
    if (arr1[i].catId != 1 && !find(arr1[i])) {
    //get all items in this category, and shuffles them to get a random item
    var thisCat = shuffle(arr1.filter(function(item) { return item.catId == arr1[i].catId; }))[0];
    otherCat.push(thisCat);
  }
}

//finds an item in otherCat array by catId
function find(item) {
    return otherCat.find(function(i) {
    return item.catId === i.catId;
  });
}


var result = [];
result.push(cat1[0]);
result.push(cat1[1]);
//concatenate both arrays
Array.prototype.push.apply(result, otherCat);
console.log(JSON.stringify(result));

I coded it this way because it is very simple to see. You could in theory loop through the whole array once to get all catId = 1 and other catId into 2 different arrays (I know I am doing multiple passes to the array, but like I said, this is just so you can get the idea).

Another way of doing it (perhaps a little more complex) is by grouping the items by category, then looping thru each category and grabbing a random element (2 in the case of catId == 1):

var arr1 = [
  { 'catId': 1, 'name': 'A' },
  { 'catId': 2, 'name': 'B' },
  { 'catId': 3, 'name': 'C' },
  { 'catId': 2, 'name': 'D' },
  { 'catId': 1, 'name': 'E' },
  { 'catId': 1, 'name': 'F' },
  { 'catId': 3, 'name': 'G' },
  { 'catId': 3, 'name': 'H' },
  { 'catId': 2, 'name': 'I' },
  { 'catId': 1, 'name': 'J' }
];

//groups by a property
//https://stackoverflow.com/a/34890276/752527
var groupBy = function(xs, key) {
  return xs.reduce(function(rv, x) {
    (rv[x[key]] = rv[x[key]] || []).push(x);
    return rv;
  }, {});
};

//shuffles an array
function shuffle(arr) {
    return arr.sort(() => Math.random() - 0.5);
}

var result = [];
var grouped = groupBy(arr1, 'catId');
var keys = Object.keys(grouped);

for (var i = 0; i < keys.length; i++) {
  var group = grouped[keys[i]]
  //if i have items in my group, shuffle them and grab 1, or 2 items from it
  if (group && group.length > 0) {
    var cat = shuffle(group);
    result.push(cat[0]);
    //adds the second item with catId ==1
    if (group[0].catId === 1) {
        result.push(cat[1]);
    }
  }
}
console.log(JSON.stringify(result));
Hanlet Escaño
  • 17,114
  • 8
  • 52
  • 75
-1

If you want to return a list of n-number of items from a particular category and one from the remaining categories, then you could group the items by their catId and then map the entries to a randomized count (length) based on whether the current key is the chosen bias.

Edit: I added a bias to keep n-number of items from the category of choice.

const categories = [
  { 'catId': 1, 'name': 'A' }, { 'catId': 2, 'name': 'B' },
  { 'catId': 3, 'name': 'C' }, { 'catId': 2, 'name': 'D' },
  { 'catId': 1, 'name': 'E' }, { 'catId': 1, 'name': 'F' },
  { 'catId': 3, 'name': 'G' }, { 'catId': 3, 'name': 'H' },
  { 'catId': 2, 'name': 'I' }, { 'catId': 1, 'name': 'J' }
];

const sortFn = (
  { catId: ai, name: an },
  { catId: bi, name: bn }
) =>
  (ai - bi) || an.localeCompare(bn);

const main = () => {
  print(pickRand(categories, ({ catId }) => catId, 1, 2).sort(sortFn));
};

const shuffle = (arr) => arr.sort(() => Math.random() - 0.5);

const grouped = (arr, keyFn) => arr.reduce((acc, item, idx) =>
  ({ ...acc, [keyFn(item)]: [...(acc[keyFn(item)] ?? []), idx] }), {});

const pickRand = (arr, keyFn, bias, count) =>
  Object
    .entries(grouped(arr, keyFn))
    .flatMap(([key, idx]) =>
      shuffle(idx).slice(0, bias == key ? count : 1)
        .map(i => arr[i]));

const print = (arr) => console.log(arr.map(x => JSON.stringify(x)).join('\n'));

main();
.as-console-wrapper { top: 0; max-height: 100% !important; }
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132