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

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

Страниц: [1]   Вниз
  Печать  
Автор Тема: Сохранить "диффы"  (Прочитано 8650 раз)
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« : Январь 26, 2020, 11:10 »

Добрый день

Молодежный термин "диффы" мелькает довольно часто и подразумевается что, мол, тут все ясно Улыбающийся Ну "в прынцыпе" да, ясно, а конкретно - вот как-то не очень.

Есть 2 блока памяти одинакового размера, предполагается что многие байты совпадают. Нужно сохранить "только изменения" так чтобы имея первый блок + изменения получить второй.

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

Спасибо
Записан
Azazello
Самовар
**
Offline Offline

Сообщений: 103


Просмотр профиля
« Ответ #1 : Январь 26, 2020, 13:44 »


Есть 2 блока памяти одинакового размера, предполагается что многие байты совпадают. Нужно сохранить "только изменения" так чтобы имея первый блок + изменения получить второй.

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

Спасибо

Да, бывает. Как зависнишь иногда над чем то.
Код:
#include <QCoreApplication>

#include <QLatin1String>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QByteArray s1("Test");
    QByteArray s2("Lkst");

    struct diff {
        int startPos;
        int size;
        char *data;
    };

    QVector <diff> diffs;

    int diffPos = -1;
    for (int i = 0; i < s1.size(); ++i) {
        if (s1[i] != s2[i]) {
            if (diffPos == -1)
                diffPos = i;
        } else {
            if (diffPos != -1 ) {
                int size = i - diffPos;
                diffs.append({diffPos,size,s2.data() + diffPos});
                diffPos = -1;
            }
        }
    }

    //diff  in last element(s)
    if (diffPos != -1) {
        int size = s1.size() - diffPos;
        diffs.append({diffPos,size,s2.data() + diffPos});
    }

    //output diffs
    for (auto &i: diffs) {
        qDebug() << QByteArray(i.data,i.size);
    }

   QByteArray restored(s1);
    //restore
    for (auto &i: diffs) {
        restored.replace(i.startPos,i.size,i.data,i.size);
    }

   //output restore mem block from the source by diffs
    qDebug() << restored;

    return a.exec();
}


На полноту тестирования не претендую, думаю смысл упаковки понятен - сохраняем позицию, размер и указатель на начало данных.

Учитывая, что работа с памятью и вам нужно это быстро делать - _mm_loadu_si128, _mm_cmpeq_epi8
Либо в исходники QByteArray (или интет), либо использовать QByteArray как источник данных (QByteArray::compare - там тоже на sse, правда только для поиска вхождения возможно использовать).

На самом деле в плане оптимизации производительности задача не такая уж тривиальная, как кажется.
На SSE, даже если будет больше кода, выигрышь в производительности будет ощутим (два порядка - т.е. в 4 раза запросто в человеской десятичной системе) - обработка пачками, что уменьшит количество итераций цикла for (предсказание ветвлений) и много других плюшек.
Если же задача оптимизации объема занимаемой памяти (например для серилизации памяти на диск), выигрышь будет ощутим даже на чистом с++ (что выше). Опять же, каков процент изменений, дифы в цепочках будут? Вопросов много. Это я так. Вы уж их сами себе позадавайте.

« Последнее редактирование: Январь 26, 2020, 14:27 от Azazello » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #2 : Январь 27, 2020, 10:47 »

Код
C++ (Qt)
diffs.append({diffPos,size,s2.data() + diffPos});
Так Вы храните указатель на данные s2, а на восстановлении s2 отсутствует, его и надо получить.

Если же задача оптимизации объема занимаемой памяти (например для серилизации памяти на диск), выигрышь будет ощутим даже на чистом с++ (что выше). Опять же, каков процент изменений, дифы в цепочках будут?
Да, запись в файл. Вчера гонял один проект юзера. 140 объектов, 300 кадров. Пакует неплохо, напр из 500K делает 16K. Но выходной файл все же 660 Mb и пишется задумчиво.. В данном случае это не катастрофа, но и объектов и кадров запросто может быть в 10 раз больше. И что делать - хз. Какие-то zip(ы) - вряд ли, там в основном флоты, жмутся плохо.

Просто "к слову". Если существительное оканчивается на шипящую, то для женского рода мягкий знак пишется (мышь, чушь, печь), а для мужского нет (малыш, товарищ, выигрыш).
Записан
Azazello
Самовар
**
Offline Offline

Сообщений: 103


Просмотр профиля
« Ответ #3 : Январь 27, 2020, 11:58 »

