Выбор случайного элемента из набора

Как выбрать случайный элемент из набора? Меня особенно интересует выбор случайного элемента из HashSet или LinkedHashSet в Java. Также приветствуются решения для других языков.

Вы должны указать некоторые условия, чтобы увидеть, действительно ли это то, что вы хотите. - Какое время вы собираетесь выбирать случайный элемент? - Нужно ли хранить данные в HashSet или LinkedHashSet, ни один из них не может быть доступен случайным образом. - Хеш-набор большой? Ключи маленькие?

David Nehme 25.09.2008 06:03
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
189
1
210 719
33
Перейти к ответу Данный вопрос помечен как решенный

Ответы 33

Поскольку вы сказали: «Также приветствуются решения для других языков», вот версия для Python:

>>> import random
>>> random.choice([1,2,3,4,5,6])
3
>>> random.choice([1,2,3,4,5,6])
4

Только [1,2,3,4,5,6] - это не набор, а список, поскольку он не поддерживает такие вещи, как быстрый поиск.

Thomas Ahle 27.12.2009 13:20

Вы все еще можете: >>> random.choice (list (set (range (5)))) >>> 4 Не идеально, но подойдет, если вам это абсолютно необходимо.

SapphireSun 26.07.2010 23:59

В Java:

Set<Integer> set = new LinkedHashSet<Integer>(3);
set.add(1);
set.add(2);
set.add(3);

Random rand = new Random(System.currentTimeMillis());
int[] setArray = (int[]) set.toArray();
for (int i = 0; i < 10; ++i) {
    System.out.println(setArray[rand.nextInt(set.size())]);
}

Ваш ответ работает, но он не очень эффективен из-за части set.toArray ().

Clue Less 24.09.2008 05:34

вы должны переместить toArray за пределы цикла.

David Nehme 25.09.2008 05:57
Ответ принят как подходящий

int size = myHashSet.size();
int item = new Random().nextInt(size); // In real life, the Random object should be rather more shared than this
int i = 0;
for(Object obj : myhashSet)
{
    if (i == item)
        return obj;
    i++;
}

Если myHashSet большой, то это будет довольно медленное решение, поскольку в среднем для поиска случайного объекта потребуется (n / 2) итераций.

daniel 24.09.2008 06:30

если ваши данные находятся в наборе хешей, вам понадобится время O (n). Нет никакого способа обойти это, если вы просто выбираете один элемент, а данные хранятся в HashSet.

David Nehme 25.09.2008 06:00

@David Nehme: Это недостаток спецификации HashSet в Java. В C++ типично иметь возможность напрямую обращаться к сегментам, составляющим хэш-набор, что позволяет нам более эффективно выбирать случайные элементы. Если в Java необходимы случайные элементы, возможно, стоит определить собственный набор хэшей, который позволит пользователю заглянуть под капот. См. [Boost's docs] [1] для получения дополнительной информации. [1] boost.org/doc/libs/1_43_0/doc/html/unordered/buckets.html

Aaron McDaid 20.07.2010 17:50

Если набор не изменяется при многократном доступе, вы можете скопировать его в массив, а затем получить доступ к O (1). Просто используйте myHashSet.toArray ()

ykaganovich 22.07.2010 00:23

@AaronMcDaid Даже тогда вам придется учитывать пустые ведра.

Viktor Dahl 20.08.2012 12:34

@ykaganovich, не усугубит ли это положение, ведь набор нужно было бы скопировать в новый массив? docs.oracle.com/javase/7/docs/api/java/util/… "этот метод должен выделить новый массив, даже если эта коллекция поддерживается массивом"

anton1980 22.11.2014 06:19

@ anton1980 См. обсуждение под ответом Дэна Дайера stackoverflow.com/a/129386/10026

ykaganovich 24.11.2014 03:33

Разве вы не можете просто получить размер / длину набора / массива, сгенерировать случайное число от 0 до размера / длины, а затем вызвать элемент, индекс которого соответствует этому числу? Я уверен, что в HashSet есть метод .size ().

В псевдокоде -

function randFromSet(target){
 var targetLength:uint = target.length()
 var randomIndex:uint = random(0,targetLength);
 return target[randomIndex];
}

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

David Haley 29.06.2010 23:01

PHP, предполагая, что "set" - это массив:

$foo = array("alpha", "bravo", "charlie");
$index = array_rand($foo);
$val = $foo[$index];

Функции Mersenne Twister лучше, но в PHP нет эквивалента array_rand для MT.

Большинство реализаций набора не имеют оператора get (i) или индексации, поэтому id предполагает, что именно поэтому OP указал свой набор

DownloadPizza 29.06.2020 16:58

PHP, используя MT:

$items_array = array("alpha", "bravo", "charlie");
$last_pos = count($items_array) - 1;
$random_pos = mt_rand(0, $last_pos);
$random_item = $items_array[$random_pos];

Решение Javascript;)

