Quantcast
Channel: Cool Software Blog
Viewing all 78 articles
Browse latest View live

C++: конструкторы и виртуальные функции

$
0
0
Как говорится, век - живи, век учись.

Захотелось мне вынести инициализацию данных класса в виртуальную функцию init(), с тем, чтобы классы-наследники могли переопределить ее и добавить в инициализацию что-то своё. Примерно так:

  1. class C1
  2. {
  3. protected:
  4.     int m_count;
  5.     virtualvoid init() { m_count = 1; }
  6. public:
  7.     C1() { init(); }
  8.     inlineint count() const { return m_count; }
  9. };
  10.  
  11. class C2: public C1
  12. {
  13. protected:
  14.     virtualvoid init() { C1::init(); m_count = m_count+1; }
  15. public:
  16.     C2() : C1() {}
  17. };
  18.  
  19. int _tmain(int argc, _TCHAR* argv[])
  20. {
  21.     C1 c1;
  22.     _tprintf(_T("C1 count: %i\n"), c1.count());
  23.     C2 c2;
  24.     _tprintf(_T("C2 count: %i\n"), c2.count());
  25.     return 0;
  26. }

Результат получается такой:

C1 count: 1
C2 count: 1

Опа! Оказывается переопределенная в классе C2 функция init() не вызывается. Получается, что если вызвать из конструктора класса виртуальную функцию этого же класса, то обычный метод вызова виртуальных функций через vtable задействован не будет - функция будет вызвана как если бы они была не-виртуальной. По-моему такое поведение не выглядит логичным, поэтому от вызова виртуальных функций из конструктора лучше отказаться.

PS. Из деструктора вызывать виртуальные функции тоже не следует.

По поводу того, что при вызове виртуальных функций из конструктора (и деструктора) не используется vtable, - тут я, похоже, был не прав. vtable используется, только в конструкторе класса C1 (см. пример) используется vtable класса C1, а не класса-потомка C2 - вот в чем дело. Аналогично, в деструкторе C1 также используется vtable класса С1.

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

C++: обработка исключений

$
0
0
В C++ под Windows есть два способа обработки исключений - традиционный для C++ с пом. try/catch и т.н. структурная обработка исключений или SEH. Основная разница между ними в том, что с помощью try/catch можно реализовывать для разных типов исключений C++ различную реакцию (поведение), а структурная обработка  исключений способна отловить ситуации, которые с пом. catch не ловятся, например, Divide By Zero или Access Violation. SEH - это механизм, предоставляемый операционной системой и на самом деле try/catch реализуется также через него.

Одновременно в одной и той же процедуре использовать оба метода обработки исключений (try/catch и SEH) нельзя. Но можно использовать в одной процедуре try/cath, а в другой SEH.

Оба метода обработки исключений не идеальны и имеют свои достоинства и недостатки.

Основной недостаток try/catch заключается в том, что он не позволяет отловить и обработать все ошибки, а также не показывает место возникновения ошибки. Впрочем, в книжке Джеффри Рихтера (Windows для профессионалов. Создание эффективных Win32-пpилoжeний с учетом специфики 64-разрядной версии Windows) приводится способ перехвата структурных исключений:

1. Нужно создать класс CSE для идентификации структурного исключения:

  1. #include<eh.h>
  2. #include<stdexcept>
  3.  
  4. class CSE
  5. {
  6. public:
  7.     staticvoid MapSEToCE(){ _set_se_translator(TranslateSEToCE); }
  8.     operator DWORD() { return (m_er.ExceptionCode);}
  9.  
  10. private:
  11.     CSE(PEXCEPTION_POINTERS pep)
  12.     {
  13.         m_er = *pep->ExceptionRecord;
  14.         m_context = *pep->ContextRecord;
  15.         if (m_er.ExceptionCode == 0xe06d7363)
  16.         {
  17.             printf("C++ Exception: Code 0x%x, Address 0x%x\n", m_er.ExceptionCode, m_er.ExceptionAddress);
  18.         }
  19.         else
  20.         {
  21.             printf("SEH Exception: Code 0x%x, Address 0x%x\n", m_er.ExceptionCode, m_er.ExceptionAddress);
  22.         }
  23.     }
  24.     staticvoid_cdecl TranslateSEToCE(UINT deEC,PEXCEPTION_POINTERS pep)
  25.     {
  26.         throw CSE(pep);
  27.     }
  28.  
  29. private:
  30.     EXCEPTION_RECORD m_er;
  31.     CONTEXT m_context;
  32. };

2. Для каждого потока надо вызвать один раз CSE::MapSEToCE(), после чего можно обрабатывать структурные исключения как обычные исключения C++:

  1. int DivideByZero()
  2. {
  3.     int x = 100;
  4.     int y = 0;
  5.     return x / y;
  6. }
  7.  
  8. void ThrowException()
  9. {
  10.     throw std::out_of_range("out_of_range");
  11. }
  12.  
  13. void TestCSE()
  14. {
  15.  
  16.     CSE::MapSEToCE();
  17.     try
  18.     {
  19.         //DivideByZero();
  20.         ThrowException();
  21.     }
  22.     catch (CSE se)
  23.     {
  24.         switch(se)
  25.         {
  26.             case EXCEPTION_INT_DIVIDE_BY_ZERO:
  27.                 printf("Divide by zero!\n");
  28.                 break;
  29.         }
  30.     }
  31.     catch (std::exception & e)
  32.     {
  33.         printf("std::exception: %s\n", e.what());
  34.     }
  35. }

3. Компилировать нужно с опцией /EHa. В противном случае _set_se_translator не сработает и исключения SEH с помощью catch (...) отлавливаться не будут, а компилятор (Visual Studio 2010) выдаст следующее предупреждение:
warning C4535: calling _set_se_translator() requires /EHa


SEH способен отлавливать все исключения и, что важно на мой взгляд, показывать место возникновения ошибки (ExceptionAddress), а также позволяет продолжить выполнение программы с прерванного места. Но, к сожалению, при возникновении исключения C++ в обработчике (фильтре) SEH документированными способами нельзя узнать тип исключения C++ и получить его текст ошибки. Ниже описан недокументированный способ.

