I would write this atop a function that partially shuffles an array, returning a randomized choice of the first n elements from it. Furthermore, I would randomly choose the number of elements to pick. We could do it with boundaries ("pick from 1 to 3 elements from the list") or without ("pick from 1 to list.length elements").
Both of those options are shown here, with anyRandomSubset built atop randomSubset, which takes the boundaries. We could easily skip the intermediary if the boundaries are not needed.
Here is an implementation of this:
const {random, floor} = Math
const excluding = (i) => (xs) =>
[... xs .slice (0, i), ... xs .slice (i + 1)]
const partialShuffle = (n) => (xs, i = floor(random () * xs .length)) =>
n <= 0 || n > xs .length || xs .length == 0
? []
: [xs[i], ... partialShuffle (n - 1) (excluding (i) (xs))]
const randomSubset = (lo, hi) => (xs) =>
partialShuffle (floor (random () * (hi - lo + 1) + lo)) (xs)
const anyRandomSubset = (xs) =>
randomSubset (1, xs .length) (xs)
for (var i = 0; i < 20; i++) {
console .log (anyRandomSubset (["mike", "ted","bill","mark"]))
}
.as-console-wrapper {min-height: 100% !important; top: 0}
The partialShuffle is a variant of this complete shuffle function:
const shuffle = (xs, i = floor(random () * xs.length)) =>
xs.length == 0
? []
: [xs[i], ... shuffle (excluding (i) (xs))]
This is a recursive formulation of the most common true shuffle of an array, the Fisher-Yates algorithm. This recursive version would run into recursion-depth problems if we used it for a long array (or for a large n in our partial version above.) But it's probably fine for smallish arrays.
Note that partialShuffle and its helper function excluding are both useful utility functions. Only randomSubset and anyRandomSubset are specific to this problem.