LINUX.ORG.RU

Посоветуйте структуру данных: за log(N) достать рейтинг записи в топе.

 


0

2

Есть такое:

// Это топ.
// Тут просто список записей, где они часто из середины прыгают в начало
// Число записей - 100M.
// Список фтопку. Воткните любую структуру данных, которая обеспечит быстрое (хотя-бы логарифм) перемещение объекта из середины в некое начало - например std::map по ключу rating.
// и которая позволит пробежать все элементы от начала до конца в порядке увеличения rating
// Список тут является простейшим примером, это обеспечивающим, но явно не хранящем значение ratings (само оно неинтересно, интересно только относительное положение).
std::list<Record>           top;

// А это чтобы быстро по ID получать запись.
std::map<uint32_t, std::list<Record>::iterator >  ids;

Первый - это не список, а любая структура данных, которая хотите, которая позволит быстро получить первые 1000 записей. Хоть дерево с ключом 'timestamp_microsec', тогда первые 1000 записей — первые 1000 самых больших ключей. Первый контейнер нужен для решения тупой задачи «посмотреть топ 1000».

Второй контейнер нужен для рандом акцесса по ID.

Задача Хочется по id не просто достать запись, что сейчас сделано на std::map, а ещё и получить порядковый номер этой записи в первом контейнере — её место в топе от начала.

Решение: после каждого переноса записи из середины в начало top, пробегать по всему top со счётчиком и сохранять в Record её позицию от начала (текущее значение счётчика). Но это медленно, за такое сразу расстреливают.

P.S.
В посте нет кода, который относится к реальности или работает.
В посте нет кода, который надо сохранить в неизменном виде и обеспечить его работоспособность.
Список приведён только в качестве примера тупой структуры, индекс от начала в которой нужно получить и которая хранит последовательность и поддерживает быстрое извлечение из середины и вставку в начало (если хранить итератор на эту середину, например).



Последнее исправление: hlamotron (всего исправлений: 10)

У тебя все говно и надо переделывать. Зачем тебе позиция элемента в списке? С тем же успехом можно выкинуть map и искать позицию, пробегая по списку. Все равно, чтобы обратиться к списку по позиции, нужно будет его весь просмотреть.

Waterlaz ★★★★★
()

Нужно делать некое дерево (вместо списка) в котором будешь считать кол-во элементов во всей ветке для каждого узла. Тогда подсчёт позиции будет состоять из восхождения от листа до корня как раз за log(N)

mashina ★★★★★
()

А как вообще звучит постановка задачи ? Т.к. на данный момент тут какой-то перебор :)

joy4eg ★★★★★
()

Храни в мапе итераторы/указатели на элементы списка, они не инвалидируются, пока соответствующий элемент из списка не удалят. Вот только непонятно, зачем вообще нужен список, но даже если нужен, то не разумнее ли вместо map использовать unordered_map (если, конечно, не нужна упорядоченность по ключам)?

Softwayer ★★
()
Ответ на: комментарий от Softwayer

По итератору на элемент в списке я никак не получу позицию данного элемента в списке.

Список нужен для того, чтобы показать, позицию в чём хочется получить.

hlamotron
() автор топика
Ответ на: комментарий от hlamotron

Что переделывать, если ничего не сделано по сабжу? Глазами пробуй читать.

Научись формулировать свои мысли четче. Во взрослой жизни поможет.

Waterlaz ★★★★★
()
Ответ на: комментарий от Waterlaz

А как определить - это я мысль плохо сформулировать или ты не осилил элементарную мысль?

hlamotron
() автор топика
Ответ на: комментарий от Softwayer

Инератор - это не позиция, а тупо указатель на сам элемент. Позиция-в-чём-то — это всегда нечто такое, что опирается на само это что-то, чаще всего это числовой индекс.

hlamotron
() автор топика
Ответ на: комментарий от Softwayer

А что такое позиция в связном списке, если не итератор?

Если я правильно понял, он хочет порядковый номер в списке.

Waterlaz ★★★★★
()
Ответ на: комментарий от hlamotron

А как определить - это я мысль плохо сформулировать или ты не осилил элементарную мысль?

Прочитать вот это вот:

Позиция-в-чём-то — это всегда нечто такое, что опирается на само это что-то, чаще всего это числовой индекс.

И тогда сразу станет ясен уровень твоих возможностей что-то излагать.

Waterlaz ★★★★★
()
Ответ на: комментарий от Softwayer