Все исключения C++ в фильтре SEH имеют один и тот же код 0xe06d7363. При этом поле NumberParameters содержит 3 (для 32-разрядных приложений), ExceptionInformation[1] указывает на объект-исключение C++, а ExceptionInformation[2] указывает на структуру _ThrowInfo (ExceptionInformation[0] не интересно). _ThrowInfo относится к так называемым Predefined C++ Types. Для использования предопределенных типов не нужно подключать никаких .h файлов. При редактировании IDE Visual Studio подчеркивает их красным цветом и пишет: Error: identifier "_ThrowInfo" is undefined. Но при этом все успешно компилируется и выполняется :-)

Ниже приведен SEH-фильтр, который способен анализировать исключения C++ и выводить информацию об исключении (what()). Стоит отметить, однако, что хотя эта процедура успешно работает в Visual Studio 2010, но нет никаких гарантий, что в будущем Microsoft не поменяет структуру _ThrowInfo.

  1. #include<eh.h>
  2. #include<stdexcept>
  3.  
  4. LONG ExceptionFilter(PEXCEPTION_POINTERS pEP, constchar * file, constchar * function, int line)
  5. {
  6.     PEXCEPTION_RECORD pER = pEP->ExceptionRecord;
  7.     if (pER->ExceptionCode == 0xe06d7363)
  8.     {
  9.         if (pER->NumberParameters == 3)
  10.         {
  11.             const _ThrowInfo * pThrowInfo = (_ThrowInfo*)pER->ExceptionInformation[2];
  12.             if (pThrowInfo != NULL)
  13.             {
  14.                 const _CatchableTypeArray * pTypeArray = pThrowInfo->pCatchableTypeArray;
  15.                 if (pTypeArray != NULL)
  16.                 {
  17.                     const _TypeDescriptor * pExceptionDesc = (_TypeDescriptor*)&typeid(std::exception);  
  18.                     for (int i = 0; i < pTypeArray->nCatchableTypes; i++)
  19.                     {
  20.                         const _CatchableType * pCatchableType = pTypeArray->arrayOfCatchableTypes[i];
  21.                         if (pCatchableType != NULL && pExceptionDesc == pCatchableType->pType)
  22.                         {
  23.                             std::exception * e = (std::exception*)pER->ExceptionInformation[1];
  24.                             printf("File: %s\nFunction: %s\nLine: %i\nC++ Exception: %s\n", file, function, line, e->what());
  25.                             return EXCEPTION_EXECUTE_HANDLER;
  26.                         }
  27.                     }
  28.                 }
  29.             }
  30.         }
  31.         printf("File: %s\nFunction: %s\nLine: %i\nC++ Exception (code 0x%x at 0x%x)\n", file, function, line, pER->ExceptionCode, pER->ExceptionAddress);
  32.         return EXCEPTION_EXECUTE_HANDLER;
  33.     }
  34.     else
  35.     {
  36.         printf("File: %s\nFunction: %s\nLine: %i\nSEH Exception (code 0x%x at 0x%x)\n", file, function, line, pER->ExceptionCode, pER->ExceptionAddress);
  37.         return EXCEPTION_EXECUTE_HANDLER;
  38.     }
  39. }
  40.  
  41. int main(int argc, char* argv[])
  42. {
  43.     __try
  44.     {
  45.         //DivideByZero();
  46.         ThrowException();
  47.     }
  48.     __except(ExceptionFilter(GetExceptionInformation(), __FILE__, __FUNCTION__, __LINE__))
  49.     {
  50.         //handle exception here
  51.     }
  52.  
  53.     return 0;
  54. }

В заключении хочу отметить, что ExceptionAddress для исключений C++ не имеет особого смысла, потому что указывает всегда на один и тот же адрес внутри функции __CxxThrowException(), которая выполняется каждый раз, когда вызывается исключение с помощью throw.

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Сборка boost в Visual Studio 2010

$
0
0
1. Скачиваем boost с официального сайта http://www.boost.org/. Текущая версия на момент написания поста: 1.53.0.

2. Распаковываем архив на диск (я распаковал в c:\boost\boost_1_53_0).

3. Запускаем Visual Studio Command Prompt (2010).

4. Переходим в каталог boost и собираем bjam запустив bootstrap.bat.

5. Собираем boost командой: bjam toolset=msvc-10.0 variant=debug,release threading=multi link=static runtime-link=static


6. Пьем кофе... Ждем завершения...


7. Выполняем шаги 5,6 еще разок с другой командой: bjam toolset=msvc-10.0 variant=debug,release threading=multi link=static

8. Прописываем в Visual Studio пути к бусту. Для этого открываем проект C++ (либо создаем новый), открываем вкладку Property Manager, выбираем Microsoft.Cpp.Win32.user в Debug | Win32 и Release | Win32 и в Property Pages->VC++ Directories добавляем в Include Directories C:\boost\boost_1_53_0, а в Library Directories C:\boost\boost_1_53_0\stage\lib


===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

PerlVCBuildScripts

$
0
0
Выложил на github perl-скрипты, которые я использую для автоматического изменения номера билда в проектах на Visual C++, а также генерации файлов xxx-build.txt с информацией о версии и контрольной суммой (MD5). Взять можно тут: https://github.com/coolsoftware/PerlVCBuildScripts

Использовать просто:

1) Нужно создать файл с информацией о билде (build-файл) в каталоге Release (где будет создан exe или dll). Например, мой проект называется HTTPGet, имя исполняемого (генерируемого) файла HTTPGet.exe, значит имя build-файла должно быть HTTPGet-build.txt. Вот как выглядит структура каталогов проекта:

HTTPGet \
  HTTPGet.sln
  HTTPGet.aspr
  Release \
    HTTPGet-build.txt
    HTTPGet.exe

Содержимое build-файла должно быть таким:

1.0.0.0
http://127.0.0.1/HTTPGet.exe
HTTPGet.exe
b9f2c07999dcafe9fe544a00521829c1

Первая строка - версия приложения (будет автоматически обновляться при сборке).
Вторая строка - URL для скачки последней версии.
Третья строка - имя файла.
Четвертая строка - MD5-подпись (будет автоматически обновляться при сборке).

