Случайны выбор дневника Раскрыть/свернуть полный список возможностей


Найдено 16821 сообщений
Cообщения с меткой

программирование - Самое интересное в блогах

Следующие 30  »
rss_rss_hh_new

[Перевод] Прощай, программирование…

Четверг, 01 Июня 2016 г. 00:32 (ссылка)



Лорен Мендоза

Инженер-программист. Воздушная акробатка. Родилась и живу в Сан-Франциско.




Когда многие продукты являются, по существу, одним и тем же приложением с различными цветовыми схемами и кнопкой копирования, почему мы всё ещё программируем?



Файлы в вашем компьютере могут быть представлены различными способами. Средством, с помощью которого большинство людей находит свои файлы, является графический интерфейс, такой как, например, Mac Finder. Но можно сделать также всё то же самое, используя текстовый интерфейс, как, например, Terminal. Оба интерфейса представляют собой различные пути для моделирования одной и той же информации и взаимодействия с нею.



Сетевое приложение может быть представлено также многими способами: архитектурная схема, диалог, модель. Разработчики программного обеспечения обычно работают с кодом. Но является ли он наиболее интуитивно понятным и производительным подходом?



Вдохновляющая идея



Для создания электронной музыки я использовала программу, называемую Reason. Мне она нравится, поскольку она позволяет «перетаскивать» провода между различными устройствами и точно показывает, как всё там соединено. Для меня наглядно показываемые проводки имеют намного больше смысла, чем бесконечное нагромождение меню и выплывающих окон, которое господствовало в самом передовом ПО в 2000-ных. (Я тебя люблю, Adobe Flash CS3 Professional). Благодаря интерфейсу Reason я лучше понимаю, что делаю, и успеваю сделать намного больше музыки, чем флэш-роликов.





Нам нужно больше удовольствия



Я не знаю, когда или как это произошло, но в какой-то момент «IT-специалист» стал «разработчиком», который превратился в «инженера-программиста». Мне, конечно, нравится называться инженером, особенно после лишь трёх месяцев «образования». Но основная часть того, что мы делаем — не инженерная деятельность. Быть инженером означает заниматься решением новых проблем, глубоким продумыванием задач. Это, действительно, интеллектуальная деятельность.



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



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



Инженеры должны решать новые и интересные задачи, а не воспроизводить одни и те же приложения снова и снова. Последнее — работа для роботов.



Тот же самый большой сайт, теперь с большим количеством CSS



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



Проблемы значимости собственной личности



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



Но знание замысловатого синтаксиса и «навыки кодирования» не делают вас хорошим инженером и тем более не делают вас умнее. Можно быть великим кодером и никаким инженером. Разработка лучших, быстрых, расширяемых, креативных способов решения реальных проблем людей — вот что ценно и что будет становиться всё более важным в ближайшие годы.



Написание текстов программ — исключительно глупое занятие



Написание текстов программ допускает опечатки. Более того, оно допускает неограниченную «креативность», также известную как типовые проблемы кода («запахи кода»). Основная масса кода сделана довольно плохо. Инженеры проводят бесчисленные часы, занимаясь синтаксисом, опечатками, структурированием расположения, проверками стиля оформления, ошибками, спорами по стилю и методам наиболее успешной практики, разработкой клавиш быстрого доступа, чтобы попытаться заставить какую-то часть кода печатать саму себя. Это — абсурд. И это — пустая трата времени.



Мы уже пришли



GraphQL устраняет внутренние маршруты и полностью заменит REST в ближайшие годы. Вы когда-либо задавались вопросом, почему нам нужен сервер, действующий как посредник между клиентом и базой данных? От этого следовало отказаться ещё вчера.



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



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



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



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



Решение



Менеджеры по продуктам, не зная, вообще, программирования, должны быть в состоянии просто заставить приложение делать то, что оно, как предполагается, должно делать. Единственное, что должна сделать компания, — создать элементы, делающие её поделку уникальной. Всё остальное было уже создано в других приложениях и должно быть лишь повторно использовано.



Что касается всего пыла и преданности вокруг открытого исходного кода, то большинство этих проектов сделано плохо, не сохраняется и не используется на уровне бизнеса. Компании «изобретают велосипеды» и дублируют сделанное.



Я сейчас хочу создать простой в использовании интерфейс, использующий «перетаскивание», в котором любой может создать полнофункциональное комплексное приложение без какого-либо программирования. Пока не знаю, как я буду делать это, но, скорее всего, я использую навыки работы с Adobe Flash CS3 Professional, упомянутым ранее. Шучу — я, вероятно, возьму React.



Преимущества



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



Полезно для инженерной культуры



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



«Moneyball» или «Человек, который изменил всё»



Вообще-то, «Moneyball» («Человек, который изменил всё») — это великолепный фильм. Я знаю, что он старый, но я просто смотрела его вчера вечером и всё пыталась связать сказанное выше с этим удивительным фильмом, но не смогла. И меня это ничуть не беспокоит.



Вы знаете, что ещё было бы неплохо?



Строительный материал в виртуальной реальности. Я только что купила кое-что, и это обман.

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/304442/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Root — робот который помогает детям учить программирование

Четверг, 30 Июня 2016 г. 21:54 (ссылка)

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







Root — это робот, который позволяет любому, кто плохо знаком с программированием, понять часто неинтуитивную природу языков программирования. Был разработан Институтом Висс (Wyss) в Гарвардском университете, это магнитный робот, который скользит по любой гладкой металической поверхности, рисует и стирает линии, сканирует цвета, издает музыкальные звуки и подает различные цветные, световые сигналы, а управляется это робо-чудо с помощью специально разработанного приложения для изучения основ программирования.







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







Зайвзан Дубровски (Zivthan Dubrovsky) руководитель группы робототехники в Гарвардском университете обратил внимание на то, что Вы можете переключаться между разными уровнями сложности столько раз, сколько пожелаете.

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


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

Таким образом, когда кто-то будет кодить в JavaScript, будет «вырабатываться» визуальное понимание того, каковы циклы, последовательности, функции, приоритеты и переменные.




Есть много способов изучить программирование, начиная с кодинга «hello world» до плавного перехода к созданию точной модели солнечной системы. Существует множество схожих приложений к примеру GameSalad, но они не позволяют Вам видеть написанный вами сценарий настолько, насколько это позволяет Root. Есть языки начального уровня, которые используют упрощенные сценарии, такие как Karel, но они не так просты для понимания детьми, как это могло бы показаться.







Root и Square соединяют эти идеи в единую систему, позволяя студентам изучать различные аспекты кодирования. На первом уровне Root предоставляет возможность проникнутся основами объектно-ориентированного программирования, все это очень схоже на Karel, который зачастую является введением для тех, кто хочет научится программировать на JavaScript или Python. Этот вид программирования — хорошая отправная точка для новичков, и способность перейти от кодирования drag-and-drop к текстовому кодированию, а так же является прекрасным способом показать детям, как работает код.







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



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



Так же вы можете сделать дорожку для робота и использовать его световые датчики, чтобы оставаться в пределах двух линий. Когда Вы программируете робота на втором и третем уровне, единственное ограничение для вас — это аппаратные возможности робота. Вы даже можете играть в собственную версию Angry Birds, рисуя траекторию движения на плоскости и программируя робота на определенную скорость и поведение на том или ином участке нарисованной линии.







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













На данный момент Вы можете зарезервировать своего мини робота Root за $199 и присоединиться к списку рассылки о новых обновлениях и работах по развитию этого проекта.
Original source: habrahabr.ru.

https://habrahabr.ru/post/304514/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Из песочницы] Что такое AXON

Четверг, 30 Июня 2016 г. 19:47 (ссылка)

AXON — это нотация для сериализованного представления объектов, документов и данных в текстовой форме. Она объединяет в себе простоту JSON, расширяемость XML и удобочитаемость YAML.



Есть проект pyaxon на python, с которым можно "поиграться". Впрочем, он создавался таким образом, чтобы не сильно уступать по скорости с модулем json. Поэтому он может сгодиться и для реальных дел.



Зачем AXON?



AXON появился как попытка справиться с недостатками или неудобствами JSON и XML, но сохранить при этом их достоинства и возможности, дополнительно добавив удобочитаемость, присущую YAML.





AXON содержит "улучшенный" вариант JSON



1. JSON имеет два неудобства:




  • имена атрибутов/ключей, которые являются идентификаторами приходится заключать в кавычки;

  • легко забыть запятую в случае вставки новой пары ключ: значение.



AXON устраняет эти неудобства следующим образом:




  • можно не заключать в кавычки имена, которые являются идентификаторами;

  • совершенно опускаются разделительные запятые, используются только пробельные символы для разделения элементов.



В результате получается более компактное представление и удобство восприятия при форматировании.



Для сравнения:



JSON



{ "name": "Alex",
"birth": "1979-12-25",
"email": "mail@example.com"}
[ "Alex"
"1979-12-25"
"mail@example.com"]


AXON



{ name: "Alex"
birth: ^1979-12-25
email: "mail@example.com"}
[ "Alex"
^1979-12-25
"mail@example.com"]


2. В JSON не гарантируется, что после загрузки



{ "name": "Alex",
"birth": "1979-12-25",
"email": "mail@example.com"}


порядок ключей/атрибутов сохранится.



В AXON констатируется, что



{ name: "Alex"
birth: ^1979-12-25
email: "mail@example.com"}


преобразуется в mapping без сохранения порядка ключей.



В то же время констатируется, что



[ name: "Alex"
birth: ^1979-12-25
email: "mail@example.com"]


преобразуется в mapping с сохранением порядка ключей.



3. AXON поддерживает синтаксиc для представления даты и времени в ISO-подобном формате:




  • даты



^2010-12-31



  • времени



^12:30
^12:30:15
^12:30+03:00
^12:30:15-04:30



  • даты и времени



^2010-12-31T12:30
^2010-12-31T12:30:05.0125
^2010-12-31T12:30+04:00
^2010-12-31T12:30:05.0123-04:00


а также для представления десятичных чисел:



1D 123456789D
3.14D 1.23e-6D


4. AXON также позволяет определять метки неатомарных значений и использовать их в качестве внутренних ссылок. Это позволяет при необходимости не создавать копии многократно используемых неатомарных значений при сериализации/десериализации.



Например:



[ { prev: &a (2012-12-30 10:00)
next: &c (2012-01-01 12:00) }
{ prev: &b (2012-12-31 13:00)
next: *a }
{ prev: *c
next: *b } ]


Метка имеет префикс & (&a &b &c), а ссылка имеет префикс * (*a *b *c).



Модель данных AXON содержит вариант модели Infoset XML в более компактной нотации



Рассмотрим иллюстративный пример XML представления структурированных данных:




John Smith
25

21 2nd Street
New York
NY

212-555-1234


AXON реализует идею более простого синтаксиса для представления XML структурированных данных:



person {
name {"John Smith"}
age {25}
address {
type: "home"
street {"21 2nd Street"}
city {"New York"}
state {"NY"}
}
phone {type:"home" "212-555-1234"}
}


