Сучасна диджитал-освіта для дітей — безоплатне заняття в GoITeens ×
Mazda CX 30
×

Как избежать GoTo?

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

Приветствую.

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

Суть такова: на языке типа Python реализовать телефонное меню. То самое, которое Вы слышите позвонив своему мобильному оператору «нажмите 1, нажмите 2» и так далее.
Вопрос не в том, как говорить или считывать данные, можно представить себе это в виде консольного диалога с пользователем. Это самое меню (IVR) представляет собой дерево выборов, но с возможностью из любой точки переместиться в любую. Т.е. по этому самому меню в теории можно ходить бесконечно.

Если представить меню как цепочку 1 — 2 — 3 — 4 — 5 -... n, то из любой точки в теории можно попасть в любую (в зависимости от ответа пользователя), причем без особых проблем с сохранением предыдущих состояний. Разве что историю прохода хранить.

В обычных VoIP решениях (Asterisk, свой скриптовый язык) тут очень сильно применяется GoTo. И ничего плохого в этом нет.

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

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

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Как избежать GoTo?
не использовать, за ненадобностью

Зачем его избегать?
goto это фактически одна инструкция jmp адрес, я сейчас про С/C++ (да и собственно про любую другую VM), управление регистром адреса исполняемой инструкции.
Это практически ничего не стоит в отличии от инструкций условного перехода, где уже может сработать или нет branch prediction.
Пользуйтесь (в разумных пределах конечно) и не партесь по поводу того что goto это почему то плохо :-)

Из описания похоже на конечный автомат (FSM) который проще всего реализуется с помощью табличек переходов
Просто погугли как это реализуется — это на уровне лабы для студента 3 курса по сложности

Да, но вопрос не в сложности лабы 3го курса, а в том, что привык работать с этим другими методами.
На 2м курсе проходят дифуры и интегралы по объему. Но это не значит, что я до сих пор умею это решать :)

Но это не значит, что я до сих пор умею это решать :)
так вроде второй линк в гугле по «конечный автомат python» решает эту проблему
habrahabr.ru/post/141503

Мне кажется, по описанию очень смахивает на конечный автомат, ака finite state machine.

Конечный автомат можно реализовать, например, с помощью словаря функций (имя_состояния => обработчик_событий), но не уверен, что в python делается именно так.

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

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

что нетак с case if или просто case ?

Все так, просто общей идеи не было :)

Запилил свой вариант, критикуйте

mapping = {
    1: [2,3,4],
    2: [5,6],
    3: [7,8],
    5: [9,10]
}

def menu(label=1):
    print 'Menu page {}'.format(label)
    items = mapping.get(label, [])
    i = 1
    for item in items:
        print 'Press {} for submenu {}'.format(i, item)
        i += 1
    if label != 1:
        print 'Press 0 to return to main menu'
    try:
        selection = int(raw_input('Select next item > '))
        return 1 if not selection else items[selection-1]
    except (ValueError, IndexError):
        return label

menu_label = 1
while True:
    menu_label = menu(menu_label)

Примерно идея понятна, буду пробовать, спасибо :)

Створюєте мапу айді до хендлера. Питаєте юзера про вибір. Викликаєте відповідну функцію. Якщо там теж меню — після вибору підпункту повертаєте його айді назад. В кореневому хендлері слухаєте: якщо повернулось значення, викликаєте відповідну функцію... Глибина стеку завжди буде root -> handler.

Понял, спасибо, судя по всему, так и попробую сделать :)

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

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

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

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

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

случае словарь — функция

Словарь — это словарь (dict), вы просто меняете переменную «текущий словарь» согласно переходу.

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

Если я правильно понял, то из обработчика будет вызываться другой обработчик по таблице (или по имени, не важно). Тогда может быть ситуация типа (глубина вызова)
обработчик 1 -> обработчик 5 -> обработчик 1 -> обработчик 4....
Просто при достаточно сложной структуре я боюсь за стек :) Вот такого типа сложности я и хочу избежать. Суть в том, что хранить кто кого вызывал и возвращаться туда — нет необходимости.

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

По факту сама система — и есть state machine. Но вот как его сделать правильно и не так, чтобы потом офигеть от сложности — вопрос.

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

Дальше запустить ее, и в ответ на действия юзеров делать переход из одного состояния в другое

Я не питонист, вот что-то нашел:
github.com/mriehl/fysom

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

А можно более детально? Как наблюдение за состоянием может чем-то помочь?

не за состоянием, а за нажатием

Я же говорю, проблем считать ответ нет. Там довольно простая конструкция типа
digits = session.getDigits(). Да, никакого async, но он там и не нужен.
Или я не понял идею.

Тогда, если можно, можете пояснить для тех, кто в танке? )

Для начала вопрос — можно ли в этом меню к любому пункту всегда построить путь без возвратов по дереву назад? Типа есть ли такое, что четвертый пункт во втором меню (2,4) доступен только через третье : (1,1) -> (2,1)->(3,2)->(2,4) и никак иначе?

Не факт. Тем более, все ни разу не прибито гвоздями. Сегодня структура переходов одна, завтра — другая.

Проще говоря, всегда ли доступны все пункты текущего меню?

Підписатись на коментарі