Я использую build-файлы в функции авто-обновления в приложениях. Реализацию этой функции я выложу как-нибудь позже.

2) Нужно прописать в Pre-Build Event->Command Line вызов:

perl $(ProjectDir)..\incbuild.pl $(ProjectDir) $(Configuration)


а в Post-Build Event->Command Line прописать:

perl $(ProjectDir)..\makebuildinfo.pl $(TargetPath)


Если вы используете ASProtect для упаковки/защиты приложения, то можно прописать путь к вашему ASPR-проекту, чтобы ASProtect был вызван автоматически при сборке:

perl $(ProjectDir)..\makebuildinfo.pl $(TargetPath) $(ProjectDir)..\$(TargetName).aspr

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

MySQL не видит my.ini

$
0
0
Понадобилось мне поднять max_connections в MySQL, который по-умолчанию был установлен инсталлером в C:\Program Files\MySQL\MySQL Server 5.6.
В этом каталоге лежит my-default.ini, который я переименовал в my.ini и прописал в нем max_connections=250. Перезапустил сервис MySQL - не работает! select @@max_connections возвращает 100. Не видит MySQL моего my.ini.
Полчаса потратил на поиски откуда же MySQL читает настройки. Поэтому и решил написать это сообщение здесь - вдруг кому-нибудь поможет и сэкономит время.


Оказывается сервис запускается с параметром --defaults-file="C:\ProgramData\MySQL\MySQL Server 5.6\my.ini". Вот, значит, где находится my.ini.

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Failure during conversion to COFF: file invalid or corrupt

$
0
0
Вылезла сегодня с утра ошибка при компиляции любого проекта в  Visual Studio 2010:

LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt

Установка в свойствах проекта Linker->Enable Incremental Linking значения NO (/INCREMENTAL:NO) на помогла.

Полез смотреть, что же поменялось за последнее время. Оказалось вечером с авто-обновлениями винды поставился .NET Framework 4.5.1. Он то и нагадил!

1. Снес .NET Framework 4.5.1. Visual Studio перестал запускаться.
2. Установил .NET 4.0. Visual Studio починился, все проекты стали компилироваться нормально.
3. Слетел Mysql .Net Connector. Переустановил. Причем, Repair не помог, сделал сперва Remove, потом Install.

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

LockLib

$
0
0
LockLibэто набор классов для организации доступа к разделяемым ресурсам в программе на C++ под Windows.
Исходники доступны на GitHub: https://github.com/coolsoftware/LockLib.

class VLock


Класс VLock используется как альтернатива CRITICAL_SECTION (на самом деле это "обертка" над CRITICAL_SECTION).

void Lock(int lPosition, volatile LONG * lpThreadLock = NULL)


Блокировка ресурса для монопольного использования. Если ресурс уже кем-то заблокирован, то происходит ожидание когда ресурс снова станет свободен и его удастся заблокировать.

Параметр lPosition служит для идентификации места вызова метода Lock и может использоваться при отладке.

Необязательный параметр lpThreadLock служит для подсчета вызовов метода Lock в текущем потоке. Подробности смотрите ниже в разделе посвященном lpThreadLock.

void Unlock(volatile LONG * lpThreadLock = NULL)


Снятие блокировки.

static void OutputDebugLocks()


При отладке и оптимизации приложений иногда нужно видеть список всех существующих блокировок и статистику по ним: сколько в данный момент активных блокировок, в каком месте они заблокированы. Посмотреть такую статистику можно вызвав OutputDebugLocks. Эта статистика доступна в Debug-версии приложении, когда объявлен _DEBUG, или когда объявлен макрос DEBUG_LOCK.

volatile LONG * lpThreadLock


Проблема с блокировками в много-поточном приложении связана с тем, что поток, который установил блокировку, может быть остановлен с помощью TerminateThread. В этом случае Unlock не будет вызван никогда и блокировка ресурса "повиснет", а другие потоки, которые попытаются заблокировать этот ресурс, также "повиснут".
Чтобы разрулить эту ситуацию, необходимо вести подсчет блокировок для каждого потока и вызывать Unlock после TerminateThread.

  1. VLock lock;
  2.  
  3. unsignedint__stdcall LockThreadProc(void * lpParam)
  4. {
  5.     lock.Lock(1, reinterpret_cast<volatile LONG *>(lpParam)); //lock resource
  6.  
  7.     //do something here
  8.  
  9.     lock.Unlock( reinterpret_cast<volatile LONG *>(lpParam)); //unlock resource
  10.  
  11.     //contuinue working
  12.  
  13.     return 0;
  14. }
  15.  
  16. void main(int argc, char* argv[])
  17. {
  18.     volatile LONG lThreadLock = 0; //initialize with zero
  19.     //create thread
  20.     HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, LockThreadProc, (void*)&lThreadLock, CREATE_SUSPENDED, NULL);
  21.     //start thread
  22.     ::ResumeThread(hThread);
  23.     //wait for 5 seconds
  24.     if(::WaitForSingleObject(hThread, 5000) == WAIT_TIMEOUT)
  25.     {
  26.         //terminate thread
  27.         ::TerminateThread(hThread, 0);
  28.         //release lock
  29.         lock.Unlock(&lThreadLock);
  30.     }
  31.     //close thread handle
  32.     ::CloseHandle(hThread);
  33. }

class VRWLock


Класс VRWLock позволяет реализовать стратегию блокировки ресурса "один писатель - много читателей".

VRWLock(LONG lMaxReaders = 65535, DWORD dwSpinCount = 1000, DWORD dwTimeout = 5)


Конструктор класса VRWLock имеет следующие параметры:

lMaxReaders - максимальное количество читателей, которые могут одновременно читать ресурс (не должно быть = 0!). Значение по-умолчанию 65535.

dwSpinCount - количество неудачных попыток блокировки ресурса (занятого), после которых происходит переключение контекстас небольшой задержкой, определяемой параметром dwTimeout(в миллисекундах).

void LockRead(int lPosition, volatile LONG * lpThreadLock = NULL)