Да, подозреваю, что список ему как раз не нужен. А вот что ему нужно, он объяснить не может. Ппредлагать решение наугад не хочется.

Waterlaz ★★★★★
()
Ответ на: комментарий от Waterlaz

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

hlamotron
() автор топика
Ответ на: комментарий от Softwayer

Вас никто не ограничивает в решениях, что вы прицепились к написанному std::list так намертво. Всё, что вас должно интересовать - это вроде как требования к алгоритму, а не какой-то приведённый код.

hlamotron
() автор топика
Ответ на: комментарий от hlamotron

Афтар, излагай еще!

Перечитал ОП два раза, не глядя в код. Решение нашел, постановку задачи—нет.

staseg ★★★★★
()
Ответ на: комментарий от hlamotron

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

Softwayer ★★
()
Ответ на: комментарий от hlamotron

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

А я заметил, что все, кроме одного человека( mashina), тебя не поняли. Правильно ли тебя понял mashina? А хрен знает. Он тебе подсказал хорошую идею, но то ли таки понял тебя неправильно, то ли ты её не смог понять, не знаю. Как бы там ни было, его пост ты проигнорировал.

Waterlaz ★★★★★
()
Ответ на: комментарий от Softwayer

Правильно. Топ по неким баллам. Надо знать порядковый номер в этом топе с начала. Знать его путём адресации по третьему параметру, например неизменному ID сущности.

hlamotron
() автор топика
Ответ на: комментарий от hlamotron

Ну тебе выше уже всё предложили: используй какое-нибудь сбалансированное двоичное дерево поиска вместо списка, а мапу храни по-прежнему в итератор для этого дерева. В каждом узле дерева храни число элементов в поддереве, корнем которого он является, и тогда индекс (т.е. количество элементов, меньших данного) сможешь узнать за логарифм.

Softwayer ★★
()
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <map>

template<class T>
using ordered_set = __gnu_pbds::tree<T, 
      std::less<T>,
      __gnu_pbds::null_type,
      __gnu_pbds::rb_tree_tag,
      __gnu_pbds::tree_order_statistics_node_update>;

struct Record {
    // your fields
    double rating;
};

bool operator < (Record a, Record b) { return a.rating < b.rating; }

ordered_set<Record> top;
std::map<uint32_t, Record> ids;

// use ordered_set<T>::find_by_order() and ordered_set<T>::order_of_key()

ну и rating конечно должен быть уникален, поэтому каждый Record тоже будет уникальным

f1u77y ★★★★
()
Последнее исправление: f1u77y (всего исправлений: 2)

да, так можно сделать. Тут нужно либо какое-то сбалансированное дерево, либо список с пропусками (skiplist)

anonymous
()
Ответ на: комментарий от f1u77y

если топ формируется не на основе какого-то рейтинга, а вручную, то это не подойдет. И вообще, использовать внутренние нестандартные библиотеки - плохая идея. Я бы поискал что-нибудь в Boost

anonymous
()
Ответ на: комментарий от anonymous

то это не подойдет

а, я не написал, как эту хрень использовать

хотим вставить на i-е место в топе – ищем i - 1 и i, присваиваем нашему Record x.rating = (top[i - 1].rating + top[i].rating) / 2 и вставляем новый Record в дерево и в мапу

как место получать, думаю понятно

использовать внутренние нестандартные библиотеки - плохая идея

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

f1u77y ★★★★
()
Последнее исправление: f1u77y (всего исправлений: 1)
Ответ на: комментарий от f1u77y

хотим вставить на i-е место в топе – ищем i - 1 и i, присваиваем нашему Record x.rating = (top[i - 1].rating + top.rating) / 2 и вставляем новый Record в дерево и в мапу

если так делать, то очень быстро упретесь в машинное эпсилон

anonymous
()
Ответ на: комментарий от hlamotron

если все-таки не против таких библиотек, то можно использовать __gnu_cxx::rope. Доки тут и тут. Только надо посмотреть, что там с инвалидацией итераторов

anonymous
()
Ответ на: комментарий от anonymous

эх, совсем забыл про rope, всё костыли пытался городить какие-то

c инвалидацией всё хорошо, т.к. это дерево

f1u77y ★★★★
()
Последнее исправление: f1u77y (всего исправлений: 1)

Решение: после каждого переноса записи из середины в начало top, пробегать по всему top со счётчиком и сохранять в Record её позицию от начала (текущее значение счётчика). Но это медленно, за такое сразу расстреливают.