Представление в формате AXON можно построить из формата XML за 5 шагов:




  1. Заменить на tag {

  2. Заменить на }

  3. Заменить attr=value на attr: value

  4. Текст внутри элементов заключить в двойные кавычки (")

  5. Удалить символ запятой (,) или заменить его на один пробел



Результат такого преобразования структурно идентичен первоначальному XML документу. По-существу это синтаксически более компактная форма представления XML документа.



Для сравнения также приведем представление в AXON с форматированием сложных элементов без {} с использованием принципа одинакового отступа для подэлементов структуры:



person
name {"John Smith"}
age {25}
address
type: "home"
street {"21 2nd Street"}
city {"New York"}
state {"NY"}
phone
type: "home"
"212-555-1234"


Это представление получается из предыдущего удалением всех символов { и }, а также ненужных пустых строк.



AXON расширяет возможности XML и JSON



В XML атрибуты могут иметь только простые значения, в AXON значением атрибута может любое значение (как и в JSON). Кроме того простые значения имеют тип (текст в формате unicode, число, десятичное число, дата и время, массив байтов в кодировке base64). AXON можно рассматривать как расширение JSON в том смысле, что объекты могут именованными, так же как и элементы XML являются именованными.



Например:



person
name: "John Smith"
age: 25
burn: 1975-10-21
locations: [
address
type: "home"
street: "21 2nd Street"
city: "New York"
state: "NY"
]
contacts: [
phone
type: "home"
"212-555-1234"
email
type: "personal"
"mail@example.com"
]


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



В качестве примера рассмотрим структурированный документ в формате XML:




paragraph

item text

paragraph

item text

paragraph


Непосредственно, без преобразования структуры этот документ не транслируется в JSON из-за важности порядка и повторяемости элементов. Один вариант трансляции, который эмулирует последовательность именованных элементов имеет вид:



{
"tag": "section",
"@": {"title": "Title"},
"*": [
{ "tag": "par",
"@": {"style":"normal", "text":"paragraph"}},
{ "tag":"enumerate",
"@": {"style": "enumerate"},
"*": [
{ "tag":"item",
"@": {"text":"item text"}}
]
},
{ "tag": "par", "@": {"style":"normal", "text":"paragraph"}},
{ "tag":"itemize",
"*": [
{ "tag":"item", "@": {"text":"item text"}}
]
},
{ "tag": "par", "@": {"style":"normal", "text":"paragraph"}}
]
}


В AXON такие структуры транслируются "один в один":



section
title: "Title"
par
style: "normal"
"paragraph"
enumerate
style: "enum"
item { "item text" }
par
style: "normal"
"paragraph"
itemize
style: "itemize"
item { "Item text" }
par
style: "normal"
"paragraph"


AXON поддерживает форматирование в стиле YAML



Привлекательной стороной YAML является формат представления в стиле wiki. AXON также поддерживает подобный стиль форматирования.

Например, для сравнения:




  • форматирование без {} (YAML-стиль)



person
name: "Alex"
age: 25



  • форматирование с {} и отступами (C/JSON-стиль)



person {
name: "Alex"
age: 25}



  • компактный формат



person{name:"Alex" age:25}


AXON может представлять серию объектов



Одно из ограничений JSON и XML связано с тем, что они представляют единственный корневой объект. Напротив, AXON представляет серию объектов или серию пар ключ:объект, которые можно загружать по одному. Например:




  • серия объектов



{ name: "Alex"
age: 32 }
{ name: "Michael"
age: 28 }
{ name: "Nick"
age: 19 }



  • серия объектов с ключами



alex: {
message: "Hello"
datetime: ^2015-07-12T12:32:35
}
michael: {
message: "How are you"
datetime: ^2015-07-12T12:32:35
}

Original source: habrahabr.ru.

https://habrahabr.ru/post/304516/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Перевод] Математические обозначения: Прошлое и будущее

Четверг, 30 Июня 2016 г. 18:05 (ссылка)





Перевод поста Стивена Вольфрама (Stephen Wolfram) "Mathematical Notation: Past and Future (2000)".

Выражаю огромную благодарность Кириллу Гузенко KirillGuzenko за помощь в переводе и подготовке публикации

Содержание



Резюме

Введение

История

Компьютеры

Будущее

Примечания

Эмпирические законы для математических обозначений

Печатные обозначения против экранных

Письменные обозначения

Шрифты и символы

Поиск математических формул

Невизуальные обозначения

Доказательства

Отбор символов

Частотное распределение символов

Части речи в математической нотации
Стенограмма речи, представленной на секции «MathML и математика в сети» первой Международной Конференции MathML в 2000-м году.

Резюме



Большинство математических обозначений существуют уже более пятисот лет. Я рассмотрю, как они разрабатывались, что было в античные и средневековые времена, какие обозначения вводили Лейбниц, Эйлер, Пеано и другие, как они получили распространение в 19 и 20 веках. Будет рассмотрен вопрос о схожести математических обозначений с тем, что объединяет обычные человеческие языки. Я расскажу об основных принципах, которые были обнаружены для обычных человеческих языков, какие из них применяются в математических обозначениях и какие нет.



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



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



Традиционная математическая нотация представляет математические объекты, а не математические процессы. Я расскажу о попытках разработать нотацию для алгоритмов, об опыте реализации этого в APL, Mathematica, в программах для автоматических доказательств и других системах.



Обычный язык состоит их строк текста; математическая нотация часто также содержит двумерные структуры. Будет обсуждён вопрос о применении в математической нотации более общих структур и как они соотносятся с пределом познавательных возможностей людей.



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



Введение



Когда собиралась эта конференция, люди подумали, что было бы здорово пригласить кого-то для выступления с речью об основаниях и общих принципах математической нотации. И был очевидный кандидат — Флориан Каджори — автор классической книги под названием «История математических обозначений». Но после небольшого расследования оказалось, что есть техническая проблема в приглашении доктора Каджори — он умер как минимум лет семьдесят назад.



Так что мне придётся его заменять.



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



В прошлом математической нотацией занимались обычно в контексте систематизации математики. Так, Лейбниц и некоторые другие люди интересовались подобными вещами в середине 17 века. Бэббидж написал тяжеловесный труд по этой теме в 1821 году. И на рубеже 19 и 20 веков, в период серьёзного развития абстрактной алгебры и математической логики, происходит очередной всплеск интереса и деятельности в этой теме. Но после этого не было почти ничего.



Однако не особо удивительно, что я стал интересоваться подобными вещами. Потому что с Mathematica одной из моих главных целей было сделать ещё один большой шаг в области систематизации математики. А более общей моей целью в отношении Mathematica было распространить вычислительную мощь на все виды технической и математической работы. Эта задача имеет две части: то, как вычисления происходят внутри, и то, как люди направляют эти вычисления для получения того, что они хотят.



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



Однако недостаточно просто проводить вычисления. Необходимо так же, чтобы люди каким-то образом сообщали Mathematica о том, какие вычисления они хотят произвести. И основной способ дать людям взаимодействовать с чем-то столь сложным — использовать что-то вроде языка.



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



Так что включает в себя эта работа?



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



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



Однако, разумеется, язык Mathematica — не английский. Это скорее сильно адаптированный фрагмент английского языка, оптимизированный для передачи информации о вычислениях в Mathematica.



Можно было бы думать, что, пожалуй, было бы неплохо объясняться с Mathematica на обычном английском языке. В конце концов, мы уже знаем английский язык, так что нам было бы необязательно изучать что-то новое, чтобы объясняться с Mathematica.



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



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



Хорошо, так что насчёт математической нотации?



Большинство людей, которые работают в Mathematica, знакомы по крайней мере с некоторыми математическими обозначениями, так что, казалось бы, было бы весьма удобно объясняться с Mathematica в рамках привычной математической нотации.



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



Однако есть один удивительный факт — он весьма удивил меня. В отличие от естественных человеческих языков, для обычной математической нотации можно сделать очень хорошее приближение, которое компьютер сможет понимать. Это одна из самых серьёзных вещей, которую мы разработали для третьей версии Mathematica в 1997 году [текущая версия Wolfram Mathematica — 10.4.1 — вышла в апреле 2016 г. — прим. ред.]. И как минимум некоторая часть того, что у нас получилось, вошла в спецификацию MathML.



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



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



Я думаю, математическая нотация — весьма интересное поле исследования для лингвистики.



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



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



История



Давайте сперва поговорим об истории.



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



Это тесно связано с историей самой математики, так что нам придётся коснуться немного этого вопроса. Часто можно услышать мнение, что сегодняшняя математика есть единственная мыслимая её реализация. То, какими бы могли быть произвольные абстрактные построения.



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



Все эти традиции довольно стары. Арифметика берёт своё начало со времён древнего Вавилона. Возможно, и геометрия тоже приходит из тех времён, но точно уже была известна в древнем Египте. Логика приходит из древней Греции.



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



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



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



Примечательно, кстати, что другая, очень старая область знаний, о которой я упомяну позднее — грамматика — по сути никогда не интегрировалась с математикой, по крайней мере до совсем недавнего времени.



Итак, давайте поговорим о ранних традициях в обозначениях в математике.



Во-первых, есть арифметика. И самая базовая вещь для арифметики — числа. Так какие обозначения использовались для чисел?



Что ж, первое представление чисел, о котором доподлинно известно — высечки на костях, сделанные 25 тысяч лет назад. Это была унарная система: чтобы представить число 7, нужно было сделать 7 высечек, ну и так далее.



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



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



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



И, фактически, один из самых важных вопросов относительно чисел, который, как я полагаю, будет всплывать ещё много раз — насколько сильным должно быть соответствие между обычным естественным языком и языком математики?



Или вот вопрос: он связан с позиционной нотацией и повторным использованием цифр.



Как можно заметить, в естественных языках обычно есть такие слова, как "десять", "сто", "тысяча", "миллион" и так далее. Однако в математике мы можем представить десять как "один нуль" (10), сто как "один нуль нуль" (100), тысячу как "один нуль нуль нуль" (1000) и так далее. Мы можем повторно использовать эту одну цифру и получать что-то новое, в зависимости от того, где в числе она будет появляться.



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



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



Вот пример их обозначений.



image



Из этой картинки можно понять, почему археология столь трудна. Это очень маленький кусок обожжённой глины. Было найдено около полумиллиона подобных вавилонских табличек. И примерно одна из тысячи — то есть всего около 400 — содержат какие-то математические записи. Что, кстати, выше отношения математических текстов к обычным в современном интернете. Вообще, пока MathML не получил достаточного распространения, это является достаточно сложным вопросом.



Но, в любом случае, маленькие обозначения на этой табличке выглядят слегка похожими на отпечатки лапок крошечных птиц. Но почти 50 лет назад в конце концов исследователи определили, что эта клинописная табличка времён Хаммурапи — около 1750 года до н.э. — фактически является таблицей того, что мы сейчас называем пифагорейскими тройками.



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



Так, к примеру, у египтян для обозначения тысячи использовался символ цветка лотоса, для сотни тысяч — птица, ну и так далее. Каждая степень десяти для её обозначения имела отдельный символ.



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



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



Вот как выглядит список чисел в греческом обозначении [вы можете скачать Wolfram Language Package, позволяющий представить числа в различных древних нотациях здесь — прим. ред.].







(Думаю, именно так сисадмины из Академии Платона адаптировали бы свою версию Mathematica; их воображаемую -600-ю (или около того) версию Mathematica.)



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



То есть это значит, что есть различные устаревшие греческие буквы, оставшиеся в системе счисления — как коппа для обозначения числа 90 и сампи для обозначения числа 900. Однако я включил их в набор символов для Mathematica, потому здесь прекрасно работает греческая форма записи чисел.



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



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



Итак, давайте попробуем римскую форму записи чисел.







Это тоже довольно неудобный способ записи, особенно для больших чисел.







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



И в целом, подобное представление для больших чисел полно неприятных моментов. К примеру, когда Архимед писал свою работу о количестве песчинок, объём которых эквивалентен объёму вселенной (Архимед оценил их количество в 1051, однако, полагаю, правильный ответ будет около 1090), то он использовал обычные слова вместо обозначений, чтобы описать столь большое число.



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



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



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



image



И на этих геометрических фигурах можно увидеть точки, которые имеют символьное представление в виде греческих букв. И в описании теорем есть множество моментов, в которых точки, линии и углы имеют символьное представление в виде букв. Так что идея о символьном представлении каких-то объектов в виде букв берёт своё начало как минимум от Евклида.



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



image



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



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



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



Но, в любом случае, без переменных всё было бы гораздо сложнее. Например, как вы представите многочлен?



Ну, Диофант — тот самый, что придумал диофантовы уравнения — сталкивался с проблемой представления многочленов в середине 2 века н.э. В итоге он пришёл к использованию определённых основанных на буквах имён для квадратов, кубов и прочего. Вот как это работало.







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



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



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



Хорошо, так что насчёт буквенного обозначения переменных?



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



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



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



После Фибоначчи наше современное представление для чисел постепенно становится всё популярнее, и ко времени начала книгопечатания в 15 веке оно уже было универсальным, хотя ещё и оставались несколько чудных моментов.



Но алгебраических переменных в полном их смысле тогда ещё не было. Они появились лишь после Виета в конце 16 века и обрели популярность лишь в 17 веке. То есть у Коперника и его современников их ещё не было. Как в основном и у Кеплера. Эти учёные для описания каких-то математических концепций использовали обычный текст, иногда структурированный как у Евклида.



Кстати, даже несмотря на то, что математическая нотация в те времена была не очень хорошо проработана, системы символьных обозначений в алхимии, астрологии и музыке были довольно развиты. Так, к примеру, Кеплер в начале 17 века использовал нечто, похожее на современную музыкальную нотацию, объясняя свою «музыку сфер» для отношений планетарных орбит.



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



Вот как Виет записывал многочлены в форме, которую он называл "zetetics", а сейчас мы бы это назвали просто символьной алгеброй:







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



Так как раньше представляли операции, в каком виде?



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



У египтян были некоторые обозначения для операций: для сложения они использовали пару идущих вперёд ног, а для вычитания — идущих назад.



А современный знак +, который, вероятно, является сокращением от "et" на латыни (означает «и»), появился лишь в конце 15 века.



А вот кое-что из 1579 года, что выглядит весьма современным, написанное в основном на английском, пока не начнёшь понимать, что те забавные загогулины — это не иксы, а специальные небуквенные символы, которые представляют различные степени для переменных.



image



В первой половине 17 века произошла своего рода революция в математической нотации, после которой она практически обрела свой современный вид. Было создано современное обозначение квадратного корня, который ранее обозначался как Rx — это обозначение сейчас используется в медицинских рецептах. И в основном алгебраическая нотация приобрела свой современный вид.



Уильям Отред был одним из тех людей, кто серьёзно занимался этим вопросом. Изобретение логарифмической линейки — одна из вещей, которая сделала его известным. На самом деле о нём практически ничего неизвестно. Он не был крупным математиком, однако сделал много полезного в области преподавания, с такими людьми, как Кристофер Рен и его учениками. Странно, что я ничего не слышал о нём в школе, особенно если учесть, что мы учились в одной и той же школе, только он на 400 лет ранее. Однако изобретение логарифмической линейки было недостаточным для того, чтобы увековечить своё имя в истории математики.



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



image



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



Вот пример.



image



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



Чего не скажешь о Лейбнице. Лейбниц много внимания уделял вопросам нотации. В действительности, он считал, что правильные обозначения есть ключ ко многим человеческим вопросам. Он был своего рода дипломат-аналитик, курсирующий между различными странами, со всеми их различными языками, и т.д. У него была идея, что если создать некий универсальный логический язык, то тогда все люди смогли бы понимать друг друга и имели бы возможность объяснить всё что угодно.



Были и другие люди, которые размышляли о подобном, преимущественно с позиции обычных естественных языков и логики. Один из примеров — довольно специфичный персонаж по имени Раймонд Лул, живший в 14 веке, который заявлял, что изобрёл некие логические колёса, дающие ответы на все вопросы мира.



Но так или иначе, Лейбниц разработал те вещи, которые были интересны и с позиций математики. То, что он хотел сделать, должно было так или иначе объединить все виды обозначений в математике в некоторый точный естественный язык с подобным математике способом описания и решения различных проблем, или даже больше — объединить ещё и все используемые естественные языки.



Ну, как и многие другие свои проекты, Лейбниц так и не воплотил это в жизнь. Однако он занимался самыми разными направлениями математики и серьёзно относился к разработке обозначений для них. Наиболее известные его обозначения были введены им в 1675 году. Для обозначения интегралов он использовал "omn.", возможно, как сокращение от omnium. Но в пятницу 29 октября 1675 года он написал следующее.



image



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



Затем в четверг 11 ноября того же года он обозначил дифференциал как "d". На самом деле, Лейбниц считал это обозначение не самым лучшим и планировал придумать ему какую-нибудь замену. Но, как мы все знаем, этого не произошло.



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



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



Так он обозначал функции.







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



В 18 веке Эйлер активно пользовался обозначениями. Однако, по сути, он следовал по пути Лейбница. Полагаю, он был первым, кто всерьёз начал использовать греческие буквы наравне с латинскими для обозначения переменных.



Есть и некоторые другие обозначения, которые появились вскоре после Лейбница. Следующий пример из книги, вышедшей через несколько лет после смерти Ньютона. Это учебник алгебры, и он содержит весьма традиционные алгебраические обозначения, уже в печатном виде.



image



А вот книга Лопиталя, напечатанная примерно в то же время, в которой уже практически современная алгебраическая нотация.



image



И, наконец, вот пример от Эйлера, содержащий весьма современные обозначения для интегралов и прочего.



image



Эйлер — популяризировал современное обозначение для числа пи, которое первоначально было предложено Уильямом Джонсом, который рассматривал его как сокращение от слова периметр.



Предложенная Лейбницем и сотоварищами нотация довольно долго оставалась неизменной. Происходили небольшие изменения, как, к примеру квадрат x x получил написание x2. Однако практически ничего нового не появилось.



Однако в конце 19 века наблюдается новый всплеск интереса к математической нотации, сопряжённый с развитием математической логики. Были некоторые нововведения, сделанные физиками, такими как Максвелл и Гиббс, в основном для векторов и векторного анализа, как следствие развития абстрактной алгебры. Однако наиболее значимые изменения были сделаны людьми, начиная с Фреге и приблизительно с 1879 года, которые занимались математической логикой.



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



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



image



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



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



image



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



Однако, как и Лейбниц, Пеано не желал останавливаться лишь на универсальной нотации для математики. Он хотел разработать универсальный язык для всего. Эта идея реализовалась у него в то, что он назвал интерлингва — язык на основе упрощённой латыни. Затем он написал нечто вроде краткого изложения математики, назвав это Formulario Mathematico, которое было основано на его обозначениях для формул, и труд этот был написал на этой производной от латыни — на интерлингве.



Интерлингва, подобно эсперанто, который появился примерно в это же время, так и не получил широкого распространения. Однако этого нельзя сказать об обозначениях Пеано. Сперва о них никто ничего толком и не слышал. Но затем Уайтхед и Рассел написали свой труд Principia Mathematica, в котором использовались обозначения Пеано.



Думаю, Уайтхед и Рассел выиграли бы приз в номинации "самая насыщенная математическими обозначениями работа, которая когда-либо была сделана без помощи вычислительных устройств". Вот пример типичной страницы из Principia Mathematica.



image



У них были все мыслимые виды обозначений. Частая история, когда авторы впереди своих издателей: Рассел сам разрабатывал шрифты для многих используемых им обозначений.



И, разумеется, тогда речь шла не о шрифтах TrueType или о Type 1, а о самых настоящих кусках свинца. Я о том, что Рассела можно было встретить с тележкой, полной свинцовых оттисков, катящему её в издательство Кембриджского университета для обеспечения корректной вёрстки его книг.



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



И хотя область математической логики немного прояснилась в результате деятельности Рассела и Уайтхеда, она всё ещё остаётся наименее стандартизированной и содержащей самую сложную нотацию.



Но что насчёт более распространённых составляющих математики?



Какое-то время в начале 20 века то, что было сделано в математической логике, ещё не произвело никакого эффекта. Однако ситуация резко начала меняться с движением Бурбаки, которое начало разрастаться во Франции в примерное сороковые года.



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



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



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



Есть один момент, который постоянно проявляется в этой области — нотация, как и обычные языки, сильно разделяет людей. Я имею в виду, что между теми, кто понимает конкретные обозначения, и теми, кто не понимает, имеется большой барьер. Это кажется довольно мистическим, напоминая ситуацию с алхимиками и оккультистами — математическая нотация полна знаков и символов, которые люди в обычной жизни не используют, и большинство людей их не понимают.



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



image



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



Что ж, это был краткий конспект некоторых наиболее важных эпизодов истории математической нотации.



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



Компьютеры



Вот вопрос: можно ли сделать так, чтобы компьютеры понимали эти обозначения?



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



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



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



Грамматика обычных разговорных языков развивалась веками. Без сомнения, многие римские и греческие философы и ораторы уделяли ей много внимания. И, по сути, уже примерно в 500 года до н. э. Панини удивительно подробно и ясно расписал грамматику для санскрита. Фактически, грамматика Панини была удивительно похожа по структуре на спецификацию правил создания компьютерных языков в форме Бэкуса-Наура, которая используется в настоящее время.



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



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



Были даже математики, которые работали над грамматиками обычных языков. Ранним примером являлся Джон Уоллис, который придумал формулу произведения Уоллиса для числа пи, и вот он писал работы по грамматике английского языка в 1658 году. Уоллис был тем самым человеком, который начал всю эту суматоху с правильным использованием "will" или "shall".



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



Некоторая определённость появилась в 50-е годы 20 века, когда Хомский и Бакус, независимо разработали идею контекстно-свободных языков. Идея пришла походу работы над правилами подстановки в математической логике, в основном благодаря Эмилю Посту в 20-х годах 20 века. Но, любопытно, что и у Хомского, и у Бакуса возникла одна и та же идея именно в 1950-е.



Бакус применил её к компьютерным языкам: сперва к Fortran, затем к ALGOL. И он заметил, что алгебраические выражения могут быть представлены в контекстно-свободной грамматике.



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



Конечно, лингвисты включая Хомского, потратили годы на демонстрацию того, насколько всё же эта идея не соответствует действительности. Но вещь, которую я всегда отмечал, а с научной точки зрения считал самой важной, состоит в том, что в первом приближении это всё-таки истина — то, что обычные естественные языки контекстно-свободны.



Итак, Хомский изучал обычный язык, а Бакус изучал такие вещи, как ALGOL. Однако никто из них не рассматривал вопрос разработки более продвинутой математики, чем простой алгебраический язык. И, насколько я могу судить, практически никто с тех времён не занимался этим вопросом.



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



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



Нил Сойффер потратил множество лет, работая над редактированием и интерпретацией математической нотации, и когда он присоединился к нам в 1991, он пытаться убедить меня, что с математической нотацией вполне можно работать — как с вводом, так и с выводом.



Часть с выводом данных была довольно простой: в конце концов, TROFF и TEX уже проделали большую работу в этом направлении.



Вопрос заключался во вводе данных.



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



Но что насчёт входных данных? Один из самых важных моментов заключался в том, с чем всегда сталкиваются при парсинге: если у вас есть строка текста с операторами и операндами, то как задать, что и с чем группируется?



Итак, допустим, у вас есть подобное математическое выражение.



Sin[x+1]^2+ArcSin[x+1]+c(x+1)+f[x+1]



Что оно означает? Чтобы это понять, нужно знать приоритеты операторов — какие действуют сильнее, а какие слабее в отношении операндов.



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



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



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



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



Дать людям возможность ввода в свободной форме — значительно более сложная задача. Но это то, что мы хотим реализовать.



Итак, что это влечёт?



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



Вот ключевая проблема: традиционная математическая нотация содержит неоднозначности. По крайней мере, если вы захотите представить её в достаточно общем виде. Возьмём, к примеру, "i". Что это — Sqrt[-1] или переменная "i"?



В обычном текстовом InputForm в Mathematica все подобные неоднозначности решены простым путём: все встроенные объекты Mathematica начинаются с заглавной буквы.



Но заглавная "I" не очень то и похожа на то, чем обозначается Sqrt[-1] в математических текстах. И что с этим делать? И вот ключевая идея: можно сделать другой символ, который вроде тоже прописная «i», однако это будет не обычная прописная «i», а квадратный корень из -1.



Можно было бы подумать: Ну, а почему бы просто не использовать две «i», которые бы выглядели одинаково, — прям как в математических текстах — однако из них будет особой? Ну, это бы точно сбивало с толку. Вы должны будете знать, какую именно «i» вы печатаете, а если вы её куда-то передвинете или сделаете что-то подобное, то получится неразбериха.



Итак, значит, должно быть два "i". Как должна выглядеть особая версия этого символа?



У нас была идея — использовать двойное начертание для символа. Мы перепробовали самые разные графические представления. Но идея с двойным начертанием оказалась лучшей. В некотором роде она отвечает традиции в математике обозначать специфичные объекты двойным начертанием.



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



Таким образом, "i" с двойным начертанием есть специфичный объект, который мы называем ImaginaryI. Вот как это работает:







Идея с двойным начертанием решает множество проблем.



В том числе и самую большую — интегралы. Допустим, вы пытаетесь разработать синтаксис для интегралов. Один из ключевых вопросов — что может означать "d" в интеграле? Что, если это параметр в подынтегральном выражении? Или переменная? Получается ужасная путаница.



Всё становится очень просто, если использовать DifferentialD или "d" с двойным начертанием. И получается хорошо определённый синтаксис.



Можно проинтегрировать x в степени d, деленное на квадратный корень от x+1. Вот как это работает:







Оказывается, что требуется всего лишь несколько маленьких изменений в основании математического обозначения, чтобы сделать его однозначным. Это удивительно. И весьма здорово. Потому что вы можете просто ввести что-то, состоящее из математических обозначений, в свободной форме, и оно будет прекрасно понято системой. И это то, что мы реализовали в Mathematica 3.



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



Одна из них — ввод таких вещей, как степени, в качестве верхних индексов. В обычном текстовом вводе для обозначения степени используется символ ^. Идея заключается в использовании control — ^, с помощью которой можно вводить явный верхний индекс. Та же идея для сочетания control — /, с помощью которого можно вводить «двухэтажную» дробь.



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







Но мы можем брать фрагменты из этого результата и работать с ними.







И смысл в том, что это выражение полностью понятно для Mathematica, то есть оно может быть вычислено. Из этого следует, что результаты выполнения (Out) — объекты той же природы, что и входные данные (In), то есть их можно редактировать, использовать их части по отдельности, использовать их фрагменты в качестве входных данных и так далее.



Чтобы заставить всё это работать, нам пришлось обобщить обычные языки программирования и кое-что проанализировать. Прежде была внедрена возможность работать с целым «зоопарком» специальных символов в качестве операторов. Однако, вероятно, более важно то, что мы внедрили поддержку двумерных структур. Так, помимо префиксных операторов, имеется поддержка оверфиксных операторов и прочего.



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



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



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



Однако если нужно полное соответствие с обычными учебниками, то понадобится уже что-то другое. И вот другая важная идея, реализованная в Mathematica 3: разделить StandardForm и TraditionalForm.



Любое выражение я всегда могу сконвертировать в TraditionalForm.







И в действительности TraditionalForm всегда содержит достаточно информации, чтобы быть однозначно сконвертированным обратно в StandardForm.



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



Так что насчёт ввода TraditionalForm?



Вы могли заметить пунктир справа от ячейки [в других выводах ячейки были скрыты для упрощения картинок — прим. ред.]. Они означают, что есть какой-то опасный момент. Однако давайте попробуем кое-что отредактировать.







Мы прекрасно можем всё редактировать. Давайте посмотрим, что случится, если мы попытаемся это вычислить.







Вот, возникло предупреждение. В любом случае, всё равно продолжим.







Что ж, система поняла, что мы хотим.



Фактически, у нас есть несколько сотен эвристических правил интерпретации выражений в традиционной форме. И они работают весьма хорошо. Достаточно хорошо, чтобы пройти через большие объёмы устаревших математических обозначений, определённых, скажем, в TEX, и автоматически и однозначно сконвертировать их в осмысленные данные в Mathematica.



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



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



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



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



image



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



Таким образом, мы смогли найти способ интерпретировать стандартную математическую нотацию. Означает ли это, что теперь вся работа в Mathematica должна теперь проводиться в рамках традиционных математических обозначений? Должны ли мы ввести специальные символы для всех представленных операций в Mathematica? Таким образом можно получить весьма компактную нотацию. Но насколько это разумно? Будет ли это читаемо?



Пожалуй, ответом будет нет.



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



А кому-то не нужны специальные обозначения. А кто-то пользуется в Mathematica FullForm. Однако с этой формой весьма утомительно работать. Возможно, именно поэтому синтаксис языков наподобие LISP кажется столь трудным — по сути это синтаксис FullForm в Mathematica.



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



image



Довольно трудно читать.



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



image



Она тоже относительно нечитабельная.



Вопрос заключается в том, что же находится между двумя такими крайностями, как LISP и APL. Думаю, эта проблема очень близка к той, что возникала при использовании очень коротких имён для команд.



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



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



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







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



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



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



Один из наиболее любопытных моментов — во все времена и практически без исключения в качестве переменных использовались лишь латинские и греческие символы. Ну, Кантор ввёл алеф, взятый из иврита, для своих кардинальных чисел бесконечных множеств. И некоторые люди утверждают, что символ частной производной — русская д, хотя я думаю, что на самом деле это не так. Однако нет никаких других символов, которые
Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Теория и практика AOP. Как мы это делаем в Яндексе

Четверг, 30 Июня 2016 г. 16:47 (ссылка)

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



— Насколько это увеличит дистрибутив?

— Как это поможет нам писать меньше и эффективнее?







Сейчас мы используем RxJava, Dagger 2, Retrolambda и AspectJ. И если о первых трёх технологиях слышал каждый разработчик, а многие даже применяют их у себя, то о четвёртой знают только хардкорные джависты, пишущие большие серверные проекты и разного рода энтерпрайзы.



Передо мной стояла цель ответить на эти два вопроса и обосновать использование AOP-методологии в Android-проекте. А это значит — написать код и показать наглядно, как аспектно-ориентированное программирование поможет нам ускорить и облегчить работу разработчиков. Но обо всём по порядку.







Начнём с азов



Хотим обернуть все запросы к API в трай-кетч, и чтоб никогда не падало! А ещё логи! А ещё...

Пфф… Пишем семь строчек кода и вуаля.

abstract aspect NetworkProtector { // аспектный класс, по умолчанию синглтон

abstract pointcut myClass(); // срез, он же — поиск мест внедрения нижележащих инструкций

Response around(): myClass() && execution(* executeRequest(..)) { // встраиваемся «вместо» методов executeRequest
try {
return proceed(); // выполняем само тело метода, перехваченного around'ом
} catch (NetworkException ex) {
Response response = new Response(); // если сервер не умеет в обработку ошибок...
response.addError(new Error(ex)); // …ну или сетевой слой написан, мягко говоря, не очень
return response;
}
}
}




Легко, правда? А теперь немного терминологии, без неё дальше никак.



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



Первое, с чего разработчик начинает постигать дзен, — поиск однородностей. Если два класса делают сколько-нибудь похожую работу, например оперируют одним и тем же объектом, — они однородны. Когда n сущностей абсолютно одинаково взаимодействуют с внешним миром — они однородны. Всё это можно описать срезами (pointcut) и начать увлекательный путь к просвещению.



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



Лучше всего начать описание срезов с аннотаций. И, честно говоря, лучше ими же закончить. Это прекрасный и очевидный подход, пришедший из пятой джавы. Именно аннотации скажут непросвещённому инженеру, что в этом классе творится какая-то запредельная магия. Именно аннотации являются вторым сердцем Spring-фреймворка, которые разруливает AspectJ под капотом. Этим же путём идут все современные большие проекты — AndroidAnnotations, Dagger, ButterKnife. Почему? Очевидность и лаконичность, Карл. Очевидность и лаконичность.



oop and aop



Инструментарий



Поговорим отдельно и коротко про наш разработческий арсенал. В среде Android великое множество инструментов и методологий, архитектурных подходов и различных компонентов. Здесь и миниатюрные библиотеки-хелперы, и монструозные комбайны типа Realm. И относительно небольшие, но серьёзные Retrofit, Picasso.

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



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



Та же ситуация, когда речь заходит об АОП.



Выбор проблем и решений



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

Пример вполне очевидной и простой «задачи» — сетевой слой. Нам понадобится:


  • Изолировать сетевой слой. (Retrofit)

  • Обеспечить прозрачное общение с UI-слоем. (Robospice, RxJava)

  • Предоставить полиморфный доступ. (EventBus)



И если раньше вы не работали с RxJava или EventBus, решение этой задачи обернётся массой подводных граблей. Начиная от синхронизации и заканчивая lifecycle.



Пару лет назад мало кто из Android-девелоперов знал про Rx, а сейчас он набирает такую популярность, что скоро может стать обязательным пунктом в описании вакансий. Так или иначе, мы всегда развиваем себя и адаптируемся к новым технологиям, удобным практикам, модным веяниям. Как говорится, мастерство приходит с опытом. Даже если на первый взгляд они не особо и нужны были :)



Новые горизонты, или зачем нужен АОП?



В аспектной среде мы видим кардинально новое понятие — однородность. Сразу в примерах и без лишних слов. Но не будем далеко отходить от Android'a.



public class MyActivityImpl extends Activity {

protected void onCreate(Bundle savedInstanceState) {
TransitionProvider.overrideWindowTransitionsFor(this);

super.onCreate(savedInstanceState);
this.setContentView(R.layout.activity_main);

Toolbar toolbar = ToolbarProvider.setupToolbar(this);
this.setActionBar(toolbar);

AnalyticsManager.register(this);
}
}




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

Чтобы всё это приобрело красивый и системный (от слова «систематизировать») вид, сперва хорошенько подумаем вот над чем: как нам изолировать такую логику? Хорошим решением здесь будет написать несколько отдельных классов, каждый из которых станет отвечать за свой маленький кусочек.



Сначала изолируем поведение тулбара
public aspect ToolbarDecorator {

pointcut init(): execution(* Activity+.onCreate(..)) && // тело метода в любом наследнике Activity
@annotation(StyledToolbarAnnotation); // только с аннотацией над классом или методом

after() returning: init() { // не будем стайлить тулбар, если onCreate крашнулся
Activity act = thisJoinPoint.getThis();
Toolbar toolbar = setupToolbar(act);
act.setActionBar(toolbar);
}
}