function choose (set) {
    return set[Math.floor(Math.random() * set.length)];
}

var set  = [1, 2, 3, 4], rand = choose (set);

Или альтернативно:

Array.prototype.choose = function () {
    return this[Math.floor(Math.random() * this.length)];
};

[1, 2, 3, 4].choose();

Я предпочитаю вторую альтернативу. :-)

marcospereira 24.09.2008 16:41

ох, мне нравится расширять добавление нового метода массива!

matt lohkamp 28.09.2008 02:06

Несколько связанное с этим Знаете ли вы:

В java.util.Collections есть полезные методы для перетасовки целых коллекций: Collections.shuffle(List<?>) и Collections.shuffle(List<?> list, Random rnd).

Потрясающий! На это нигде в java-документе нет перекрестных ссылок! Нравится Python random.shuffle ()

smci 08.02.2012 23:48

Но это работает только со списками, то есть структурами, имеющими функцию .get ().

coneyhelixlake 20.02.2015 01:27

@ bourbaki4481472 абсолютно правильно. Это работает только для тех коллекций, которые расширяют интерфейс List, но не для интерфейса Set, обсуждаемого OP.

Thomas 28.02.2015 23:57

Однако OP желает выбрать элемент а (как я полагаю). Перемешивание всего списка (а также перенос всех элементов в наборе в список) очень дорого обходится для этого одного элемента ... Если у вас есть список, вы используете List.get(new Random().nextInt(size)).

Erk 01.01.2021 01:56

Perl 5

@hash_keys = (keys %hash);
$rand = int(rand(@hash_keys));
print $hash{$hash_keys[$rand]};

Вот один из способов сделать это.

Значок имеет тип набора и оператор случайного элемента, унарный "?", Поэтому выражение

? set( [1, 2, 3, 4, 5] )

выдаст случайное число от 1 до 5.

Случайное начальное число инициализируется 0 при запуске программы, поэтому для получения разных результатов при каждом запуске используйте randomize().

В C#

        Random random = new Random((int)DateTime.Now.Ticks);

        OrderedDictionary od = new OrderedDictionary();

        od.Add("abc", 1);
        od.Add("def", 2);
        od.Add("ghi", 3);
        od.Add("jkl", 4);


        int randomIndex = random.Next(od.Count);

        Console.WriteLine(od[randomIndex]);

        // Can access via index or key value:
        Console.WriteLine(od[1]);
        Console.WriteLine(od["def"]);

В шепелявке

(defun pick-random (set)
       (nth (random (length set)) set))

Это работает только для списков, верно? С ELT он может работать для любой последовательности.

Ken 20.12.2010 06:41

Если вы хотите сделать это на Java, вам следует подумать о копировании элементов в какую-то коллекцию с произвольным доступом (например, ArrayList). Потому что, если ваш набор не маленький, доступ к выбранному элементу будет дорогостоящим (O (n) вместо O (1)). [ed: список копий тоже O (n)]

В качестве альтернативы вы можете поискать другую реализацию Set, которая более точно соответствует вашим требованиям. ListOrderedSet от Commons Collections выглядит многообещающим.

Копирование в список будет стоить O (n) по времени, а также использовать O (n) памяти, так почему это лучший выбор, чем выборка с карты напрямую?

mdma 20.07.2010 17:51

Это зависит от того, сколько раз вы хотите выбрать из набора. Копирование - это одноразовая операция, и затем вы можете выбирать из набора столько раз, сколько вам нужно. Если вы выбираете только один элемент, то да, копия не ускоряет работу.

Dan Dyer 12.04.2011 04:10

Это только одноразовая операция, если вы хотите иметь возможность выбирать с повторением. Если вы хотите, чтобы выбранный элемент был удален из набора, вы вернетесь к O (n).

