How can I determine if a character is alpha? - openedge

I have a list of phone numbers that sometimes have a person in parenthesis at the end. I need to extract the person's name (and add that as a note in a separate field). Here is an example of the data:
(517)234-6789(Bob)
701-556-2345
(325)663-5977
(215)789-8585
425-557-7745(Pauline)
There is always a () around the person's name, but often there is also a () around the area code, so I can't use the ( as a way to know a name has started. I'd like to create a loop that goes through the phone number string and if it sees alpha characters, builds a string that will be assigned to a variable as the name.
Something like this. I am making up the IS-ALPHA syntax, of course. That is what I am looking for, or something where I don't have to list every letter.
PROCEDURE CreatePhoneNote (INPUT cPhone AS CHARACTER)
DEFINE VARIABLE cPersonName AS CHARACTER NO-UNDO.
DEFINE VARIABLE cThisChar AS CHARACTER NO-UNDO.
DEFINE VARIABLE iCount AS INTEGER NO-UNDO.
DO iCount 1 TO LENGTH(cPhone):
cThisChar = SUBSTRING(cPhone,iCount,1).
IF IS_ALPHA(cThisChar) THEN cPersonName = cPersonName + cThisChar.
END.
//etc.....
END PROCEDURE.

Since these are the fun questions, just one more isAlpha answer that does not use hard-coded ASCII codes but leans on the property / assumption that an alpha character has an upper and lower case version:
function isAlpha returns logical (
i_cc as char
):
return compare( upper( i_cc ), '<>', lower( i_cc ), 'case-sensitive' ).
end function.
With some code to test the function:
// test
def var ic as int.
do ic = 0 to 255:
if isAlpha( chr(ic) ) then
message ic chr( ic ).
end.
And then you see that the hard-coded ASCII answer did not take characters with diacritics into account. :-)
Watch it run on ProgressAblDojo.
Watch it run again on ProgresAblDojo with a fix to help ProgressAblDojo over it's ignorance of it's own codepage.

i can not comment, but a suggestion is to see what character is at the 0th index of the string, if it is a ( then you know how to deal with that condition. Although the next method will only work for usa numbers (it does seem that is what you have), you can check if the length matches a set number (10 since there are 10 digits in a usa number, or 12 since that is how long it would be with 2 parenthesis), and if its not, you know you have a name at the end. You would then split that string at the appropriate index

You don't have to go through each character. You can use the open parenthesis to break up the string and get the data after the last parenthesis. This may run faster if you have a large amount of data.
DEFINE VARIABLE cPhone AS CHARACTER NO-UNDO INITIAL "(517)234-6789(Bob)".
DEFINE VARIABLE cPersonName AS CHARACTER NO-UNDO.
DEFINE VARIABLE iCount AS INTEGER NO-UNDO.
DEFINE VARIABLE iNum AS INTEGER NO-UNDO.
iCount = NUM-ENTRIES(cPhone, "("). /* See how many open parentheses there are */
cPersonName = ENTRY(iCount, cPhone, "("). /* Get the string after the last open paren */
iNum = INTEGER(SUBSTRING(cPersonName, 1, 1)) NO-ERROR. /* See if the first character is a number */
IF iNum > 0 THEN
cPersonName = "". /* If it's a number, there is no name so blank out the variable */
ELSE
cPersonName = SUBSTRING(cPersonName, 1, LENGTH(cPersonName) - 1). /* Drop the closed paren */
MESSAGE cPersonName VIEW-AS ALERT-BOX INFORMATION.

You can do this using the ABL's ASC() function.
if asc(cThisChar) ge 65
and asc(cThisChar) le 90
and asc(cThisChar) ge 97
and asc(cThisChar) le 122
then
cPersonName = cPersonName + cThisChar.
ASC() works simply enough for all codepages for codepoints between 0 and 255; for others it'll depend on your -cpinternal / session:cpinternal value.

Related

How can I access more than 1 element from a field in Progress 4GL

all. I want to get more than one element from a field. If, for instance, the SQL code is as folow:
SELECT cod_emit, nom_abrev FROM emit WHERE cod_emit IN ('101','102','500');
I'm trying to transform it into Progress using temp-tables so code would be as follows
DEF TEMP-TABLE tt-emit NO-UNDO
FIELD cod-emit LIKE emit.cod-emit
FIELD nom-abrev LIKE emit.nom-abrev.
FOR EACH emit
WHERE emit.cod-emit = 101
OR emit.cod-emit = 102
OR emit.cod-emit = 500 NO-LOCK:
CREATE tt-emit.
ASSIGN tt-emit.cod-emit = emit.cod-emit
tt-emit.nom-abrev = emit.nom-abrev.
END.
Later, I would get the temp-table through a JSON and use it on our .php. However, the cod-emit we need to use will be inserted by the user in a .php.
I don't know if we can use a comma in this case (but for what I tried, we can't) or if there's any other solution to this conundrum. Thanks for your time.
You might do something like this:
define temp-table tt_emit no-undo /* avoid using "-" in names */
cod_emit like emit.cod-emit
nom_abrev like emit.nom-abrev
/* it is not required but you really ought to define an index... */
.
define variable elist as character no-undo.
define variable ecode as character no-undo.
define variable i as integer no-undo.
define variable n as integer no-undo.
elist = '101,102,500'.
n = num-entries( elist ).
do i = 1 to n:
ecode = entry( i, elist ).
for each emit no-lock where emit.cod-emit = ecode:
create tt_emit.
assign
tt_emit.cod_emit = emit.cod-emit
tt_emit.nom_abrev = emit.nom-abrev
.
end.
end.

