Reputation: 97
/* Edit: Please note that this code is an example, and that my question is related to (C#) coding in general because I often find myself with situations where I loop through something, and simply don't need to check for an if-statement at all. */
I doubt if this is possible at all, but I want to know for sure.
I have an assignment where I have to read a file and use the variables. The items are in a txt file in one line separated by commas. The file I got had the items spaced out by \t and sometimes with spaces. an example: "\t Boeing 737-800" (notice the two spaces)
This is the function I made to get rid of the tabs (and spaces):
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;
}
Now to the real question: Once all of the tabs and spaces have been skipped it does not need to check for anything anymore. Especially not the first if statement. Is there a way I could disable the first if-statement?
In this example I doubt it makes any significant impact on performance. Well, I know it doesn't. But since my i5-4570 has trouble with games like Overwatch and Battlefield 4 I would like to make things as efficient as possible. And I think that checking for a lot of things when there is no possibility of meeting the requirement could have some impact on CPU usage in already CPU-intensive applications.
Upvotes: 3
Views: 528
Reputation: 2320
You could look into replacing the body of the loop with an Action<>
object that encapsulates the required functionality and can be changed at run time.
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;
}
I wouldn't recommend this approach for a simple problem like the one you use as an example in your question, but for more complex scenarios it might be worth considering. However, always consider that someone may have to modify the code in the future and possibly won't be too happy if you've been too clever.
Upvotes: 0
Reputation: 113242
Once all of the tabs and spaces have been skipped it does not need to check for anything anymore.
It's worth noting that the code you have skips spaces only in the initial part, but tabs anywhere. It's not clear whether you want that or what you describe here. I'm going to assume that the code you have is correct, in terms of its results.
This is a common enough pattern where you want to do something for part of a loop and something else for the rest. It's easily done by removing the syntactic sugar from the foreach
so:
foreach (char c in text)
{
if (c == ' ' && tempText == "")
{
//nothing has to be done
}
else if (c != '\t')
{
tempText += c;
}
}
Becomes:
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;
}
}
}
Now that we've broken up the foreach
we can change it further easily enough.
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
}
Now we're only doing the check to see if c
is a space until the first time it finds a non-space and do a different sort of loop for the rest of the enumeration. (If you meant to skip tabs only at the start too then take it out of the inner loop and make the initial test c != ' ' && c != '\t'
).
(How much this is worth it is another question. I've found that changes similar to this can produce appreciable improvements considered in isolation, but unless the input string is very large or the code is hit very often it's not going to matter much — an appreciable change to something that is not itself appreciable in the context of the wider application is not an appreciable change in that wider context).
That's the general case that works for any foreach
. Here though we can do two further things.
One is that we can remove the using
as we know the enumerator for a string doesn't do anything in its Dispose()
. Only do that if you are really sure you can.
The other is that we can change from foreach
to iterating. Consider that we could also write your original logic as:
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;
}
}
From this starting point we could do:
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 "";
}
Or:
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;
}
Which are both patterns that do two half-loops but using indices rather than foreach
. Many people find this simpler and it is more performant in some cases (particularly with arrays and strings [note that the compiler turns a foreach
on a variable of array or string type not into the canonical MoveNext()/Current
combo but into an indexing for(;;)
behind the scenes, you don't want to lose that optimisation]) though not as general (it won't work on an IEnumerable<char>
that can't be indexed into.
Upvotes: 1
Reputation: 32740
Don't reinvent the wheel. To remove starting and trailing whitespaces, use string.Trim(' ')
(or the equivalent string.TrimStart
and string.TrimEnd
if applicable).
The rest, can be simply done using LINQ:
var noTabsString = new string(text.Trim(' ').Where(c => c != '\t').ToArray());
Using string concatenation to build arbitrarly long strings is normally not a good idea, you should be using in any case a StringBuilder
.
But I'd sidestep your approach alltoghether; leverage the fact that string
implements IEnumerable<char>
and instantiate one single string
once you've filtered out all unwanted characters.
UPDATE Answering your concrete question, there is no way to deactivate an if
condition inside a loop if its not needed anymore. The only thing you can do, is break the loop into two, but I'd seriously not advise doing that unless the condition you are checking is proven to be an unacceptable bottle neck.
One way to do it would be the following pattern:
using (var enumerator = sequence.GetEnumerator())
{
while (enumerator.MoveNext())
{
if (veryExpensiveCheck) { ... }
else if (cheapCheck)
{
...
break; //veryExpensiveCheck not needed anymore.
}
}
while (enumerator.MoveNext())
{
if (cheapCheck) { ... }
}
}
Readability goes down the drain, so, again, don't do this unless its really necessary and you have empirical data that proves that the first option is not acceptable.
Upvotes: 3
Reputation: 1957
You can club the conditional checks in a way that first if should always be false.
if (!(c == ' ' && tempText == "") && (c != '\t')){
tempText += c;
}
Hope this helps.
Upvotes: 0