Russian Qt Forum
Ноябрь 22, 2024, 10:34 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: 1 [2] 3 4   Вниз
  Печать  
Автор Тема: Быстрая вставка  (Прочитано 20373 раз)
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #15 : Сентябрь 03, 2020, 11:51 »

Если кандидат не в состоянии распознать и написать set_union/set_intersect, то зачем такой кандидат нужен?

Да мало ли. Гуйню программить, интерфейсы для филдбасов всяких, дазы банных etc.

Просто ИМХО задачи на собеседованиях нивелируют саму идею со-БЕСЕДОВАНИЯ. Т.е. слово подразумевает беседы, общение, выявление интересов, соответствие предпочтений и предложений. А не экзамен с высоким уровнем вероятности удачи-неудачи.

Скиллы? Они из резюме видны. Человека можно спросить в конце концов, чем он раньше занимался. Если не "прыгун" - расскажет сам с удовольствием. А случайный пассажир или тупить будет, или наоборот про воздушные замки распинаться - но таких сразу видно.

Задачи - да пожалуйста. Только на испытательном сроке, когда уже контракт подписан. Но не раньше.
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #16 : Сентябрь 03, 2020, 12:01 »


Просто ИМХО задачи на собеседованиях нивелируют саму идею со-БЕСЕДОВАНИЯ. Т.е. слово подразумевает беседы, общение, выявление интересов, соответствие предпочтений и предложений. А не экзамен с высоким уровнем вероятности удачи-неудачи.


Если вы 40 минут будете молчать а потом гордо скажете "я сделяль", то вас не возьмут, даже если ваше решение оптимальное.
Цель задач - не только решить их, а посмотреть как кандидат думает, рассуждает, какие трейд-оффы делает.
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #17 : Сентябрь 03, 2020, 16:27 »

На самом деле, сортировку можно сделать за линейное время, у нас же индексы известны, по сути это недосортировка подсчетом.
Бежим по массиву. Встретили cube_5 положили в hash["cube"][5], встретили "cube", положили в hash["cube"][-1] (мультимапа нужна). Теперь мы знаем для каждого подхэша количество элементов, "развернуть" в прямой хэш задача нетрудная.
Ну а дальше у нас есть "массивы" (хэши) "сортированные" по индексу - смержить их задача тоже тривиальная.
В общем за О(Н) делается, какие конкретно структуры данных применить (можно вместо внутреннего хэша использовать вектор) оставлю внимательному читателю.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #18 : Сентябрь 04, 2020, 12:13 »

В общем за О(Н) делается, какие конкретно структуры данных применить (можно вместо внутреннего хэша использовать вектор) оставлю внимательному читателю.
Ага, "хвостиком махнул" Улыбающийся

Похоже я просто чуть "недожал"
Как сделано сейчас: для каждого вставляемого (напр "Cube 10") просматривается весь массив до обнаружения "Cube >= 10", наилучшая позиция вставки запоминается. Напр нашли "Cube 5" в позиции (по-простому "в строке") 44, значит фиксируем: место вставки 44+1 = 45. Но еще не вставляем, ведь может найдется "Cube 7" и.т.п. Наконец весь массив просмотрен, вставляем в запомненное место, если оно -1 (вообще нет никаких "Cube"), то просто в конец.
Все это можно делать пробегая исходный массив один раз для всех вставляемых
Код
C++ (Qt)
// строка + место вставки + индекс
using TLine = std::tuple<QString, int, int>;
inline QString & Name( TLine & line ) { return std::get<0>(line); }
inline int & InsP( TLine & line ) { return std::get<1>(line); }
inline int & Index( TLine & line ) { return std::get<2>(line); }
 
// исходный массив и вставляемый
std::vector<TLine> src, arrived;
...
// сортируем вставляемых
for (auto & a : arrived)
 InsP(a) = -1;
std::sort(arrived.begin(), arrived.end());
 
// для каждой строки исходного обновляем места вставки всех
// вставляемых с тем же первым ключом
for (size_t i = 0; i < src.size(); ++i) {
 auto it = std::lower_bound(arrived.begin(), arrived.end(), src[i]);
 for (; it != arrived.end(); ++it) {
   if (Name(src[i]) != Name(*it))) break;
   if (Index(*it) >= Index(src[i]))
       InsP(*it) = i;
 }
}
 
Теперь сортируем вставляемых по месту вставки и вставляем накапливая смещение
Код
C++ (Qt)
std::sort(arrived.begin(), arrived.end(),  [](const TLine & a, const TLine & b) -> bool
{
   return InsP(a) < InsP(b);
});
 
