Красноречивые коллекции: каждый против foreach

Акция

ВСЕМ, КТО ОСТАВИЛ ЗАЯВКУ ДО 23 декабря

(ДА-ДА, ИМЕННО 2 ПОЛНОЦЕННЫХ 45 МИНУТНЫХ УРОКА, А НЕ ПРОБНЫХ, КАК У ДРУГИХ!)

1. В акции принимают участие все заполнившие заявку
2. Первый пробный урок (30 минут) проводится бесплатно.
3. При первой оплате 5 занятий студент получает 2 урока по 45 минут с выбранным преподавателем и по выбранному курсу бесплатно (1 урок с преподавателем — носителем языка).**
4. Акция не суммируется с другими действующими предложениями и скидками.*
* Например, студент оплативший первые три занятия по льготной цене, не принимает участия в данной акции.
** При оплате 30 минутных уроков студенту предоставляется 2 урока по 30 минут (1 урок 30 минут с носителем языка).

Приложение III: Заключительные мысли и измерительные тесты

Самой проблемной вещью, конечно же, была реализация LINQ, которая была ужасно медленной. Оказывается, все это связано с оптимизацией компилятора делегата. LukeH предоставил лучшее и наиболее удобное решение , которое фактически заставило меня попробовать разные подходы к этому. Я пробовал различные подходы в методе (или , как он называется в Gist):

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

  2. путем определения локальной переменной-предиката:

  3. локальный построитель предикатов:

  4. локальный построитель предикатов и локальная переменная предиката:

  5. построитель предикатов уровня класса (статический или экземплярный):

  6. предикат, определенный извне и предоставленный как параметр метода

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

    1. предикат предоставляется непосредственно при вызове метода в цикл:

    2. построитель предикатов, определенный вне :

Результаты — что работает лучше всего

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

  1. все остальные заняли где-то между 4200 мс — 4360 мс и поэтому считаются непригодными для использования.

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

range() и enumerate()

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

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

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

  • ;
  • ;
  • .

Здесь — это первый элемент последовательности (включительно), — последний (не включительно), а — разность между следующим и предыдущим членами последовательности.

Подробнее о функции range тут:

Функция range в Python

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

Советы для изучения фразовых глаголов

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

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

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

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

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

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

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

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

Добавление/удаление элементов

Мы уже знаем методы, которые добавляют и удаляют элементы из начала или конца:

  • – добавляет элементы в конец,
  • – извлекает элемент из конца,
  • – извлекает элемент из начала,
  • – добавляет элементы в начало.

Есть и другие.

Как удалить элемент из массива?

Так как массивы – это объекты, то можно попробовать :

Вроде бы, элемент и был удалён, но при проверке оказывается, что массив всё ещё имеет 3 элемента .

Это нормально, потому что всё, что делает – это удаляет значение с данным ключом . Это нормально для объектов, но для массивов мы обычно хотим, чтобы оставшиеся элементы сдвинулись и заняли освободившееся место. Мы ждём, что массив станет короче.

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

Метод arr.splice(str) – это универсальный «швейцарский нож» для работы с массивами. Умеет всё: добавлять, удалять и заменять элементы.

Его синтаксис:

Он начинает с позиции , удаляет элементов и вставляет на их место. Возвращает массив из удалённых элементов.

Этот метод проще всего понять, рассмотрев примеры.

Начнём с удаления:

Легко, правда? Начиная с позиции , он убрал элемент.

В следующем примере мы удалим 3 элемента и заменим их двумя другими.

Здесь видно, что возвращает массив из удалённых элементов:

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

Отрицательные индексы разрешены

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

Метод arr.slice намного проще, чем похожий на него .

Его синтаксис:

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

Это похоже на строковый метод , но вместо подстрок возвращает подмассивы.

Например:

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

Метод arr.concat создаёт новый массив, в который копирует данные из других массивов и дополнительные значения.

Его синтаксис:

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

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

Если аргумент – массив, то все его элементы копируются. Иначе скопируется сам аргумент.

Например:

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

…Но если объект имеет специальное свойство , то он обрабатывается как массив: вместо него добавляются его числовые свойства.

