Выбор случайного взвешенного объекта из массива - PullRequest
0 голосов
/ 20 марта 2019

Я пытаюсь найти способ выбрать случайный объект из массива, основываясь на его свойстве веса. Вот пример массива:

var item = [{
    verDiv: 'div-gpt-ad-1553003087342-0',
    verKv: 'version1',
    verSize: [300, 250],
    weight: 10 //should be chosen in 10% of cases
},
{
    verDiv: 'div-gpt-ad-1553003087342-1',
    verKv: 'version2',
    verSize: [300, 250],
    weight: 25 //should be chosen in 25% of cases
},
{
    verDiv: 'div-gpt-ad-1553003087342-2',
    verKv: 'version3',
    verSize: [160, 600],
    weight: 25 //should be chosen in 25% of cases
},
{
    verDiv: 'div-gpt-ad-1553003087342-3',
    verKv: 'version4',
    verSize: [728, 90],
    weight: 40 //should be chosen in 40% of cases
}];

Что я хочу сделать, так это выбрать один из четырех объектов с помощью функции, которая учитывает их весовые свойства, чтобы я мог затем вызывать другие свойства при необходимости.

console.log([item[weightFunction()].verDiv]);
console.log([item[weightFunction()].verKv]);
console.log([item[weightFunction()].verSize]);

РЕДАКТИРОВАТЬ : Выше приведено только предположение, я уверен, что есть лучшие способы сделать это.

Ответы [ 4 ]

1 голос
/ 20 марта 2019

Вы можете взять замыкание по массиву весов со всеми весами и вернуть функцию, которая получает индекс, основанный на сумме всех весов.

function getWeightedDistribution(weights) {
    return function () {
        var random = Math.random(),
            sum = 0;
        return weights.findIndex(w => random < (sum += w));
    };
}

var weights = [0.1, 0.25, 0.25, 0.4], // all values have to sum to 1
    i;
    weightFunction = getWeightedDistribution(weights),
    counts = [0, 0, 0, 0];

for (i = 0; i < 1e6; i++) counts[weightFunction()]++;

console.log(...counts);

Вместе с вашим кодом

function getWeightedDistribution(weights) { // weights sums up to 1
    return function () {
        var random = Math.random(),
            sum = 0;
        return weights.findIndex(w => random < (sum += w));
    };
}

var item = [{ verDiv: 'div-gpt-ad-1553003087342-0', verKv: 'version1', verSize: [300, 250], weight: 10 }, { verDiv: 'div-gpt-ad-1553003087342-1', verKv: 'version2', verSize: [300, 250], weight: 25 }, { verDiv: 'div-gpt-ad-1553003087342-2', verKv: 'version3', verSize: [160, 600], weight: 25 }, { verDiv: 'div-gpt-ad-1553003087342-3', verKv: 'version4', verSize: [728, 90], weight: 40 }],
    weightFunction = getWeightedDistribution(item.map(({ weight }) => weight / 100));

console.log(item[weightFunction()].verDiv);
console.log(item[weightFunction()].verKv);
console.log(item[weightFunction()].verSize);
1 голос
/ 20 марта 2019

Предполагая, что сумма всех весов в точности равна 100 (в противном случае рассчитайте ее и используйте в качестве cumul начальное значение и случайный множитель:

function weightFunction(items) {
  var cumul = 100
  var random = Math.floor(Math.random() * 100)

  for(var i = 0; i < items.length; i++) {
    cumul -= items[i].weight
    if (random >= cumul) {
      return items[i]
    }
  }
}
0 голосов
/ 20 марта 2019
  • определить массив с именем stat_map, который будет иметь размер sum of all weights, в конечном итоге
  • заполнить stat_map индексами предметов, чтобы stat_map содержал столько же индексов предметов, сколько и его вес.
  • теперь stat_map содержит 10 0 (индекс первого элемента), 25 1 (индекс второго элемента), 25 2 (индекс третьего элемента), 40 3 (индекс четвертого элемента)
  • если вы выберете случайный элемент из stat_map, это будет индекс выбранного элемента, и очевидно, что элементы будут выбраны в соответствии с их весом.

const item = [{
    verDiv: 'div-gpt-ad-1553003087342-0',
    verKv: 'version1',
    verSize: [300, 250],
    weight: 10 //should be chosen in 10% of cases
},
{
    verDiv: 'div-gpt-ad-1553003087342-1',
    verKv: 'version2',
    verSize: [300, 250],
    weight: 25 //should be chosen in 25% of cases
},
{
    verDiv: 'div-gpt-ad-1553003087342-2',
    verKv: 'version3',
    verSize: [160, 600],
    weight: 25 //should be chosen in 25% of cases
},
{
    verDiv: 'div-gpt-ad-1553003087342-3',
    verKv: 'version4',
    verSize: [728, 90],
    weight: 40 //should be chosen in 40% of cases
}];

const randomItem = (item) => {
  const stat_map = []
  item.map((v, i) => stat_map.push(...new Array(v.weight).fill(i)))
  const rand = Math.floor(Math.random() * stat_map.length)
  return item[stat_map[rand]]
}

console.log(randomItem(item))
0 голосов
/ 20 марта 2019

Это более абстрактный подход к проблеме, который позволяет суммарному весу быть выше 100, и вы можете определить, как будет получено свойство веса для каждого элемента.

Способ, которым это работает, заключается в создании карты диапазонов для каждого значения, и он возвращает первый элемент, диапазон которого «поймал» случайное число.

var item = [{
    verDiv: 'div-gpt-ad-1553003087342-0',
    verKv: 'version1',
    verSize: [300, 250],
    weight: 10 //should be chosen in 10% of cases
  },
  {
    verDiv: 'div-gpt-ad-1553003087342-1',
    verKv: 'version2',
    verSize: [300, 250],
    weight: 25 //should be chosen in 25% of cases
  },
  {
    verDiv: 'div-gpt-ad-1553003087342-2',
    verKv: 'version3',
    verSize: [160, 600],
    weight: 25 //should be chosen in 25% of cases
  },
  {
    verDiv: 'div-gpt-ad-1553003087342-3',
    verKv: 'version4',
    verSize: [728, 90],
    weight: 40 //should be chosen in 40% of cases
  }
];

function weightFunction(list, getWeight) {
  var total = 0; // Faster than doing another loop with reduce
  var map = list.reduce(function(result, value, index) {
    var currentWeight = getWeight(value, index);
    total += currentWeight;
    result[total] = value;
    return result;
  }, {});
  var random = Math.random() * total;
  return map[Object.keys(map).find(function(index) {
    return index >= random;
  })];
}

console.log(weightFunction(item, x => x.weight).verDiv);
console.log(weightFunction(item, x => x.weight).verKv);
console.log(weightFunction(item, x => x.weight).verSize);
...