OberonCore
https://forum.oberoncore.ru/

Убираю LOOP'ы
https://forum.oberoncore.ru/viewtopic.php?f=82&t=2547
Страница 4 из 5

Автор:  Александр Ильин [ Четверг, 21 Апрель, 2011 09:53 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Разбирал очередной LOOP, увидел вот что. Не только выход из LOOP с помощью RETURN, но и выход из FOR c помощью EXIT.
Код:
   d := 0; i := 0;
   LOOP
      IF d = LEN (array, 1) THEN
         RETURN FALSE
      END;
      FOR i := 0 TO LEN (array, 0) - 1 DO
         PrepareForCheck1 (array [d, i]);
         IF Check1 (array [d, i]) THEN
            EXIT
         END;
         PrepareForCheck2 (array [d, i]);
         IF Check2 (array [d, i]) THEN
            EXIT
         END;
      END; (* for i *)
      INC (d);
   END; (* loop *)
   HandleSuccess ();
   RETURN TRUE

Первым делом объединил процедуры PrepareForCheckN с CheckN, чтобы можно было целиком поставить в IF (побочных эффектов у них не было).
Код:
   d := 0; i := 0;
   LOOP
      IF d = LEN (array, 1) THEN
         RETURN FALSE
      END;
      FOR i := 0 TO LEN (array, 0) - 1 DO
         IF PrepareAndCheck1 (array [d, i]) THEN
            EXIT
         END;
         IF PrepareAndCheck2 (array [d, i]) THEN
            EXIT
         END;
      END; (* for i *)
      INC (d);
   END; (* loop *)
   HandleSuccess ();
   RETURN TRUE

Уже видно, что внутри FOR условия можно объединить с помощью OR, а сам FOR при этом легко превращается в обычный поисковый WHILE. По сути EXIT из FOR - это успешное завершение поиска, которое надо сигнализировать за пределы LOOP. Заводим для этого переменную res: BOOLEAN:
Код:
   res := FALSE; d := 0;
   WHILE (d < LEN (array, 1)) & ~res DO
      i := 0;
      WHILE (i < LEN (array, 0)) & ~res DO
         res := PrepareAndCheck1 (array [d, i]) OR PrepareAndCheck2 (array [d, i]);
         INC (i);
      END; (* while i *)
      INC (d);
   END; (* while d *)
   IF res THEN
      HandleSuccess ();
   END;
   RETURN res

Значения i и d не используются в HandleSuccess. Если бы использовались, то надо было бы их уменьшить на 1 при успешном завершении поиска.
Итого, устранены три преждевременных выхода из циклов, добавлена одна локальная переменная, исходник уложился в понятный шаблон и стал короче на 6 строк (правда, пришлось написать вспомогательную процедуру, но она пригодилась ещё раз для усмирения аналогичного LOOP'а в том же модуле).

Кстати, сравните приведённый выше код вот с этим:
Код:
   res := FALSE; d := 0;
   WHILE (d < LEN (array, 1)) & ~res DO
      i := 0;
      WHILE (i < LEN (array, 0)) & ~(PrepareAndCheck1 (array [d, i]) OR PrepareAndCheck2 (array [d, i])) DO
         INC (i);
      END; (* while i *)
      res := ~(i < LEN (array, 0));
      INC (d);
   END; (* while d *)
   IF res THEN
      HandleSuccess ();
   END;
   RETURN res

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

Автор:  Peter Almazov [ Четверг, 21 Апрель, 2011 13:17 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Если я правильно понял, то эти переработки не эквивалентны оригиналу. В нем HandleSuccess () вызывается всегда, там сплошной Success :)

Автор:  Евгений Темиргалеев [ Четверг, 21 Апрель, 2011 13:53 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Peter Almazov писал(а):
Если я правильно понял, то эти переработки не эквивалентны оригиналу. В нем HandleSuccess () вызывается всегда, там сплошной Success :)
Не всегда --- в теле цикла RETURN

Автор:  Илья Ермаков [ Четверг, 21 Апрель, 2011 13:56 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

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

Автор:  Peter Almazov [ Пятница, 22 Апрель, 2011 12:16 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

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

Автор:  Александр Ильин [ Понедельник, 25 Апрель, 2011 16:45 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Ужас, летящий на крыльях ночи.
Код:
PROCEDURE CheckAll (flag: BOOLEAN);
VAR
   done: BOOLEAN;
   fc: SHORTINT;
BEGIN
   IF SomeCondition () THEN
      fc := 0;
   ELSE
      fc := 2;
   END;
   done := FALSE;
   LOOP
      IF FirstObject () THEN
         REPEAT
            IF fc = 2 THEN
               IF SomeCheck () THEN DEC (fc) END;
            ELSIF (fc = 1) & SomeCheck () THEN
               EXIT
            ELSIF AccessCheck () THEN
               done := DoSomething (flag);
            END;
         UNTIL done OR ~NextObject ();
         DEC (fc);
      ELSE
         EXIT
      END; (* if FirstObject *)
      IF fc < 0 THEN
         EXIT
      END;
   END; (* loop *)
END CheckAll;
Задача - выяснить, в каких отношениях состоят проверки SomeCondition, SomeCheck и переменная fc, после чего упростить код.

Автор:  Евгений Темиргалеев [ Понедельник, 25 Апрель, 2011 17:02 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Александр Ильин писал(а):
Код:
   IF SomeCondition () THEN
      fc := 0;
   ELSE
      fc := 2;
   END;
Задача - выяснить, в каких отношениях состоят проверки SomeCondition, SomeCheck и переменная fc, после чего упростить код.
Предлагаю для начала выписать две версии процедуры --- для fc = 0 и fc = 2.

Автор:  Valery Solovey [ Понедельник, 25 Апрель, 2011 18:17 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Тут сложно что-то онветить. Непонятно, какие процедуры используют элементы из последовательности (NextObject).

fc - это состояние конечного автомата.
SomeCondition - это переход с состояния, находящегося на более высоком уровне.
SomeCheck - это переход между состояниями на этом уровне.

Автор:  Александр Ильин [ Понедельник, 25 Апрель, 2011 18:27 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Valery Solovey писал(а):
Тут сложно что-то онветить. Непонятно, какие процедуры используют элементы из последовательности (NextObject).
FirstObject-NextObject - результаты их работы используются всеми процедурами, кроме SomeCondition. Т.е. SomeCheck, AccessCheck и DoSomething - все, кто находится внутри REPEAT..UNTIL, работают с очередным объектом.

Автор:  Valery Solovey [ Понедельник, 25 Апрель, 2011 18:40 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

а FirstObject возвращает к началу последовательности или выдаёт FALSE при повторном обращении?

Автор:  Александр Ильин [ Понедельник, 25 Апрель, 2011 18:54 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Valery Solovey писал(а):
а FirstObject возвращает к началу последовательности или выдаёт FALSE при повторном обращении?
Возвращает к началу. FALSE возвращает только если вообще нету объектов для обработки (пустой список).

Автор:  Valery Solovey [ Понедельник, 25 Апрель, 2011 20:58 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Стало больше, чем было.

Код:
PROCEDURE CheckAll (flag: BOOLEAN);
VAR
   done: BOOLEAN;
   objNotNil, twinsFound : BOOLEAN;
BEGIN
   objNotNil := FirstObject();
   IF objNotNil THEN
      IF SomeCondition() THEN (* fc = 0 *)
         (*done := AccessCheck() & DoSomething(flag); (* DoSomething не добавляет элементы в последовательность? *)
         WHILE ~done & NextObject() DO done := AccessCheck() & DoSomething(flag) END*)
         WHILE ~(AccessCheck() & DoSomething(flag)) & NextObject() DO END
      ELSE (* fc = 2 *)
         WHILE objNotNil & ~SomeCheck() DO objNotNil := NextObject() END;
         IF ~objNotNil THEN (* SomeCheck не нашёл ничего с первого раза - не найдёт и со второго. Поэтому сразу копируем сюда то же, что выполняется для fc = 0 *)
            objNotNil := FirstObject();
            WHILE ~(AccessCheck() & DoSomething(flag)) & NextObject() DO END
         ELSE (* SomeCheck пытается повторить удачу *)
            twinsFound := FALSE;
            done := FALSE;
            (* цикл ниже не будет работать как исходный пример, если хвост очереди < 2. В исходном примере при хвосте = 1 последовательность завернётся на начало, и SomeCheck снова найдёт тот же объект, что и на предыдущем проходе по последовательности. Так и надо? *)
            WHILE ~twinsFound & ~done & NextObject() DO
               twinsFound := SomeCheck();
               (*IF ~twinsFound & AccessCheck() THEN done := DoSomething(flag) END;*)
               done := ~twinsFound & AccessCheck() & DoSomething(flag)
            END
         END
      END
   END
END CheckAll;


P.S. Не проверял.

Автор:  Александр Ильин [ Воскресенье, 01 Май, 2011 06:40 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Valery Solovey писал(а):
Стало больше, чем было.
Понятно, что станет больше. Это нормально. Здесь проблема как раз в чрезмерной оптимизации. По-хорошему, к такому коду надо комментарий на полстраницы, а тут ни строчки не было.

DoSomething ничего в последовательность не добавляет.

Автор:  Роман М. [ Четверг, 05 Май, 2011 09:48 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

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

Автор:  Valery Solovey [ Четверг, 05 Май, 2011 12:47 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

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

Здесь цикл завёрнут на вызов NextObject: вместо трёх идущих друг за другом действий, каждое из которых обращается к последовательности (выполняет независимый вызов следующего объекта), было объявлено только одно место вызова NextObject, а всё остальное построили вокруг этой операции. То ли решили таким образом соптимизировать что-то, то ли боялись, что при написании кода забудут своевременно добавить вызов процедуры.

Автор:  Александр Ильин [ Суббота, 02 Февраль, 2019 03:22 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Перечитывал ветку, заметил в этом посте, что финальный вариант кода можно ещё немного сократить. Для начала напомню, с чего всё началось.
Код:
LOOP
   ok := Proc1 ();
   IF ~ok THEN
      EXIT
   END;
   SomeCode ();
   ok := Proc2 ();
   IF ok THEN
      EXIT
   END;
END;

После моих сокращений получилось вот так:
Код:
ok := TRUE;
REPEAT
   IF Proc1 () THEN
      SomeCode ();
   ELSE
      ok := FALSE;
   END;
UNTIL ~ok OR Proc2 ();

А можно было ещё чуть проще. Убираем присвоения констант и ветку ELSE:
Код:
REPEAT
   ok := Proc1 ();
   IF ok THEN
      SomeCode ();
   END;
UNTIL ~ok OR Proc2 ();

Автор:  Comdiv [ Суббота, 02 Февраль, 2019 13:59 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Код:
ok := FALSE;
WHILE ~ok & Proc1() DO
   SomeCode ();
   ok := Proc2 ()
END

Автор:  Peter Almazov [ Суббота, 02 Февраль, 2019 20:35 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Хорошие примеры бестолковых попыток сложить мозаику.
Выносить Proc() в условие цикла - это "5". Теперь попробуйте объяснить кому-нибудь, что делает этот цикл.
Осмысленный вариант:
Код:
ok := Proc1();
WHILE ok DO
   SomeCode ();
   ok := ~Proc2 () & Proc1();
END

Автор:  Comdiv [ Суббота, 02 Февраль, 2019 22:07 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

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

Автор:  Peter Almazov [ Суббота, 02 Февраль, 2019 22:37 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Формально вы правы, я не лез так глубоко, где было это требование. Но оно само по себе является таким же бредом, как и сами представленные здесь потуги.
Тут надо в консерватории править.

Страница 4 из 5 Часовой пояс: UTC + 3 часа
Powered by phpBB® Forum Software © phpBB Group
https://www.phpbb.com/