Теперь избавимся от переопределения анимаций активити
public aspect TransitionDecorator                          {

pointcut init(TransitionAnnotation t): @within(t) && // аннотация мастхэв
execution(* Activity+.onCreate(..)); // уже видели

before(TransitionAnnotation transition): init(transition) {
Activity act = thisJoinPoint.getThis();
registerState(transition);
overrideWindowTransitionsFor(act);
}
}




И, наконец — выкинем аналитику в отдельный класс
public aspect AnalyticsInjector {
private static final String API_KEY = “…”;

pointcut trackStart(): execution(* Activity+.onCreate(..)) &&
@annotation(WithAnalyticsInit);

after(): returning: trackStart() {
Context context = thisJoinPoint.getThis();
YandexMetrica.activate(context, API_KEY);
Adjust.onCreate(new AdjustConfig(context, “…”, PROD));
}
}






Ну вот и всё. Мы получили чистый и компактный код, где каждая порция однородной функциональности красиво изолирована и пристёгивается только туда, где она явно нужна, а не в каждый класс, посмевший отнаследоваться от Activity.

Финальный вид:

@StyledToolbarAnnotation
@TransitionAnnotation(TransitionType.MODAL)
@WithContentViewLayout(R.layout.activity_main) // ну прямо как AndroidAnnotations! \m/
public class MyActivityImpl extends Activity {

@WithAnalyticsInit
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/* ... */
}
}




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



Благодаря аннотациям сохраняется понимание процессов, происходящих в коде. Новичок сразу смекнёт, что здесь подкапотная магия. Самодокументируемость может позволить нам легко управлять служебными инструментами — логированием, профилированием. Инструментирование Java-кода можно легко настроить на поиск по вхождениям ключевых слов в именах классов, методов или полей, доступ и использование которых хотим отследить.



О нестандартных аспектах применения аспектов.


Большие команды часто выстраивают строгий flow коммита, по которому код проходит множество этапов. Здесь могут быть тестовые сборки на CI, инспекция кода, обкатка тестами, pull-request. Количество итераций в этом процессе можно сократить без потери качества путём введения статического анализа кода, для которого вовсе не обязательно устанавливать дополнительное ПО, заставлять разработчика изучать lint-репорты или выносить этот кейс на сторону того же svc.



Достаточно описать директивы компилятору, который сумеет сам определить, что именно в нашем коде делается «неправильно» или «потенциально плохо».



Простенькая проверка на запись филда вне метода-сеттера
public aspect AccessVerifier {

declare warning : fieldSet() && within(ru.yandex.example.*)
: "writing field outside setter" ;

pointcut fieldSet(): set(!public * *) && !withincode(* set*(..));
// set означает доступ к полю на запись, а в конце — паттерн метода-сеттера
}






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



Проверка на отлов NPE и вызов конструктора за пределами билд-метода
public aspect AnalyticsVerifier {

declare error : handler(NullPointerException+) // декларация try-catch блока с обработкой NPE
&& withincode(* AnalyticsManager+.register(..))
: "do not handle NPE in this method";

declare error : call(AnalyticsManager+.new(..))
&& !cflow(static AnalyticsManager.build(..))
: "you should not call constructor outside a AnalyticsManager.build() method";
}


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





Мне важен порядок! А вдруг что-то отработает не вовремя?
public aspect StrictVerifyOrder {

// сначала инжекторы/декораторы, потом проверяем что да как
declare precedence: *Injector, *Decorator, *Verifier, *;
// не обязательно писать названия целиком, кругом паттерны!
}


Просто об этом часто спрашивают :) Да, можно ручками настроить «важность» и очерёдность каждого отдельного аспекта.

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





Выводы



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



Мы пишем один коротенький класс в десяток строк и внедряем его в два десятка других классов через прозрачные и простые «якоря» или условия. При этом затраты на описание устойчивых аспектных классов тем ниже, чем быстрее мы сами адаптируемся к поиску и применению однородностей в своём коде.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/280117/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Почему разработчики видеонаблюдения любят ритейл больше, чем производство?

Четверг, 30 Июня 2016 г. 16:07 (ссылка)

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



Почему? Давайте разбираться



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



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



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



image

Тепловая карта интенсивности движения Macroscop в холле торгового центра



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



Не тут-то было…

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



Судите сами. В Macroscop мы получаем много запросов на видеоаналитику от разных производственных компаний. Например, с помощью видеосистем наши заказчики хотят:

• распознавать отломившийся зубец экскаватора;

• определять фракции щебня в кузове самосвалов на карьере;

• считать пластиковые бутылки в паллете;

• детектировать драгоценные камни на конвейерной ленте.



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

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



Можно сэкономить

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



#Лайфхак

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



image



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

Стоимость программной составляющей этого решения 1800 рублей (за 1 камеру).



Альтернативная точка зрения

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

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



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



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



В итоге

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



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



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

1. Платить достаточно большие деньги за индивидуальную разработку.

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

3. Или не использовать видеоанализ, а подключать человеческие ресурсы.

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/304490/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Как разравнять Пирамиду смерти

