вторник, 18 декабря 2007 г.

Системные иконки

Продолжаю читать блог The Moth, где постоянно появляется что-нибудь интересное. Например, иконки, которые используются в MessageBox можно не рисовать самому, т.к. они находятся в классе System.Drawing.SystemIcons. В частности, там есть:
  • Application
  • Asterisk
  • Error
  • Exclamation
  • Hand
  • Information
  • Question
  • Shield
  • Warning
  • WinLogo
P.S.: Теперь бы еще узнать где во фреймворке можно достать иконки для папок и разных типов файлов...

вторник, 11 декабря 2007 г.

Без слов...

среда, 5 декабря 2007 г.

Юмор

читаем и плачем :)

пятница, 30 ноября 2007 г.

Свершилось! Parallel Extensions to the .NET FX CTP

Мы ждали, ждали и наконец - дождались :) Доступен для скачивания ParallelFX CTP Кратко о том, что это такое, можно почитать здесь.

P.S.: радость была недолгой... Для установки требуется релиз .Net Framework 3.5 или выше. Естественно - релиз не ставится рядом с беткой. Естественно - без бетки не работает бетка студии 2008 (ну и бетки экспресс-версий - тоже).

Так что кому интересно попробавать эту штуку - придется качать релиз студии (3,9 Гб) или же ее экспресс версии (800 Мб)

Еще немного о сериализации

Читая новые rss-ки увидел вот этот пост.

А привлек мое внимание вот этот абзац:

Среда сериализации присваивает каждому классу, поддерживающему сериализацию номер версии, под именем serialVersionUID. Этот номер используется при восстановлении состояния объекта, чтобы убедиться, что классы отправителя и получателя сериализованного объекта совместимы между собой. Если класс получателя имеет отличный от отправителя serialVersionUID, тогда десереилазация завершится исключением InvalidClassException. Поддерживающий сериализацию класс может явно объявить собственный serialVersionUID, объявив поле с именем «serialVersionUID», которое должно быть длинным целым с модификаторами static и final.

Должен сказать довольно удобная приблуда. Без нее API пришлось бы использовать номер версии скомпилированной сборки, который может меняться при каждой компиляции, даже если определение класса не изменялось. Но при помощи serialVersionUID разработчики могут сами решать, одинаковы ли версии их классов или нет.


Интересно, почему разработчики .Net Framework'a не реализовали подобную функциональность (да похоже и не собираются)? Фактически, предложенный способ управления сериализацией находится в промежутке между xml-сериализацией и soap (когда вместе с данными еще и хранится информация о версии сборки, в которой находится описание сериализуемого класса).

среда, 28 ноября 2007 г.

Не все DateTime одинаково полезны

Маленькое предисловие

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




Есть у меня одна программа, предназначенная для поиска дубликатов файлов. Для сравнения файлов используется алгоритм рассчета Tiger Tree Hash. Рассчеты ТТН ведутся только для первых 5 Мб с начала файла, что позволяет резко сократить время работы программы (Естественно, что при сравнении учитывается не только ТТН).
Основная идея программы в том, чтобы запоминать уже рассчитанные ТТН. И в дальнейшем сравнивать ТТН новых файлов с запомненными - причем старый файл уже может и не существовать на диске.
Поскольку программа писалась на коленке, для, так сказать, "внутреннего использования" - все данные сохранялись в XML (о том, как сериализовать сложные коллекции - типа Dictionary и ей подобных - я писал ранее). И всё было бы ничего, да вот только сейчас xml с данными весит уже 70 Мб и собирается расти дальше :) Ну, в принципе, что-то такое я и ожидал... Так что было принято волевое решение переписать программу так, чтобы она использовала базу данных как хранилище.

Сказано - сделано.

Как база данных был выбран Access 2007 - т.к. он у меня уже установлен, а со всеми остальными базами данных надо разбираться (вообщем, лень :) )
Открываю Access, рисую таблицу вида:

код
ТТНstring
размер файлаint
полный путь к файлуstring
дата созданияDateTime


Вообщем, ничего сложного. Используя мастера студии 2008 создаю DataSetTableAdapter, добавляю в него два запроса - Select и Insert.

Тестирую:

FileInfo1TableAdapter fi=new FileInfo1TableAdapter();
string TTH="aaa";
int Length=10;
string path="filename";
DateTime date=new DateTime(2007, 11, 27, 9, 20, 10);

fi.InsertQuery(TTH, Length, path, date);
Работает! Ура :)

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

Потратив полчаса времени, я все-таки выяснил, что причина ошибки кроется в параметре date.

А теперь внимание! Значение date - это дата и время создания файла. Время создания дается с точностью до миллисекунд. При тестировании я использовал значение времени с точностью до секунд (напоминаю: тестовый пример - работал). Так вот, оказывается, что если вызывать, например, вот такой конструктор DateTime(2007, 11, 27, 9 /*часы*/, 20 /*минуты*/, 10 /*секунды*/, 10 /*миллисекунды*/) - мы гарантированно получаем ошибку не соответствия типов при попытке поместить значение DateTime в базу!

P.S.: вывод - если нечто выглядит как DateTime, ведет себя как DateTime, то это не значит что данное нечто - DateTime :)

понедельник, 26 ноября 2007 г.

Vista Glass для формы

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

Сначала добавляем в проект вот этот класс:


using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Drawing;

namespace VistaApi
{
    /// Class for enabling glass transparent mode in area
    /// You must set Color = Black in control!
    public class GlassApi
    {
        [DllImport("dwmapi.dll")]
        static extern void DwmExtendFrameIntoClientArea(System.IntPtr hWnd, ref Margins pMargins);

        [DllImport("dwmapi.dll")]
        static extern void DwmIsCompositionEnabled(ref bool isEnabled);

        struct Margins
        {
            public int Left, Right, Top, Bottom;
        }

        ///  Glass Window Handle 
        IntPtr Handle;

        /// Constructor
        /// Handle of window with glass effect
        public GlassApi(IntPtr Handle)
        {
            this.Handle = Handle;
        }

        /// Glass rectangle
        Margins margins = new Margins();

        public void SetGlassArea(Rectangle r)
        {
            //-1 because strange bug: on left corner is 1-pixel black square
            margins.Left = r.Left-1;
            margins.Top = r.Top-1;
            margins.Right = r.Right;
            margins.Bottom = r.Bottom;
            DwmExtendFrameIntoClientArea(Handle, ref margins);
        }


        bool glassEnabledCalled = false;
        private bool GetIsGlassEnabled()
        {
            glassEnabledCalled = true;
            //Checking if OS is >= Vista
            if (Environment.OSVersion.Version.Major < 6)
                return false;

            //Check if DWM is enabled
            bool isGlassSupported = false;
            DwmIsCompositionEnabled(ref isGlassSupported);
            return isGlassSupported;
        }

        bool _IsGlassEnabled=false;

        /// returns True if glass can be used
        public bool IsGlassEnabled
        {
            get
            {
                if (!glassEnabledCalled)
                    _IsGlassEnabled = GetIsGlassEnabled();
                return _IsGlassEnabled;
            }
        }
    }
}


Использование его очень просто:
1. В конструкторе передаем хэндл нашего окна.
2. Для того, чтобы узнать - можно ли использовать вистовскую прозрачность - используем свойство класса IsGlassEnabled.
3. Чтобы сделать форму прозрачной необходимо залить ее SolidBrush кистью, причем используя только основные семь цветов (белый цвет к ним не относится!). В зависимости от использованного цвета заливки будет меняться цвет полупрозрачности.