TurnipEntropy 15.04.2017 16:58

Решение Clojure:

(defn pick-random [set] (let [sq (seq set)] (nth sq (rand-int (count sq)))))

Это решение также является линейным, потому что для получения элемента nth вы также должны пройти через seq.

Bruno Kim 24.03.2013 11:19

Это также линейно, так как прекрасно вписывается в одну строку: D

Krzysztof Wolny 02.09.2016 23:37

К сожалению, это невозможно сделать эффективно (лучше, чем O (n)) ни в одном из контейнеров набора стандартной библиотеки.

Это странно, поскольку очень легко добавить функцию рандомизированного выбора как к хэш-наборам, так и к двоичным наборам. В не разреженном наборе хешей вы можете пробовать случайные записи, пока не получите результат. Для двоичного дерева вы можете произвольно выбирать между левым или правым поддеревом с максимумом O (log2) шагов. Я реализовал демонстрацию следующего ниже:

import random

class Node:
    def __init__(self, object):
        self.object = object
        self.value = hash(object)
        self.size = 1
        self.a = self.b = None

class RandomSet:
    def __init__(self):
        self.top = None

    def add(self, object):
        """ Add any hashable object to the set.
            Notice: In this simple implementation you shouldn't add two
                    identical items. """
        new = Node(object)
        if not self.top: self.top = new
        else: self._recursiveAdd(self.top, new)
    def _recursiveAdd(self, top, new):
        top.size += 1
        if new.value < top.value:
            if not top.a: top.a = new
            else: self._recursiveAdd(top.a, new)
        else:
            if not top.b: top.b = new
            else: self._recursiveAdd(top.b, new)

    def pickRandom(self):
        """ Pick a random item in O(log2) time.
            Does a maximum of O(log2) calls to random as well. """
        return self._recursivePickRandom(self.top)
    def _recursivePickRandom(self, top):
        r = random.randrange(top.size)
        if r == 0: return top.object
        elif top.a and r <= top.a.size: return self._recursivePickRandom(top.a)
        return self._recursivePickRandom(top.b)

if __name__ == '__main__':
    s = RandomSet()
    for i in [5,3,7,1,4,6,9,2,8,0]:
        s.add(i)

    dists = [0]*10
    for i in xrange(10000):
        dists[s.pickRandom()] += 1
    print dists

На выходе я получил [995, 975, 971, 995, 1057, 1004, 966, 1052, 984, 1001], так что распределение швов хорошее.

Я боролся с той же проблемой для себя, и я еще не решил, стоит ли повышение производительности этого более эффективного выбора на накладные расходы, связанные с использованием коллекции на основе Python. Я, конечно, мог бы доработать его и перевести на C, но сегодня для меня это слишком много работы :)

Причина, по которой я думаю, что это не реализовано в двоичном дереве, заключается в том, что такой метод не будет выбирать элементы равномерно. Поскольку это узлы без левых / правых дочерних элементов, может возникнуть ситуация, когда левый дочерний элемент содержит больше элементов, чем правый дочерний элемент (или наоборот), это сделает выбор элемента в правом (или левом) дочернем элементе более вероятным.

Willem Van Onsem 13.12.2012 05:55

@CommuSoft: Вот почему я храню размер каждого поддерева, поэтому я могу выбирать свои вероятности на основе них.

Thomas Ahle 13.12.2012 17:09

C++. Это должно быть достаточно быстрым, поскольку не требует повторения по всему набору или его сортировки. Это должно работать из коробки с большинством современных компиляторов, если они поддерживают tr1. Если нет, возможно, вам придется использовать Boost.

Документы Boost могут помочь здесь объяснить это, даже если вы не используете Boost.

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

//#include <boost/unordered_set.hpp>  
//using namespace boost;
#include <tr1/unordered_set>
using namespace std::tr1;
#include <iostream>
#include <stdlib.h>
#include <assert.h>
using namespace std;

