Constructing a Small Program

When you understand the basic concepts, programming is an iterative process. You go through the steps many times, refining your code as you go. When you are starting out, you test frequently, using trial and error. The more familiar you become with the language, the more quickly you can program and the more preliminary testing you can do in your head.

The basic programming steps include:

  • Stating the problem.
  • Breaking the problem down into discrete elements.
  • Constructing the pieces.
  • Testing and fixing the pieces.
  • Assembling the pieces.
  • Testing the whole program.

Here are some things to remember when you are getting started:

  • Clearly delineate the problem before trying to solve it. If you don't, you'll end up having to make a lot of changes, throw away code, start over, or be satisfied with something that is less than what you really want.
  • Break the problem down into manageable steps instead of trying to solve the whole problem at once.
  • Test and debug sections of code as you develop. Test to see if the code does what you want it to do. Debugging is the process of finding and fixing the problems that prevent the code from doing what you want it to do.
  • Refine your data and data storage to make it easier to manipulate the data through program code. This often means structuring your tables properly.

The rest of this section traces the steps in creating a small Visual FoxPro program.

Stating the Problem

Suppose you get a lot of data from various sources. Though most of the data is strictly numeric, some data values contain dashes and spaces in addition to numbers. You should remove all the spaces and dashes from those fields and save the numeric data.

Instead of trying to remove spaces and dashes from the original data, you could formulate the goal of the program as:

Goal   Replace the existing values in a field with other values that contain everything from the original values except the spaces and dashes.

This formulation avoids the difficulty of manipulating a string of characters that keeps changing in length as you work with it.

Breaking the Problem Down

Because you have to provide specific instructions to Visual FoxPro in terms of operations, commands, and functions, you need to break the problem down into discrete steps. The most discrete task for the example problem is to look at each character in the string. Until you can look at a character individually, you can't determine whether you want to save it.

After you look at a character, you need to check to see if it is a dash or a space. At this point, you might want to refine the statement of the problem. What if you get data later that contains open and closed parentheses? What if you want to get rid of currency symbols, commas, and periods? The more generic you can make the code, the more work you can save yourself later; the whole point is to save work. Here is a formulation of the problem that works with a much greater variety of data:

Refined Goal   Replace the existing values in a field with other values that contain only the numeric characters from the original values.

With this formulation, you can now restate the problem at the character level: if the character is numeric, save the character; if the character is not numeric, move on to the next character. When you have constructed a string that contains only the numeric elements of the initial string, you can replace the first string and move on to the next record until you have gone through all the data.

To summarize, the problem breaks down into these steps:

  1. Look at each character.
  2. Decide if the character is numeric or not.
  3. If it is numeric, copy it to the second string.
  4. When you have gone through every character in the original string, replace the original string with the numeric-only string.
  5. Repeat these steps for all the records in the table.

Constructing the Pieces

When you know what you need to do, you can start to formulate the pieces in terms of Visual FoxPro commands, functions, and operators.

Since the commands and functions will be used to manipulate data, you need some test data to work with. You want the test data to resemble the actual data as closely as possible.

For this example, you can store a test string to a variable by entering the following command in the Command window:

cTest = "123-456-7 89 0"

Look at Each Character

First, you want to look at a single character in the string. For a list of functions that can be used to manipulate strings, see Character Functions.

You will find three functions that return specific sections of a string: LEFT( ), RIGHT( ), and SUBSTR( ). Of the three, SUBSTR( ) returns characters from any part of the string.

SUBSTR( ) takes three arguments or parameters: the string, the initial location in the string, and the number of characters to return from the string, starting from the initial location. To test if SUBSTR( ) is going to do what you want, type the following commands in the Command window:

? SUBSTR(cTest, 1, 1)
? SUBSTR(cTest, 3, 1)
? SUBSTR(cTest, 8, 1)

Output

1
3
-

You can see that the first, third, and eighth characters of the test string have been displayed in the main Visual FoxPro window.

