Russian Qt Forum

Программирование => Общий => Тема начата: Igors от Сентябрь 15, 2017, 16:43



Название: Undo для данных мапы
Отправлено: Igors от Сентябрь 15, 2017, 16:43
Добрый день

Есть скромные данные в мапе упорядоченные по ключу "время" (double). Данные показываются в виде графика, юзер может менять как данные так и ключ таская точки по вертикали и/или горизонтали. Конечно когда время изменилось данные удаляются и вставляются снова - теперь они уже в следуют в др порядке.

Как сохранить undo перед активностью юзера? Приделывать "уникальное ID" ну очень не хотелось бы

Спасибо


Название: Re: Undo для данных мапы
Отправлено: Racheengel от Сентябрь 18, 2017, 21:48
memento как вариант.
сериализировать все ключи и значения после каждого изменения.


Название: Re: Undo для данных мапы
Отправлено: deMax от Сентябрь 19, 2017, 10:48
Есть паттерн команда. Когда изменяете что то на графике создаете команду(сдвинуть точку туда то, удалить(с запоминанием что было), создать...) и эта команда меняет данные в вашем Qmap. Тогда будет легко пройтись по изменениям.


Название: Re: Undo для данных мапы
Отправлено: Igors от Сентябрь 19, 2017, 11:54
memento как вариант.
сериализировать все ключи и значения после каждого изменения.
Да, тоже не вижу ничего лучшего

Есть паттерн команда. Когда изменяете что то на графике создаете команду(сдвинуть точку туда то, удалить(с запоминанием что было), создать...) и эта команда меняет данные в вашем Qmap. Тогда будет легко пройтись по изменениям.
Команла - не команда... для undo что сохранять?


Название: Re: Undo для данных мапы
Отправлено: deMax от Сентябрь 19, 2017, 12:02
Команла - не команда... для undo что сохранять?
Разницу между что было и что стало, список действий: Удалили такие то ключи(с содержимым), добавили такие, изменили такие(старое содержимое). Тогда undo будет тоже самое что и обычные действия пользователя(только команда будет инвертирована).


Название: Re: Undo для данных мапы
Отправлено: Igors от Сентябрь 19, 2017, 14:09
Разницу между что было и что стало, список действий: Удалили такие то ключи(с содержимым), добавили такие, изменили такие(старое содержимое). Тогда undo будет тоже самое что и обычные действия пользователя(только команда будет инвертирована).
Во как хорошо с "паттернами" :) Вникать в задачу, думать - та зачем? Лепи по образцу - и все дела! Только вот почему-то частенько так не выходит, напр в данном случае не вижу как мне "инвертировать" время - как же undo найдет эл-т что был изменен?


Название: Re: Undo для данных мапы
Отправлено: ViTech от Сентябрь 19, 2017, 15:08
Только вот почему-то частенько так не выходит, напр в данном случае не вижу как мне "инвертировать" время - как же undo найдет эл-т что был изменен?

Undo и не должен ничего искать, это команда/список команд, которые изменяют данные.

Лепи по образцу - и все дела!

Именно так: по образцу. Благо их в интернетах полно. Надо только почитать.


Название: Re: Undo для данных мапы
Отправлено: Igors от Сентябрь 19, 2017, 15:27
Undo и не должен ничего искать, это команда/список команд, которые изменяют данные.
Так в том и вопрос: какие (или которые) данные? Не вижу каким образом undo перепишет время/данные именно того эл-та что был изменен.

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


Название: Re: Undo для данных мапы
Отправлено: ViTech от Сентябрь 19, 2017, 15:35
юзер может менять как данные так и ключ таская точки по вертикали и/или горизонтали

Эти данные какими методами/функциями меняются в коде? Вот их в команды и записывайте.

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

И в чём проблема сожрать очередную ссылочку и осмыслить её применимость к поставленной задаче? Или Вы хотите чтобы другие за Вас жевали? :)


Название: Re: Undo для данных мапы
Отправлено: deMax от Сентябрь 19, 2017, 16:18
Как сохранить undo перед активностью юзера? Приделывать "уникальное ID" ну очень не хотелось бы
ну если время уникально, то ориентироваться на время (удаляем такие то ключи, добавляем такие). Время это ключ, изменение это тоже самое что и удалить+добавить.
если время не уникально то только добавлением id. Можно было бы запоминать номер по порядку какой элемент нужно удалить, но он же будет сортировать всегда по разному(и с одинаковым ключом элементы будут меняться местами, а если и данные одинаковы).
имхо оптимальный вариант запоминать по дате, а для элементов с одинаковым временем создавать backup.(имхо оптимально)


Название: Re: Undo для данных мапы
Отправлено: deMax от Сентябрь 19, 2017, 16:33
на самом деле проще: если удаляете то удаляйте все элементы с таким же временем и потом их добавляете.