Для корректной обработки в объекте должны быть числовые свойства и :

Какое время измеряется?

Я делаю следующие шаги:

  1. Сгенерируйте диапазоны (как показано ниже в результатах)
  2. Создайте экземпляры объектов для IterationLookup, LinqLookup (и мои оптимизированные класс диапазона дат BitCountLookup, который здесь не обсуждается)
  3. Запустить таймер и выполнить 1 миллион поисков в случайные дни в пределах максимального диапазона дат (как видно из результатов) с использованием ранее созданного класса IterationLookup.
  4. Запустить таймер и выполнить 1 миллион поисков в случайные дни в пределах максимального диапазона дат (как видно из результатов) с помощью ранее созданного класса LinqLookup.
  5. Запустить таймер и выполнить 1 миллион поисков (6 раз) с использованием ручных циклов foreach + break и вызовов Linq.

Как видите, создание экземпляра объекта не измеряется .

Итак … какой из них я должен использовать?

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

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

LINQ против традиционного способа

Что касается LINQ, вы можете изучить функциональное программирование (FP) — не C # FP материал, но реальный =) FP язык, такой как Haskell. Функциональные языки имеют особый способ выражения и представления кода. В некоторых ситуациях он превосходит нефункциональные парадигмы.

Известно, что FP намного лучше, когда дело доходит до манипулирования списками ( list как универсальный термин, не связанный с __). Учитывая этот факт, возможность выражать код C # более функциональным способом, когда дело доходит до списков, является довольно хорошей вещью.

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

for vs. foreach

There is a common confusion that those two constructs are very similar and that both are interchangeable like this:

and:

The fact that both keywords start by the same three letters doesn’t mean that semantically, they are similar. This confusion is extremely error-prone, especially for beginners. Iterating through a collection and doing something with the elements is done with ; doesn’t have to and shouldn’t be used for this purpose, unless you really know what you’re doing.

Let’s see what’s wrong with it with an example. At the end, you’ll find the full code of a demo application used to gather the results.

In the example, we are loading some data from the database, more precisely the cities from Adventure Works, ordered by name, before encountering «Boston». The following SQL query is used:

The data is loaded by method which returns an . Here is what looks like:

Let’s rewrite it with a , assuming that both are interchangeable:

Both return the same cities, but there is a huge difference.

  • When using , is called one time and yields 47 items.
  • When using , is called 94 times and yields 28153 items overall.

What happened?

is lazy. It means that it will do the work only at the moment when the result is needed. Lazy evaluation is a very useful concept, but has some caveats, including the fact that it’s easy to miss the moment(s) where the result will be needed, especially in the cases where the result is used multiple times.

In a case of a , the result is requested only once. In a case of a as implemented in the incorrectly written code above, the result is requested 94 times, i.e. 47 × 2:

  • Every time is called (47 times),

  • Every time is called (47 times).

Querying a database 94 times instead of one is terrible, but not the worse thing which may happen. Imagine, for example, what would happen if the query would be preceded by a query which also inserts a row in the table. Right, we would have which will call the database 2,147,483,647 times, unless it hopefully crashes before.

Of course, my code is biased. I deliberately used the laziness of and wrote it in a way to repeatedly call . One can note that a beginner will never do that, because:

  • The doesn’t have the property , but only the method . Calling a method is scary, and one can expect its result to not be cached, and not suitable in a block.

  • The indexing is unavailable for and it’s not obvious to find the LINQ extension method.

Probably most beginners would just convert the result of to something they are familiar with, like a .

Still, this code is very different from the alternative. Again, it gives the same results, and this time the method is called only once, but yields 575 items, while with , it yielded only 47 items.

The difference comes from the fact that causes all data to be loaded from the database. While requested only the cities before «Boston», the new requires all cities to be retrieved and stored in memory. With 575 short strings, it probably doesn’t make much difference, but what if we were retrieving only few rows from a table containing billions of records?

Effective or efficient?