int main() {
  unordered_set<int> u;
  u.max_load_factor(40);
  for (int i=0; i<40; i++) {
    u.insert(i);
    cout << ' ' << i;
  }
  cout << endl;
  cout << "Number of buckets: " << u.bucket_count() << endl;

  for(size_t b=0; b<u.bucket_count(); b++)
    cout << "Bucket " << b << " has " << u.bucket_size(b) << " elements. " << endl;

  for(size_t i=0; i<20; i++) {
    size_t x = rand() % u.size();
    cout << "we'll quickly get the " << x << "th item in the unordered set. ";
    size_t b;
    for(b=0; b<u.bucket_count(); b++) {
      if (x < u.bucket_size(b)) {
        break;
      } else
        x -= u.bucket_size(b);
    }
    cout << "it'll be in the " << b << "th bucket at offset " << x << ". ";
    unordered_set<int>::const_local_iterator l = u.begin(b);
    while(x>0) {
      l++;
      assert(l!=u.end(b));
      x--;
    }
    cout << "random item is " << *l << ". ";
    cout << endl;
  }
}

прочитав эту ветку, лучшее, что я мог написать, это:

static Random random = new Random(System.currentTimeMillis());
public static <T> T randomChoice(T[] choices)
{
    int index = random.nextInt(choices.length);
    return choices[index];
}

Вопрос касается наборов, а не массивов. Также нет необходимости заполнять Random текущим временем; new Random() из коробки возвращает правильно засеянный экземпляр.

dimo414 28.04.2016 18:07

Быстрое решение для Java с использованием ArrayList и HashMap: [element -> index].

Мотивация: мне нужен был набор предметов со свойствами RandomAccess, особенно чтобы выбрать случайный предмет из набора (см. Метод pollRandom). Случайная навигация в двоичном дереве не точна: деревья не сбалансированы идеально, что не приведет к равномерному распределению.

public class RandomSet<E> extends AbstractSet<E> {

    List<E> dta = new ArrayList<E>();
    Map<E, Integer> idx = new HashMap<E, Integer>();

    public RandomSet() {
    }

    public RandomSet(Collection<E> items) {
        for (E item : items) {
            idx.put(item, dta.size());
            dta.add(item);
        }
    }

    @Override
    public boolean add(E item) {
        if (idx.containsKey(item)) {
            return false;
        }
        idx.put(item, dta.size());
        dta.add(item);
        return true;
    }

    /**
     * Override element at position <code>id</code> with last element.
     * @param id
     */
    public E removeAt(int id) {
        if (id >= dta.size()) {
            return null;
        }
        E res = dta.get(id);
        idx.remove(res);
        E last = dta.remove(dta.size() - 1);
        // skip filling the hole if last is removed
        if (id < dta.size()) {
            idx.put(last, id);
            dta.set(id, last);
        }
        return res;
    }

    @Override
    public boolean remove(Object item) {
        @SuppressWarnings(value = "element-type-mismatch")
        Integer id = idx.get(item);
        if (id == null) {
            return false;
        }
        removeAt(id);
        return true;
    }

    public E get(int i) {
        return dta.get(i);
    }

    public E pollRandom(Random rnd) {
        if (dta.isEmpty()) {
            return null;
        }
        int id = rnd.nextInt(dta.size());
        return removeAt(id);
    }

    @Override
    public int size() {
        return dta.size();
    }

    @Override
    public Iterator<E> iterator() {
        return dta.iterator();
    }
}

Что ж, это сработает, но вопрос был в интерфейсе Set. Это решение заставляет пользователей иметь конкретные ссылки на типы RandomSet.

Johan Tidén 05.05.2015 19:42

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

Kostas Chalkias 18.12.2015 10:17

@KonstantinosChalkias, встроенные коллекции также не являются потокобезопасными. Только те, у которых есть название Concurrent, действительно безопасны, те, которые завернуты в Collections.synchronized(), являются полубезопасными. Кроме того, OP ничего не сказал о параллелизме, так что это правильный и хороший ответ.

TWiStErRob 03.08.2016 23:50

Итератор, возвращенный здесь, не должен иметь возможность удалять элементы из dta (это может быть достигнуто, например, с помощью Iterators.unmodifiableIterator guava). В противном случае реализации по умолчанию, например, removeAll и keepAll в AbstractSet и его родителях, работающих с этим итератором, испортят ваш RandomSet!

muued 12.08.2016 17:17

Хорошее решение. Фактически вы можете использовать дерево, если каждый узел содержит количество узлов в поддереве, корнем которого он является. Затем вычислите случайное вещественное число в 0..1 и примите взвешенное трехстороннее решение (выберите текущий узел или спуститесь в левое или правое поддерево) на каждом узле на основе количества узлов. Но, как мне кажется, ваше решение намного лучше.