Блокировка ресурса читателем. Если ресурс занят писателем или превышено максимальное количество читателей, то происходит ожидание освобождения ресурса, когда удастся его заблокировать. Смотрите описание параметров в описании метода Lock класса VLock.

void LockWrite(int lPosition, volatile LONG * lpThreadLock = NULL)


Блокировка ресурса писателем. Если ресурс занят писателем или одним или несколькими читателями, то происходит ожидание освобождения ресурса, когда удастся его заблокировать. Смотрите описание параметров в описании метода Lock класса VLock.

void ReLockWrite(int lPosition, volatile LONG * lpThreadLock = NULL)


Изменение статуса блокировки с читателя на писателя. Если ресурс не занят ни читателем ни писателем, то действие функции аналогично LockWrite (блокировка писателем). Если ресурс занят одним читателем, то происходит переключение его статуса с читателя на писателя. Если ресурс занят писателем или более чем одним читателем, то происходит ожидание момента, когда ресурс будет не занят писателями и занят не более чем одним читателем. Смотрите описание параметров в описании метода Lock класса VLock.

void Unlock(volatile LONG * lpThreadLock = NULL)


Снятие блокировки.

static void OutputDebugLocks()


Вывод статистики блокировок (см. выше описание одноименного метода для класса VLock).

class VLockPtr, class VReadLockPtr, class VWriteLockPtr


Есть такая идиома: RAII - захват ресурса есть инициализация. Суть ее в следующем: создается класс-обертка такой, что в конструкторе класса вызывается соответствующая функция блокировки ресурса, а в деструкторе блокировка снимается. Это удобно по двум причинам:
  1. Нет необходимости делать явный вызов Unlock (а это часто, как показывает практика, забывают сделать).
  2. В случае возбуждения исключения между вызовами Lock и Unlock ресурс может оказаться занят "навсегда". А при использования RAII, деструктор класса-обертки, а следовательно и Unlock, будет вызван и в случае исключительной ситуации.
VLockPtr - RAII класс-обертка над VLock.
VReadLockPtr - RAII класс-обертка над VRWLock::LockRead.
VWriteLockPtr - RAII класс-обертка над VRWLock::LockWrite.

  1. unsignedint__stdcall LockPtrThreadProc(void * lpParam)
  2. {
  3.     {
  4.         VLockPtr lockptr(&lock, 1, reinterpret_cast<volatile LONG *>(lpParam)); //lock resource
  5.  
  6.         //do something here
  7.  
  8.     } //unlock will be done here
  9.  
  10.     //contuinue working
  11.  
  12.     return 0;
  13. }

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

LockLib update

$
0
0
Обновился LockLib на GitHub: https://github.com/coolsoftware/LockLib.

1. Классы VLock, VRWLock, VLockPtr, VReadLockPtr, VWriteLockPtr теперь "uncopyable", то есть их нельзя скопировать (см. Листинг 1: Ошибка 1 и Ошибка 2). Запрет на копирование осуществляется путем наследования этих классов от VUncopyable. При компиляции кода, содержащего запрещенное копирование, будет выдано сообщение ошибке:

VLock.h(62): error C2248: 'VUncopyable::VUncopyable' : cannot access private member declared in class 'VUncopyable'

2. Конструктор класса VRWLock объявлен с ключевым словом explicit для того, чтобы исключить неявное создание экземпляра этого класса при вызове функции (см. Листинг 1: Ошибка 3). При компиляции кода, содержащего такое неявное создание VRWLock, будет выдано сообщение об ошибке:

TestLock.cpp(184): error C2664: 'RWFunc' : cannot convert parameter 1 from 'int' to 'const VRWLock &'
          Reason: cannot convert from 'int' to 'const VRWLock'
          Constructor for class 'VRWLock' is declared 'explicit'

