Стек

Стеки и потоки Python

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

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

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

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

Ответ заключается в том, что мы можем использовать LIFOqueue, и мы знаем, что LIFO означает Last In First Out. Он использует put() и gets() для добавления и удаления элемента стека.

Использовать

Обработка звонков на сайт

Обычно манипуляции со стеком вызовов, необходимые на месте вызова подпрограммы, минимальны (что хорошо, поскольку может быть много узлов вызова для каждой вызываемой подпрограммы). Значения фактических аргументов оцениваются на сайте вызова, поскольку они специфичны для конкретного вызова, и либо помещаются в стек, либо помещаются в регистры, как определено используемым соглашением о вызовах . Фактическая инструкция вызова, такая как «переход и ссылка», затем обычно выполняется для передачи управления коду целевой подпрограммы.

Обработка записи подпрограммы

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

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

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

Язык программирования Forth допускает явную намотку стека вызовов (называемого там «стеком возврата»).

Обработка возврата

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

Размотка

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

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

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

Примеры

В качестве примера рассмотрим код для рекурсивного поиска файлов, расположенный на MSDN:

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

Пример второго подхода, взятый из вопроса «Почему происходит переполнение стека?» с сайта под названием Stack Overflow (сайт является сборником вопросов и ответов на любые программистские темы, а не только по переполнению стека, как может показаться):

Как видно, в функции main выделяется память в стеке под массивы типов int и float по миллиону элементов каждый, что в сумме дает чуть менее 8 мегабайт. Если учесть, что по умолчанию Visual C++ резервирует под стек лишь 1 мегабайт, то ответ становится очевидным.

А вот пример, взятый из GitHub-репозитория проекта Flash-плеера Lightspark:

Можно надеятся, что h.getLength()-7 не будет слишком большим числом, чтобы на следующей строчке не произошло переполнения. Но стоит ли сэкономленное на выделении памяти время «потенциального» вылета программы?

Дополнение от переводчика

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

Эта куча (heap)

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

Здесь я приведу лишь простое объяснение структуры данных куча.

Куча — это такой вид дерева, у которого есть одно важное свойство: если узел A — это родитель узла B, то ключ узла A больше ключа узла B (или равен ему). Лучше всего это можно увидеть на картинке

Лучше всего это можно увидеть на картинке.

Реализации кучи как структуры данных в JavaScript совсем не так просто как стека и очереди. Более подробно это рассмотрено например в этой статье.

Spread the love

8
Поделились

Последствия ошибки

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

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

Чтобы быть совсем точным, следует отметить, что подобное описание событий верно лишь для компиляторов, компилирующих в «родной» (native) код. В управляемых языках у виртуальной машины есть свой стек для управляемых программ, за состоянием которого гораздо проще следить, и можно даже позволить себе при возникновении переполнения передать программе исключение. В языках Си и Си++ на подобную «роскошь» рассчитывать не приходится.

Переполнение стека

Переполнение стека — это когда вы использовали больше памяти для стека, чем предполагалось использовать в вашей программе. Во встроенных системах у вас может быть только 256 байт для стека, и если каждая функция занимает 32 байта, тогда вы можете иметь только вызовы функций 8 функции функции 2 с функцией глубокой функции 1, которая вызывает функцию 3, которая вызывает функцию 4…. кто звонит функция 8, которая вызывает функцию 9, но функция 9 перезаписывает память за пределами стека. Это может перезаписать память, код и т.д.

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

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

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

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

Стек и стратегия кэш-игры

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

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

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

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

Если на каком-то этапе у вас стек снизился до уровня в 40 ББ, то лучше докупиться до полного. Тем более, что в онлайн покер-румах всегда есть функция автоматической докупки. Старайтесь играть правильно с первых дней. Пусть вы будете играть на низких бай-инах, но с полным стеком, чем на высоких с коротким. Поверьте, во втором случае банкролл будет таять намного быстрее, а опыта игры и навыков хорошего покериста вы не приобретете.

Но в некоторых ситуациях фишки необходимо сбрасывать. Это обязательно необходимо делать, если количество фишек в вашем стеке значительно превышает первоначальный показатель. Предположим, в игру на $0,1/$0,2 вы взяли с собой в качестве стека фишек на $20. То есть у вас полный стек. На каком-то этапе у вас скопилось $100.

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

Объясняется это довольно просто. Судите сами, если в ответ на агрессивный олл-ин оппонента со стеком в $120 вы ответите и проиграете свою сотню, то вы не сможете за один раз докупить проигранные фишки на $100. В игре стоит ограничение по докупке — $20. Следовательно, вы не сможете в полной мере реализовать математические ожидания от ситуации. Другими словами, впоследствии ваши $20 против его уже $220 на лимите $0,1/$0,2 так или иначе превратятся в пыль. К проигранным $100 добавятся еще $20.

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

Очередь

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

Так же и очередь — это линейная структура последовательных и упорядоченных элементов, похожая на стек, с той разницей, что она работает по принципу «первым пришел — первым вышел» (FIFO — first in first out).

Структура данных очереди имеет две основные операции:

  1. enqueue—Эта операция отвечает за вставку или отправку нового элемента в очередь.
  2. dequeue—Эта операция отвечает за удаление первого элемента из очереди.

Как и случае со стеком в JavaScript есть уже готовый метод удаления первого элемента массива, которым является метод массива shift.

Благодаря этому реализация очереди в JavaScript становится очень простой и мощной. Мы можем определить наш массив очереди следующим образом:

let stack = [];

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

const enqueue = (item) => queue.push(item);

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

const dequeue = () => queue.shift();

Все довольно просто

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

Помните, что методы push и pop имеют сложность O(1)? Метод   имеет сложность O(n).

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

function Queue() {
  this.queue = {};
  this.tail = 0;
  this.head = 0;
}

// Add an element to the end of the queue.
Queue.prototype.enqueue = function(element) {
  this.queue = element;
}

// Delete the first element of the queue.
Queue.prototype.dequeue = function() {
  if (this.tail === this.head)
      return undefined

  var element = this.queue;
  delete this.elements;
  return element;
}

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

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

Как создать стек с помощью массива

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

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

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

Для удаления элемента мы будем просто уменьшать на один.

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

Чтобы посмотреть пуст ли стек мы просто проверяем условие :

  • Если оно , то стек пуст.
  • Если оно , то в стеке есть какие-то элементы.

Реализация стека с помощью массива находится ниже:

#include <iostream>

using namespace std;

int main() {
int steck;
int i = -1; // объявили стек

for (int j = 0; j < 6; j++) {
int a;

cin >> a;

i++; // увеличиваем i на один

steck = a; // добавляем в стек элемент
}

if (i == -1) cout << «Стек пуст»; // проверяем пуст ли стек (нет)

cout << steck << » это верхний элемент стека»;

cout << «Сейчас мы удалим верхний элемент»;

i—; // уменьшаем i на один

system(«pause»);
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

#include <iostream>
 

usingnamespacestd;

intmain(){

intsteck20;

inti=-1;// объявили стек

for(intj=;j<6;j++){

inta;

cin>>a;

i++;// увеличиваем i на один

stecki=a;// добавляем в стек элемент

}

if(i==-1)cout<<«Стек пуст»;// проверяем пуст ли стек (нет)

cout<<stecki<<» это верхний элемент стека»;

cout<<«Сейчас мы удалим верхний элемент»;

i—;// уменьшаем i на один

system(«pause»);

return;

}

Ссылки [ править ]

  1. ^ Берли, Джеймс Крейг (1991-06-01). «Использование и перенос GNU Fortran» . Архивировано из оригинала на 2012-02-06.
  2. ^ a b В чем разница между ошибкой сегментации и переполнением стека? в StackOverflow
  3. ^ «Введение в схему и ее реализацию» . 1997-02-19. Архивировано из оригинала на 2007-08-10.
  4. ^ «Использование коллекции компиляторов GNU (GCC): параметры оптимизации» . Проверено 20 августа 2017 .
  5. ^ Ричард Келси; Уильям Клингер; Джонатан Рис; и другие. (Август 1998 г.). «Пересмотренный отчет 5 по алгоритмической языковой схеме» . Вычисление высшего порядка и символическое вычисление . 11 (1): 7–105. DOI10,1023 / A: 1010051815785 . Проверено 9 августа 2012 .
  6. Перейти ↑ Feldman, Howard (2005-11-23). «Современное управление памятью, часть 2» .
  7. ^ «Руководство по программированию ядра: советы по производительности и стабильности» . Apple , Inc . 2014-05-02.

Операции над стеками.

Операции,
выполняемые над стеками, имеют свои специальные названия: добавление элемента в
стек (push) и удаление из стека (pop).Если элемент i
помещается в стек S, то выполняется операция push(s,i). Операция i=pop(s)
удаляет верхний элемент из стека и присвоит его значение переменной i.

Заметим, что
число элементов в стеке не известно (по определению стека). Поэтому при
помещении нового элемента в стек не должно возникать никаких трудностей. В
случае, если стек содержит один элемент и этот элемент удаляется из стека, то в
результате выполнения этой операции ни один элемент не содержится больше в
стеке. Такой стек называют пустым. Очевидно, что операция удаления
элемента из стека не может быть выполнена для пустого стека. Поэтому перед тем
как выполнить операцию удаления из стека необходимо убедиться в том, что стек не
пустой. Для этого введем специальную операцию empty(s). Эта операция проверяет
является ли стек пустым. Результатом выполнения этой операции будет логическая
переменная, которая принимает значение “истина”, если стек пуст, и значение
“ложь” в противном случае.

Стек в покере

Определение

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

Размер стека

Понятно, что он может быть разным, и каждый имеет свое название и особенности, о которых мы поговорим далее. Пока же следует уяснить, что размер стека в покере всегда измеряется блайндами, которые приняты за столом, за которым идет игра. Предположим, вы играете за столом $1/$2. У вас фишек на $100. Следовательно, размер вашего стека составляет 50 Больших блайндов (ББ). Однако, как мы уже говорили, от размера стека напрямую зависит стратегия игры – чем больше стек, тем аккуратнее и осмотрительнее должна быть игра. Но об этом позже. Пока разберемся с тем, в чем заключается разница, которую имеют стеки в кэш-игре и в турнирах.

Покерный стек в кэш-играх и турнирах

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

К примеру, в игре Безлимитный Холдем с размером ББ $0,04 можно более или менее комфортно играть и при стеке в $1,5, но оптимальным решением здесь будет стек в $4. Естественно, речь идет о начальном стеке, который будет меняться по ходу игры – при проигрышах уменьшаться, при выигрышах – расти.

Для управления стеком в кэш-играх следует знать некоторые нюансы этого формата:

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

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

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

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

языки и системы высокого уровня

но в языках высокого уровня, работающих в операционных системах:

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

Какую реализацию стека следует рассмотреть?

Мы упомянули три метода реализации стека в Python. Вышеупомянутые методы имеют свои преимущества или недостатки.

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

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

Стек программы

Стек программы — это специальная области памяти, организованная по принципу очереди LIFO (Last in, first out — последним пришел, первым ушел). Название «стек» произошло из-за аналогии принципа его построения со стопкой (англ. stack) тарелок — можно класть тарелки друг на друга (метод добавления в стек, «заталкивание», «push»), а затем забирать их, начиная с верхней (метод получения значения из стека, «выталкивание», «pop»). Стек программы также называют стек вызовов, стек выполнения, машинным стеком (чтобы не путать его со «стеком» — абстрактной структурой данных).

Для чего нужен стек? Он позволяет удобно организовать вызов подпрограмм. При вызове функция получает некоторые аргументы; также она должна где-то хранить свои локальные переменные. Кроме того, надо учесть, что одна функция может вызвать другую функцию, которой тоже надо передавать параметры и хранить свои переменные. Используя стек, при передаче параметров нужно просто положить их в стек, тогда вызываемая функция сможет их оттуда «вытолкнуть» и использовать. Локальные переменные тоже можно хранить там же — в начале своего кода функция выделяет часть памяти стека, при возврате управления — очищает и освобождает. Программисты на высокоуровневых языках обычно не задумываются о таких вещах — весь необходимый рутинный код за них генерирует компилятор.

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

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