int offset = 0;
for (const auto & a : arrived) {
 if (insP(a) < 0)
  src.push_back(a);
 else {
  int insP = InsP(a) + offset;
  assert(Name(a) == Name(insP));
  if (Index(src[index] == Index(a))  // override
    src[index] = a;
  else {
   src.insert(src.begin() + insP + 1, a);  // insert after
   ++offset;
  }
}
}
 
Писал здесь и tuple юзал первый раз, так что может где-то и насвистел

Ну а дальше уже дело техники.
Да, но хотелось бы эту технику видеть. А "генератором идей" я и сам могу..  Улыбающийся
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #19 : Сентябрь 04, 2020, 14:15 »

Я накидал линейный алгоритм с кучей дополнительной памяти и аллокаций. Но тут нарушено требование что ключи в результирующем контейнере идут в порядке возрастания. В остальном все как надо - ключи уникальны, дырки заполняются.
Поправить сортировку можно, гм, отсортировав в последнем цикле или используя линейно бегущий индекс как счетчик при вставке. Оба варианта убивают всю линейность, особенно второй в случае, если есть большая дырка (как в примере со sphere 5000).
Так что можно не выпендриваться и сразу писать как я изначально предлагал через слияние сортированных массивов. Может быть "продемонстрирую навыки" позже.

Код:
#include <algorithm>
#include <iostream>
#include <optional>
#include <string>
#include <vector>
#include <unordered_map>

#include <assert.h>

int main(int argc, char *argv[])
{
    struct Line {
        std::string name;
        std::optional<int> index;
    };

    using Lines = std::vector<Line>;

    Lines source{
        {"sphere", 1},
        {"cube", 1},
        {"cube", 3},
        {"sphere", 2},
        {"sphere", 5},
    };
    Lines arrived{
        {"sphere", 1},
        {"cube", 2},
        {"cube"},
        {"cube"},
        {"sphere", 1},
        {"sphere", 5000},
    };

    // usually, LinesInnerHash contains exactly 1 element in the vector => so we can use
    // QVarLengthArray<Line, 1> here to avoid allocations. The only case when we need multiple
    // elements is to store linex with no index assigned, i.e. when key is std::nullopt
    using LinesInnerHash = std::unordered_map<std::optional<int>, Lines>;
    using LinesHash = std::unordered_map<std::string, LinesInnerHash>;

    // first, divide lines for by their name, O(N), N == source.size()
    LinesHash sourceHash;
    for (auto line: source) {
        assert(line.index); // source array should not contain unindexed elements
        sourceHash[line.name][line.index] = {line};
    }

    LinesHash arrivedHash;
    for (auto line: arrived) { // O(K), K == arrived.size()
        // arrivedHash[line.name][std::nullopt] will contain all unindexed elements
        arrivedHash[line.name][line.index].push_back(line);
    }

    // now, we need to index unindexed lines
    for (auto &item: arrivedHash) { // totally ~O(K) ?
        auto &innerHash = item.second;
        int index = 1;
        // extract unindexed lines from the hash
        Lines unindexedLines(std::move(innerHash[std::nullopt]));
        innerHash.erase(std::nullopt);
        for (auto &line: unindexedLines) {
            // find first free index
            while (innerHash.find(index) != innerHash.end())
                index++;
            line.index = index; // update line
            innerHash[index] = {line}; // and insert it with the new index
            index++; // not necessary, small optimisation to avoid extra find() call
        }
    }

    for (const auto &item: arrivedHash) { // O(K)
        auto &sourceInnerHash = sourceHash[item.first];
        auto &arrivedInnerHash = item.second;
        for (const auto &subItem : arrivedInnerHash) {
            // if we use unordered_map as an inner container, this will be simply
             sourceInnerHash[subItem.first] = std::move(subItem.second);
        }
    }

    Lines result;
    for (const auto &item: sourceHash) { // O(K)
        auto &innerHash = item.second;
        for (const auto &innerItem: innerHash)
            result.push_back(innerItem.second.front());
    }

    for (const auto &line: result) {
        std::cout << line.name << " " << *line.index << std::endl;
    }

    return 0;
}

Upd: только он не работает, я налажал со дырками=)
« Последнее редактирование: Сентябрь 04, 2020, 19:53 от Авварон » Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #20 : Сентябрь 04, 2020, 20:32 »

