четверг, 31 марта 2011 г.

Отчет о поиске для сайта магистра

Всем, кто разрабатывает собственный сайт на портале магистров ДонНТУ, необходимо составить отчет о поиске.

Что это такое? Это информация о количестве документов, найденных разными поисковиками по ключевым фразам, касающимся магистерской работы и выводы.

Вводить вручную запросы в поисковик и потом опять же вручную заполнять таблицу как-то не охота, поэтому было решено автоматизировать этот процесс. Результат - небольшое приложение Juchka на Python. Узнав, что мой одногруппник разрабатывает нечто похожее, я решил залить Жучку в открытый репозитарий с целью обмена опытом и, возможно, совместной доработки.

Ссылка: http://www.assembla.com/code/juchka/subversion/nodes

На данный момент приложением уже можно пользоваться - мануал в readme.txt.

P. S. Для тех, кто никогда не пользовался SVN - это не сложно - погуглите "TortoiseSVN Export" (если используете Windows, или "svn export" иначе).

четверг, 17 марта 2011 г.

Проблема "точки и запятой" при преобразовании строки в число с плавающей точкой в .NET (C#)

Достаточно часто возникает задача преобразовать string в float/double. При этом разработчик сталкивается с проблемой "точки и запятой" (разделителя целой и дробной частей десятичной дроби). Задача, в принципе, тривиальная, но все-таки хотелось бы рассмотреть некоторые моменты поподробнее.

Какие есть варианты ее решения?

1) заменить запятую на точку и распарсить:

public static float AsFloat1(this string s)
{
 return float.Parse(s.Replace(",", "."), CultureInfo.InvariantCulture);
}

2) попробовать распарсить с использованием ожидаемой (или как вариант - текущей) локали, и если что не так (FormatException), попробовать распарсить в инвариантной локали (InvariantCulture).

public static float AsFloat2(this string s)
{
 try
 {
  return float.Parse(s, CultureInfo.GetCultureInfo("uk"));
 }
 catch (FormatException)
 {
  return float.Parse(s, CultureInfo.InvariantCulture);
 }
}

3) "более правильный 1-й вариант": заменять запятую не на точку, а на символ, определенный в качестве разделителя в инвариантной локали:

public static float AsFloat3(this string s)
{
 return float.Parse(
  s.Replace(",", CultureInfo.InvariantCulture.NumberFormat.NumberDecimalSeparator),
  CultureInfo.InvariantCulture);
}

Какой же из вариантов наиболее подходящий?

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

Но с другой стороны: как у этих методов с производительностью?

private static void Main()
{
 var strings = new[] {"12,3", "12.3"};
 var functions = new Func<string, float>[]
      {
       x => x.AsFloat1(),
       x => x.AsFloat2(),
       x => x.AsFloat3()
      };

 var watch = new Stopwatch();
 var tests = from function in functions
    select new Action<string>(x =>
             {
              watch.Restart();
              var n = function(x);
              watch.Stop();
              Console.WriteLine("String: {0}; Number: {1}; Time: {2} ms.",
                 x, n, watch.Elapsed.TotalMilliseconds);
             });

 var testIndex = 0;
 foreach (var test in tests)
 {
  Console.WriteLine("Method: AsFloat{0}", ++testIndex);
  foreach (var s in strings)
   test(s);
 }
}

Оказывается, что:

Method: AsFloat1
String: 12,3; Number: 12,3; Time: 0,1989 ms.
String: 12.3; Number: 12,3; Time: 0,0018 ms.
Method: AsFloat2
String: 12,3; Number: 12,3; Time: 0,2546 ms.
String: 12.3; Number: 12,3; Time: 44,7575 ms.
Method: AsFloat3
String: 12,3; Number: 12,3; Time: 0,2659 ms.
String: 12.3; Number: 12,3; Time: 0,0032 ms.
Для продолжения нажмите любую клавишу . . .

Поэтому, с точки зрения производительности, наиболее верным есть 1-й вариант (что, в принципе, не удивительно). Хотя 3-й вариант, который не полагается на знание того, что в инвариантной локали разделитель - точка, не сильно отстал и показывает примерно то же время.

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