how to sum the digits in an integer using recusion?

Write a recursive method that computes the sum of the sum of the digits in an integer. use the following method header:
public static int sumDigits(long n)
For example, sumDigits(234) returns 2 + 3 + 4 = 9. Write a real program that prompts the user to enter an integer and displays its sum.
Receive an integer as a parameter
Convert to string
Parse the string's individual characters
Remove a character (first or last doesn't matter)
Put the remaining characters back into a single string
Cast that string back to integer
Call "result = removedChar As Integer + function(remainingChars as Integer)" <--- this is the recursion
In the future you should at least make one attempt for others to help you edit when you post an obvious homework question ;)

How to export a text file in for each loop?

I have written a program for export file to a specific directory and I feel I have written some unwanted logic. so I would like to know short and best way to do export the files. Let me share what I tried
DEFINE VARIABLE cData AS CHARACTER NO-UNDO.
DEFINE VARIABLE i AS INTEGER NO-UNDO.
DEFINE VARIABLE icount AS INTEGER NO-UNDO.
DEFINE VARIABLE cName AS CHARACTER NO-UNDO.
DEFINE VARIABLE cPath AS CHARACTER NO-UNDO.
DEFINE TEMP-TABLE ttdata
FIELD GetName AS CHARACTER
FIELD iValue AS INTEGER.
ASSIGN
icount = 2
cPath = "*******".
DO I = 1 TO icount:
IF I = 1 THEN cName = "David".
IF I = 2 THEN cName = "Macavo".
CREATE ttdata.
ASSIGN
ttdata.GetName = cName
ttdata.iValue = 100.
END.
/** ttdata has two records now*/
FOR EACH ttdata.
RUN CallProc.p (INPUT ttdata.GetName,
INPUT ttdata.iValue).
END.
PROCEDURE CallProc:
DEFINE INPUT PARAMETER getName AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER iValue AS INTEGER NO-UNDO.
OUTPUT TO cPath.
PUT UNFORMATTED ttdata.GetName ttdata.GetName.
OUTPUT CLOSE.
END PROCEDURE.
From my logic its working well and exporting 2 files as I expected but its poor idea to call another procedure.Please help this case.
I am going to use the sports2000 db in my example. Everyone has a copy so it is easy to run the sample.
define stream outFile. /* using a named stream rather than the default, unnamed, stream avoids unintended conflicts if someone else's code is lazily using the unnamed stream */
function mkTemp returns character ( input tmpid as character, input extension as character ):
define variable fname as character no-undo.
run adecomm/_tmpfile.p ( tmpid, extension, output fname ).
/* create the temp file with no content
*/
output stream outFile to value( fname ).
output stream outFile close.
return fname.
end.
procedure doStuff:
define input parameter tmpfile as character no-undo.
define input parameter custid as integer no-undo.
output stream outFile to value( tmpFile ) append. /* open the existing file in append mode */
put stream outFile "customer:" custId skip.
for each order no-lock where order.custNum = custId and orderStatus <> "shipped" and salesRep = "bbb":
put stream outFile orderNum " " promised skip.
end.
output stream outFile close.
return.
end.
define variable i as integer no-undo.
define variable tmpName as character no-undo.
/* tmpName = mkTemp( "xyzzy", ".tmp" ). */ /* if you only need one temp file get the name here and comment it out below */
for each customer no-lock:
tmpName = mkTemp( "xyzzy", ".tmp" ). /* use this if every customer should get a distinct temp file */
run doStuff ( tmpName, custNum ).
/* if there is no good reason to be calling the doStuff() procedure then just remove it and do it inline like this: */
/*
*
output stream outFile to value( tmpFile ) append. /* open the existing file in append mode */
put stream outFile "customer:" customer.custNum skip.
for each order no-lock where order.custNum = customer.CustNum and orderStatus <> "shipped" and salesRep = "bbb":
put stream outFile orderNum " " promised skip.
end.
output stream outFile close.
*/
i = i + 1.
if i >= 3 then leave. /* just do 3 customers for the sample run... */
end.
The program doesn't look too bad at first glance, but there are several issues.
The DEFINE TEMP-TABLE could use a NO-UNDO.
You should probably use "FOR EACH ttdata:" instead of "FOR EACH ttdata." which is old style.
You are running CallProc.p which is an external program and not the internal procedure included in your example. If your code actually runs you would have to show us the code in CallProc.p.
Assuming the code from CallProc, the file you open is named cPath. (I don't get why are saying two files are written.) If you want the file to be named "*******" you have to write value(cPath) instead of cPath, but "*******" is an invalid name in Windows anyway.
It doesn't hurt too much to run a procedure for every line. The bigger issue is that you open and close the file every time. Open the file before the for each and close it afterwards. If you are using a current OpenEdge version you should close it inside a finally block.
Also you are opening the file without APPEND which means that you are overwriting it every time, so only the last record gets written.
As for not using a procedure this should be pretty trivial, especially since you don't use the parameters you pass to the procedure. You are currently outputting ttdata.GetName twice though, which is probably an error. Also you are missing a SKIP at the end of the put statement and a space in between since UNFORMATTED doesn't add any spaces. I suppose you should have written PUT UNFORMATTED getName " " iValue skip.
I suppose this is some kind of homework?
If you want two (or more) separate export files, you'll need to give them unique names. I did that here by reusing your 'I' variable and reassigning cPath each time. And although I don't agree that calling a separate procedure to write the file is a poor idea, I've incorporated it into the single FOR-EACH loop. I've also fixed some of the points that idspispopd made.
DEFINE VARIABLE i AS INTEGER NO-UNDO.
DEFINE VARIABLE icount AS INTEGER NO-UNDO.
DEFINE VARIABLE cName AS CHARACTER NO-UNDO.
DEFINE VARIABLE cPath AS CHARACTER NO-UNDO.
DEFINE TEMP-TABLE ttdata NO-UNDO
FIELD GetName AS CHARACTER
FIELD iValue AS INTEGER.
ASSIGN
icount = 2.
DO I = 1 TO icount:
/* Using a CASE statement makes it easier to add in other values in the future */
CASE I:
WHEN 1 THEN cName = "David".
WHEN 2 THEN cName = "Macavo".
END CASE.
CREATE ttdata.
ASSIGN
ttdata.GetName = cName
ttdata.iValue = 100.
END.
/** ttdata has two records now*/
I = 1.
FOR EACH ttdata NO-LOCK:
cPath = ".\" + STRING(I) + ".txt".
OUTPUT TO VALUE(cPath).
PUT UNFORMATTED ttdata.GetName ttdata.iValue SKIP.
OUTPUT CLOSE.
I = I + 1.
END.

F#: Type mismatch error when trying to count number of lowercase letters in a string

So i'm trying to count the number of lowercase letters in a string. Like this:
intput: "hello world"
output: 10
This is what I have:
let lowers (str : string) : int =
let count = 0
for i=0 to (str.Length-1) do
if (Char.IsLower(str.[i])) then (count = count+1)
else count
printf "%i" count
But I keep getting this error:
All branches of an 'if' expression must have the same type. This expression was expected to have type 'bool', but here has type 'int'.
I've spent hours trying to figure out this problem, but haven't progressed a single bit. How can i print out just the count value that I have? It also says:
expecting an int but given a unit
Please help
In F#, variables are immutable by default. That means that you can't assign new value to them: count = count+1 does not mean "take the value of count, add 1 to it, and assign that new value to count" like it does in other languages. Instead, the = operator (when it's not part of a let x = ... declaration) is the comparison operator. So count = count+1 means "true if count is equal to count plus one, or false if the two values are not equal". This is always false, of course.
What you're trying to do, assigning a new value to a variable, uses the <- operator, and requires that the variable be declared mutable first:
let mutable count = 0
count <- count + 1
So your code needed to look like this:
let lowers (str : string) : int =
let mutable count = 0
for i=0 to (str.Length-1) do
if (Char.IsLower(str.[i])) then count <- count+1
count
Another thing to note is that I removed the else count line. Both sides of an if...then...else expression must have the same type, and the type of a variable assignment is "no type", which F# calls unit for reasons I won't get into here as it's best when learning something new to focus on one concept at a time. Also, there are better ways (such as certain built-in functions) to count the number of characters in a string that match a certain condition, but again, one concept at a time.
Update: One other change that your code needs that I forgot to mention. You've declared your lowers function as returning an int value, but the last line of your original code was printf "%d" count, which returns "nothing" (the type known as unit). That's where the "expecting an int but given a unit" error was coming from. To return the value of count, the last line of your code needed to be just plain count: the return value of an F# function is the value of the last expression in the function. Here, that's the value of count, so the last expression in the function needed to be a line saying just plain count, so that that becomes the function's return value.

how to convert an array of characters to qstring?

I have an array -
char name[256];
sprintf(name, "hello://cert=prv:netid=%d:tsid=%d:pid=%d\0", 1010,1200, 1300);
QString private_data_string = name;
At the last offset of this string i.e. '\0',when I try to do the following.
while(private_data_string.at(offset) != ':' &&
private_data_string.at(offset) != ';' &&
private_data_string.at(offset).isNull() == false)
The application aborts. Looks like that the data pointer is also zero at the string '\'. How can I fix this?
QString doesn't contain terminating character as you expect that is why you are failing assertion out of bounds. This is proper approach:
while(offset<private_data_string.length() &&
private_data_string.at(offset) != ':' &&
private_data_string.at(offset) != ';') {
// ...
}
It looks like you are doing something strange. Looks like your question is wrong. You are asking how to fix your strange solution of some mysterious problem, instead explain what are you trying to do and then as a bonus: how did you try to solve it.
You need to know several facts:
Writing \0 at tge end of your string literal is not necessary. String literals are null-terminated by default. Literal "abc" will actually contain 4 characters including terminating null character. Your string literal has 2 null characters at its end.
You have used the default constructor QString(char*). There is no additional data about buffer's length, so QString reads characters from the buffer until it encounters first null character. It doesn't matter how many null characters are actually at the end. The null character is interpreted as a buffer end marker, not a part of the string.
When you have QString "abc", its size is 3 (it would be surprising to have another value). Null character is not a part of the string. QString::at function can be used for positions 0 <= position < size(). This is explicitly specified in the documentation. So it doesn't matter if QString's internal buffer is null-terminated or not. Either way, you don't have access to null terminator.
If you really want null character to be part of your data, you should use QByteArray instead of QString. It allows to specify buffer size on construction and can contain as many null characters as you want. However, when dealing with strings, it's usually not necessary.
You should use QString::arg instead of sprintf:
QString private_data_string =
QString("hello://cert=prv:netid=%1:tsid=%2:pid=%3")
.arg(netid).arg(tsid).arg(pid);
sprintf is unsafe and can overrun your fixed-size buffer if you're not careful. In C++ there's no good reason to use sprintf.
"A QString that has not been assigned to anything is null, i.e., both the length and data pointer is 0" - this has nothing to do with your situation because you have assigned a value to your string.

Resources