вторник, 4 августа 2015 г.

Каждому по способностям, или причуды разработчиков

Однажды, пришел к администратору базы данных сотрудник и сказал:
 - о великий админ, организуй мне, пожалуйста, в нашей базе вот такие вот таблички!
И админ вопросил его:
- А зачем тебе такие таблички?
- Я буду записывать в ключевую таблицу караваны верблюдов и вот в эти красивые таблички различные отчеты по их работе: как торговых, как фото-услуг, как прокатных.
 - Ты не познал науку проектирования баз данных, сотрудник. Почему в этой таблице у тебя сразу две сущности? Ведь в одном караване может быть более одного верблюда? Или забыл ты, что "одна сущность - одна таблица" должны быть?
 - Не забыл я, о великий админ, но связь между сущностями этими - один к одному. А потому смело объединяю я их, во благо моих запросов.
 - Помнишь ли ты, сотрудник, что в прошлый раз втвои верблюды могли объединяться в караваны произвольной длины?
 - Помню, о админ, но это другие верблюды, другие караваны, другие товары и другие работы и я хочу для них одну таблицу!

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

Прошел год

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

И призадумался молодой админ:
Ну структуру-то я новую создан, но... как сохранить ему его данные?.. Да еще и раскидать их по новой структуре?

Так админ узнал о новой штуке:

CREATE TABLE  test (a1 integer, a2 varchar(16));
INSERT INTO test(a1, a2) SELECT b1, b2 FROM source_table_name;

Примечание:
http://www.w3schools.com/sql/sql_select_into.asp
http://stackoverflow.com/questions/25969/sql-insert-into-values-select-from



воскресенье, 14 июня 2015 г.

ООП и JavaScript

Последние несколько недель свободного времени мне пришлось потратить на осознание одного простого факта: "Не все объекты одинаково полезны."

Идея была проста: "А давайте сделаем js игру-тамагочу?" Пусть это будет... хомяк?..

И сегодня расскажу вам о том, как си-шарпер пытается познать ООП в малознакомом ему яваскрипте.

Если не художественно, то материалы, которые спасли мою психику находятся здесь: http://javascript.ru/tutorial/object .

Тем не менее, здесь будет еще и краткий конспект и без того краткого мануала.

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

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

Если бы я писала на шарпах все было бы просто. Но не вопрос думаю я, я так часто слышу, что js это ооп объект. Есть столько расширений, библиотек, фреймворков... наверное и я смогу это освоить.

Поэтому... я открываю гугл и забиваю что-то вроде "классы в js". И сразу узнаю, что я могу создать объект конструкцией вроде:

window.Hamster = {

    status = "good",    health = 50,    age = 0
}

Что, в целом, неплохо.
Я могу добавить методы:

window.Hamster = {

    ... ,
    EatSmth : function(n) { this.health =+ this.health + n; },
    CheckHealth : function() { if (this.health < 50) this.status = "ill"; }
}

И вот тут я думаю, а что бы мне не вызвать функцию проверки здоровья собственно сразу при изменении здоровья?.. И... и тут начинаются проблемы. Потому, что как это не удивительно для меня, старого си-шарпера, код...

this.CheckHealth();

...удивительным образом не работает. 

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

Примечание: зато работает:

EatSmth : function(n) 

    this.health =+ this.health + n; 
    window.Hamster.CheckHealth();
},

В общем, один статический класс с кучей статических методов: никаких экземпляров, доступ к объекту от окна и невозможность адекватной работы методами. Если подходить к вопросу более подковано, то мы совершенно неожиданно для себя имеем реализацию паттерна Одиночка - он же Singleton. Что, в целом, круто (взял, что-то накодил, бац, реализовал паттерн...), но совершенно не о том, что нам надо и потому неприемлемо.

Что же делать мне, при желании реализовать свой объектный замысел?

Факт первый, чтобы создавать одинаковые структуры нужно создать функцию. Любая функция в js может создать объект, если вызывается через  new.  Полями объекта являются все определенные в функции через this поля.
Чтобы создать тип объекта нам нужна своего рода порождающая функция.

function Hamster() { 
    this.status = "good";    this.health = 50;
    this.EatSmth = function(n) 
    { 
       this.health =+ this.health + n; 
       this.CheckHealth();
    }
    this.CheckHealth = function() {
       if (this.health < 50) this.status = "ill"; 
    }
}

var MyNewHam = new Hamster();
MyNewHam.EatSmth(5);

И, вуаля, я могу иметь сколько угодно хомяков c какой мне угодно сложностью цепочек вызова.

И хотя задача решена... Но это не самое интересное, "что я узнал сегодня". Тем более, что углубляясь в задачу у меня возник вопрос наследования

Факт второй.
В js нет разницы между классом и переменной (как можно было уже заметить на самом первом примере, который оказался реализацией singleton). По крайней мере в явном виде, как я привыкла в C#.

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

function BaseParam() { 
    this.status = "good";    this.value = 50;
}

И унаследовать от него свои параметры, которые уже буду вставлять в хомяка. Например, здоровье. И, собственно, для таких случаев, когда очень хочется вставить в свою архитектуру немножко наследования в js есть прототипирование! У нас нет возможности создавать классы в явном виде... зато мы можем наследоваться от другой переменной. Для этого внутри каждой функции есть свойство prototype, через которое происходит наследование создаваемыми функцией объектами. По умолчанию, все создаваемые объекты наследуются от класса Object.

Вообще говоря, каждый раз когда мы создаем новый объект-структуру: var a = {}  неявно вызывается функция конструктор  var a = new Object();

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

Поэтому создание моих параметров будет выглядеть, например,  так:

function HealthParam(current_health) {
    this.value = current_health;

    this.EatSmth = function(n) 
    { 
       this.value =+ this.value + n; 
       this.CheckHealth();
    }

    this.CheckHealth = function() {
       if (this.value < 50) this.status = "ill"; 
    }
}
HealthParam.prototype = new BaseParam();

