一旦在循环中满足要求,就停止检查条件

时间:2017-03-02 10:21:11

标签: c# performance visual-studio-2015

/ * 编辑:请注意,此代码是一个示例,我的问题通常与(C#)编码有关,因为我经常发现自己处于循环访问的情况,并且根本不需要检查if - 声明。 * /

我怀疑这是否可行,但我想确切地知道。

我有一个作业,我必须读取文件并使用变量。 这些项目位于一行中的逗号文件中,以逗号分隔。我得到的文件的项目间隔为\ t,有时还有空格。 一个例子:" \ t波音737-800" (注意两个空格)

这是我为摆脱标签(和空格)所做的功能:

private string RemoveTab (string text)
{
    string tempText = "";
    foreach (char c in text)
    {
        if (c == ' ' && tempText == "")
        {
            //nothing has to be done
        }
        else if (c != '\t')
        {
            tempText += c;
        }
    }
    return tempText;
}

现在回答真正的问题: 一旦跳过所有选项卡和空格,就不再需要检查任何内容。尤其不是第一个if语句。有没有办法可以禁用第一个if语句?

在这个例子中,我怀疑它对性能有任何重大影响。嗯,我知道它没有。但是由于我的i5-4570在Overwatch和Battlefield 4等游戏中遇到了麻烦,我希望尽可能提高效率。我认为在没有可能满足要求的情况下检查很多东西可能会对CPU密集型应用程序中的CPU使用率产生一些影响。

4 个答案:

答案 0 :(得分:3)

不要重新发明轮子。要删除起始和尾随空格,请使用string.Trim(' ')(或等效的string.TrimStartstring.TrimEnd(如果适用)。

其余的,可以使用LINQ完成:

var noTabsString = new string(text.Trim(' ').Where(c => c != '\t').ToArray());

使用字符串连接来构建任意长字符串通常不是一个好主意,您应该在任何情况下使用StringBuilder

但是,我还不能回避你的方法;利用string实现IEnumerable<char>的事实,并在您过滤掉所有不需要的字符后,实例化一个string

更新回答您的具体问题,如果不再需要,则无法在循环内停用if条件。你唯一可以做的就是将循环分成两部分,但我真的不建议这样做,除非你检查的条件被证明是一个不可接受的瓶颈。

一种方法是使用以下模式:

using (var enumerator = sequence.GetEnumerator())
{
     while (enumerator.MoveNext())
     {
         if (veryExpensiveCheck) { ... }
         else if (cheapCheck)
         {
             ...
             break; //veryExpensiveCheck not needed anymore.
         }
     }

     while (enumerator.MoveNext())
     {
         if (cheapCheck) { ... }
     }
}

可读性有所下降,所以,除非真的有必要,否则不要这样做,并且你有经验数据证明第一种选择是不可接受的。

答案 1 :(得分:1)

  

一旦跳过所有选项卡和空格,就不再需要检查任何内容了。

值得注意的是,您所拥有的代码仅在初始部分中跳过空格,但在任何地方都会标记。目前尚不清楚您是否想要这里或您在此描述的内容。我将假设你的代码在结果方面是正确的。

这是一个很常见的模式,你想为循环的一部分做一些事情,而其余部分做其他事情。通过从foreach中删除语法糖很容易做到:

foreach (char c in text)
{
    if (c == ' ' && tempText == "")
    {
        //nothing has to be done
    }
    else if (c != '\t')
    {
        tempText += c;
    }
}

变为:

using(var en = text.GetEnumerator())
{
    while (en.MoveNext())
    {
        char c = en.Current;
        if (c == ' ' && tempText == "")
        {
            //nothing has to be done
        }
        else if (c != '\t')
        {
            tempText += c;
        }
    }
}

现在我们已经分解了foreach,我们可以更轻松地进行更改。

using(var en = text.GetEnumerator())
{
    while (en.MoveNext())
    {
        char c = en.Current;
        if (c != ' ')
        {
            do
            {
                if (c != '\t')
                {
                    tempText += c;
                }
            } while (en.MoveNext());
            return tempText;
        }
    }
    return ""; // only hit if text was all-spaces
}

现在我们只是检查c是否是空格,直到它第一次找到非空格并为枚举的其余部分执行不同类型的循环。 (如果您只想在开始时跳过制表符,则将其从内循环中取出并进行初始测试c != ' ' && c != '\t')。

(这值得多少是另一个问题。我发现类似于此的更改可以产生明显的改进,但是除非输入字符串非常大或代码经常受到影响,否则它不会重要的是 - 对于在更广泛的应用背景下本身并不明显的事物的明显改变,并不是在更广泛的背景下的明显改变。)

这是适用于任何foreach的一般情况。虽然我们可以做两件事。

一个是我们可以删除using因为我们知道字符串的枚举器在Dispose()中没有做任何事情。只有你真的确定你可以。

另一个是我们可以从foreach更改为迭代。考虑一下我们也可以将原始逻辑编写为:

for(int i = 0; i < text.Length; ++i)
{
    char c = text[i]; // We could also have done char[c] arr = text.Chars and used that. The compiler does the equivalent.
    if (c == ' ' && tempText == "")
    {
        //nothing has to be done
    }
    else if (c != '\t')
    {
        tempText += c;
    }
}

从这个起点我们可以做到:

for(int i = 0; i < text.Length; ++i)
{
    char c = text[i];
    if (c != ' ')
    {
        do
        {
            c = text[i];
            if (c != '\t')
            {
                tempText += c;
            }
        } while(++i < text.Length);
        return tempText;
    }
    return "";
}

或者:

int i = 0
while(i < text.Length)
{
    char c = text[i];
    if (c != ' ')
    {
        break;
    }
    ++i;
}
while(i < text.Length)
{
    char c = text[i];
    if (c != '\t')
    {
        tempText += c;
    }
    ++i;
}

这两种模式都使用索引而不是foreach来执行两个半循环。很多人发现这个更简单,在某些情况下性能更高(特别是对于数组和字符串[请注意,编译器将数组或字符串类型的变量上的foreach转换为规范MoveNext()/Current组合但是进入幕后的索引for(;;),你不想失去那个优化])虽然不是一般的(它不适用于无法编入索引的IEnumerable<char>

答案 2 :(得分:0)

您可以采用首先如果应该始终为假的方式进行条件检查。

if (!(c == ' ' && tempText == "") && (c != '\t')){
     tempText += c;
}

希望这有帮助。

答案 3 :(得分:0)

您可以考虑用一个Action<>对象替换循环体,该对象封装了所需的功能,并且可以在运行时更改。

private string RemoveTab(string text)
{
    string tempText = String.Empty;

    // An Action is an object that encapsulates a method that does
    // not return a value. If you need to return something, use a Func<>.

    // Create an Action<> that will be used in the loop until the initial
    // condition has ceased.  This Action<> will replace itself with the
    // subsequent Action<>.
    Action<char> Process = new Action<char>(c =>
    {
        if ((c != ' ') && (c != '\t'))
        {
            tempText += c;
            // Replace the Action with new functionality for subsequent
            // iterations of the loop.
            Process = new Action<char>(c1 =>
            {
                if (c1 != '\t')
                {
                    tempText += c1;
                }
            });
        }
    });

    // Now the loop will use the Process Action<>, which will change
    // itself to new behaviour once the initial condition no longer holds.
    foreach (char c in text)
    {
        Process(c);
    }
    return tempText;
}

我不推荐这种方法来处理一个简单的问题,例如你在问题中使用的问题,但对于更复杂的情况,可能值得考虑。但是,请始终考虑将来可能需要修改代码,如果您太聪明,可能会感到非常高兴。