Как выбрать случайный элемент из набора? Меня особенно интересует выбор случайного элемента из HashSet или LinkedHashSet в Java. Решения для других языков также приветствуются.
Вы должны указать некоторые условия, чтобы увидеть, действительно ли это то, что вы хотите. - Как раз вы будете выбирать случайный элемент? - Должны ли данные быть сохранены в HashSet или LinkedHashSet, ни те, ни другие не доступны в случайном порядке. - Большой ли хеш установлен? Ключи маленькие?
Дэвид Нехм
Ответы:
88
int size = myHashSet.size();int item =newRandom().nextInt(size);// In real life, the Random object should be rather more shared than thisint i =0;for(Object obj : myhashSet){if(i == item)return obj;
i++;}
Если myHashSet велик, то это будет довольно медленное решение, поскольку в среднем потребуется (n / 2) итераций для поиска случайного объекта.
Даниэль
6
если ваши данные находятся в хэш-наборе, вам нужно время O (n). Обойти это невозможно, если вы просто выбираете один элемент, а данные хранятся в HashSet.
Дэвид Неем
8
@David Nehme: это недостаток в спецификации HashSet в Java. В C ++ обычно можно получить прямой доступ к сегментам, которые составляют хэш-набор, что позволяет нам более эффективно выбирать случайные элементы. Если в Java необходимы случайные элементы, возможно, стоит определить пользовательский хэш-набор, который позволит пользователю заглянуть в тайник. См. [Документацию для повышения] [1], чтобы узнать больше об этом. [1] boost.org/doc/libs/1_43_0/doc/html/unordered/buckets.html
Аарон МакДейд,
11
Если набор не видоизменяется при множественном доступе, вы можете скопировать его в массив и получить доступ к O (1). Просто используйте myHashSet.toArray ()
ykaganovich
2
@ykaganovich Разве это не усугубит ситуацию, поскольку набор должен быть скопирован в новый массив? docs.oracle.com/javase/7/docs/api/java/util/… "этот метод должен выделять новый массив, даже если эта коллекция поддерживается массивом"
Но это работает только со списками, то есть со структурами, которые имеют функцию .get ().
bourbaki4481472
4
@ bourbaki4481472 абсолютно правильно. Это работает только для тех коллекций, которые расширяют Listинтерфейс, а не Setинтерфейс, обсуждаемый OP.
Томас,
31
Быстрое решение для Java с использованием ArrayListи HashMap: [element -> index].
Мотивация: мне нужен был набор предметов со RandomAccessсвойствами, особенно чтобы выбрать случайный предмет из набора (см. pollRandomМетод). Случайная навигация в двоичном дереве не точна: деревья не идеально сбалансированы, что не приведет к равномерному распределению.
publicclassRandomSet<E>extendsAbstractSet<E>{List<E> dta =newArrayList<E>();Map<E,Integer> idx =newHashMap<E,Integer>();publicRandomSet(){}publicRandomSet(Collection<E> items){for(E item : items){
idx.put(item, dta.size());
dta.add(item);}}@Overridepublicboolean add(E item){if(idx.containsKey(item)){returnfalse;}
idx.put(item, dta.size());
dta.add(item);returntrue;}/**
* Override element at position <code>id</code> with last element.
* @param id
*/public E removeAt(int id){if(id >= dta.size()){returnnull;}
E res = dta.get(id);
idx.remove(res);
E last = dta.remove(dta.size()-1);// skip filling the hole if last is removedif(id < dta.size()){
idx.put(last, id);
dta.set(id, last);}return res;}@Overridepublicboolean remove(Object item){@SuppressWarnings(value ="element-type-mismatch")Integer id = idx.get(item);if(id ==null){returnfalse;}
removeAt(id);returntrue;}public E get(int i){return dta.get(i);}public E pollRandom(Random rnd){if(dta.isEmpty()){returnnull;}int id = rnd.nextInt(dta.size());return removeAt(id);}@Overridepublicint size(){return dta.size();}@OverridepublicIterator<E> iterator(){return dta.iterator();}}
Хорошо, что это сработало бы, но вопрос был об интерфейсе Set. Это решение заставляет пользователей иметь конкретные ссылки на тип RandomSet.
Йохан Тиден
Мне очень нравится это решение, но оно не поточно-ориентированное, возможны неточности между картой и списком, поэтому я бы добавил несколько синхронизированных блоков
Kostas Chalkias
@KonstantinosChalkias встроенные коллекции также не являются потокобезопасными. Только те, у кого есть имя Concurrent, действительно безопасны, а те, что завернуты Collections.synchronized()в полубезопасны Также ОП ничего не сказал о параллелизме, так что это правильный и хороший ответ.
TWiStErRob
Возвращенный здесь итератор не должен иметь возможности удалять элементы dta(например, это может быть достигнуто с помощью guava Iterators.unmodifiableIterator). В противном случае реализации по умолчанию, например, removeAll и retainAll в AbstractSet и его родителях, работающих с этим итератором, могут испортить ваш RandomSet!
muued
Хорошее решение. На самом деле вы можете использовать дерево, если каждый узел содержит количество узлов в поддереве, которое оно коренит. Затем вычислите случайное действительное число в 0..1 и примите взвешенное трехстороннее решение (выберите текущий узел или перейдите в левое или правое поддерево) в каждом узле на основе количества узлов. Но, по-моему, ваше решение намного приятнее.
Джин
30
Это быстрее, чем цикл 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. (Кроме того, это компилируется без добавления дополнительного оператора возврата.)
Обратите внимание, что этот код (и большинство других ответов) может быть применен к любой коллекции, а не только к множеству. В общей форме метода:
publicstatic<E> E choice(Collection<?extends E> coll,Random rand){if(coll.size()==0){returnnull;// or throw IAE, if you prefer}int index = rand.nextInt(coll.size());if(coll instanceofList){// optimizationreturn((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();}}
Если вы хотите сделать это в Java, вам следует подумать о копировании элементов в какую-то коллекцию произвольного доступа (например, ArrayList). Потому что, если ваш набор не мал, доступ к выбранному элементу будет дорогим (O (n) вместо O (1)). [ed: копия списка также O (n)]
В качестве альтернативы вы можете найти другую реализацию Set, которая более точно соответствует вашим требованиям. ListOrderedSet из Commons Коллекции выглядит многообещающим.
Копирование в список будет стоить O (n) во времени, а также потребует O (n) памяти, так почему это будет лучшим выбором, чем выборка с карты напрямую?
МДМА
12
Это зависит от того, сколько раз вы хотите выбрать из набора. Копирование выполняется один раз, и затем вы можете выбирать из набора столько раз, сколько вам нужно. Если вы выбираете только один элемент, то да, копия не делает вещи быстрее.
Дэн Дайер
Это только одноразовая операция, если вы хотите иметь возможность выбирать с повторением. Если вы хотите, чтобы выбранный элемент был удален из набора, вы вернетесь к O (n).
TurnipEntropy
13
В Java 8:
static<E> E getRandomSetElement(Set<E> set){return set.stream().skip(newRandom().nextInt(set.size())).findFirst().orElse(null);}
Set<Integer> set =newLinkedHashSet<Integer>(3);
set.add(1);
set.add(2);
set.add(3);Random rand =newRandom(System.currentTimeMillis());int[] setArray =(int[]) set.toArray();for(int i =0; i <10;++i){System.out.println(setArray[rand.nextInt(set.size())]);}
Это ужасно неэффективно. Ваш конструктор ArrayList вызывает .toArray () для предоставленного набора. ToArray (в большинстве, если не во всех стандартных реализациях коллекции) выполняет итерацию по всей коллекции, заполняя массив по мере его поступления. Затем вы перемешиваете список, который заменяет каждый элемент случайным элементом. Вам было бы гораздо лучше просто перебирать множество случайных элементов.
Крис Боде
4
Это идентично принятому ответу (Хот), но с удалением ненужных sizeи iпеременных.
int random =newRandom().nextInt(myhashSet.size());for(Object obj : myhashSet){if(random--==0){return obj;}}
Несмотря на то, что покончено с двумя вышеупомянутыми переменными, вышеупомянутое решение все еще остается случайным, потому что мы полагаемся на случайное (начиная со случайно выбранного индекса), чтобы уменьшать себя в 0течение каждой итерации.
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;}}
Вышеупомянутое решение говорит с точки зрения задержки, но не гарантирует равную вероятность каждого выбранного индекса.
Если это необходимо учитывать, попробуйте отбор проб из резервуара. http://en.wikipedia.org/wiki/Reservoir_sampling . Collections.shuffle () (как предлагают немногие) использует один такой алгоритм.
Только [1,2,3,4,5,6] - это не набор, а список, поскольку он не поддерживает такие вещи, как быстрый поиск.
Томас Ахл
Вы все еще можете сделать: >>> random.choice (list (set (range (5)))) >>> 4 Не идеально, но это подойдет, если вам абсолютно необходимо.
SapphireSun
1
Разве вы не можете просто получить размер / длину набора / массива, сгенерировать случайное число от 0 до размера / длины, а затем вызвать элемент, индекс которого совпадает с этим числом? Я уверен, что в HashSet есть метод .size ().
В псевдокоде -
function randFromSet(target){
var targetLength:uint = target.length()
var randomIndex:uint = random(0,targetLength);return target[randomIndex];}
Это работает, только если рассматриваемый контейнер поддерживает случайный поиск индекса. Многие реализации контейнеров этого не делают (например, хеш-таблицы, двоичные деревья, связанные списки).
В большинстве реализаций множеств нет оператора get (i) или индексации, поэтому id предполагает, что OP указал свой набор
DownloadPizza
1
Значок имеет тип набора и оператор случайного элемента, унарный "?", Поэтому выражение
? set([1,2,3,4,5])
будет производить случайное число от 1 до 5.
Случайное начальное число инициализируется равным 0 при запуске программы, так что для получения разных результатов при каждом запуске программы randomize()
Random random =newRandom((int)DateTime.Now.Ticks);OrderedDictionary od =newOrderedDictionary();
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"]);
Похоже, что они недооценены, потому что дрянной Java-словарь (или так называемый LinkedHashSet, независимо от того, что, черт возьми, это) не может быть "случайным образом доступен" (доступ к которому ключ, я думаю). Ява дерьмо заставляет меня смеяться так много
Федерико Берасатеги
1
Решение 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 (){returnthis[Math.floor(Math.random()*this.length)];};[1,2,3,4].choose();
Это работает только для списков, верно? С ELTним может работать любая последовательность.
Кен,
1
В 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};
Для забавы я написал RandomHashSet, основанный на выборке отклонения. Это немного странно, поскольку HashMap не позволяет нам напрямую обращаться к его таблице, но он должен работать просто отлично.
Он не использует никакой дополнительной памяти, а время поиска равно O (1). (Потому что Java HashTable является плотным).
На самом деле, возвращаясь к этому, я думаю, что это не совсем равномерно, если в хэш-карте много коллизий, а мы делаем много запросов. Это потому, что в java hashmap используются сегменты / цепочки, и этот код всегда будет возвращать первый элемент в конкретном сегменте. Мы все еще едины в отношении случайности хэш-функции.
С Гуавой мы можем сделать немного лучше, чем ответ Кота:
publicstatic E random(Set<E> set){int index = random.nextInt(set.size();if(set instanceofImmutableSet){// ImmutableSet.asList() is O(1), as is .get() on the returned listreturn set.asList().get(index);}returnIterables.get(set, index);}
Вы также можете перевести набор в массив. Использовать массив. Вероятно, он будет работать в малом масштабе. В любом случае, в цикле ответа с наибольшим количеством голосов - O (n)
Object[] arr = set.toArray();int v =(int) arr[rnd.nextInt(arr.length)];
Если вы действительно просто хотите выбрать «любой» объект из Set, без каких-либо гарантий случайности, проще всего взять первый, возвращенный итератором.
Set<Integer> s =...Iterator<Integer> it = s.iterator();if(it.hasNext()){Integer i = it.next();// i is a "random" object from set}
Это не будет случайным выбором, хотя. Представьте, что вы выполняете одну и ту же операцию над одним и тем же набором несколько раз. Я думаю, что порядок будет таким же.
Менезес Соуза
0
Общее решение, использующее ответ Хота в качестве отправной точки.
/**
* @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++;}returnnull;}
К сожалению, это невозможно сделать эффективно (лучше, чем O (n)) ни в одном из контейнеров набора стандартной библиотеки.
Это странно, поскольку очень легко добавить случайную функцию выбора к хэш-наборам, а также к двоичным наборам. В не разреженном хэш-наборе вы можете пробовать случайные записи, пока не получите удар. Для двоичного дерева вы можете произвольно выбирать между левым или правым поддеревом, с максимумом O (log2) шагов. Я реализовал демо позже ниже:
import random
classNode:
def __init__(self, object):
self.object = object
self.value = hash(object)
self.size =1
self.a = self.b =NoneclassRandomSet:
def __init__(self):
self.top =None
def add(self, object):"""Add any hashable object to the set.Notice:Inthis simple implementation you shouldn't add two
identical items."""new=Node(object)if not self.top: self.top =newelse: self._recursiveAdd(self.top,new)
def _recursiveAdd(self, top,new):
top.size +=1ifnew.value < top.value:if not top.a: top.a =newelse: self._recursiveAdd(top.a,new)else:if not top.b: top.b =newelse: 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]*10for i in xrange(10000):
dists[s.pickRandom()]+=1
print dists
Я получил [995, 975, 971, 995, 1057, 1004, 966, 1052, 984, 1001] в качестве выхода, так что швы распределения хорошие.
Я боролся с той же самой проблемой для себя, и я еще не решил, стоит ли выигрыш в производительности этого более эффективного выбора, который стоит затрат на использование коллекции на основе Python. Я мог бы, конечно, уточнить его и перевести на C, но это слишком много для меня сегодня :)
Причина, по которой я думаю, что это не реализовано в бинарном дереве, заключается в том, что такой метод не будет равномерно выбирать элементы. Поскольку они являются узлами без левого / правого дочернего элемента, может возникнуть ситуация, когда левый дочерний элемент содержит больше элементов, чем правый дочерний элемент (или наоборот), что сделает выбор элемента у правого (или левого) дочернего элемента более вероятным.
Виллем Ван Онсем
1
@CommuSoft: Вот почему я храню размер каждого поддерева, так что я могу выбирать свои вероятности, основываясь на них.
Ответы:
источник
В некотором родстве Знаете ли вы:
Есть полезные методы
java.util.Collections
для перетасовки целых коллекций:Collections.shuffle(List<?>)
иCollections.shuffle(List<?> list, Random rnd)
.источник
List
интерфейс, а неSet
интерфейс, обсуждаемый OP.Быстрое решение для Java с использованием
ArrayList
иHashMap
: [element -> index].Мотивация: мне нужен был набор предметов со
RandomAccess
свойствами, особенно чтобы выбрать случайный предмет из набора (см.pollRandom
Метод). Случайная навигация в двоичном дереве не точна: деревья не идеально сбалансированы, что не приведет к равномерному распределению.источник
Concurrent
, действительно безопасны, а те, что завернутыCollections.synchronized()
в полубезопасны Также ОП ничего не сказал о параллелизме, так что это правильный и хороший ответ.dta
(например, это может быть достигнуто с помощью guavaIterators.unmodifiableIterator
). В противном случае реализации по умолчанию, например, removeAll и retainAll в AbstractSet и его родителях, работающих с этим итератором, могут испортить вашRandomSet
!Это быстрее, чем цикл for-each в принятом ответе:
Конструкция for-each вызывает
Iterator.hasNext()
каждый цикл, но с тех порindex < set.size()
эта проверка не требует дополнительных затрат. Я видел увеличение скорости на 10-20%, но YMMV. (Кроме того, это компилируется без добавления дополнительного оператора возврата.)Обратите внимание, что этот код (и большинство других ответов) может быть применен к любой коллекции, а не только к множеству. В общей форме метода:
источник
Если вы хотите сделать это в Java, вам следует подумать о копировании элементов в какую-то коллекцию произвольного доступа (например, ArrayList). Потому что, если ваш набор не мал, доступ к выбранному элементу будет дорогим (O (n) вместо O (1)). [ed: копия списка также O (n)]
В качестве альтернативы вы можете найти другую реализацию Set, которая более точно соответствует вашим требованиям. ListOrderedSet из Commons Коллекции выглядит многообещающим.
источник
В Java 8:
источник
В Java:
источник
источник
Это идентично принятому ответу (Хот), но с удалением ненужных
size
иi
переменных.Несмотря на то, что покончено с двумя вышеупомянутыми переменными, вышеупомянутое решение все еще остается случайным, потому что мы полагаемся на случайное (начиная со случайно выбранного индекса), чтобы уменьшать себя в
0
течение каждой итерации.источник
if (--random < 0) {
, гдеrandom
достигает-1
.Clojure решение:
источник
nth
элемент, вы также должны пройти через негоseq
.Perl 5
Вот один из способов сделать это.
источник
C ++. Это должно быть достаточно быстро, так как оно не требует итерации по всему набору или сортировки. Это должно работать "из коробки" с большинством современных компиляторов, если они поддерживают tr1 . Если нет, возможно, вам придется использовать Boost.
Документы Boost полезны здесь, чтобы объяснить это, даже если вы не используете Boost.
Хитрость заключается в том, чтобы использовать тот факт, что данные были разделены на сегменты, и быстро идентифицировать случайно выбранный сегмент (с соответствующей вероятностью).
источник
Вышеупомянутое решение говорит с точки зрения задержки, но не гарантирует равную вероятность каждого выбранного индекса.
Если это необходимо учитывать, попробуйте отбор проб из резервуара. http://en.wikipedia.org/wiki/Reservoir_sampling .
Collections.shuffle () (как предлагают немногие) использует один такой алгоритм.
источник
Поскольку вы сказали «Решения для других языков также приветствуются», вот версия для Python:
источник
Разве вы не можете просто получить размер / длину набора / массива, сгенерировать случайное число от 0 до размера / длины, а затем вызвать элемент, индекс которого совпадает с этим числом? Я уверен, что в HashSet есть метод .size ().
В псевдокоде -
источник
PHP, предполагая, что «набор» является массивом:
Функции Mersenne Twister лучше, но в PHP нет эквивалента MT для array_rand.
источник
Значок имеет тип набора и оператор случайного элемента, унарный "?", Поэтому выражение
будет производить случайное число от 1 до 5.
Случайное начальное число инициализируется равным 0 при запуске программы, так что для получения разных результатов при каждом запуске программы
randomize()
источник
В C #
источник
Решение Javascript;)
Или в качестве альтернативы:
источник
В лиспе
источник
ELT
ним может работать любая последовательность.В Mathematica:
Или, в последних версиях, просто:
Это получило отрицательное голосование, возможно, потому что ему не хватает объяснения, поэтому вот один из них:
Random[]
генерирует псевдослучайное число с плавающей точкой между 0 и 1. Это умножается на длину списка, а затем функция потолка используется для округления до следующего целого числа. Этот индекс затем извлекается изa
.Поскольку функциональность хэш-таблицы часто выполняется с помощью правил в Mathematica, а правила хранятся в списках, можно использовать:
источник
Как насчет просто
источник
Для забавы я написал RandomHashSet, основанный на выборке отклонения. Это немного странно, поскольку HashMap не позволяет нам напрямую обращаться к его таблице, но он должен работать просто отлично.
Он не использует никакой дополнительной памяти, а время поиска равно O (1). (Потому что Java HashTable является плотным).
источник
Самый простой с Java 8 это:
где
n
случайное целое число Конечно, он имеет меньшую производительность, чем уfor(elem: Col)
источник
С Гуавой мы можем сделать немного лучше, чем ответ Кота:
источник
PHP, используя MT:
источник
Вы также можете перевести набор в массив. Использовать массив. Вероятно, он будет работать в малом масштабе. В любом случае, в цикле ответа с наибольшим количеством голосов - O (n)
источник
Если вы действительно просто хотите выбрать «любой» объект из
Set
, без каких-либо гарантий случайности, проще всего взять первый, возвращенный итератором.источник
Общее решение, использующее ответ Хота в качестве отправной точки.
источник
К сожалению, это невозможно сделать эффективно (лучше, чем O (n)) ни в одном из контейнеров набора стандартной библиотеки.
Это странно, поскольку очень легко добавить случайную функцию выбора к хэш-наборам, а также к двоичным наборам. В не разреженном хэш-наборе вы можете пробовать случайные записи, пока не получите удар. Для двоичного дерева вы можете произвольно выбирать между левым или правым поддеревом, с максимумом O (log2) шагов. Я реализовал демо позже ниже:
Я получил [995, 975, 971, 995, 1057, 1004, 966, 1052, 984, 1001] в качестве выхода, так что швы распределения хорошие.
Я боролся с той же самой проблемой для себя, и я еще не решил, стоит ли выигрыш в производительности этого более эффективного выбора, который стоит затрат на использование коллекции на основе Python. Я мог бы, конечно, уточнить его и перевести на C, но это слишком много для меня сегодня :)
источник