Следствие №1: все контролы, залитые основным цветом будут также полупрозрачными. Это так же касается всех надписей (по умолчанию они черного цвета).
Следствие №2: если цвет формы не менять (оставить цвет Control), а у контролов на форме - менять, то контролы будут полупрозрачными, а форма - нет.

Приключения. Или как перенести Vista на другой раздел

Сидел я, значит, никого не трогал и тут - бац! Windows Server 2008 RC0 вежливо сообщает мне, что мой 160 Гб диск помирать собирается... А ведь на нем - система! А ведь на нем - все мои проекты!! Вообщем - жуть :(
Пришлось срочно решать проблему - как запихнуть 160 гигов в 20 (именно столько у меня было свободно на втором винчестере). К счастью - на работе обнаружился свободный (новенький!) 250 гиговый винчестер...

Ну да это всё присказка. А сказка - дальше.

Итак, имеем проблему: на диске С: (второй раздел) стоит Windows Server 2008 RC0 (почти Vista :) ). На первом разделе - загрузчик. Необходимо перетащить систему на третий диск (40 Гигов, чистый).

Этап №1. Перенос системы на другой диск.

Я - существо ленивое :) Кроме того - в момент совершения данных действий физически я аходился на работе и управлял системой удаленно - через удаленный рабочий стол. Поэтому естественным для меня выбором было попробовать использовать стандартные средства новой ОС.
Первое - необходимо установить Windows Server Backup. Делается это в оснастке Server Manager в разделе Features.
Второе - собственно сам процесс бекапа описывать я думаю не имеет смысла - там три кнопки, кому надо - разберетесь (кому лень - всегда можно сделать копию из под другой ОС).
Третье - восстанавливаем бекап на другой диск. Здесь вы наверняка столкнетесь со следующей проблемой: нельзя восстановить бекап раздела, если раздел, на который мы восстанавливаем бекап, имеет другой размер. В этом случае можно произвести восстановление по файлам.

Примечание: самое удивительное, что после восстановления бекапа мало того, что все права на файлы сохранились (что логично), но сохранились так же и сжатие у файлов, и папки-ссылки на другие папки. Причем ссылки даже работали! Чудеса :)

Этап 2. Учим Vista загружаться с нового диска.

На этом этапе я потерял примерно 3 часа времени. Очень хотелось поотрывать руки разработчикам :)

Первое - записываем загрузчик на новый диск. Поиск в интернете говорит, что можно это сделать командой "Bootsect.exe /NT60 All". Упс! Нет такого файла на системном разделе. Еще немного поисков... Ага! Этот файл доступен, если загрузиться с CD-диска в режиме восстановления. Вставляем диск, грузимся, вызываем консоль, отдаем команду. Перегружаемся.

Упс... Не загружается.

Итак. Как мы все знаем, в Windows XP управление загрузками ОС с разных разделов осуществляется через файл boot.ini. В новой ОС - новый способ описания откуда грузить операционку. Вся информация теперь хранится в папке Boot куда и обращается наш загрузчик.
Главная проблема, с которой я столкнулся в том - что теперь используются не номера дисков/разделов, а уникальные идентифигаторы (GUID'ы). Естественно, что идентификатор нового диска я не знаю.
Вот тут и началось огромное количество приключений в попытках прописать правильную информацию :(
Краткий итог - все без исключения существующие на данный момент утилиты работают только с системным разделом, с которого была произведена загрузка. Естественно, что поправить я ничего не мог, потому что не мог загрузиться с нужного раздела. Получился этакий замкнутый круг.
Очередные поиски в интернете - ага! Необходимо загрузиться с CD-диска и в режиме восстановления и выбрать опцию "Исправление загрузочной информации".
Вставляю диск с сервером 2008 и... Щаз! Нет такого раздела!!
Потратив нное количество времени нахожу диск с Vista и загружаюсь с него. Ага! Есть! Жмем ссылку. Перегружаемся.

Этап 3. Учим ОС запускаться с нужного диска.

Грузимся. Логинимся. Ждем... И ждем... И ждем...
И тут понимаем - что-то не то :)
Итак: диск, с которого я загружался у меня был подмонтирован под буквой U:. Система же хотела загружаться с диска С: (который уже был физически отключен к тому времени).
Вспоминаю бурную молодость, и то, как поднимал еще Windows 2000 в аналогичной ситуации :) (Правда в тот раз я не мог залогиниться в систему - пришлось ее ломать через удаленный реестр).
Проверяем: есть возможность вызвать Диспетчер Задач (если вдруг не получается - загружаемся в безопасном режиме).
Запускаем Regedit. HKLM\System\MountedDevices.
Ага! Вот они. Меняем названия разделов \DosDevices\U: на \DosDevices\C: Перегружаемся.


Собственно, на этом и все... Система живет уже больше недели. Проблем не обнаружено.

P.S.: а винт, несмотря на бэд-блоки, всё ещё жив...

Интересный блог

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

P.S.: А получилось ли у меня что-нибудь я узнаю только вечером, т.к. в пределах досягаемости висты нет :(

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

Немного о малоиспользуемых операторах С#

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


public class AbstractFactory where Products: IBuilder, new()
{
    ....
}


Обратите внимание - в данном случае new() означает, что класс Products должен иметь конструктор без параметров! Каюсь - даже и не знал, что так можно...

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

Немного о семантике

Обновился проект LinqToRdf до версии 0.3.

Кто не знает что такое Linq - вам сюда. Кто не знает что такое RDF - вам сюда.

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

P.S.: а никто не знает, где можно найти частоты встречаемости русских/английских слов (причем с учетом частей речи для английских слов, т.к. одно и то же слово там может быть, например, и наречием и существительным) в текстах различной тематики? Есть идея сделать ранжирование вариантов перевода по этой статистике... Причем сам алгоритм явно будет очень маленьким - только ему надо много-много данных :(

Update: Стоит еще обратить внимание на проект Introducing SyncLINQ. Позволяет делать data binding на LINQ-запросах.

про С++

Один товарищ написал объемистый документ с ответами на часто задаваемые вопросы про С++. Этакое собрание граблей :)
Почитать можно тут. На английском.

P.S.: а те грабли, на которые я наступил в своё время там тоже есть ;)

пятница, 12 октября 2007 г.

Обратите внимание!

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

среда, 10 октября 2007 г.

Страсти-то какие!

Цитирую cnews.ru

"Ужесточение борьбы с нелегальным программным обеспечением не могло не сказаться на росте доходов производителей ПО. Так, по итогам первого полугодия 2007 г. «Лаборатория Касперского» в России, СНГ и Балтии продемонстрировала рост по сравнению с аналогичным периодом 2006 г. на 130%. Рост продаж Microsoft Russia в 2007 финансовом году вырос на рекордные 107%, продажи Autodesk в странах СНГ во II квартале финансового года составили 160%, по итогам третьего квартала 2007 финансового года продажи Adobe в России выросли на 260%."

Нет слов.

И тут я задумался: а ведь программисты (ну понятно, что "хорошие программисты" :) ) в этом отношении самые счастливые люди в стране. Нам ничего не нужно из программ.

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

Зачем мне Photoshop, когда есть Paint.Net?
Зачем мне, например, WebSite Watcher, если подобная программа может быть написана на коленке за один вечер? Ну естественно, менее функциональная - так ведь там в любом случае будет всё что мне нужно - я же ее напишу-то :).
А уж если покопаться в своих экспериментальных проектах - столько всего полезного можно нарыть... :)

Да, неудобно. В том же Ворде2007 есть много интересных вещей, без которых обойтись, конечно, можно, но...