function Hamster() { 
    this.Health = new HealthParam(50);
    this.Strength = new StrengthParam(50);
    this.EatSmth = function(n)
    {
        this.Health.EatSmth(n);
        this.Strength,EatSmth(n);
        //все что еще мне нужно по событию кормления хомяка
    }
}

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

В попытке создать хомяка использованы:
1. http://javascript.ru/tutorial/object
2. Хабрахабр: Использование паттернов проектирования в javaScript: Порождающие паттерны

А также для более хитрых и англоязычных читателей:
3. http://shichuan.github.io/javascript-patterns/
4. http://addyosmani.com/resources/essentialjsdesignpatterns/book/

четверг, 30 апреля 2015 г.

CodeStars. Track 05.

Задача
Чтобы твой трек попал в ротацию на online радио для айтишников, реши следующую задачу: Билет на твой первый концерт в Лужниках содержит 6 цифр от 0 до 5. Сколько среди всех билетов «счастливых», т.е. таких, для которых сумма первых 3 цифр равна сумме последних 3 цифр?

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

Наш номер билета можно представить как AB, где А сумма первой половины билета, В - сумма второй половины билета. А и В входят в множество Х. Счастливыми будут билеты AA, где А = В.

Минимальная сумма у размещения: "000" = 0, максимальная у "555" = 15, т.е. всего Х = 16 различных сумм. Каждую сумму можно собрать из заданных чисел различными способами, назовем  эти способы функцией S(x). Тогда наш счастливый билет для суммы    х   можно представить как:

AB => AA => S(x) S(x)

Т.е. счастливых билетов для каждой суммы это количество перестановок функции S(x) по двум позициям = S(x)^2

Осталось найти сколькими же способами можно собрать каждую сумму и просуммировать это безобразие.

Тут есть два способа: перебором и математикой. Я, пожалуй, воспользуюсь формулой из вот этой статьи , с учетом того что у нас n = 3 позиции и цифры от 0 до 5

 5
 N3(x) =  N2(x – l),
l=0

при этом  N1(x) =  1 (x от 0 до 5) или 0 (x > 5)
Если l > x, слагаемое возвращает 0.

Решение.

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

public partial class MainWindow : Window
{
    List<int> ValidNumbers = new List<int>() { 0, 1, 2, 3, 4, 5 };
    int Positions = 3; 

    public MainWindow()
    {
        InitializeComponent();
    }

    private void EncryptBtn_Click(object sender, RoutedEventArgs e)
    {
       int result = 0;

       int min_sum = ValidNumbers.Min() * Positions;
       int max_sum = ValidNumbers.Max() * Positions;
       for (int x = min_sum; x < max_sum + 1; x++)
       {
          result = result + Convert.ToInt32(Math.Pow(GetNByX(x, Positions), 2));
       }
       this.ResultTB.Text = result.ToString();
    }

    private int GetNByX(int x, int division)
    {
       if ((division == 1) && (!ValidNumbers.Contains(x)))
          return 0;
       else if ((division == 1) && (ValidNumbers.Contains(x)))
          return 1;
       else
       {
          int result = 0;
          for (int l = 0; l < ValidNumbers.Max() + 1; l++)
          {
              if (l <= x)
                 result = result + GetNByX(x - l, division - 1);
          }
          return result;
       }
    }
}
Примечание:
Использовано:

  • Википедия
  • Подборка статей о вычислении количества счастливых билетиков: http://www.ega-math.narod.ru/Quant/Tickets.htm

Картинка, музычка и проектик:






среда, 11 марта 2015 г.

Если данные есть, но их нет...

Загадка:
SELECT * from t1 where t1_id = 99; - пустота.
SELECT * from t2 where t1_id = 99; - две строчки.
SELECT t1_id FROM t2 WHERE t1_id NOT IN (SELECT t1_id FROM t1); - пустота.

Как это возможно?

История.

Не разворачивается dbimport базы. Создает таблицы, грузит данные, начинает разворачивать схему и... "525 Failure to satisfy referential constraint" - не могу повесить на эти данные внешний ключ, потому что в данных нарушена целостность. (в таблице на которую я ссылаюсь нет такого значения первичного ключа, какое есть в столбце ссылок)

Удивляемся. Лезем в данные и ищем как же так вышло:
SELECT t1_id FROM t2 WHERE t1_id NOT IN (SELECT t1_id FROM t1); - две строчки.
Не целостные ссылки - это ссылки на t1_id = 98 и 99.
Ищем данные по их первичному индексу: SELECT * from t1 where t1_id in (98,99); - пустота.
Ищем данные, которые ссылались на них: SELECT * from t2 where t1_id in (98, 99); - две строчки.
Удивляемся. Проверяем наличие каскадного удаления по внешнему ключу в таблице t2. - все хорошо, каскадное удаление присутствует.

Еще больше удивляемся и лезем в базу, с которой сделан экспорт.

SELECT * from t1 where t1_id in (98,99); - пустота.
SELECT * from t2 where t1_id in (98, 99); - две строчки.
SELECT t1_id FROM t2 WHERE t1_id NOT IN (SELECT t1_id FROM t1); - пустота...
Каскадное удаление присутствует.

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

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

воскресенье, 1 марта 2015 г.

Шифрование на уровне столбцов таблиц в Informix Dynamic Server:

http://www.ibm.com/developerworks/ru/library/dm-0711mohan/

Неожиданный и потом полезный для меня взгляд на суррогатные ключи в БД:

http://habrahabr.ru/post/107834/

GraphSharp + QuickGraph или CodeStars. Track 04. Визуализация хоть чего-нибудь

Я выбрала для решения своей задачи QuickGraph потому что не нашла ему альтернатив, кроме самостоятельной реализации алгоритма.
И я хотела добавить своему решению лоска визуализацией. У QuickGraph есть сборка QuickGraph.Glee  Но, судя по всему, ее надо было ставить дополнительно.
А у GraphSharp есть WPF контроллер... и он использует QuickGraph. "Сказка же!" - думаю я: минимум зависимостей, все аккуратненько. С контроллером. Легко и просто...