Ладно, вот логарифмеческий с мержем, тоже пришлось повозиться с вытираторами, но идея простая.
Сперва сливаем все элементы с одинаковым ключом в массив так что сперва идут новые. Дальше сортируем массив стабильной сортировкой так чтобы неиндексированные элементы были в конце. Так как сортировка стабильная, то парные ключи сохраняют порядок новый-старый. Делаем трюк с std::unique - удаляем дупликаты которые имеют одинаковые номера. При этом важно исключить непроиндексированные так как у них одинаковый (пустой ключ). Так как мы идем с начала массива, то будет оставлен "новый" ключ (помним про порядок старый-новый), а старый удален.
Ну а дальше самое сложное - назначить индексы непроиндексированным элементам (лежат по-прежнему в конце). Тут пришлось повозиться=) оказалось ошибка вообще в другом месте, как всегда.

Код:
#include <algorithm>
#include <iostream>
#include <optional>
#include <string>
#include <vector>
#include <unordered_map>

#include <assert.h>

struct Line {
    std::string name;
    std::string payload;
    std::optional<int> index;
};

using Lines = std::vector<Line>;

void logarithmic(const Lines &source, const Lines &arrived)
{
    using LinesHash = std::unordered_map<std::string, Lines>;

    // first, divide lines for by their name, O(N), N == source.size()
    LinesHash hash;
    for (auto line: arrived) {
        hash[line.name].push_back(line);
    }
    for (auto line: source) {
        assert(line.index); // source array should not contain unindexed elements
        hash[line.name].push_back(line);
    }

    Lines result;
    for (auto &item: hash) {
        auto &lines = item.second;

        auto lessThan = [](const Line &lhs, const Line &rhs) { // std::nullopt should be last
            if (lhs.index && rhs.index)
                return *lhs.index < *rhs.index;
            if (!lhs.index && !rhs.index)
                return false;
            return lhs.index && !rhs.index;
        };
        std::stable_sort(lines.begin(), lines.end(), lessThan); // N * log(N)

        // find position of the last existing element and the number of elements
        const auto noIndex = [](const auto &line) { return !line.index; };
        auto mid = std::find_if(lines.begin(), lines.end(), noIndex); // O(N)
        const auto unindexedCount = std::distance(mid, lines.end());

        // remove duplicates between the first existing element and the last one
        // this effectively implements "override" logic
        const auto equals = [](const auto &lhs, const auto &rhs) {
            return lhs.index == rhs.index;
        };
        const auto newEnd = std::unique(lines.begin(), mid, equals); // O(N)
        lines.erase(newEnd, mid); // O(N)

        Lines indexedLines;
        indexedLines.reserve(lines.size());

        // assign indexes to elements in the [mid..end) range
        mid = lines.end() - unindexedCount; // erase invalidates iterator, re-assign it
        int index = 1;
        auto it = lines.begin();
        for (auto jt = mid; it != mid || jt != lines.end(); ) { // O(N)
            if (jt == lines.end() || it->index == index) {
                indexedLines.push_back(*it);
                ++it;
            } else {
                jt->index = index;
                indexedLines.push_back(*jt);
                ++jt;
            }
            ++index;
        }

        // add current chunk to the result
        result.insert(result.end(), indexedLines.begin(), indexedLines.end()); // O(N)
    }

    for (const auto &line: result) {
        std::cout << line.name << " " << line.payload;
        if (line.index)
            std::cout << " " << *line.index;
        std::cout << std::endl;
    }
}

int main(int argc, char *argv[])
{
    Lines source{
        {"sphere", "old", 1},
        {"cube", "old", 1},
        {"cube", "old", 3},
        {"sphere", "old", 2},
        {"sphere", "old", 5},
    };
    Lines arrived{
        {"sphere", "new", 1},
        {"cube", "new", 2},
        {"cube", "new"},
        {"cube", "new"},
        {"sphere", "new", 4},
        {"sphere", "new"},
        {"sphere", "new", 5000},
    };

    logarithmic(source, arrived);
    return 0;
}

Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #21 : Сентябрь 05, 2020, 09:38 »

Ну во-первых с меня рабочий пример (аттач). Идея выше, но почистил и выкинул tuple (здесь не очень лепится)
Я накидал линейный алгоритм с кучей дополнительной памяти и аллокаций. Но тут нарушено требование что ключи в результирующем контейнере идут в порядке возрастания.
Ну если бы не было (мерзкого) первого ключа (строки "Cube", "Sphere"), то и проблем/трудностей бы никаких не было, банальная/тривиальная сортировка - и все дела. А тут есть произвольный порядок следования по первому ключу