Пиратство будет жить до тех пор, пока цены на ПО не будут приведены в соответствие с его качеством и популярностью.

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

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

Enum'ы с человеческим лицом!

Сегодня речь я поведу о том, как облегчить себе жизнь при работе с enum-типами. То, что использовать их нужно понимают, я думаю, все. Это удобно: код становится понятнее, вероятность совершить ошибку (если использовать вместо enum «магические» числа) – ниже и т.д. и т.п.
Все это, конечно, хорошо… Но что делать, если необходимо отображать пользователю списки (например, combobox'ы) состоящие из этих самых enum (или сводящиеся к ним)? Естественно, что пользователь не знает английского, да и в любом случае – адекватно воспринимать замену пробела символом подчеркивания он точно не будет :)
Если вспомнить про то, что юникод для дотнета – это «родное», а студия еще и корректно работает с русскими именами функций и переменных, то можно, конечно, называть значения в нашем enum русскими именами. Но! Это ж умучаешься раскладки переключать :)
Поэтому самое простое неправильное решение – это, как правило, использование строкового массива, в котором задаются отображаемые пользователю русские имена.
Можно пойти дальше и использовать вместо массива, например, Dictionary. Этот способ как минимум более надежен и шанс , что в программе появится странная ошибка если мы решим добавить еще несколько элементов в enum, резко уменьшится.
Кстати, этот способ имеет право на жизнь. Однако – он неудобен. Намного удобнее, когда описания элементов enum хранятся вместе с ними. Даже очень удобно :)
Как этого добиться? (И вот тут все должны громко воскликнуть – «Ну конечно атрибуты!»)
Первое что нам понадобится – это атрибут, через который мы будем задавать описание:

[AttributeUsage(AttributeTargets.Field)]
public class EnumDescriptionAttribute: Attribute
{
    public readonly string description;

    public EnumDescriptionAttribute(string description)
    {
        this.description = description;
    }
}

public enum etest
{
    [EnumNameAttribute("Значение 1")]
    value1,
    [EnumNameAttribute("Значение 2")]
    value2,
    …
}
Как видите – здесь все просто :)
А теперь самое сложное: получение этих описаний (чтобы отдать их пользователю).

Примечание: и вот тут-то руки и опускаются – сразу после того, как мы слышим жуткое слово reflection :) Но не пугайтесь, все не так страшно ;)

Чтобы получить наш атрибут-описание для известного значения, надо вызвать следующий код:


((EnumDescriptionAttribute)typeof(etest).GetField(p.ToString()).GetCustomAttributes(typeof(EnumDescriptionAttribute), true)[0]).description;


Примечание: стра-а-ашно??? :)

Чтобы получить имена для всех значений enum (например, чтобы сформировать список, из которого будет производиться выбор нужного значения):

public static string[] GetAllEnumDescription()
{
    T[] vals=(T[])Enum.GetValues(typeof(T));
    string[] names = new string[vals.Length];
    for (int i = 0; i < vals.Length; i++)
        names[i] = GetEnumDescription(vals[i]);
    return names;
}

public static string GetEnumDescription(T p)
{
    return ((EnumDescriptionAttribute)typeof(T).GetField(p.ToString()).GetCustomAttributes(typeof(EnumDescriptionAttribute), true)[0]).description;
}


Примечание: я решил, что данный вариант (использующий generics) функции будет полезнее – этот вариант функции можно использовать для любого enum (при условии, что у его элементов есть наш атрибут). Кому не нравится – его легко можно переделать.

Вообщем-то и всё…

P.S.: ой, нет! Чуть не забыл: если в функции GetAllEnumDescription использовать конструкцию yeld return – то она будет еще более удобна :) и один new можно будет убрать…

пятница, 5 октября 2007 г.

"Продвинутое" использование класса Settings, а также немного о Databinding

В предыдущем посте среди прочих способов сохранения настроек я упомянул класс Settings. Однако написал я про него не очень много и как-то не очень внятно. Вряд ли по тому посту будет понятно насколько это хорошая штука :) Поэтому я решил рассказать про него поподробнее и на конкретном примере. Тем более, что в сети информация все больше общего характера – просто непонятно зачем этот класс нужен. Примечание: проект я буду делать в Visual Studio 2008 Beta 2 установленной на Windows Server 2008 RC0. Принципиального отличия от VS2005 в данном случае нет. В обоих версиях студии способ работы с классом Settings одинаков.

Начнем с создания нового проекта. Будет это Windows Forms Application с одной формой (я покажу только как разработывается форма с настройками приложения):


Кидаем на форму несколько контролов, через которые и будут редактироваться наши настройки:

Собственно, ничего особенного тут нет. Я думаю справитесь :)
А теперь начинается самое интересное :) Начинаем использовать класс Settings. Для этого надо для каждого контрола проделать следующие шаги:
1. Выделить его. Переключиться на вкладку Properties и найти раздел ApplicationSettings.
2. В данном случае - для checkBox - нам нужно сохранять свойство Checked.

3 И жмем ссылку new

Здесь можно ввести название переменной, в которой будет храниться состояние нашей настройки, значение по-умолчанию (True/False) и где она будет храниться (оставляем как есть).
4. А собственно и все :)
Ну и напоследок, посмотрим как это работает. В метод button1_Click пропишем добавим следующий код:
MessageBox.Show(Properties.Settings.Default.setting1.ToString());

И мы увидим, что всё работает :)

Ну и напоследок
Что делать, если необходимо добавить какую-либо проверку значений вводимых пользователем параметров или, например, осуществлять преобразование типов?
В этом случае придется немного пописать ручками :)
Добавим свойство с методами get/set через которое и будет осуществляться доступ к настройкам:

public string Setting3
{
  get
  {
      return Properties.Settings.Default.setting3;
  }
  set
  {
      string tmp = value.ToLower();
      Properties.Settings.Default.setting3 = tmp;
  }
}

Свяжем его с соответствующим свойством у контрола на форме:

this.textBox1.DataBindings.Add("Text", this, "Setting3");

В данном случае, первый параметр - это имя свойства у контрола которое будет связываться. Второй параметр - ссылка на объект где находится свойство с которым будет производиться связывание. Ну и третий параметр - это, соответственно, имя свойсва.
И всё! Дальше всё будет работать само.
Пожалуй на этом и закончу.

пятница, 21 сентября 2007 г.

Сохранение настроек приложения

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

1. Сохранение настроек в собственный файл используя сериализацию.

Данный подход обладает достаточно большой гибкостью и удобен в использовании. Однако требует наибольшее количество кода. Итак. Первое что нам понадобится - это класс с настройками. Для удобства использования, лучше всего если все члены класса будут статическими (Это, конечно, не самый лучший способ, однако, если наше приложение очень маленькое и не будет сильно расти/развиваться в дальнейшем, то можно и так. Более универсальный способ - это реализовать для класса с настройками паттерн singleton).
public class MySettings
{
   public static int Setting1;
   public static string Setting2;
}
Думаю - код класса понятен и проблем ни у кого не вызовет :) Для сохранения данных - сериализуем:
XmlSerializer ser = new XmlSerializer(typeof(MySettings));
TextWriter writer = new StreamWriter("settings.xml");
ser.Serialize(writer, MySettings);
writer.Close();
Упс!.. Ошибка: "'MySettings' is a 'type' but is used like a 'variable' (CS0118)" Переделываем:
MySettings settings=new MySettings();

XmlSerializer ser = new XmlSerializer(typeof(MySettings));
TextWriter writer = new StreamWriter("settings.xml");
ser.Serialize(writer, settings);
writer.Close();
Пожалуй стоит пояснить. Первой строкой мы создаем объект класса MySettings, но поскольку он содержит только статические члены - данные в переменных класса не изменятся, но мы получим объект (переменную), которую уже можно сериализовать :) Компилируем, запускаем, смотрим что получилось:

<?xml version="1.0" encoding="utf-8"?>
<MySettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
Упс!.. А где данные? Проблема в том, что сериализовать статические члены класса нельзя! (В дополнение: xml-сериализация работает только с public-членами класса). Делаем "финт ушами":
public class MySettings
{
    public static int Setting1;
    public static string Setting2;
    public int _Setting1
    {
        get{ return Setting1;}
        set{ Setting1=value;}
    }
    public string _Setting2
    {
        get{ return Setting2;}
        set{ Setting2=value;}
    }
}
Компилируем, запускаем, смотрим:
<?xml version="1.0" encoding="utf-8"?>
<MySettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <_Setting1>5</_Setting1>
  <_Setting2>test</_Setting2>
</MySettings>
Вот оно! :) Суть в том, что через не-статические public-члены класса сериализатор получает доступ к статическим. :) Ну а теперь - самое легкое: десериализация.
XmlSerializer deser = new XmlSerializer(typeof(MySettings));
TextReader reader = new StreamReader("settings.xml");
settings=((MySettings)ser.Deserialize(reader));
reader.Close();

2. "Ручное" сохранение в файл.

Поклонникам C++ рекомендуется :) В смысле - смотрите и завидуйте! Итак. В .Net Framework в пространстве имен System.IO есть один замечательный класс - File, которым мы и воспользуемся. В частности, статическими функциями ReadAllLines и WriteAllLines. Первое что на надо сделать для сохранения данных, это подготовить и заполнить массив строк:
string[] settings = new string[5];
settings[0] = "setting1=" + setting1.ToString();
settings[2] = "setting1=" + setting2.ToString();
Примечание: Многие увидевшие такой код наверняка захотят оторвать мне руки :)) Однако, сделано это специально, для того, чтобы показать, что делать если настройки сохранить надо, времени мало (лень), да и настроек 2-3 штуки. Естественно, что в серьезном приложении так делать нельзя! После чего пишем одну строку кода(!!!) и все:
File.WriteAllLines("settings.txt", settings);
При желании, можно указать и кодировку:
File.WriteAllLines("settings.txt", settings, Encoding.GetEncoding(1251));
Теперь перейдем к самому ответственному - чтению настроек. :)
string[] settings=File.ReadAllLines("settings.txt");
foreach(string s in settings)
{
    string[] tmp=s.Split(new char[] {'='}, StringSplitOptions.RemoveEmptyEntries);
    if(tmp.Length==2) //на всякий случай 
    {
        switch(tmp[0])
        {
            case "setting1":
                Int32.TryParse(tmp[1], out setting1); //тоже, чтобы не падало :)
                break;
            case "setting2":
                setting2 = tmp[1];
                break;
        }
    }
}
Вообщем-то и все.

3. Использование класса Settings.

Внимание: данный способ работает только в Visual Studio. По крайней мере в Sharp Delevop 2.2 этого точно нет. Более того: данный способ доступен только для WinForms или консольных проектов. Первое что нам надо сделать - это добавить те настройки, что мы хотим сохранять. Делается это в разделе Properties списка файлов нашего проекта. Там дважды кликаем на раздел Settings и получаем в результате окно с таблицей. В этой таблице придумываем и вводим имена перменных (в которых и будут храниться наши настройки) их тип и значение по-умолчанию. Доступ к настройкам осуществляется так:
Properties.Settings.Default.имя_нашей_перменной = значение;
После компиляции рядом .exe файлом будет лежать файл с таким же именем и расширением .exe.config. В этом файле хранятся значения по-умолчанию для наших настроек. Об этом надо помнить! Сами же настройки приложения хранятся в
Documents and Settings\имя_пользователя\Local Settings\Application Data\имя_компании_из_AssemblyInfo.cs\имя_приложения.exe_StrongName_<разные левые числа>\номер_версии"
Причем номер версии берется в AssemblyInfo.cs из атрибута [assembly: AssemblyVersion("1.0.0.0")] Если его изменить - то путь для настроек также изменится и, соответсвенно - наше приложение запустится с настройками по-умолчанию. (Тому, кто это придумал в Microsoft, по-хорошему следовало бы оторвать руки...) К счастью - атрибут [assembly: AssemblyFileVersion("1.0.0.0")] можно менять как хочется :) Самое веселое, что сохранять текущие настройки куда-либо помимо указанного пути стандартными средствами нельзя! Настройки загружаются автоматически при старте программы. Сохранять их надо вручную, вот так:
Properties.Settings.Default.Save();
В случае, если вам надо сбросить все настройки в их значения по умолчанию, используется команда:
Properties.Settings.Default.Reset();
Ну и хватит наверное :) Более подробно - читаем МСДН.

понедельник, 17 сентября 2007 г.

Параллельное выполнение кода в .Net Framework 3.5

Вы не ждали? А мы пришли!

Э... О чем это я?

А!

Microsoft разрабатывает библиотеку для упрощения процесса написания программ, в которых отдельные элементы алгоритма можно выполнять параллельно. Более подробно (на русском!) об этой библиотеке можно почитать здесь

Последнее время все чаще встречаю статьи о том, что "несколько ядер/процессоров это конечно хорошо... но вот программировать под них...". Так вот, теперь это не просто, теперь это очень просто! :)

P.S.: а С++ пора начинать забывать. Как страшный сон.

суббота, 15 сентября 2007 г.

Полезный ListView

Совершенно случайно нашел то, что давно искал, а именно - модифицированный ListView, который позволяет группировать элементы. Да-да, я знаю, что стандартный ListView это тоже умеет, однако делает он это очень плохо. Контрол можно найти тут: http://www.codeproject.com/cs/miscctrl/outlooklistcontrol.asp Выглядит он вот так: Самое интересное, что возможностей у него очень и очень много. Кроме того, можно переопределять стандартную отрисовку любой части контрола, причем для этого даже не надо лезть и что-то исправлять в его исходном коде! Хотя нет. Вру :) Мне все-таки пришлось кое-что подправить. Видите воон ту желтую область слева? Вот ее-то и пришлось править - цвет области был жестко зашит в коде. Вообщем, теперь у меня это выглядит вот так (в моем втором более-менее крупном проекте MusicSorter):

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

Новый движок для скриптов готов!

    Сегодня великий день :) Я окончательно доделал новый движок скриптов (скрипты даже компилируются и работают в основной программе-переводчике!). Теперь скрипты пишутся исключительно на на языке C#. Применяться он будет в составе алгоритма синтаксического анализа предложений вместо уже существующего.

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

  1. поиск слов имеющих заданные параметры и замена в строке-представлении предложения символа в соответствующей позиции (равной номеру слова в предложении) на заданный симво
  2. поиск в предложении участка по заданному регулярному выражению
  3. если поиск увенчался успехом, то производятся заданные действия - например, установка у указанных слов определенных параметров, добавление связей между словами и т.п.

где разработчик правила мог указывать необходимый (и строго фиксированный!) набор параметров.

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

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