Так Вы храните указатель на данные s2, а на восстановлении s2 отсутствует, его и надо получить.

Так измените структуру на:
Код:
struct diff {
        int startPos;
        QByteArray data;
    };

Этож всего лишь пример. Тем более если Вы сохраняете, предыдущие данные присутствует, поэтому будет лишнее копирование.
« Последнее редактирование: Январь 27, 2020, 12:52 от Azazello » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #4 : Январь 27, 2020, 13:13 »

Так измените структуру на:
Код:
struct diff {
        int startPos;
        QByteArray data;
    };
1) Наверное startPos все-таки unsigned int. Академически даже size_t но на это наверно лучше забить, считаем блоков > 4Gb нет.

2) Копировать данные в QByteArray data - ну на выходе требуется блок памяти для сериализации (а не контейнеры). Вылить вектор diff в поток можно, но тогда лучше делать это с той структурой что Вы сначала предложили.

3) Пусть пакуются данные байты которых совпадают "через один", напр строки
Цитировать
abcdefgh
aacceegg
При этом упакованный размер намного превысит изначальный

Раздражает, да? Меня тоже Улыбающийся Какая-то совершенно глупая задачка (явно не стоит выеденного яйца), а из нее все лезут и лезут проблемы, та шо такое  Плачущий
Записан
Azazello
Самовар
**
Offline Offline

Сообщений: 103


Просмотр профиля
« Ответ #5 : Январь 27, 2020, 13:24 »

1) Наверное startPos все-таки unsigned int. Академически даже size_t но на это наверно лучше забить, считаем блоков > 4Gb нет.

Демагогия началась. Qt - int, std - size_t.
Но это вообще не имеет никакого отношение к решению данной проблемы.

Цитировать
3) Пусть пакуются данные байты которых совпадают "через один", напр строки
abcdefgh
aacceegg
При этом упакованный размер намного превысит изначальный

Раздражает, да? Меня тоже Улыбающийся Какая-то совершенно глупая задачка (явно не стоит выеденного яйца), а из нее все лезут и лезут проблемы, та шо такое  Плачущий

Ну так у вас 64 разрядная вроде система?
Тогда сделайте проверку сразу на блок символов по 8 байт. В вашем случае достаточно взять блок по 2 байта, чтобы уйти от этой проблемы.

Но я думаю вы надумали её. В ваших блоках хранятся совершенно конкретные данные, конкретных типов, и думаю, они все выравнены по 64 (32) бита, так что сравнение побайтово имеет, гм, чисто академический интерес Улыбающийся

Но даже если отбросить эти теоретические выкладки, для хранения изменений вам нужно минимум 2 int  - итого 64 бита. Глупо записывать 1 байт данных на 8 байт метаданных, так что 64 таки блок делать. Или запихнуть метаданные в 1 int, тогда 32 байта блок.

Или решайте совсем универсально.
Определите эффективный размер блока.
Создайте матрицу, которая содержит изменения блоков (да, нет)
Объедините блоки, которые рядом (опционально, зависит от размера блока)
Посчитайте размер с метаданными всего диффа. Если не устраивает (например 70% от общего), записывайте оригинал.
« Последнее редактирование: Январь 27, 2020, 15:26 от Azazello » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #6 : Январь 27, 2020, 16:14 »

Qt - int, std - size_t.
Но это вообще не имеет никакого отношение к решению данной проблемы.
Имеет, и с этой (вроде мелкой - но гнусной) проблемкой я сталкиваюсь уже не раз. Делать "по честному" (все 64) хорошо пока это ничего не стоит. А вот здесь уже стоит, и что делать - хз. Как минимум нужны комменты что работает до 4Gb, может лучше assert.

Демагогия началась.
...
Тогда сделайте проверку
...
Или решайте совсем
...
Создайте матрицу..
Таких советов может дать массу каждый. Но Вы сами бы последовали хотя бы одному из них? Улыбающийся Думаю что нет. Писать код не хотите, уже поняли что там работа кропотливая. Ладно, давайте по-другому

Вот эта возня с "паковкой" - она неизбежна или все же надумана? Если последнее - то где более "ызячное" решение (лично я его не вижу)
Записан
Azazello
Самовар
**
Offline Offline

Сообщений: 103


Просмотр профиля
« Ответ #7 : Январь 27, 2020, 17:39 »

Имеет, и с этой (вроде мелкой - но гнусной) проблемкой я сталкиваюсь уже не раз. Делать "по честному" (все 64) хорошо пока это ничего не стоит. А вот здесь уже стоит, и что делать - хз. Как минимум нужны комменты что работает до 4Gb, может лучше assert.