Четверг, 30 Июня 2016 г. 14:02 (ссылка)

Настроить webpack по мануалу, запрограммировать ангуляр и даже послать json по ajax — кажись каждый может, но вот как взглянешь на сам код… В этом посте будет показана разница между нововведениями.



Итак вы открыли ноду и увидели, что почти все функции «из коробки» последним аргументом принимают колбэк.



var fs = require("fs");
fs.readdir(__dirname, function(error, files) {
if (error) {
console.error(error);
} else {
for (var i = 0, j = files.length; i < j; i++) {
console.log(files[i]);
}
}
});




Пирамида смерти





А в чем собственно проблема? Проблема в том, что на маке с ретиной порой заканчивается место под пробелы (конечно можно сказать, что 4 пробела на таб — роскошь) и весь код маячит далеко справа при использовании хотя бы десятка таких функций подряд.







var fs = require("fs");
var path = require("path");
var buffers = [];

fs.readdir(__dirname, function(error1, files) {
if (error1) {
console.error(error1);
} else {
for (var i = 0, j = files.length; i < j; i++) {
var file = path.join(__dirname, files[i]);
fs.stat(file, function(error2, stats) {
if (error2) {
console.error(error2);
} else if (stats.isFile()) {
fs.readFile(file, function(error3, buffer) {
if (error3) {
console.error(error3);
} else {
buffers.push(buffer);
}
});
}
});
}
}
});

console.log(buffers);




Так что же c этим можно сделать? Не применяя библиотек, для наглядности, так как с ними все примеры не займут и строчки кода, дальше будет показано как с этим справиться используя сахар es6 и es7.



Promise



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



var fs = require("fs");
var path = require("path");

function promisify(func, args) {
return new Promise(function(resolve, reject) {
func.apply(null, [].concat(args, function(error, result) {
if (error) {
reject(error);
} else {
resolve(result);
}
}));
});
}

promisify(fs.readdir, [__dirname])
.then(function(items) {
return Promise.all(items.map(function(item) {
var file = path.join(__dirname, item);
return promisify(fs.stat, [file])
.then(function(stat) {
if (stat.isFile()) {
return promisify(fs.readFile, [file]);
} else {
throw new Error("Not a file!");
}
})
.catch(function(error) {
console.error(error);
});
}));
})
.then(function(buffers) {
return buffers.filter(function(buffer) {
return buffer;
});
})
.then(function(buffers) {
console.log(buffers);
})
.catch(function(error) {
console.error(error);
});




Кода стало немного больше, но зато сильно сократилась обработка ошибок.



Обратите внимание .catch был использован два раза потому, что Promise.all использует fail-fast стратегию и бросает ошибку, если ее бросил хотя бы один промис на практике такое пременение далеко не всегда оправдано, например если нужно проверить список проксей, то нужно проверить все, а не обламываться на первой «дохлой». Этот вопрос решают библиотеки Q и Bluebird и тд, поэтому его освещать не будем.



Теперь перепишем это все с учетом arrow functions, desctructive assignment и modules.



import fs from "fs";
import path from "path";

function promisify(func, args) {
return new Promise((resolve, reject) => {
func.apply(null, [...args, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
}]);
});
}

promisify(fs.readdir, [__dirname])
.then(items => Promise.all(items.map(item => {
const file = path.join(__dirname, item);
return promisify(fs.stat, [file])
.then(stat => {
if (stat.isFile()) {
return promisify(fs.readFile, [file]);
} else {
throw new Error("Not a file!");
}
})
.catch(console.error);
})))
.then(buffers => buffers.filter(e => e))
.then(console.log)
.catch(console.error);





Generator



Теперь совсем хорошо, но…ведь есть еще какие-то генераторы, которые добавляют новый тип функций function* и ключевое слово yeild, что будет если использовать их?



import fs from "fs";
import path from "path";

function promisify(func, args) {
return new Promise((resolve, reject) => {
func.apply(null, [...args, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
}]);
});
}

function getItems() {
return promisify(fs.readdir, [__dirname]);
}

function checkItems(items) {
return Promise.all(items.map(file => promisify(fs.stat, [path.join(__dirname, file)])
.then(stat => {
if (stat.isFile()) {
return file;
} else {
throw new Error("Not a file!");
}
})
.catch(console.error)))
.then(files => {
return files.filter(file => file);
});
}

function readFiles(files) {
return Promise.all(files.map(file => {
return promisify(fs.readFile, [file]);
}));
}

function * main() {
return yield readFiles(yield checkItems(yield getItems()));
}

const generator = main();

generator.next().value.then(items => {
return generator.next(items).value.then(files => {
return generator.next(files).value.then(buffers => {
console.log(buffers);
});
});
});




Цепочки из generator.next().value.then не лучше чем колбэки из первого примера однако это не значит, что генераторы плохие, они просто слабо подходят под эту задачу.



Async/Await



Еще два ключевых слова, с мутным значением, которые можно попробовать прилепить к решению, уже надоевшей задачи по чтению файлов- Async/Await
import fs from "fs";
import path from "path";

function promisify(func, args) {
return new Promise((resolve, reject) => {
func.apply(null, [...args, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
}]);
});
}

function getItems() {
return promisify(fs.readdir, [__dirname]);
}

function checkItems(items) {
return Promise.all(items.map(file => promisify(fs.stat, [path.join(__dirname, file)])
.then(stat => {
if (stat.isFile()) {
return file;
} else {
throw new Error("Not a file!");
}
})
.catch(console.error)))
.then(files => {
return files.filter(file => file);
});
}

function readFiles(files) {
return Promise.all(files.map(file => {
return promisify(fs.readFile, [file]);
}));
}

async function main() {
return await readFiles(await checkItems(await getItems()));
}

main()
.then(console.log)
.catch(console.error);




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



Если писать этот код не для примера, то получилось бы как-то так:



import bluebird from "bluebird";
import fs from "fs";
import path from "path";

const myFs = bluebird.promisifyAll(fs);

function getItems(dirname) {
return myFs.readdirAsync(dirname)
.then(items => items.map(item => path.join(dirname, item)));
}

function getFulfilledValues(results) {
return results
.filter(result => result.isFulfilled())
.map(result => result.value());
}

function checkItems(items) {
return bluebird.settle(items.map(item => myFs.statAsync(item)
.then(stat => {
if (stat.isFile()) {
return [item];
} else if (stat.isDirectory()) {
return getItems(item);
}
})))
.then(getFulfilledValues)
.then(result => [].concat(...result));
}

function readFiles(files) {
return bluebird.settle(files.map(file => myFs.readFileAsync(file)))
.then(getFulfilledValues);
}

async function main(dirname) {
return await readFiles(await checkItems(await getItems()));
}

main(__dirname)
.then(console.log)
.catch(console.error);

Original source: habrahabr.ru.

https://habrahabr.ru/post/304474/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Разбор задач отборочного раунда RCC 2016

Среда, 29 Июня 2016 г. 18:27 (ссылка)





Завершился очередной — отборочный — раунд международного чемпионата программистов Russian Code Cup 2016. И по сложившейся традиции мы предлагаем вам ознакомиться с решениями задач, которые предлагались конкурсантам в последнем раунде. На этот раз задач было шесть, хотя на их решение по-прежнему выделялось два часа. За выход в следующий раунд сражалось 604 человека. RCC впервые проводится в том числе для англоязычных участников. Результат не заставил ждать — русскоговорящим программистам будет составлена серьезная конкуренция. В финал прошло 50 человек и из них 19 человек — не русскоговорящие участники. Среди финалистов представители России, Украины, Беларусии, Литвы, Словакии, Армении, Польши, Швейцарии, Финляндии, Японии, Китая, Южной Кореи.




  1. Контрольная работа

  2. Многоножка

  3. Двоичное дерево

  4. Пробирки и реактивы

  5. Размен денег

  6. Задача F



Задача А. Контрольная работа



Условие



Ограничение по времени 2 секунды

Ограничение по памяти 256 мегабайт



Скоро Коле предстоит писать важную контрольную, поэтому он решил тщательно подготовиться. Он выяснил, что у контрольной будет n вариантов, i-й из которых состоит из mi заданий, пронумерованных от 1 до mi. Для каждого задания каждого варианта Коля оценил время tij минут, которое займёт у него решение.



Во время контрольной он планирует решать задания строго в том порядке, в котором они расположены в варианте. Сдать работу надо не позже окончания контрольной, которая длится t минут. Конечно, Коля мог бы списать контрольную, но он считает, что это не очень правильно. Поэтому готов списать не более одного задания. На списывание любого задания он потратит t0 минут.



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



Формат входных данных



В первой строке находятся три целых числа n, t и t0 (1 <= n <= 100, 1 <= t <= 10 000, 1 <= t0 <= 100) — количество вариантов, продолжительность контрольной и время, которое Коля потратит на списывание задания.



В следующих n строках находится описание вариантов. Первое число mi (1 <= mi <= 100) — количество заданий в варианте i. Следующие mi чисел tij (1 <= tij <= 100) — время, которое Коля потратит на решение j-го задания i-го варианта.



Формат выходных данных



Для каждого варианта выведите на новой строке одно целое число — количество заданий, которые Коля успеет решить, если ему попадётся этот вариант.



Примеры



Входные данные

4 45 5

5 10 10 10 10 10

4 30 10 10 10

3 40 40 5

1 100



Выходные данные

5

4

2

1



Решение



Переберём префикс заданий, которые решит Коля. Найдём время, которое он потратит, если будет сам решать все задания, и максимальное время, которое он потратит на одно задание в таком случае. Понятно, что если он будет списывать, то списывать надо именно самое долгое задание из тех, что он делает. Тогда время, которое Коля потратит, равно S – max(0, M t0). Теперь найдём наибольший префикс, у которого это время не превышает t.



Задача B. Многоножка



Условие



Ограничение по времени 2 секунды

Ограничение по памяти 256 мегабайт



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



Каждый день Филя записывал себе в блокнот два числа — сколько левых и сколько правых ног использовала многоножка в этот день.



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



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



Формат входных данных



Входные данные содержат несколько тестовых наборов. В первой строке задано количество тестов t (1 <= t <= 10 000).

Каждый из тестов описывается следующим образом: в первой строке описания теста содержится число n (2 <= n <= 109) — суммарное количество ног многоножки, а во второй строке число m (1 <= m <= 105) — количество записей.



В следующих m строках содержатся по два числа li и ri (1 <= li, ri <= n) — количество левых и правых ног, используемых многоножкой в i-й день согласно записям Фили, соответственно.



Суммарное количество записей по всем тестам не превосходит 105.



Формат выходных данных



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



Примеры



Входные данные

3

4

3

1 2

1 3

1 4

2

2

2 2

1 2

5

4

1 4

2 3

3 2

4 1



Выходные данные

1 3

1 1

1 4



Решение



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



Теперь нужно узнать, сколько записей из первых i будут верны. Запись c номером j <= i верна, если rj <= n – li. Нахождение всех таких rj является стандартной задачей и решается, например, деревом отрезков.



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



Задача C. Двоичное дерево



Условие



Ограничение по времени 4 секунды

Ограничение по памяти 256 мегабайт



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



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



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



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



При выводе количества дней, которые ему необходимы, Вася хочет увидеть лишь остаток от деления этого числа на число 109 + 7. Обратите внимание, что Васе надо минимизировать именно число дней, за которое он сможет завершить формирование дерева, а не остаток от его деления на 109 + 7, остаток необходимо взять непосредственно перед выводом.



Формат входных данных



Входные данные содержат несколько тестовых наборов. В первой строке задано количество тестов t.



Каждый из следующих t тестов описывается следующим образом: в первой строке описания теста дано одно целое число n (2 <= n <= 2 • 105) — количество вершин в дереве. В следующих n – 1 строках описания теста даны пары чисел ui, vi (1 <= ui, vi <= n) — номера вершин, соединённых i-м ребром. Гарантируется, что рёбра образуют дерево.



Гарантируется, что сумма чисел n по всем тестам не превосходит 2 • 105.



Формат выходных данных



Выведите ответ для каждого теста на отдельной строке.



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



Иначе выведите два целых числа — номер вершины, которую нужно выбрать корнем, и остаток от деления числа дней, которое потребуется Васе, на 109 + 7. Если есть несколько вершин, позволяющих выполнить задание в минимальный срок, выведите вершину с минимальным номером.



Примеры



Входные данные

3

3

1 3

3 2

4

4 2

3 2

3 1

5

1 2

1 3

1 4

1 5



Выходные данные

3 0

2 3

-1



Решение



Чтобы минимизировать количество дней, которые придётся потратить Васе, нужно минимизировать высоту итогового бинарного дерева — если глубина листа в итоговом дереве равна h, то Вася потратит 2h – 1 – n дней на то, чтобы подвесить необходимое число вершин.



