пятница, 2 августа 2013 г.

RacerPro + Apache Jena. Подводные камешки

Недавно пришлось писать клиент с использованием первых двух + использованием SparQL в качестве языка запросов.
Это довольно мутная задача, так как целью была работа именно со SparQL + RacerPro.  Jena оказалась при том, что базу знаний необходимо было таки грузить.
В качестве языка разработки клиента юзался Java, что довольно логично, учитывая наличие JRacer. 
Стоит, отметить что SparQL поддерживает только версия 2.0+ RacerPro, а она из статуса "Preview" еще давно не вышла. Поэтому при работе стоит учесть несколько фактов:
  1. Запросы придется выполнять в виде "(sparql-answer-query  "текст запроса")".  Все должно писаться в одну строку - иначе все будет ломаться.
  2. Версия 2.0 поддерживает префиксы (с запросами в Jena были проблемы)
  3. Применять UNION он не умеет
  4. Квантификаторы (к примеру "+") в запросах тоже
Имеет смысл учесть, что при изменении структуры базу знаний придется перезагружать. Из-за скупости и непонятности офф. документации как сделать красиво узнать не удалось. Если кому интересен конкретный код - вы можете его посмотреть на https://code.google.com/p/db2-ontology-coursework-2013/

четверг, 25 октября 2012 г.

MySQL C Connector + MinGW

Недавно столкнулся с сабжем и довольно сразу нашел решение - http://sourceforge.net/projects/windiana.u/files/mysql/port-mingw/ . Собирать, правда, лучше под MSYS. Но коннектор для C собирается легко и влет. Для C++ - думаю после плясок с бубном тоже возможно.
Родной, от Oracle, естественно не собирается вообще. А жаль.

пятница, 19 октября 2012 г.

Замыкания для бедных

Сегодня речь зайдет о довольно специфичной проблеме, а именно о замыканиях. На текущий момент замыкания включены в новый стандарт C++11, и, собственно, данная проблема, как проблема, исчерпана. Для чего можно применить замыкания? Да для кучи разных задач. В данной конкретной задаче, замыкание было необходимо для реализации произвольного сообщения, которое посылалось одним потоком другому и выполняло в целевом потоке произвольный код.
 На данный момент не все компиляторы на текущий момент хорошо поддерживают старые компиляторы. В данном случае необходимо было реализовать замыкания в довольно старой версии MSVC (2008) и MinGW с версией GCC 4.4.3.  В них был необходим  другой способ для  работы с замыканиями, который бы упростил их создание и объявление до следующих этапов: 
  • Объявление переменных, сохраняемых в контексте функции. Сохраняться будут значения а не ссылки. Для сохранения ссылок стоит использовать smartpointers
  • Объявление исполняемого кода замыкания
  • Инициализация контекста функции внешними переменными
  • Передача функции, как объекта во внешнюю функцию (к примеру, какую-нибудь функцию run)
Также довольно серьезным ограничением является то, что замыкание в данном случае не будет ничего возвращать (для возврата, в принципе, достаточно небольшой модификации решения.).
Довольно простым решением для данной задачи выглядит объявление анонимного класса, который содержит в себе необходимый контекст и метод для исполнения, при этом у данного класса создается объект, который инициализируется и передается во внешнюю функцию. У данного метода есть несколько легкорешаемых проблем:
  • Нельзя объявить несколько переменных внутри одной функции (решается взятием объявления и инициализации в блок)
  • Необходимо скопировать объект внутрь функции передачи (решается путем объявления функции копирования или созданием объекта на куче и другими способами, однако для этого у класса должно быть имя. Однако коррелирующие имена внутри блока кода в данных компиляторах не являются проблемой)
  • Громоздкий синтаксис. Можно решить при помощи макросов.
Дополнительной проблемой в GCC 4.4.3. станет ещё и сигнатура функции run. В обычном случае достаточно шаблонной функции. Однако в нашем случае, инстанцирование не сработает с анонимным классом. Поэтому приходится использовать динамический полиморфизм. Итоговое решение выглядит cледующим образом:


#include <stdio.h>

/** Базовый класс, который будет использован для создания на 
    его основе функций замыкания
 */
class ClosureBasic
{
 public:
    /** Метод класса, который будет описывать замыкания
     */
    virtual void run()=0;
    virtual ~ClosureBasic() {}
};
/** Макрос, с которого должно начинаться определение замыкания
 */