Та делаете везде 64. А при записи обрезайте до 32-х. Исправить потом запись в одном месте, если превысит (а оно не привысит) ничего не будет стоить.

Цитировать
Таких советов может дать массу каждый. Но Вы сами бы последовали хотя бы одному из них? Улыбающийся Думаю что нет. Писать код не хотите, уже поняли что там работа кропотливая. Ладно, давайте по-другому

Каких советов. Да держите. Тестируйте сами и в std сами переводите.

Код:
#include <QCoreApplication>

#include <QCoreApplication>

#include <QBitArray>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QByteArray s1("Tksteeeeeeeeeeeeeeeeeeeeeeeeeeeeehhhhhhhhhhhhhhhhhhhhhhsdfsdfffffffffffffffffffffffff");
    QByteArray s2("Testeeeeeeeeeeeeeeeeeeefeeeeeeeeehhhhhhhhhhhhhhhhhhhhhhsdfsdfffffffffffffffffffffffff");

    struct diff {
        int startPos;
        int size;
        char *data;
    };

    QVector <diff> diffs;

    constexpr int BLOCK_SIZE = 8; //nice to store in degree 2
    const int dataSize = s1.size();

    //count of blocks
    int blockCount = (dataSize % BLOCK_SIZE == 0) ? dataSize / BLOCK_SIZE : dataSize / BLOCK_SIZE + 1;
    QBitArray diffVector(blockCount);  //std::bitset
    //fill vector
    for (int i = 0; i < blockCount; ++i) {
        int dataPos = i * blockCount;
        //in real word for perfomance need comapre in registers
        //for instance
        //warning  - need unbounding check!
        //if (((__int64)s1[dataPos] != (__int64)s2[dataPos]))
        //or memcmp
        if (s1.mid(dataPos,BLOCK_SIZE) != s2.mid(dataPos,BLOCK_SIZE))
            diffVector.setBit(i);
    }

    //optimize result vector - in this case union through the one block
    //does not matter if block size equal metadata size
    //bad idea if block size is more than metadata size
   //good idea if block size is less than metadata size
    for (int i = 2; i < diffVector.size();++i) {
        if (diffVector[i] && diffVector[i] == diffVector[i - 2])
            diffVector.setBit(i - 1);
    }


    int diffPos = -1;
    //calc diffs
    for (int i = 0; i < diffVector.size(); ++i) {
       if (diffVector[i]) {
           if (diffPos == -1)
                diffPos = i * BLOCK_SIZE;
       } else {
           if (diffPos != -1) {
               int size = i * BLOCK_SIZE - diffPos;
               diffs.append({diffPos,size,s2.data() + diffPos});
               diffPos = -1;
           }
       }
    }

    //diff  in last element(s)
    if (diffPos != -1) {
        int size = dataSize - diffPos;
        diffs.append({diffPos,size,s2.data() + diffPos});
    }


    //calc diff size
    int diffSize = 0;
    for (int i = 0; i < diffs.size(); ++i) {
        diffSize += diffs[i].size + 8;
    }

    //nothing to do - diff more that 70% of the source size
    double fillPercent = diffSize / (double)dataSize;
    if (fillPercent > 0.7) {
        return 0;
    }

    //DO SAVE

    return a.exec();
}


Создайте тесты и на своих рабочих данных гоняйте их, изменяя например размер блока и данные, чтобы оценить эффективность. Никто в этом мире на ваших данных не подскажет вам параметры.

Это как размер кластера на винчестере. Рекомендации то у всех есть, а определять самому надо.
Да и вообще подходов может быть много. Вы же можете хранить данные в памяти с идентификацией объекта. А объект сам знает, чем он отличается от другого объекта.

Можно цепочку создавать. Я так понимаю, данные кусками идут повторяющиеся.
Ну, можно сравнить с видео - предыдущий кадр практически совпадает с текущим. Пока не появится новая сцена, тогда нужно новую цепочку строить. Это мои предположения, т.к. мы не знаем ваши условия.

P.S. Подправил исходники, неверно размер вычислялся
« Последнее редактирование: Январь 28, 2020, 08:31 от Azazello » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #8 : Январь 28, 2020, 11:20 »

Каких советов. Да держите. Тестируйте сами и в std сами переводите.
Спасибо конечно, но что держать-то?  Улыбающийся
Цитировать
Вот тебе швейная машинка - почини и строчи сколько хочешь
"DO SAVE" (что Вы видимо оставили мне для самостоятельной работы) - не простая формальность. Пример
Цитировать
abcdefghabcdefgh
abcdefg11bcdefgh
Нужно сохранить 10 байт (8 заголовок + 2 данные), а не 24. Конечно Вы не обязаны давать рабочий пример, ну и ладно, поговорим по коду