Листинг. 1.

  1. void RWFunc(const VRWLock&)
  2. {
  3.     //do something here
  4.     //...
  5.     
  6. VLock lock1;
  7. VLock lock2 = lock1; //Ошибка 1
  8. VRWLock rwlock1;
  9. VRWLock rwlock2 = rwlock1; //Ошибка 2
  10. RWFunc(1); //Ошибка 3
  11.      

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Дерево Интервалов (Отрезков)

$
0
0
Я уже несколько раз сталкивался с необходимостью решать следующую задачу: есть список интервалов и нужно найти один или все интервалы, в которые входит заданное значение. Пример: есть список диапазонов IP-адресов, каждому диапазону присвоен двух-буквенный код страны. Требуется для заданного IP-адреса определить страну.

Когда интервалов не много, то можно обойтись полным перебором (brute-force). Но когда их несколько десятков тысяч, а искать приходится часто, то нужна оптимизация.

Подходящая структура для быстрого поиска в списке интервалов называется "Дерево Интервалов" (Interval Tree) или "Дерево Отрезков". Хорошее описание (англ.), а также пример реализации я нашел тут: http://www.drdobbs.com/cpp/interval-trees/184401179.

Теория


Коротко описание структуры дерева отрезков и алгоритма поиска приведу на примере: пусть есть список именованных отрезков:

a=[75, 76], b=[75, 79], c=[75, 84],
d=[76, 80], e=[79, 80], f=[79, 83],
g=[79, 91], h=[80, 84], i=[80, 90],
j=[83, 84], k=[84, 85], l=[91, 92],
m=[91, 92]

Выпишем координаты вершин отрезков, отсортированные по-возрастанию:

X = {75, 76, 79, 80, 83, 84, 85, 90, 91, 92}

Массив X содержит 10 элементов: X[0], X[1], ..., X[9].
Возьмем среднее значение (медиану): 83 = X[m], m = (9-0) div 2.

Возможны три варианта расположения отрезков относительно медианы:
1. отрезки, которые содержат медиану.
2. отрезки, которые лежат слева от медианы.
3. отрезки, которые лежат справа от медианы.

Создадим узел дерева, который будет включать в себя значение медианы (дискриминант = 83) и список всех отрезков, которые содержат медиану:

R = {83, [c, f, g, h, i, j]}

Оставшиеся отрезки слева от R: {a, b, d, e}и справа от R: {k, l, m}.

Построим левый узел дерева для отрезков {a, b, d, e}, находящихся левее медианы, рассматривая вершины слева от медианы: {75, 76, 79, 80}. Получим: O = {76, [a, b, d]}.
Аналогично правый узел для отрезков {k, l, m}и вершин {84, 85, 90, 91, 92}: U = {90, []}. Обратите внимание на возможность существования узлов, не содержащих отрезков.
Продолжая процесс рекурсивно, получим следующие узлы:
N = {75, []}, P = {79, [e]}, S = {84, [k]}, V = {91, [l, m]}, Q = {80, []}, T = {85, []}, W = {92, []}.

      R
   /     \
  O       U
 / \     / \
N   P   S   V
     \   \   \
      Q   T   W

Висячие вершины N, Q, T и W можно убрать. Окончательно получим дерево:

     R
   /   \
  O     U
   \   / \
    P S   V

Алгоритм поиска выглядит следующим образом. Начинаем с вершины дерева. Сравниваем дискриминант текущего узла с искомым значением q. Если они равны (случай 1), то выводим все отрезки, на которые указывает текущий узел, и завершаем процесс. Если дискриминант текущего узла больше, чем q (случай 2), то перебираем все отрезки, на которые указывает текущий узел, и выводим такие, которые содержат q. Затем переходим к левому узлу. Если дискриминант текущего узла меньше, чем q (случай 3), то аналогично перебираем все отрезки текущего узла и выводим те, которые содержат q, а затем переходим к правому узлу.

В двух последних случаях (когда дискриминант узла не равен q) нужен перебор отрезков, на которые указывает узел. Этот перебор можно оптимизировать, если иметь два отсортированных массива отрезков: первый массив AL должен быть отсортирован по возрастанию левой (меньшей) координаты отрезка, а второй массив DH должен быть отсортирован по убыванию правой (большей) координаты отрезка. Для узла R из нашего примера:

AL = {c, f, g, h, i, j}
DH = {g, i, j, h, c, f}

Тогда, если дискриминант узла больше q (случай 2), то перебираем отрезки из массива AL до тех пор, пока левая координата отрезка  не будет больше q. Если дискриминант узла меньше q (случай 2), то перебираем отрезки из массива DH до тех пор, пока правая координата отрезка не будет меньше q.

Практика


Реализацию дерева интервалов можно взять на github: https://github.com/coolsoftware/ITree.
Класс itree в itree.h.

За основу я взял реализацию Yogi Dandass, в которую добавил некоторые недостающие (ommited) определения, исправил пару багов и существенно оптимизировал построение дерева (метод construct_tree).

Баг 1в методе itree::construct_tree. Код ниже вызывал выход за границы массива.

  1. std::sort(&(al[list_start]),
  2.     &(al[list_start + list_size]), comp_for_al);
  3. std::sort(&(dh[list_start]),
  4.     &(dh[list_start + list_size]), comp_for_dh);

Он заменен на следующее:

  1. std::sort(al.begin()+list_start,
  2.   al.begin()+list_start+list_size,
  3.   comp_for_al);
  4. std::sort(dh.begin()+list_start,
  5.   dh.begin()+list_start+list_size,
  6.   comp_for_dh);

Баг 2в методе query_iterator::init_node. Если value == cur_node->discrim, то требуется проверка, что cur_node->size != 0, а иначе возможен выход за границы массива.

  1. void init_node(void)
  2. {
  3.     index = 0;        
  4.     while (cur_node != NULL)
  5.     {
  6.         if (value < cur_node->discrim)
  7.         {
  8.             if ((cur_node->size != 0) &&
  9.                 ((*p_al)[cur_node->start]->low() <= value))
  10.                 return;
  11.             else
  12.                 cur_node = cur_node->left;
  13.         }
  14.         elseif (value > cur_node->discrim)
  15.         {
  16.             if ((cur_node->size != 0) &&
  17.                 ((*p_dh)[cur_node->start]->high() >= value))
  18.                 return;
  19.             else
  20.                 cur_node = cur_node->right;
  21.         }
  22.         else//(value == cur_node->discrim)
  23.         {
  24.             if (cur_node->size == 0) //VIT 2014 bugfix!
  25.             {
  26.                 cur_node = NULL;
  27.             }
  28.             return;
  29.         }
  30.     }
  31. }

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Вызов скриптов Perl из программы на C++

$
0
0
Оказалось, что организовать вызов Perl-скриптов из C/C++ (MS Visual C++ 2010) достаточно просто:
  1. Прописываем в Include Directoriesи Library Directoriesпроекта путь к Perl\CORE:


  2. Добавляем perl512.lib в Linker->Input->Additional Dependencies.


  3. Ниже приведен пример кода, вызывающего perl из консольного приложения C++. Обращу внимание на два момента: a) #pragma warning (disable:4005)для подавления сообщения компилятора "'ENOTSOCK' : macro redefinition"; b) если опустить вызов PERL_SYS_INIT(0, NULL), то на шаге perl_parse получим Access Violation.

    1. #include"stdafx.h"
    2. //avoid warning C4005: 'ENOTSOCK' : macro redefinition
    3. #ifdef _MSC_VER
    4. #pragmawarning ( disable : 4005 )
    5. #endif
    6. #include<perl.h>
    7.  
    8. PerlInterpreter *my_perl;
    9.  
    10. int _tmain(int argc, _TCHAR* argv[])
    11. {
    12.     PERL_SYS_INIT(0, NULL);
    13.  
    14.     my_perl = perl_alloc();
    15.     perl_construct(my_perl);
    16.  
    17.     perl_parse(my_perl, NULL, argc, argv, NULL);
    18.     perl_run(my_perl);
    19.  
    20.     perl_destruct(my_perl);
    21.     perl_free(my_perl);
    22.  
    23.     return 0;
    24. }

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Генерация пар открытых/закрытых ключей (RSA) на C#