- "группа" (или "кучка") Sphere
- группа Cube
- группа Sphere
и.т.п.

И его надо сохранить, можно добавлять в группы, но не менять их порядок следования. Бросается в глаза решение: создать ассоциативные контейнеры для каждого первого ключа (строки). Тогда да, вставляемые в них прекрасно лягут, но.. дальше что? Я не вижу след хода, ведь инфы о "группах" нет и как ее заполучить - хз

Ладно, вот логарифмеческий с мержем, тоже пришлось повозиться с вытираторами, но идея простая.
Сперва сливаем все элементы с одинаковым ключом в массив так что сперва идут новые. Дальше сортируем массив стабильной сортировкой так чтобы неиндексированные элементы были в конце. Так как сортировка стабильная, то парные ключи сохраняют порядок новый-старый. Делаем трюк с std::unique - удаляем дупликаты которые имеют одинаковые номера. При этом важно исключить непроиндексированные так как у них одинаковый (пустой ключ). Так как мы идем с начала массива, то будет оставлен "новый" ключ (помним про порядок старый-новый), а старый удален.
Ну а дальше самое сложное - назначить индексы непроиндексированным элементам (лежат по-прежнему в конце). Тут пришлось повозиться=) оказалось ошибка вообще в другом месте, как всегда.
Простите, но "ни асилил" (хотя я и хорошо знаком с задачей). Не исключено что это я туплю, но может и объективно быть слишком сложно.

По поводу "непроиндексированных". Зачем включать это в алгоритм, не лучше ли сделать это пре-проходом, типа "нет индекса - назначим". Какой назначать - в постановке этого нет, значит на усмотрение разработчика. Я использовал новые индексы "от максимального" (в обоих массивах). Можно и "заполнять дырки", это та самая задача генерации уникального ID (о которой был разговор и которую Вы охаяли  Улыбающийся)

Ну и сам алгоритм - не вижу как Вы сохраняете исходный порядок по первому ключу. И этот "контейнер на контейнер" - оно конечно "не запрещено", но хорошего впечатления не производит.
« Последнее редактирование: Сентябрь 05, 2020, 09:43 от Igors » Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #22 : Сентябрь 05, 2020, 12:27 »

Ну и сам алгоритм - не вижу как Вы сохраняете исходный порядок по первому ключу.

Про это в постановке задачи ничего не было. Сохранение порядка начинает вызывать слишком много вопросов. Допустим мы начинаем генерить ключ с максимального из существующих. Тогда при слиянии все элементы будут добавляться в последнюю группу, то есть без разрывов. Внимание вопрос, а откуда разрыв-то появляется? Что мешает сделать сортированный массив по обоим ключам?
Если у вас несортированный массив с требованием сохранять порядок - ну тут ничего кроме перебора особо не придумаешь.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #23 : Сентябрь 05, 2020, 14:06 »

Про это в постановке задачи ничего не было.
Та было (вернее есть). И словами, и примером
Сохранение порядка начинает вызывать слишком много вопросов. Допустим мы начинаем генерить ключ с максимального из существующих. Тогда при слиянии все элементы будут добавляться в последнюю группу, то есть без разрывов. Внимание вопрос, а откуда разрыв-то появляется? Что мешает сделать сортированный массив по обоим ключам?
Приложение имеет специфический 3D объект: ("instancer"), контейнер др 3D объектов ("instances"). Смысл очевиден: хранить один базовый "Cube" и один "Sphere", для каждого инстанса уникальны лишь данные трансформа/анимации. Инстансы упорядочены в порядке их создания, это имеет значение. Имена инстансов уникальны, в этом смысл "индексов"

Ну и конечно набегает ф-ционал import/export, т.е. извлечь инстансы из инстансера (получить "обычные" объекты с которыми можно работать индивидуально), и наоборот, поместить в инстансер. Причем помещать можно не только ранее извлеченные, требуется чтобы сбивалось базовое имя и геометрия. Ну вот и имеем то что имеем

Ага, пошли "разговоры о задаче", "как всегда" корявая постановка и.т.п. Улыбающийся

Если у вас несортированный массив с требованием сохранять порядок - ну тут ничего кроме перебора особо не придумаешь.
Как в том анекдоте
Цитировать
Ну уж после 3 стаканов Вы бы точно так работать не смогли!!
Записан
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #24 : Сентябрь 10, 2020, 12:11 »

Если вы 40 минут будете молчать а потом гордо скажете "я сделяль", то вас не возьмут, даже если ваше решение оптимальное.
Цель задач - не только решить их, а посмотреть как кандидат думает, рассуждает, какие трейд-оффы делает.

