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

#javascript

Вопрос:

У меня есть этот набор данных:

 const data = {
  catA: {
    color: ['red', 'blue', 'yellow', 'gold'],
    animal: ['cat', 'dog', 'hamster'],
  },
  catB: {
    city: ['Syndey', 'Paris', 'Rome', 'London', 'Tokyo', 'Delhi', 'Cairo']
  }
}
 

и то, что я хотел бы получить, — это аналогичная структура данных с процентным значением, присвоенным каждому элементу в массивах. Сумма процентных значений должна составлять 100.
Некоторые элементы могут иметь значение 0, а некоторые нет, не важно, сколько элементов имеют процент 0.

Ниже приведены некоторые примеры того, чего я ожидаю:

 const res1 = {
  catA: {
    color: [['red', 0], ['blue', 10], ['yellow', 5], ['gold', 0]],
    animal: [['cat', 50], ['dog', 0], ['hamster', 5]],
  },
  catB: {
    city: [['Syndey', 20], ['Paris', 0], ['Rome', 0], ['London', 10], ['Tokyo', 0], ['Delhi', 0], ['Cairo', 0]]
  }
}

const res2 = {
  catA: {
    color: [['red', 100], ['blue', 0], ['yellow', 0], ['gold', 0]],
    animal: [['cat', 0], ['dog', 0], ['hamster', 0]],
  },
  catB: {
    city: [['Syndey', 0], ['Paris', 0], ['Rome', 0], ['London', 0], ['Tokyo', 0], ['Delhi', 0], ['Cairo', 0]]
  }
}

const res3 = {
  catA: {
    color: [['red', 60], ['blue', 0], ['yellow', 0], ['gold', 10]],
    animal: [['cat', 5], ['dog', 5], ['hamster', 20]],
  },
  catB: {
    city: [['Syndey', 5], ['Paris', 5], ['Rome', 10], ['London', 5], ['Tokyo', 0], ['Delhi', 5], ['Cairo', 10]]
  }
}
 

Поэтому я создал функцию, которая возвращает массив значений, сумма которых фиксирована:

 function randomNumbersWithFixedSum(quantity, sum) {
  if (quantity === 1) return [sum];
  const randomNum = _.random(0, sum);
  return [
    randomNum,
    ...randomNumbersWithFixedSum(quantity - 1, sum - randomNum),
  ];
}
 

_.random является функцией Lodash.

Теперь, как я могу создать функцию, которая возвращает результат, аналогичный предыдущим?

 const dataWithPercentages = addPercentages(data, 100)

function addPercentages(dataset, fixedSum) {
   const flatData = Object.values(data).flat();
   const dataCounter = flatData.length;
   const percentages = randomNumbersWithFixedSum(random(1, dataCounter), fixedSum)

   // and here?
}
 

Я не знаю, с чего начать. Может быть, я должен использовать reduce , но как?

Ответ №1:

Мы могли бы использовать lodash _.range и _.shuffle для этого мы создаем случайные числа с оставшейся суммой, используя Array.map.

Затем мы проходим по каждому пути, который мы хотим задать, уменьшая sum допустимое значение на каждой итерации.

 const data = {
  catA: {
    color: ['red', 'blue', 'yellow', 'gold'],
    animal: ['cat', 'dog', 'hamster'],
  },
  catB: {
    city: ['Sydney', 'Paris', 'Rome', 'London', 'Tokyo', 'Delhi', 'Cairo']
  }
}

// Add a last item argument to tell the function to use all the remaining sum input.
function randomNumbersWithFixedSum(input, sum, multiplier, lastItem) {
    return _.shuffle(_.range(input.length).map((n, i) => {
         const result = (i === input.length-1) amp;amp; lastItem ? sum: Math.floor(Math.random() * (sum))
         sum -= resu<
         return resu<
    })).map((x,i) => [input[i], x * multiplier]);
}
   