$
0
0
Понадобилось мне создать пару открытый/закрытый ключ на C#. Я поискал немного и нашел замечательную криптографическую библиотеку под названием Bouncy Castle: https://www.bouncycastle.org/. Для C# исходники можно скачать здесь: http://www.bouncycastle.org/csharp/. Ниже описан порядок установки и использования.
  1. Качаем bccrypto-net-1.7-src.zip, распаковываем в какой-нибудь каталог на диске.
  2. Открываем проект csharp.sln в Visual Studio 2010.
  3. В свойствах проекта crypto в Build->General->Conditional compilation symbols комментируем INCLUDE_IDEA.


  4. Пробуем выполнить Build и видим ошибки:
    error CS1504: Source file 'C:\temp\csharp\crypto\src\crypto\engines\IDEAEngine.cs' could not be opened ('Неопознанная ошибка ')
    error CS1504: Source file 'C:\temp\csharp\crypto\test\src\crypto\test\IDEATest.cs' could not be opened ('Неопознанная ошибка ')
    error CS1504: Source file 'C:\temp\csharp\crypto\src\asn1\misc\IDEACBCPar.cs' could not be opened ('Неопознанная ошибка ')
  5. Удаляем из проекта crypto отсутствующие файлы:
    src\crypto\engines\IDEAEngine.cs
    src\asn1\misc\IDEACBCPar.cs
    test\src\crypto\test\IDEATest.cs
  6. Пробуем еще раз выполнить Build - на этот раз все должно получиться и будет создана библиотека crypto.dll.
  7. Прописываем созданную библиотеку в References проекта.


  8. Код для генерации пары открытый/закрытый ключ приведен ниже. Открытый ключ - в формате PKCS#8. Закрытый ключ - в формате PKCS#1.

    1. using Org.BouncyCastle.Asn1.Pkcs;
    2. using Org.BouncyCastle.Asn1.X509;
    3. using Org.BouncyCastle.Crypto;
    4. using Org.BouncyCastle.Crypto.Parameters;
    5. using Org.BouncyCastle.Pkcs;
    6. using Org.BouncyCastle.Security;
    7. using Org.BouncyCastle.X509;
    8. using Org.BouncyCastle.Crypto.Generators;
    9.  
    10. RsaKeyPairGenerator rsa = new RsaKeyPairGenerator();
    11. rsa.Init(new KeyGenerationParameters(new Org.BouncyCastle.Security.SecureRandom(), 1024));
    12. AsymmetricCipherKeyPair pair = rsa.GenerateKeyPair();
    13. PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(pair.Private);
    14. byte[] serializedPrivateBytes = privateKeyInfo.PrivateKey.ToAsn1Object().GetDerEncoded();
    15. string privateKey = Convert.ToBase64String(serializedPrivateBytes);
    16. SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pair.Public);
    17. byte[] serializedPublicBytes = publicKeyInfo.ToAsn1Object().GetDerEncoded();
    18. string publicKey = Convert.ToBase64String(serializedPublicBytes);

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Сборка zlib в Visual Studio 2010

$
0
0

1. Скачиваем zlib 1.2.8 с http://www.zlib.net/
2. Распаковываем архив в c:\zlib-1.2.8
3. Открываем в Visual Studio 2010 проект C:\zlib-1.2.8\contrib\vstudio\vc10\zlibvc.sln
4. Меняем следующие настройки проекта zlibstat:

  Конфигурация Debug:

    General->Output Directory: ..\..\..\win32-MDd\
    General->Intermediate Directory: ..\..\..\win32-MDd\Tmp\
    C/C++->Preprocessor->Preprocessor Definitions: <удаляем ZLIB_WINAPI>
    C/C++->Code Generation->Runtime Library: Multi-threaded Debug DLL (/MDd)
    Librarian->General->Output File: $(OutDir)\..\zlibMDd.lib

  Конфигурация Release:

    General->Output Directory: ..\..\..\win32-MT\
    General->Intermediate Directory: ..\..\..\win32-MT\Tmp\
    C/C++->Preprocessor->Preprocessor Definitions: <удаляем ZLIB_WINAPI>
    C/C++->Code Generation->Runtime Library: Multi-threaded (/MT)
    Librarian->General->Output File: $(OutDir)\..\zlibMT.lib
   
5. Собираем Debug и Release версии zlibstat. На выходе получаем:

  C:\zlib-1.2.8\zlibMDd.lib
  C:\zlib-1.2.8\zlibMT.lib

6. Открываем в Visual Studio 2010 любой проект C++ и открываем Property Manager->(Project Name)->Debug | Win32->Microsoft.Cpp.Win32.user->Common Properties->VC++ Directories. Добавляем c:\zlib-1.2.8 в Include Directories, Library Direcories и Source Directories.


===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Throttling

$
0
0

Троттлинг (throttling) - это регулирование (ограничение) скорости какого-нибудь процесса. Например, bandwidth throttling - регулирование пропускной способности канала (обычно измеряется в килобайтах в секунду, kB/s).

В листинге ниже показано, как можно реализовать троттлинг.

  1. for (int i = 0; i < 1000; i++)
  2. {
  3.     while (!throttle_acquire()); //цикл ожидания
  4.     doWork(); //выпоняем работу
  5. }

В этом примере процесс состоит из 1000 итераций. Каждая итерация заключается в вызове функции doWork(), которая, например, отправляет очередную порцию данных. Ограничение скорости заключается в введении лимита на количество этих вызовов N за период времени dT. Перед вызовом doWork() осуществляется проверка превышения лимита: функция throttle_acquire() возвращает false, если лимит превышен, и true, в противном случае.

Троттлинг можно реализовать с использованием кольцевого буфера. Этот кольцевой буфер заполняется моментами времени последних вызовов функции doWork() (см пример выше). Размер буфера равен максимально разрешенному количеству этих вызовов N за период времени dT. Если буфер полностью заполнен, то функция acquire() возвращает false. Это означает, что необходимо подождать, пока из буфера не будет удален хотя бы один момент времени, который располагается от текущего момента ("сейчас") дальше, чем dT.

Реализацию троттлинга на C++ (throttle.h)  я выложил на github: https://github.com/coolsoftware/Throttle.

Пример использования класса throttleможно посмотреть в TestThrottle.cpp.

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Raspberry Pi

$
0
0
Полезные ссылки, касающиеся разработки под Raspberry Pi. Пишу главным образом для себя, чтобы не забыть :) Пост будет время от времени дополнятся (я надеюсь :)