воскресенье, 6 марта 2011 г.

"Access volation" при загрузке моделей X-формата (DirectX) в Irrlicht 1.7.2

В некоторых случаях можно столкнуться с ошибкой нарушения прав доступа при чтении памяти (access violation), пытаясь загрузить X-модель в Irrlicht 1.7.2.

device->getSceneManager()->getMesh("Models/model.x");

Где возникает ошибка (первая строка здесь - это 1019 строка в CXMeshFileLoader.cpp):

u32* data = new u32[datasize];
for (u32 j=0; j<datasize; ++j)
 data[j]=readInt();
if (dataformat&0x102) // 2nd uv set
{
 mesh.TCoords2.reallocate(mesh.Vertices.size());
 u8* dataptr = (u8*) data;
 const u32 size=((dataformat>>8)&0xf)*sizeof(core::vector2df);
 for (u32 j=0; j<mesh.Vertices.size(); ++j)
 {
  mesh.TCoords2.push_back(*((core::vector2df*)(dataptr)));
  dataptr += size;
 }
}

Почему возникает ошибка: всего указателем data адресуются datasize*4 байт памяти, при загрузке модели может оказаться, что mesh.Vertices.size()*size больше, чем datasize*4 (в моем случае так и было) - отсюда и ошибка.

Что предлагается:

u32* data = new u32[datasize];
for (u32 j=0; j<datasize; ++j)
 data[j]=readInt();
if (dataformat&0x102) // 2nd uv set
{
 mesh.TCoords2.reallocate(mesh.Vertices.size());
 u8* dataptr = (u8*) data;
 const u32 size=((dataformat>>8)&0xf)*sizeof(core::vector2df);
 u32 boundary = (datasize << 2)/size;
 for (u32 j=0; j<mesh.Vertices.size() && j<boundary; ++j)
 {
  mesh.TCoords2.push_back(*((core::vector2df*)(dataptr)));
  dataptr += size;
 }
}

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

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

Успешной разработки!

суббота, 22 января 2011 г.

jQuery Validation Plugin и поля формы с одинаковыми именами

Широко используемый плагин Validation для jQuery имеет одну не совсем приятную особенность: имена полей должны быть уникальными, иначе восприниматься будет только первое из всех полей с одинаковым именем.

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

воскресенье, 19 декабря 2010 г.

ASP.NET MVC 2: Controller.File

Тем, кто решит вернуть пользователю файл с кириллическим именем, следует учесть, что такое имя будет воспринято только Firefox'ом (тестировал на версии 3.6), Chrome (тестировал на версии  8.0) и IE (тестировал на версии 8.0) заменят имя на имя действия контроллера.

В принципе, поведение объяснимо.

Пример.

public ActionResult Plain(string text)
{
   File(text.ToBytes(), "text/plain", "Текст.txt");
}

Файрфокс отобразит в диалоге сохранения файла: "Текст.txt", Хром же, как и Эксплорер, отобразят "Plain.txt".

Как вариант решения может быть использована транслитерация.

Пример.

public static class TranslitExtensions
{
    private static readonly Dictionary<string, string> letters 
        = new Dictionary<string, string>
        {
            {"№", "N"},
            {"а", "a"},
            {"б", "b"},
            {"в", "v"},
            {"г", "g"},
            {"д", "d"},
            {"е", "e"},
            {"ё", "yo"},
            {"ж", "zh"},
            {"з", "z"},
            {"и", "i"},
            {"й", "j"},
            {"к", "k"},
            {"л", "l"},
            {"м", "m"},
            {"н", "n"},
            {"о", "o"},
            {"п", "p"},
            {"р", "r"},
            {"с", "s"},
            {"т", "t"},
            {"у", "u"},
            {"ф", "f"},
            {"х", "h"},
            {"ц", "c"},
            {"ч", "ch"},
            {"ш", "sh"},
            {"щ", "sch"},
            {"ъ", "j"},
            {"ы", "i"},
            {"ь", "j"},
            {"э", "e"},
            {"ю", "yu"},
            {"я", "ya"},
            {"А", "A"},
            {"Б", "B"},
            {"В", "V"},
            {"Г", "G"},
            {"Д", "D"},
            {"Е", "E"},
            {"Ё", "Yo"},
            {"Ж", "Zh"},
            {"З", "Z"},
            {"И", "I"},
            {"Й", "J"},
            {"К", "K"},
            {"Л", "L"},
            {"М", "M"},
            {"Н", "N"},
            {"О", "O"},
            {"П", "P"},
            {"Р", "R"},
            {"С", "S"},
            {"Т", "T"},
            {"У", "U"},
            {"Ф", "F"},
            {"Х", "H"},
            {"Ц", "C"},
            {"Ч", "Ch"},
            {"Ш", "Sh"},
            {"Щ", "Sch"},
            {"Ъ", "J"},
            {"Ы", "I"},
            {"Ь", "J"},
            {"Э", "E"},
            {"Ю", "Yu"},
            {"Я", "Ya"}
        };

