Wednesday, December 29, 2010

Подсчитать число уникальных слов

На RSDN обсуждался интересный вопрос - как подсчитать число уникальных слов в тексте. Если слова раскиданы в Excel, то сделать это очень просто.

Например, слова записаны в столбец A. Рядом в столбец B пишем формулу:

=IF(COUNTIF($A$1:A1;A1)=1;1;0)
=IF(COUNTIF($A$1:A2;A2)=1;1;0)

И так для каждой строки в A. Разумеется саму формулу руками не вбиваем - ее можно скопировать в буфер и, выделив весь столбец B, вставить. Или вбить ее в первую строку B и потом растянуть.

Теперь в C1 можно написать =SUM(B:B). Получим сколько уникальных слов в столбце A.

Tuesday, December 28, 2010

Опасный IndexOf

Нашел интересный пост Wei Zhao про поиск и сравнение строк. Задача формулируется так: заменить все двойные пробелы на одиночные. Код очень простой:
            while (original.IndexOf("  ") != -1)
            {
                original = original.Replace("  ", " ");
            }

Для “обычных” строк все работает отлично.
А вот если строка original содержит символы пробелов в Unicode, то этот код зависает. Вот такая например:
  string original = new string(new char[]{ '\u0020', '\ufffd', '\u0020' });

Проблема заключается в том, что метод IndexOf ищет в текущей культуре, а символом \ufffd обозначаются ошибочные Unicode-символы. Другими словами, IndexOf будет возвращать всегда ноль:
  char[] test = { '\u0020', '\ufffd', '\u0020' };
  string testStr = new string(test);
  int i = testStr.IndexOf("  "); // вернет 0, а не -1

Более подробно можно прочитать по ссылке выше в блоге автора.
А правильный вариант кода такой:
     while (original.IndexOf("  ",  StringComparison.Ordinal) != -1)
     {
        original = original.Replace("  ", " ");
     }

Теперь код зависать не будет ни при каких обстоятельствах.

Friday, December 24, 2010

Обработка необработанных исключений в WinForms

Обработчик добавляется следующим образом:

Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);

Но этого мало. Нужно еще "включить" работу этого обработчика:

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

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

Wednesday, December 22, 2010

Отображение дня недели в Excel

  1. Выделите ячейки, содержащие даты, которые должны отображаться в виде дней недели.
  2. На вкладке Главная в группе Число щелкните стрелку, нажмите кнопку Другие числовые форматы, а затем откройте вкладку Число.
  3. В поле Числовые форматы выделите пункт (все форматы) и в поле Тип введите значение дддд, чтобы отображались полные названия дней недели (понедельник, вторник и т. д.), либо ддд, чтобы отображались сокращенные названия дней недели (Пн, Вт, Ср и т. д.).
http://office.microsoft.com/ru-ru/excel-help/HP010070474.aspx

Sunday, December 12, 2010

KeyNotFoundException

В стандартной реализации исключение KeyNotFoundException сообщает что ключ не найден, но почему-то не сообщает какой именно. Исправить ситуацию можно так:

    public class MyDictionary : Dictionary<string, object>
    {

        public new object this[string key]
        {
            get
            {
                object value;
                if (!base.TryGetValue(key, out value))
                    throw new KeyNotFoundException(string.Format("Значение [{0}] не найдено", key));
                else
                    return value;
            }
            set
            {
                base[key] = value;
            }
        }

    }Теперь это исключение можно поймать и сообщить пользователю что-то осмысленное.

Saturday, December 11, 2010

Антипаттерн "наивный код"

Не знаю, наверное, у этого антипаттерна есть красивое название. Смысл его в том, что программист пишет код, не задумываясь или просто надеясь, что программа будет работать в тех же условиях, что и разрабатывалась.
Вот очередной пример:
   file.Open("C:\\AutoSystem\\nastroika.dat",CFile::modeRead);
Почему собственно файл настроек должен лежать именно там? Да и вообще кто сказал, что диск C будет существовать на том компьютере, где будет устанавливаться система?

Тоже самое касается веб-варианта, когда URL вычисляется сложением
  "http://" + serverName + "/" + pageName
Запустить такой сайт через https не получится - протокол зашит в код намертво.

Бессмысленные комментарии

Даже самые несогласные соглашаются, что комментарии в коде должны быть (я уже писал зачем). Но – какой смысл писать их не понимая смысла этого действия?
Как я загнул… J Но если в тексте эта заумная фраза выглядит странно, то почему некоторые программисты считают, что аналогичные комментарии выглядят нормально:
  CStdioFile file;// объявление класса file
Правда ведь все понятно стало?
Или так:
  i++; // увеличиваем i на 1
  if (count > 0) // если больше нуля
И не лень кому-то тратить свое время на набор этих буквичек…

Friday, December 10, 2010

Наследование классов (часть 2)

Итак, у нас два класса – Door и Window и базовый для них BaseObject (см часть 1). В базовом классе есть методы Open и Close.
class BaseObject
{
    private bool isOpened;
    public void Open()
    {
        isOpened = true;
    }
    public void Close()
    {
        isOpened = false;
    }
}