Слово Значение Пример
effective эффективный, действенный This medicine is really effective. – Это лекарство действительно эффективно.
действующий, имеющий силу (= effectual) The law will be effective from December. – Закон вступит в силу в декабре.
фактический The country was under effective control of another country. – Страна находилась под фактическим контролем другой страны.
efficient эффективный, целесообразный, результативный (обычно о человеке или какой-то машине/оборудовании) Fluorescent lamps are efficient for saving electric energy. – Флуоресцентные лампы эффективны для экономии электроэнергии.
умелый, квалифицированный You should take training courses from time to time if you want to be efficient in your job. – Следует время от времени проходить курсы повышения квалификации, если хочешь быть компетентным в своей работе.

Фразовые глаголы английского языка

У английских фразовых глаголов древняя история. Они использовались еще в древнеанглийском языке, но тогда их значения были напрямую связаны со значениями предлогов и наречий. Например, значение любого фразового глагола с предлогом out выводилось из значения самого глагола и смысла «движение наружу», например, go out – выходить.

Постепенно фразовые глаголы видоизменялись и приобретали новые смыслы. В 1066 году Англию завоевали нормандцы, и английский язык стал развиваться одновременно в двух направлениях. Высшие классы обогатили английский многочисленными французскими, латинскими и греческими заимствованиями, в том числе глаголами, а низшие сословия использовали наследие древнего английского языка и активно пользовались фразовыми глаголами. Так появились пары синонимов: put off – postpone, give up – surrender, stand up for – defend.

В составе английских фразовых глаголов предлоги и наречия часто играют такую же роль, как и приставки в русских глаголах. Например, значение глагола «ходить» может меняться в зависимости от приставки: «выходить», «входить», «заходить», «уходить», «приходить». То же самое происходит с английским глаголом: walk – walk out, walk in, walk across.

Но английские предлоги и наречия в составе фразовых глаголов могут значительно повлиять на смысл, чего не бывает в русском языке. О значении многих фразовых глаголов невозможно догадаться, исходя из значений его составляющих: look after – ухаживать, give up – сдаться, hang up – положить трубку.

Большинство методов поддерживают «thisArg»

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

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

Вот полный синтаксис этих методов:

Значение параметра становится для .

Например, вот тут мы используем метод объекта как фильтр, и передаёт ему контекст:

Если бы мы в примере выше использовали просто , то вызов был бы в режиме отдельной функции, с . Это тут же привело бы к ошибке.

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

Best practice

Цикл по списку

Перебрать в цикле не составляет никакого труда, поскольку список — объект итерируемый:

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

Цикл по словарю

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

Цикл, в таком случае, будет выглядеть следующим образом:

Цикл по строке

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

Как сделать цикл for с шагом

Цикл с шагом создается при помощи уже известной нам функции , куда, в качестве третьего по счету аргумента, нужно передать размер шага:

Обратный цикл for

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

for в одну строку

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

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

Приведем пример, в котором продублируем каждый символ строки

Другой пример, но теперь уже с условием:

Поиск в массиве

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

Методы arr.indexOf, arr.lastIndexOf и arr.includes имеют одинаковый синтаксис и делают по сути то же самое, что и их строковые аналоги, но работают с элементами вместо символов:

  • ищет , начиная с индекса , и возвращает индекс, на котором был найден искомый элемент, в противном случае .
  • – то же самое, но ищет справа налево.
  • – ищет , начиная с индекса , и возвращает , если поиск успешен.

Например:

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

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

Кроме того, очень незначительным отличием является то, что он правильно обрабатывает в отличие от :

Представьте, что у нас есть массив объектов. Как нам найти объект с определённым условием?

Здесь пригодится метод arr.find.

Его синтаксис таков:

Функция вызывается по очереди для каждого элемента массива:

  • – очередной элемент.
  • – его индекс.
  • – сам массив.

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

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

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

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

Метод arr.findIndex – по сути, то же самое, но возвращает индекс, на котором был найден элемент, а не сам элемент, и , если ничего не найдено.

Метод ищет один (первый попавшийся) элемент, на котором функция-колбэк вернёт .

На тот случай, если найденных элементов может быть много, предусмотрен метод arr.filter(fn).

Синтаксис этого метода схож с , но возвращает массив из всех подходящих элементов:

Например:

for против foreach

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

__

а также:

__

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

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