Но пришлось создавать отдельный пост, потому что просто не получилось.

Все, казалось бы просто. У меня уже есть граф. Я его посчитала и теперь хочу отобразить.
Тем временем, чтобы отобразить граф с помощью Graph# есть два способа.

Визуализация графа Graph#

Простой способ.
Мануал: http://www.youtube.com/watch?v=VTbuvkaPGxE
Встроенный WPF контроллер GraphLayout  позволяет привязать к себе IBidirectionalGraph<object, IEdge<object>>. Что и делают в имеющемся мануале.
Но только его.
Нюанс в том, что, например BidirectionalGraph<object, Edge<object>> не может быть преобразован  к данному интерфейсу никаким приведением.
В то же время алгоритмы кратчайшего пути из QuickGraph работают именно с графами, а не интерфейсами.

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

Полный и абсолютно настраиваемый способ. 
Мануал:  https://sachabarbs.wordpress.com/2010/08/31/pretty-cool-graphs-in-wpf/
Мы описываем с самого начала тип данных вершины, тип данных ребра, тип графа (наследуем от Bidirectional...), свой Layout для отображения (наследуем от встроенного) и далее по тексту.

Это трудоемко, но можно попробовать.

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

if (!graph.ContainsVertex(language_from))
   graph.AddVertex(language_from);

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

Визуализация произвольного графа с базовыми типами в Graph#

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

Мне надо сначала заполнить граф, потом посчитать, потом отобразить.
Заполнение и расчет требуют одних интерфейсов, а отображение другого (вполне конкретного).

Поэтому я не нашла ничего лучше, чем перед отображением копировать граф в новую структуру, приспособленную к GraphLayout:

private void ShowGraph(BidirectionalGraph<object, Edge<object>> graph)
{
  var graphForVis = new BidirectionalGraph<object, IEdge<object>>();

  foreach(object node in graph.Vertices)
  {
     graphForVis.AddVertex(node);
  }
  foreach (Edge<object> edge in graph.Edges)
  {
     graphForVis.AddEdge(new Edge<object>(edge.Source, edge.Target));
  }

  //this.LanguageGraphCanvas - мой LayoutGraph контроллер
  this.LanguageGraphCanvas.Graph = graphForVis;
}       

Буду считать это такой разновидностью контроллера между моделью данных (QuickGraph) и их представлением (Graph#). 

Плюсы такого решения:

  1. Я, в процессе экспериментов, изменила тип графа на Bidirectional. Но могла бы этого и не делать. Как следствие: мне не пришлось ради отображения менять данные и механику работы с ними.
  2. Минимальное количество дополнительного кода. Описание классов вершины, ребра, графа... - очень объемно и трудоемко, т.е. избыточно, при том что мне никаких особых свойств не надо.
  3. Так можно отобразить если не любой граф, то достаточное их количество и при этом решить проблему с совместимостью типа графа с мат. методами QuickGraph.
Минусы:
  1. this.LanguageGraphCanvas.Graph = graphForVis; 
    Привязка данных к WPF контроллеру из кода скорее моветон, чем норма. Но чтобы привязка была человеческая тип графа должен бы поддерживать INotifyPropertyChanged - как в примере - чтобы отображение привязанного сразу из XAML графа изменилось после заполнения. 


Ссылки (cобраны из обоих постов в кучку):

1. Визуализация средствами QuickGraph.Glee http://quickgraph.codeplex.com/wikipage?title=Visualization%20Using%20Glee .
2. Домашняя страница и конкретно мануалы по Graph#  http://graphsharp.codeplex.com/wikipage?title=Tutorials&referringTitle=Home
3. Пример работы QuickGraph и Graphiz
http://dotnet.wonderu.com/2009/05/blog-post.html

Картинка, Проект. Музычка





суббота, 28 февраля 2015 г.

CodeStars. Track 04. Математика

Задача.
Чтобы узнать, сколько комментариев у тебя будет на GitHub, реши следующую задачу:На международном музыкальном фестивале участники говорят на множестве разных языков. Чтобы все могли понимать друг друга, организаторы предложили использовать автоматические переводчики, но переводчики есть не для всех пар языков. В текстовом файле в каждой строке содержится (через пробел) имя переводчика, с какого языка и на какой он может переводить. Какое минимальное количество переводчиков необходимо, чтобы переводить с Исландского на Албанский?

Предисловие.
Ох лол... у меня не будет комментариев на GitHub, потому что у меня нет аккаунта на GitHub. Но задача есть задача. Классическая задача на графы, каждый язык - вершина графа, а переводчик - ребро графа, связывающее языки. И надо найти кратчайший путь в графе. Структура данных проста: есть язык, точки входа и точки выхода. Алгоритм известен... что означает, мы с легкостью можем его загуглить, например здесь (Задача о кратчайшем пути в Википедии)Наученная прошлым опытом и вооруженная ленью, я предполагаю, что библиотеки работы с графами для C# уже есть. Есть три аспекта работы с графами: создание структуры данных, алгоритмы и отображение. Почти все библиотеки поддерживают создание структур и их отображение. Различия в основном лежат в алгоритмах визуализации, различных параметрах визуализации, механизмах выгрузки картинок (например в pdf), способа ввода данных и возможностях автоматической генерации графа. А вот математические алгоритмы реализует только QuickGraph. Может быть и не только он.. но остальных мне найти не удалось. )) 
  1. Microsoft Automatic Graph Layout (MSAGL) она же Glee - собственно была библиотека Glee, теперь она же перекуплена MS, доработана и называется страшной аббревиатурой MSAGL. Также иногда в инетиках именуется Microsoft Glee. Что не совсем корректно, по ссылке есть раздел отличий.
  2. Graphviz но в нем мне не устанавливая разобраться не удалось. Хотя в этом формате вот здесь есть пример: QuickGraph + Graphiz  . Если верить документации на сайте очень развитые варианты  вывода и настроек отображения.
  3. Graph# - по факту WPF контроллер для отображения графов. Содержит несколько алгоритмов визуализации. 
  4. NodeXL - вроде работает только с графами заданными через Excel.
  5. QuickGraph - библиотека обеспечивающая мат. алгоритмы работы с графами. А также позволяет выводить графы через Glee, Graphiz. 
Решение.
Сначала разберемся с математикой. 
  1. Добавляем QuickGraph в ссылки и включаем использование:

    using QuickGraph;

    using QuickGraph.Algorithms.ShortestPath;
  2. Создаем граф. Так как переводчик единица универсальная - в том смысле, что, вообще говоря, переводить может в обоих направлениях, то создаем ненаправленный граф.

     
    UndirectedGraph<string, Edge<string>> languageGraph = null;
  3. Пишем метод, для заполнения этого графа из файла. Предположим FillGraph();
  4. Думаем над тем, какой нам нужен алгоритм. Мне нравится Дейкстра. Это ничем не обосновано, на самом деле.

    var a = 
    new UndirectedDijkstraShortestPathAlgorithm<string, Edge<string>>(languageGraph, edgeCost);
  5. Используем алгоритм:
    -
      Назначаем ключевую вершину:

     
    a.SetRootVertex("Исландский");

    Делаем расчет алгоритма: a.Compute(); Это по факту запускает алгоритм с заданным графом и настройками.
    - Получаем искомое: 


    if (a.TryGetDistance("Албанский", out path_length))
                   MessageBox.Show(path_length.ToString());
    else
         
       MessageBox.Show("Что-то не так.");

  6. Получаем правильный ответ. 
Визуализация  : to be continued. 

пятница, 27 февраля 2015 г.

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

Прекрасный человек оглядел рабочий монитор, тяжело вздохнул при виде моей RAD Studio 2010 и выдал установщик CnWizard.

Теперь мой делфи код не страшно открывать и можно читать. В нем теперь видно связки begin - end, легко смотреть где что и как используется...  а кнопочки в формах просто выравнивать.

Как солнцем осветили.

четверг, 26 февраля 2015 г.

Informix, Logical Logs и onlog или как узнать кто испортил наши данные

Как бы ни было клево решать простые задачки из курса дискретной математики... Иногда надо работать.

3 вещи которые я узнала за сегодня:
1.  Информикс умеет (и ведет по умолчанию и, по ходу, помешать этому нельзя) вести логи транзакций. Называется эта штука Logical Logs.
Посмотреть их можно командой onlog. Главное - не пугаться вывода.
Вывод этой команды разнообразен и прекрасен. Поэтому: как интерпретировать записи логического журнала Informix. Там вообще рядом много всего интересного. Возможно позже напишу пример.
По умолчанию эти логи бекапятся вместе с базой.

2. Для ведения контролируемых логов изменения могут оказаться полезными ключевые SQL слова "CURRENT" и "USER". Первое вернет текущую дату-время, второе - юзера от которого был запрос.

Пример: INSERT INTO test (dt, username) VALUES (CURRENT, USER);

3. Интересное про аудит (т.е. учет) изменений данных и стуктуры БД: http://rsdn.ru/article/db/db_audit.xml.

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

Codestars. Track 03.

Задача

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

Узнай, сколько всего языков существует у муз.

Лирическое отступление.

Первый вопрос, который бросился в глаза и ввел меня в недоумение: "А одна муза может говорить только на одном языке? Или они полиглоты?.."
Для начала, предположу, что каждая муза говорит только на одном языке. Просто потому, что я понятия не имею как решать эту задачу в обратном случае.
Тогда, внутри каждого языка существует множество общающихся друг с другом муз. И нам надо найти, всего лишь, количество этих множеств.
Из предположения 1 муза - 1 язык есть два любопытных следствия:

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

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

Решение.

Для начала я составлю список муз и их контактов.
Муза у нас уникальная и обозначается номером. Так как мне лень каждый раз при сравнении делать Convert.ToInt32() (а еще потому, что называть муз числами как-то грустно, и это могли бы быть имена...) то моя структура для хранения записных книжек муз выглядит так:

Dictionary<string, List<string>> Muses = new Dictionary<string, List<string>>();

В этом словаре ключом выступает имя музы, а в словарь я записываю ее контакты. Начиная с ее собственного.

Теперь у нас для каждой музы есть ее записная книжка. Чтобы узнать количество языков, надо сгруппировать муз, т.е. переписать муз одного языка в одну книжку. Пусть это будет книжка первый встреченной музы данной группы. Искать мы их будем транзитивно - по принципу того, что контакты моего контакта - мои контакты. Берем первую музу и ее контакты (множество М1), для каждого контакта с его контактами (Mi) добавляем различающиеся элементы M1 и Mi.

Как водится у меня есть велосипед (которые мне почему-то очень хочется писать) и адекватный (более менее, наверное) вариант работы с множествами в C#.

Велосипед выглядел как-то так:

for (int i = 0; i < Muses.Keys.Count; i++)
{
    string current_muse = Muses.Keys.ElementAt(i);

    for (int j = 1; j < Muses[current_muse].Count; j++)
    {
string contact_muse = Muses[current_muse][j];
        if (Muses.Keys.Contains(contact_muse))
{
           List<string> except_muses = new List<string>();

            foreach (string transit_muse in Muses[contact_muse])
           {
if (!Muses[current_muse].Contains(transit_muse))
                  Muses[current_muse].Add(transit_muse);  
           }

            Muses.Remove(contact_muse);
}
    }
}

Но где-то здесь надо остановиться. Все что нам на самом деле надо сделать описывается псевдокодом как-то так:

foreach (M1 in M)
{
    foreach (mi in M1)
    {
M1 = M1 + mi; //где "+" - это операция объединения множеств.
M.Remove(mi);
    }
}

Благодаря пространству имен System.Linq любая коллекция в C# поддерживает интерфейс IEnumarable<T>. А он вполне себе множество и имеет такие классные методы как Union, Intersect, Except, where... В общем позволяет вспомнить курс дискретной математики и применить его на практике. Так что мой псевдокод в итоге выглядит так:

for (int i = 0; i < Muses.Keys.Count; i++ )
{
    string current_muse = Muses.Keys.ElementAt(i);

    for (int j = 1; j < Muses[current_muse].Count; j++)
    {
string contact_muse = Muses[current_muse][j];
if (Muses.Keys.Contains(contact_muse))
{
          Muses[current_muse] = Muses[current_muse].Union(Muses[contact_muse]).ToList();
          Muses.Remove(contact_muse);
}
    }
}

Для самых внимательных: итератор foreach не позволяет изменять коллекцию в теле цикла. А мне нужно. Поэтому for.

Проверка на существование contact_muse нужна на случай, если мы записуню книжку этой музы уже обработали.

Вот собственно и все.
Картинка, музычка и проект. )



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

CodeStars. Track 02.

Задача
Случайно к тебе попало письмо с текстом будущего хита от твоего главного соперника за первое место в хит-парадах! Но, к сожалению, все сообщение оказалось закодированным. Твои помощники определили, что в песне используется два смысловых слова — Yo и Nice — причем, Nice означает конец очередной буквы, а количество предшествующих слов Yo определяет порядковый номер буквы в алфавите. Два подряд идущих слова Nice обозначают конец слова. В приведенном файле также содержатся другие слова-паразиты, которые не несут смысловой нагрузки, и бессмысленные знаки препинания (которые тем не менее используются для разделения слов). Расшифруй текст песни, содержащийся в файле. Если ты сделаешь все правильно, ты получишь текст только из букв и пробелов, без лишних пробелов и знаков препинания.

Решение

Что описывать в решении я не знаю... все очень просто. Но я попытаюсь.

У нас есть файл с кучей всяких слов. Которые нас не волнуют. Нас волнуют только две вещи:
  • слово, которое встречаясь увеличивает порядковый код буквы в слове на единицу. Yo.
  • слово "пробел" - разделитель слов. Nice. Когда оно встречается счетчик наших Yo обнуляется. 
Наша задача получить символы из которых состоит файл. Эти символы буквы (латинские) и пробелы (так сказано в задании). Каждому символу соответствует число Yo. Для пробела между словами число Yo будет равно нулю (ровно столько Yo мы насчитаем между двумя идущими подряд разделительями Nice). Если заглянуть в ASCII табличку то мы это и увидим. Сначала пробел, а потом маленькие латинские буковки от 'a' до 'z'. Значит значение аски-кода символа связано со значением счетчика с неким постоянным смещением (96 - можно вычислить по таблице). Значит, для вычисления буковки мы берем полученный счетчик и прибавляем к нему 96. И делаем Convert.ToChar(Counter);

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

Все.

Картинка. Проект. И бонусная песня, которую мы "слямзили" своими нечеловеческими усилиями.



среда, 11 февраля 2015 г.

CodeStars. Track 01



Задача. 
Твой первый клип не попадет в ротацию MTV, если ты не решишь эту задачу.

На съемках твоего первого клипа не обойтись без массовки — 106 симпатичных девушек разного роста, одетых в разноцветные костюмы. Но как их лучше разместить в кадре? Чтобы найти удачный ракурс, тебе придется попробовать все возможные расстановки для героинь твоего видео. Сколько часов потребуется на просмотр всех расстановок, если на просмотр одной расстановки требуется 10 секунд?

Предисловие.
Нам нужно количество перестановок в кадре различных девиц - значит, факториал. И умножить это значение на 10 - чтобы узнать общее время.
Поскольку 106! - это очень и очень много, надо сразу учесть, что в int и даже в longint это богатство не влезет. Поэтому нам нужно использовать длинную арифметику.

Ларчик открывался, на самом деле, невероятно просто. Примерно за 30 минут. Без извращений и попыток убить себя об стену.

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

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

Решение.
Если опустить мои четырехдневные мытарства с написанием собственного класса, то задача в рамках C# решается при помощи 10 строк с использованием пространства имен System.Numerics и  его типов BigInteger и Complex. Даже только при помощи первого.

Но чтобы за потраченное время было не так обидно, я расскажу про кнопочку.  Раз уж я теперь звезда электрокода, то надо раскрасить свой проект, так что пусть будет WPF: с кислотно-розовой рамочкой и с большой желтой кнопкой.

Для создания шаблона большой желтой кнопки были совмещены вот эти два источника:


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


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

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

Загадка )


Дано:
string MyNum = "1";

Вопрос:
Convert.ToInt32(MyNum[0]) == ?

Ответ и пояснение под катом. )
Дано:
 - Два поля ввода исходных данных.
 - Кнопка, для осуществления по ним расчета.
 - Три поля вывода результата.

Требуется:
 - Реализовать проверки введенных данных перед расчетами.

Пусть данные на форме перед расчетами проверяются методом ValidateData(). 
У меня есть два быстро приходящих в голову способа реализовать этот метод:

void ValidateData
{
     try
     {
         Convert.ToInt32(this.GirlsCountTB.Text);
     } catch { ... }
     ....
}
Но вообще, try - catch это долго и неэффективно - раз.
Мне в любом случае придется делать эту конвертацию впоследствии, при переходе к расчетам - два. Поэтому второй вариант:
void ValidateData
{
    int girls = 0;
    if (!Int32.TryParse(this.GirlsCountTB.Text, out girls)) { ... }
}

Это уже по-человечески, но куда мне деть результат? Можно вернуть, но у меня здесь два поля, которые надо проверить. А если их будет 10? Можно создать и заполнить словарь. И на этом я бы и остановилась и тогда поста бы не было. Но... Тем временем, коллега на обеде рекомендует мне третий вариант. 
"А как же дзен обработки ввода? Используй событие TextChanged." Идея кажется мне неплохой... 

      -  И... в момент события TextChanged отображение изменения уже произошло. Отменять действие уже поздно.
      -  В списке есть TextInput, но интернет мне услужливо сообщает, что это событие блокируется элементом TextBox. 
      -  Зато есть PreviewTextInput. Оно не блокируется, позволяет отменить ввод...  в общем все прекрасно. Но есть нюанс: это событие не генерируется при нажатии пробела. Пробел нам предлагают обрабатывать дополнительно через KeyDown. 
