четверг, 17 сентября 2009 г.

Ошибка 1053. Сервисы на Windows 7

Наткнулся недавно на любопытный баг. Переносил группу проектов с Delphi 2007 на Delphi 2010. С параллельным переходом на Windows 7. Да, знаю, нельзя менять сразу все, но.. так получилось :)

Проекты - группа сервисов. Баг выразился следующим образом. Запускаю любой (один!) сервис - все в порядке. Пытаюсь запустить любой второй - вылетает ошибка "Error 1053: The service did not respond to the start or control request in a timely fashion". Самое неприятное - ошибка вылетает немедленно, сразу при запуске сервиса. Все попытки поставить в ServiceStart Sleep(пара минут) и следом бряк ничего не дали - ошибка вылетает раньше, до того, как выполнится ServiceStart.

Правда, один сервис работал корректно и никак не мешал запуску других сервисов. Сравнил - выяснил, что все проблемные сервисы использовали Indy. Точнее, класс TIdHTTPServer. А тот сервис, что работал корректно, этот класс не использовал и с Indy никак связан не был. Поискал в интернете - нашел описание подобной проблемы на форуме Embarcadero. Правда, решения там не оказалось.

Проблема решилась на следующий день на удивление просто. Создал новый сервис. Визард написал следующий код в файле проекта:

begin
// Windows 2003 Server requires StartServiceCtrlDispatcher to be
// called before CoRegisterClassObject, which can be called indirectly
// by Application.Initialize. TServiceApplication.DelayInitialize allows
// Application.Initialize to be called from TService.Main (after
// StartServiceCtrlDispatcher has been called).
//
// Delayed initialization of the Application object may affect
// events which then occur prior to initialization, such as
// TService.OnCreate. It is only recommended if the ServiceApplication
// registers a class object with OLE and is intended for use with
// Windows 2003 Server.
//
// Application.DelayInitialize := True;
//
if not Application.DelayInitialize or Application.Installing then
Application.Initialize;
Application.CreateForm(TService1, Service1);
Application.Run;
end.
Проверил - все нерабочие сервисы отложенную инициализацию не использовали. Заменил "Application.Initialize;" на

Application.DelayInitialize := True;
if not Application.DelayInitialize or Application.Installing then begin
Application.Initialize;
end;
и все заработало.

суббота, 20 июня 2009 г.

Стоит ли читать книги Кийосаки?

Наткнулся на статью Джона Рида, содержащую разгромную критику книг Кийосаки (реферат статьи на русском). Для тех кто не в курсе, Роберт Кийосаки - один из наиболее популярных авторов книг по бизнесу. Его серия книг "Богатый Папа, Бедный Папа" на настоящий момент состоит из 18 книг. Роберт Кийосаки основал международную образовательную компанию "Rich Dad`s Organization", которая занимается обущением бизнесу и инвестированию. В его игру "Cashflow" с увлечением играет масса народа.

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

Необходимо отметить один важный момент. Все книги Кийосаки, которые я прочитал, не содержат какой-либо конкретики. Они содержат, скорее, общие рассуждения о предмете. Излагают общую философию, без конкретных практических рекомендаций. Подобно книгам Юрия Мороза. Увлекательно, интересно и все чертовски правильно написано, но.. что конкретно делать то? :)

На мой взгляд, абсолютное большинство книг по бизнесу страдают одним общим недостатком. Они описывают только вершину айсберга, но не дают фундаментальных знаний. Поясню чуть подробнее.

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

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

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

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

Не берусь сказать, насколько корректна критика Рида. Скорее всего, он несколько перегибает палку. Решайте сами. Но лично меня очень особенно огорчили два момента: основной источник дохода Кийосаки, похоже, его книги, а не инвестирование в недвижимость; "богатый папа" - это мифический персонаж. Все это разом переносит книги Кийосаки из "non-fiction" в "fiction". А фикшин есть фикшин.

P.s. John T. Reed громит не только Кийосаки. Досталось и многим другим гуру: Dolf DeRoos, Carleton Sheets, Donald Trump, Russ Whitney. Лишь сам Рид весь в белом:) И предлагает для обучения процессу инвестирования в недвижимость несколько собственных книжек. Надо будет почитать. Кстати, материалы, размещенные на сайте, весьма интересны.
UpdateБолее серьезный источник информации: цикл статей Сергея Спирина Введение в инвестирование.

четверг, 11 июня 2009 г.

Opera Links. Синхронизация закладок браузера между компьютерами.

Я регулярно работаю на трех разных компьютерах - домашний, рабочий и ноутбук. На каждом из них установлено по браузеру. Я предпочитаю работать в браузере Opera, остальные браузеры используются лишь для тестов. Так что получается, что работаю я в трех разных экземплярах оперы.

Естественно, в каждой опере у меня отдельный набор закладок. И естественно, меня давно мучает проблема, как бы эти закладки между собой синхронизировать. Недавно дошли руки, и я с этой проблемой разобрался. Решение оказалось на удивление простым - Opera Links. Возможность автоматической синхронизации закладок между компьютерами встроена в Opera.

Выбираем пункт меню "File\Synchronize Opera". Щелкаем кнопку "Sign up". Указываем имя пользователя, пароль и email. Опера создаст вам личный профиль на сайте opera.com. Профиль нужно активировать - ссылку для активации присылают на email в течении нескольких секунд после регистрации. Как только профиль активирован, вводим выбранные имя пользователя и пароль в окне "Opera Links", щелкаем "Opera Link Options", выбираем, какие элементы нужно синхронизировать между компьютерами (закладки, Speed Dial, заметки и т.п.), даем команду "Log in".



Все, синхронизация запущена. Теперь, в течении короткого времени, Opera скопирует все закладки с вашего компьютера в профиль на сайте. В дальнейшем, список закладок на вашем компьютере и список закладок в профиле будут синхронизироваться автоматически. Добавили новую закладку в браузер, Opera тут же скопировала ее в ваш профиль. Позже, закладка автоматически появится в списках закладок оперы на других ваших компьютерах. Только надо не забыть на этих компьютерах запустить синхронизацию в Opera.

Преимущества налицо. Во-первых, автоматическая синхронизация списков закладок между разными компьютерами. Очень удобно. Во-вторых, список закладок теперь можно просматривать online. Причем не только из оперы, но и из любого другого браузера. Достаточно зайти на сайт http://link.opera.com/ и ввести ваше имя пользователя и пароль. В-третьих, автоматически решается проблема резервного копирования списка закладок. В прошлом, я пару раз терял все свои нажитые непосильным трудом закладки - было очень обидно.

Конечно, закладки можно хранить на сервисах типа бобрдобр и delicious.com. Но это - немножко не то. Возможность пометить ссылку тегами хороша - другим пользователям будет проще эту ссылку найти. А самому мне будет гораздо быстрее и проще найти нужную ссылку прямо в браузере, через фильтр в списке закладок. Уж больно он хорошо сделан!

p.s. А профиль на delicious.com я все-таки завел. Уж больно удобен этот сервис для поиска информации. Надо с ним поработать..

среда, 20 мая 2009 г.

Работа с Excel из C#.

Столкнулся с необходимостью поработать с файлами Excel из C#. Задачи простые - считать данные из книги Excel, создать новую книгу Excel, внести в нее данные. Предполагается, что Excel на компьютере имеется, но какой версии, не известно. Файлы по размерам маленькие, скорость работы не критична.

Существует несколько вариантов работы с Excel из C#: автоматизация Excel, подключение через OleDB/ODBC, дополнительные библиотеки (Aspose Excel), работа через XML, через Open XML и т.п.

Наиболее простой вариант - воспользоваться автоматизацией Excel. Да, скорость работы - не блеск. Зато удобно использовать, код пишется быстро, объемы кода не велики. Из .NET автоматизация подключается парой кликов мыши - добавил в References сборку Microsoft.Office.Interop.Excel - и работай с привычными com-объектами Application,Workbook, Worksheet и т.п.