Название: Re: Undo для данных мапы
Отправлено: Igors от Сентябрь 19, 2017, 16:54
ну если время уникально, то ориентироваться на время (удаляем такие то ключи, добавляем такие). Время это ключ, изменение это тоже самое что и удалить+добавить.
Здесь конкретный вопрос (а не "принципиальные основы"). Думал так: записать новое время + старое время. Тогда undo по "новому" найдет эл-т, изменит время на "старое" и пересортирует. Вроде должно работать, но как-то подозрительно... И нужно новое время иметь на момент undo, это не всегда так уж просто

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

И в чём проблема сожрать очередную ссылочку и осмыслить её применимость к поставленной задаче?
Э нет, ничего осмысливать жрущий уже не будет, просто времени на это уже не останется,  ведь надо сожрать как можно больше :)


Название: Re: Undo для данных мапы
Отправлено: ViTech от Сентябрь 19, 2017, 19:17
Похоже подробностей о том, как в коде изменяются данные мапы и как часто надо откатывать действия пользователя, мы не дождёмся. Впрочем, нам не привыкать :). Поэтому вариант наудачу, а вдруг:

Код
C++ (Qt)
using Action = function<void()>;
 
struct UserAction
{
   Action redo;
   Action undo;
};
 
using Storage = map<double, double>;
 
void print(const Storage & storage)
{
   cout << "{ ";
   for (auto value:storage)
       cout << "{" << value.first << ", " << value.second << "}, ";
   cout << " }" << endl;
}
 
void addValue(Storage & storage, double time, double value)
{
   storage.emplace(time, value);
}
 
void removeValue(Storage & storage, double time)
{
   storage.erase(time);
}
 
void testUndoRedo()
{
   cout << "initial storage:" << endl;
   Storage storage;
 
   addValue(storage, 5, 7);
   addValue(storage, 6, 8);
   print(storage);
 
   cout << "user actions:" << endl;
   vector<UserAction> actions;
 
   UserAction add_value{ bind(&addValue, ref(storage), 7, 9)
                       , bind(&removeValue, ref(storage), 7) };
   actions.push_back(add_value);
   (*actions.rbegin()).redo();
   print(storage);
 
   UserAction add_other_value{ bind(&addValue, ref(storage), 6.5, 4)
                             , bind(&removeValue, ref(storage), 6.5) };
   actions.push_back(add_other_value);
   (*actions.rbegin()).redo();
   print(storage);
 
   UserAction remove_value{ bind(&removeValue, ref(storage), 6)
                          , bind(&addValue, ref(storage), 6, storage.at(6)) };
   actions.push_back(remove_value);
   (*actions.rbegin()).redo();
   print(storage);
 
   cout << "undo user actions:" << endl;
   for (auto i = actions.rbegin(); i != actions.rend(); ++i)
   {
       (*i).undo();
       print(storage);
   }
 
   cout << "redo user actions:" << endl;
   for (auto i = actions.begin(); i != actions.end(); ++i)
   {
       (*i).redo();
       print(storage);
   }
}
 
int main()
{
   testUndoRedo();
 
   return 0;
}
 
