Трудности перевода

    Микрософт сказал, что прекращает развитие языка Visual Basic. Значит, пришла пора в Ситилинк переписывать код.

    Вне зависимости от этого эпохального решения, самому потребовалось перевести один пет-проект с VBA на C#. Я обычно все прототипирую на MS Access, и потом уже код переезжает на более серьезную базу данных (читай: SQL Server) и другой язык программирования.

    И вот циклы.
    В Basic и Pascal есть процедура цикла
     
    for i = Lower to Upper step Step
                  …
    next

    Она универсальна в том смысле, что может ехать как снизу вверх, так и сверху вниз. Ее можно использовать и если Step>0, и если Step<0.

    А как записать то же самое в нотации C/C++/C#/JavaScript? Оказывается, что ихний цикл for вовсе не эквивалент Basic-овскому:

      for (i = Lower; i < Upper+1; i+=Step) { … }

    Как модифицировать это, чтобы работало и для случая, когда Step<0? Приходит в голову такое, не самое изящное, решение:

    for (i = Lower; Sign(Step)*i <= Sign(Step)*Upper; i+=Step) { … }

    Он, как и изначальный for… next, зациклится при Step =0.

    Коллеги предложили еще вариант:

    for ( i=Lower; Step>0? i<=Upper: i>=Upper; i+=Step)  {… }

    На самом деле, поскольку нам надо, чтобы последним значением переменной цикла было Upper, и она тоже поучаствовала в теле цикла, это можно запрограммировать как цикл с постусловием:

    i = Lower — Step;
    do
    i += Step;

    while (i != Upper)

    но этот код не годится, если Step не делит нацело диапазон Lower … Upper

    Оказывается, цикл с использованием range на Python также не позволяет двигаться от большего значения к меньшему, range(b, a) при b>a задает просто пустой диапазон.

    Какое решение предложите вы?

    Комментарии 24

      +1
      А как же вариант для C# с Linq?
      // По возрастанию
      foreach (int value in Enumerable.Range(1, 10))
      {
          ...
      }
      // По обыванию
      foreach (int value in Enumerable.Range(1, 10).Reverse())
      {
          ...
      }
      

      И для Step, наверное, можно .Where() добавить
        0
        Я согласен, что это не совсем тот цикл, но есть ли смысл привязываться к for, если есть другие варианты?
          0
          Ну я подумывал о чем-то вроде автоматического перекодировщика
          0
          Нужен универсальный код, который годится и для положительного, и для отрицательного шага!
            0

            И часто у вас попадаются циклы, которые идут в обе стороны?

          0
          Чем плохо решение оставить код на VBA?
          Если бы действительно требовалось что-то кроме VBA, то изначально бы подключался Python, JScript и т.п. в 5 строк VBA.
            –1
            Тем, что это не решение)
            +4
            статья из раздела «хабр, который мы заслужили».
              0
              наверное эквивалент for все-таки будет не for(i=lower;i<upper+1;i+=step);, а что-то вроде for(i=start;i!=end;i+=step);
                0
                если step не укладывается целое число раз в диапазон start..end, то из цикла никогда не выйдем!
              0
              ЕМНИП, в циклах Pascal нет step. Там используются выражения for to и for downto для циклов с увеличением и уменьшением счётчика на единицу, соответственно.
              А заменить можно, например, так:
              for (i = Lower, j = 0; j <= (Upper - Lower) / Step; i += Step, j++)
                0
                Кстати да, в Pascal так и было, это МИП)) Ваш код не сработает, если Upper<Lower
                  +1
                  Если Upper < Lower, то и Step должен быть отрицательный. Иначе и в VB цикл FOR не сработает.
                +7
                Я бы предложил почитать учебник. Без вариантов.
                  +6
                  Оказывается, что ихний цикл

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

                    –3
                    Не стойте на пути у прогресса)
                    +1
                    for(var i = Lower; Math.Min(Lower, Upper) <= i && i <= Math.Max(Lower, Upper); i += Step)
                    {
                        ...
                    }
                      0

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

                        +3
                        for i = Lower to Upper step Step
                        Она универсальна в том смысле, что может ехать как снизу вверх, так и сверху вниз. Ее можно использовать и если Step>0, и если Step<0.

                        Но ведь если Step отрицательный, надо и Lower c Upper местами поменять, а не просто задать значение Step. А вы почему-то пытаетесь на других языках привести к форме, когда можно только Step поменять.


                        Пример из документации


                        For indexB = 20 To 1 Step -3
                          0

                          Для Питона надо ```python
                          range(b, a, -1)


                          > Оказывается, цикл с использованием range на Python также не позволяет двигаться от большего значения к меньшему, range(b, a) при b>a задает просто пустой диапазон.
                            +1

                            1) В питоне range(start, end, step) прекрасно работает с отрицательным шагом.
                            2) В бейсике цикл не lower to upper, а тоже start to end. Не вводите себя и других в заблуждение.


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


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


                            • for i = start to finish
                              = на питоне range(start, finish+1)
                              = на сях for(i = start; i <= finish; ++i)
                              Это обычный перебор индексов.
                            • for i = finish to start step -1
                              = на питоне range(finish, start-1, -1)
                              = на сях for(i = finish; i >= start; --i)
                              Это перебор в обратном направлении
                            • Перебор в прямом или обратном направлении в зависимости от флага
                            • Перебор в прямом направлении с произвольным шагом
                            • Перебор с произвольным шагом в любом направлении

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


                            В общем виде, на питоне это всего лишь range(start, finish + step, step).
                            На сях — предлагаю не вертеть арифметические формулы со знаком, а написать БУКВАЛЬНО.


                            for(
                                i = start;
                                (step > 0) ? i <= finish : (step < 0) ? i >= finish : false;
                                i += step
                            )

                            Да, кстати. На сях вас ещё ждут весёлые игры с беззнаковыми целыми. После бейсика это будет больно.


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


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


                            for x in array:
                                print(x)
                            
                            for i, x in enumerate(array):
                                print(i, '=>', x)
                            
                            # вместо
                            for i in range(len(array)):
                                print(i, '=>', array[i])

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

                              0

                              Вот код, который вы ищете, для случая когда Step отрицательный.


                              for (i = Upper; i >= Lower; i += Step)

                              Хотя в большинстве случаев Step положительный, и делают вот так.


                              for (i = Upper; i >= Lower; i -= Step)
                                0
                                Если уж хочется по простому переписать, то вот вам вариант:

                                public sealed class Loop
                                {
                                    public static void ForNext(int start, int end, int step, Action<int> action) 
                                    {
                                        int ind = start;
                                
                                        while(step > 0 ? ind <= end : ind >= end)
                                        {
                                            action(ind);
                                            ind += step;
                                        }
                                            
                                    }
                                }
                                class Program
                                {
                                    static void Main(string[] args)
                                    {
                                        Console.WriteLine("begin up loop");
                                
                                        var s = "=>";
                                        Loop.ForNext(1, 6, 2, (n) => Console.WriteLine(s+n));
                                
                                        Console.WriteLine("begin down loop");
                                
                                        s = "<=";
                                        Loop.ForNext(5, -2, -2, (n) => Console.WriteLine(s+n));
                                
                                        Console.ReadLine();
                                
                                    }
                                }
                                

                                Выведет:
                                begin up loop
                                =>1
                                =>3
                                =>5
                                begin down loop
                                <=5
                                <=3
                                <=1
                                <=-1

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

                                Самое читаемое