Проблема одна - сборки "Microsoft.Office.Interop.Excel" для каждой версии Excel разные. У меня установлен office 2003 и, соответственно, interop-сборка версии 11. А что делать, если мне нужно разработать приложение, которое может работать с Excel 97? Или с любыми версиями Excel? Для того, чтобы считать или записать пару значений в ячейки листа Excel сложного api не требуется - любой Excel сгодится. А мне приходится привязываться к конкретной версии.

Можно воспользоваться поздним связыванием. Но если его использовать "в лоб", то ни о никаком удобстве работы речи уже не идет - код станет сложным, а вызовы методов - косвенными, нетипизированными. Ошибок будет - вагон.

Выход нашелся. На СodeProject обнаружил замечательную статью "SafeCOMWrapper - Managed Disposable Strongly Typed safe wrapper to late bound COM". В ней изложена элегантная методика использования позднего связывания, устраняющая все проблемы: связывание становится поздним (привязки к конкретной версии Excel нет), для объектов автоматизации автоматически реализуется шаблон IDisposable (отработанные объекты уничтожаются автоматически), все вызовы методов явные.

Идея реализации, вкратце, следующая. Для каждого COM-объекта автоматизации вы прописываете отдельный интерфейс. В интерфейс включаете все методы и свойства, которые вам необходимо использовать у этого COM-объекта. Обратите внимание - только те, которые необходимо использовать, а вовсе не все, реализуемые COM-объектом.

Каждый COM-объект автоматизации "заворачивается" в класс COMWrapper, унаследованный от RealProxy. RealProxy - это стандартный класс, позволяющий организовать перехват вызовов методов. При создании COM-объекта вы создаете экземпляр COMWrapper и указываете требуемый вам интерфейс. Среда динамически генерирует прокси-объект, реализующий этот интерфейс. С этим прокси-объектом вы в дальнейшем и работаете как с объектом автоматизации. Вызов любого метода прокси-объетка перехватывается и транслируется в вызов метода Invoke класса RealProxy, перекрытый в классе COMWrapper. Здесь его можно обработать как душе угодно. В реализации по умолчанию, вызовы свойств транслируются в вызовы соответствующих методов get_ и set_, создаваемых .NET, возвращаемые объекты автоматизации автоматически заворачиваются в COMWrapper и т.п.

В оригинально статье приведен пример использования данной методики для Microsoft.Outlook. Я, на базе приведенных исходных кодов, адаптировал методику для работы с Microsoft.Excel. Естественно, реализовав интерфейсы только для тех объектов автоматизации, которые потребовались мне для работы. Дополнить интерфейсы недостающими методами или добавить интерфейсы для других объектов автоматизации не составляет труда. Достаточно посмотреть через Object Browser метаданные сборки "Microsoft.Office.Interop.Excel" и скопировать оттуда необходимые объявления методов и интерфейсов (с минимальной адаптацией).

Пример кода для работы с Excel:
using (Application app = ExcelApplication.Create()) {
String s = app.Version;
app.Visible = true; // make excel visible
using (Workbook wb = app.Workbooks.Add(Type.Missing)) {
wb.Title = "new workbook";
using (Worksheets worksheets = wb.Sheets) {
using (Worksheet ws = worksheets[1]) {
//try to assign some values to some cells
using (Range cells = ws.Cells) {
for (int i = 1; i < 10; ++i) {
using (Range r = cells[i, i]) {
r.Value = i * i;
}
}
}
}
}
wb.Saved = true;
wb.Close();
}
app.Quit();
}
Оригинальные исходные коды лежат на codeproject вместе со статьей. Исходные коды, адаптированные для Microsoft Excel, можно взять здесь.Update. Практика использования выявила небольшую проблему - забыть вызвать Dispose у оберток объектов Excel очень легко. После чего Excel висит в памяти до тех пор, пока не закроешь приложение (или до следующей сборки мусора). Пример:
using (Workbook wb = app.Workbooks.Add(Type.Missing)) {
using (Worksheet ws = wb.Sheets[1]) 
/*Здесь потерян Dispose у объекта Worksheets, который создается при 
вызове wb.Sheets*/
{ .... }}
вместо
using (Workbook wb = app.Workbooks.Add(Type.Missing)) {
using (Worksheets worksheets = wb.Sheets) {
using (Worksheet ws = worksheets[1]) {
.... }}

Просто надо быть внимательнее.

понедельник, 4 мая 2009 г.

Ссылки: доклад Авантюриста, переписка Стругацкого с Ходорковским

Стенограмма доклада Авантюриста о глобальном экономическом кризисе на "Большом кофе-брейке для бизнеса", в г. Томске, 27 марта 2009 года. Вот здесь обработанные выдержки из доклада - уже более официальный источник. P.s.Похоже расшифровка выступления авантюриста изначально взята отсюда.

Во что вкладывать деньги? В профессию и в детей...

Переписка Бориса Стругацкого и Михаила Ходорковского в Новой газете:
письмо 1,
письмо 2,
письмо 3.

понедельник, 27 апреля 2009 г.

Самооценка компетентности программиста.

Хороший ли я программист? Как самому себе ответить на такой вопрос?

Достаточно давно слышал о таком способе самооценки. Если пять программистов из тех, кто на голову превосходят вас по уровню программирования, отозвались о вас как об очень хорошем программисте, то можно считать, что это действительно так. Тут главное, суметь найти нужное количество таких программистов :)

На днях на глаза попался другой, более формализованный способ: матрица компетентности программиста.

Update. Как вариант, можно попробовать оценить качество кода, и по этой оценке судить о профессионализме программиста.

пятница, 24 апреля 2009 г.

Видео-лекции на английском языке.

Наткнулся недавно на замечательный ресурс: videolectures.net - коллекция лекций. Коллекция содержит видеозаписи лучших лекций, прочитанных на различных конференциях, семинарах, летних школах и т.п. Все лекции на английском языке.

Тематика лекций - научная. Масса лекций на тему компьютерных наук, посвященных Data Mining, анализу изображений, машинному обучению, компьютерной графике, распознаванию речи и т.п. Есть лекции по биологии, философии, истории, окружающей среде, психологии, бизнесу, экономике и т.д. Кроме лекций, на сайте выложено изрядное количество интервью.

Понравилось, как все сделано. Просматриваешь лекцию, а рядом с видео показывается картинка со слайдами. Меняется слайд в лекции, меняется слайд на картинке. Очень удобно. Слайды можно скачать отдельно в PDF или PowerPoint. Материал хорошо систематизирован. Можно выбрать лекции по теме, по автору, по конференции.

Лекции, надо сказать, интересные и информативные. Так что, тренеруем английский, получаем полезную информацию.

Update:

вторник, 24 февраля 2009 г.

Как получить hardware id. Исходники DiskID32 на Delphi

Потребовалось защитить приложение, написанное на Delphi, от копирования. Точнее, потребовалось обеспечить возможность привязывать приложение к конкретному компьютеру, на котором оно устанавливается.

Чтобы "привязать" программу к компьютеру, необходимо уметь генерировать уникальный идентификатор для его "железа" - hardware id. По hardware id генерируется серийный номер, который будет работать только на заданному компьютере. Такой подход активно используется для защиты ПО, хотя для пользователей он порой не слишком удобен - при апгрейде компьютера серийный номер может перестать работать.

Вопрос - как сгенерировать hardware id?

Изучение интернета показало, что имеется три основных варианта.
  1. Использовать MAC-адрес сетевой карты.
  2. Использовать информацию из WMI.
  3. Использовать серийный номер жесткого диска, на котором установлена операционная система.
Недостатки использования MAC-адреса описаны здесь:
  • Сетевых карт может быть несколько.
  • При включении bluetooth/wi-fi появляется новая "сетевая карта".
  • При отключенном сетевом проводе MAC-адрес не определяется.
Недостатки WMI:
  • В Me/2000/XP эта служба встроена, но в более ранних версиях Windows ее нужно доставлять.
  • Под Vista, с включенным UAC, без прав администратора доступ к WMI не получить.
  • Cлужба WMI может быть отключена.
  • Получение информации из WMI - очень медленный процесс. Несколько секунд задержки при запуске гарантированы.