На данный момент запланированы следующие задачи:

  1. реализовать поддержку нескольких библиотек скриптов в редакторе с соответствующей компиляцией в разные .dll
  2. сконвертировать все старые скрипты к новому виду - т.е. фактически написать алгоритм генерации класса по описанию правила.
  3. разработать набор визардов для автоматической генерации простых скриптов (лень - двигатель прогресса!)

    Ну и как не похвалиться? На скриншоте можно посмотреть структуру стандартного скрипта:

    Функция Init используется для инициализации. Там необходимо выполнять те действия, которые можно сделать один раз и потом пользоваться результатом В частности я предполагаю выполнять там действия по инициализации алгоритма поиска.

    Ну и самая главная функция - Run - которая вызывается в цикле до тех пор пока возвращает true (ну о том, как именно работает алгоритм анализа я расскажу по-позже).

четверг, 6 сентября 2007 г.

Nullable типы в C#

Разбираясь с примером по WWF наткнулся на странное объявление переменной. Выглядело это примерно так:

int? i;

Недолгие поиски в гугле привели меня на сайт MSDN.

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

Как это работает.

Объявленный таким способом тип имеет помимо прочих еще два члена:

  • HasValue - показывает, не записано ли null в Value
  • Value - содержит само значение переменной нашего типа

В случае, если данные в переменную не записаны (т.е. фактически она равна null), то при попытке как-то взаимодействовать с такой переменной мы получаем InvalidOperationException. Если данные записаны - то все работает так же как и у переменных обычного типа.

Примеры:


int? x = 10;
if (x.HasValue)
    System.Console.WriteLine(x.Value);
else
    System.Console.WriteLine("Undefined");

int? y = 10;
if (y != null)
    System.Console.WriteLine(y.Value);
else
    System.Console.WriteLine("Undefined");

понедельник, 3 сентября 2007 г.

Xml-сериализация сложных коллекций

Ну я надеюсь что про простую xml-сериализацию вы уже знаете :) Однако постоянно встает вопрос - как сериализовать классы, содержащие коллекции типа Hastable, Dictionary или Hashset (новый тип, доступный в 3-ей версии .Net Framework). Итак. Первое что нам понадобится:
using System.Runtime.Serialization; 
Второе. Пометить сериализуемые классы (все! включая вложенные!) атрибутом [DataContract] и все члены класса, данные которых надо сериализовать артибутом [DataMember]:
[DataContract]
public class MySerializableClass
{
   [DataMember]
   public Dictionary MyCollection = new Dictionary();
   ...
}
Сериализация производится следующим образом:

public void SaveData(string path, MySerializableClass msc)
{
   XmlTextWriter xw = new XmlTextWriter(path, Encoding.UTF8);
   //а это чтобы красиво было :)
   xw.Formatting = Formatting.Indented;
   XmlDictionaryWriter writer = XmlDictionaryWriter.CreateDictionaryWriter(xw);
   DataContractSerializer ser = new DataContractSerializer(typeof(MySerializableClass));
   ser.WriteObject(writer, ds);
   writer.Close();
   xw.Close();
}
Ну и, соответственно, десериализация:

public MySerializableClass LoadData(string path)
{
   MySerializableClass msc = null;   using (FileStream fs = new FileStream(path, FileMode.Open))
   {
      XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(fs, Encoding.UTF8, new XmlDictionaryReaderQuotas(), null);
      DataContractSerializer ser = new DataContractSerializer(typeof(MySerializableClass));
      msc = (MySerializableClass)ser.ReadObject(reader);
   }

   return msc;
}
P.S.: ну естественно, что все красоты с использованием отступов и кодировок можно опустить - но наша же цель состоит именно в том, чтобы получить более-менее читаемый файл...

пятница, 31 августа 2007 г.

Компиляция кода из своей программы

В данный момент я пытаюсь написать более гибкий движок для скриптов. Основная идея - скрипты должны писаться не на каком-то там "псевдо-языке", а на обычном C#.
В принципе, первая попытка написать такой движок уже была. Каждый скрипт хранился в отдельном файле. При старте менеджера скриптов, он их компилировал и в дальнейшем они использовались в работе переводчика.
Ну а то, что в зависимости от количества этих самых скриптов скорость загрузки программы падала экспоненциально думаю и так понятно :)
Поэтому сейчас я делаю редактор скриптов, который бы в итоге генерировал сборку, понятную менеджеру модулей переводчика и, соответственно, которая и использовалась бы для анализа предложений в процессе перевода. Это позволит избежать компиляции при загрузке.
Ну а теперь перейдем к теме поста.
Итак, что нужно для того чтобы скомпилировать произвольный код написанный, например, на языке C# и получить в итоге .exe или .dll файл?
Оказывается все очень и очень просто:

public static void Compile(string code, out string errors)
{
CompilerParameters cp = new CompilerParameters(); 

//указываем список используемых сборок
cp.ReferencedAssemblies.Add("System.dll"); 

//генерируем .exe файл
cp.GenerateExecutable = true; 

//в файл с именем
cp.OutputAssembly = "c:\\tmp.exe"; 

//Здесь можно поставить true, если нет необходимости сохранять
//скомпилированный файл и вы умеете пользоваться сборками, 
//которые загружены в память :)
cp.GenerateInMemory = false;

CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerResults cr = provider.CompileAssemblyFromSource(cp, new string[] {code} );

if (cr.Errors.Count != 0) {
//если были ошибки при компиляции, выводим в errors их список StringBuilder sb = new StringBuilder(); foreach (CompilerError ce in cr.Errors) { sb.Append(ce.ToString()); sb.Append(System.Environment.NewLine); } errors = sb.ToString(); } else errors = "Compiled!";
}
Ну а пока что можно полюбоваться на новый редактор :) Вот оно:

суббота, 26 мая 2007 г.

Link Grammar в моем переводчике!

Наконец-то переделал порт Link Grammar чтобы он принимал для обработки не только строку, а еще и массив слов. Результат можно скачать тут. Пока что результаты работы модуля сохраняются только в output.txt и нигде не используются, но все равно - уже какой-то прогресс есть :) Осталось сделать так, чтобы генерируемые данным модулем связи между словами понимались остальными модулями. Судя по всему - придется преобразовать класс TLink в интерфейс с тем, чтобы можно было переделывать его реализацию по мере необходимости. Правда в этом случае возникает одна проблема... Нескольким модулям может потребоваться разные реализации класса связей. Как в этом случае поступать - я пока что не имею ни малейшего представления. P.S.: К сожалению, в текущей реализации Link Grammar наблюдается странный глюк - если предложение начинается с предлога, то ссвязи в таком предложении не находятся. Если предлог убрать - находятся.

пятница, 25 мая 2007 г.

Visual Studio Code Name 'Orcas' Express Editions

Если вдруг кого заинтересует: оказывается, несмотря на то, что до выхода самой VS Orcas еще далеко, Express-версии среды уже есть здесь в виде VB, C#, Web Developer, и C++ версий.

четверг, 17 мая 2007 г.

Гугл