И в итоге тогда обработка данных происходит в трех местах:

  1. PreviewTextInput
  2. KeyDown
  3. ValidateData (потому что если ни одно из событий не произошло поле остается пустым... и не валидным. И нам надо об этом знать).
В общем идея с событиями мне кажется несостоятельной. Странно?..
Не очень, WPF подразумевает активное, судя по всему даже повсеместное, использование привязок. В этом случае ввод проверяется на уровне объекта, вместе с бизнес-логикой. И все эти события ввода с клавиатуры нам не нужны. 
Но пока у меня в архитектуре нет сформированных объектов, хотя, подозреваю, они еще будут, но пока это будет выглядеть так:

private bool ValidateData()
{
    List<string> errors = new List<string>();
    int girls = 0;
    double timeForOne = 1.0;

    if (this.GirlsCountTB.Text == "")
       errors.Add("Не задан размер массовки.");
                        
    if (!Int32.TryParse(this.GirlsCountTB.Text, out girls))
       errors.Add("Некорректное число массовщиц.");

    if (this.OneVarTimeTB.Text == "")
       errors.Add("Не задано время на проработку одного варианта.");

    if (!Double.TryParse(this.OneVarTimeTB.Text, out timeForOne))
       errors.Add("Некорректное время обработки одного варианта");

    ShowErrorMsg(errors);

    return (errors.Count == 0);

}

Использованные материалы по теме:

http://samorazvitie.net/book/80-osnovy-proektir-interfejsov-s-ispolzovaniem-texnologii-windows-presentation-foundation-shamshev-a/36-55-sobytiya-wpf.html

вторник, 3 февраля 2015 г.

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

пятница, 23 января 2015 г.

Роберт Мартин "Чистый код". То о чем я подумаю завтра. И послезавтра...

Итак, книга закрыта и большая ее часть осмыслена. В большинстве случаев, мне пришлось согласиться с авторами. Не смотря на первую скептическую реакцию.

Тем не менее, осталось два аспекта, по поводу которых мне пока не удалось придти к внутренней гармонии.

1. Самокомментируемый код. Т.е. код без комментариев. Т.е. у вас могут быть комментарии для выражения ссылок на предметную облсть или ТЗ или другую документацию. Но если у вас возникло желание написать комментарий объясняющий код, то вернитесь к коду и попробуйте переписать его. А очевидные комментарии тем более не нужны.

Как сказал мой друг "а мне повезло родиться во времена, когда переменная уже могла себе позволить быть длиннее 5 символов."... А мне вот тяжело.  Каким бы ни был читабельным и аккуратным код,  когда я удаляю даже очевидный и, следовательно, ненужный комментарий, где-то в моей душе все равно умирает котенок. Серьезно.

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

2. TDD - Test Driven Development.
Чистый код - код полностью покрытый и легко покрываемый тестами. Это абсолютно легитимно, но... с этим просто очень сложно жить.

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

Между тем, авторы открыли мне весьма очевидный факт... чтобы тестирование не было такой мукой, а проект его подразумевал... что? Надо просто СНАЧАЛА подумать о том, как будут сформулированы тесты для текущей подзадачи и написать их. И уже потом оформлять задачу, учитывая этот факт.

Тем не менее, данный пункт я пока не готова применить. Увы.
Я попробую, когда освоюсь с более легкой частью материала.

Из диаложиков на полях:

[17:05] К. М.: так вот, юнит-тесты иногда таки сильно упрощают жизнь
[17:06] U.: юнит тесты ВСЕГДА сильно упрощают жизнь.
[17:06] U.: кроме одного момента - момента их создания
[17:07] К. М.: они далеко не везде нужны, точнее
[17:08] U.: Когда например нет?
[17:10] К. М.: когда трата времени на формализацию и написание теста не даст кратного выигрыша по времени при дальнейшем использовании (рефакторинг, расширение функционала проекта етц); вот моё имхо
[17:11] U.: идея хорошая
[17:12] U.: ты можешь как-нибудь сформулировать критерий оценки потерь времени при дальнейшем использовании при отсутствии тестов?
[17:13] К. М.: не могу. Но вот есть мать её Управляющая Х : мониторит процесс Х, и перезагружает его, если тот отожрал слишком много памяти или пропустил контрольное время обновления логов.
[17:13] К. М.: цикл вайл(труъ), а в нём три функции проверки состояния КОДа
[17:14] К. М.: чо тут покрыть тестами? А попытки написать хоть какие-то тесты на многопоточность сожрут массу времени. 

пятница, 16 января 2015 г.

Winforms and databings. Advanced options

Это отчасти продолжение предыдущего. Но мне явно не хватает компетенции и решительности поставить в вопросе точку.

Шаг 6. Украшение

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

textbox1.DataBindings.Add("Text", CurrentFotoConf.AppObj, "Alpha", true, DataSourceUpdateMode.OnPropertyChanged)

Которые пользователю хотелось бы видеть в градусах. И округленными до второго знака. Значит нам нужно форматирование. Если мы собираемся модифицировать исходные данные, то нам нужен еще и парсер ввода (который будет переводить пользовательские градусы, обратно в радианы).
У класса Binding (экземпляр которого мы тут неявно создаем), есть два события для этих случаев: Format и Parse (тип события ConvertEvent...). Их нам и надо обработать.

Залезть в этот экземпляр привязкм можно совершенно по-хамски:
textbox1.DataBindings[0].Format += ...

Но можно и по-человечески:
Binding b = new Binding("Text", CurrentFotoConf.AppObj, "Alpha", true, DataSourceUpdateMode.OnPropertyChanged);
b.Format += new ConvertEventHandler(...);
b.Parse += new ConvertEventHandler(...);