Вариант с серийным номером жесткого диска - хорош. Тем более, что есть программа DiskID32, с открытыми исходными кодами, которая позволяет получать серийный номер жесткого диска в любых версиях Windows (от 9X до 64-bit), причем как при наличии прав администратора, так и при их отсутствии. Бери и пользуйся.

Беда только в том, что у нас приложение написано на Delphi. Значит и процедура получения hardware id нужна на Delphi (вариант с DLL не подходит). А DiskID32 написан на С++...

Делать нечего. Вооружился отладчиком и перевел исходные коды DiskID32 с C++ на Delphi. Основные проблемы, с которыми столкнулся: в Delphi нет битовый полей (они использовались при описании ряда структур) и нет полноценной адресной арифметики. К счастью, битовые поля оказались не принципиальны и я их просто отбросил. Операции над указателями (инкремент, декремент) заменил на аналогичные операции с массивами. Не слишком изящно, но работает. Код старался транслировать один в один, ничего не удаляя. Оригинальные исходники DiskID содержат код для определения MAC-адресов сетевых карт. В моем случае MAC-адрес не требовался, поэтому эти функции я не транслировал.

Код был протестирован на Windows XP, Vista, Server 2003 (с рейдом и без), а так же под Vista 64-bit. Под 9x код не тестировался, поэтому гарантий, что он заработает, нет никаких. Если вы найдете ошибку в коде - пожалуйста, сообщите мне о ней на email, указанный в исходных кодах.

Остается выразить благодарность датской компании Efaktum, для которой была выполнена эта трансляция кода, и которая любезно разрешила мне выложить переведенный код в открытый доступ.

Скачать исходные коды DiskID32 for Delphi.

Update: В первой версии исходных кодов нашлась ошибка, см. комментарии. Исправил ее и, заодно, перевел проект на Delphi 2010 (совместимость с Delphi 7 сохранена):

Скачать исходные коды DiskID32 for Delphi 2010.

Download source codes of DiskId32 for Delphi.
Download source codes of DiskId32 for Delphi (updated 16.01.2012)

View source codes of DiskId32 for Delphi.
Short description in English.

P.s. если вы не хотите, чтобы diskID выводил информацию на консоль, закомментируйте
макрос {$DEFINE PRINTING_TO_CONSOLE_ALLOWED}. В таком виде исходники пригодны для использования в неконсольных приложениях.

Update: Под x64 нет crtdll.dll. Вместо нее следует использовать msvcrt.dll. Т.е. в crtdll_wrapper объявить crt-функции следующим образом:
function crt_isspace(ch: Integer): Integer; cdecl; external 'msvcrt.dll' name 'isspace';
function crt_isalpha(ch: Integer): Integer; cdecl; external 'msvcrt.dll' name 'isalpha';
function crt_tolower(ch: Integer): Integer; cdecl; external 'msvcrt.dll' name 'tolower';
function crt_isprint(ch: Integer): Integer; cdecl; external 'msvcrt.dll' name 'isprint';
function crt_isalnum(ch: Integer): Integer; cdecl; external 'msvcrt.dll' name 'isalnum';
Библиотека crtdll.dll нужна только если требуется совместимость с win95.

Update: август 2013: Прислали на email обновление: "1. Сделал рабочей ReadIdeDriveAsScsiDriveInNT - char/ansichar ошибка; 2. Избавился от msvcrt.dll (переписал функции на дельфи); 3. Немного оптимизации." Обновленную версию следует брать в svn.

понедельник, 23 февраля 2009 г.

Чай. Как заваривать и где покупать.

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

До поездки в Китай я считал, что существует два типа чая: черный и зеленый. Я пил только черный - зеленый мне не нравился принципиально. Когда я приехал в Пекин, то быстро выяснилось, что альтернативы зеленому чаю нет. Либо пьешь зеленый, либо не пьешь никакой. Поэтому в первый же день я сходил в магазин, купил пачку чая за 60 юаней (240 рублей) и мы стали заваривать его по вечерам прямо в стаканах (заварочного чайника у нас не было ни в одном отеле). Когда через две недели я вернулся домой, то НИКАКОЙ другой чай я уже пить не хотел :)

Довольно быстро выяснилось, что пили мы вовсе не зеленый чай, а улунский, oolong, что означает "черный дракон". От черного и зеленого он отличается степенью окисления. При изготовлении зеленого чая окисление не используется вообще, черные чаи окислены на 100%, а улуны - с серединки на половинку, на 10-70%. (Подробно о процессе окисления и о шести возможных видах чаев: зеленый, желтый, белый, улун, черный, и пуэр, - можно прочитать здесь). Улуны сразу полюбились мне больше всего и даже сейчас, когда я попробовал множество других видов чая, я предпочитаю именно их.

Второй важный момент, который я выяснил в Китае - как правильно заваривать чай. Оказывается всю жизнь я заваривал его не правильно. Насыпаешь заварки в заварочный чайник, завариваешь кипятком, ждешь пока настоится, наливаешь заварки на четверть чашки, доливаешь кипятка... Заварку используешь несколько раз, несколько дней... кто на что горазд.

На чайной церемонии в Китае нам рассказали, как заваривать чай правильно. Нужно положить листья чая в чайник. Залить кипятком и дать настояться. Недолго - 5-10 секунд (У разных чаев время первой заварки может существенно различаться. Лично я его всегда определяю экспериментально. Если чай получается горький, просто уменьшаю время). Далее чай разливается по чашкам. При этом воду из чайника нужно выливать полностью, листья чая ни в коем случае не должны оставаться в воде. Это - принципиальный момент. Как говорят китайцы, если вы оставите листья чая в воде, то на следующий день этим чаем можно будет очень хорошо мыть ноги (полезно для кожи), но пить его будет невозможно.

Одну и ту же заварку можно использовать 4-5 раз. Вторая и третья заварка - самые лучшие. Чай получается наиболее вкусным и насыщенным. Китайцы утверждают, что первую заварку вообще выливают. Ее цель - дать листьям размокнуть. Но для меня такой подход не рентабелен, поэтому я его не практикую.

Китайцы заваривают чай во френч-прессах. Для китайского метода заварки чая он подходит идеально.

Естественно, просто так в магазине чай не продают. Вернее то, что продают у нас в магазинах, это совсем не тот чай. Настоящий китайский чай продают в Китае и покупать его нужно именно там. (Пара магазинов с настоящим китайскием чаем у нас в Красноярске есть, но цены там очень высокие).

К счастью, в наше время это не проблема - чай легко покупается через Интернет. Лично я несколько лет покупаю чай на сайте www.teaspring.com.

Здесь продают множество различных видов чаев, в том числе, зеленые, улуны и черные. Черные чаи я заказывал дважды, на пробу и оба раза они мне не понравились. Зато улуны там великолепные. Yong Chun Fo Shou, Dong Ding Oolong, Huang Jin Gui, Tie Guan Yin, Gui Hua Oolong, Feng Huang Dan Cong - любой из этих чаев я могу смело порекомендовать. Стоят они правда недешево. Самый недорогой (но очень достойный чай) Yong Chun Fo Shou - $8 за 100 г, остальные по $11, $15, $20 и т.п. При выборе обратите внимание, что цены на сайте для разных чаев указаны за разное количество грамм.

Оплата заказов - кредитной карточкой. На этом сайте при оплате карточкой требуется указывать код CVV, так что подходит подавляющее большинство карточек, даже обычные карты ВТБ24. Но я для интернет платежей давно уже завел специальную карточку в Промсвязьбанке - ей можно рассчитываться без указания CVV (например, на www.cabelas.com), у нее нет никаких дурацких ограничений в 500 долларов в месяц и стоит она всего $4 в год. Подобные карты есть и в других банках.

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

Сумма заказа должна быть более $70 - тогда доставка почтой бесплатная, при меньшей стоимости доставка стоит $3.8. Общая сумма посылки должна быть менее 10 тыс руб, а еще лучше - менее 5 тыс, иначе могут начаться неприятности с таможней (ранее я уже писал об этой проблеме). Следует иметь в виду, что в период 7 дней можно получить не более одной посылки из-за границы - при покупке в разных интернет магазинах одновременно это ограничение надо учитывать.