Gene 02.09.2016 16:33

В системе Mathematica:

a = {1, 2, 3, 4, 5}

a[[ ⌈ Length[a] Random[] ⌉ ]]

Или, в последних версиях, просто:

RandomChoice[a]

Это получило отрицательное голосование, возможно, из-за отсутствия объяснения, поэтому вот одно из них:

Random[] генерирует псевдослучайное число с плавающей запятой между 0 и 1. Оно умножается на длину списка, а затем используется функция потолка для округления до следующего целого числа. Затем этот индекс извлекается из a.

Поскольку функции хеш-таблицы часто выполняются с помощью правил в Mathematica, а правила хранятся в списках, можно использовать:

a = {"Badger" -> 5, "Bird" -> 1, "Fox" -> 3, "Frog" -> 2, "Wolf" -> 4};

List asList = new ArrayList(mySet);
Collections.shuffle(asList);
return asList.get(0);

Это ужасно неэффективно. Ваш конструктор ArrayList вызывает .toArray () для предоставленного набора. ToArray (в большинстве, если не во всех стандартных реализациях коллекций) выполняет итерацию по всей коллекции, заполняя массив по мере его поступления. Затем вы перемешиваете список, в котором каждый элемент меняет местами случайный элемент. Было бы намного лучше просто перебирать набор до случайного элемента.

Chris Bode 11.03.2013 05:28

Как насчет просто

public static <A> A getRandomElement(Collection<A> c, Random r) {
  return new ArrayList<A>(c).get(r.nextInt(c.size()));
}

Ради интереса я написал RandomHashSet, основанный на выборке отклонения. Это немного взломано, поскольку HashMap не позволяет нам напрямую обращаться к своей таблице, но он должен работать нормально.

Он не использует дополнительную память, а время поиска амортизируется за O (1). (Потому что java HashTable плотный).

class RandomHashSet<V> extends AbstractSet<V> {
    private Map<Object,V> map = new HashMap<>();
    public boolean add(V v) {
        return map.put(new WrapKey<V>(v),v) == null;
    }
    @Override
    public Iterator<V> iterator() {
        return new Iterator<V>() {
            RandKey key = new RandKey();
            @Override public boolean hasNext() {
                return true;
            }
            @Override public V next() {
                while (true) {
                    key.next();
                    V v = map.get(key);
                    if (v != null)
                        return v;
                }
            }
            @Override public void remove() {
                throw new NotImplementedException();
            }
        };
    }
    @Override
    public int size() {
        return map.size();
    }
    static class WrapKey<V> {
        private V v;
        WrapKey(V v) {
            this.v = v;
        }
        @Override public int hashCode() {
            return v.hashCode();
        }
        @Override public boolean equals(Object o) {
            if (o instanceof RandKey)
                return true;
            return v.equals(o);
        }
    }
    static class RandKey {
        private Random rand = new Random();
        int key = rand.nextInt();
        public void next() {
            key = rand.nextInt();
        }
        @Override public int hashCode() {
            return key;
        }
        @Override public boolean equals(Object o) {
            return true;
        }
    }
}

Именно то, о чем я думал! Лучший ответ!

mmm 08.10.2015 14:22

На самом деле, возвращаясь к этому, я предполагаю, что это не совсем единообразно, если хэш-карта имеет много коллизий и мы выполняем много запросов. Это связано с тем, что хэш-карта java использует сегменты / цепочки, и этот код всегда будет возвращать первый элемент в конкретном сегменте. Однако мы по-прежнему едины в случайности хэш-функции.

Thomas Ahle 07.04.2016 15:31

Это быстрее, чем цикл for-each в принятом ответе:

int index = rand.nextInt(set.size());
Iterator<Object> iter = set.iterator();
for (int i = 0; i < index; i++) {
    iter.next();
}
return iter.next();

Конструкция for-each вызывает Iterator.hasNext() в каждом цикле, но, начиная с index < set.size(), эта проверка не требует дополнительных затрат. Я видел прирост скорости на 10-20%, но YMMV. (Кроме того, это компилируется без добавления дополнительного оператора возврата.)