код на ideone (https://ideone.com/Yk9MoP)

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


Название: Re: Undo для данных мапы
Отправлено: deMax от Сентябрь 20, 2017, 08:23
Здесь конкретный вопрос (а не "принципиальные основы"). Думал так: записать новое время + старое время. Тогда undo по "новому" найдет эл-т, изменит время на "старое" и пересортирует. Вроде должно работать, но как-то подозрительно... И нужно новое время иметь на момент undo, это не всегда так уж просто
А если новое и старое время будет с кем то совпадать? городить логику?
Код:
struct Data{}
struct UserAction { QMap<Time, Data> oldData, newData; }
Если время удаляемых данных совпадает с другими данными, то удаляем их тоже, а потом добавим. Если вам нужно знать что такой то элемент изменился(анимацию например показать), тогда:
Код:
struct UserAction { QMap<Time, Data> oldData, newData; QVector<QPair<int,int>> moveData; } // moveData содержит позицию в oldData и newData для элементов которые изменились.
Тут предлагали бекапить весь массив на каждой итерации, я предлагаю бекапить изменения(причем все данные с таким же временем тоже бекапить, что бы не городить логику). Вроде все просто или я чего то не понял?


Название: Re: Undo для данных мапы
Отправлено: Igors от Сентябрь 20, 2017, 10:04
ViTech, спасибо за приятную демонстрацию современного сынтаксыса, но меня интересует алгоритм/принцип, можно просто словами, напр так
Думал так: записать новое время + старое время. Тогда undo по "новому" найдет эл-т, изменит время на "старое" и пересортирует. Вроде должно работать, но как-то подозрительно... И нужно новое время иметь на момент undo, это не всегда так уж просто

А если новое и старое время будет с кем то совпадать? городить логику?
А чем грозит "совпадение времени"? Ну сработает undo вхолостую, не беда
Тут предлагали бекапить весь массив на каждой итерации, я предлагаю бекапить изменения(причем все данные с таким же временем тоже бекапить, что бы не городить логику). Вроде все просто или я чего то не понял?
Если "некоторые" (не все) то undo должно сначала вырезать данные время которых изменилось (а потом вставить запомненное).

Тут полазил по коду - к сожалению, нет, ключ не уникален. Напр копирование делается так: дублятся выбранные точки и юзер таскает этот дубликат. Когда мыша отпущена удаляются точки для которых ключ И ДАННЫЕ одинаковы.

В общем, впечатление такое что, наверное, можно, но уж слишком "хитро-делано" выходит. Наверное стоит остановиться на туповатом но простом и надежном варианте "сохранить всех"


Название: Re: Undo для данных мапы
Отправлено: deMax от Сентябрь 20, 2017, 11:02
А чем грозит "совпадение времени"? Ну сработает undo вхолостую, не беда
А как различать который удалять если у нас время это ключ. Поэтому удаляем и добавляем все совпадающие по времени.
пример:
было: (1:00, Data1) (2:00, Data2) (2:00, Data3) (3:00, Data4) (3:00, Data5)
стало: (1:00, Data1) (2:00, Data2) (2:00, Data3new) (3:00, Data4) (3:00, Data5)

действие:
old:  (2:00, Data2) (2:00, Data3)
new: (2:00, Data2) (2:00, Data3new)

вперед: удалить по времени из old, добавить new
назад: удалить по времени из new, добавить old

Вроде бы все просто. Или я что то не понял?


Название: Re: Undo для данных мапы
Отправлено: ViTech от Сентябрь 20, 2017, 11:45
ViTech, спасибо за приятную демонстрацию современного сынтаксыса, но меня интересует алгоритм/принцип, можно просто словами, напр так...

Когда Вам приводят пример кода, Вы только на синтаксис смотрите? :) Кроме синтаксиса, этот вариант чем не устраивает, для решения задачи в формулировке из первого сообщения?


Название: Re: Undo для данных мапы
Отправлено: Igors от Сентябрь 20, 2017, 15:05
вперед: удалить по времени из old, добавить new
назад: удалить по времени из new, добавить old
Так а Вы какую задачу решаете? :) Вопрос был сохранить для undo изменение времени т.е самого ключа (а не только данных). Хотя вероятно и здесь Ваша находка (всех с тем же ключом) будет работать. Я, правду сказать, не додумался что так можно.

Кроме синтаксиса, этот вариант чем не устраивает, для решения задачи в формулировке из первого сообщения?
Юзер нажал на точку графика и потянул, т.е. drag начался. Поймав этот момент я тупо сохранил все данные для undo и закрыл его блок нафиг. Теперь на каждое перемещение точек я перестраиваю данные, сортирую, и показываю юзеру новый график. Undo меня уже совершенно не волнует, для него я все сделал.  А что будет с Вашей схемой работы?



Название: Re: Undo для данных мапы
Отправлено: ViTech от Сентябрь 20, 2017, 15:42
Юзер нажал на точку графика и потянул, т.е. drag начался. Поймав этот момент я тупо сохранил все данные для undo и закрыл его блок нафиг. Теперь на каждое перемещение точек я перестраиваю данные, сортирую, и показываю юзеру новый график. Undo меня уже совершенно не волнует, для него я все сделал.  А что будет с Вашей схемой работы?

Надо определить набор действий, которые изменяют данные. "Юзер нажал на точку графика и потянул, т.е. drag начался." - это что за действие? Допустим "перемещение значения", примерно:
Код
C++ (Qt)
void moveValue(Storage & storage, double from_time, double value, double to_time )
{
   storage.erase(from_time);
   storage.emplace(to_time, value);
}
 
в списке команд соответственно:
Код
C++ (Qt)
   UserAction move_value{ bind(&moveValue, ref(storage), 5, 7, 9)
                        , bind(&moveValue, ref(storage), 9, 7, 5) };
   actions.push_back(move_value);
 

При перетаскивании точки графика будет возникать множество moveValue(), постоянно добавлять их в actions не надо, достаточно изменять последнее действие, которое зафиксируется при "отпускании" точки юзером. Итого добавится одна команда, полностью отменяющая перетаскивание.