class Door : BaseObject
{
}

class Window : BaseObject
{
}

Мы знаем еще, что для Door при открытии и закрытии нужны дополнительные действия (т.е. методы) – открыть и закрыть дверь ключом (OpenByKey, CloseByKey). Добавить их не сложно:
class Door : BaseObject
{
    public void OpenByKey()
    {
    }
    public void CloseByKey()
    {
    }
}

Проблема будет в том, что при вызове метода Open нужно не забыть вызвать метод OpenByKey, иначе к нам заберутся воры J Ну а там где есть слово “нужно не забыть” – забудут наверняка, тем более, что не понятно, как подсказать программисту, использующему наш класс, что этот вызов вообще нужен.
Хорошо бы иметь свою реализацию метода Open, такую, что в ней уже вызывается работа с ключом. Получается, что с одной стороны, мы хотим использовать базовый класс и его реализацию, чтобы не дублировать код. С другой – иметь собственную реализацию этого метода.
Оказывается, сделать это очень легко – для этого используется перекрытие методов.
Метод в базовом классе мы должны обозначить как перекрываемый, добавив к нему слово virtual (метод будет называться виртуальным, но я пока не буду вдаваться в подробности почему):
class BaseObject
{
    private bool isOpened;
    public virtual void Open()
    {
        isOpened = true;
    }
    public virtual void Close()
    {
        isOpened = false;
    }
}

А в классе наследнике мы должны будем обозначить, что мы перекрываем метод базового класса. Для этого используется слово override:
class Door : BaseObject
{
    public override void Open()
    {
        base.Open();
        OpenByKey();
    }

    public override void Close()
    {
        base.Close();
        CloseByKey();
    }

    private void OpenByKey()
    {
    }
    private void CloseByKey()
    {
    }
}

Обратите внимание на две вещи. Во-первых, на вызов base.Open и base.Close в перекрытых методах. Это означает, что мы вызываем методы базового класса. И, во-вторых, методы работы с ключом я сразу же сделал закрытыми (private), т.к. больше нет необходимости вызывать их самостоятельно – все будет сделано внутри классов.

Thursday, December 9, 2010

Реализация треугольника

Задача заключалась в создании класса треугольник с заданными сторонами. Нужно было сделать проверку, что это действительно треугольник. Ну и плюс подсчитать его параметры.
Вот пример кода (на C++), с которым мы и будем разбираться:
class triangle
{
private:
  double x,y,z;
public:
  triangle()
  {
     x=1;y=1;z=1;
  }

  triangle(double a,double b,double c)
  {
    if (a+b>c && c+b>a && a+c>b)
    {
       a=x; b=y; c=z;
    }
    else
     cout<<"please try one's more"<<endl;
  }
  double sumSide(double a,double b,double c)
  {
     return (a+b+c);
  }
  double square(double a,double b,double c)
  {
    double p=sumSide(a,b,c)/2;
    return sqrt(p*(p-a)*(p-b)*(p-c));
  }
  void type(double a,double b,double c)
  {
    if ( ( a*a > b*b+c*c ) || ( b*b > a*a+c*c ) || ( c*c > a*a+c*c ) )
      cout<<"one angle is more that 90 digreas"<<endl;
    else
      ........удалил часть кода.........
  }
  void angle (double a,double b,double c)
  {
     double alpha,betta,gamma;
     alpha=acos( (b*b+c*c-a*a)/(2*(b*c)) );
     betta=acos( (b*b+a*a-c*c)/(2*(b*a)) );
     gamma=acos( (c*c+a*a-b*b)/(2*(c*a)) );
     cout<<"alpha= "<<alpha<<endl;
     cout<<"betta= "<<betta<<endl;
     cout<<"gamma= "<<gamma<<endl;
  }
};

int main()
{
  freopen("input.txt","rt",stdin);
  freopen("output.txt","wt",stdout);
  double a,b,c;
  cin>>a;
  cin>>b;
  cin>>c;
  triangle firstTriangle(a,b,c);
  cout<<firstTriangle.square(a,b,c)<<endl;
  firstTriangle.type(a,b,c);
  firstTriangle.angle(a,b,c);
  return 0;
}

Что мне не нравится и вызывает вопросы:
·         Почему в конструкторе создается треугольник 1/1/1? Нужен ли вообще такой конструктор? Ведь есть же тот, который сразу принимает три параметра. Мне кажется что пустой конструктор просто не нужен. Но тут зависит от задачи.
·         Во втором конструкторе проверка что это не треугольник есть, но – если это не треугольник, то выводится сообщение и внутренние переменные не инициализируются вообще. Т.е. в результате получается не понятный объект с непонятными полями.
·         Выводить сообщения на консоль в конструкторе и вообще в других методах – не правильно. Это не задача класса треугольник сообщать пользователю об ошибках. Это задача того класса, который пытается создать этот треугольник. А уже как он будет его выводить – другой вопрос. Может быть это вообще будет WinForms-приложение, а не консольное.
·         Мне одному кажется что этот код вообще не работает и должно быть ровно наоборот: x=a; y=b; z=c; а не как сейчас?
·         Зачем вообще нужны эти x,y,z если они не используются нигде, а везде суется a,b,c? По идее методы sumSize и все другие вообще не должны иметь параметров, а использовать x,y,z. См. предыдущий пункт - оно и работать тогда не будет.
·         Метод type по идее должен возвращать тип треугольника, а не выводить его на консоль.
Дополнительно:
·         Не проверяется что файл открыт успешно и из него можно читать
·         Не проверяется что прочитано хоть что-то
·         Не проверяется что файл создан и в него можно писать