Весь день натыкался на новости о том, что гугл объединили (собираются объединить) все виды поиска в один. Зашел на Google Labs посмотреть что же такое там придумали-то :) Во-первых, на что стоит обратить внимание, это timeline-поиск, например вот такой, что подтверждается и обычным поиском, действительно больше всего документов по Link Grammar было в 2000-м году. Такой поиск несомненно удобен, если хочется оценить тенденции чего-либо... (Примечание: с русским текстом не работает) Во-вторых, однозначно стоит пользоваться Google Scholar. По тому же самому запросу была сразу найдена книга "Parsing English with a Link Grammar" (опубликованная не только на оригинальном сайте http://www.link.cs.cmu.edu/link/ftp-site/link-grammar/LG-tech-report.pdf, но и на других). В принципе, она находится и обычным поиском, однако для этого надо уже знать ее название и вот такого поискового запроса уже недостаточно. (Примечание: а вот на русском по запросу "грамматика связей" ничего полезного найдено не было...)

четверг, 10 мая 2007 г.

Все еще перевожу Link Grammar на C#... Уже научился загружать грамматику из файла и парсить данные. Осталось теперь научиться применять загруженную информацию. Не совсем понятна разница между тремя видами скобок. Мало информации... Пока что известно, что:
  • квадратные скобки прибавляют 1 к весу связи
  • круглые скобки - похоже просто ограничивают группу операндов в выражении
  • что делают фигурные скобки - пока что загадка
25392226.a5e351c855901a7acefacc6448e97458.1178813598.2cae6782503463cac1f3838e7e2e1f07

вторник, 1 мая 2007 г.

Эксперименты - 2

Потратив н-ое количество времени выяснил, что OpenNLP - это все-таки не совсем то, что хочется. Т.е. - оно, конечно, работает, но... Претензий много :) Например, то, как эта библиотека написана. Сразу видно тяжёлое детство в виде Java проекта :) Особенно меня потряс код вида:
class MyClass
{
   MyClass(StringBuilder sb): this (sb.ToString().ToCharArray())
   {
   }

   MyClass(char[] str)
   {
      ...
   }
}
И такое там везде... Неудивительно что столько памяти расходуется. Ну и сама работа библиотеки, хм... странная. Т.е. того, что я хочу - а хочу я предсказуемости результата парсинга - от нее не добиться. Поэтому опять переключился на Link Grammar .NET Lib. Тоже страшная :) Начать с того, что ее .Net Framework порт представляет собой смесь из трех проектов:
  1. на чистом Си
  2. на Managed С++
  3. на C#

А уж как они взаимодействуют друг с другом - это вообще песня :) Ну про то, что разобраться как работает Си кусочек практически невозможно я думаю можно и не упоминать...

MC++ проект использует часть классов, описанных в С# проекте. А тот, в свою очередь - активно пользуется классами написанными на MC++

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

Примечание: нет, я конечно слышал о том, что MC++ - далеко не самый лучший язык. Но что настолько, я даже и не догадывался. Например вот такое:

int Sentence::separate_sentence(System::Collections::Generic::IList __gc* words, Sentence_s *sent) ;

Ну да ладно... Этот вопрос я все-таки решил. Впереди самое трудное - заставить Cи-часть взаимодействовать с моей базой данных, а не читать данные с диска.

понедельник, 30 апреля 2007 г.

Эсперимент

Два дня экспериментирую с грамматикой связей (взял здесь http://www.codeplex.com/sharpnlp). В принципе, встроить ее в мою программу можно легко. Однако есть некоторые проблемы. В комплекте с библиотекой идут .nbin файлы в которых и содержится описание грамматики. Если использовать их - то при парсинге эти файлы полностью грузятся в память - это порядка 250 Мб. Есть альтернативный вариант - использовать sqlite. В этом случае - используется порядка 30 Мб памяти, но! скорость резко падает - я так и не дождался когда будет обработан текстовый пример. Кроме того, примерно в 3 раза больше места требуется для файлов моделей. Попробую ее полностью прикрутить к переводчику (пока что парсер я еще не подключал) - посмотрю что получится на выходе...

воскресенье, 29 апреля 2007 г.

Ссылки - 2

Гугл рулит! (Конечно не сам гугл, а его codesearch, но все-таки...) Предельно глупыми запросами было найдено столько всего интересного...

суббота, 28 апреля 2007 г.

Модель предложения в переводчике

Вроде бы про слова более-менее рассказал, теперь очередь за предложениями. Итак, предложения (строки) бывают двух видов TMStr и TRStr.
  • TMStr - описание английского предложения
  • TRStr - результат перевода на русский
TMStr является одним из базовых классов. Умеет:
  1. Добавлять/удалять связи между словами
  2. Добавлять/удалять слова (с учетом/коррекцией связей между словами)
  3. Сегментировать часть предложения

Сначала поговорим про сегментирование и вообще о том, что такое сегменты в системе.

Сегмент - это группа слов, по отношению к предложению представляемая как одно слово. Для чего это нужно?

Возьмем, например, такое предложение: I am looking at the big, fast river.

В нем можно выделить следующие участки: I am looking [at [the [big, [fast river]]]]. Вот именно по выделенным участкам и будет произведено сегментирование (свертка). В результате предложение выродится в: I am looking river (естественно, river приобретет ряд дополнительных параметров(свойств), которые будут установлены в процессе свертки предложения).

Для чего это нужно? Для упрощения алгоритма синтаксического анализа (по-крайней мере - моей реализации этого алгоритма). После проведения свертки для определения подлежащего/сказуемого нам достаточно проверить простое правило вида: местоимение/существительное+глагол_ing+уловия_для_дополнений (ну, на самом деле проверяемые правила несколько сложнее, но принцип именно такой). Таким образом мы преобразовали сложное предложение в более простое, тем самым упростив синтаксические правила, которые необходимо проверять на предложениях. Более того - теперь для описания синтаксической структуры предложений вида Present Continuous достаточно всего 3-х правил (обычная форма, вопросительная форма, пассив), а не бесконечного количества :)

При создании сегмента (свертке) указывается какое их слов в будущем сегменте является главным. Снаружи сегмента будет видно именно это слово, со всеми его параметрами.

Допускается (даже желательна) вложенность сегментов (см. пример выше).

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

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

Ну и напоследок - немного о том как реализован TMStr.

  1. список всех слов в предложении
  2. список индексов
  3. список сегментов

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

Реальная последовательность слов хранится в списке индексов (каждый индекс указывает на слово в списке слов).

Список сегментов - содержит только сегменты самого верхнего уровня.

Сегменты обладают примерно такой же структурой что и TMStr, т.е. также содержат индексы слов, список слов (это ссылка на основной список слов в TMStr) и список сегментов.

Удалять/добавлять слова можно только в TMStr, в сегменты - нельзя (ну если только вручную...).

При создании сегмента в его список индексов переносятся индексы из основного списка индексов предложения. Сами слова никуда не перемещаются.

Связи между словами бывают четырех видов:

  1. между словами в предложении
  2. связь ведет от слова к слову-сегменту
  3. связь ведет от слова-сегмента к слову
  4. связь ведет от слова-сегмента к слову-сегменту

Соответственно, при каких-либо модификациях предложения связи корректируются.

Уф... Ну вроде бы пока - все.

четверг, 26 апреля 2007 г.

Зависимости

Потратив четыре часа времени таки построил зависимость кол-ва вариантов перевода предложений (Y) от кол-ва слов в предложении (X). (Excel 2007 - это что-то! :) Зато нашел много нового и интересного... )
А вот еще - относительный разброс количества вариантов перевода в зависимости от кол-ва слов в предложении (т.е. величина характеризует, насколько разное количество вариантов получается для предложений с одинаковым количеством слов.):
Данные сняты со стандартного текста для тестирования. Размер - 29 кб, 395 предложений.

Ссылки на интересное про алгоритмы обработки текстов

В этом посте буду публиковать найденные интересные ссылки касающиеся вопросов автоматической обработки текста (все что касается перевода, поиска и т.п.) Интересные блоги:

Статьи про семантические сети:

Поиск и все с ним связанное:

среда, 25 апреля 2007 г.

Back to basics

Посмотрел предыдущие посты и понял, что про реализацию переводчика я практически ничего и не написал. Исправляюсь. Не секрет, что любой компьютерный переводчик - ошибается. Иногда - сильно. (Почему про это забыли ПРОМТовцы - я не имею ни малейшего представления). Поэтому система изначально спроектирована под работу с несколькими вариантами перевода, т.е.:
  • каждое английское слово имеет несколько вариантов перевода
  • каждое английское предложение также имеет несколько вариантов перевода

