Гость
Целевая тема:
Создать новую тему:
Автор:
Форумы / Программирование [игнор отключен] [закрыт для гостей] / Тяпничная со-программа / 25 сообщений из 54, страница 1 из 3
19.08.2016, 15:17
    #39294661
mayton
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
Добрый день коллеги. С пятницей.

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

Тема - со-программами или co-routines

Я квотирую определение с вики чтоб было понятно

wikiСопрограмма (англ. coroutine) — компонент программы, обобщающий понятие подпрограммы,
который дополнительно поддерживает множество входных точек (а не одну как подпрограмма),
остановку и продолжение выполнения с сохранением определённого положения.

Сопрограммы являются более гибкими и обобщёнными, чем подпрограммы, но реже используются
на практике. Применение сопрограмм являлось методикой ещё ассемблера, практиковалось
лишь в некоторых высокоуровневых языках (Simula, Modula-2). Сопрограммы хорошо
пригодны для реализации многих похожих компонентов программ (итераторов, бесконечных
списков, каналов, совместных задач).


Под катом - мотивация сабжа для меня.

Год назад я пытался нарисовать картинку Тяпничная география
- распределение Ipv4 адресов по странам и отображение этого
на диаграмме в виде цветных квадратов.

Это была не бизнес разработка и чуть позже она мне надоела и я ее отложил.

Для рисования квадратов я кодил заполняющую кривую Гилберта. И резальтатом разработок
была экспериментальная консольная тулза https://sourceforge.net/p/countryipdiagram/code/HEAD/tree/trunk/utils/src/gilbertroute.c
которая должна быть генератором координат Гилберта. Сами коды выводились в stdout для передачи в другую утилиту.

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

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

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

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

Вобщем как обстоят дела с со-программами (co-routines) в С/С++/C#/Java/Groovy/Scala/JavaScript/Delphi.


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


Я вот сделал некоторую табличку в которой попробовал обрисовать положение вещей на текущий момент

Поддержка со-программ в ЯП
LanguageCo-routines supportCno supportС++Возможно с поддержкой boost::coroutinesC#with yield returnJavano suportGroovy?Scalawith yieldDelphino supportJava Script Since ECMA6
В многочисленных статьях также упоминается что для поддежки co-routines язык
или среда должен поддерживать continuations.

Вот такая ситуация. У кого какие были практики с coroutines? Поделитесь.
...
Рейтинг: 0 / 0
19.08.2016, 16:46
    #39294711
Вася Уткин
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
Было бы круто, если бы был полностью рабочий локализованный пример, который использует сопрограммы или который не использует, но при их наличии был бы намного понятнее/быстрее, на любом из упомянутых языков на онлайн компиляторе: http://ideone.com/

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

Нафига выходить по yield, выходи по return, а текущее состояние всех итераторов храни как члены класса, сохранятся в объекте.
Если просто хочется распараллеливания, то используй потоки, выходи по yield(), и используй потокобезопасный обмен - ровно теже сопрограммы.
...
Рейтинг: 0 / 0
19.08.2016, 17:01
    #39294718
Anatoly Moskovsky
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
maytonУ кого какие были практики с coroutines? Поделитесь.
Используем в продакшене Boost.Coroutine (для асинхронных операций).
Код на порядок проще и понятнее, чем с колбэками.
...
Рейтинг: 0 / 0
19.08.2016, 17:04
    #39294720
kealon(Ruslan)
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
mayton,
а goto отменили что ли уже?

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

каналы как абстракция делаются в любом из языков поддерживающих генерики или метапрограммирование, зачем для этого делать языковые конструкции?
...
Рейтинг: 0 / 0
19.08.2016, 17:04
    #39294721
Anatoly Moskovsky
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
Вася УткинНафига выходить по yield, выходи по return, а текущее состояние всех итераторов храни как члены класса, сохранятся в объекте.
Так написал же человек - не хочет он явно хранить состояние, когда алгоритмически никакого состояния нет.
Т.е. то что вы предлагаете, это усложнение алгоритма.
...
Рейтинг: 0 / 0
19.08.2016, 17:04
    #39294722
mayton
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
Я щас сходу не готов дать охватывающий пример. У меня щас нет такого под рукой.

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

Обеспечте интерфейс который обходит все узлы:

Код: plaintext
1.
2.
3.
4.
interface RecursiveIterator<Node>{
  bool hasNext();
  Node next();
}



Это и есть контр пример. Тоесть пример того как кодить неудобно.

Без использования уступчивого return, мультипоточности и contnuations.

Это моё ИМХО.
...
Рейтинг: 0 / 0
19.08.2016, 17:12
    #39294726
mayton
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
kealon(Ruslan)mayton,
а goto отменили что ли уже?

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

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

Приведите пример плиз. Буду признателен.
...
Рейтинг: 0 / 0
19.08.2016, 17:45
    #39294736
kealon(Ruslan)
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
maytonНу... я готов покорректировать табличку. Наверное в Турбо-Паскале есть языковый API для работы
с каналами. Я этого не знал.
языковых конструкций нету, я же говорю делал нитевидную многозадачность под досом
выделялась память под стэк и проца yield, которая меняла регистр SP на доступные стэки.
Сомневаюсь что так можно под виндой сделать, хотя попробовать не мешает ...

а каналы генериками в Delphi, fpc делаются довольно легко
...
Рейтинг: 0 / 0
19.08.2016, 17:48
    #39294737
mayton
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
kealon(Ruslan)а каналы генериками в Delphi, fpc делаются довольно легко
Но это language features? Или нет?
...
Рейтинг: 0 / 0
19.08.2016, 17:51
    #39294741
kealon(Ruslan)
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
maytonЯ щас сходу не готов дать охватывающий пример. У меня щас нет такого под рукой.

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

Обеспечте интерфейс который обходит все узлы:

Код: plaintext
1.
2.
3.
4.
interface RecursiveIterator<Node>{
  bool hasNext();
  Node next();
}



Это и есть контр пример. Тоесть пример того как кодить неудобно.

Без использования уступчивого return, мультипоточности и contnuations.

Это моё ИМХО.
а в чем проблема указатели в стэке сохранить?

Код: pascal
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.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
   generic BinMapEnum<TBinNode,TItem>=class(TObject)
    public
     type
       PBinNode=^TBinNode;
    private
     Stack:TStack;
     CurrentNode:PBinNode;
     function GetCurrent: TItem;
    public
     constructor Create(const ARoot:PBinNode);
     destructor Destroy;override;

     function MoveNext: Boolean;
     property Current: TItem read GetCurrent;
   end;

{ BinMapEnum }

function BinMapEnum.GetCurrent: TItem;
begin
  Result:=CurrentNode^.Data;
end;

constructor BinMapEnum.Create(const ARoot: PBinNode);
var N:PBinNode;
begin
  inherited Create;
  N:=ARoot;
  while N<>nil do begin
    Stack.Push(N);
    N:=N^.Left;
  end;
end;

destructor BinMapEnum.Destroy;
begin
  Stack.Done;
  inherited Destroy;
end;

function BinMapEnum.MoveNext: Boolean;
begin
  if CurrentNode=nil then begin
    CurrentNode:=Stack.Pop;
  end else begin
    if CurrentNode^.Right<>nil then begin
      CurrentNode:=CurrentNode^.Right;
      While CurrentNode^.Left<>nil do begin
        Stack.Push(CurrentNode);
        CurrentNode:=CurrentNode^.Left;
      end;
    end else begin
      CurrentNode:=Stack.Pop;
    end;
  end;

  Result:=(CurrentNode<>nil);
end;


...
Рейтинг: 0 / 0
19.08.2016, 17:55
    #39294743
kealon(Ruslan)
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
maytonkealon(Ruslan)а каналы генериками в Delphi, fpc делаются довольно легко
Но это language features? Или нет?
это скорее language-oldest, зачем это делать если либами можно сделать? да и с ОС проблемы, она просто так стэковые регистры менять не даёт
...
Рейтинг: 0 / 0
19.08.2016, 18:09
    #39294746
mayton
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
kealon(Ruslan)
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
   generic BinMapEnum<TBinNode,TItem>=class(TObject)
    public
     type
       PBinNode=^TBinNode;
    private
     Stack:TStack;
     CurrentNode:PBinNode;
     function GetCurrent: TItem;
    public
     constructor Create(const ARoot:PBinNode);
     destructor Destroy;override;

     function MoveNext: Boolean;
     property Current: TItem read GetCurrent;
   end;



Эээ... надо подумать.
...
Рейтинг: 0 / 0
19.08.2016, 20:14
    #39294780
Dima T
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
yield в C# это тема, правда я в нее еще не вникал глубоко. Тут фундаментально меняется подход, ты получаешь на вход интерфейс, который будет (будет в будущем) возвращать тебе последовательность, дальше можно его передать на вход следующей функции и т.д. Это и есть основа LINQ.
И в итоге это выглядит так, например найти первое число в массиве между 1-10
Код: c#
1.
res = array.Where(x => x >= 1 && x <= 10).Single();


и тут перебор будет именно до первого удовлетворяющего условию. Синтаксический сахар конечно, но сахар он на то и нужен чтобы сладко было.
...
Рейтинг: 0 / 0
19.08.2016, 20:38
    #39294785
mayton
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
Dima T, это пример вырожденный. Его легко итерациями сделать.
А вот задачка обхода деревьев как функция которая возвращает
узлы - это красиво.
...
Рейтинг: 0 / 0
19.08.2016, 20:52
    #39294786
Dima T
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
maytonDima T, это пример вырожденный. Его легко итерациями сделать.
Я не спорю, я же написал что это синтаксический сахар, но я не люблю С/С++ именно за то что обязательно надо это делать, т.е. писать кучу букав, потом отлаживать, а тут написал одну строчку и получил что хотел. Мне не нравится С именно поэтому, т.к. для элементарного действия надо написать портянку на два экрана, когда в высокоуровневом ЯП это одна строка.
...
Рейтинг: 0 / 0
19.08.2016, 21:17
    #39294790
mayton
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
Дима.

1) Я понял. Я думаю что ближайшие лет 10 основной темой споров будут
доводы за и против лямбд и Stram-операций в обычных ЯП.

У меня отношения к лямбдам весьма сдержанное. Тоесть я сперва
хочу понять pitfalls в части их практического использования (Java) как то
отладка, области видимости (лямбда не видит не статичные переменные класса),
и обработка исключений внутри лямбд (куда мы вываливаемся?) и
конечно-же перформанс.

Но эта тема другого топика.

2) Я вернусь к своим баранам. А именно к coroutine. Разворачивая рекурсию
в С-программе которую я привел выше я пришел к следующему итератору.

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

Код: java
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.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
public class GilbertIteratorBlockingQueue implements Iterator<Position> {

    static Logger logger = LogManager.getLogger(GilbertIteratorBlockingQueue.class);

    Producer producer;
    int size;
    BlockingQueue<Position> queue;
    Position position = Position.DUMMY;
    int state = 0;

    public GilbertIteratorBlockingQueue(int size) {
        if (size<4){
            throw new IllegalArgumentException("The dimension of 'Gilbert' curve cannot be less than 4 x 4!");
        }
        this.size = size;
        queue = new ArrayBlockingQueue<>(1024);
        producer = new Producer();
        new Thread(producer).start();
    }


    class Producer implements Runnable {

        static final int u = 1;
        int glx;
        int gly;
        int level;

        public Producer() {
            level = log2up(size);
            int clp2size = clp2(size);
            if (clp2size > size) {
                logger.warn("Warning! The real dimension of iterator's space has been extended to {}x{} pixels", clp2size, clp2size);
            }
            state = 1;
        }

        public void run() {
            try {
                moveto(0, 0);
                a(level);
                queue.put(Position.DUMMY);
            } catch (InterruptedException e) {
                logger.error(e);
            }
        }

        void linerel(int x, int y) throws InterruptedException {
            glx += x;
            gly += y;
            queue.put(new Position(glx, gly));
        }

        void moveto(int x, int y) throws InterruptedException {
            glx = x;
            gly = y;
            queue.put(new Position(glx, gly));
        }

        // Elements of curve
        void a(int i) throws InterruptedException {
            if (i > 0) {
                d(i - 1);
                linerel(+u, 0);
                a(i - 1);
                linerel(0, u);
                a(i - 1);
                linerel(-u, 0);
                c(i - 1);
            }
        }

        void b(int i) throws InterruptedException {
            if (i > 0) {
                c(i - 1);
                linerel(-u, 0);
                b(i - 1);
                linerel(0, -u);
                b(i - 1);
                linerel(u, 0);
                d(i - 1);
            }
        }

        void c(int i) throws InterruptedException {
            if (i > 0) {
                b(i - 1);
                linerel(0, -u);
                c(i - 1);
                linerel(-u, 0);
                c(i - 1);
                linerel(0, u);
                a(i - 1);
            }
        }

        void d(int i) throws InterruptedException {
            if (i > 0) {
                a(i - 1);
                linerel(0, u);
                d(i - 1);
                linerel(u, 0);
                d(i - 1);
                linerel(0, -u);
                b(i - 1);
            }
        }


    }

    @Override
    public boolean hasNext() {
        if (state == 2){
            return false;
        }
        try {
            position = queue.take();
            if (position == Position.DUMMY) {
                state = 2;
                return false;
            }
        } catch (InterruptedException e) {
            logger.error(e);
        }
        return true;
    }

    @Override
    public Position next() {
        return position;
    }
}



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

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



3) По поводу Streams (Java8). Я надеюсь они мне позволят заменить со-программы.
...
Рейтинг: 0 / 0
19.08.2016, 23:08
    #39294809
kealon(Ruslan)
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
maytonМеня это обескураживает и я ищу простые лаконичные механизмы
не жрущие списки памяти и в то-же время выдающие сиквенс
значений из рекурсии.

рекурсия это и есть список памяти, ты просто фактически не хочешь о нём заботиться сам
Рекурсия кстати процентов на 20-30 медленнее (за счёт сохранения всех данных). Чем стэковый автомат в уже выделенном блоке. За простоту кода приходится платить ресурсами.
...
Рейтинг: 0 / 0
19.08.2016, 23:20
    #39294813
mayton
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
kealon(Ruslan)рекурсия это и есть список памяти, ты просто фактически не хочешь о нём заботиться сам
Рекурсия кстати процентов на 20-30 медленнее (за счёт сохранения всех данных). Чем стэковый автомат в уже выделенном блоке. За простоту кода приходится платить ресурсами.
Очень часто мы (разработчики) согласны платить 20-30% (здесь я сомневаюсь надо пересчитать)
но при этом иметь простой концептуальный код.

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

Заметьте что мы с вами еще не рассматривали оценку сложности этого процесса.
...
Рейтинг: 0 / 0
19.08.2016, 23:22
    #39294814
mayton
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
Я имею в виду оценку сложности процесса переписывания рекурсии в автомат со стеком.
...
Рейтинг: 0 / 0
19.08.2016, 23:39
    #39294823
kealon(Ruslan)
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
maytonЕсли yield return несет в себе нулевые накладные расходы (синхронизации нет, нет потоков, IPC,
и очередей) то я согласен и я хочу использовать этот return в противоположность разворачиванию
рекусии в конечный автомат.

несёт, для них нужен отдельный блок стэка, сколько его выделить 100кб, 1Мб или больше?
траты на yield в общем случае : все регистры, связка pushad-popad довольно быстрая, но не бесплатная операция. это мы ещё не вспоминаем про регистры fpu

maytonЗаметтье что мы с вами еще не рассматривали оценку сложности этого процесса.


есть ещё вариант: если в функциональном стиле написать алгоритм, то его можно довольно просто механически разложить через комбинаторы в "автомат со стэковой памятью"
...
Рейтинг: 0 / 0
20.08.2016, 14:43
    #39294944
kealon(Ruslan)
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
mayton,

вот же нашёл ты тему, всё таки получилось у меня это сделать
...
Рейтинг: 0 / 0
20.08.2016, 15:09
    #39294952
mayton
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
kealon(Ruslan), спасибо. Но я не специалист в FreePascal. Вряд-ли я могу собрать это.

А вот по поводу комбинаторов и автоматов со стеком - подкинь материала.
...
Рейтинг: 0 / 0
20.08.2016, 15:32
    #39294955
kealon(Ruslan)
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
maytonkealon(Ruslan), спасибо. Но я не специалист в FreePascal. Вряд-ли я могу собрать это.

А вот по поводу комбинаторов и автоматов со стеком - подкинь материала.
тут надо подумать, знаю что можно, но вот где и когда читал - подзабыл
...
Рейтинг: 0 / 0
21.08.2016, 08:14
    #39295061
tchingiz
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
авторподдерживает множество входных точек (а не одну как подпрограмма),
остановку и продолжение выполнения с сохранением определённого положения.
...
Применение сопрограмм .....практиковалось
лишь в некоторых высокоуровневых языках (Simula, Modula-2)
по моему брехня.
точки входа в подпрограмму были чуть ли не везде (пл-1 и алгол), а продолжать выполнение с сохранением определенного положения -- можно благодаря static
...
Рейтинг: 0 / 0
21.08.2016, 11:59
    #39295092
kealon(Ruslan)
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Тяпничная со-программа
tchingizточки входа в подпрограмму были чуть ли не везде (пл-1 и алгол), а продолжать выполнение с сохранением определенного положения -- можно благодаря static

Fullstack CoRoutines на основе одной переменной в общем виде повторить не получится
смотри пример с обходом бинарного дерева

рекурсию всё равно с помощью стэка придётся делать в том или ином виде
...
Рейтинг: 0 / 0
Форумы / Программирование [игнор отключен] [закрыт для гостей] / Тяпничная со-программа / 25 сообщений из 54, страница 1 из 3
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]