6.2. While-Statements with Sequences

6.2.1. One Character Per Line

We will process many sequences or collections. At this point the only collection we have discussed is a string - a sequence of characters that we can index.

Consider the following silly function description and heading as a start:

// Print the characters of s, one per line.
static void OneCharPerLine(string s)

OneCharPerLine("bug") would print:

b
u
g

We are accessing one character at a time. We can do that with the indexing notation. Thinking concretely about the example above, we are looking to print, s[0], s[1], s[2]. If we knew we would always have three characters, we could do this with three explicit print statements, but we are looking to write a general definition for an arbitrary length string: This requires a loop. For now our only option is a while loop. We can follow our basic loop planning rubric, one step at a time: The index is changing in a simple repetitive sequence. We can call the index i. Its initial value is clearly 0. That is our initialization. We need a while loop continuation condition. For the 3-character string example, the last index above is 2. In general we want all the characters. Recall the index of the last character is the length - 1, or with our parameter s, s.Length - 1 The while loop condition needs to allow indices through s.Length - 1. We could write a condition with <= or more concisely:

while (i < s.Length) {

In the body of the loop, the main thing is to print the next character, and the next character is s[i]:

Console.WriteLine(s[i]);

We also need to remember the part to get ready for the next time through the loop. We have dealt with regular sequence of values before. We change i with:

i = i+1;

This change is so common, there is a simpler syntax:

i++;

This increases the value of the numeric variable i by 1. (The reverse is i--;) [1]

So all together:

// Print the characters of s, one per line.
static void OneCharPerLine(string s)
{
   int i = 0;
   while (i < s.Length) {
      Console.WriteLine(s[i]);
      i++;
   }
}

You can test this with example char_loop1/char_loop1.cs.

This is a very common pattern. We could do anything we want with each individual character, not just print it.

6.2.1.1. String Backwards Exercise/Example

Here is a variation:

/// Print s in reverse order; no extra newlines
static void PrintReversed(string s)

There are a few changes:

  • You do not want to go on to the next line, so use Write, not WriteLine.
  • It is still a regular sequence of character indices, but we are working backwards through the string. We have created a decreasing sequence before. Where do you start? Where do you stop? What is the condition? How do you get ready for the next time through the loop? (Remember our newest notation.)

Our code with driver is in reversed_print/reversed_print.cs.

6.2.3. IsDigits Function

Consider a variation, determining if all the characters in a string are vowels. We could work on that, but it is not very useful. Instead let us consider if all the characters are digits. This is a true-false question, so the function to determine this would return a Boolean result:

There are several ways to check if a character is a digit. We could use the Contains idiom from above, but here is another option: The integer codes for digits are sequential, and since characters are technically a kind of integer, we can compare: The character s[i] is a digit if it is in the range from '0' to '9', so the condition can be written:

'0' <= s[i] && s[i] <= '9'

Similarly the condition s[i] is not a digit, can be written negating the compound condition as in Compound Boolean Expressions:

s[i] < '0' || s[i] > '9'

If you think of going through by hand and checking, you would check through the characters sequentially and if you find a non-digit, you would want to remember that the string is not only digits.

One way to do this is have a variable holding an answer so far:

bool allDigitsSoFar = true;

Of course initially, you have not found any non-digits, so it starts off true. As you go through the string, you want to make sure that answer is changed to false if a non-digit is encountered:

if ('0' > s[i] || s[i] > '9') {
    allDigitsSoFar = false;
}

When we get all the way through the string, the answer so far is the final answer to be returned:

/// Return true if s contains one or more digits
/// and nothing else. Otherwise return false.
static bool IsDigits(string s)
{
   bool allDigitsSoFar = true;
   int i = 0;
   while (i < s.Length) {
      if (s[i] < '0' || s[i] > '9') {
         allDigitsSoFar = false;
      }
      i++;
   }
   return allDigitsSoFar;
}

Remember something to always consider: edge cases. In the description it says it is true for a string of one or more digits.

Check examples of length 1 and 0. Length 1 is fine, but it fails for the empty string, since the loop is skipped and the initial answer, true is returned.

There are many ways to fix this. We will know right up front that the answer is false if the length is 0, and we could immediately set allDigitsSoFar to false. We would need to change the initialization so it checks the length and chooses the right value for allDigitsSoFar, true or false. Since we are selecting between two values, an if statement should occur to you:

bool allDigitsSoFar;
if (s.Length > 0) {
    allDigitsSoFar = true;
}
else {
    allDigitsSoFar = false;
}

If we substitute this initialization for allDigitsSoFar, the code will satisfy the edge case, and the code will always work. Still, this code can be improved:

Examine the if statement more closely:

if the condition is true, allDigitsSoFar is true;
if the condition is false, allDigitsSoFar is false;

See the symmetry: the value assigned to allDigitsSoFar is always the value of the condition.

A much more concise and still equivalent initialization is just:

bool allDigitsSoFar = (s.Length > 0);

In more generality this conciseness comes from the fact that it is a Boolean value that you are trying to set each time, based on a Boolean condition: You do not need to do that with an if statement! You just need an assignment statement. If you use an if statement in such a situation, you being verbose and marking yourself as a novice!

It could even be slightly more concise: The precedence of assignment is very low, lower than the comparison >, so the parentheses could be omitted. We think the code is easier to read with the parentheses left in, as written above.

The whole function would be:

/// Return true if s contains one or more digits
/// and nothing else. Otherwise return false.
static Boolean IsDigits(string s)
{
   Boolean allDigitsSoFar = (s.Length > 0);
   int i = 0;
   while (i < s.Length) {
      if (s[i] < '0' || s[i] > '9') {
         allDigitsSoFar = false;
      }
      i++;
   }
   return allDigitsSoFar;
}

You can try this code in example check_digits1/check_digits1.cs.

Note that we earlier made an improvement by replacing an if-else statement generating a Boolean value by a simple Boolean assignment. In the most recent sample code, there is an if statement setting a Boolean value:

if (s[i] < '0' || s[i] > '9') {
   allDigitsSoFar = false;
}

You might be tempted to replace this if statement by a simple Boolean assignment:

allDigitsSoFar = (s[i] < '0' || s[i] > '9');  // bad!

Play computer with this change to see for yourself why it is bad, before looking at our explanation below....

The place where we originally said to use a simple Boolean assignment was replacing an if-else statement, that always set a Boolean value. In the more recent correct code for digits, we had a simple if statement, and were only setting the boolean variable to false some of the time: when we had not found a digit. The bad code sets the variable for each character in the string, so it can change an earlier false value back to true for a later digit. The final value always comes from the the last character in the string! We want the function to come up with an answer false if any character is not a digit, not just the last character. The bad code would give the wrong answer with the string “R2D2”. If you do not see that, play computer with this string and the bad code variation that sets allDigitsSoFar every time through the loop.

There is a less commonly useful way to make an assignment without if work here [2], but a much more important, improved approach follows:

The last correct code is still inefficient. If an early character in a long string is not a digit, we already know the final answer, but this code goes through and still checks all the other characters in the string! People checking by hand would stop as soon as they found a non-digit. We can do that in several ways with C#, too. Since this is a function, and we would know the final answer where we find a non-digit, the simplest thing is to use the fact that a return statement immediately terminates the function (even if in a loop).

Instead of setting a variable to false to later be returned, we can return right away, using the loop:

while (i < s.Length) {
   if (s[i] < '0' || s[i] > '9') {
      return false;
   }
   i++;
}

What if the loop terminates normally (no return from inside)? That means no non-digit was found, so if there are any characters at all, they are all digits. There are one or more digits as long as the string length is positive. Again we do not need an if-else statement to check the length and set the Boolean result. Look in the full code for the function:

/// Return true if s contains one or more digits
/// and nothing else. Otherwise return false.
static Boolean IsDigits(string s)
{
   int i = 0;
   while (i < s.Length) {
      if (s[i] < '0' || s[i] > '9') {
         return false;
      }
      i++;
   }
   return (s.Length > 0);
}

The full code with a Main testing program is in example check_digits2/check_digits2.cs.

Returning out of a loop is a good pattern to remember when you are searching for something, and you know the final answer for your function as soon as you find it.

6.2.4. Play Computer With a Loop

We have not given you a chance to play computer with a loop. Here is some simple silly code, loop_steps/loop_steps.cs, also using a sequence:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
using System;

class LoopSteps
{
   static void Main()
   {  // play computer and predict what this loop does
      string s = "abcd";
      int i = 1;
      while (i < 4) {
         Console.Write ("/" + s[i] + s[i - 1]);
         i++;
      }
      Console.WriteLine();
   }
}

Play computer, completing the table. You fill in the line numbers, carefully. The sequence is not 9, 10, 11, 12, 13!

Line i Comment
5 - Start at beginning of Main
7   set s = “abcd” (does not change)
8 1 initialize i
...    
[1]

To be complete, the statements c = c + 1; and c++; are not always equivalent.

In c++ the type of c must be numeric, but not necessarily int. It could be a smaller type, like char.

With a c of type char the c++ could not be replaced by c = c + 1, but you could use c = (char)(c + 1): The int literal 1 forces the sum expression to be an int, which must be cast back to a char to be assigned to c. Similarly with the -- operator.

[2]

The Boolean assignment did not work when allDigitsSoFar was already false, and the next character was a digit. This could be fixed with a compound Boolean expression in the assignment:

allDigitsSoFar = allDigitsSoFar  && (s[i] < '0' || s[i] > '9');

This way, once allDigitsSoFar is false, it stays false.

6.2.4.1. Duplicate Character Exercise

Create a file double_char_test.cs, and write and test a function with the documentation and heading below:

/// If two consecutive characters in s are the same, return true.
/// Return false otherwise.  Examples:
/// HasDoubleChar("bigfoot") and HasDoubleChar("aaah!") are true;
/// HasDoubleChar("treated") and HasDoubleChar("haha!") are false.
static bool HasDoubleChar(string s)

You may want to play computer on a short example - there is an easy mistake to make.