В общем виде, реализованный мной алгоритм перевода выглядит так:

  1. Получение текста для перевода.
  2. Лексический анализ
  3. Преобразование разбитого на элементы текста во внутреннее представление (TextParser)
  4. Для каждого предложения текста производится поиск всех вариантов перевода для каждого слова предложения
  5. Выполняется первичный синтаксический анализ. Его особенность в том, что применяемые правила должны быть 100% истинными (без исключений/неясностей). Например, для словосочетания "his hand" можно сразу отбросить варианты перевода-глаголы для слова hand.
  6. Генерируются варианты предложения. Их особенность в том, что у каждого слова в предложении должны быть переводы только одной части речи (соотв. в другом варианте предложения - у этого слова будут переводы другой части речи)
  7. Для каждого варианта предложения опять запускаем синтаксический анализ
  8. Формируем русские предложения по английским
  9. Склоняем все слова в каждом варианте русских предложений
  10. Формируем текст для пользователя

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

Конечно, у многих сразу возникает вопрос - как много вариантов перевода предложения будет генерироваться и насколько сильно такой подход требователен к памяти/процессору?

Отвечаю: на текущий момент, при 1500 слов в словаре, в среднем по 0,77 перевода на слово более 16 вариантов перевода предложения не было получено ни разу. При этом процесс перевода тестового фрагмента (29 кб текст, кусок книжки) длится на Athlon 3000+ порядка 6 секунд. Этот же текст в ПРОМТе переводится не менее 10 мин. (Ну понятно, что когда будет реализован хоть какой-то алгоритм семантического анализа скоростные показатели изменятся, но пока что...)

Разрабатываемый сейчас алгоритм поиска фраз будет находиться между 4-ым и 5-ым пунктами.

вторник, 24 апреля 2007 г.

Фразеологический анализ-2

А теперь перейдем к первым проблемам. Первоначально я хотел сделать как у всех (да собственно, так и сделал) - английская фраза является отдельной словарной статьей. Не самый лучший вариант. Почему? Потому что это резко усложняет алгоритм поиска фраз:
  • необходимо создать список всех фраз в базе данных.
  • перебрать все слова в предложении на предмет вхождения в полученный список.
  • для каждого слова, для которого на предыдущем этапе были найдены фразы необходимо проверить близлежащие слова, чтобы выбрать правильные варианты

(ну и попутно есть некторые проблемы связанные с конкретной реализацией класса TMStr. Вообщем-то это не настолько существенно, но еще усложняет алгоритм)

Стоит ли говорить о том, что такой подход - медленнен? Поиск по базе с выбором всех вариантов, проверка всех слов на совпадение с выбранными первыми словами фраз и т.п. - циклы, циклы, циклы...

Буквально на днях реализовал другую мысль - перевод английской фразы задается у первого слова фразы, но к этому варианту перевода добавляются дополнительные условия.

Рассмотрим , например, предлог because of: это производная форма от слова because с "пристегнутым" к нему предлогом of. Соответственно, перевод данной фразы задаем у слова because, но дополнительно указываем, что после этого слова должен стоять предлог of.

Чем такой лучше? Если учесть то, как работает система в целом - он идеально в нее вписывается: морфологический анализатор определяется исходные формы слов фразы, после чего производится поиск всех возможных вариантов перевода слов. Соответственно, перевод фразы также будет добавлен к слову because.

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

В принципе, это все. Однако, если бы все было настолько просто - никаких вопросов бы и не возникло.

Проблемы:

  1. фразы могу быть написаны по разному: shut up, shut the hell up и т.п.
  2. разные формы основных и "пристегнутых" слов дают разные фразы: going to и go to - это разные фразы (ну... в большинстве случаев, просто пример получше как-то не придумывается).
  3. думаю стоит упомянуть, что во фразе могут быть слова не в исходной форме, при этом эти слова могут отсутствовать в словаре или же там может быть задан перевод не той части речи, что использован во фразе -> алгоритм морфологического анализа на этих словах даст неверный результат, что не позволит обнаружить фразу
  4. для некоторых фраз существуют слова-модификаторы, не являющиеся частью фразы, но прямо влияющие на ее перевод (пример был раньше: going to do и going to)

Возможно еще что-то забыл, когда вспомню - допишу.

Ну с п.3 я сделать ничего не могу по-определению.

п.1. Можно задать специальный вид слова фразы (регулярное выражение?), который будет позволять задавать условия вида shut smth up. Реализуемо, вообщем-то. Конечно, хотелось бы чего-нибудь попроще...

п.2. Ввести в параметры перевода основного (первого) слова фразы отдельное поле для параметров, которыми должно обладать английское слово в предложении, чтобы этот вариант перевода мог быть добавлен к слову.

п.4. Ну, опять-таки можно задать отдельный список (в какой форме?) этих слов-модификаторов... Надо думать...

О! Кстати, вокруг всего этого еще и неопределенность с границами фраз есть например: so beautiful as far as i know...

Есть идеи? Замечания?

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

Фразеологический анализ

Ну, все что было раньше - это так, вступление. А теперь, наверное, самое интересное - алгоритм находится в разработке в данный момент, соответственно, есть возможность приложить свою руку к его созданию... Цель: получив на входе предложение выделить в нем все фразы и подготовить предложение к дальнейшей обработке. Примечание: здесь и далее, если это не оговорено особо, под предложением понимается его реализация в системе TMStr или TRStr (по умолчанию - TMStr), под словом - TMWord. Начнем, пожалуй с выработки теории. Я предполагаю, что фраза (как группа слов) характеризуется двумя независимыми параметрами: 1.
  • единой, как, например, going to
  • разрывной, как, например, so ... as

2.

  • постоянной, т.е. группа слов будет переводиться строго определенным образом вне зависимости от переводов отдельных слов группы. Пример - shut up.
  • непостоянной, т.е. группа слов может восприниматься и как единое целое и как каждое слово, образующее группу, поотдельности.

Кроме того, следует пожалуй учесть, что для некоторых постоянных/непостоянных фраз существуют слова-модификаторы, которые меняют вторую характеристику фразы на противоположное значение. Пример:

  1. i was going to do smth
  2. i was going to smth

В первом случае наличие глагола после to полностью меняет значение фразы going to.

P.S.: безусловно, все вышеизложенное - спорно и не факт что близко к реальности... Но в любом случае - пока что чего-то более умного мне в голову не пришло

Морфологический анализ

Ну этот алгоритм я думаю для большинства сложности не составит... Реализация проста как кирпич:

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

  1. Слово написано не в исходном виде.
  2. Слово отсутствует в словаре.
  3. Слово - имя собственное.

Подробнее:

  1. по правилам английского языка (см. школьный учебник) изменяем окончание слова так, чтобы получить исходную форму слова. Пытаемся найти слово в базе. Если не нашли - см п.2.
  2. добавляем как перевод само слово (дополнительно, мой алгоритм еще по окончанию делает предположение о возможной части речи слова, но это уже не главное).
  3. по определенным причинам несловарные имена собственные (имена, фамилии) вынесены в отдельную базу - это позволяет загружать/сохранять их вместе с текстом

В принципе, это все :)

P.S.: вот хоть убейте, я не могу понять почему ПРОМТовцы уже лет 20 не могут научить переводчик самостоятельно определять имена собственные в тексте. Проблема решается за 2 минуты (я - решил :) ).