Посылка доставляется обычно в течении 3х недель - в наш Красноярск она идет окольными путями, через Москву. Если заказывать на новогодние праздники, то посылка может идти месяц и больше. Я стараюсь не заказывать слишком большие посылки, т.к. при превышении определенного веса (кажется 1.5 кг) посылки начинают идти как-то по другому, получать их приходится не в почтовом отделении, а на ж/д вокзале и идут они в два раза дольше.

На сайте есть система бонусов - поинтов. При покупке на $100 вы получаете бонусы на $10 - эти бонусы можно использовать в виде скидки при следующей покупке. Дополнительные бонусы можно зарабатывать, если писать на сайте отзывы о купленном чае.

Конечно, есть и другие сайты, на которых продается чай. Например, перечисленные здесь:Но сам, лично, я их пока не проверял.

"Чай" из пакетиков я теперь пить совсем не могу. В связи с кризисом придется, видимо, думать о том, чтобы перейти с чая на обычную воду :)

Update 2011: топик в блоге Экслера на тему чая - народ накидал ряд полезных ссылок на магазины.

пятница, 20 февраля 2009 г.

В помощь начинающему писателю.

"Комиссия в детском саду спрашивает заведующую:
- Скажите, почему у вас дети так странно говорят: "пришедши", "ушедши"?
- Да они у нас так привыкши..."

Грамотная речь всегда понятнее, чем безграмотная. Как научиться грамотно и доступно излагать свои мысли? Основные приемы известны.

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

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

Добавлено:

среда, 18 февраля 2009 г.

О пользе LinkedIn

Прислали ссылку на интересную статью How to Close $100,000 in Business With LinkedIn Using 4 Quick Steps. О том, какую пользу может принести участие в LinkedIn.

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

P.s. LinkedIn - социальная сеть для поиска и установления деловых контактов. Сеть ориентирована на IT-специалистов.

вторник, 17 февраля 2009 г.

Модель-представление-контроллер. Раз модель, два модель.

Приложение использует архитектуру MVC. В нем реализовано множество различных представлений. Два из них используют общий набор данных, который интересен только им и к модели не имеет никакого отношения. Где хранить эти данные?

Я с такой задачей столкнулся при разработке программы, в которой главное окно способно отображать множество различных видов. Окно разделено на две части. Слева - панель, аналогичная Navigation Pane, справа - вид. Один из видов - это графический редактор с нарисованными объектами. Если выбрать этот вид, то слева на панели будет дополнительно выведен полный список объектов. Объекты можно выделять мышкой как в редакторе, так и в списке объектов. Вопрос - где хранить состояние выделенности объектов? Эта информация интересна только для двух представлений: редактора и списка объектов. Ни к модели, ни к вычислительному процессу, ни к другим видам она не имеет отношения.

Попробуем разобраться. Для начала напомню, вкратце, что такое паттер "Model-View-Controller", он же "Модель-Представление-Контроллер", он же MVC.

Паттерн MVC разделяет приложение на три части: обработка данных, вывод и ввод. В модели хранятся данные и реализуется их обработка. Данные в модели представлены в том виде, в котором их удобно обрабатывать (а вовсе не в том, в котором удобно отображать). Модель совершенно не зависит от представления данных в интерфейсе пользователя. Интерфейсом занимаются представления. Они считывают данные из модели, приводят в требуемый вид и отображают пользователю. Если данные нужно показывать разными способами, представлений будет несколько.

Каждое представление, по идее, имеет связанный с ним контроллер. Контроллер занимается обработкой ввода данных. Он принимает разнообразные события, поступающие от мышки и с клавиатуры, и транслирует их в команды для представления или модели. При этом контроллер вовсе не является "посредником" между моделью и представлением. Единственная функция контроллера - это обработка ввода данных. Представление может свободно обращаться к модели напрямую, контроллер для этого вовсе не требуется.

Поскольку представлений несколько, необходимо позаботиться о синхронизации изменений. Если контроллер одного представления изменяет модель, то все остальные представления должны быть поставлены об этом в известность. Т.е. в моделе необходим механизм подписки на рассылку уведомлений. Изменились данные, модель послала уведомление, все заинтересованные лица его получили - и обновились.

Вот, собственно, и вся архитектура. Осталось заметить, что контроллер нужен далеко не всегда. Разделение "контроллер-представление" нужно вводить только тогда, когда оно действительно требуется. Если их не разделять, то получается частный случай архитектуры MVC, называемый Document-View. Document - это модель, View - это представление + контроллер.

Возвращаемся к вопросу о хранении информации о состоянии выделенности объектов. Варианты:
  • Ввести дополнительное свойство у объекта - флаг "выделен"/"не выделен". Это простейшее решение, но тогда в модели мы будем хранить данные, которые связаны только с представлением - это плохо. А если таких данных нам будет требоваться со временем все больше и больше? И что делать, если у нас подобных представлений "с состоянием" будет несколько и каждому потребуются подобные флаги?
  • Хранить данные прямо в представлении. Хорошая идея, но в каком из двух? Предположим, мы храним информацию о состоянии в представлении A, а представление B знает об этом и берет ее из A. Сразу появляется проблема синхронизации. Если мы изменили состояние выделенности объекта в A, как об этом узнает B?
Мне кажется ответ на поверхности - нужно ввести вторую модель.

Другими словами, я предлагаю применить шаблон дважды. Представления A и B будут работать с двумя моделями. Первая модель глобальная, с ней работают все представления в программе. Вторая модель локальная, к ней имеют доступ только представления A и B. Эта локальная модель хранит состояние отображения данных в A и B и отвечает за синхронизацию при изменении этого состояния. Кроме того, локальная модель должна быть подписана на получение сообщений от глобальной модели. Если, например, объект будет уничтожен, то наша локальная модель об этом узнает и удалит информацию о его отображении. Появится новый объект - она добавит для него информацию по умолчанию.

В программе такая локальная модель может выглядеть как объект, который хранится в главном окне программы, передается в конструкторы представлений A и B и регистрируется, наравне со всеми представлениями, для получения уведомлений от глобальной модели.

Вот как будет выглядеть схема MVC с двумя моделями.Теперь осталось реализовать идею на практике и посмотреть, насколько удобно будет работать, и какие недостатки вылезут. Поживем - увидим.

P.s. Информацию по паттерну MVC можно найти в книжке
Мартин Фаулер "Архитектура корпоративных программных приложений", 2004 год. У Фаулера книжки интересные и очень полезные, но конкретно эту, лично мне, читать было довольно сложно. Бесконечные перекрестные ссылки на паттерны здорово запутывают. Далее, на сайте у Фаулера есть статья GUI Architectures, написанная примерно в том же стиле. Там рассказывается и про MVC, и про MVP. Но лично мне больше всего понравилось, как тема MVC изложена в книге, которую Фаулер рекомендует: Frank Buschmann "Pattern-oriented software architecture. A system of patterns. Volume 1", 2001. Написано и проще, и подробнее.

воскресенье, 15 февраля 2009 г.

Ловим исключения

Вот еще интересный пример, который встретился также в тесте по С++
struct BaseException {
virtual void Output() {
std::cout << "Base" << std::endl; }
};
struct DerivedException : public BaseException{
virtual void Output() {
std::cout << "Derived" << std::endl; }
};
void Test() {
try {
throw DerivedException();
} catch (BaseException& e) {
e.Output();
} catch (DerivedException& e) {
e.Output();
} catch (...) {
std::cout << "Unhandled exception" << std::endl;
}
};

Вопрос стандартный: какой catch сработает и что будет выведено на печать? Ответ: срабатывает первый catch, на печать будет выведено "Derived".