Внимание, вопрос: и где мне разместить мои обработчики?.. Можно здесь же, на этой форме - но это не очень рационально: они наверняка понадобятся где-то еще. Поэтому, я сделала так:

public static class Converters
{
   public static void RadToGradFormat(object sender, ConvertEventArgs e)
   {
      if (e.DesiredType != typeof(string))
         return;

      e.Value = Math.Round(Measures.RadToGrad(Convert.ToDouble(e.Value)), 2);
   }

   public static void GradToRadParse(object sender, ConvertEventArgs e)
   {
      if (e.DesiredType != typeof(double))
         return;

      e.Value = Math.Round(Measures.GradToRad(Convert.ToDouble(e.Value)));
   }
}
...
b.Format += new ConvertEventHandler(Converters.RadToGradFormat);
b.Parse += new ConvertEventHandler(Converters.GradToRadParse);

Где, класс Measures - это такой же статический класс позволяющий мне конвертировать много разных форматов представления. А Math - стандартный класс С#. По аналогии с которым я и выбрала это решение, хотя, полагаю есть более изящный способ.

Шаг 7. Бесконечный путь к совершенству или размышления, которые стоит не забыть до понедельника.

Сейчас мои привязки данных выглядят так:


Binding b = new Binding("Text", CurrentFotoConf.AppObj, "Betta", false, DataSourceUpdateMode.OnPropertyChanged);
b.Format +=new ConvertEventHandler(Converters.RadToGradFormat);
b.Parse += new ConvertEventHandler(Converters.GradToRadParse);
this.AcrossTB.DataBindings.Add(b);
b = new Binding("Text", CurrentFotoConf.AppObj, "Alpha", false, DataSourceUpdateMode.OnPropertyChanged);
b.Format += new ConvertEventHandler(Converters.RadToGradFormat);
b.Parse += new ConvertEventHandler(Converters.GradToRadParse);
this.AlignTB.DataBindings.Add(b);


А я размышляю, что это тоже не слишком читаемо и было бы, наверное, круто, сделать что-то вроде:



this.AcrossTB.DataBindings.Add(CurrentFotoConf.AppObj.BindBetta("Text"));
this.AlignTB.DataBindings.Add(CurrentFotoConf.AppObj.BindAlpha("Text"));

или даже так

CurrentFotoConf.AppObj.BindBetta(this.AcrossTB, "Text");
CurrentFotoConf.AppObj.BindAlpha(this.AlignTB, "Text");
CurrentFotoConf.BindConfName(this.ConfNameTB, "Text");

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

А еще... еще можно было бы создать уже четвертый (верхний я вам не показала :p) уровень наследования и классы вроде:


class FotoConfView : FotoConf
{
...
}

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

Или... (о, эти безграничные возможности) я могу выполнять преобразования в моих { set; get; }. Но, это сильно ограничит меня, поскольку... как же мне тогда для вычислений получить нужные мне радианы?..


WinForms, простая привязка данных и наследование.

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

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


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

Шаг 1. Прямая привязка дело не хитрое.

textbox1.DataBindings.Add("Text", CurrentFotoConf, "ConfName");
- предсказуемо приводит к отображению в текстовом поле данных.

Шаг 2. На уровень глубже

textbox1.DataBindings.Add("Text", CurrentFotoConf, "AppObj.ConfName");
- так наша привязка делать, как выяснилось не умеет. Зато умеет так:
textbox1.DataBindings.Add("Text", CurrentFotoConf.AppObj, "ConfName");
- в самом деле - объекту источнику довольно все равно, из каких глубин вы его извлечете. Главное, чтобы был доступ на чтение.

Шаг 3. Обновление отображения при изменении источника.

Приведенная выше привязка работает только в одну сторону: изменяем значение в интерфейсе, изменяется объект. А как обновить отображение автоматически, при изменении источника?
Для этого нам понадобятся две составляющих:
  • Класс, который мы привязываем, должен поддерживать интерфейс INotifyPropertyChanged. Т.е. у него есть событие, например PropertyChanged, на которое сможет подписаться наш биндинг, и в случае изменения полей это событие вызывается. Подробнее про интерфейс и как выглядит его интеграция можно найти в конце поста. Using для волшебного интерфейса: System.ComponentModel .

  • Надо подписать наш биндинг на это событие. Например, так:
textbox1.DataBindings.Add("Text", CurrentFotoConf.AppObj, "Alpha", true, DataSourceUpdateMode.OnPropertyChanged)
- или другим, более симпатичным вам образом.

Шаг 4. Опаньки: подводный камень.

textbox1.DataBindings.Add("Text", CurrentFotoConf.AppObj, "Alpha", true, DataSourceUpdateMode.OnPropertyChanged)
- работает
textbox1.DataBindings.Add("Text", CurrentFotoConf, "ConfName", true, DataSourceUpdateMode.OnPropertyChanged)
- не работает
textbox1.DataBindings.Add("Text", CurrentFotoConf.AppObj, "ConfName", true, DataSourceUpdateMode.OnPropertyChanged)
- не работает.

Как так?...

Очень просто. Свойство "ConfName" - унаследованное. C# считает, что оно принадлежит не классу Objective (или FotoConf), откуда я его беру для привязки, а классу родителю, где оно объявлено - BDModelClass.
Поэтому, конструктор новой привязки, т.е. биндинга, берет свойство, лезет в класс, с намерением привязать к нему свой обработчик событий изменения... и не находит там этого события. И интерфейса тоже не находит.

Поэтому...
... Если вы привязываете унаследованное свойство - класс родитель, где это свойство объявлено, тоже должен поддерживать интерфейс INotifyPropertyChanged. А метод set нашего свойства, должен инициировать событие после изменения.

Ура!

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

Шаг 5. Зачистка.

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

class BDModelClass : INotifyPropertyChanged
{
   public event PropertyChangedEventHandler PropertyChanged;

   protected void OnPropertyChanged(string update_args)
   {
      PropertyChangedEventHandler TmpThreadHandl = PropertyChanged;
      if (TmpThreadHandl != null)
         TmpThreadHandl(this, new PropertyChangedEventArgs(update_args));
   }