    /// <summary>
    /// Performs translation from Russian to Translit.
    /// </summary>
    /// <param name="russian">String in Russian to translate.</param>
    /// <returns>String in Translit.</returns>
    public static string ToTranslit(this string russian)
    {
        var builder = new StringBuilder(russian);

        foreach (var letter in letters)
        {
            builder.Replace(letter.Key, letter.Value);
        }

        return builder.ToString();
    }
}

public ActionResult Plain(string text)
{
   File(text.ToBytes(), "text/plain", "Текст.txt".ToTranslit());
}

четверг, 4 ноября 2010 г.

Упрощение навигации по DOM

Хотелось бы затронуть тему правильной организации DOM, best practices, так сказать. Возможно немного пространно сформулировал, но смысл станет ясен после примера.

Пример. Форма ввода свойств некоторой сущности. Конкретнее: редактирование данных пользователя.



С чем столкнулся на практике:

<form action="#">
    <input id="UserId" type="hidden" />
    <div>
        <label>Имя:<input id="UserName" type="text" /></label>
    </div>
    <div>
        <label>Описание:<input id="UserDescription" type="text" /></label>
    </div>
</form>

Запрос с помощью jQuery выглядит следующим образом:

function GetUser() {
    return {
        id: $("#UserId").val(),
        name: $("#UserName").val(),
        description: $("#UserDescription").val()
    };
}

И казалось бы, все отлично. Если бы не:

  • а если у нас несколько элементов управления для ввода данных о пользователях - конфликт идентификаторов (идентификатор должен быть уникален в пределах страницы)?
  • а если наша сущность называется OurEntityWithVeryVeryReallyVeryLongName и имеет множество полей, представляете себе кошмар в виде id="OurEntityWithVeryVeryReallyVeryLongNameFieldWithLongName1" и тому подобное?
Что предлагаю:

<form class="User" action="#">
    <input class="Id" type="hidden" />
    <div>
        <label>Имя:<input class="Name" type="text" /></label>
    </div>
    <div>
        <label>Описание:<input class="Description" type="text" /></label>
    </div>
</form>

Теперь и запрос с помощью jQuery выглядит иначе:

function GetUser() {
    var user = $(".User");
    return {
        id: $(".Id", user).val(),
        name: $(".Name", user).val(),
        description: $(".Description", user).val()
    };
}

Как по мне, вполне приемлемо, и даже похоже на объектную нотацию ("User.Name").

Возможно где-то в Сети уже есть подобное или лучшее решение, как делают Про я не искал (зря? тогда жду комментариев), сразу решил поделиться пришедшей мыслью. На этом все. Удачной разработки!

вторник, 2 ноября 2010 г.

jQuery: позиционирование элементов

Простая задача - разместить абсолютно позиционированный (в стиле определено: position: absolute) div под элементом ввода (input).

var input = $("input");
var offset = input.offset();

var x = offset.left;
var y = offset.top + input.height();

$("div").offset({
    left: x,
    top: y
});

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

Окончательное решение далее.

var input = $("input");
var offset = input.offset();

var x = offset.left;
var y = offset.top + input.height() + $(window).scrollTop();

$("div").offset({
    left: x,
    top: y
});