Теперь чуть-чуть изменим код

} catch (BaseException e) {
И на печать будет выведено уже "Base". Рассчитано, конечно, на невнимательность. Я, когда проходил тест, больше задумывался, попадет ли DerivedException в catch с BaseException. Оказалось, что попадет. А на тонкость с "&" я бы внимание вообще не обратил, если бы (так уж вышло) мне не встретилось практически подряд два "одинаковых" вопроса.

template Run(T process); Что бы это значило?

При прохождении теста по С++ столкнулся вот с такой конструкцией
template Run(T process); 
В тесте спрашивается, что именно эта конструкция может означать: нешаблонную функцию-член класса, определение шаблонной функции, объявление шаблонной функции, определение шаблонного класса, объявление шаблонного класса?

Поначалу решил - ошибка. Разве есть конструкции со словом template без скобочек? Но в памяти что-то смутно зашевелилось :) Полез в справочник по шаблонам (есть такая замечательная книжка - Дэвид Вандевурд, Николаи М. Джосаттис, "Шаблоны С++", 2003 год). И нашел такую конструкцию. Называется - директива явного инстанцирования (стр. 87). Сравните:
template int const& max(int const&, int const&);
Это - явное инстанцирование шаблона функции max() для int. Таким образом, "template Run(T process);" может означать следующее: явное инстанцирование шаблона функции Run, принимающего один параметр типа T и возвращающего int.

C неявным int, правда, есть проблема. Стандарт С++ не поддерживает default int. В частности, в Visual C++ 2005 при попытке откомпилировать объявления функции, в которой не указан возвращаемый тип, мы получаем ошибку C4430: "missing type specifier - int assumed. Note: C++ does not support default-int". В более ранних версиях компилятора такие конструкции были разрешены. Ошибку можно исключить с помощью pragma, поэтому следующий код компилируется на ура:

typedef int T;
template<class T1>
int Run(T1 process) {
return 1;}
#pragma warning(disable: 4430)
template Run(T process);
#pragma warning(default: 4430)
Осталось ответить на вопрос теста. Собственно, выбирать остается между вариантами 2 и 3. В справочнике написано следующее: "Директива явного инстанцирования содержит ключевое слово template, за которым следует объявление объекта, экземпляр которого необходимо сгенерировать, с полностью выполненными подстановками". Так что похоже правильный ответ на вопрос - 3. Это - объявление шаблонной функции. Правда, как видите, с небольшими оговорками.

пятница, 13 февраля 2009 г.

Функциональные адаптеры C++.

Любой программист С++ при написании кода стремится достичь, как минимум, трех целей: сделать код производительным, наглядным и максимально компактным. Активное использование алгоритмов STL здесь может существенно помочь. Если алгоритм выбран правильно, то в большинстве случаев он выполнит задачу быстрее, чем самописный код. Использование алгоритмов вместо обычных циклов повышает наглядность кода, поскольку алгоритмы в тексте программы гораздо более четко отражают намерения программиста, чем однотипные циклы for. Возможность "налету" генерировать объекты функций и передавать их в алгоритмы в качестве предикатов обеспечивает коду компактность.

С предикатами в C++ все не так просто. По большому счету - предикат, это функция, возвращающая тип bool. Поэтому в качестве предикатов в алгоритмы можно передавать указатели на обычные функции. Однако, объекты функций гораздо предпочтительнее. Причина проста - объекты функций обеспечивают более эффективный код. В книге Скотта Мейерса на этот случай приводится пример с функцией std::sort - использование объектов функций всегда работает быстрее, выигрыш в скорости может составлять от 50% до 160%. Объясняется все тривиально - при использовании объекта функции компилятор способен встроить передаваемую функцию в тело алгоритма, а при использовании обычной функции встраивания не производится.

Но с объектам функций тоже все не просто! Прежде всего, из-за огромного количества вариантов подобных объектов, которые встречаются на практике. Может потребоваться вызов обычной функции или функции-члена класса. Алгоритм может требовать бинарную или унарную функцию. Подходящая для наших целей функция может требовать передачи дополнительных параметров. Объект, которому принадлежит функция, может быть константым или не константым. Аргументы в функцию могут передаваться по значению или по ссылке. Функция может быть реализована в класее. Наконец, требуемую функциональность могут реализовывать ни одна, а несколько разных функций, так что потребуется как-то скомбинировать их вызовы в одном функциональном объекте.

Впрочем, разработчики STL были прекрасно осведомлены обо всех этих сложностях. В состав STL включен отдельный файл <functional>, содержащий массу функциональных адаптеров, позволяющих "налету" создавать подходящие объекты функций в самых разнообразных случаях. Дело за малым - осталось разобраться какие адаптеры когда применять.

Начнем с простого примера. Предположим, нам требуется найти в векторе объект, который будет удовлетворять заданному условию. Для простоты предположим, что вектор у нас содержит целые числа и нам нужно найти в нем двойку.

std::vector<int> t;
bool f(int Value) {
  return Value == 2;
}
std::vector<int>::iterator p =
  std::find_if(t.begin(), t.end(), f); //находим 2

тот же самый вызов алгоритма можно переписать вот так

std::vector<int>::iterator p =
  std::find_if(t.begin(), t.end(), std::ptr_fun(f));
Код работает одинаково. Но во втором случае в алгоритм передается уже не просто функция, а сгенерированный "налету" объект функции.

Использование объектов функции, помимо потенциального прироста производительности, предоставляет новые возможности. Предположим, мы хотим повысить гибкость функции поиска и передавать в f() искомое значение в качестве дополнительного параметра. Пожалуйста:

bool f2(int Value1, int Value2) {
  return Value1 == Value2;
}
int n = 10;
std::vector<int>::iterator p =
  std::find_if(t.begin(), t.end()
   , std::bind1st(std::ptr_fun(f2), n));
Теперь наша функция f2() принимает два значения. Но алгоритму find_id требуется функция, которая принимает только ОДИН параметр - элемент из списка. Поэтому, мы воспользовались функцией std::bind1st. Эта функция создает экземпляр функционального адаптера std::binder1st, который "заворачивает" вызов функции с двумя параметрами в функцию с одним параметром. В итоге, алгоритм вызывает правильную функцию с одним параметром. Вызов этой функции преобразуется адаптером std::binder1st в вызов f2, причем в качестве значения дополнительного параметра адаптер подставляет то значение, которое мы передали ему при вызове std::bind1st.

В STL два связывающих адаптера: std::binder1st и std::binder2nd. Первый подставляет переданное нами значение в качестве первого параметра функции, второй - в качестве второго. Поскольку оба этих адаптера представляют из себя шаблоны с несколькими параметрами, их обычно создают через вспомагательные функции std::bind1st и std::bind2st, которые позволяют не указывать типы параметров шаблона вручную.

Пусть нам теперь требуется решить обратную задачу - найти в векторе значение, НЕ РАВНОЕ заданному. Можем ли мы для ее решения обойтись все той же функцией f2? Элементарно:

std::vector::iterator p =
  std::find_if(t.begin(), t.end()
   , std::not1(std::bind1st(std::ptr_fun(f2), n)));

Бинарные функции

До сих пор мы работали с унарными функциями. Между тем, многие алгоритмы STL требуют бинарных функций. Пусть нам требуется отсортировать тот же целочисленный вектор. Сортируем:

bool less(int lhs, int rhs) {
return lhs < rhs;
}
//используем собственную функцию сравнения
std::sort(t.begin(), t.end(), std::ptr_fun(less));

//используем стандартные функции меньше, больше
std::sort(t.begin(), t.end(), std::less<int>() );
std::sort(t.begin(), t.end(), std::greater<int>() );

//используем стандартные функции больше/меньше или равно
std::sort(t.begin(), t.end(), std::greater_equal<int>() );
std::sort(t.begin(), t.end(), std::less_equal<int>() );

//Комбинируем адаптеры: НЕ "больше или равно"
std::sort(t.begin(), t.end()
  , std::not2(std::greater_equal<int>()));
Другой пример. Переместим в начало списка все элементы удовлетворяющие заданному условию:

//все элементы равные двум
std::partition(t.begin(), t.end()
  , std::bind2nd(std::equal_to<int>(), 2));
//все элементы НЕ равные двум
std::partition(t.begin(), t.end()
  , std::bind2nd(std::not2(std::equal_to<int>()), 2));