Мне кажется ты делаешь heap, нет?

xpahos ★★★★★
()
Ответ на: комментарий от Softwayer

Такое решение всем хорошо. По ID через мапу мы спокойно узнаём итератор. А по итератору уже поднимаясь по данному дереву за logN находим его позицию относительно начала. Но тут есть тоноксть, что дерево должно балансироваться. Во время балансировки ноды будут сильно перемещаться, и все итераторы на них будут инвалидироваться, что повлечёт за собой необходимость также изменения значения итераторов в std::map<key, value>. Значит нужно менять в мапе наши value. Будет что-то около logN запросов на смену итератора в std::map. То есть log^2(N) (тут мог напутать. Поправьте, если неправ).

zamazan4ik ★★
()
Последнее исправление: zamazan4ik (всего исправлений: 1)
Ответ на: комментарий от unt1tled

Worst case O(N). Не подходит. Но структура интересная, спасибо.

zamazan4ik ★★
()

Первое правило программиста на крестах: никогда и ни при каких обстоятельствах не используй std::list. Вообще. Далее, выбор основного хранилища. Сами данные логично хранить в векторе или векторе указателей и идентифицировать по индексу. Если такое невозможно, тогда unordered_map. Для поиска топа так и просится std::multimap<rating_t, id_t>. С быстрым определением позиции в топе сложнее. Если статистически будут смотреть в основном только те элементы, что в топе, можно искать итератор в мультимапе и потом считать до начала. Если предполагается искать позицию любых элементов, тогда сложнее. Самый простой вариант - разбить область оценок на интервалы и заменить индексный мультимап на массив из мультимапов для каждого интервала. Тогда поиск позиции будет осуществлять я отсчётом итератора в мультимапе и суммированием размеров мультимапов до него. Но этот вариант работает только если рейтинговых интервал известен и рейтинги распределены более-менее равномерно. Если это не выполняется, придётся колхозить более сложную структуру индекса: std::map<rating_t, std::multimap<rating_t, id_t>*>, во внешней мапе ищешь элемент с рейтингом не более искомого (есть такой метод) и получаешь указатель на мультимап для интервала оценок. При добавлении в индекс следишь, чтоб количество элементов в мультимапе не превысило некий лимит, если превышает - удаляешь мультимап из мапа, разбиваешь на 2 половины и вставляешь соответственно 2 интервала рейтингов. Только смотри чтоб элементы с равным рейтингом не попали в разные половины. Ну и если будешь такое делать, помни что сортированные контейнеры (map/multimap/set/multiset) реализованы как балансируемые деревья, так что очень не любят, когда их заполняют в порядке возрастания или убывания ключа.

anonymous
()

Короче, чуваки, есть ответ.

Незнаю как эта структура называется, но она такова: берём std::map (двоичное дерево поиска). В каждую ноду добавляем дополнительное целочисленное поле и сохраняем в него число сыновей данной ноды, включая её саму. Соответственно, по правой ссылке можно глянуть в правую ноду и посмотреть сколько сыновей там, по левой - сколько там.

Как эту инфу апдейтить: проталкивая +1 или -1 наверх от вновь вставившейся или удалившейся ноды по принципу дерева отрезков.

Как это юзать: допустим нам надо выяснить, какой индекс занимает ключ K от начала. Что считать началом - пох, не суть; поддерживается оба начала сразу. Находим этот ключ K (т.е. ноду дерева) обычным деревянно-бинарным поиском. Нам известно сколько у этой ноды сыновей слева и сколько справа. Известно, что слева лежат «меньше», а справа лежат «не меньше». Ну и всё - в зависимости от того, индекс с какого конца нас интересует, на того сына и смотрим. Допустим, нас интересует индекс в направлении возрастания. Тогда найдём K и возьмём число сыновей левого сына ноды - это ответ на наш запрос.

hlamotron
() автор топика
Ответ на: комментарий от hlamotron

Соответственно, по правой ссылке можно глянуть в правую ноду и посмотреть сколько сыновей там, по левой - сколько там.

ещё, если ищём сколько слева, то добавляем сумму увеличенных на единицу левых сыновей всех родителей К, для которых К находится в правой ветви. Ну и чтоб дерево не вырождалось в список, их балансируют, в std::map используются красно-чёрные деревья, каждая вставка может вызвать перебалансировку части дерева, что потребует пересчёта количества сыновей. Короче, придётся самому делать перебалансировку и правильно модифицировать счётчики

khrundel ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.