3

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?

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
Noitidart
  • 35,443
  • 37
  • 154
  • 323

3 Answers3

3

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>
Rami Chasygov
  • 2,714
  • 7
  • 25
  • 37
  • 1
    This needs a limited range due to creation of huge array by `range`. So i cant do `range(0, 50000000)`. – Noitidart Jul 22 '19 at 13:49
2

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>
Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
  • Thanks for this, it works but I also came up with something like this. I was looking for the lodash way. – Noitidart Apr 10 '18 at 08:16
1

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>
David Lemon
  • 1,560
  • 10
  • 21
  • Except that `_.random()` allows non-integer return values via the third optional parameter set to `true`, in which case the distance won't matter as long as it's non-zero. – Patrick Roberts Apr 10 '18 at 15:43
  • 1
    It is not said but I supposed he needs integer values. Also I didn't used _.random() – David Lemon Apr 10 '18 at 16:01
  • Very interesting. Yep integer is what i need. I upvoted it is similar to looping solution. I just feel lodash has something smoother. Thanks very much for this share! – Noitidart Apr 10 '18 at 20:00
  • I don't think that lodash has something like that, it is too specific. – David Lemon Apr 11 '18 at 07:54