有用的替代控制结构?

时间:2010-11-27 20:27:28

标签: loops programming-languages control-structure

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

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

此代码的语义是始终运行foo(),然后检查条件。如果为true,则运行bar(),然后返回第一个块(从而再次运行foo()等)。感谢a comment by reddit user zxqdms,我了解到Donald E. Knuth在他的论文"Structured programming with go to statements"中写到了这个结构(参见第279页)。

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

我的目标是为自己和他人提供构建代码的新方法,以改善分块和推理。

注意:我不是在询问如何概括所有可能的控制结构,无论是使用jneif / {{ 1}},Lisp宏,continuation,monads,组合子,夸克或其他任何东西。我问的是 specializations 在描述代码时有用。

28 个答案:

答案 0 :(得分:20)

一个相当常见的是无限循环。我想这样写:

forever {
  // ...
}

答案 1 :(得分:20)

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

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

(我不是特别喜欢这种语法,但你明白了这一点)

答案 2 :(得分:18)

大多数语言都有内置函数来覆盖常见的情况,但是“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 += ", ";
}

答案 3 :(得分:18)

使用else循环:

while (condition) {
  // ...
}
else {
  // the else runs if the loop didn't run
}

答案 4 :(得分:14)

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

您可以使用常规while

轻松完成此操作
while (true) {
    foo();
    if (!condition) break;
    bar();
}

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

答案 5 :(得分:13)

如果你看一下Haskell,虽然各种控制结构都有特殊的语法,但控制流通常是按类型捕获的。最常见的这种控件类型是Monads,Arrows和applicative functors。因此,如果你想要一种特殊类型的控制流,它通常是某种高阶函数,你要么自己编写,要么在Haskells包数据库(Hackage)中找到一个非常大的。

此类函数通常位于Control命名空间中,您可以在其中找到用于并行执行错误处理的模块。通常在过程语言中找到的许多控制结构在Control.Monad中都有一个函数副本,其中包括循环和if语句。 if-else是haskell中的keyworded表达式,如果没有else在表达式中没有意义,但在monad中是完美意义的,那么没有else的if语句被函数when和{{捕获1}}。

另一种常见情况是在更一般的上下文中进行列表操作。函数式语言非常喜欢unless,以及foldmap等特殊版本。如果你有一个monad,那么filter就会自然延伸到它。这称为fold,因此您可以考虑使用任何特定版本的折叠,例如foldMmapM

答案 6 :(得分:10)

这只是一般概念和语法:

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

始终评估ALSO条件。 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

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

答案 7 :(得分:9)

使用(lisp风格)宏,尾部调用和延续,所有这一切都很古怪。

使用宏,如果标准控制流构造对于给定的应用程序来说是不够的,程序员可以编写自己的(以及更多)。它只需要一个简单的宏来实现你给出的结构作为例子。

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

Continuations是一个功能强大的控制流原语(try / catch是它们的受限版本)。结合尾调用和宏,复杂的控制流模式(回溯,解析等)变得直截了当。此外,它们在Web编程中很有用,因为它们可以反转控制的反转;你可以有一个功能,要求用户输入一些信息,进行一些处理,要求用户提供更多输入等。

为了解释Scheme标准,您应该尝试删除使其他功能显得必要的限制,而不是在您的语言中添加更多功能。

答案 8 :(得分:8)

标签循环是我发现自己有时从主流语言中遗漏的东西。如,

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

是的,我通常可以用goto来模拟这个,但continue的等价物要求你在标签后将增量移动到循环体的末尾,这会损害可读性。您也可以通过在内部循环中设置一个标志并在外部循环的每次迭代中检查它来执行此操作,但它总是看起来很笨拙。

(额外奖励:我有时希望redocontinuebreak一起使用。它会在不评估增量的情况下返回循环的开头。)

答案 9 :(得分:8)

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

如果3个条件中的任何一个被评估为真,则运行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。

作为讨论内容的实用性和可读性。

答案 10 :(得分:8)

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

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

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

答案 11 :(得分:8)

如果不是:

unless (condition) {
  // ...
}

而不是:

until (condition) {
  // ...
}

答案 12 :(得分:6)

怎么样

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();
}

或者那种效果。但是,我确实喜欢这样的想法,即如果需要,可以使用其结果(如颜色示例中所示)。

答案 13 :(得分:5)

我想我应该提一下 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 $}

答案 14 :(得分:5)

D'scope guards是一种非常常见的有用控制结构。

答案 15 :(得分:4)

ignoring - 忽略某个代码块中发生的异常。

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

使用ignoring控件构造,您可以更简洁,更可读地编写它:

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

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

答案 16 :(得分:4)

我想看一个用于分组输出的关键字。而不是:

        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);
        }

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

答案 17 :(得分:4)

取代

的东西
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超出范围。

答案 18 :(得分:4)

另请注意,许多控制结构在monadic上下文中具有新的含义,具体取决于特定的monad - 在Haskell中查看mapM,filterM,whileM,sequence等。

答案 19 :(得分:3)

如果你主要使用非函数语言,那么Python中的生成器真的很新颖。更一般地说:延续,共同惯例,懒惰列表。

答案 20 :(得分:3)

for int i := 0 [down]to UpperBound() [step 2]

缺少每种C语言。

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

  1. UpperBound()仅评估一次

  2. 案例UpperBound() == MAX_INT不会产生无限循环

答案 21 :(得分:3)

这有点儿玩笑,但你可以得到你想要的行为:

#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;
}

P.S。格式化这有点困难,我绝对不满意;然而,emacs更糟糕。有人在乎尝试使用vim吗?

答案 22 :(得分:3)

这可能不算数,但在Python中,我很沮丧,没有做循环。

Anto确保我没有得到这个答案的赞成票,我对我工作的任何语言都感到恼火,这段时间缺乏goto的。

答案 23 :(得分:2)

foo();

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

答案 24 :(得分:2)

这类似于@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等的普及,这种需求似乎并没有像过去那样出现。但有时候,我希望我手边有这种控制结构。

答案 25 :(得分:1)

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可能会毫不费力地允许这样做,但内在的语言支持可能会更有效率。

答案 26 :(得分:1)

许多语言中没有的控制结构之一是case-in类型结构。与开关类型结构类似,它允许您有一个整齐格式的可能选项列表,但匹配第一个是真的(而不是匹配输入的第一个)。这样的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构造更精确的表示形式,而不是一个开关,它可以非常简洁地以清晰,可读的方式表达该逻辑。

答案 27 :(得分:0)

如何通过列表迭代移动窗口(n个元素而不是1个)? 我认为这与@ munificent的answer相关。

这样的东西
#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循环