Если человек пришел и 40 минут тупо молчит и ничего не рассказывает, это уже как то в общем подозрительно... Туда ли он вообще пришел?

Но я хоть убей не понимаю этого тренда с "задачами". Что хотят от человека? чтобы он алгоритмы на доске писал или чтоб работу работал?
Бизнес-задачу за полчаса "на коленке" он не решит, это надо специфику сперва понимать.
А по "тестовому примеру" мало что можно выяснить. Ну разве что если на "джуна" человек идет.
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #25 : Сентябрь 10, 2020, 13:47 »

Racheengel, я вижу, перешел на "работу с людьми", какие-то там вставки его уже не интересуют Улыбающийся
Но я хоть убей не понимаю этого тренда с "задачами". Что хотят от человека? чтобы он алгоритмы на доске писал или чтоб работу работал?
Бизнес-задачу за полчаса "на коленке" он не решит, это надо специфику сперва понимать.
А по "тестовому примеру" мало что можно выяснить. Ну разве что если на "джуна" человек идет.
И что, давайте брать всех с улицы? Это еще хорошо если потом от него легко избавиться.

Что хотят от человека?
А действительно, что Вы от него хотите? Какую работу он должен делать? Если тестовая задача с этим связана - она вполне к месту. А оценить какие-то общие познания - можно и без нее
Записан
RedDog
Частый гость
***
Offline Offline

Сообщений: 221


Просмотр профиля
« Ответ #26 : Сентябрь 10, 2020, 14:18 »

А по "тестовому примеру" мало что можно выяснить. Ну разве что если на "джуна" человек идет.
Подобные этой теме примеры решать в коде не надо на собеседовании.
А вот понять, по рассуждениям может ли чел мыслить в нужном направлении, вполне возможно.
Если кандидат заявляет что "работал с большими объемами данных" а в рассуждениях над подобными примерами молчит, то можно на счет него сделать неутешительный вывод.
PS: но на самом деле, для собеседования данный пример немного жестковат.
Записан
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #27 : Сентябрь 10, 2020, 15:46 »

Подобные этой теме примеры решать в коде не надо на собеседовании.
А вот понять, по рассуждениям может ли чел мыслить в нужном направлении, вполне возможно.
Если кандидат заявляет что "работал с большими объемами данных" а в рассуждениях над подобными примерами молчит, то можно на счет него сделать неутешительный вывод.
PS: но на самом деле, для собеседования данный пример немного жестковат.

И я про то же. Пусть человек "выговорится". Если "сам делал" - расскажет, что да как. Если только чтоб "вайти" - и без примера отвалится.
P.S. если не отвалится, то дорога ему в продажники, а не в девелоперы Улыбающийся
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #28 : Сентябрь 10, 2020, 15:52 »

Но я хоть убей не понимаю этого тренда с "задачами". Что хотят от человека? чтобы он алгоритмы на доске писал или чтоб работу работал?
Бизнес-задачу за полчаса "на коленке" он не решит, это надо специфику сперва понимать.
А по "тестовому примеру" мало что можно выяснить. Ну разве что если на "джуна" человек идет.

Ну смотрите, вы даете задачку реализовать баннерокрутилку. У вас есть несортированный набор рекламных объявлений с приоритетом. Вам надо показать топ-10 по высшему приоритету. Прикладная задача? Вполне.
Как решать? Можно отсортировать и взять топ 10 (N*logN + C). Если кандидат умный то он знает про std::partial_sort (N*logK, K == 10 в примере). Если он решал задачки на литкоде, то знает как сделать за (примерно) линейное время (quickselect).
Вполне себе бизнес задача на 40 минут (ну ладно, квиксорт\квикселект я за 40 минут не напишу да и не нужно это, но если кандидат упомянет такое решение - это плюс, собственно тут обсуждение и нужно).
Записан
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #29 : Сентябрь 10, 2020, 15:56 »

Racheengel, я вижу, перешел на "работу с людьми", какие-то там вставки его уже не интересуют Улыбающийся

"Вставок без людей не бывает" Улыбающийся

Ну а по теме, действительно, условие по сохранению "порядка" - оно новое. Поэтому и решение меняется.
Код лень писать, но я бы сначала сделал кэш по первичному ключу в виде <имя:<номер:позиция>>  ( QMap<QString, QMap<int, int> >)



Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
Страниц: 1 [2] 3 4   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.395 секунд. Запросов: 23.