Логические операции "меньше", "больше", "не", "больше или равно" и т.д. используются настолько часто, что шаблоны для определения подходящих функциональных объектов уже включены в STL. Та же история со стандартными арифметическими и логическими операциями.

std::vector<int> a, b;

//n = N % a1 % a2 % a3...
n = std::accumulate(a.begin(), a.end(), N
  , std::modulus<int>());

//n = n0 - (a1*b1) - (a2*b2) - (a3*b3) - ...
n = std::inner_product(a.begin(), a.end(), b.begin(), n0
  , std::minus<int>()
  , std::multiplies<int>());

//n = n0 + (a1/b1) + (a2/b2) + (a3/b3) + ...
n = std::inner_product(a.begin(), a.end(), b.begin(), n0
  , std::plus<int>()
  , std::divides<int>());

//n = N || a1 || a2 || a3...
n = std::accumulate(a.begin(), a.end(), N
  , std::logical_or<int>());

//n = N && a1 && a2 && a3...
n = std::accumulate(a.begin(), a.end(), N
  , std::logical_and<int>());

Переходим к объектам

С преобразованием обычных функций в функциональные объекты все ясно. А как быть, если функции являются членами класса?
Предположим у нас имеется вектор объектов

struct x {
  bool f() {
    return true;}
};
std::vector<x> v;
Нам требуется найти объект, у которого функция f вернет "true". Как это сделать?

Вместо std::ptr_fun нужно использовать std::mem_fun_ref:

std::vector<x>::iterator p = std::find_if(v.begin(), v.end()
  , std::mem_fun_ref(&x::f));
Если в векторе хранятся указатели на объекты, то нужен адаптер std::mem_fun:

std::vector<x*> w;
std::vector<x*>::iterator p = std::find_if(w.begin(), w.end()
  ,std::mem_fun(&x::f1));

Собственные объекты функций

Если вы определяете собственные функциональные объекты, то их желательно наследовать от std::binary_function или std::unary_function. Эти функции добавят в определение ваших классов необходимые объявления типов и, в результате, стандартные функциональные адаптеры, типа std::bind1st, std::not1 и т.п. смогут с ними работать точно так же, как со стандартными.

Чего не хватает для счастья

Очень много. При использовании стандартных адаптеров можно налететь на проблему "ссылка на ссылку". Нужно не забывать указывать std::ptr_fun. Нет стандартных функций для создания композиций функциональных адаптеров (как, например, найти в векторе 2 ИЛИ 3?). Адаптеры std::bindXXX не пригодны для работы с функциями, у которых больше двух параметров. Наконец, в нашем распоряжении нет функторов!

Все проблемы решаются с помощью boost. Библиотека реализует улучшенный набор функциональных адаптеров, которые идентичны по синтаксису стандартным и, одновременно, свободны от проблемы "ссылки на ссылку" и не требуют явного указания std::ptr_fun. Но главная прелесть не в этом. В boost есть замечательные библиотеки: boost::bind, boost::function, boost::mem_fn и boost::lambda, которые позволяют снять указанные выше проблемы как класс и на порядок упростить создание функциональных объектов. Библиотеки boost::bind, boost::function, boost::mem_fn вошли в TR1 и теперь являются стандартными. Однако обсуждение этих библиотек - тема слишком обширная, так что отложим ее до другого раза.

среда, 4 февраля 2009 г.

Инкремент даты в XSLT 1.0.

Пусть в XSLT преобразовании требуется к заданной дате прибавить заданное количество дней. Другими словами, у насть есть три числа - год, месяц, день. Нужно прибавить к ним произвольное количество дней и получить новые год, месяц, день... Вопрос - как это сделать?

В XSLT 1.0 встроенных средств нет. В XSLT 2.0, насколько я понимаю,
можно выкрутится встроенными средствами, но мне требуется XSLT 1.0.

Можно подключить JavaScript и написать скрипт для инкремента даты. Однако, как оказалось, можно обойтись и без JavaScript'а.

Вот здесь приведены исходные коды двух функций, позволяющих преобразовать дату в количество дней, прошедших с 31-декабря 0-ого года, и обратно. Если у нас в распоряжении есть такие функции, то выполнить инкремент даты не составит проблем:
int date; //количество дней
itod(&date, 2009, 2, 3); //получаем количество дней для даты 3.02.2009

int y, m, d;
dtoi(date+1, &y, &m, &d); //получаем год, месяц, число для 4.02.2009.


Приложение на С выглядит так:
void dtoi (int date, int &year, int &month, int &day) {
// the date specified as the number of days elapsed since 
//31 Dec of year 0. So 1 Jan 0001 is 1.
  int Z = date + 306;
  int H = 100*Z - 25;
  int A = H/3652425;
  int B = A - A/4;
  year = (100*B + H) / 36525;
  int C = B + Z - 365*year - year / 4;
  month = (5*C + 456) / 153;
  day = C - (153*month - 457) / 5;
  if (month > 12) { year += 1; month -= 12; }
}

int itod (int y, int m, int d) {
  if (m < 3) { m += 12; y -= 1; }
  return d + (153*m - 457) / 5 + 365*y + y/4 - y/100 + y/400 - 306;
}

int _tmain(int argc, _TCHAR* argv[])
{
  int date = itod(2009, 12, 31);
  int y, m, d;
  dtoi(date+1, y, m, d);
}



Перевел код на XSLT. Получилось следующее:
<xsl:template name="date_to_int">
  <xsl:param name="y" />
  <xsl:param name="m" />
  <xsl:param name="d" />
  <xsl:choose>
    <xsl:when test="number($m) < 3">
    <xsl:call-template name="date_to_int_auxilary">
        <xsl:with-param name="y" select="number($y)-1"/>
        <xsl:with-param name="m" select="number($m)+12"/>
        <xsl:with-param name="d" select="$d"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="date_to_int_auxilary">
        <xsl:with-param name="y" select="$y"/>
        <xsl:with-param name="m" select="$m"/>
        <xsl:with-param name="d" select="$d"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="date_to_int_auxilary">
  <xsl:param name="y" />
  <xsl:param name="m" />
  <xsl:param name="d" />

  <xsl:value-of select="number($d) + floor((153.0*number($m) - 457.0) div 5.0) + 365*number($y) + floor(number($y) div 4 ) - floor(number($y) div 100) + floor(number($y) div 400) - 306"/>
</xsl:template>


