Featured image of post Разбор матэматычных выразаў на C#. Алгарытм сартавальнай станцыі

Разбор матэматычных выразаў на C#. Алгарытм сартавальнай станцыі

Зіма - самая халодная пара года, але нават яна не настолькі халодная, як рэакцыя інтэрв’юера на тваю рэалізацыю калькулятара для постфікснай натацыі. Тым не менш у нас няма часу на эмоцыі, таму прыступім да разбору алгарытму сартавальнай станцыі, згаданага ў папярэднім пасце.

Дадзены алгарытм прызначаны для пераўтварэння выразаў з інфікснай натацыі ў постфіксную. У дадзеным артыкуле не будзе прыводзіцца поўны доказ працы дадзенага алгарытму, а толькі дадзены частковыя тлумачэнні, каб зрабіць рэалізацыю больш інтуітыўна зразумелай.

Пачнем з самых простых прыкладаў: прымяненне бінарнага аператара да аперандаў. Напрыклад, для выразу 2 + 3 аналагічным выразам у постфікснай натацыі будзе 2 3 +. Тое ж будзе верна для іншых бінарных аператараў. Паспрабуем скласці простую мадэль на C# для працы з такімі выразамі:

  • Пакуль выраз мае токены бярэм чарговы токен:

    • Калі токен з’яўляецца аперандам, то запісваем яго ў выніковую паслядоўнасць;

    • Калі токен з’яўляецца аператарам, то захоўваем яго ў асобным сховішчы;

  • Дастаем захаваны токен са сховішча і дадаем яго ў выніковы выраз.

Усё гэта працуе цудоўна да таго моманту, пакуль мы не захочам ўскладніць зыходны выраз і дадаць яшчэ адзін аператар 2 + 3 - 4. У дадзеным выпадку аналагічным постфіксным выразам будзе 2 3 + 4 -. Заўважым, што папярэдні алгарытм не можа захоўваць больш за адзін аператар ў часовым сховішчы, такім чынам мы страцім адзін аператар у працэсе вылічэння і ў выніку атрымаем 2 3 4 -. Гэта не можа не засмучаць, таму паспрабуем унесці паляпшэнне ў наш алгарытм пераўтварэння.

  • Пакуль выраз мае токены бярэм чарговы токен:
    • Калі токен з’яўляецца аперандам, то запісваем яго ў выніковую паслядоўнасць;
    • Калі токен з’яўляецца аператарам, то запамінаем яго ў асобным сховішчы. Калі сховішча змяшчае іншы аператар, то запісваем захаваны аператар у выніковую паслядоўнасць, а новы аператар захоўваем у сховішча.
  • Дастаем захаваны токен са сховішча і дадаем яго ў выніковы выраз.

Прыгажосць! Але што ж рабіць, калі аператары маюць розны прыярытэт? 2 + 3 * 4 выраз павінны быць пераўтвораны ў выраз 2 3 4 * +, але ж ніяк не ў выраз 2 3 + 4 *. Для далейшага паляпшэння алгарытму разгледзім таксама выраз 2 * 3 + 4 і яго аналаг 2 3 * 4 +. Адсюль мы можам зрабіць выснову, што наш алгарытм не працуе толькі для выпадкаў, калі наступны аператар мае больш высокі прыярытэт, чым папярэдні. Прычым у выпадку, калі наступны аператар мае больш высокі прыярытэт, аператары павінны як бы памяняцца месцамі ў выніковым выразе. Адсюль напрошваецца выснова, што неабходна для сховішча аператараў выкарыстоўваць стэк, таму ўнясем чарговае змяненне ў наш алгарытм:

  • Пакуль выраз мае токены бярэм чарговы токен:
    • Калі токен з’яўляецца аперандам, то запісваем яго ў выніковую паслядоўнасць;

    • Калі токен з’яўляецца аператарам, то запамінаем яго ў асобным сховішчы. Калі сховішча змяшчае іншы аператар, то запісваем захаваны аператар у выніковую паслядоўнасць, а новы аператар захоўваем у сховішча.

    • Калі токен з’яўляецца аператарам, то:

      • Пакуль стэк аператараў не пусты, і ў вяршыні стэку ляжыць аператар з не меншым (> =) прыярытэтам, дастаем яго са стэку і запісваем у выніковую паслядоўнасць;

      • Захоўваем токен ў стэк аператараў

  • Дастаем захаваны токен са сховішча і дадаем яго ў захаваныя аператары са стэку аператараў і дадаем іх у выніковы выраз.

Застаўся апошні штрых - дадаць падтрымку дужак, якія могуць змяняць прыярытэт выканання аперацый. Разгледзім наступны выраз (2 + 3 * 4) / 5 і аналагічны выраз у постфікснай натацыі 2 3 4 * + 5 /. Іншымі словамі, выраз у дужках павінен быць вылічаны перад выкананнем дзялення. Пашырым спіс магчымых аператараў аператарамі парадку (дужкамі):

Сімвал закрывальнай дужкі з’яўляецца па сутнасці сваёй сігналам, які паведамляе, што да гэтага моманту неабходна выканаць разлік. Паколькі постфіксны калькулятар з’яўляецца паслядоўным, то чым пазней аператар з’яўляецца ў постфікснай натацыі, тым пазней адбываецца яго выкананне. Таму пашырым алгарытм наступнымі правіламі:

  • Пакуль выраз мае токены бярэм чарговы токен:

    • Калі токен з’яўляецца аперандам, то запісваем яго ў выніковую паслядоўнасць;

    • Калі токен з’яўляецца аператарам (не дужкай), то:

      • Пакуль стэк аператараў не пусты, і ў вяршыні стэку ляжыць аператар з не меншым (> =) прыярытэтам, дастаем яго са стэку і запісваем у выніковую паслядоўнасць;
      • Захоўваем токен ў стэк аператараў.
    • Калі токен з’яўляецца адкрывальнай дужкай, то захоўваем аператар у стэк аператараў;

    • Калі токен з’яўляецца закрывальнай дужкай, то дастаем аператары са стэку аператараў і захоўваем у выніковую паслядоўнасць, пакуль не сустрэнем адкрываную дужку. Калі адкрывальная дужка не была знойдзена, то сігналізуем пра памылку ў выразе;

  • Дастаем захаваныя аператары са стэку аператараў і дадаем іх у выніковы выраз. Калі сярод аператараў ёсць адкрывальная дужка, то сігналізуем пра памылку ў выразе.

Поўную рэалізацыю можна паглядзець на GitHub праекта coding-interview. На гэтым будзем заканчваць разбор алгарытму сартавальнай станцыі, і, не губляючы часу, пераходзім да алгарытму такенізацыі, які апісаны ў наступным пасце.

Створана пры дапамозе Hugo
Тэма Stack, дызайн Jimmy