Обратите внимание, что этот код (и большинство других ответов) можно применить к любой коллекции, а не только к Set. В форме универсального метода:

public static <E> E choice(Collection<? extends E> coll, Random rand) {
    if (coll.size() == 0) {
        return null; // or throw IAE, if you prefer
    }

    int index = rand.nextInt(coll.size());
    if (coll instanceof List) { // optimization
        return ((List<? extends E>) coll).get(index);
    } else {
        Iterator<? extends E> iter = coll.iterator();
        for (int i = 0; i < index; i++) {
            iter.next();
        }
        return iter.next();
    }
}

вы также можете передать набор в массив использовать массив он, вероятно, будет работать в небольшом масштабе, я вижу, что цикл for в ответе с наибольшим количеством голосов - O (n) в любом случае

Object[] arr = set.toArray();

int v = (int) arr[rnd.nextInt(arr.length)];

Это идентично принятому ответу (Хот), но с удаленными ненужными переменными size и i.

    int random = new Random().nextInt(myhashSet.size());
    for(Object obj : myhashSet) {
        if (random-- == 0) {
            return obj;
        }
    }

Несмотря на устранение двух вышеупомянутых переменных, вышеупомянутое решение по-прежнему остается случайным, потому что мы полагаемся на случайное (начиная со случайно выбранного индекса), чтобы уменьшаться в сторону 0 на каждой итерации.

Третьей строкой также может быть if (--random < 0) {, где random достигает -1.

Salvador 20.08.2019 17:41

Приведенное выше решение говорит о задержке, но не гарантирует равную вероятность выбора каждого индекса. Если это необходимо учитывать, попробуйте отбор проб из резервуара. http://en.wikipedia.org/wiki/Reservoir_sampling.
Collections.shuffle () (как было предложено немногими) использует один такой алгоритм.

Если вы действительно хотите выбрать «любой» объект из Set, без каких-либо гарантий случайности, проще всего взять первый, возвращенный итератором.

    Set<Integer> s = ...
    Iterator<Integer> it = s.iterator();
    if (it.hasNext()){
        Integer i = it.next();
        // i is a "random" object from set
    }

Однако это не будет случайный выбор. Представьте, что вы выполняете одну и ту же операцию над одним и тем же набором несколько раз. Думаю, порядок будет такой же.

Menezes Sousa 06.09.2015 09:01

Самый простой вариант с Java 8:

outbound.stream().skip(n % outbound.size()).findFirst().get()

где n - случайное целое число. Конечно, производительность ниже, чем у for(elem: Col).

Общее решение, использующее ответ Хота в качестве отправной точки.

/**
 * @param set a Set in which to look for a random element
 * @param <T> generic type of the Set elements
 * @return a random element in the Set or null if the set is empty
 */
public <T> T randomElement(Set<T> set) {
    int size = set.size();
    int item = random.nextInt(size);
    int i = 0;
    for (T obj : set) {
        if (i == item) {
            return obj;
        }
        i++;
    }
    return null;
}

Если размер набора невелик, это можно сделать с помощью массивов.

int random;
HashSet someSet;
<Type>[] randData;
random = new Random(System.currentTimeMillis).nextInt(someSet.size());
randData = someSet.toArray();
<Type> sResult = randData[random];

С Гуава мы можем сделать немного лучше, чем ответ Хота:

public static E random(Set<E> set) {
  int index = random.nextInt(set.size();
  if (set instanceof ImmutableSet) {
    // ImmutableSet.asList() is O(1), as is .get() on the returned list
    return set.asList().get(index);
  }
  return Iterables.get(set, index);
}

Если вы не возражаете против сторонней библиотеки, в библиотеке Утилиты есть IterableUtils, у которого есть метод randomFrom (Iterable iterable), который будет принимать Set и возвращать из него случайный элемент.

Set<Object> set = new HashSet<>();
set.add(...);
...
Object random = IterableUtils.randomFrom(set);

Он находится в центральном репозитории Maven по адресу:

<dependency>
  <groupId>com.github.rkumsher</groupId>
  <artifactId>utils</artifactId>
  <version>1.3</version>
</dependency>

В Java 8:

static <E> E getRandomSetElement(Set<E> set) {
    return set.stream().skip(new Random().nextInt(set.size())).findFirst().orElse(null);
}

Другие вопросы по теме