<!-- Convert number of days to date (year, month, day - required part of the date is selected by 'required_value' parameter -->
<xsl:template name="int_to_date">
  <xsl:param name="g"/>
  <xsl:param name="required_value" select="'year'"/> <!-- 'year' or 'month' or 'day'-->

  
  <xsl:variable name="Z" select="floor(number($g) + 306)"/>
  <xsl:variable name="H" select="floor(100*number($Z) - 25)"/>
  <xsl:variable name="A" select="floor(number($H) div 3652425)"/>
  <xsl:variable name="B" select="floor(number($A) - number($A) div 4)"/>
  <xsl:variable name="year" select="floor((number($B)*100 + number($H)) div 36525)"/>
  <xsl:variable name="C" select="floor(-365.0*number($year) + number($B) + number($Z)- floor(number($year) div 4))"/>
  <xsl:variable name="month" select="floor((number($C)*5 + 456) div 153)"/>
  <xsl:variable name="day" select="number($C) - floor(( floor(153*number($month)) - 457) div 5)"/>

  <xsl:choose>
    <xsl:when test="number($month) > 12">
  <xsl:call-template name="int_to_date_helper">  
    <xsl:with-param name="year" select="number($year)+1"/>
    <xsl:with-param name="month" select="number($month)-12"/>
    <xsl:with-param name="day" select="number($day)"/>
    <xsl:with-param name="required_value" select="$required_value"/>
  </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
  <xsl:call-template name="int_to_date_helper">  
    <xsl:with-param name="year" select="number($year)"/>
    <xsl:with-param name="month" select="number($month)"/>
    <xsl:with-param name="day" select="number($day)"/>
    <xsl:with-param name="required_value" select="$required_value"/>
  </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>
  
<xsl:template name="int_to_date_helper">
  <xsl:param name="year"/>
  <xsl:param name="month"/>
  <xsl:param name="day"/>
  <xsl:param name="required_value"/> <!-- 'year' or 'month' or 'day'-->

  <xsl:choose>
    <xsl:when test="$required_value='year'">
       <xsl:value-of select="round($year)"/>
    </xsl:when>
    <xsl:when test="$required_value='month'">
       <xsl:value-of select="round($month)"/>
    </xsl:when>
    <xsl:when test="$required_value='day'">
       <xsl:value-of select="round($day)"/>
    </xsl:when>
  </xsl:choose>
</xsl:template>


Функция int_to_date соответствует C-шной функции itod, функция date_to_int - функции dtoi. Функция date_to_int принимает два параметра - количество дней и название требуемой части даты - 'year', 'month' или 'day' (передавать прямо в апострофах). Таким образом, если вы хотите напечатать дату в виде "Год.Месяц.День", то функцию date_to_int придется вызвать трижды - один раз, чтобы получить год, второй - месяц, третий - день. Впрочем, совсем несложно изменить шаблон так, чтобы функция возвращала дату целиком, в виде строки в подходящем формате.

Полный текст тестового преобразования с примером вызова можно взять здесь.

P.s. самой заковыристой частью трансляции кода оказалось правильное округление чисел в алгоритме. Дело в том, что в алгоритме используются целые числа и во всех расчетах нужно использовать именно целые, а не вещественные числа. Отсюда многочисленные floor в преобразовании.

понедельник, 2 февраля 2009 г.

Интеллект карта для C# 2.0

Тем, кто готовится проходить тестирование по какому-либо языку программирования, может оказаться крайне полезной возможность посмотреть на него "сверху" - охватить взглядом его возможности, тонкости и особенности. Можно легко определить слабые места в своих знаниях и, даже, обнаружить особенности языка, о которых вообще не имеешь представления.

Такая возможность есть для C#. На сайте FreeMind опубликована интеллект-карта для C# 2.0. Для просмотра ничего не требуется кроме браузера с установленным флеш-плеером. Там же есть аналогичная карта для Python 2.5.



Если вы еще не сталкивались с интеллект-картами - обратите на них внимание. Крайне удобный на практике инструмент для анализа информации, планирования, конспектирования и т.д. Вот здесь я писал про них поподробнее.

воскресенье, 1 февраля 2009 г.

Что выбрать: std::map, std::vector или boost::unordered_map?

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

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

"Стандартных" контейнера, которые подойдут в данном случае, как минимум три: std::map, сортированный std::vector и boost::unordered_map, который уже вошел в TR1. Вопрос, какой из них лучше подойдет для данной задачи?

Чтобы определить, какой вариант лучше, написал небольшое тестовое приложение. В нем содержатся три класса, реализующие варианты подсчета токенов с помощью разных ассоциативных контейнеров, два класса для генерации случайной последовательности токенов (int и string) и тестовая функций, выполняющая тестовый перебор последовательности несколько раз и определяющая среднее время, затрачиваемое на его выполнение. У программы два параметра: максимальное количество токенов и длина последовательности. Программа тестировалась на VS2005 с родной реализацией STL.


Результаты тестирования для последовательности из 1e5 целочисленных токенов


Кол-во токенов:101e21e31e41e5
std::map, int373531464716
std::vector, int403731511446
boost::unordered_map, int35312340427


Результаты тестирования для последовательности из 1e5 строковых токенов


Кол-во токенов:101e21e31e41e5
std::map, string556270493391
std::vector, string7174871071-
boost::unordered_map, string47463893437


Вектор работает плохо, но это и следовало ожидать. boost::unorderd_map демонстрирует лучшую производительность, чем std::map, однако если по началу их результаты примерно одинаковы, то потом следует резкий перекос (производительность boost::unorderd_map в 5-10 раз лучше), а затем маятник качается в обратную сторону, так что производительность std::map становится даже лучше. Посмотрим, как ведет себя производительность при других соотношениях длины последовательноси и количества токенов.

Кол-во токенов/длина последовательности:1e5/1e61e5/1e71e6/1e7
std::map, int625503113875
boost::unorderd_map, int98425786062
std::map, string17961534328875
boost::unorderd_map, string132863751125


Выводы


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

boost::unordered_map работает быстрее std::map при условии, что длина последовательности гораздо больше количества токенов, т.е. каждый токен в среднем входит в последовательность множество раз.

Если же длина последовательности и количество токенов примерно совпадают (т.е. все токены входят в последовательность один-несколько раз ), то std::map может оказаться быстрее, но в редких случаях..

Итог - для моей задачи boost::unordered_map предпочтителен. И как хорошо, что это теперь стандартный контейнер :)

Исходные коды тестового проекта можно взять здесь.

понедельник, 19 января 2009 г.

Перевод статьи "Поиск ассоциативных правил при интеллектуальном анализе данных"

Обнаружил статью Раймонда Лэма "Discovering Association Rules in Data Mining", посвященную поиску ассоциативных правил. Подробный и широкий обзор темы - что такое ассоциативные правила, в чем суть проблемы, какие типы правил существуют (булевые, обобщенные, количественные, временные), алгоритм Apriori и его предшественники, усовершенствованные версии алгоритма Apriori (AprioriTid, AprioriHybrid), DHP, SSM, распараллеливание и масштабируемость Apriori, не-apriori алгоритмы (FP-деревья), новые подходы генерации правил, APACS2, Evolutionary Algorithm, негативные ассоциации...

На мой взгляд, прекрасная обзорная статья. Автору респект за проделанную работу - такие статьи редко встречаются и бывают крайне полезны. Лично для меня особенно любопытной оказалась информация об алгоритме APACS2, позволяющем генерировать ассоциативные правила не указывая confidence, а так же сведения о распараллеленных и масштабируемых версиях алгоритма Apriori - Intelligent Data Distribution и Hybrid Distribution.

Статью перевел на русский язык и выложил перевод.

среда, 14 января 2009 г.

Синглетон Мейерса в смешанных сборках CLI/C++

В текущем проекте, разрабатываемым под Microsoft .Net 2.0, столкнулся со следующей ошибкой:

The exception unknown software exception (0xc00200001) occured in the application at location 0x7c812a5b.

Ошибка возникает периодически при завершении работы приложения и регулярно при вызове некоторых юнит-тестов.

Стал разбираться в чем дело. Проект включает две сборки: интерфейсная часть написана на C#, реализация движка - на CLI/C++.  Поискав ошибку в интернете обнаружил, что подобные ошибки встречаются в случае наличия  в C++-ной сборке статических объектов с нетривиальными деструкторами. Как раз мой случай - один из классов в моей сборке включал реализацию синглетона Мейерса. 

class Kernel { 
     Kernel(void);
     ~Kernel(void) {  /*удаление unmanaged объектов*/  }
public:
      static Kernel GetInstance() {
         static Kernel k;  //Синглетон Мейерса
         return k;
     } 
  ...
}

Никаких вызовов управляемого кода в моем статическом объекте вообще нет - Kernel, это чистый native-класс.

Поскольку  без синглетонов при разработке приложений обойтись трудно, решил разобраться, как можно реализовать синглетон Мейерса в сборке, написанной на CLI/C++, в которой управляемый и неуправляемый коды смешаны.  

Написал простой тестовый проект, включающий две сборки. Первая сборка Test написана на CLI/С++ и содержит управляемый класс Managed и неуправляемый Unmanaged, причем неуправляемый вызывается из управляемого. Вторая сборка написана на C# и вызывает класс Managed из первой. Управляемая сборка включает тест NUnit, через который производится запуск приложения (можно, конечно, запускать приложение и напрямую, но через NUnit проблем вылазит больше).

Код на C# тривиален:
class Class1 {
       public void Test1() {
           Test.ManagedClass m = new Test.ManagedClass();
              m.Dispose(); 
        } 
}