1)
Код:
    struct diff {
        int startPos;
        int size;
        char *data;
    };
Зачем хранить data если известно смещение в оригинале?

2)
Код:
QBitArray diffVector(blockCount);  //std::bitset
Зачем здесь экономить биты снижая скорость (пусть немного)? Простецкий vector<char> лучше

3)
Код:
       //in real word for perfomance need comapre in registers
        //for instance
        //warning  - need unbounding check!
        //if (((__int64)s1[dataPos] != (__int64)s2[dataPos]))
        //or memcmp
        if (s1.mid(dataPos,BLOCK_SIZE) != s2.mid(dataPos,BLOCK_SIZE))
            diffVector.setBit(i);
Батенька, ну это же "lapsus manus" (как говорили древние). Правда такой код потом ускорять хорошо, счет пойдет не на % а на разы. Наверно в этом была идея.

4)
Код:
   //nothing to do - diff more that 70% of the source size
    double fillPercent = diffSize / (double)dataSize;
    if (fillPercent > 0.7) {
        return 0;
Не понял Вашу задумку. Пакованные данные (по сути та же байт-паковка) имеют формат
Цитировать
header (fixed size)
data (var size)
Отсюда вытекает что "расстояние" между записываемыми байтами в оригинале должно быть >= header_size. Пример
Цитировать
abcdefgh
1bcdefg2
Нужно писать 16 байт (8 заголовок + 8 данные), хотя только 2 байта различны. Иначе мы потратим 8 + 1 + 8 + 1. Пусть ВСЕ байты разные - ну и ладно, значит будет 1 блок (копия 2-го исходного блока) + 8 байт на header накладные расходы, переживем. Поэтому упаковка cостоится "при любой погоде". И никакой свободы с выбором "размера блока" нет - он тупо равен размеру заголовка.

5) Отсюда единственная возможность как-то усилить алгоритм - как-то сократить размер заголовка. Подозреваю что оптимальным будет размер 4 (3 байта на смещение, до 16 Mb + 1 байт на размер). Но однобайтовый счетчик будет переполняться, придется создавать больше блоков. Накладные расходы приемлемы (до 2%), но возня.. я не решился на этот вариант, тупо заточил на header_size =8

Да, и конечно мой вариант (аттач), а то нехорошо с моей стороны. Обратите внимание что контейнеры (обычно такие удобные) здесь только мешают. Единственный вектор и то наверно лучше снести. "Разбрасывать носки" тоже надо уметь  Улыбающийся

Можно цепочку создавать. Я так понимаю, данные кусками идут повторяющиеся.
Ну, можно сравнить с видео - предыдущий кадр практически совпадает с текущим. Пока не появится новая сцена, тогда нужно новую цепочку строить. Это мои предположения, т.к. мы не знаем ваши условия.
Любопытство может быть легко удовлетворено - это данные skin деформаций. Для каждой "кости" (60 и более на объект) записываются все индексы + вес всех вертексов на которые влияет эта кость + матрицы трансформа. Вот и объем прет. Ну теперь Вы знаете "мои условия" - это как-то помогло?  Улыбающийся


Записан
Azazello
Самовар
**
Offline Offline

Сообщений: 103


Просмотр профиля
« Ответ #9 : Январь 28, 2020, 21:17 »

Вы наверное прикалуетесь. Или Вам общения не хватает. Мыслей у меня умней нет.
Возможно, Вы хотите просто поговорить. Или постебаться.

Вот тебе швейная машинка - почини и строчи сколько хочешь.
"DO SAVE" (что Вы видимо оставили мне для самостоятельной работы) - не простая формальность. Пример
Ай. Извиняюсь. Зарылся и пошел писать и тестировать код. Через месяц встретимся.
Всё для Вас.
Особенно порадовало самостаятельная работа в одну строку.
qDebug() << diff.startPos << diff.QByteArray(diff.data,diff.size);
Где qDebug заменяется на поток записи в файл.

Цитировать
Зачем хранить data если известно смещение в оригинале?

Зачем здесь экономить биты снижая скорость (пусть немного)? Простецкий vector<char> лучше

Батенька, ну это же "lapsus manus" (как говорили древние). Правда такой код потом ускорять хорошо, счет пойдет не на % а на разы. Наверно в этом была идея.

