循环-有用的替代控制结构?

有时当我编程时,我发现某些特定的控制结构对我非常有用,但在我的编程语言中不直接可用。 我认为我最常见的愿望是“一会儿分裂”(我不知道该怎么称呼):

{
    foo();
} split_while( condition ) {
    bar();
}

该代码的语义是始终运行foo(),然后检查条件。 如果为true,则运行jne,然后返回第一个块(因此再次运行if,依此类推)。 感谢reddit用户zxqdms的评论,我了解到Donald E. Knuth在他的论文“使用jne语句进行结构化编程”(请参阅第279页)中写了关于这种结构的文章。

您认为哪些替代控制结构是组织计算的有用方法?

我的目标是为自己和其他人提供有关代码构造的新思路,以改善分块和推理。

注意:我不是在问如何通过使用jneif / goto,Lisp宏,延续,monad,组合器,夸克或其他方法来概括所有可能的控制结构。 我在问什么专业对描述代码有用。

28个解决方案
20 votes

相当普遍的一种是无限循环。 我想这样写:

forever {
  // ...
}
Jordão answered 2020-07-27T08:00:02Z
20 votes

有时,我需要一个带有索引的foreach循环。 可以这样写:

foreach (index i) (var item in list) {
  // ...
}

(我并不特别喜欢这种语法,但是您知道了)

Jordão answered 2020-07-27T08:00:27Z
18 votes

与其他循环:

while (condition) {
  // ...
}
else {
  // the else runs if the loop didn't run
}
Jordão answered 2020-07-27T08:00:47Z
18 votes

大多数语言都具有内置功能来覆盖常见情况,但是“ fencepost”循环始终是一件繁琐的事情:您希望在每次迭代中执行某些操作以及在每次迭代之间执行其他操作的循环。 例如,使用分隔符连接字符串:

string result = "";
for (int i = 0; i < items.Count; i++) {
    result += items[i];
    if (i < items.Count - 1) result += ", "; // This is gross.
    // What if I can't access items by index?
    // I have off-by-one errors *every* time I do this.
}

我知道褶皱可以解决这种情况,但是有时您需要一些必要的东西。 如果可以,这将很酷:

string result = "";
foreach (var item in items) {
    result += item;
} between {
    result += ", ";
}
munificent answered 2020-07-27T08:01:12Z
14 votes
{
    foo();
} split_while( condition ) {
    bar();
}

您可以使用常规break轻松完成此操作:

while (true) {
    foo();
    if (!condition) break;
    bar();
}

我现在经常这样做,因为我克服了break的非理性厌恶。

munificent answered 2020-07-27T08:01:36Z
13 votes

如果您查看Haskell,尽管各种控件结构都有特殊的语法,但是控件流通常按类型捕获。 这种控件类型中最常见的一种是Monad,箭头和应用函子。 因此,如果您想要一种特殊类型的控制流,它通常是某种高阶函数,您可以自己编写它或在Haskells软件包数据库(Hackage)中找到一个很大的函数。

此类函数通常在Control名称空间中,您可以在其中找到用于并行执行与错误处理的模块。 通常在过程语言中找到的许多控制结构在Control.Monad中具有功能对等物,其中包括循环和if语句。 if-else是haskell中的关键字表达式,如果没有else则在表达式中没有意义,而在monad中则是完美的含义,因此不带有else的if语句由2984653509209209097216和map函数捕获。

另一个常见的情况是在更一般的上下文中执行列表操作。 功能语言非常喜欢fold和特殊版本,例如2984653509209097097217和2984653509209097097218。如果您使用monad,则自然会扩展fold。 这称为foldM,因此,您还可以想到任何特殊版本的折页的扩展名,例如mapMfilterM

HaskellElephant answered 2020-07-27T08:02:07Z
11 votes

这只是一个一般性的想法和语法:

if (cond)
   //do something
else (cond)
   //do something
also (cond)
   //do something
else
   //do something
end

还始终评估条件。 ELSE照常工作。

它也适用于情况。 也许这是消除break语句的好方法:

case (exp)
   also (const)
      //do something
   else (const)
      //do something
   also (const)
      //do something
   else
      //do something
end

可以理解为:

switch (exp)
   case (const)
      //do something
   case (const)
      //do something
      break
   case (const)
      //do something
   default
      //do something
end

我不知道这是否有用或简单易懂,但这只是一个例子。

Maniero answered 2020-07-27T08:02:45Z
10 votes

使用(lisp样式)宏,尾部调用和延续,所有这些都是古朴的。

使用宏,如果标准控制流结构不足以用于给定的应用程序,则程序员可以编写自己的宏(以及更多)。 只需一个简单的宏即可实现您作为示例给出的构造。

通过尾调用,可以将复杂的控制流模式(例如实现状态机)分解为功能。

连续是强大的控制流原语(try / catch是它们的受限版本)。 结合尾调用和宏,复杂的控制流模式(回溯,解析等)变得简单明了。 另外,它们在Web编程中很有用,因为您可以反转控件的反转。 您可以使用一个功能,要求用户进行一些输入,进行一些处理,要求用户进行更多输入等。

为了解释Scheme标准,您应该设法消除使其他功能显得必要的限制,而不是在您的语言上叠加更多的功能。

Brian answered 2020-07-27T08:03:24Z
8 votes

如果不:

unless (condition) {
  // ...
}

而没有:

until (condition) {
  // ...
}
Jordão answered 2020-07-27T08:03:50Z
8 votes

标签循环是我发现自己有时会在主流语言中缺少的东西。 例如。,

int i, j;
for outer ( i = 0; i < M; ++i )
    for ( j = 0; j < N; ++j )
        if ( l1[ i ] == l2[ j ] )
           break outer;

是的,我通常可以用redo进行模拟,但是与continue等效的方法将要求您将增量移动到标签后的循环主体的末端,这会损害可读性。 您也可以通过在内部循环中设置一个标志并在外部循环的每次迭代中对其进行检查来实现此目的,但是它总是显得笨拙。

(奖金:我有时希望将redocontinuebreak一起使用。它将返回循环的开始而不评估增量。)

Boojum answered 2020-07-27T08:04:20Z
8 votes

我建议使用“ then”运算符。 它在第一次迭代时返回左操作数,而在所有其他迭代中返回右操作数:

var result = "";
foreach (var item in items) {
    result += "" then ", ";
    result += item;
}

在第一个迭代中,它在结果中添加“”,在所有其他结果中添加“,”,因此您将获得一个字符串,其中包含用逗号分隔的每个项目。

helium answered 2020-07-27T08:04:44Z
8 votes
if (cond)
   //do something
else (cond)
   //do something
else (cond)
   //do something
first
   //do something
then
   //do something
else (cond)
   //do something
else
   //do something
end

如果三个条件中的任何一个被评估为true,则FIRST和THEN块运行。 FIRST块在条件块之前运行,然后THEN在条件块运行之后运行。

在FIRST和THEN语句之后的ELSE条件或最终写操作与这些块无关。

它可以读为:

if (cond)
   first()
   //do something
   then()
else (cond)
   first()
   //do something
   then()
else (cond)
   first()
   //do something
   then()
else (cond)
   //do something
else
   //do something
end


function first()
   //do something
return
function then()
   //do something
return

这些功能只是一种阅读形式。 他们不会创建范围。 它更像是来自Basic的gosub / return。

讨论的实用性和可读性。

Maniero answered 2020-07-27T08:05:22Z
6 votes

怎么样

alternate {
    statement 1,
    statement 2,
    [statement 3,...]
}

用于在每个连续遍历中循环遍历可用语句。

编辑:琐碎的例子

table_row_color = alternate(RED, GREEN, BLUE);

player_color = alternate(color_list); // cycles through list items

alternate(
    led_on(),
    led_off()
);

编辑2:在上面的第三个示例中,语法看起来像个函数,可能有点令人困惑。 实际上,每次通过仅评估一条语句,而不是两次。 更好的语法可能类似于

alternate {
    led_on();
}
then {
    led_off();
}

或类似的东西。 但是,我喜欢这样的想法:如果需要,可以使用调用结果的方式(如颜色示例中所示)。

Peter Gibson answered 2020-07-27T08:06:00Z
5 votes

D的范围卫士是一种非常有用的控制结构,很少见。

helium answered 2020-07-27T08:06:20Z
5 votes