1. Кросс-компиляция Qt 5 для Raspberry Pi:

http://qt-project.org/wiki/RaspberryPi_Beginners_guide

Несколько замечаний:

a) git clone git://gitorious.org/qt/qt5.git не работает, вместо него нужно выполнять: git clone http://git.gitorious.org/qt/qt5.git

б) сперва лучше (imho) залить образ на флешку ( sudo dd bs=1M if=2015-02-16-raspbian-wheezy.img of=/dev/sdb ), вставить флешку в Raspberry Pi, установить апдейты и библиотеки (например, для разработки под X11: http://doc.qt.io/qt-5/linux-requirements.html), потом сделать образ этой флешки ( sudo dd bs=1M if=/dev/sdb of=rasp-pi.img ) и уже с ним работать дальше.

в) "Магическое" число 62914560 (
sudo mount -o loop,offset=62914560 rasp-pi.img/mnt/rasp-pi-rootfs ) можно вычислить следующим образом:

[vitaly@localhost opt]$ sudo fdisk -l rasp-pi.img

Disk rasp-pi.img: 7892 MB, 7892631552 bytes, 15415296 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x0009bf4f

Устр-во Загр     Начало       Конец       Блоки   Id  Система
rasp-pi.img1            8192      122879       57344    c  W95 FAT32 (LBA)
rasp-pi.img2          122880     6399999     3138560   83  Linux

122880 - это начало раздела (номер первого сектора раздела),  который нужно подмонтировать. 122880 * 512 = 62914560.

2."Нативная" компиляция Qt5 на Raspberry Pi.

http://qt-project.org/wiki/Native_Build_of_Qt5_on_a_Raspberry_Pi

Я не пробовал. Утверждается, что идет очень долго.

3. Кросс-компиляция Wtна Raspberry Pi:

http://redmine.emweb.be/projects/wt/wiki/Cross_compile_Wt_on_Raspberry_Pi

4. "Экономичная реализация графического интерфейса пользователя на базе одноплатного компьютера Raspberry Pi" ("Автоматика и программная инженерия", 2014, №2(8))

http://www.nips.ru/images/stories/zhournal-AIPI/10/aipi-2-2014-3.pdf

Хорошее подробное описание настройки кросс-компиляции.

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Teredo ipv6

$
0
0
Настройка ipv6 (Teredo):
  1. Мой компьютер (контекстное меню) –> Управление -> Службы (Рис. 1):
        Вспомогательная служба IP –> Тип запуска –> Выбираем из списка: Автоматически
  2. Пуск –> Выполнить –> regedit
  3. В реестре по адресу
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Dnscache\Parameters
    создать ключ AddrConfigControl типа DWORD со значением 0 (Рис. 2).
  4. Пуск –> Панель управления –> Сеть и Интернет –> Сетевые подключения –> Подключение по локальной сети –> Свойства (Рис. 3):
        Протокол Интернета версии 6 (TCP/IPv6) –> включить и в Свойствах указать:
        Использовать следующие адреса DNS-серверов:
        Предпочитаемый DNS-сервер: 2001:4860:4860::8888
        Альтернативный DNS-сервер: 2001:4860:4860::8844
  5. Пуск –> Выполнить –> gpedit.msc
  6. Конфигурация компьютера –> Административные шаблоны –> Сеть –> Параметры TCP/IP –> Технологии Туннелирования IPv6 (Рис. 4):
        Классификация Teredo по умолчанию –> Включить –> Включенное состояние
        Частота обновления Teredo –> Включить –> 10
        Состояние Teredo –> Включить –> Корпоративный клиент
        Порт клиента Teredo -> Не задано
        Имя сервера Teredo –> Включить –> Выбираем из списка: teredo.remlab.net
  7. Пуск – Выполнить – cmd:
        netsh int ipv6 delete route ::/0 Teredo
        netsh int ipv6 add route ::/0 Teredo
  8. Тестирование подключения: http://test-ipv6.com/
Рис. 1

Рис. 2

Рис.3

Рис. 4

Стянул отсюда: http://blog.cherepovets.ru/serovds/2011/11/15/teredo-win7/

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Обновить иконки рабочего стола (Windows)

$
0
0
Заметка себе на память:

ie4uinit.exe -ClearIconCache



===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

ASUS N10 Nano vs TP-LINK TL-WN823N

$
0
0
Решил сравнить скорость двух беспроводных сетевых USB-адаптеров: ASUS N10 Nano и TP-LINK TL-WN823N. На первом написно до 150 Mbps, на втором - до 300 Mbps.

Оба адаптера втыкались в Raspberry Pi B+. Для тестов использовался iperf.

Результаты тестов представлены на Рис. 1,2. Как видим, ASUS показывает скорость около 42 Mbit/sec, TP-LINK - около 74 Mbit/sec.

Рис.1. ASUS N10 Nano.


Рис.2. TP-LINK TL-WN823N.




===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Lazarus Exe

$
0
0
Чтобы уменьшить размер генерируемого Lazarus exe файла, нужно включить следующие опции в параметрах компилятора:


  1. "Компиляция и компоновка"->"Стиль модулей"->"Умная компоновка (-CX)"
  2. "Компиляция и компоновка"->"Компоновка"->"Умная компоновка (-XX)"
  3. "Отладка"->"Информация для GDB"->"Использовать внешний файл отладочных символов GDB (-Xg)"
  4. "Отладка"->"Прочая отладочная информация"->"Вырезать символы из исполнимого файла (-Xs)"






===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Поиск подстрок с помощью дерева цифрового поиска

$
0
0
Теория

Задача: имеется два больших (100 000+) списка строк, и требуется отфильтровать первый список (Source List) таким образом, чтобы в нем остались только строки, содержащие подстроки из второго списка (Search List).

Решать эту задачу будем следующим образом: для каждой строки Z из Source List будем искать строку S из Search List, такую, что S является подстрокой Z.

Примитивный алгоритм