function getArrayPaths(obj, keys = [], paths = []) {
    Object.keys(obj).forEach(key => { 
        if (Array.isArray(obj[key])) {
            paths.push([...keys, key].join("."))
        } else if (typeof obj === 'object') {
            getArrayPaths(obj[key], [...keys, key], paths)
        }
    })
    return paths;
}

let sum = 100;
let paths = getArrayPaths(data);
let result = paths.reduce((acc, path, i) => { 
    let arr = _.get(acc, path);
    let newArr = randomNumbersWithFixedSum(arr, sum, 1, i === paths.length -1);
    // Reduce our remaining sum...
    sum -= _.sum(newArr.map(a => a[1]));
    _.set(acc, path, newArr);
    return acc;
}, data);


console.log("Result:", result);
console.log("Remaining sum:", sum); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG ljU96qKRCWh quCY7yefSmlkQw1ANQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> 

Комментарии:

1. Спасибо, Терри, но это не то, что мне нужно. Мне нужна та же data структура

2. Ммм нет, обратите внимание, что ваша сумма намного больше 100

Ответ №2:

Вот довольно простой reduce() способ, который использует рекурсию для прохождения вашего пройденного dataset . Для каждого свойства, которое является объектом (но не массивом), он рекурсирует, на обратном пути filters() извлекает свойства, которые являются массивами, суммирует их общую длину и генерирует массив «процентов». Затем он повторяет каждое свойство массива и отключает необходимое количество процентов и помещает их в свойство. splices

 function randomNumbersWithFixedSum(quantity, sum) {
  let
    randArr = Array.from({ length: quantity }, () => Math.random()),
    randTotal = randArr.reduce((a, b) => a   b, 0),
    resultSum = 0,
    percent = n => Math.floor((n / randTotal) * sum);

  return randArr.map((n, i) => i === quantity - 1 ? sum - resultSum : (resultSum  = percent(n), percent(n)));
}

function addPercentages(dataset, fixedSum) {
  const zip = (a, b) => a.map((k, i) => [k, b[i]]);

  return Object.keys(dataset).reduce((acc, key) => {
    let element = dataset[key];

    if (typeof element === 'object' amp;amp; !Array.isArray(element)) {
      element = addPercentages(element, fixedSum);

      const
        arrays = Object.entries(element).filter(([, e]) => Array.isArray(e)),
        arraysTotalLength = arrays.reduce((a, [, { length }]) => a   length, 0),
        randomData = randomNumbersWithFixedSum(arraysTotalLength, fixedSum);

      for (const [_key, array] of arrays) {
        const r = randomData.splice(0, array.length);
        element[_key] = zip(array, r);
      }
    }

    return acc[key] = element, acc;
  }, {});
}

const
  data = { catA: { id: 1, color: ['red', 'blue', 'yellow', 'gold'], animal: ['cat', 'dog', 'hamster'], catC: { id: 3, fish: ['cod', 'halibut', 'monkfish'] } }, catB: { id: 2, city: ['Syndey', 'Paris', 'Rome', 'London', 'Tokyo', 'Delhi', 'Cairo'] } },
  result = addPercentages(data, 100);

console.log(JSON.stringify(result, null, 2)); 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 

Ответ №3:

это сделает работу за вас. все,что вам нужно сделать, это изменить «Math.floor(Math.random() * 100) 1», который выводит случайное число с вашими желаемыми значениями

 function alternate(data) {
    return Object.keys(data).reduce((p, c) => ({
            ...p,
            [c]: Object.keys(data[c]).reduce((pr, cu) => ({
                    ...pr,
                    [cu]: data[c][cu].map(i => ([i, Math.floor(Math.random() * 100)   1]))
                })

                , {})
        })
        , {})
}


const data = {
  catA: {
    color: ['red', 'blue', 'yellow', 'gold'],
    animal: ['cat', 'dog', 'hamster'],
  },
  catB: {
    city: ['Syndey', 'Paris', 'Rome', 'London', 'Tokyo', 'Delhi', 'Cairo']
  }
}

console.log(alternate(data)) 

Комментарии:

1. Спасибо, но «общая сумма data » должна быть 100