我想我应该提一下CityScript(CityDesk的脚本语言),它具有一些非常漂亮的循环结构。

从帮助文件:

{$ forEach n var in (condition) sort-order $}
... text which appears for each item ....
{$ between $}
.. text which appears between each two items ....
{$ odd $}
.. text which appears for every other item, including the first ....
{$ even $}
.. text which appears for every other item, starting with the second ....
{$ else $}
.. text which appears if there are no items matching condition ....
{$ before $}
..text which appears before the loop, only if there are items matching condition
{$ after $}
..text which appears after the loop, only of there are items matching condition
{$ next $}
Danko Durbić answered 2020-07-27T08:06:45Z
4 votes

还要注意,取决于单子,许多控制结构在单子上下文中会获得新的含义-在Haskell中查看mapM,filterM,whileM,序列等。

jkff answered 2020-07-27T08:07:06Z
4 votes

util.control-忽略某些代码块中发生的异常。

try {
  foo()
} catch {
  case ex: SomeException => /* ignore */
  case ex: SomeOtherException => /* ignore */
}

使用util.control控制构造,您可以将其编写得更简洁明了:

ignoring(classOf[SomeException], classOf[SomeOtherException]) {
  foo()
}

[Scala在其标准库中以util.control包提供了此(以及许多其他异常处理控制构造)。 ]

missingfaktor answered 2020-07-27T08:07:35Z
4 votes

我想查看用于对输出进行分组的关键字。 代替这个:

        int lastValue = 0;

        foreach (var val in dataSource)
        {
            if (lastValue != val.CustomerID)
            {                    
                WriteFooter(lastValue);
                WriteHeader(val);
                lastValue = val.CustomerID;
            }
            WriteRow(val);
        }
        if (lastValue != 0)
        {
            WriteFooter(lastValue);
        }

这样的事情怎么样:

        foreach(var val in dataSource)
        groupon(val.CustomerID)
        {            
            startgroup
            {
                WriteHeader(val);
            }
            endgroup
            {
                WriteFooter(val)
            }
        }
        each
        {
            WriteRow(val);
        }

如果您拥有一个不错的平台,控件和/或报告格式,则无需编写此代码。 但是令人惊讶的是,我发现自己经常这样做。 最烦人的部分是最后一次迭代后的页脚-在实际示例中,如果不复制代码,很难做到这一点。

Paul Keister answered 2020-07-27T08:08:04Z
4 votes

替代的东西

bool found = false;
for (int i = 0; i < N; i++) {
  if (hasProperty(A[i])) {
    found = true;
    DoSomething(A[i]);
    break;
  }
}
if (!found) {
  ...
}

喜欢

for (int i = 0; i < N; i++) {
  if (hasProperty(A[i])) {
    DoSomething(A[i]);
    break;
  }
} ifnotinterrupted {
  ...
}

我一直认为,除了在循环主体的最后一次(常规)执行之后执行某些操作之外,引入标记还必须有更好的方法。 可以检查!(i < N),但是i在循环后超出范围。

Meinersbur answered 2020-07-27T08:08:33Z
3 votes

这是一个笑话,但是您可以得到想要的行为,如下所示:

#include <iostream>
#include <cstdlib>

int main (int argc, char *argv[])
{
  int N = std::strtol(argv[1], 0, 10); // Danger!
  int state = 0;
  switch (state%2) // Similar to Duff's device.
  {
    do {
      case 1: std::cout << (2*state) << " B" << std::endl;
      case 0: std::cout << (2*state+1) << " A" << std::endl; ++state;
    } while (state <= N);
      default: break;
  }

  return 0;
}

ps。 格式化它有点困难,我对此绝对不满意。 但是,emacs甚至更糟。 任何人都想尝试vim吗?

thechao answered 2020-07-27T08:08:58Z
3 votes

如果您主要使用非功能语言,则Python中的生成器确实是新颖的。 更一般而言:延续,例程,惰性列表。

Paul Harrison answered 2020-07-27T08:09:18Z
3 votes

这可能不算在内,但是在Python中,我很沮丧,没有do循环。

安托(Anto)确保我不会对此答案表示支持,但我会在任何缺乏goto语言的时间内对自己使用的任何语言感到恼火。

