问题
I was wondering what was the most concise way to get an array of a certain size, of unique random numbers.
I get random numbers like this:
times(4, () => random(30, 95));
However this is not unique. I can filter this with uniq
but I need to gurantee length of 4 of array. And I want to do it the lodash way. Any ideas?
回答1:
I know this isn't "the lodash way", but it guarantees uniqueness, and allows you to use the same arguments as you were using before. It also scales better than methods that require a binary or linear search through an array, as set.has() is O(1) on average, rather than O(log(n)) or O(n).
function uniqRandom (times, ...args) {
const set = new Set()
while (times > 0) {
const rand = _.random(...args)
if (!set.has(rand)) {
set.add(rand)
times--
}
}
return Array.from(set)
}
console.log(uniqRandom(4, 30, 33));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
回答2:
Much easiear would be...
const uniqRandomNumbers = _.sampleSize(_.range(30, 95), 4);
console.log(uniqRandomNumbers);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
回答3:
I solved it using from a functional programming perspective. refillRandom
is a recursive function that checks the number of items left to generate and calls itself again until the are the required number of items.
It also throws an Error
when is imposible to generate the sequence, if the distance between min
and max
random number is greater than the required unique items. It's better to throw an Error than waiting forever.
const generator = (min, offset) => () =>
Math.floor(Math.random() * offset + min);
const refillRandom = (list, min, max, times) => {
const offset = max - min,
num = times - list.length;
if (times > offset) {
throw new Error("Imposible to generate it");
}
const result = _.uniq(_.times(num, generator(min,offset)));
if (result.length < num) {
return result.concat(
refillRandom(list, min, max, num - result.length)
);
}
return result;
}
const r = refillRandom([], 30, 95, 4);
console.log(r);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
EDIT: I found another solution, I mantain an ordered array of generated numbers and increment the generated number so it get mapped to a number that has not been generated yet. This way I only call random the times
specified.
const randomGenerator = (min, offset, generated, times) => {
if (!times || !offset) return generated;
var number = Math.floor(Math.random() * offset + min);
const len = generated.length;
for (var i = 0; i < len; i++) {
if (generated[i] <= number) {
number++;
} else {
generated.splice(i, 0, number);
return randomGenerator(min, offset - 1, generated, times - 1);
}
}
generated[i] = number;
return randomGenerator(min, offset - 1, generated, times - 1);
};
const r = randomGenerator(30, 95 - 30, [], 12);
console.log(r);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
来源:https://stackoverflow.com/questions/49743588/sized-array-of-random-unique-numbers