Заметим, что если в дереве есть вершина степени больше 3, то задание выполнить не получится, — в полном бинарном дереве все вершины имеют не больше трёх соседей. Обратим внимание, что корень в ответе имеет ровно двух сыновей.



Таким образом, чтобы решить задачу, нужно перебрать корень — вершину степени не больше 2 — и выбрать корнем такую вершину, что максимальное из расстояний от неё до всех остальных минимально. Это и будет высота итогового дерева.



Задача D. Пробирки и реактивы



Условие



Ограничение по времени 2 секунды

Ограничение по памяти 256 мегабайт



Сегодня учитель химии дал Пете очень ответственное задание — разлить реактивы по пробиркам. В распоряжение ему предоставили n пробирок и m реактивов. Для каждой пробирки известны mini и maxi — минимальное и максимальное суммарное количество жидкости в миллилитрах, которое может в ней находиться, соответственно, а также номер реактива ci и число pi. Для каждого реактива также известно число vi — сколько миллилитров его есть у Пети. Учитель просит Петю разлить все реактивы так, чтобы в i-й пробирке как минимум pi процентов от всей жидкости, в ней находящейся, занимал реактив ci, а также чтобы в каждой пробирке были удовлетворены ограничения на mini и maxi. Все реактивы должны быть полностью разлиты. Будем считать, что никаких химических реакций и изменений объёма реактивов не происходит.



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



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




  • В первую пробирку налить 3 миллилитра первого реактива и 2 миллилитра второго.

  • Во вторую пробирку налить 4 миллилитра третьего реактива и 1 миллилитр второго.

  • В последнюю пробирку налить 3 миллилитра четвёртого реактива и 1 миллилитр второго.



В этом случае все ограничения на mini и maxi выполняются, а также выполняются ограничения на проценты реактивов в пробирках: в первой пробирке 3/5 = 60% первого реактива, во второй пробирке 4/5 = 80% третьего реактива, а в последней пробирке 3/4 = 75% >= 70% четвёртого реактива. Также все реактивы полностью разлиты, поэтому все требования, данные учителем, выполнены и ответ верен.



Формат входных данных



Входные данные содержат несколько тестовых наборов. В первой строке задано количество тестов t (1 <= t <= 100).



Каждый из следующих t тестов описывается таким образом: в первой строке описания теста содержатся два числа n, m (1 <= n, m <= 105) — количество пробирок и реактивов соответственно.



В i-й из следующих n строк содержится четыре целых числа mini, maxi, ci, pi (1 <= mini <= maxi <= 105, 1 <= ci <= m, 1 <= pi <= 100) — минимальное и максимальное суммарное количество миллилитров жидкости, которое можно налить в i-ю пробирку, номер реактива и его минимальный процент содержания в пробирке соответственно.



В последней строке описания теста содержатся m целых чисел vi (1 <= vi <= 105) — количество миллилитров i-го реактива у Пети.



Гарантируется, что сумма n и сумма m во всех тестах не превосходит 105.



Формат выходных данных



Для каждого теста выведите ответ на него.



Если ответ существует, в первой строке выведите YES, а в i-й из следующих n строк выведите сначала число k — количество различных реактивов в i-й пробирке, а затем k пар положительных чисел id, v — номер реактива и его количество в пробирке. Если существует несколько ответов, разрешается вывести любой.



Если ответа на тест не существует, в единственной строке выведите NO.



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



Примеры



Входные данные

2

3 4

5 8 1 60

4 6 3 80

3 4 4 70

3 4 4 3

3 4

6 8 1 60

4 6 3 80

3 4 4 70

3 4 4 3



Выходные данные

YES

2 1 3.0000000000 2 2.0000000000

2 2 1.0000000000 3 4.0000000000

2 2 1.0000000000 4 3.0000000000

NO



Решение



Приведём конструктивный алгоритм в несколько этапов:




  • Если sum(mini) > sum(vi) — ответ NO.

  • Если sum(maxi) < sum(vi) — ответ NO.

  • Нальём в каждую пробирку minipi / 100 миллилитров реактива ci.

  • После этого для каждого реактива i отсортируем пробирки, для которых cj = i, по возрастанию pj и будем по очереди наливать в них оставшийся реактив номер i.

  • В каждую пробирку нальём ещё (maxj – minj) pj / 100 миллилитров i-го реактива или меньше, если он закончится.

  • Всё оставшееся от реактивов разольём по пробиркам любым способом: в i-ю пробирку суммарно можно налить любое количество жидкости, не превосходящее summaryi • 100 / pi (summaryi — суммарное количество жидкости в ней на текущий момент).

  • Если разлить не получилось и жидкость осталась — ответ NO.

  • Единственная оставшаяся проблема — в каких-то пробирках всё ещё может быть меньше mini миллилитров жидкости.

  • Для исправления этого в пробирке i сначала перельём из всех остальных пробирок j реактивы, отличные от cj, в i-ю пробирку, так, чтобы в j-й пробирке осталось хотя бы minj жидкости.

  • Если и этого не хватило, перельём из остальных пробирок j жидкость номер cj (теперь это не испортит условие на процентное содержание).



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

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



Задача E. Размен денег



Условие



Ограничение по времени 4 секунды

Ограничение по памяти 256 мегабайт



За свою долгую жизнь Боря собрал коллекцию из n монет. Он выложил все эти монеты в ряд. При этом i-я в ряду монета имеет номинал ai.



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



Боря хочет ответить на несколько запросов. В каждом запросе Боря хочет узнать, какую минимальную сумму он не сможет заплатить без сдачи, если он возьмёт все монеты с li-й по ri-ю. Более формально — он хочет найти такое минимальное натуральное число z, что нельзя выбрать подмножество монет с номерами от li до ri, суммарный номинал которых равен z.



Формат входных данных



В первой строке задано два целых числа n и m (1 <= n, m <= 150 000) — количество монет у Бори и количество запросов. В следующей строке задано n чисел ai (1 <= ai <= 109) — номинал i-й монеты.



В следующих m строках задано по два числа li и ri (1 <= li <= ri <= n) — описание запросов.



Формат выходных данных



На каждый из m запросов выведите минимальную сумму, которую нельзя заплатить без сдачи, воспользовавшись монетами с li-й по ri-ю.



Примеры



Входные данные

5 5

2 1 5 3 1

1 5

1 3

1 1

2 4

2 5



Выходные данные

13

4

1

2

11



Решение



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



Пусть на очередном шаге мы рассмотрели все монеты, стоимость которых не превышает X, и мы можем заплатить любую сумму до Y включительно. Пусть суммарная стоимость монет, каждая из которых стоит больше X, но не больше Y + 1, равна sum. Тогда если sum = 0, то Y + 1 мы не сможем представить с помощью этого набора. Иначе перейдём к состоянию, что рассмотрены все монеты, стоимость которых не больше Y + 1, и мы можем представить любую сумму до Y + sum включительно.



Заметим, что стоимость максимальной монеты, которую мы рассмотрели, растёт как минимум так же быстро, как числа Фибоначчи. Значит, этот процесс завершится за O(log Answer) итераций.



Чтобы решить исходную задачу, необходимо научиться находить сумму чисел, меньших X, на отрезке. Если делать это за O(log n), то можно решать задачу суммарно за O(m log n log Answer).



Эта задача является стандартной. Рассмотрим метод, который требует линейного количества памяти. Для этого отсортируем все числа в исходном массиве и будем отвечать на все запросы параллельно. На очередной итерации для каждого запроса известно текущее максимальное количество денег, которое можно получить. Отсортируем все запросы по нему. Далее будем добавлять в дерево Фенвика числа исходного массива в порядке возрастания и в нужный момент обновлять границу для очередного запроса.



Задача F



Условие



Ограничение по времени 5 секунд

Ограничение по памяти 256 мегабайт



Гном Паша пишет отборочный раунд Gnome Math Cup. В качестве задачи F предложена следующая.



Дано n натуральных чисел a1, a2, ..., an и натуральное число d, требуется найти любой набор ненулевых целых чисел x1, x2, ..., xn, такой, что a1x1 + a2x2 + ... + anxn = d. Так как у гномов плохо с умножением и сложением, все числа d, ai не превышают 106, а числа xi должны быть от –106 до 106, но не равны 0.



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



Формат входных данных





Первая строка входных данных содержит t — количество тестов. Каждый тест задаётся двумя строками. Первая строка содержит два числа n и d (1 <= n <= 105, 1 <= d <= 106). Вторая строка содержит n чисел ai (1 <= ai <= 106). Сумма n по всем тестам не превосходит 105.



Формат выходных данных



Выведите ответ на каждый тест. Если искомый набор xi существует, то в первой строке выведите YES и во второй выведите любой подходящий набор. Иначе в единственной строке ответа на тест выведите NO.



Примеры



Входные данные

2

2 1

2 3

3 3

2 3 1000



Выходные данные

YES

2 -1

YES

503 -1 -1



Решение



Ответ NO бывает только в одном случае, когда d не делится на наибольший делитель всех чисел ai. В любом другом случае ответ всегда существует. Для начала мы научимся получать хоть какой-то ответ без ограничения на значения, предварительно поделив все ai и d на gcd(a1, ..., an). Если n = 1, то x1 = d/a1. В других случаях мы будем подбирать для каждого префикса [1, p] такие xi, p, что a1x1, p + ... + apxp, p = gcd(a1, ..., ap). Делается это индуктивным методом. x1, 1 = 1. Пусть мы уже нашли xi, p и хотим найти xi, p + 1. С помощью расширенного алгоритма Евклида мы находим s и t: s • gcd(a1, ..., ap) + tap + 1 = gcd(gcd(a1, ..., ap), ap + 1) = gcd(a1, ..., ap + 1). Тогда для i <= p xi, p + 1 = s xi, p и xp + 1, p + 1 = t. Получив xi, n, вычисляем xi = d/gcd(a1, ..., an)xi, n = dxi, n. Такое решение работает за O(n2), что не подходит под ограничения. Чтобы сократить время работы, мы выберем среди ai подмножество, у которого наибольший общий делитель такой же, как у всего множества, и такое, что нельзя уменьшить. Для этого мы будем перебирать от a1 до an и брать число в подмножество, если оно уменьшает количество различных простых делителей. Таким образом, выбранное подмножество содержит не более 7 элементов, так как максимальное количество простых делителей у числа, меньшего либо равного 106, равно 7. Для чисел из подмножества мы запускаем описанный алгоритм, а для чисел, не вошедших в множество, просто выставляем xi = 0.



Теперь алгоритм работает за O(n), но не выполняются условия, что xi по модулю не превышают 106 и среди них не должно быть нулей. Для этого опишем процедуру нормализации. Сначала выполним для xi первое условие. В первую очередь сделаем все xi для i > 1 неотрицательными и меньшими a1. Это делается простой операцией эквивалентного изменения: мы вычитаем из x1 kai и прибавляем к xi ka1, где k = (xi mod a1xi)/a1. Отметим, что результаты выполнения операции влезают в 64-битный тип данных, так как x1 по модулю не может превзойти |d – a2 x2 – ... – an xn| < 106 • 106 • 105. Теперь пройдёмся по всем xi, начиная со второго, до последнего и с каждым будем последовательно производить операцию: вычесть a1 из xi и прибавить ai к x1. Заметим, что существует i такое, что после применения операции к x1, ..., xi получилось 0 >= x1 >= –106. Пусть не существует такого i, тогда все xi стали отрицательными, чего случиться не могло, так как a1 x1 + … an xn даёт d, то есть положительное число. После того как мы научились получать |xi| <= 106, осталось сделать их ненулевыми. Разобьём все нули на пары, возможно кроме одного. В каждой паре i и j присвоим xi = aj и xj = –ai. У нас, возможно, остался единственный индекс p, для которого xp = 0. Мы рассмотрим несколько случаев:




  • Если существует xj, которое по модулю не равно ap, тогда мы применяем операцию: вычитаем sign(xj)ap из xj и прибавляем sign(xj)aj к xp.

  • Если же такого xj не существует, но ap <= 5 • 105, тогда к любому xj можно применить операцию: прибавить sign(xj)ap к xj и вычесть sign(xj)ap из xp.

  • В противном случае должно найтись aq, такое, что aq /= ap. Если такого не существует, то все ai = a1, а значит, из-за нормировки на наибольший общий делитель ai = a1 = 1 и должен был выполниться второй случай. Мы проводим операцию: вычтем sign(xj)ap из xq и прибавим sign(xj)aq к xp. После этого xq равно нулю. Но теперь, если n > 2, для q выполнится первый случай, а если n = 2, то для q выполнится второй случай, так как d = aq ap <= 106.



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



***

