具有不断变化的值的数组分布

时间:2016-08-16 21:18:32

标签: javascript arrays loops distribution

我有一系列用户需要提供产品,例如:

值的数量总是在变化 - 第一次执行分配时,可能只有一个值,第二次是三个值。

这些值必须进行洗牌和分发,同时确保个人不会获得相同的值两次。

到目前为止,我设法做的就是对阵列进行洗牌,并为每个用户提供十种产品:

var users = ["user1", "user2", "user3"];

var products = ["p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9", "p10","p11", "p12", "p13", "p14", "p15", "p16", "p17", "p18", "p19", "p20", "p21", "p22", "p23", "p24", "p25", "p26", "p27", "p28", "p29", "p30"];

function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;

while (0 != currentIndex) {

  randomIndex = Math.floor(Math.random() * currentIndex);
  currentIndex -= 1;

  temporaryValue = array[currentIndex];
  array[currentIndex] = array[randomIndex];
  array[randomIndex] = temporaryValue;
}

return array;
}

i = 0;
while (i < users.length) { 
    newArray = (shuffle(products)).slice(0, 10)
    console.log(users[i++] + ' products: ' + newArray);
}

结果:

user1 products: p5,p6,p13,p2,p19,p22,p8,p20,p27,p28
user2 products: p6,p30,p22,p9,p25,p2,p7,p17,p19,p10
user3 products: p20,p25,p23,p4,p28,p9,p12,p14,p21,p17

1 个答案:

答案 0 :(得分:0)

这个问题的难点在于您希望分发的产品比产品多一些,因此少数产品会归因于多个用户。然而,您还希望均匀地分发产品,并确保没有人获得重复的产品。

为了有效地做到这一点,即在用户和产品的线性时间内,需要更多的代码。

以下算法将坚持:

  • 所有产品将至少分发一次;
  • 其中约20%(向上舍入)将再次分发;
  • 没有用户会两次获得相同的产品;
  • 所有用户都会收到几乎相同数量的产品。区别在于最多一种产品;
  • 随机选择可能少一件产品的用户;
  • 用户收到的产品的顺序是随机的。

function shuffled(arr) {
  // Does NOT alter the given array, but returns new one
  arr = arr.slice();
  var i = arr.length;
  while (i) {
    var j = Math.floor(Math.random() * i--);
    [ arr[i], arr[j] ] = [ arr[j], arr[i] ];
  }
  return arr;
};

function distribute(products, users) {
    // Cases with only one possible outcome:
    if (users.length === 0) return {};
    if (users.length === 1) return { [users[0]]: shuffled(products) };    

    // We will distribute about 20% of the products twice
    var distributeCount = Math.ceil(products.length * 1.2); 
    
    // Create object structure to return: key is user name, 
    //   value is set of products
    var possessions = users.reduce( (acc, user) => 
        Object.assign(acc, { [user]: new Set() }), {} );

    // Order of users in which products will be assigned
    var usersLeft = shuffled(users);
    while (usersLeft.length < distributeCount)
        usersLeft = usersLeft.concat(usersLeft);
    usersLeft.length = distributeCount; // clip array to exact size
    
    // Order of products in which they will be assigned; double length
    var productsLeft = shuffled(products).concat(shuffled(products)); 

    // Assign products to users
    while (usersLeft.length) {
        product = productsLeft.pop();
        user = usersLeft.pop();
        if (possessions[user].has(product)) { // not allowed to add again
            if (usersLeft.length) { // not the very last product distribution
                // swap with next user, as others do not have this product
                [ user, usersLeft[usersLeft.length-1] ] = 
                    [ usersLeft[usersLeft.length-1], user ]; 
            } else {
                // The very last product distribution:
                // pick another product: this will eventually work, as 80% 
                // of the products are available, 
                // and a user only has at most 50% of them.
                do {
                    product = productsLeft.pop();
                    if (!product) throw "Could not find product for user";
                } while (possessions[user].has(product));
            }
        }
        possessions[user].add(product);
    }
    // Turn Sets into Arrays
    for (user in possessions) possessions[user] = [...possessions[user]];
    return possessions;
}

var users = ["user1", "user2", "user3"];
var products = ["p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9", "p10","p11", 
                "p12", "p13", "p14", "p15", "p16", "p17", "p18", "p19", "p20", "p21", 
                "p22", "p23", "p24", "p25", "p26", "p27", "p28", "p29", "p30"];

var result = distribute(products, users);

console.log(result);