   public string ConfName
   {
      set
      {
         _ConfName = value;
         OnPropertyChanged("ConfName");
      }
      get { return _ConfName; }
   }  

}

class FotoConf : BDModelClass
{
   public FotoConf(...)
   {
      ....
      OnPropertyChanged(string.Empty);
   }
}


Примечание.

Этого нет здесь, потому что я узнала об этом раньше, но это частые нюансы, поэтому я их упомяну.
  1. Привязывать можно только свойства. Не поля. Публичные, открытые, хотя бы на чтение, свойства.
  2. Это относится к сложным привязкам, т.е. привязкам списков ко всяким DataGridView - но я оставлю это тут: не все списки одинаково съедобны. Т.е. не все поддерживают интерфейс INotifyPropertyChanged при изменении коллекции.

    List<T>, кстати, не поддерживает.
    System.Collections.ObjectModel.ObservableCollection<T> - точно поддерживает. Обычно я использую его, хотя никак не могу это мотивировать.
    BindingList<T> - не пробовала. Но я бы очень надеялась, что класс с таким названием все-таки это делает. 
  3. Подробнее про интерфейс, способы привязки и вообще, например, здесь:  http://rsdn.ru/article/dotnet/Data_Binding_Basics.xml .

четверг, 15 января 2015 г.

О своих опытах я расскажу позже, а пока это останется здесь.

Привязка данных в Windows Forms.
Как я жила без нее все эти годы?..

http://rsdn.ru/article/dotnet/Data_Binding_Basics.xml

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

Роберт Мартин "Чистый код. Создание, анализ и рефакторинг"


Роберт Мартин "Чистый код. Создание, анализ и рефакторинг"
или книга о мелочах.

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

А еще я хочу ее обсудить.
Глава 3: функции и методы.

Честно говоря, резюмируя эти первые сто страниц, мне очень хочется спросить у авторов "What?..". Ок, завершение главы, меня почти помирило с автором:
"Если вы будете слеовать этим правилам, ваши функции будут короткими, удачно названными и хорошо организованными. Но не забывайте, что ваша настоящая цель - "рассказать историю" системы, а написанные функции должны четко складываться в понятный и точный язык изложения".
Ура! Я нашла то, что хотела.
Казалось бы, что плохого в коротких, удачно названных и хорошо организованных функциях?.. С ними все прекрасно, кроме того, что по примерам из главы - эти функции идеально читаемы... каждая. Отдельно. И упорядочивание одной функции не должно вносить хаоса в логику класса. А увеличение сущностей в десяток раз - это хаос, которого стоит избегать.
К сожалению, в этой главе очень мало о том, что хорошо читаться должно все вместе. А стек вызовов, для решения маленькой простой задачки, все-таки, я полагаю, не должен зашкаливать за пару десятков. Это едва ли упрощает чтение кода, поддержку и отладку... на любом этапе.

Разбор смутившего меня.

  1. Методы не должны действовать скрыто. Т.е. если метод что-то делает, это должно быть очевидно из названия. И это, я считаю, прекрасно. 
  2. Никаких секций в функции. Если функция осмысленно делится на секции, значит она точно делает более одной вещи, значит... смотри номер 3.
  3. 1 операция - 1 функция.

    Я, вообще говоря, стараюсь следовать этому правилу. Но есть нюансы...

3.1 Я полагаю, что в ряде случаев метод, ради простоты использования нашего инструментария, обязан делать достаточно большое число штук. Я даже могу рассмотреть это на примере книги.
Вот есть у нас некий метод: checkPassword(string username, string password). В нем, как следует из названия, проверяется корректность пары и... и еще он открывает новую сессию подключения. Неждачик.
И тут мы бы должны сделать тaк: checkAuthAndInitConnection(string username, string password). Чем приведем по второму правилу все в норму. Но... но упс. Теперь этот метод очевидно делает чего-то два: сначала проверяет данные, потом открывает новую сессию подключения.
А я у меня объект подключения к БД. Этот объект предоставляет пользователю интерфейс работы с базой данных. И я очень не хочу объяснять пользователю что он сначала сам должен вызвать метод checkAuth(...) и только потом вызвать метод InitConnection(...).
А иначе на него посыплются градом ошибки. Ведь первый метод не должен устанавливать подключение, а второй не должен ничего проверять... и, сэкономив радостному программисту 15 секунд (он с удовольствием найдет в интерфейсе InitConnection) я заберу у него час (найти документацию к объекту, прочитать ее и исправить ошибку), когда упадет тест на ввод неверной авторизации.

3.2. Второй нюанс. Следствие этого правила: 1 функция - 1 уровень вложенности.
По факту эта оптимизация читабельности приводит нас сначала к замене одной функции на да листа пятью функциями на те же два листа в сумме: но это, обычно здорово.
А потом к 15 функциям по 3 строки... Нет, это определенно не улучшает отладку и сопровождение. Ну правда же.

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

try
{
    a = R\N;
}
catch (Exception e)
{
    MessageBox.Show("О ужас! Деление на ноль");
    a = 0;
}
finally
{
    return a;
}


мы пишем что-то вроде: return getResultOrError(a, R, N)  и здорово упрощаем себе этим жизнь. Вообще мне, после обдумывания это понравилось. Хотя предложение создать еще и :
TryRNExpression(R,N);
CatchDivisionByZero();
вызвало некототорое смущение. Которое все еще меня грызет.

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

Также в третьей главе авторы:
 - рассказывают про именование. И это хорошо;
- советуют нам использовать исключения, вместо возвращаемых кодов ошибок;
- между делом объяснили мне абстрактную фабрику;
- выступают против флаговых атрибутов и хорошо это аргументируют... но не вполне меня убеждают;
- не рекомендуют нам использовать выходные параметры и... и ничто меня так не бесит в делфи как функция DateTimeStringFormat;
- и упоминают много других полезных штук, с которыми я не буду спорить и которые вызвали достаточную степень (как минимум) принятия.