0

I have an object like this:

var database = [
  {
    category: 'CPUs',
    id: 1,
    products: [Product, Product, Product] //Product is an object
  },
  {
    category: 'GPUs',
    id: 2,
    products: [Product, Product]
  }      
];

and so on..

I'd like to get 10 random products in total, non-repeating. There can be more than one from the same category, as long as they are different products. How can I do this? I tried this:

function getRandomFromObject(){
    var productsCollected = [];

    while(productsCollected.length < 10){
        var randomCategory = database[Math.floor(Math.random()*database.length)];
        var randomProduct = randomCategory.products[Math.floor(Math.random()*randomCategory.products.length)];
        productsCollected.push(randomProduct);
    }
    return productsCollected;
}
nick
  • 2,819
  • 5
  • 33
  • 69
  • "Non-repeating" kind of makes it non-random, as the next value depends on the state of previous values. One approach for this could be to randomly sort (shuffle) the array and then just take the first 10 elements. – David Apr 24 '17 at 13:20
  • It would be simpler if your products were array of IDs, and Products would be a table of its own – Brian Apr 24 '17 at 13:21
  • get your random products, but add them to a set(). the set enforces uniqueness, so iterate over products until your set.length == 10 – Cruiser Apr 24 '17 at 13:23

1 Answers1

3

Things become easier if you first concatenate all the products into one array, then shuffle that array and finally take the first 10 from that:

function shuffle(a) {
    for (let i = a.length; i; i--) {
        let j = Math.floor(Math.random() * i);
        [a[i - 1], a[j]] = [a[j], a[i - 1]];
    }
    return a;
}

function getRandomFromObject(count){
    return shuffle([].concat(...database.map(o => o.products))).slice(0, count);
}

var database = [
  {
    category: 'CPUs',
    id: 1,
    products: ['a', 'b', 'c'] //Product is an object
  },
  {
    category: 'GPUs',
    id: 2,
    products: ['d', 'e']
  },
  {
    category: 'GPUs',
    id: 3,
    products: ['f', 'g', 'h', 'i', 'j']
  }
];

console.log(getRandomFromObject(10).join(','));

Addendum: If you can have the same Product object occurring in different categories, then apply a Set to the concatenated array, so to eliminate these duplicates:

return shuffle([...new Set([].concat(...database.map(o => o.products)))]).slice(0, count);

ES5 Code

As you asked in comments for ES5, and the need to consider products with the same ISBN property as the same products, here is the code for that:

function shuffle(a) {
    for (var i = a.length; i; i--) {
        var j = Math.floor(Math.random() * i);
        var temp = a[i - 1];
        a[i - 1] = a[j];
        a[j] = temp;
    }
    return a;
}

function getRandomFromObject(count){
    var uniq = {}; // Unique list of products, keyed by ISBN
    database.forEach(function (o) {
        o.products.forEach(function (product) {
            uniq[product.isbn] = product;
        });
    });
    var products = []; 
    for (product in uniq) {
        products.push(uniq[product]);
    }
    return shuffle(products).slice(0, count);
}

var database = [
  {
    category: 'CPUs',
    id: 1,
    products: [{ isbn: 'a' }, { isbn: 'b' }, { isbn: 'c' }] //Product is an object
  },
  {
    category: 'GPUs',
    id: 2,
    products: [{ isbn: 'd' }, { isbn: 'a' }, { isbn: 'j' }] // has same isbn as in CPUs
  },
  {
    category: 'Others',
    id: 3,
    products: [{ isbn: 'e' }, { isbn: 'f' }, { isbn: 'g' }, { isbn: 'h' }, { isbn: 'i' }]
  }
];

console.log(getRandomFromObject(10));
Community
  • 1
  • 1
trincot
  • 317,000
  • 35
  • 244
  • 286
  • Hey, nice this is what I was looking for, however I need to support some older browsers so I can't use ES6 but I have another question: There are some products that are in two categories, but they have a different ID key, so running the array with the objects through a dedupe function doesn't work, how can I fix that? – nick Apr 24 '17 at 13:44
  • You mean they have the *same* key? Or how else would you know they are dupes? (2) Can you support ES5? – trincot Apr 24 '17 at 13:45
  • 1) The product is the same in the two categories, but it has a different ID in each one. But there's a field called `ISBN` that can be used to compare them, but I don't know how to do that. 2) Yes I can support ES5 – nick Apr 24 '17 at 13:50
  • See addition in my answer. – trincot Apr 24 '17 at 14:01
  • Thanks! Just a small thing, I had to change this: `products.push(product)` for this: `products.push(uniq[product])` because I was getting the ISBN instead of the objects. – nick Apr 24 '17 at 14:10