задачка для программистов
Feb. 21st, 2007 12:12 pmЗнакомый рассказал, ему на интервью задали. Красивая задачка, но совсем не подходящая для интервью, по-моему.
Есть некий поток элементов. Вызывая снова и снова функцию getnext(), вы получаете каждый раз следующий элемент из потока, и так до тех пор, пока функция не вернет EOF, что означает, что поток закончился. Гарантировано, что все элементы, полученные таким образом, будут отличаться друг от друга. Вернуться к началу потока, или получать элементы в каком-то другом порядке, кроме как один за другим с помощью getnext(), невозможно.
Известно, что поток когда-нибудь кончается, но заранее про количество элементов ничего не известно.
Требуется: написать функцию, которая, когда ее вызывают (а вызывают ее один раз), возвращает выбранный случайным образом один из элементов потока, притом у каждого из элементов потока одинаковая вероятность быть возвращенным этой функцией.
Функция должна использовать фиксированное количество памяти (иными словами, O(1) памяти).
Update: учтите, что в комментариях уже есть правильные ответы.
no subject
Date: 2007-02-21 10:32 am (UTC)Я считаю, что на интервью задачи, для решения которых необходимо найти "изюминку", неправильно - потому что то, что кандидат не догадался, практически ничего не означает. Кому-то пришло в голову, кому-то не пришло. Это все равно, что оценивать знание математики по умению решать олимпиадные задачки. Гораздо лучше давать вопросы, в которых "изюминка" позволяет сделать что-то лучше и качественнее, но и без нее, просто грамотно используя свои знания и умения, можно придти к достойному ответу.
no subject
Date: 2007-02-21 10:45 am (UTC)Но в этой задаче, мне кажется, этого даже не требуется. "Догадаться, что следует хранить один элемент в качестве текущего кандидата, которого мы выдаем, если поток в сей момент кончился" очень просто. Я именно с конца ее и решил. Т.е. вот мы встретили EOF, значит, надо что-то выдать, но что? Значит, есть некий Element res. Инициализируем его первым значением (потому что больше просто нечем). И он должен по каким-то правилам меняться; по каким?
no subject
Date: 2007-02-21 11:03 am (UTC)no subject
Date: 2007-02-21 11:16 am (UTC)no subject
Date: 2007-02-21 04:26 pm (UTC)no subject
Date: 2007-02-22 07:44 am (UTC)no subject
Date: 2007-02-21 10:47 am (UTC)Но ведь фраза условия "Функция должна использовать фиксированное количество памяти (иными словами, O(1) памяти)." уже подразумевает под собой хранение одного элемента. До этого не нужно догадываться, это условие. А дальше нужно думать, как именно хранить его так, чтобы сохранять равную вероятность.
Вот если бы этой фразы в задаче не было, тогда другое дело. Тогда действительно, "просто свои используя знания" можно было бы придти к лобовому ответу (сохранять всё по мере поступления), а до "изюминки" надо было бы додумываться.
no subject
Date: 2007-02-21 10:50 am (UTC)по-моему она подозревает конечное поличество эелементов, а не один. если я еще что-то помню из того, что учила. но от конечного до одного - один шаг.
no subject
Date: 2007-02-21 11:03 am (UTC)no subject
Date: 2007-02-21 11:46 am (UTC)no subject
Date: 2007-02-21 10:52 am (UTC)я как раз из доступной памяти исходил
Date: 2007-02-21 01:16 pm (UTC)Оказалось (в комментах) массив может быть мощностью 1 без ущерба для случайности...
no subject
Date: 2007-02-21 10:53 am (UTC)думаю, привльнее использовать то, что ты предложил.
или что-то достаточно большое, чтоб требовать не результат, а как бы просить человека, чтоб он рассуждал вслух.
no subject
Date: 2007-02-21 11:01 am (UTC)no subject
Date: 2007-02-21 11:46 am (UTC)no subject
Date: 2007-02-21 12:05 pm (UTC)no subject
Date: 2007-02-21 01:09 pm (UTC)Re: Reply to your comment...
Date: 2007-02-21 12:29 pm (UTC)no subject
Date: 2007-02-21 04:46 pm (UTC)). Поскольку умножения вероятностей производит Бог, а не компьютер, то опасаться ошибок округления тоже нечего. А вот с аппаратным генератором как раз могут быть проблемы: они не аккуратностью распределения славны...Что может здесь подпортить жизнь – это имеющиеся у некоторых генераторов корреляции между i-ым и i+N-ым обращениями при каком-нибудь неожиданном N. Ну, значит, генератор должен быть хороший....
no subject
Date: 2007-02-21 07:15 pm (UTC)Можно для смеху попытаться оценить эту погрешность сверху, полагая что rand(n) = rand() % n (так он и работает наверняка), где random() равновероятно выдаёт числа от 0 до 2^32 - 1. У меня получилось
P(rand(n) = 0) = (floor((N - 1)/n) + 1)/N = 1/n + (1 - 1/n - frac((N-1)/n))/N.
Вот эта правая часть (абсолютная погрешность) колеблется вокруг 0.5/N. То есть 2^-33.
Правда, я что-то не пойму, как правильно перевести абсолютную погрешность в относительную, потому что если просто оценить её сверху как 1 - (1 - 2^-33)^1000 (для тысячи элементов), то число очень маленькое получается, даже неожиданно как-то =) И для миллиона маленькое.
no subject
Date: 2007-02-22 06:00 am (UTC)Ага. Вечно я все сокращения путаю...
Ну, это надо уж очень плохо об авторах библиотеки думать.... На самом деле, с libc просто нет входа
int rand(int), есть толькоint rand(void). (http://www.opengroup.org/onlinepubs/009695399/functions/rand.html) В тех библиотеках, где она есть, я надеюсь, что она реализована типа (псевдокод)U32 rand(U32 limit)
{
return (rand32() * limit) >> 32;
}
no subject
Date: 2007-02-22 05:02 pm (UTC)Даже если ваш компилятор зачем-то решит перемножить и int32 и uint32 как int64, так что ваша функция будет выдавать ещё что-нибудь, кроме нулей, природу-то всё равно не обманешь -- rand32 выдаёт 2^32 разных равновероятных чисел, rand(3) - три. 2^32 на три не делится. Тут разве что можно поставить проверку, что если ранд нам выдал число из последней группы остатков, то нужно скипнуть (I mean, something like
uint32 tmp;
do
{
tmp = rand();
while ((uint32)(tmp + limit) < limit)
Но как-то это к очень нетривиальной работе рандома может привести.
no subject
Date: 2007-02-22 06:01 pm (UTC)Ыыы, кажется, и написал... ;)
Ну я же сказал, что это псевдокод...
Виноват, сонный был....
Да нет, ничего нетривиального, метод фон-Неймана называется, в любом учебнике по Монте-Карло не дальше пятой страницы...
В GSL, собственно, так и сделано:
unsigned long int
gsl_rng_uniform_int (const gsl_rng * r, unsigned long int n)
{
unsigned long int offset = r->type->min;
unsigned long int range = r->type->max - offset;
unsigned long int scale;
unsigned long int k;
if (n > range || n == 0)
{
GSL_ERROR_VAL ("invalid n, either 0 or exceeds maximum value of generator",
GSL_EINVAL, 0) ;
}
scale = range / n;
do
{
k = (((r->type->get) (r->state)) - offset) / scale;
}
while (k >= n);
return k;
}
no subject
Date: 2007-02-21 01:02 pm (UTC)