Можно решить задачу нахождения подстроки из списка Search List при помощи следующего очевидного (примитивного), но крайне медленного алгоритма: будем последовательно перебирать все строки S из Search List до тех пор, пока не окажется, что S является подстрокой Z. При этом, алгоритм поиска подстроки в строке можно использовать либо примитивный, либо один из более продвинутых (например, Бойера-Мура, см. https://ru.wikipedia.org/wiki/Поиск_подстроки). Однако, использование более быстрого алгоритма поиска подстроки в строке не сильно ускоряет поиск подстроки из списка, т.к. все-равно требуется перебрать в среднем N/2 строк (здесь N - количество строк в Search List).

Дерево цифрового поиска

Дерево цифрового поиска (также бор, Trie, Префиксное дерево) - структура данных, которая используется для осуществления поразрядного поиска. Каждый узел дерева содержит значение одного "разряда" (в нашем случае - строковый символ). Вся строка представлена последовательностью узлов от корня до листа дерева. Пример - дерево цифрового поиска для строк A, ABA, ABB, ABBA, ABC, BAC, BC:

    [ROOT NODE]
    /         \
   A           B
  / \         / \
{0}  B       A   C
   / | \    /
  A  B  C  C
    / \
  {0}  A

Почитать про эту структуру можно, например, тут: https://en.wikipedia.org/wiki/Trie.

Алгоритм A1 поиска префикса строки S по дереву:

1. Пусть L = длина строки S.
2. Если L = 0 ("пустая" строка), то выход: префикс не найден.
3. Если дерево не содержит узлов ("пустое" дерево), то выход: префикс не найден.
4. Терминальный узел T = NULL.
5. Текущий узел P = корень дерева ( [ROOT NODE] ).
6. Цикл по K = 1..L:
7.   Если у текущего узла нет дочернего, соответствующего символу S[K], то выход из цикла.
8.   Текущий узел P = дочерний, узел, соответствующий символу S[K].
9.   Если у узла P есть дочерний узел с терминальным символом {0}, то присваиваем его T.
10. Конец цикла.
11. Если узел P - лист, то выход: префикс найден.
12. Если T не равно NULL, то выход: префикс найден.
13. Префикс не найден.

В качестве терминального символа {0} как правило используется символ NUL с ASCII-кодом 0, который не может встретиться в строке.

Алгоритм A2 поиска подстроки строки S по дереву.

1. Пусть L = длина строки S.
2. Если L = 0 ("пустая" строка), то выход: подстрока не найдена.
3. Если дерево не содержит узлов ("пустое" дерево), то выход: подстрока не найдена.
4. Цикл по K = 1..L:
5.  Пусть Sk - суффиксстроки S, начиная с K-го символа (суффикс длины L-K+1).
6.  Используем алгоритм A1 для поиска префикса строки Sk по дереву; если префикс найден, то выход: подстрока найдена.
7. Конец цикла.
8. Подстрока не найдена.

В алгоритмах A1 и A2 использованы определения префиксаи суффикса строки из статьи: https://ru.wikipedia.org/wiki/Подстрока. По-простому: префикс строки длины X - это первые X символов строки, суффикс строки длины Y - последние Y символов строки.

Построение дерева цифрового поиска

Обозначения:

N - количество строк.
Si - i-я строка, i = 1..N.
Li - длина i-ой строки.
[ROOT NODE] - корневой узел.

Построение дерева цифрового поиска:

1. Сортировка списка строк по возрастанию, удаление дубликатов.
2. Вызов рекурсивной процедуры R( [ROOT NODE], 1, 1, N).

Рекурсивная процедура R (параметры: P - узел-родитель, K - позиция текущего символа, I0, J0 - индексы строк; I0<=J0):

1. Индекс i = I0.
2. Если K > Li и i = J0, то выход из процедуры R.
3. Если K <= Li, то символ C = Si[K], иначе C = {0}.
4. Создать X = узел (C, P).
5. Индекс j = i.
6. Цикл:
7.      Если j = J0 или S(j+1)[K] не равно C, то:
8.        Вызвать R (X, K+1, i, j)
9.        Если j = J0, то выход из цикла.
10.      i = j+1.
10.      C = Si[K].
11.      Создать X = узел (C, P).
12.    Конец если.
13.    j = j + 1.
14. Конец цикла.

См. иллюстрацию на Python: http://blog.coolsoftware.ru/2016/03/blog-post_23.html.

Оптимизация (сжатие)

Под-деревья, узлы которых содержат не более одного потомка, можно объединить в один узел - см. узел (AC) в примере ниже.

    [ROOT NODE]
    /         \
   A           B
  / \         / \
{0}  B      (AC) C
   / | \
  A  B  C
    / \
  {0}  A


===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Построение дерева цифрового поиска

$
0
0
Ниже приведена иллюстрация построения дерева цифрового поиска на Python (см. алгоритм в посте http://blog.coolsoftware.ru/2016/03/blog-post.html):

f =open("f1.txt")
lines = f.read().split()
f.close()
 
def sort(array):
equal =[]
greater =[]
less =[]
iflen(array)<=1:
returnarray
linem =array[int((len(array)-1)/2)]
for line inarray:
if linem < line:
greater.append(line)
elif linem > line:
less.append(line)
elif linem == line:
equal.append(line)
return sort(less) + equal + sort(greater)
 
sorted_lines = sort(lines)
print(sorted_lines)
 
def node(C, P):
print(P + C)
return P + " "
 
def R(P, K, I0, J0):
I = I0
Si = sorted_lines[I]
Li =len(Si)
if K >= Li and I == J0:
return
if K < Li:
C = Si[K]
else:
C ="{0}"
X = node(C, P)
J = I
whileTrue:
if J == J0 or sorted_lines[J+1][K]!= C:
R(X, K+1, I, J)
if J == J0:
break
I = J + 1
Si = sorted_lines[I]
Li =len(Si)
C = Si[K]
X = node(C, P)
J = J + 1
 
N =len(sorted_lines)
X = node("[ROOT_NODE]","")
R(X,0,0, N-1)

f1.txt:

BC
ABBA
ABC
ABA
ABB
BAC
A

Вывод:

['A', 'ABA', 'ABB', 'ABBA', 'ABC', 'BAC', 'BC']
[ROOT_NODE]
A
{0}
B
A
B
{0}
A
C
B
A
C
C

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
Viewing all 78 articles
Browse latest View live