class Program {
/*Функция Main для запуска приложения напрямую, без NUnit*/
static void Main(string[] args)  {
           Class1 c = new Class1();
           for (int i = 0; i < 10; ++i) c.Test1();
}
}

[TestFixture] 
public class test {
[Test] /*Тест NUnit для запуска приложения*/
public void Execute() {
Class1 c = new Class1();
for (int i = 0; i < 10; ++i) c.Test1();
}
}
Код на CLI/C++ взятый за основу:
class Unmanaged1 {
    int *m_p;
public:
    Unmanaged1(void) : m_p(new int()) {}
    ~Unmanaged1(void) {   delete m_p; /*non trivial destructor*/   }
   static Unmanaged1& GetInstanceRef() {
   static Unmanaged1 u;
      return u;
    } 
    int* GetPtr() { return m_p; }
};

Вариант 1. Использование #pragma managed

Первое, что приходит в голову - это явно указать, что класс Unmanaged является неуправляемым.
#pragma managed(push, off)
class Unmanaged {
......
};
#pragma managed(pop)
Не работает. При завершении работы приложения возникает исключение 0xc0020001: The string binding is invalid. Вызов деструктора класса производится при выгрузке DLL, когда деинициализация CLR уже фактически проведена. Если в деструкторе вызывается управляемый код, то возникает ошибка. Явно у меня управляемый код нигде не вызвается. Есть какой-то неявный вызов (?)

Вариант 2a. Разделение кода на cpp и h-файлы

Вынесем функцию GetInstanceRef() в cpp-файл:
/*unmanaged.cpp*/
Unmanaged& Unmanaged::GetInstanceRef() {
static Unmanaged2 u;
return u;
}
Тоже не работает. При вызове возникает ошибка: This function must be called in the default domain.

Вариант 2b. Вынос статической переменной за пределы класса

Вынесем переменную u из GetInstanceRef.
/*unmanaged.cpp*/
static Unmanaged2 u;
Unmanaged2& Unmanaged2::GetInstanceRef() {
    return u;
}
Такой вариант срабатывает.Почему он срабатывает объяснено в книге Expert Visual C++/CLI: .NET for Visual C++ Programmers. Дело в том, что инициализация статических и глобальных переменных, объявленных в cpp-модулях, компилируемых с ключем /clr, производится в специальном конструкторе модуля. Конструктор модуля, по сути, представляет из себя инициализатор сборки и является первой управляемой функцией, вызываемой CLR. Соответственно, деинициализация переменных производится не при выгрузке DLL, а непосредственно перед завершением работы CLR. В итоге - все работает. Кстати, порядок инициализации переменных в смешанной сборке таков. Вначале производится инициализация всех глобальных и статических объектов, объявленных в cpp-модулях, компилируемых без ключа /clr. Затем производится инициализация всех глобальных и статических объектов, объявленных в cpp-модулях, компилируемых с ключем /clr.

Вариант 2c. Инициализация синглетона из неуправляемого кода.

Возможно проблема варианта 2а в том, что синглетон инициализируется вызовом управляемого кода. А что если его инициализировать заранее?
/*unmanaged.cpp*/
Unmanaged2& dummy = Unmanaged2::GetInstanceRef();
Unmanaged2& Unmanaged2::GetInstanceRef() {
static Unmanaged2 u; 
    return u;
}
Такой код тоже срабатывает.

Вариант 3. Компиляция без ключа /clr

В моем случае класс Unmanaged не вызывает никакого управляемого кода. Теоретически, статические переменные этого класса можно было бы инициализировать при загрузке DLL, уничтожать при выгрузке DLL и проблем быть не должно. Что если этот класс откомпилировать без ключа /clr? В книге Хиджа приведен детальный алгоритм, как сделать обычный неуправляемый проект DLL (который будет компилироваться без ключа /clr) и включить в него несколько исходных файлов с управляемым кодом (которые будут компилироваться с ключем /clr). Для этого нужно создать второй precompiled header - stdafx_clr.h, и задать отдельные свойства компиляции (с ключом /clr) для файла stdafx_clr.cpp и всех cpp-файлов, содержащих управляемый код. Попробовал. Вариант 2a заработал. Вариант 1 не работает и в этом случае. Если вызывать текстовый класс через unit test, то получаем ошибку Managed Debugging Assistant 'LoaderLock' has detected a problem in 'C:\Program Files\NUnit 2.4.8\bin\nunit.exe'. Additional Information: Attempting managed execution inside OS Loader lock. Do not attempt to run managed code inside a DllMain or image initialization function since doing so can cause the application to hang. Если вызывать приложение напрямую, то ошибка "0xC0020001: The string binding is invalid.".

Итоги

Итак, как можно реализовать синглетон Мейерса в неуправляемом коде в смешанной сборке? Реализовать синглетон в заголовочном файле C++ (вариант 1) не удается - такой синглетон нельзя вызвать из управляемого кода без появления ошибки при закрытии приложения. Если вынести реализацию в cpp-шный файл (вариант 2), то ситуация меняется. В случае, когда весь проект компилируется с ключом /clr, статическую переменную необходимо вынести за пределы класса и объявить глобально в теле cpp-файла (вариант 2b). Альтернативный вариант - можно предварительно инициализировать синглетон из неуправялемого кода, до его первого вызова из управляемого кода (вариант 2c). Если проект компилируется без ключа /clr, а ключ /clr включен лишь для файлов с управляемым кодом, то статическую переменную можно оставлять локальной (вариант 2a). Исходники тестового проекта можно взять здесь. Тестирование велось под VS 2005. Update: недавно налетел на столь же неприятную проблему с синглетонами на Android. Реализация синглетона - вещь непростая..

вторник, 13 января 2009 г.

посылки из-за границы

Последние несколько лет наловчился покупать товары в интернет-магазине cabelas.com.  В основном - одежду. Качество отличное, цены существенно меньше наших,  даже с учетом доставки, да еще и скидки порой можно отловить хорошие. С доставкой проблем ни разу не было - посылка доставляется курьерской службой UPS, курьер привозит посылку по любому адресу. Вернее - проблем не было раньше. А недавно неожиданно появились.

В декабре  заказал себе куртку. Обычно посылку привозят за пять дней, а тут - проходит неделя, никто не звонит. Захожу на сайт, смотрю информацию по заказу. В Tracking Information написано:

THE VALUE OF THE COMMODITY EXCEEDS THE LIMIT
> ALLOWED BY UPS / RETURNED TO SHIPPER MOSCOW RU

Т.е. посылка доехала до Москвы - и поехала обратно.  Я очень удивился - стоимость посылки была ~$200, т.е. значительно меньше разрешенных беспошленных 10 тыс руб. Несколько месяцев назад я получал с cabelas посылку примерно с такой же стоимостью и все было в порядке. 

В итоге, перерыв интернет, я разобрался в чем дело.  Оказывается, беспошлинный лимит при пересылке составляет 10 тысяч рублей только для организаций, "обладающих правом работы с международными почтовыми отправлениями".  Т.е. для почты.  А для курьерских служб беспошлинный лимит составляет всего лишь 5 тысяч рублей.   Если лимит превышен, то посылку автоматически отправляют назад. И меня, естественно, никто в уведомление не ставит.  Т. к. курс доллара в декабре значительно вырос, я со своими $200 выскочил за этот лимит.  В итоге - с Cabelas мне вернули деньги за заказ, но не полностью, а удержав стоимость курьерских расходов.

Правила отправки посылок в Россию можно посмотреть на сайте таможни.  Плюс есть хорошая статья  на эту тему .


Update: с 1 июля 2010 г правила изменились. Теперь в течении одного месяца на один адрес можно получать беспошлино посылки суммарной стоимостью до 1000 евро и суммарным весом до 31 кг. Правила для почты и курьерских служб практически одинаковые.

Update апрель,2011: Похоже, курьерские службы устанавливают собственные лимиты на стоимость посылок, например I have just received email from our FedEx account representative, that limit to Russia has been increased to $260.. Узнал случайно - сделал очередной заказ на cabelas, а мне говорят - в Россию не более $250...