Чемпионат Russian Code Cup входит в число инициатив Mail.Ru Group, направленных на развитие российской IT-отрасли и объединённых ресурсом IT.Mail.Ru. Платформа IT.Mail.Ru создана для тех, кто увлекается IT и стремится профессионально развиваться в этой сфере. Проект объединяет чемпионаты Russian AI Cup, Russian Code Cup и Russian Developers Cup, образовательные проекты Технопарк в МГТУ им. Баумана, Техносфера в МГУ им. М. В. Ломоносова и Технотрек в МФТИ. Кроме того, на IT.Mail.Ru можно с помощью тестов проверить свои знания по популярным языкам программирования, узнать важные новости из мира IT, посетить или посмотреть трансляции с профильных мероприятий и лекции на IT-тематику.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/303216/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Из песочницы] SObjectizer: что это, для чего это и почему это выглядит именно так?

Среда, 29 Июня 2016 г. 15:22 (ссылка)

Разработка многопоточных программ на C++ — это не просто. Разработка больших многопоточных программ на C++ — это очень не просто. Но, как это обычно бывает в C++, жизнь сильно упрощается, если удается подобрать или сделать «заточенный» под конкретную задачу инструмент. Четырнадцать лет назад выбирать было особенно не из чего, поэтому мы сами для себя сделали такой «заточенный» инструмент и назвали его SObjectizer. Опыт повседневного использования SObjectizer-а в коммерческом софтостроении пока не позволяет жалеть о содеянном. А раз так, то почему бы не попробовать рассказать о том, что это, для чего это и почему у нас получилось именно так, а не иначе…



Что это?



SObjectizer — это небольшой OpenSource инструмент, свободно распространяющийся под 3-пунктной BSD-лицензией. Основная идея, лежащая в основе SObjectizer, — это построение приложения из мелких сущностей-агентов, которые взаимодействуют между собой через обмен сообщениями. SObjectizer при этом берет на себя ответственность за:


  • доставку сообщений агентам-получателям внутри одного процесса;

  • управление рабочими нитями, на которых агенты обрабатывают адресованные им сообщения;

  • механизм таймеров (в виде отложенных и периодических сообщений);

  • возможности настройки параметров работы перечисленных выше механизмов.



Можно сказать, что SObjectizer является одной из реализаций Actor Model. Однако, главное отличие SObjectizer от других подобных разработок, — это сочетание элементов из Actor Model с элементами из других моделей, в частности, Publish-Subscribe и CSP.



В «классической» Actor Model каждый актор и есть непосредственный адресат в операции send. Т.е. если мы хотим отослать сообщение какому-то актору, мы должны иметь ссылку на актора-получателя или идентификатор этого актора. Операция send просто добавляет сообщение в очередь сообщений актора-получателя.



В SObjectizer операция send получает ссылку не на актора, а на такую штуку, как mbox (message box, почтовый ящик). Mbox можно рассматривать как некий прокси, скрывающий реализацию процедуры доставки сообщения до получателей. Таких реализаций может быть несколько, и они зависят от типа mbox-а. Если это multi-producer/single-consumer mbox, то, как и в «классической» Actor Model, сообщение будет доставлено единственному получателю, владельцу mbox-а. А вот если это multi-producer/multi-consumer mbox, то сообщение будет доставлено всем получателям, которые подписались на данный mbox.



Т.е. операция send в SObjectizer больше похожа на операцию publish из модели Publish-Subscribe, нежели на send из Actor Model. Следствием чего является наличие такой полезной на практике возможности, как широковещательная рассылка сообщений.



Механизм доставки сообщений в SObjectizer похож на модель Publish-Subscribe еще и процедурой подписки. Если агент хочет получать сообщения типа A, то он должен подписаться на сообщения типа A из соответствующего mbox-а. Если хочет получать сообщения типа B — должен подписаться на сообщения типа B. И т.д. При этом тип сообщения играет ту же самую роль, как и название топика в модели Publish-Subscribe. Ну и как в модели Publish-Subscribe, где получатель может подписаться на любое количество топиков, агент в SObjectizer может быть подписан на любое количество типов сообщений из разных mbox-ов:



class example : public so_5::agent_t {
...
public :
virtual void so_define_agent() override {
// Подписка разных обработчиков событий на разные сообщения
// из одного mbox-а.
// Тип сообщения автоматически выводится из типа аргумента
// обработчика события.
so_subscribe(some_mbox)
.event( &example::first_handler )
.event( &example::second_handler )
// Обработчик может быть задан и в виде лямбда-функции.
.event( []( const third_message & msg ){...} );

// Подписка одного и того же события на сообщения
// из разных mbox-ов.
so_subscribe(another_mbox).event( &example::first_handler );
so_subscribe(yet_another_mbox).event( &example::first_handler );
...
}
...
private :
void first_handler( const first_message & msg ) {...}
void second_handler( const second_message & msg ) {...}
};


Следующим важным отличием SObjectizer от других реализаций «классической» Actor Model является то, что в SObjectizer у агента нет своей очереди сообщений. Очередь сообщений в SObjectizer принадлежит рабочему контексту, на котором обслуживается агент. А рабочий контекст определяется диспетчером, к которому привязан агент.



Диспетчер — это одно из краеугольных понятий в SObjectizer. Диспетчеры определяют где и когда агенты будут обрабатывать свои сообщения.



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



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



Следующей отличительной чертой SObjectizer является наличие такого понятия, как "кооперация агентов". Кооперация — это группа агентов, которая совместно выполняет какую-то прикладную задачу. И эти агенты должны начинать и завершать свою работу единовременно. Т.е. если какой-то агент не может стартовать, то не стартуют и все остальные агенты кооперации. Если какой-то агент не может продолжать работу, то не могут продолжать свою работу и все остальные агенты кооперации.



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


  • кто стартует первым и в каком порядке он создает остальных;

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

  • что делать, если при старте очередного агента возникает какая-то проблема.



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



// Создаем и регистрируем кооперацию из 3-х агентов:
// - первый обслуживает TCP-подключение к AMQP-брокеру и реализует AMQP-протокол;
// - второй выполняет взаимодействие с СУБД.
// - третий выполняет обработку прикладных сообщений от брокера
// (используя при этом функциональность первых двух агентов);
so_5::environment_t & env = ...;
// Сам экземпляр кооперации, в котором будут храниться агенты.
// У каждой кооперации должно быть уникальное имя, которое, как в данном
// случае, может быть сгенерировано самим SObjectizer-ом.
auto coop = env.create_coop( so_5::autoname );
// Наполняем кооперацию...
coop.make_agent< amqp_client >(...);
coop.make_agent< db_worker >(...);
coop.make_agent< message_processor >(...);
// Регистрируем кооперацию.
// Дальнейшей ее судьбой будет заниматься сам SObjectizer.
env.register_coop( std::move(coop) );


Отчасти кооперации решают ту же проблему, что и система супервизоров в Erlang: входящие в кооперацию агенты как бы находятся под контролем супервизора all-for-one. Т.е. сбой одного из агентов приводит к дерегистрации всех остальных агентов кооперации.



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



Агенты в SObjectizer могут представлять из себя довольно сложные конечные автоматы: поддерживается вложенность состояний, временные ограничения на пребывания агента в состоянии, состояния с deep- и shallow-history, а также обработчики входа и выхода в состояние.



class device_handler : public so_5::agent_t {
// Перечень состояний, в которых может находиться агент.
// Есть два состояния верхнего уровня...
state_t st_idle{ this }, st_activated{ this },
// ... и три состояния, которые являются подсостояниями
// состояния st_activated.
st_cmd_sent{ initial_substate_of{ st_activated } },
st_cmd_accepted{ substate_of{ st_activated } },
st_failure{ substate_of{ st_activated } };
...
public :
virtual void so_define_agent() override {
// Начинаем работать в состоянии st_idle.
st_idle.activate();

// При получении команды в состоянии st_idle просто меняем
// состояние и делегируем обработку команды состоянию st_activated.
st_idle.transfer_to_state< command >( st_activated );

// В состоянии st_activated реагируем всего на одно сообщение:
// по приходу turn_off возвращаемся в состояние st_idle.
// При этом реакция на сообщение turn_off наследуется
// всеми дочерними состояниями.
st_activated
.event( [this](const turn_off & msg) {
turn_device_off();
st_idle.activate();
} );
// В состояние st_cmd_sent "проваливаемся" сразу после входа
// в состояние st_activated, т.к. st_cmd_sent является начальным
// подсостоянием.
st_cmd_sent
.event( [this](const command & msg) {
send_command_to_device(msg);
// Проверим, что произошло с устройством через 150ms.
send_delated(*this, 150ms);
} )
.event( [this](const check_status &) {
if(command_accepted())
st_cmd_accepted.activate();
else
st_failure.activate();
} );
...
// В состоянии st_failure находимся не более 50ms,
// после чего возвращаемся в st_idle.
// При входе в это состояние принудительно сбрасываем
// настройки устройства.
st_failure
.on_enter( [this]{ reset_device(); } )
.time_limit( 50ms, st_idle );
}
...
};


Из CSP-модели SObjectizer позаимствовал такую штуку, как каналы, которые в SObjectizer называются message chains. CSP-ные каналы были добавлены в SObjectizer как инструмент для решения одной специфической проблемы: взаимодействие между агентами строится через обмен сообщениями, поэтому очень просто дать какую-то команду агенту или передать какую-то информацию агенту из любой части приложения — достаточно отсылать сообщение посредством send. Однако, как агенты могут воздействовать на не-SObjectizer частью приложения?



Эту проблему решают message chains (mchains). Message chain может выглядеть совсем как mbox: отсылать сообщения в mchain нужно посредством все того же send-а. А вот извлекаются сообщения из mchain функциями receive и select, для работы с которыми не требуется создавать SObjectizer-овских агентов.



Работа с message chain в SObjectizer похожа на работу с каналами в языке Go. Хотя есть и серьезные различия:


  • mchains в SObjectizer могут одновременно хранить сообщения любых типов, тогда как Go-шные каналы типизированы;

  • в Go-шной конструкции select можно использовать как send-, так и receive-операции. Тогда как в SObjectizer-овском select-е допускаются только receive-операции (по крайней мере в версиях до 5.5.17 включительно);

  • mchains в SObjectizer могут иметь, а могут и не иметь ограничений на размер очереди сообщений. Тогда как в Go размер канала ограничен всегда. Для mchain-а с ограниченным размером SObjectizer заставляет выбрать подходящее поведение для попытки поместить новое сообщение в полный mchain (например, подождать какое-то время и выбросить самое старое сообщение из mchain или ничего не ждать и сразу породить исключение).



Message chains — это относительно недавнее добавление в SObjectizer. Однако, штука оказалась весьма полезной и довольно неожиданным следствием ее добавления стало то, что некоторые многопоточные приложения на SObjectizer стало возможно разрабатывать даже без применения агентов:



void parallel_sum_demo()
{
using namespace std;
using namespace so_5;

// Тип сообщения, которое отошлет каждая рабочая нить в конце своей работы.
struct consumer_result
{
thread::id m_id;
size_t m_values_received;
uint64_t m_sum;
};

wrapped_env_t sobj;

// Канал для отсылки сообщений рабочим нитям.
auto values_ch = create_mchain( sobj,
// Канал имеет ограничение на размер, поэтому назначаем
// паузу в 5м на попытку добавить сообщение в полный канал.
chrono::minutes{5},
// Не более 300 сообщений в канале.
300u,
// Память под внутреннюю очередь канала выделяется заранее.
mchain_props::memory_usage_t::preallocated,
// Если место в канале не появилось даже после 5м ожидания,
// то просто прерываем все приложение.
mchain_props::overflow_reaction_t::abort_app );

// Простой канал для ответных сообщений от рабочих потоков.
auto results_ch = create_mchain( sobj );

// Рабочие потоки.
vector< thread > workers;
for( size_t i = 0; i != thread::hardware_concurrency(); ++i )
workers.emplace_back( thread{ [&values_ch, &results_ch] {
// Последовательно читаем значения из входного
// канала, подсчитываем количество значений и
// их сумму.
size_t received = 0u;
uint64_t sum = 0u;
receive( from( values_ch ), [&sum, &received]( unsigned int v ) {
++received;
sum += v;
} );

// Отсылаем результирующие значения назад.
send< consumer_result >( results_ch,
this_thread::get_id(), received, sum );
} } );

// Отсылаем несколько значений во входной канал. Эти значения будут
// распределятся между рабочими потоками, висящими на чтении данных
// из входного канала.
for( unsigned int i = 0; i != 10000; ++i )
send< unsigned int >( values_ch, i );

// Закрываем входной канал и даем возможность дочитать его содержимое
// до самого конца.
close_retain_content( values_ch );

// Получаем результирующие значения от всех рабочих нитей.
receive(
// Мы точно знаем, сколько значений должно быть прочитано.
from( results_ch ).handle_n( workers.size() ),
[]( const consumer_result & r ) {
cout << "Thread: " << r.m_id
<< ", values: " << r.m_values_received
<< ", sum: " << r.m_sum
<< endl;
} );

for_each( begin(workers), end(workers), []( thread & t ) { t.join(); } );
}