To do the same thing a number of times, use a loop. Since the test string has a specific number of characters (14), you can use a FOR loop. The counter in the FOR loop is incremented each time the code in the loop is executed, so you can use the counter in the SUBSTR( ) function. You could test the looping constructs in the Command window, but at some point you'll want to save your work to build on it later. Now would be a good time to create a new program.

To create a new program

  1. Type the following command in the Command window:

    MODIFY COMMAND numonly
    
  2. In the window that opens, type the following lines of code:

    FOR nCnt = 1 TO 14
       ? SUBSTR(cTest, nCnt, 1)
    ENDFOR
    

Now that you've created a program, you can run it.

To run a program

  1. In the open program window, press CTRL+E.
  2. If a Save dialog box appears, choose OK.

When you run this program, the individual characters in the test string are printed on separate lines in the main Visual FoxPro window.

The first task has been accomplished. You can now look at each character in the string.

Decide if the Character is Numeric

After you have fetched a single character from the string, you need to know if it is a number. You can find this out using ISDIGIT( ).

Type the following commands in the Command window:

? ISDIGIT('2')
? ISDIGIT('-')
? ISDIGIT(SUBSTR(cTest, 3, 1))

Output

.T.
.F.
.T.

From this output, you can see that '2' is a number, '– ' is not a number, and the third character in cTest, 3, is a number.

If the Character is Numeric, Copy it to the Second String

Now that you can look at the characters and determine whether they are numeric, you need a variable to hold the numeric values: cNumOnly.

To create the variable, assign it an initial value, a zero-length string:

cNumOnly = ""

As the FOR loop moves through the string, it's a good idea to create another variable to temporarily hold each character from the string as it is being manipulated:

cCharacter = SUBSTR(cTest, nCnt, 1)

Tip   It's often better to store the result of a calculation, evaluation, or function to a variable. You can then manipulate the variable without having to repeat the calculation or evaluation.

The following line of code can be used each time a number is encountered to add the number to the second string:

cNumOnly = cNumOnly + cCharacter

The program so far is:

cNumOnly = ""
FOR nCnt = 1 TO 14
   cCharacter = SUBSTR(cTest, nCnt, 1)
   IF ISDIGIT(cCharacter)
      cNumOnly = cNumOnly + cCharacter
   ENDIF
ENDFOR

Testing the Pieces

If you add a few commands to the end to display the strings, and then run the program, you can see that the program works with the test string:

cNumOnly = ""
FOR nCnt = 1 TO 14
   cCharacter = SUBSTR(cTest, nCnt, 1)
   IF ISDIGIT(cCharacter)
      cNumOnly = cNumOnly + cCharacter
   ENDIF
ENDFOR
? cTest
? cNumOnly

Output

123-456-7 89 0
1234567890

The output looks correct. But if you change the test string as you are testing the pieces, you can run into problems. Type the following command in the Command window and run the program again:

cTest = "456-789 22"

The program generates an error message. The FOR loop tried to execute 14 times, but there were only 10 characters in the string. You need a way to adjust for varying string lengths. Use LEN( ) to return the number of characters in a string. If you substitute this command in the FOR loop, you'll find that the program works correctly with both test strings:

cNumOnly = ""
FOR nCnt = 1 TO LEN(cTest)
   cCharacter = SUBSTR(cTest, nCnt, 1)
   IF ISDIGIT(cCharacter)
      cNumOnly = cNumOnly + cCharacter
   ENDIF
ENDFOR
? cTest
? cNumOnly

Putting the Pieces Together

To complete the programming solution for this problem, you might want to switch to reading your data from a table. When you have selected a table to use, scan the records in it and apply your program code to a field in the table rather than to a variable.

First, you could create a temporary table containing a variety of sample strings. Such a table could contain a single character field called TestField and four or five records:

TestField Contents  
123-456-7 89 0 -9221 9220 94321 99-
456-789 22 000001 98-99-234

When you substitute the name of the field for the name of the test string, the program looks like this:

FOR nCnt = 1 TO LEN(TestField)
   cCharacter = SUBSTR(TestField, nCnt, 1)
   IF ISDIGIT(cCharacter)
      cNumOnly = cNumOnly + cCharacter
   ENDIF
ENDFOR
? TestField
? cNumOnly

You can manually adjust the record pointer by browsing the table and scrolling through it. When the record pointer is on each of the records, the program works the way you want it to. Or, you can now wrap table navigation code around the rest of your program:

SCAN
   cNumOnly = ""
   FOR nCnt = 1 TO LEN(TestField)
      cCharacter = SUBSTR(TestField, nCnt, 1)
      IF ISDIGIT(cCharacter)
         cNumOnly = cNumOnly + cCharacter
      ENDIF
   ENDFOR
? TestField
? cNumOnly
?
ENDSCAN

Output

123-456-7 89 0
1234567890

456-789 22
45678922

 -9221 9220 94321 99-
922192209432199

000001 98-99-234
0000019899234

Testing the Whole Program

Instead of printing the string at the end of the program, you want to save it in your table. Use the following line of code to do this:

REPLACE TestField WITH cNumOnly

The complete program becomes:

SCAN
   cNumOnly = ""
   FOR nCnt = 1 TO LEN(TestField)
      cCharacter = SUBSTR(TestField, nCnt, 1)
      IF ISDIGIT(cCharacter)
         cNumOnly = cNumOnly + cCharacter
      ENDIF
   ENDFOR
   REPLACE TestField WITH cNumOnly
ENDSCAN

When you have finished the complete program, you need to test it on the sample data before trying it on your real data.

Making the Program More Robust

A robust program does what you want it to, but it also anticipates and deals with possible things that could go wrong. The example program does what you want it to do, but it makes some assumptions that must be true if the program is to work:

  • A table is open in the current work area.
  • The table has a character field named TestField.

If the table isn't open in the current work area or if the table doesn't have a character field with the expected name, the program will generate an error message and fail to accomplish the task.

Program to Remove the Non-Numeric Characters from a Field for All Records

Code Comments
lFieldOK = .F.
This variable determines if the necessary conditions exist for the program to work. Initially, set the variable to false (.F.) to assume that the necessary conditions do not exist.
FOR nCnt = 1 TO FCOUNT( )
   IF FIELD(nCnt) = ;
     UPPER("TestField")
      IF TYPE("TestField") = "C"
         lFieldOK = .T.
      ENDIF
      EXIT
   ENDIF
ENDFOR
This section of code goes through every field in the current table until it finds a character field named TestField. As soon as the correct field is found, lFieldOK is set to true (.T.) and EXIT ends the loop (there is no reason to keep checking after the correct field is identified). If no field matches the criteria, lFieldOK remains false (.F.).
IF lFieldOK
The conversion section of the program is executed only if a character field named TestField is present in the currently active table.
SCAN
   cNumOnly = ""
   FOR nCnt = 1 TO LEN(TestField)
     cCharacter = ;
    SUBSTR(TestField, nCnt, 1)
     IF ISDIGIT(cCharacter)
     cNumOnly = cNumOnly + ;
     cCharacter
     ENDIF
   ENDFOR
The conversion code.
   REPLACE TestField WITH ;
  cNumOnly
ENDSCAN
 
ENDIF
End of the IF lFieldOK condition.

The most limiting feature of this program is that you can use it for only one field. If you want to remove the non-numeric characters from a field other than TestField, you have to go through the program and change every occurrence of TestField to the name of the other field.

Converting the program to a function allows you to make the code you have written more generic and more reusable, saving you work later.

See Also

Procedures and User-Defined Functions | Character Functions | Basic Programming Concepts | Creating Programs vs. Inputting Manually | Creating Programs | LEFT( ) | RIGHT( ) | SUBSTR( ) | ISDIGIT( )