Tom Ritter answered 2020-07-27T08:09:42Z
3 votes
for int i := 0 [down]to UpperBound() [step 2]

每种C衍生语言都缺少。

请在投票或发表评论之前考虑:
这对于for (int i = 0; i <= UpperBound(); i++)来说不是多余的,它具有不同的语义:

  1. UpperBound() == MAX_INT仅被评估一次

  2. 情况UpperBound() == MAX_INT不产生无限循环

Meinersbur answered 2020-07-27T08:10:20Z
2 votes

这类似于@Paul Keister的回复。

(喃喃自语)几年前,我正在开发的应用程序具有所谓的控制中断处理的多种变体,即将排序后的数据行分为具有页眉和页脚的组和子组的所有逻辑。 由于应用程序是用LISP编写的,因此我们已经在名为WITH-CONTROL-BREAKS的宏中捕获了常见的习惯用法。 如果我要将这种语法转换为越来越流行的弯曲形式,它可能看起来像这样:

withControlBreaks (x, y, z : readSortedRecords()) {
  first (x) :     { emitHeader(x); subcount = 0; }
  first (x, y) :  { emitSubheader(x, y); zTotal = 0; }
  all (x, y, z) : { emitDetail(x, y, z); ztotal += z; }
  last (x, y) :   { emitSubfooter(x, y, zTotal); ++subCount; }
  last (x) :      { emitFooter(x, subcount); }
}

在这个现代时代,随着SQL,XQuery,LINQ等的广泛使用,这种需求似乎没有以前那么多了。 但是我不时希望自己拥有这种控制结构。

WReach answered 2020-07-27T08:10:50Z
2 votes
foo();

while(condition)
{
   bar();
   foo();
}
Alex answered 2020-07-27T08:11:05Z
1 votes

PL / I样式的“ for”循环范围如何? VB等效为:

' Counts 1, 2, ... 49, 50, 23, 999, 998, ..., 991, 990
  For I = 1 to 50, 23, 999 to 990 Step -1

我可以看到的最常见用法是让循环运行一个索引列表,然后再添加一个。 顺便说一句,For-Each用法也很方便:

' Bar1, Bar2, Bar3 are an IEnum(Wazoo); Boz is a Wazoo
  For Each Foo as Wazoo in Bar1, Bar2, Enumerable.One(Boz), Bar3

这将在Bar1中的所有项目,Bar2,Boz和Bar3中的所有项目上运行循环。 Linq可能会毫不费力地允许这样做,但是内在语言支持可能会更有效率。

supercat answered 2020-07-27T08:11:35Z
1 votes

在许多语言中没有的控件结构之一是case-in类型的结构。 与开关类型结构类似,它允许您以整齐的格式列出可能的选项,但与第一个为true的匹配(而不是与输入匹配的第一个)。 这样的LISP(确实有):

(cond
   ((evenp a) a)        ;if a is even return a
   ((> a 7) (/ a 2))    ;else if a is bigger than 7 return a/2
   ((< a 5) (- a 1))    ;else if a is smaller than 5 return a-1
   (t 17))              ;else return 17

或者,对于那些更喜欢C格式的用户

cond 
    (a % 2 == 0): 
        a;     break;
    (a > 7):
        a / 2; break;
    (a < 5):
        a - 1; break;
    default:
        17;    break;

从本质上讲,它是if/elseif/elseif/else构造比开关更准确的表示,并且可以以清晰易懂的方式极其熟练地表达该逻辑。

RHSeeger answered 2020-07-27T08:12:04Z
0 votes

如何遍历列表中的移动窗口(由n个元素代替1个元素)进行迭代?我认为这与@munificent的答案是切线相关的。

就像是

#python
#sum of adjacent elements
for x,y in pairs(list):
    print x + y

def pairs(l):              
    i=0                    
    while i < len(l)-1:    
        yield (l[i],l[i+1])
        i+=1               

它对某些类型的事物很有用。 不要误会我的意思,这很容易实现,但是我认为很多人会尝试在有更具体/描述性的工具时使用forwhile循环。

Ehtesh Choudhury answered 2020-07-27T08:12:34Z
translate from https://stackoverflow.com:/questions/4293744/useful-alternative-control-structures