Я попробую описать несколько возможных вариантов реализации.

Вариант 1 – генерация исключений в конструкторе


Этот вариант хорошо работает для C# и не очень хорошо для С++.

    public class Triangle
    {
        private double x;
        private double y;
        private double z;

        public Triangle(double x, double y, double z)
        {
            if (Check(x, y, z))
            {
                this.x = x;
                this.y = y;
                this.z = z;
            }
            else
            {
                throw new ApplicationException("Неверные параметры треугольника");
            }
        }

        private bool Check(double x, double y, double z)
        {
            // проверяем все что надо и возвращаем true/false
            // если надо – можем тут кинуть дополнительные исключения
        }
    }

В этом случае создать некорректный объект просто не получится, о чем мы и сообщим тому, кто будет создавать:

            try
            {
                Triangle t = new Triangle(10, 10, 10);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Не удалось создать треугольник с такими параметрами. Причина: " + ex.Message);
            }

Причем ловить это исключение нужно в том месте, где мы готовы его обработать. В данном случае – в основной программе.

Вариант 2. Статический метод


Если конкретная причина почему не удалось создать объект треугольника нас не интересует, то можно добавить статический метод:

    public class Triangle
    {
        ..............................
        public static Triangle TryCreateTriangle(double x, double y, double z)
        {
            try
            {
                return new Triangle(x, y, z);
            }
            catch
            {
                return null;
            }
        }
    }

И тогда просто проверяем получилось или нет:

Triangle t1 = Triangle.TryCreateTriangle(10, 10, 10);
if (t1 != null)
{
    // создали и можно с ним работать
}
Но это очень редко бывает и в самых простых случаях, типа int.TryParse(). В большинстве случает так делать не стоит, т.к. здесь глушатся совершенно все исключения и найти причину ошибки будет сложно.

Вариант 3. Тип треугольника


Еще вариант – делаем перечисление:

    public enum TriangleType
    {
        Error = 0,
        OneMore90 = 1,
        OneEqual90 = 2,
        AllLess90 = 3
    }

В конструкторе проверяем и вычисляем все нужные поля и тип треугольника:

        public Triangle(double x, double y, double z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
            this.type = GetTriangleType();
        }
В методе GetTriangleType делаем и проверки и вычисления типа.
А при создании просто проверяем тип – если он не Error, то все хорошо и треугольник какой-никакой, но получился. Т.к. кроме наших собственных исключений у нас больше тут ничего не планируется, то их можно и не делать и обойтись этим вариантом.

            Triangle t2 = new Triangle(10, 10, 10);
            if (t1.Type == TriangleType.Error)
            {
                Console.WriteLine("все плохо");
            }

Какой вариант выбрать


Выбор варианта зависит от конкретной задачи. Если вам на выбор предложат стол или стул, то первый вопрос будет – “для чего?” Чтобы определиться с конкретной реализацией (здесь я привел всего три варианта, а их можно придумать и больше), нужно понимать в каких условиях это будет работать, в каком месте кода и т.д.
Прежде всего нужна постановка бизнес-задачи. Т.е. задача должна формулироваться с точки зрения пользователя реализуемой программы, а не просто “напишите класс треугольник”. Написать “просто” сложно – слишком много вариантов. Конечно тот вариант, который я показал в начале вообще не проходит. Но и нормальных вариантов реализации очень много.
Что должно происходить, если не удалось создать объект? Просто сообщить об этом пользователю? Попросить пользователя ввести данные повторно? Данные вводит пользователь или они читаются из файла? В файле хранится один треугольник или их список?
Нужен ли вариант с генерацией исключений? Вообще говорят, в таком простом коде – нет. Исключения очень ресурсоемкие и их использование существенно усложняет и замедляет код. Если других исключений, кроме наших собственных не планируется, то можно обойтись и без исключений, например, с помощью варианта с типом треугольника, а причину ошибки, если она есть – хранить в специальном поле.
Если код будет сложнее, например, данные будут читаться из XML или из БД, то возможны другие исключения в этом коде – файл может быть недоступен, иметь неверный формат, в нем могут храниться не числа, числа неверной размерности и т.д. Другими словами, у нас уже есть возможный список исключений и так или иначе, нам придется их обрабатывать. А значит создавать еще один, параллельный механизм смысла не имеет и проще работать с механизмом исключений, добавив к ним свои.