Вы понимаете о чём Вы говорите?
Ваша задача оптимизировать объем записи. Зачем вообще думать о производительности?
Какая разница char* или bit. Вы я так понял человек процесса, а не результата.
Зачем отвлекаться на незначимые вещи, которые не имеют никакого значения?
Ну сделайте чтобы оно хоть как-то работало медленно, а вопрос производительсти решается на ура потом. Главное восприятие и понятность.
Но нет, оно не работает нифига, зато нужно рассуждать о задаче, которая разбита на этапы (чтобы лучше воспринималась) и рассуждать о производительности.

Делаем машину. Она не едет, зато рассуждения об оббивке сидений.

Кстати, что за фамильярность,
Цитировать
Батенька?
Вы же у нас тут "Облико морале".

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

Не хочу больше рассуждать об этом.
Закончу, что я имел в виду, а Вы не поняли: универсальный метод типа RLE  вам не грозит в связи с малым объемом данных. Хотите пробовать - протестируте, ок если я ошибаюсь.
 Значит нужно искать свой гребаный путь для своих данных  - находить пакеты отностительно похожих данных с одинаковой границей.

Люди на таких задачах только на тестовых данных недели тратят. НЕ! Швейную машинку ему дали не ту.
Недели и месяцы, чтобы просто проаналзировать, что происходит и только потом голову чухают, а как же его сделать?

"Боюсь за границы 4Gb вылезти при использовании int". Прикольный save на 4 Гб. Если вы её эффективно решите, можете забить на свою программу, вы уже миллионэр.
Не отвечайте на данный пост.

P.S. Не удержался.
Хотите алгоритм - вот вам.
1. Сохраняете свои блоки, при разных условия, мышкой водите там или чего. Короче для разных процессов.
2. Пишите тулузу, которая читает эти данные и визуализиует для Вас. Аля дефрагментор как раньше показывал. Синенькие совпадаю, красненькие жопас.
3. Думаете.
4. Находите почему так и что-то общее.
5. Прозреваете либо забиваете.
6. Задумываетесь.

P.S.S. Не отвечайте.
« Последнее редактирование: Январь 28, 2020, 21:58 от Azazello » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #10 : Январь 29, 2020, 08:59 »

Возможно, Вы хотите просто поговорить.
Да, и думаю все присутствующие тоже. Только полный идиот надеется что на форуме (любом) будут ему "памагать". Это типа утренней газеты, которую можно прочитать с интересом и пользой (узнав мнение др людей). Ну не всегда конечно
Или постебаться.
Нет

Да, и Вы мне помогли. Вариант что я выложил написан уже после общения с Вами, он не первый и даже не второй. Те были очень похожи на Ваши. И вот мне стало интересно - ну почему такая сопливая задачка выливается в нечто ужасное? Думаю что "довлеет" накопленный опыт. Напр мы интуитивно (или рефлекторно) лепим контейнеры (ну а как иначе?) и хотим с них что-то "наварить". Как только мне удалось от этого избавиться (не без труда) - все легло вполне прилично. Ведь это просто типа студенческая лаба.

Спасибо
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #11 : Январь 30, 2020, 22:08 »

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


Угу, только здесь нужны не контейнеры, а вью. Но вы пока не доросли, ведь "опыт контейнеров" и "студенческих либ" неискореним=)
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #12 : Январь 31, 2020, 11:48 »

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

Сообщений: 3260


Просмотр профиля
« Ответ #13 : Январь 31, 2020, 17:54 »

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

Ну вот же

Код:
struct CMemBlock {
char * mData;
UInt32 mSize;
};

Это уже давно (с++17) изобрели за вас. Не нравится строка, возьмите span чаров.
А "контейнеры" тут действительно не нужны.
Просто их везде пихали по той причине, что работать с std::string удобнее, чем с голым char*.
Теперь есть удобная обертка над "сишным массивом", тягать голые указатели больше не надо.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #14 : Февраль 01, 2020, 07:44 »

Это уже давно (с++17) изобрели за вас.
..
Теперь есть удобная обертка над "сишным массивом", тягать голые указатели больше не надо.
А, да, эта вещь давно "витала в воздухе", и я юзал не одну реализацию. Кстати когда я предложил одну из них здесь (см "Кладовая") - был подвергнут уничтожающей критике местных знатоков  Улыбающийся

Только не вижу как это связано с темой. Вроде "вьюить" тут ничего и не надо. Более естественно объявить аргументы векторами - но это придется делать по всему старому коду, а его не мало. В конце-концов "примитивно" совсем не значит "плохо"  Улыбающийся
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


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