Лексический анализ

С вводной частью пока что закончили... Перейдем к описанию отдельных этапов перевода. Первым у нас будет лексический анализ. Основные цели лексического анализатора в разрабатываемой системе это:
  • выделение слов и знаков препинания в тексте
  • определение и пометка границ предложений и абзацев

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

- текст режется на отдельные элементы, т.е. слова (числа воспринимаются как слова) и знаки препинания.

- на выходе получаем массив элементов текста. Для каждого элемента расставляются следующие свойства:

  • кол-во пробелов до начала элемента (если элемент - не первый в предложении, то оно всегда 0)
  • кол-во пробелов после элемента
  • маркер конца предложения
  • маркер конца абзаца

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

- помимо этого, лексический анализатор активно взаимодействует со словарем для того, чтобы корректно обрабатывать различные сокращения (например "Mr." и т.п.)

По окончании работы алгоритма получаем массив промаркированных элементов текста, который и отдаем TTextParser на растерзание.

Основы-2

Продолжим :) Ранее я уже упоминал элементы алгоритмя перевода. На всякий случай еще раз:
  1. лексический анализ
  2. морфологический анализ
  3. фразеологический анализ
  4. синтаксический анализ
  5. семантический анализ

Сразу хочу сообщить - я не лингвист. Я простой программист :) Поэтому мои подходы к решению лингвистических задач несколько хм... отличаются от общепринятых. Прошу сильно не бить :)

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

  1. лексический анализатор - получает на входе текст и выделяет в нем слова, предложения, и абзацы.
  2. морфологический анализатор - получает на входе слова и определяет их исходную форму. В моей версии алгоритма перевода - на этом этапе еще и определяются все переводы слов.
  3. фразеологический анализатор - получает на входе слова и принимает решение об объединении некоторых из них в одно слово.
  4. синтаксический анализатор - получает на входе предложения (массив слов) и определяет отношения между словами, корректирует характеристики и части речи слов.
  5. семантический анализатор - отсутствует на данный момент :(

Наверное теперь самое время определиться - что же такое слово/предложение/текст?

Слово описывается специальным классом TMWord и содержит следующие основные элементы:

  • оригинальная форма (то, как слово было написано в тексте)
  • исходная форма (то, как слово записано в словаре)
  • части речи слова
  • параметры слова
  • переводы слова

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

Каждый вариант перевода слова описывается:

  • русское слово
  • часть речи
  • параметры слова
  • шаблон склонения

Параметры слова - это синтаксические и семантические характеристики слова. Например, для глаголов - это переходный/непереходный, совершенный/несовершенный и т.п.

Параметры на данный момент делятся на три вида:

  • применимые к английским словам
  • применимые к русским слова
  • применимые ко всем языкам

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

Ну со словом вроде более-менее определились. Перейдем к предложению. Я думаю ни для кого не секрет, что предложение - это группа слов :) Английское предложение описывает специальный класс TMStr, русское - TRStr. Ну, об их особенностях поговорим позже...

Текст обычно состоит из абзацев, абзацы из предложений. Эту структуру описывает класс TTextParser. Фактически - это третий самый главный базовый класс (первые два - это TMWord и TMStr). Все взаимодействие GUI-модулей с текстом идет через него.

Основы

Продолжаю свой рассказ :) Услышав, что я пишу переводчик у многих сразу же возникает вопрос - зачем? Ведь есть ПРОМТ и подобные программы? Ответ будет м... неприятным.... Да, пожалуй так точнее всего. Я более менее активно пользуюсь ПРОМТом уже лет 7 (года на два больше чем я пишу переводчик). С того времени перевод ПРОМТом тестового кусочка текста не изменился. (Нет, конечно, я немного привираю - перевод отдельных предложений чуть-чуть улучшился/ухудшился, однако каких либо радикалных изменений не было и нет даже в текущей - только-что вышедшей - 8-ой версии) Что собственно и не удивительно, т.к. я встречал интервью с разработчиками (2002-ого года, кажется), где они прямо сказали, что алгоритм перевода существенно не менялся с 1992 года!!! А за это время столько всего изменилось.... Вы спросите - "а что же другие программы?". Ну... Знаете, мне сложно оценивать переводчик, неспособный корректно склонять простые русские слова (я уж не упоминаю о самом переводе - перлы Stylus и ПРОМТа, ставшие уже классикой - это корректно, абсолютно точно переведенный текст :) ) На данный момент я не вижу ни одной программы, реально могущей конкурировать с тем же ПРОМТом. Чем лучше мой вариант перевода? Я предоставляю базовый "движок" перевода, легко модифицируемый сторонними разработчиками. Это позволяет легко, с минимальными затратами, создавать и - самое главное - тестировать новые алгоритмы. Пожалуй, пример не помешает... Допустим вы занимаетесь исследованием ну... например... семантических алгоритмов. Тема эта актуальна уже лет 30 и интерес к ней с каждым днем все возрастает. Итак, для проверки своего алгоритма вам придется:
  • реализовать GUI - т.е. чтобы можно было вводить информацию в программу (ну можно и без интерфейса какое-то время прожить... Но сопутствующие трудности...)
  • реализовать лексический анализатор (а вы что думали - кто же еще вам будет текст на слова разбивать? Все сами...)
  • реализовать морфологический анализатор (нет, конечно, можно обойтись и без него, но это переложит работу этого алгориитма на плечи человеку, которому придется особым образом формировать входные данные, что намного сложнее простого текста на естественном языке)
  • реализовать синтаксический анализатор (да-да! Куда же без него-то...)
  • и вот только после этого вы сможете начать работу над своейпрямой задачей

А теперь вспомним еще и про сопутствующие трудности, как-то:

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

Весело? А казалось бы все так просто: придумал алгоритм - реализовал и получил Нобелевку :)

Именно поэтому мой переводчик - вам просто-таки жизненно необходим! :)

Итак, достоинства моего подхода:

  • модульная структура. Алгоритм перевода разбит на отдельные, программнно независимые друг от друга элементы (модули), каждый из которых может быть легко заменен на аналог от другого разработчика.
  • Единая система описания текста. Существует набор базовых классов, детально описывающих текст и связи в нем (ну и перевод, конечно). Таким образом, разработчики, которые реализуют, например, интерфейс взаимодействия с пользователем никак не зависят от того, что реализуют переводчики.

Ну если забыть про независимых разработчиков, то можно добавить еще и:

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

За сим, пожалуй, пока все.

воскресенье, 22 апреля 2007 г.

Приветствие!

Доброго времени суток! Надеюсь, что кто-то будет читать этот блог. Планирую писать здесь о процессе разработки программы-переводчика с английского на русский (почему только с английского на русский? Ну пытаться перевести с русского на английский - это все равно что программу на C++ перевести на бейсик :) ) Пишу на C# уже три года (и на С++ не вернусь ни за что :) ). Переводчик пишу уже 5-ый год. На данный момент более-менее работают следующие компоненты системы: - лексический анализ - морфологический анализ - фразеологический анализ (на данный момент - только-только дописан черновой вариант) - синтаксический анализ Основное достоинство моего переводчика - модульная структура, все элементы алгоритма выполнены как независимые модули, что позволяет расширять и/или улучшать отдельные элементы системы независимо от остальных Последнюю версию можно взять тут: http://ertranslator.narod.ru/ERTranslateIt_21_04_07.rar В комплекте: редактор-переводчик - Editor-translator2.0.exe редактор словаря - BaseEditor2.0.exe редактор синтаксических правил - IdmEdit.exe редактор склонения русских слов - Sklon_Phrases.exe