Зачем это?



Наверняка у читателей, которые никогда раньше не использовали Actor Model и Publish-Subscribe, уже возник вопрос: «И что, все вышеперечисленное действительно упрощает разработку многопоточных приложений на C++?»



Да. Упрощает. Проверенно на людях. Многократно.



Понятное дело, упрощает не для всех приложений. Ведь многопоточность — это инструмент, который используется в двух очень разных направлениях. Первое направление, называемое parallel computing, использует потоки для загрузки всех имеющихся вычислительных ресурсов и сокращения общего времени расчета вычислительных задач. Например, ускорение перекодирования видео за счет загрузки всех вычислительных ядер, при этом каждое ядро выполняет одну и ту же задачу, но на своем наборе данных. Это не то направление, для которого создавался SObjectizer. Для упрощения решения такого класса задач предназначены другие инструменты: OpenMP, Intel Threading Building Blocks, HPX и т.д.



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



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



Прежде всего за счет выстраивания взаимодействия между агентами через асинхронный обмен сообщениями.



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



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



Так что главное, что дает разработчику SObjectizer (как и любая другая реализация Actor Model) — это возможность представления независимых активностей внутри приложения в виде агентов, общающихся с окружающим миром только через сообщения.



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



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



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



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



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


  • один диспетчер типа one_thread, на котором некий агент работает AMQP-клиентом;

  • один диспетчер типа thread_pool, на котором работают агенты, отвечающие за обработку сообщений из AMQP-шных топиков;

  • один диспетчер типа active_obj, к которому привязываются агенты для взаимодействия с СУБД;

  • еще один диспетчер типа active_obj, на котором будут работать агенты, общающиеся с подключенными к компьютеру HSM-ами;

  • и еще один thread_pool-диспетчер для агентов, которые следят и управляют всей описанной выше кухней.



Еще одной штукой, которую сами пользователи SObjectizer отмечают как одну из важнейших, является поддержка отложенных и периодических сообщений. Т.е. работа с таймерами.



Очень часто бывает нужно выполнить какое-то действие через N миллисекунд. А затем через M миллисекунд проверить наличие результата. И, если результата нет, выждать K миллисекунд и повторить все заново. Ничего сложного: есть send_delayed, которая делает отложенную на указанное время отсылку сообщения.



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



Почему SObjectizer именно такой?



SObjectizer никогда не был экспериментальным проектом, он всегда применялся для упрощения повседневной работы с C++. Каждая новая версия SObjectizer сразу шла в работу, SObjectizer постоянно использовался в разработке коммерческих проектов (в частности, в нескольких business-critical проектах компании Интервэйл, но не только). Это накладывало свой отпечаток на его развитие.



Работы над последним вариантом SObjectizer (мы его называем SObjectizer-5), начались в 2010-ом, когда стандарт C++11 еще не был принят, какие-то вещи C++11 кое-где уже поддерживались, а каких-то пришлось ждать более пяти лет.



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



При этом нам требовалось еще и заботиться о совместимости: когда SObjectizer лежит в основе business-critical приложений, нельзя просто выбросить какой-то кусок из SObjectizer или каким-то кардинальным образом поменять часть его API. Поэтому даже если время показывало, что где-то мы ошиблись и что-то можно делать проще и удобнее, то возможности «взять и переписать» не было. Мы двигались и двигаемся эволюционным путем, постепенно добавляя новые возможности, но не выбрасывая в одночасье старые куски. В качестве небольшой иллюстрации: какого-либо серьезного нарушения обратной совместимости не было с момента выхода версии 5.5.0 осенью 2014-го года, хотя с тех пор состоялось уже около 20 релизов в рамках развития версии 5.5.



SObjectizer приобрел свои уникальные черты в результате многолетнего использования SObjectizer в реальных проектах. К сожалению, эта уникальность «вылазит боком» при попытках рассказать о SObjectizer широкой публике. Слишком уж SObjectizer не похож на Erlang и другие проекты, созданные по образу и подобию Erlang-а (например, C++ Actor Framework или Akka).



Вот, скажем, есть у нас возможность запустить несколько независимых экземпляров SObjectizer-а в одном приложении. Возможность весьма экзотичная. Но добавлена она была потому, что на практике иногда такое бывает необходимо. Для поддержки этой возможности в SObjectizer появилось такое понятие, как SObjectizer Environment. И этот SObjectizer Environment потребовалось «протягивать» через изрядную часть API SObjectizer-а, что не могло не сказаться на лаконичности кода.



А вот в C++ Actor Framework такой возможности изначально не было. API акторов в CAF выглядел гораздо проще, а примеры кода — короче. Из-за чего мы часто встречаемся с утверждениями, что CAF воспринимается проще и понятнее, чем SObjectizer.



Ирония, однако, в том, что со временем разработчики CAF-а так же пришли к выводу, что им нужно иметь нечто вроде SObjectizer Environment (они это называют actor_system). И в следующей версии CAF ожидается добавление этой штуки. С очередной поломкой совместимости между версиями CAF-а. В этих поломках, кстати говоря, CAF так же сильно опережает SObjectizer.



Еще одна вещь, которая проистекает из опыта использования SObjectizer, которая нам кажется правильной и естественной, но которая вызывает нездоровую реакцию публики: отсутствие поддержки в SObjectizer-5 встроенных средств для распределенности. Мы часто слышим что-то вроде «Ну как же так? Вот в Erlang-е есть, в Akka — есть, в CAF — есть, а у вас нет?!!»



Нет. По очень простой причине: в SObjectizer-4 такая поддержка была, но со временем выяснилось, что не бывает транспорта, который бы идеально подходил под разные условия. Если узлы распределенного приложения гоняют друг-другу большие куски видеофайлов — это одно. Если обмениваются сотнями тысяч мелких пакетов — совсем другое. Если C++ приложение должно общаться с Java приложениями — это третье. И т.д.



Поэтому мы решили не добавлять в SObjectizer-5 универсальный транспорт, который мог бы оказаться весьма посредственным в каждом из реальных сценариев использования, а задействовать те коммуникационные возможности, которые нужны под задачу. Где-то это AMQP, где-то MQTT, где-то REST. Просто все это реализуется сторонними инструментами. Что в итоге обходится проще, дешевле и эффективнее.



Коль скоро речь зашла о других подобных разработках, то можно сказать, что живых и развивающихся проектов подобного рода для C++ буквально раз-два и обчелся:


  • прежде всего относительно молодой и еще не стабилизировавшийся CAF, который является настолько близкой реализацией Erlang-а средствами C++, насколько это возможно;

  • старый, промышленного качества проект QP, сильно заточенный под разработку встраиваемых систем;

  • минималистичная и тривиальная реализация Actor Model в коммерческой библиотеке Just::Thread Pro Actor Edition.



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



Послесловие



В этой небольшой статье мы попытались дать краткие ответы на самые распространенные вопросы, с которыми мы сталкиваемся, пытаясь рассказывать о SObjectizer-е широкой публике: «Что это такое?», «Зачем это?» и «Почему оно именно такое?» Для сохранения приемлемого объема статьи мы контурно обозначили лишь самые значимые моменты и яркие черты SObjectizer-а. Если же углубляться в детали, то написать можно еще с десяток-другой статьей подобного объема. И если читателям будет интересно, то мы с удовольствием расскажем еще больше о SObjectizer-е, о принципах его работы, об опыте разработки софта с его помощью, о перспективах дальнейшего развития и т.д, и т.п.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/304386/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
SoftLabirint

JavaScript с самого начала (2016) Видеокурс » SoftLabirint.Ru: Скачать бесплатно и без регистрации - Самые Популярные Новости Интернета

Среда, 29 Июня 2016 г. 23:06 (ссылка)
softlabirint.ru/video/video...okurs.html


JavaScript с самого начала (2016) Видеокурс

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



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



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



Содержание:

1. Основы. Переменные и выражения.

2. Типы данных и функции

3. Синтаксис основных операторов

4. DOM

5. События и их слушатели

6. Строки, массивы и объекты

7. Обзор библиотеки jQuery

8. Асинхронное программирование

 



JavaScript с самого начала (2016) Видеокурс



JavaScript с самого начала (2016) Видеокурс



JavaScript с самого начала (2016) Видеокурс






Информация о видеокурсе

Название: javascript с самого начала

Автор: Государев Илья

Год выхода: 2016

Жанр: Видеокурс

Язык: Русский

Выпущено: Россия

Продолжительность: ~19 часов



Файл

Формат: MKV, PDF

Видео: AVC, 1366x768/1280x672, ~203 Kbps

Аудио: AAC, 131 Kbps, 48.0 KHz

Размер: 3.62 Gb



Скачать: javascript с самого начала (2016) Видеокурс >>>



 



Подписка на новости сайта…

http://feeds.feedburner.com/Soft-Labirint

http://feeds.feedburner.com/Soft-Labirint?format=xml

https://feedburner.google.com/fb/a/mailverify?uri=Soft-Labirint

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Перевод] Пропорции в искусстве. Есть ли что-то лучше золотого сечения? Исследование более 1 000 000 старых и современных картин

Вторник, 28 Июня 2016 г. 17:07 (ссылка)



Перевод поста Майкла Тротта (Michael Trott) "Aspect Ratios in Art: What Is Better Than Being Golden? Being Plastic, Rooted, or Just Rational? Investigating Aspect Ratios of Old vs. Modern Paintings".

Код, приведенный в статье, можно скачать здесь.

Выражаю огромную благодарность Кириллу Гузенко KirillGuzenko за помощь в переводе и подготовке публикации

Содержание



Предисловие: золотое сечение — красивая математическая концепция

Работа Фехнера 1876 года об эстетичности прямоугольников и соотношениях сторон в картинах

Легкий старт: анализ «Artwork» — области базы знаний Wolfram Knowledgebase

Первая часть: особенности вероятностного распределения соотношений сторон

Соотношения сторон для разных веков, жанров и художников

Анализируя пять старых немецких музейных каталогов

Коллекция Кресса: четыре больших PDF файла

У нас представлены коллекции следующих галерей: Метрополитен (Metropolitan), институт искусств Чикаго, Эрмитаж, Национальная Галерея (National Gallery), Рейксмюзеум (Rijks) и Тейт Британия

Исключение в соотношениях сторон: Национальная портретная галерея

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

Примечание II: важность точности в измерениях

WikiArt: еще один крупный веб-ресурс

Коллекция Французского государственного музея

Картины в итальянских церквях: высота есть всё

Смитсоновская коллекция

Большая коллекция картин в Великобритании

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

Проданные картины: большинство написаны недавно, а у распределения длинный хвост

Восток: все показатели отличаются

Пропорции пакетов, автомобилей, этикеток, логотипов, эмблем, бумаги, банкнот, почтовых марок и фильмов

Продукты из супермаркета

Винные этикетки

Этикетки немецких сортов пива

Логотипы продуктов питания

Банкноты

Размеры автомобилей

Бумажные листы

Марки

Эмблемы команд NCAA (Национальной ассоциации студенческого спорта)

Эмблемы немецких футбольных клубов

Форматы фильмов

Заключение: так какое соотношение самое «лучшее»?
Картины великих мастеров — едва ли не самое прекрасное из человеческого наследия. Ими дорожили и восхищались, бережно хранили и продавали за сотни миллионов долларов, и, возможно, не по случайности они являются главной целью похитителей предметов искусства. Их композиции, цвета, детали, темы могут держать нас в восхищении и внимании часами. Но что можно сказать об отношении их внешних размеров — высоты к ширине?



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



Немного больше узнать о Фехнере нам поможет следующий код:







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



После обзора золотой пропорции и выводов Фехнера, мы рассмотрим соотношения сторон в различных группах картин и итоговое распределение, а также наиболее популярные соотношения. Мы узнаем о тенденциях последнего столетия в области соотношений сторон и рассмотрим то, как оно стало более рационалистическим.



Предисловие: золотое сечение — красивая математическая концепция



Золотое сечение

https://habrahabr.ru/post/304326/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Комментарии (0)КомментироватьВ цитатник или сообщество

Следующие 30  »

<программирование - Самое интересное в блогах

Страницы: [1] 2 3 ..
.. 10

LiveInternet.Ru Ссылки: на главную|почта|знакомства|одноклассники|фото|открытки|тесты|чат
О проекте: помощь|контакты|разместить рекламу|версия для pda