#define CLOSURE {                                \
                 class ____: public ClosureBasic \
                 {                               \
                  public:                        \
                   ClosureBasic  * ___()         \
                   {                             \
                    return new ____(*this);      \
                   }
/** Макрос для объявления переменных контекста. 
    Может быть опущен, однако удобен для сохранения общего стиля
  */
#define CLOSURE_DATA(X) X
/** Макрос для объявления кода замыкания (как общего метода)
 */
#define CLOSURE_CODE(X) virtual void run()  { X }
/** Макрос для инициализации замыкания общим контекстом. 
    Просто конструирует объект по умолчанию и выполняет
    код в нем.
 */
#define INITCLOSURE(X)          } ______; X;
/** Завершающий макрос для передачи замыкания на исполнение
    Может быть опущен, а вызов ______.___() 
    использован для передачи замыкания куда-либо
    Главное - закрывающая фигурная скобка обязательна
 */
#define SUBMITCLOSURE   run(______.___() ); } 
/** Макрос для установки значения переменной внутри замыкания
 */
#define CLSET(PROP,VAL) ______. PROP = VAL;


/** Тестовая функция, которая просто выполняет код
    замыкания
    \param[in] cl сам объект замыкания
 */
void run(ClosureBasic * cl)
{
    cl->run();
    // Данная строка удаляет объект и удалив её, 
    // мы получим утечку памяти
    delete cl;  
}

int main(int argc, char** argv)
{
 int a=55;
 // Простой пример замыкания, которое берет
 // переменную а и выводит её
 CLOSURE
 CLOSURE_DATA( int a;  )
 CLOSURE_CODE( printf("%d\n", a); 
               printf("%d\n", a); 
             )
 INITCLOSURE( CLSET(a,a); )
 SUBMITCLOSURE;
 
 
 return 0;
}    
  

воскресенье, 9 октября 2011 г.

Несложная обертка для Graphviz. Строим граф из файла из-под Windows.

Не так давно потребовалось визуализировать AST-дерево. В принципе, для визуализации у Graphviz есть библиотеки для языка C++, однако не хотелось их тащить ради такой мелкой цели. В результате родился следующий несложный код:

/*! Функция для создания графа из файла. Решение WIN32-only
    \param[in] firstname строка, использующаяся для формирования 
                         имени конечного файла
    \param[in] filename имя файла с графом
*/
void spawn_dot(const char * firstname,const std::string & filename)
{
 //Создадим структуру для выходных параметров создания процесса
 PROCESS_INFORMATION * pi=new PROCESS_INFORMATION();
 STARTUPINFOA * inf=new STARTUPINFOA();
 memset(pi,0,sizeof(PROCESS_INFORMATION));
 memset(inf,0,sizeof(STARTUPINFOA));
 //Заполним параметры запуска. Указываем то, что выходной поток дочернего
 //процесса будет перенаправлен
 inf->cb=sizeof(STARTUPINFOA);
 inf->dwFlags=STARTF_USESTDHANDLES;
 //Сформируем имя конечного файла
 std::string output_filename=firstname;
 output_filename+=".png";
 //Здесь происходит очень интересная вещь:
 //Если просто перенаправить поток выходного процесса в файл
 //то есть шанс, что он туда ничего не запишет, так как не будет очищен буффер
 //дескриптора выходного потока. Поэтому мы, сохраняем контроль над ним,
 //перенаправляя наш выходной поток в файл и передавая его дескриптор
 //в дочерний процесс.
 freopen(output_filename.c_str(),"wb",stdout);
 inf->hStdOutput=GetStdHandle(STD_OUTPUT_HANDLE);
 //Формируем строку вызова. Нам нужно, чтобы dot вызвался
 //По информации из переменной среды PATH, поэтому мы должны сформировать её полностью
 //Кроме того из документации следует, что указатель должен быть изменяемым.
 char * tmp=new char[271];
 strcpy(tmp,"dot.exe ");
 strcat(tmp,filename.c_str());
 strcat(tmp," -Tpng");
 //Создаем процесс
 BOOL result = CreateProcessA(NULL,tmp, NULL, NULL, TRUE,
                              0, NULL,NULL, inf, pi);
 //Если создать процесс не удалось
 if (result==FALSE)
 {
  //Восстанавливаем изначальный выходной поток и выводим ошибку
  freopen("CONOUT$","wb",stdout);
  printf("Failed to spawn dot %d\n",GetLastError());
 }
 else
 {
  //Дожидаемся завершения процесса
  WaitForSingleObject(pi->hProcess,INFINITE);
  //Закрываем дескрипторы и восстанавливаем выходной поток.
  CloseHandle(pi->hProcess);
  CloseHandle(pi->hThread);
  freopen("CONOUT$","wb",stdout);
 }
 //Очищаем память
 delete pi;
 delete inf;
 delete tmp;
}

пятница, 22 июля 2011 г.

Задача о счастливых билетах на шаблонах.

Итак, недавно захотелось попрактиковаться в извращениях на шаблонах.  Недолго думая, я решил сосчитать на них число счастливых билетов от 100 000 до 999 999. Назовем таковыми билеты, у  которых сумма первых трех цифр равна сумме последних .

При этом задача должна выполниться в compile-time на шаблонах. 

Первое что приходит в голову при решении такой задачи - организация простого цикла на шаблонах вида:


#include <stdio.h>

#define GET1(a)  //получение первой цифры числа
#define GET2(a)  //получение второй цифры числа
#define GET2(a)  //получение второй цифры числа
#define GET3(a)  //получение третьей цифры числа
#define GET4(a)  //получение четвёртой цифры числа
#define GET5(a)  //получение пятой цифры числа
#define GET6(a)  //получение шестой цифры числа

#define CHECK(a) ((GET1(a)+GET2(a)+GET3(a)==GET4(a)+GET5(a)+GET6(a))?1:0)

template<int A>
struct  get
{
  static const int val=CHECK(A)+get<A-1>::val;
}; 

template<>
struct  get<100000>
{
 static const int val=0;
};

int main(int argc,char ** argv) 
{
 printf("%d\n",get<999999>::val);
 return 0;
}

Но это решение не будет работать. Не будут работать и решения с разбитием числа сразу на отдельные символы и линейной итерацией  по ним. Причина в том, что происходит много инстанцирований "в глубину", т.е. не определив класс get<x> мы определяем классы get<x-1>, get<x-2>. и.т.п.  Современные компиляторы дают возможность делать много инстанцирований в глубину, в GCC к примеру был зашит предел в 500 который легко обходился указанием параметра -ftemplate-depth-1000000. Однако, это вызывало вылет компилятора. Очевидно, данное решение крайне неудачно.

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


#include <stdio.h>
#define GET(A) ((a0+a1+a2 == a3+a4+ A )?1:0)

template<
char a0,
char a1,
char a2,
char a3,
char a4,
>
struct get_lucky6
{
  static const int val=GET(9)+GET(8)+GET(7)+GET(6)+GET(5)+GET(4)+GET(3)+GET(2)+GET(1)+GET(0);
};


template<
char a0,
char a1,
char a2,
char a3,
char a4
>
struct get_lucky5
{
  static const int val=get_lucky6<a0,a1,a2,a3,a4>::val+get_lucky5<a0,a1,a2,a3,a4-1>::val;
};

template<
char a0,
char a1,
char a2,
char a3
>
struct get_lucky5<a0,a1,a2,a3,0>
{
  static const int val=get_lucky6<a0,a1,a2,a3,0,9>::val;
};

template<
char a0,
char a1,
char a2,
char a3
>
struct get_lucky4
{
  static const int val=get_lucky5<a0,a1,a2,a3,9>::val+get_lucky4<a0,a1,a2,a3-1>::val;
};

template<
char a0,
char a1,
char a2
>
struct get_lucky4<a0,a1,a2,0>
{
  static const int val=get_lucky5<a0,a1,a2,0,9>::val;
};

template<
char a0,
char a1,
char a2
>
struct get_lucky3
{
  static const int val=get_lucky4<a0,a1,a2,9>::val+get_lucky3<a0,a1,a2-1>::val;
};

template<
char a0,
char a1
>
struct get_lucky3<a0,a1,0>
{
  static const int val=get_lucky4<a0,a1,0,9>::val;
};

template<
char a0,
char a1
>
struct get_lucky2
{
  static const int val=get_lucky3<a0,a1,9>::val+get_lucky2<a0,a1-1>::val;
};


template<
char a0
>
struct get_lucky2<a0,0>
{
  static const int val=get_lucky3<a0,0,9>::val;
};

template<
char a0
>
struct get_lucky1
{
  static const int val=get_lucky2<a0,9>::val+get_lucky1<a0-1>::val;
};


template<>
struct get_lucky1<1>
{
  static const int val=get_lucky2<1,9>::val;
};


int main(int argc,char ** argv)
{
  printf("%d\n",get_lucky1<9>::val);
  return 0;
}

Здесь при помощи препроцессора развернут последний внутренний цикл, дабы чуть сократить время компиляции. Однако, этот код не дает успешного результата. При компиляции данного кода компилятор "съедает" 300+ Мб памяти, и после 4 часов компиляции не завершает свою работу.

Замечу, что выше уже приведено направление оптимизации: нужно делать вычисление не одного билета за инстанцирование, а нескольких. Здесь на помощь приходит препроцессор: с его помощью и жертвой безопасности типов можно рекуррентно развернуть еще один цикл, существенно сократив число инстанцирований:


#include <stdio.h>

#define GET(A1,A) ((a0+a1+a2 == a3+A1+ A )?1:0)
#define GETA1(A1) (GET(A1,9)+GET(A1,8)+GET(A1,7)+GET(A1,6)+GET(A1,5)+GET(A1,4)+GET(A1,3)+GET(A1,2)+GET(A1,1)+GET(A1,0))
#define GETA2     (GETA1(9)+GETA1(8)+GETA1(7)+GETA1(6)+GETA1(5)+GETA1(4)+GETA1(3)+GETA1(2)+GETA1(1)+GETA1(0))

#define GET0(A1,A) ((a0+a1+a2 == A1+ A )?1:0)
#define GET0A1(A1) (GET0(A1,9)+GET0(A1,8)+GET0(A1,7)+GET0(A1,6)+GET0(A1,5)+GET0(A1,4)+GET0(A1,3)+GET0(A1,2)+GET0(A1,1)+GET0(A1,0))
#define GET0A2     (GET0A1(9)+GET0A1(8)+GET0A1(7)+GET0A1(6)+GET0A1(5)+GET0A1(4)+GET0A1(3)+GET0A1(2)+GET0A1(1)+GET0A1(0))


template<
char a0,
char a1,
char a2,
char a3
>
struct get_lucky4
{
  static const int val=GETA2+get_lucky4<a0,a1,a2,a3-1>::val;
};

template<
char a0,
char a1,
char a2
>
struct get_lucky4<a0,a1,a2,0>
{
  static const int val=GET0A2;
};

template<
char a0,
char a1,
char a2
>
struct get_lucky3
{
  static const int val=get_lucky4<a0,a1,a2,9>::val+get_lucky3<a0,a1,a2-1>::val;
};

template<
char a0,
char a1
>
struct get_lucky3<a0,a1,0>
{
  static const int val=get_lucky4<a0,a1,0,9>::val;
};

template<
char a0,
char a1
>
struct get_lucky2
{
  static const int val=get_lucky3<a0,a1,9>::val+get_lucky2<a0,a1-1>::val;
};


template<
char a0
>
struct get_lucky2<a0,0>
{
  static const int val=get_lucky3<a0,0,9>::val;
};

template<
char a0
>
struct get_lucky1
{
  static const int val=get_lucky2<a0,9>::val+get_lucky1<a0-1>::val;
};


template<>
struct get_lucky1<1>
{
  static const int val=get_lucky2<1,9>::val;
};


int main(int argc,char ** argv)
{
  printf("%d\n",get_lucky1<9>::val);
  
  return 0;
}
Данный код "съедает" порядка 450Мб, компилируется около 1,5 мин и дает корректный результат.

Может показаться, что в таком случае задачу на шаблонах не стоит строить и лучше воспользоваться чистым препроцессором. Это не так. Скомпилировав код:


#include <stdio.h>
#define GET(A0,A1,A2,A3,A4,A5) ((A0+A1+A2 == A3+A4+ A5 )?1:0)
#define GET6(A0,A1,A2,A3,A4)   (GET(A0,A1,A2,A3,A4,0)+GET(A0,A1,A2,A3,A4,1)+GET(A0,A1,A2,A3,A4,2)+GET(A0,A1,A2,A3,A4,3)+GET(A0,A1,A2,A3,A4,4)+ \        GET(A0,A1,A2,A3,A4,5)+GET(A0,A1,A2,A3,A4,6)+GET(A0,A1,A2,A3,A4,7)+GET(A0,A1,A2,A3,A4,8)+GET(A0,A1,A2,A3,A4,9))
#define GET5(A0,A1,A2,A3)      (GET6(A0,A1,A2,A3,0)+GET6(A0,A1,A2,A3,1)+GET6(A0,A1,A2,A3,2)+GET6(A0,A1,A2,A3,3)+GET6(A0,A1,A2,A3,4)+ \                                GET6(A0,A1,A2,A3,5)+GET6(A0,A1,A2,A3,6)+GET6(A0,A1,A2,A3,7)+GET6(A0,A1,A2,A3,8)+GET6(A0,A1,A2,A3,9))
#define GET4(A0,A1,A2)         (GET5(A0,A1,A2,0)+GET5(A0,A1,A2,1)+GET5(A0,A1,A2,2)+GET5(A0,A1,A2,3)+GET5(A0,A1,A2,4)+GET5(A0,A1,A2,5)+ \                                GET5(A0,A1,A2,6)+GET5(A0,A1,A2,7)+GET5(A0,A1,A2,8)+GET5(A0,A1,A2,9))
#define GET3(A0,A1)            (GET4(A0,A1,0)+GET4(A0,A1,1)+GET4(A0,A1,2)+GET4(A0,A1,3)+GET4(A0,A1,4)+GET4(A0,A1,5)+GET4(A0,A1,6)       \                                +GET4(A0,A1,7)+GET4(A0,A1,8)+GET4(A0,A1,9)) 
#define GET2(A0)               (GET3(A0,0)+GET3(A0,1)+GET3(A0,2)+GET3(A0,3)+GET3(A0,4)+GET3(A0,5)+GET3(A0,6)+GET3(A0,7)+GET3(A0,8)+GET3(A0,9))
#define GET1                    (GET2(1)+GET2(2)+GET2(3)+GET2(4)+GET2(5)+GET2(6)+GET2(7)+GET2(8)+GET2(9))

const int lucky=GET1;
        
int main(int argc,char ** argv)
{
  printf("%d\n",lucky);
  
  return 0;
}
Мы при компиляции получим порядка минуты, однако компилятор при этом "съедает" около 952Мб памяти.

Вообще вычисление этой задачи в compile-time несёт мало преимуществ. Однако, это возможно.

При тестировании использовался компьютер со следующими параметрами:
Процессор:     Core 2 Duo 1.87GHz
RAM:              2Gb
Версия GCC: 4.4.3

SCons раздельная сборка + рекурсивное сканирование

Продолжаю репосты из своего старого блога. Также спасибо chexov - без него мне бы прикрутить highlight.js было бы сложнее.

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

SConstruct


import platform
import os

def get_mingw_environment():
     mingw=ARGUMENTS.get('MINGW_TOOLCHAIN_PATH',"C:\\MinGW")
     env = Environment(tools = ['mingw'], ENV = os.environ)
     if mingw.endswith('\\') :
        env.PrependENVPath('PATH', mingw+'\\bin')
        env.PrependENVPath('LIB',  mingw+'\\lib')
     else:
        env.PrependENVPath('PATH', mingw+'bin')
        env.PrependENVPath('LIB',  mingw+'lib')
     return env

  
if platform.system() == 'Windows':
    use_mingw=ARGUMENTS.get('USE_MINGW',"NO")
    if use_mingw=="NO":
      env = Environment(ENV = os.environ)
    else:
      env=get_mingw_environment()
else:
     env = Environment(ENV = os.environ)


SConscript('SConstruct.me',build_dir='build',exports='env',duplicate=0)

Данный скрипт, создает "среду", используя пример из предыдущего скрипта после чего выполняет задание в SConstruct.me, который и собирает программу, при этом указывая папку для сборки и экспортируя в него переменную "среды".

Сам SConstruct.me уже осуществляет сканирование и сборку программы:


import os
import fnmatch

def merge_list(list1,list2):
    if list2 is not None:
       for item in list2:
           list1.append(item)

def is_matches(filename,include,excludes):
    match = False
    for pattern in include: 
       if fnmatch.fnmatchcase(filename, pattern):
           match=True
           break
    if match and excludes is not None:
        for pattern in excludes:
            if fnmatch.fnmatchcase(filename, pattern):
                match = False
                break
    return match

def merge_path(a,b):
     if (a=='.') or (a==''):
          return b
     if (b=='.') or (b==''):
       return a
     return a+"/"+b

def get_files(src_dir,abs_path,include,excludes=None):
    files = []
    for file in os.listdir(merge_path(abs_path,src_dir)):
       tpath=merge_path(src_dir,file)
       if os.path.isdir(merge_path(abs_path,tpath)):
          merge_list(files,get_files(tpath,abs_path,include,excludes))
       else:
          if (is_matches(tpath,include,excludes)):
              files.append(tpath)
    return files     
# Данная процедура осуществляет рекуррентный спуск из паки src_dir используя 
# шаблоны include и исключая файлы подпадающие под шаблоны exclude
def get_files_recursive(src_dir,include,excludes=None):
    return get_files(src_dir,Dir(src_dir).srcnode().abspath,include,excludes)
 
Import('env')
#  Здесь и "собирается новая программа, приведено чисто для примера"
env.Program('progr',get_files_recursive('.',["*.cpp"],["*/bomb.cpp"]), LIBS=['m'], LIBPATH=['.'],INCDIR=['.']);