В этом примере мы загружаем некоторые данные из базы данных, точнее, города из Adventure Works, упорядоченные по имени, прежде чем встретить «Бостон». Используется следующий SQL-запрос:

__

Данные загружаются методом , который возвращает __. Вот как выглядит :

__

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

__

Оба возвращают одни и те же города, но есть огромная разница.

  • При использовании , вызывается один раз и возвращает 47 элементов.
  • При использовании , вызывается 94 раза и дает в общей сложности 28153 элемента.

Что случилось?

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

В случае результат запрашивается только один раз. В случае как реализовано в неправильно написанном коде выше, результат запрашивается 94 раза , т.е. 47 × 2:

  • Каждый раз, когда вызывается (47 раз),

  • Каждый раз вызывается (47 раз).

Запрашивать базу данных 94 раза вместо одной ужасно, но не хуже, что может случиться. Представьте, например, что произойдет, если запросу предшествует запрос, который также вставляет строку в таблицу. Правильно, у нас будет , который будет вызывать базу данных 2 147 483 647 раз, если только она не завершится аварийно.

Конечно, мой код необъективен. Я сознательно использовал лень и ​​написал его таким образом, чтобы повторно вызывать . Можно заметить, что новичок никогда этого не сделает, потому что:

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

  • Индексирование недоступно для __, и нет ничего очевидного в том, чтобы найти метод расширения LINQ.

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

__

Тем не менее, этот код сильно отличается от альтернативы . Опять же, он дает те же результаты, и на этот раз метод вызывается только один раз, , но возвращает 575 элементов, в то время как с он дает только 47 предметов.

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

Async/Await и генераторы

Другой крайний случай с forEach() — это то, что он не совсем правильно работает с async/await или генераторами. Если ваш callback forEach() является синхронным, то это не имеет значения, но вы не сможете использовать await внутри callback forEach ():

async function run() {
  const arr = ;
  arr.forEach(el => {
    // SyntaxError
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log(el);
  });
}

Вы также не сможете использовать yield:

function* run() {
  const arr = ;
  arr.forEach(el => {
    // SyntaxError
    yield new Promise(resolve => setTimeout(resolve, 1000));
    console.log(el);
  });
}

Но приведенные выше примеры отлично работают с for/of:

async function asyncFn() {
  const arr = ;
  for (const el of arr) {
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log(el);
  }
}

function* generatorFn() {
  const arr = ;
  for (const el of arr) {
    yield new Promise(resolve => setTimeout(resolve, 1000));
    console.log(el);
  }
}

Даже если вы пометите свой callback forEach() как async, вам будет сложно заставить асинхронный метод forEach() работать последовательно. Например, приведенный ниже скрипт будет печатать 0-9 в обратном порядке.

async function print(n) {
  // Wait 1 second before printing 0, 0.9 seconds before printing 1, etc.
  await new Promise(resolve => setTimeout(() => resolve(), 1000 - n * 100));
  // Will usually print 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 but order is not strictly
  // guaranteed.
  console.log(n);
}

async function test() {
  .forEach(print);
}

test();

T

Вывод: если вы используете async/await или генераторы, помните, что forEach() является синтаксическим сахаром. Как сахар, его следует использовать экономно и не для всего.

Заключение

Как правило, for/of — это самый надежный способ перебора массива в JavaScript. Он более лаконичен, чем обычный цикл for, и не имеет такого количества граничных случаев, как for/in и forEach(). Основным недостатком for/of является то, что вам нужно проделать дополнительную работу для доступа к индексу массива (см. дополнение), и вы не можете строить цепочки кода, как вы можете это делать с помощью forEach(). Но если вы знаете все особенности forEach(), то во многих случаях его использование делает код более лаконичным.

Дополнение: Чтобы получить доступ к текущему индексу массива в цикле for/of, вы можете использовать функцию  .

for (const  of arr.entries()) {
  console.log(i, v); // Prints "0 a", "1 b", "2 c"
}

Оригинал: For vs forEach() vs for/in vs for/of in JavaScript

Spread the love

Рейтинг
( Пока оценок нет )
Понравилась статья? Поделиться с друзьями:
Все про сервера
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: