7.6
Character
String Processing
At this point, we have sufficient skills to create our own versions of the string utilities found in string.h. User-friendly interactive programming provides many opportunities to do character manipulation.
Example 7.18
Let us create a function StrCpy. Our version of the function will have no explicit return. It will place the NewString in the String. We shall pass the size of the container to prevent overflow. We shall begin with a main program which declares a ten-character string variable called Name.
-
char
Name [10];
int
Index;
|
We plan to test our StrCpy function on Name. Since it currently contains garbage, let us fill the string with known information (Perhaps A,B,C,...).
|
for (Index = 0; Index < sizeof (Name); Index ++)
Name[Index]=Index+65;
|
|
The following code fragment can be used
-
for (Index = 0; Index < sizeof (Name); Index ++)
printf ("%2d -> %c [%3d]\n",Index,Name[Index],Name[Index]);
|
to display the contents of Name.
-
0 -> A [ 65]
1 -> B [ 66]
2 -> C [ 67]
3 -> D [ 68]
4 -> E [ 69]
5 -> F [ 70]
6 -> G [ 71]
7 -> H [ 72]
8 -> I [ 73]
9 -> J [ 74]
|
We have to remember to leave room for the end of string marker. The basic logic for StrCpy is
1. Set Pos to 0
2. While the next character to copy is not the end of string marker and
there are still more characters left in the original string
2.1 Copy NewString[Pos] into String[Pos]
2.2 Increment Pos
3. Put and end-of-string marker in String.
There are many solutions to this problem. One of the solutions is
void StrCpy1 (char String[], char NewString[],long int MaxChars)
{
long int
Pos;
for (Pos = 0; NewString[Pos] && Pos < MaxChars -1 ; Pos ++)
String[Pos] = NewString[Pos];
String[Pos]=0;
}
Since the NewString contains only eight characters, there is no need to alter more than eight characters of the String. Name[9] still contains the original 'J'. Since printf and puts print only until the end-of-string marker is found, the 'J' is never printed.
If StrCpy1 were evoked with
-
StrCpy1 (Name, "Abe Linc", sizeof (Name));
for (Index = 0; Index < sizeof (Name)+3; Index ++)
printf ("%2d -> %c [%3d]\n",Index,Name[Index],Name[Index]);
|
then the following output could be generated.
-
0 -> A [ 65]
1 -> b [ 98]
2 -> e [101]
3 -> [ 32]
4 -> L [ 76]
5 -> i [105]
6 -> n [110]
7 -> k [107]
8 -> [ 0]
9 -> J [ 74]
|
Another working version is found in StrCpy2.
void StrCpy2 (char String[], char NewString[],long int MaxChars)
{
long int
Pos = 0;
while ( ( Pos < MaxChars-1 ) && ( NewString[Pos] != 0 ) )
{
String[Pos] = NewString[Pos];
Pos ++;
}
String[Pos]=0;
}
Example 7.19
Let us create a function StrLen. Our version of the function will explicitly return a long integer indicating the number of characters in the original string. Once more we shall use the main program which declares a ten-character string variable called Name.
long int StrLen1 (char String[])
{
long int
Pos,
Count;
for ( Pos = 0, Count = 0 ; String[Pos]; Pos ++, Count++);
return (Count);
}
If StrLen1 and StrCpy1 were evoked with
-
StrCpy1 (Name, "", sizeof (Name));
printf ("String [%s] has %lu characters\n",Name,StrLen1(Name));
StrCpy1 (Name, "Tex", sizeof (Name));
printf ("String [%s] has %lu characters\n",Name,StrLen1(Name));
StrCpy1 (Name, "Texas Rangers", sizeof (Name));
printf ("String [%s] has %lu characters\n",Name,StrLen1(Name));
|
then the following output could be generated.
-
String [] has 0 characters
String [Tex] has 3 characters
String [Texas Ran] has 9 characters
|
The code fragment above helps to thoroughly test both StrLen1 and StrLen1. Notice that first call passes the "Null String", sometimes called the empty string. The second call passes a string that can easily fit in a ten-character container. The third call passes a string which exceeds ten characters; note that our StrLen1 function has prevented the string overflow which can occur with the normal strcpy function. Many of the string function solutions can use pointer arithmetic; such an alternative StrLen2 function is found below.
long int StrLen2 (char *Ptr)
{
long int
Count = 0;
while ((*Ptr) != '\0')
{
Count++;
Ptr++;
}
return (Count);
}
If StrLen2 and StrCpy2 were evoked with
-
StrCpy2 (Name, "", sizeof (Name));
printf ("String [%s] has %lu characters\n",Name,StrLen2(Name));
StrCpy2 (Name, "Tex", sizeof (Name));
printf ("String [%s] has %lu characters\n",Name,StrLen2(Name));
StrCpy2 (Name, "Texas Rangers", sizeof (Name));
printf ("String [%s] has %lu characters\n",Name,StrLen2(Name));
|
then the following output could be generated.
-
String [] has 0 characters
String [Tex] has 3 characters
String [Texas Ran] has 9 characters
|
Example 7.20
There are times that we would like the user to fill a character string with a collection of integers. Let us create a function IsIntString1 which explicitly returns True if (1) the string includes only the characters '0' - '9' and (2) the string is no more than four characters in length; otherwise return False.
boolean IsIntStr1 (char String[])
{
long int
Pos = 0;
if ((strlen (String) > 4) || (String[0] == '\0')) /* Empty String Not Valid */
return (False); /* >4 Characters Not Valid */
while (String[Pos])
{
if ((String[Pos] < '0') || (String[Pos] > '9'))
return (False);
else
Pos++;
}
if (String[Pos]) /* If Last Character Examined Not The End-Of-String Marker */
return (False); /* Then The String Is Not Valid */
else
return (True);
}
If IsIntStr1 were evoked with
-
do
{
printf ("Enter an Integer String <999 to Exit> : ");
gets(NoString);
if (IsIntStr1(NoString))
printf ("--> %s - Valid Integer String\n\n",NoString);
else
printf ("--> [%s] - Not Valid Integer String\n\n",NoString);
} while (strcmp (NoString, "999") != 0);
|
then the following output could be generated.
-
Enter an Integer String <999 to Exit> :
--> [] - Not Valid Integer String
Enter an Integer String <999 to Exit> : 123l2
--> [123l2] - Not Valid Integer String
Enter an Integer String <999 to Exit> : -12
--> [-12] - Not Valid Integer String
Enter an Integer String <999 to Exit> : 12345
--> [12345] - Not Valid Integer String
Enter an Integer String <999 to Exit> : 1
--> 1 - Valid Integer String
Enter an Integer String <999 to Exit> : 32
--> 32 - Valid Integer String
Enter an Integer String <999 to Exit> : 9876
--> 9876 - Valid Integer String
Enter an Integer String <999 to Exit> : 999
--> 999 - Valid Integer String
|
The first call, above, indicates that the Null string is not a valid integer string. In the second call, the user accidentally entered an 'l' instead of a '1'; this common typing error is correctly processed with our function. The number passed in the third call, 12312, is technically an integer, but in accordance with our specification, we reject all five digit integers. This is one of many ways that our IsIntStr function can be more robust; it shall be left as an exercise. The number passed in the fourth call, 12312, is also technically an integer, but in accordance with our specification, we reject all integers that begin with a plus or minus sign. This is another way that our IsIntStr function can be more robust; it too shall be left as an exercise. The last four calls reflect correct one, two, three and four digit integers.
Example 7.21
Imagine the problems trying to match character data in a database. Suppose the original name were entered "John DOe".
-
char
Name;
printf ("Enter Name : ");
gets (Name); Enter Name : John DOe
|
Searches for "John Doe" or "JOHN DOE" or "john doe" would all prove unsuccessful.
-
printf ("Enter Sought Name : "); Enter Sought Name : John Doe
gets (SoughtName); Enter Sought Name : john doe
if (strcmp(SoughtName, Name) == 0) Enter Sought Name : JOHN DOE
|
The user might have to try many combinations, before finding John. If we had a function, UpperCase, which would convert a string to all capital letters, it would be easier for the user to find matches.
-
printf ("Enter Name : ");
gets (Name); Enter Name : John DOe
UpperCase1 (Name);
printf ("Enter Sought Name : ");
gets (SoughtName); Enter Sought Name : John Doe
UpperCase1 (SoughtName);
printf ("%s - %s\n",Name, SoughtName); JOHN DOE - JOHN DOE
if (strcmp(SoughtName, Name) == 0)
|
Let us write a subprogram, called UpperCase, which will change all lowercase letters in a string to uppercase letters.
void UpperCase1 (char String[])
{
int
Pos;
for (Pos = 0; String[Pos]; Pos++)
{
if ((String[Pos] >= 'a') && (String[Pos] <= 'z'))
String[Pos] = String[Pos] - 32;
}
}
It is important to alter only the lowercase letters! If we test the function with
-
strcpy (TempString,"123ABCabc(*&^");
puts(TempString);
UpperCase1(TempString);
puts(TempString);
|
then the following output would be generated.
-
123ABCabc(*&^
123ABCABC(*&^
|
Example 7.22
Let us write a code fragment which will continue to prompt the teacher for an integer exam score, in the range 10 - 100, until the user enters a valid choice.
The valid integer range is {-32768, -32767, ... , +32766, +32767}. We have already discussed the importance of selecting data types of the proper size. The programmer has control and can avoid errors such as the one below.
-
int
No;
No = 32768;
printf ("No = %d\n",No); No = -32768
|
The programmer has little control when the user enters an invalid integer from keyboard in response to scanf.
-
int
No;
printf ("Enter No : ");
scanf ("%d",&No); Enter No : 32768
printf ("No = %d\n",No); No = -32768
|
The programmer has even less control when the user enters non digits from keyboard in response to scanf.
-
int
No;
printf ("Enter No : ");
scanf ("%d",&No); Enter No : 32a68
printf ("No = %d\n",No); No = 32 [a68 still in buffer]
|
The stdlib.h contains a function to convert a character string to a valid integer value; this function is called atoi. With the proper include file, the code fragment
-
No = atoi ("234");
printf ("[%d]\n", No);
printf ("[%d]\n", atoi ("32767"));
printf ("[%d]\n", atoi ("32768"));
|
would produce the output below.
-
Function atoi works correctly for valid integer strings. The last call above, atoi("32768"), is not processed correctly because "32768" is not a valid integer string.
Let us now return to the task at hand. Even though our IsIntStr1 is not as robust as we would like, we shall use it in our solution.
-
do
{
Exam = -999;
printf ("Enter an Exam Score [10-100] : ");
gets(ExamString);
if (IsIntStr1(ExamString))
{
Exam = atoi(ExamString);
if (Exam > 100)
printf ("--> [%d] - Too Big!\n\n",Exam);
else if (Exam < 10)
printf ("--> [%d] - Too Small!\n\n",Exam);
}
else
printf ("--> [%s] - Not a Valid Integer String\n\n",
ExamString);
}
while ((Exam > 100) || (Exam < 10));
|
Output from the code fragment above might be :
-
Enter an Exam Score [10-100] : 5
--> [5] - Too Small!
Enter an Exam Score [10-100] : 105
--> [105] - Too Big!
Enter an Exam Score [10-100] :
--> [] - Not a Valid Integer String
Enter an Exam Score [10-100] : 5a
--> [5a] - Not a Valid Integer String
Enter an Exam Score [10-100] : a5
--> [a5] - Not a Valid Integer String
Enter an Exam Score [10-100] : 1r1
--> [1r1] - Not a Valid Integer String
Enter an Exam Score [10-100] : 95
|
All user-friendly systems
(1) read potential numeric responses in string format (NoString)
(2) verify the correctness of the string (NoString)
(3) convert the string (NoString) to numeric format (No)
(4) verify the range of the response No <= HIGH and No >= Low
(5) provide appropriate error responses
Can you imagine the amount of code necessary to process each and every input (thousands) on a large interactive system? The computer scientist would quickly begin to wonder if it were not time to modify GetAnInteger1 to simplify the coding and reduce the redundancy. The prototype for GetAnInteger1 is
boolean GetAnInteger1 (char Prompt[], int *NewInt, int Low, int High, int Sentinel);
With a more robust IsIntString function, a much more user-friendly GetAnInteger2 can be designed. This is left as an exercise.
––––––––––––––––––––––
PROGRAMMING'>FOCUS ON
PROGRAMMING
––––––––––––––––––––––
The sample program for this chapter features the use of arrays and subprograms. Since sorting an array is a common practice, it has been included as part of the program. Specifically, suppose the Home Sales Realty Company, Inc. wants to print a list containing the amount of all sales for a month. Each sale amount is provided by the user and the number of homes sold is less than 20. Write a program to do the following:
1. Read the data from keyboard input.
2. Print the data in the order in which it is read with a suitable header and format.
3. Print a sorted list (high to low) of sales with a suitable header and format.
4. Print the total number of sales for the month, the total amount of sales, the average sale price, and the company commission (7 percent).
Sample input would be
65000
56234
95100
78200
101750
56700
where each line represents the sale price of a home. Typical output would include an unsorted list of sales, a sorted list of sales, and appropriate summary data.
A first–level pseudocode development is
1. FillListFromKeyboard
2. PrintList
3. SelectionSortList
4. PrintList
5. Compute
6. PrintResults
Notice that PrintList is called twice and PrintResults includes output for number of sales, total of sales, average sale price, and company commission. These are printed with suitable headings.
Figure 7.4
Hierarchy Structure
Chart for Home Sales
Reality Company
|
Module specifications for the main modules are
1. FillListFromKeyboard
Data received: None
Information returned: Sales for a Month
Actual Number of Sales
Logic: Use a while loop to read entries into an array.
2. DisplayList Module
Data received: Array of Sales
Actual Number of Sales
Information returned: None
Logic: Use print statements to display a suitable heading for an unsorted list.
Use a for loop with the array length as a loop control variable to print the list of sales for a month.
3. SelectionSort Module
Data received: Actual Number of Sales
Information returned: Sorted Array of Sales
Logic: Use a selection sort to sort the array.
4. Compute Module
Data received: Array of Sales
Actual Number of Sales
Information returned: Total Sales
Average Sale
Company Commission
Logic: Compute the total sales.
Compute the average sale.
Compute company commission by using a defined constant, CommissionRate.
5. PrintResults Module
Data received: Total sales
Average sales
Company commission
Information returned: None
Logic: Use print statements to display a summary report.
This program illustrates the use of Arrays with functions. Note the use of both value and variable parameters. Also note that a function is used to sort the Array.
PROGRAM
Reality.c
/*******************************************************************************
** Purpose: This program illustrates the use arrays with procedures. **
** It includes filling an array from keyboard, sorting the **
** array, and calculating the sum of its elements and its **
** average. **
** Modules Required: ClearScreen, PrintHeading, GetAFloat **
*******************************************************************************/
# include
# include
# define COMPANY_COMMISSION 0.07
# define MAX_SALES 20
# define True 1
# define False 0
# define SUCCESSFUL 1
# define UNSUCCESSFUL 0
typedef int boolean;
void FillListFromKeyboard (float List[], int *ActNo, int Max);
void DisplayList (char Title[], float List[], int ActNo);
void SelectionSort (float List[], int ActNo);
void Compute (float List[], int ActNo, float *Sum, float *Average,
float *Commission );
void PrintResults (float TotalSales, float AverageSale,
float CompanyCommission );
boolean GetFloat1 (char Prompt[], float * Newfloat, float Low, float High,
float Sentinel);
main ()
{
int
ActNoSales=0;
float
Sales [ MAX_SALES + 1],
TotalSales, AverageSale, CompanyCommission;
FillListFromKeyboard (Sales, &ActNoSales, MAX_SALES);
DisplayList (" Unsorted Sales For June",Sales, ActNoSales);
SelectionSort (Sales, ActNoSales);
DisplayList (" Sorted Sales For June",Sales, ActNoSales);
Compute (Sales, ActNoSales, &TotalSales, &AverageSale, &CompanyCommission);
PrintResults (TotalSales, AverageSale, CompanyCommission);
}
/*******************************************************************************
** Purpose: Fill a float array from keyboard and update the ActNo counter. **
** Use Elements 1..Max **
** Modules Required: None **
** Headers Required: stdio.h **
** Globals Required: None **
** Defines Required: None **
*******************************************************************************/
void FillListFromKeyboard (float List[], int *ActNo, int Max)
{
boolean
Done = False;
char
Prompt[30];
*ActNo = 0;
do
{ /* sprintf - print to string - fills Prompt with Enter List [1] */
sprintf (Prompt,"Enter List [%d]",(*ActNo)+1);
if (GetFloat1 (Prompt, &List[(*ActNo)+1], 0, 500000, -1) )
(*ActNo)++;
else
Done = True;
}
while ((*ActNo < Max) && (!Done));
}
/*******************************************************************************
** Purpose: Display a title line and the contents of a float array. **
** Modules Required: None **
** Headers Required: stdio.h **
** Globals Required: None **
** Defines Required: None **
*******************************************************************************/
void DisplayList (char Title[], float List[], int ActNo)
{
int
Counter;
printf ("\n\n%s\n\n", Title);
for (Counter = 1; Counter <= ActNo; Counter++ )
printf (" <%2i> $%8.2f\n", Counter, List[Counter]);
}
/*******************************************************************************
** Purpose: Selection Sort a float array in ascending order **
** Modules Required: None **
** Headers Required: stdio.h **
** Globals Required: None **
** Defines Required: None **
*******************************************************************************/
void SelectionSort (float List[], int ActNo)
{
int
IndexSmallestNo,
Pass,
Subscript;
float
TempFloat;
for (Pass = 1; Pass <= ActNo-1; Pass++ )
{
IndexSmallestNo = Pass;
for (Subscript = Pass + 1;Subscript <= ActNo;Subscript++)
if (List[Subscript] < List[IndexSmallestNo])
IndexSmallestNo = Subscript;
TempFloat = List[IndexSmallestNo];
List[IndexSmallestNo] = List[Pass];
List[Pass] = TempFloat;
}
}
/*******************************************************************************
** Purpose: Compute the sum, average, and commission for a float array. **
** Modules Required: None **
** Headers Required: stdio.h **
** Globals Required: None **
** Defines Required: COMPANY_COMMISSION **
*******************************************************************************/
void Compute (float List[], int ActNo, float *Sum, float *Average,
float *Commission )
{
int
Index;
(*Sum) = 0;
for (Index = 1; Index <= ActNo; Index++)
(*Sum) = (*Sum) + List[Index];
(*Average) = (*Sum)/ActNo;
(*Commission) = (*Sum) * COMPANY_COMMISSION;
}
/*******************************************************************************
** Purpose: Print the Total Sales, Average Sale, and Company Commission **
** Modules Required: None **
** Headers Required: stdio.h **
** Globals Required: None **
** Defines Required: None **
*******************************************************************************/
void PrintResults (float TotalSales, float AverageSale,
float CompanyCommission )
{
printf ("\n\n Statistics\n ----------\n\n");
printf (" Total Sales = $%10.2f\n\n", TotalSales);
printf (" Average Sale = $%10.2f\n\n", AverageSale);
printf (" Company Commission = $%10.2f\n\n", CompanyCommission);
}
The output from Reality.c is as follows:
-
Enter List [1] < -1.000000 to Exit> : 65000
Enter List [2] < -1.000000 to Exit> : 56234
Enter List [3] < -1.000000 to Exit> : 95100
Enter List [4] < -1.000000 to Exit> : 78200
Enter List [5] < -1.000000 to Exit> : 101750
Enter List [6] < -1.000000 to Exit> : 56700
Enter List [7] < -1.000000 to Exit> : -1
Unsorted Sales For June
< 1> $65000.00
< 2> $56234.00
< 3> $95100.00
< 4> $78200.00
< 5> $101750.00
< 6> $56700.00
Sorted Sales For June
< 1> $56234.00
< 2> $56700.00
< 3> $65000.00
< 4> $78200.00
< 5> $95100.00
< 6> $101750.00
Statistics
----------
Total Sales = $ 452984.00
Average Sale = $ 75497.34
Company Commission = $ 31708.88
––––––––––––––––––––––
999>999>999>999>999>999>999>999>999>
Share with your friends: |