Название: Re: Undo для данных мапы
Отправлено: Авварон от Сентябрь 20, 2017, 17:49
Я бы сделал команду с 3мя значениями: ключ, список старых value, список новых value.
Если список пустой, то при undo/redo делаем erase ключа. Иначе делаем ренж-инсерт списка старых\новых значений в зависимости от undo/redo.
В случае, если мапа обычная, а не мульти, вместо списков можно хранить maybe на значение.

Если данных много, а изменений мало, то можно хранить список изменений - add/delete/update + value. Причем update == delete old + add new (т.е. есть всего 2 типа действий, но update разбиваем на 2). При redo действия выполняем слева направо, при redo - справа налево.


Название: Re: Undo для данных мапы
Отправлено: Racheengel от Сентябрь 20, 2017, 19:55
Вот тут идея, как обощить команды, основываясь на той же сериализации:
http://www.emodel.org.ua/index.php/uk/57-archive/2017-%D1%80%D1%96%D0%BA/39-1/997-39-1-6-u.html


Название: Re: Undo для данных мапы
Отправлено: Igors от Сентябрь 21, 2017, 09:57
А как вообще двигать точки (даже без undo)? У меня там довольно развесисто, не мапа, но все равно есть ключ/данные. А для мапы предлагаю так
Код
C++ (Qt)
typedef QMap<double, CData> TMap;
typedef TMap::iterator TIter;
typedef QVector<TIter> TMapSelection;
 
void MoveSelection( TMap & map, TMapSelection & selection, double dx, double dy )
{
 for (int i = 0; i < selection.size(); ++i) {
   TIter & it = selection[i];
   CData temp = it.value() + dy;
   double time = it.key() + dx;
   map.erase(itime);
   it = map.insert(time, temp);
 }
}
 
Ну и как-то не вижу легкого способа встроить сюда undo. Конечно сделать можно, но выходит весьма громоздко


Название: Re: Undo для данных мапы
Отправлено: Igors от Сентябрь 21, 2017, 10:08
При перетаскивании точки графика будет возникать множество moveValue(), постоянно добавлять их в actions не надо, достаточно изменять последнее действие, которое зафиксируется при "отпускании" точки юзером. Итого добавится одна команда, полностью отменяющая перетаскивание.
Так рекомендуют в букваре, но Undo у меня свое и "merge" там нет, ну то ладно, мои проблемы. Но так ли уж это хорошо обновлять undo на каждое движение мыша? А при exception можно еще и остаться с невалидным undo. Да и c merge еще придется пыхтеть (см selection в посте выше)


Название: Re: Undo для данных мапы
Отправлено: Old от Сентябрь 21, 2017, 10:25
Но так ли уж это хорошо обновлять undo на каждое движение мыша?
Ну так вам и предлагают undo-стек обновлять только после завершения действия пользователем, один раз, в конце, когда пользователь кнопочку на мышке отпустит.

При перетаскивании точки графика будет возникать множество moveValue(), постоянно добавлять их в actions не надо, достаточно изменять последнее действие, которое зафиксируется при "отпускании" точки юзером. Итого добавится одна команда, полностью отменяющая перетаскивание.


Название: Re: Undo для данных мапы
Отправлено: deMax от Сентябрь 22, 2017, 09:21
Так а Вы какую задачу решаете? :) Вопрос был сохранить для undo изменение времени т.е самого ключа (а не только данных). Хотя вероятно и здесь Ваша находка (всех с тем же ключом) будет работать. Я, правду сказать, не додумался что так можно.
Будет. Это самый простой способ, но увеличивает объем данных на действия пользователя(перемещение).
второй способ: если ввести идентификатор, это сократит объем действий пользователя при перемещении, но увеличит объем данных.
третий способ: дополнительные данные не требуются, но дольше работает(удаление и перемещение сравнивает данные)
UserAction{ QVector<Time, Data> old, new; QVector<QPair<TimeOld, TimeNew>,Data> move }
old - мы ищем первый элемент точно с таким временем и данными и удаляем(нет разницы какой из одинаковых элементов удалять).
move - ищем первый элемент с TimeOld,Data и меняем время
new - добавляем(тут просто)
для операции "вперед" вначале удаляем old, потом перемещаем(возможно надо запомнить перемещенные элементы и не перемещать их второй раз, хотя имхо и без этого будет работать), потом добавляем
операция назад наоборот удаляем new, перемещаем move, добавляем old
p.s. хотя возможно порядок не важен.


Название: Re: Undo для данных мапы
Отправлено: deMax от Сентябрь 22, 2017, 09:28
первый способ оптимален когда: много данный, мало действий пользователя(данные на часто пересекаются по времени)
второй: по сути тоже самое что и третий(жрет больше данный но быстрее ищет), вместо полного сравнивания структуры Data будут сравниваться идентификаторы.
третий: если будет очень много данных с одинаковым временем, будет долго работать. Особенно если struct Data много весит.