Counting instances of a character in a string - openedge

I need to be able to count the instances of a period in a string. (I need to capture the decimal point in a number, but discard the other periods in the name or title.) I know NUM-ENTRIES essentially counts the number of entries around that character, but I want to do the opposite. My broader problem is to parse a decimal number out of a string, where occasionally the string has other periods in the string.
What syntax can I use to determine the number of periods in this string? See my pretend "NUM-PERIODDS" pretend Progess function below.
Erin L. Halpin (33.333%)
Mr. Thomas Q. Smith 66.6%
I have an algorith to take everything in front of the "%" sign, then I process through all the numbers, but if I find a second "." then I need to skip that character. (If there are better ways to do my algorithm, would love suggestions on that, too.)
//takes in the full joint name and sees if there is a percentage value in it
//then finds whatever is in front of the % sign
IF INDEX (full_name_percentage, "%") GT 0 THEN DO:
cBeforePercentageStr=
SUBSTRING(full_name_percentage,1,INDEX(full_name_percentage,"%") - 1).
IF LENGTH (cBeforePercentageStr) GT 0 THEN DO:
//MESSAGE full_name_percentage VIEW-AS ALERT-BOX.
cThisChar = "".
DO iTemp = 1 TO LENGTH(cBeforePercentageStr):
cThisChar = SUBSTRING(cBeforePercentageStr,iTemp,1).
IF fnIsNumericOrPeriod(cThisChar) THEN
cPercentage = cPercentage + cThisChar.
END.
//need to account for if there are two decimal points
IF **NUM-PERIODS** (cPercentage, ".") GT 1 THEN DO:
MESSAGE "cPercentage value " + cPercentage VIEW-AS ALERT-BOX.
cPercentage = SUBSTRING(cPercentage,2,LENGTH(cPercentage)).
END.
dPercent = TRUNCAT (DECIMAL(cPercentage),2).
END.
END.

There is no need for loops. The number of characters in a string is equal to the length of the string minus the length of that same string without those characters.
define variable p as integer no-undo.
define variable myString as character no-undo.
myString = 'Erin L. Halpin (33.333%) Mr. Thomas Q. Smith 66.6%'.
p = length( myString )
- length( replace( myString, '.', '' ) )
.
message 'there are' p 'periods in the string'.

Golfing on you can also just return the number of entries with "." as delimiter minus one but with a floor of 0.
DEFINE VARIABLE i AS INTEGER NO-UNDO.
DEFINE VARIABLE cStr AS CHARACTER NO-UNDO.
cStr = 'Erin L. Halpin (33.333%) Mr. Thomas Q. Smith 66.6%'.
i = MAX(NUM-ENTRIES(cStr, ".") - 1,0) .
MESSAGE SUBSTITUTE("There are &1 periods in the string", i) VIEW-AS ALERT-BOX.

I like Stefan's idea (and it's pretty foolproof) but for a bit of code golf fun, you can loop without looping all the characters ...
DEFINE VARIABLE str AS CHARACTER NO-UNDO.
DEFINE VARIABLE findChr AS CHARACTER NO-UNDO.
DEFINE VARIABLE cnt AS INTEGER NO-UNDO.
DEFINE VARIABLE pos AS INTEGER NO-UNDO.
DEFINE VARIABLE startPos AS INTEGER NO-UNDO.
str = "Erin L. Halpin (33.333%) Mr. Thomas Q. Smith 66.6%".
findChr = ".".
startPos = 1.
pos = INDEX(str, findChr, startPos).
DO while pos > 0:
cnt = cnt + 1.
startpos = pos + 1.
pos = INDEX(str, findChr, startPos).
eND.
MESSAGE
cnt
VIEW-AS ALERT-BOX.

This is kind of crude and ugly but it should work just fine:
define variable i as integer no-undo.
define variable n as integer no-undo.
define variable p as integer no-undo.
define variable myString as character no-undo.
myString = "Erin L. Halpin (33.333%) Mr. Thomas Q. Smith 66.6%".
n = length( myString ).
do i = 1 to n:
if substring( myString, i, 1 ) = "." then p = p + 1.
end.
message "there are" p "periods in the string".

Related

number convert binoc

Can you help me.
if convert binary to octal anyone can help me..???
this code convert octal to binary. help me for change this code.
function oct2bin returns character ( input octalString as character ):
define variable i as integer no-undo.
define variable n as integer no-undo.
define variable c as character no-undo.
define variable result as character no-undo.
n = length( octalString ).
if n < 2 or substring( octalString, 1, 1 ) <> "~\" then /* a valid octalString begins with "\" */
do:
message "valid octal strings must begin with ~\".
result = ?.
end.
else
do i = 2 to n:
c = substring( octalString, i, 1 ).
if asc( c ) < 48 or asc( c ) > 55 then /* a valid octalString only contains the digits 0 thru 7 */
do:
message c "is not a valid numeric character".
result = ?.
leave.
end.
Groups of three bits can be converted into an octal digit. Divide the binary number into three-bit groups, convert the group to an octal digit, then string the digits into an octal number.
PROCEDURE BinToOct:
DEFINE INPUT PARAMETER pcBinStr AS CHARACTER NO-UNDO.
DEFINE OUTPUT PARAMETER pcOctStr AS CHARACTER NO-UNDO.
DEFINE VARIABLE cPadding AS CHARACTER NO-UNDO.
DEFINE VARIABLE iLoop AS INTEGER NO-UNDO.
DEFINE VARIABLE cBinDigit AS CHARACTER NO-UNDO.
DEFINE VARIABLE cOctDigit AS CHARACTER NO-UNDO.
/* Pad input with zeroes to make it divisible by 3. */
IF LENGTH(pcBinStr) MOD 3 <> 0 THEN
ASSIGN
cPadding = FILL("0", (3 - LENGTH(pcBinStr) MOD 3))
pcBinStr = cPadding + pcBinStr.
/* Divide up bits into groups of 3. */
DO iLoop = 1 TO LENGTH(pcBinStr) BY 3:
cBinDigit = SUBSTRING(pcBinStr, iLoop, 3).
/* Convert bits to octal digit. */
CASE cBinDigit:
WHEN "000" THEN cOctDigit = "0".
WHEN "001" THEN cOctDigit = "1".
WHEN "010" THEN cOctDigit = "2".
WHEN "011" THEN cOctDigit = "3".
WHEN "100" THEN cOctDigit = "4".
WHEN "101" THEN cOctDigit = "5".
WHEN "110" THEN cOctDigit = "6".
WHEN "111" THEN cOctDigit = "7".
OTHERWISE MESSAGE "Bad input" VIEW-AS ALERT-BOX ERROR.
END CASE.
/* Add digit to result string. */
pcOctStr = pcOctStr + cOctDigit.
END.
END PROCEDURE.
DEFINE VARIABLE cResult AS CHARACTER NO-UNDO.
RUN BinToOct (INPUT "1010011100101", OUTPUT cResult).
MESSAGE cResult VIEW-AS ALERT-BOX INFORMATION.

How to use Windows API 'GetTcpTable' to find a avaiable tcp port?

I'm trying to use windows api 'GetTcpTable', but I don't know how to prepare the data structure 'PMIB_TCPTABLE' and get the return value from it.
PROCEDURE GetTcpTable EXTERNAL "Iphlpapi":U:
DEFINE OUTPUT PARAMETER mTcpTable AS HANDLE TO MEMPTR. // the API expects a pointer, so use HANDLE TO syntax to pass one
DEFINE INPUT-OUTPUT PARAMETER SizePointer AS HANDLE TO LONG.
DEFINE INPUT PARAMETER Order AS LONG.
DEFINE RETURN PARAMETER IPHLPAPI_DLL_LINKAGE AS LONG.
END PROCEDURE.
DEFINE VARIABLE mTcpTable AS MEMPTR NO-UNDO.
DEFINE VARIABLE SizePointer AS INT NO-UNDO.
DEFINE VARIABLE Order AS INT NO-UNDO.
DEFINE VARIABLE IPHLPAPI AS INT NO-UNDO.
DEFINE VARIABLE mTempValue AS MEMPTR NO-UNDO.
DEFINE VARIABLE dwNumEntries AS INT NO-UNDO.
DEFINE VARIABLE dwState AS INT NO-UNDO.
DEFINE VARIABLE dwLocalAddr AS INT64 NO-UNDO.
DEFINE VARIABLE dwLocalPort AS INT NO-UNDO.
DEFINE VARIABLE dwRemoteAddr AS INT64 NO-UNDO.
DEFINE VARIABLE dwRemotePort AS INT NO-UNDO.
DEFINE VARIABLE ix AS INTEGER NO-UNDO.
SizePointer = 4.
Order = 1.
SET-SIZE(mTcpTable) = SizePointer.
RUN GetTcpTable(OUTPUT mTcpTable, INPUT-OUTPUT SizePointer,INPUT Order, OUTPUT IPHLPAPI). //get ERROR_INSUFFICIENT_BUFFER and know the real buffer now
MESSAGE "IPHLPAPI is " IPHLPAPI SKIP // ERROR_INSUFFICIENT_BUFFER = 122
"SizePointer is " SizePointer SKIP
VIEW-AS ALERT-BOX.
SET-SIZE(mTcpTable) = 0.
SET-SIZE(mTcpTable) = SizePointer.
RUN GetTcpTable(OUTPUT mTcpTable, INPUT-OUTPUT SizePointer,INPUT Order, OUTPUT IPHLPAPI).
MESSAGE "IPHLPAPI is " IPHLPAPI SKIP // NO_ERROR = 0
"SizePointer is " SizePointer SKIP
GET-LONG(mTcpTable,1) SKIP //dwNumEntries
VIEW-AS ALERT-BOX.
IF IPHLPAPI = 0 THEN DO:
dwNumEntries = GET-LONG(mTcpTable,1).
OUTPUT TO VALUE ("C:\temp\debug.txt") UNBUFFERED.
DO ix = 0 TO dwNumEntries - 1.
dwState = GET-UNSIGNED-LONG(mTcpTable,5 + ix * 20). // get value of dwState
dwLocalAddr = GET-UNSIGNED-LONG(mTcpTable,9 + ix * 20). // get value of dwLocalAddr
SET-SIZE(mTempValue) = 2.
PUT-BYTE(mTempValue,2) = GET-UNSIGNED-SHORT(mTcpTable,13 + ix * 20).
PUT-BYTE(mTempValue,1) = GET-UNSIGNED-SHORT(mTcpTable,14 + ix * 20).
dwLocalPort = GET-UNSIGNED-SHORT(mTempValue,1). // get value of dwLocalPort, The maximum size of an IP port number is 16 bits, so only the lower 16 bits should be used. The upper 16 bits may contain uninitialized data.
SET-SIZE(mTempValue) = 0.
dwRemoteAddr = GET-UNSIGNED-LONG(mTcpTable,17 + ix * 20). // get value of dwRemoteAddr
SET-SIZE(mTempValue) = 2.
PUT-BYTE(mTempValue,2) = GET-UNSIGNED-SHORT(mTcpTable,21 + ix * 20).
PUT-BYTE(mTempValue,1) = GET-UNSIGNED-SHORT(mTcpTable,22 + ix * 20).
dwRemotePort = GET-UNSIGNED-SHORT(mTempValue,1). // get value of dwRemotePort, The maximum size of an IP port number is 16 bits, so only the lower 16 bits should be used. The upper 16 bits may contain uninitialized data.
SET-SIZE(mTempValue) = 0.
PUT UNFORMATTED dwState "~t" dwLocalAddr "~t" dwLocalPort "~t" dwRemoteAddr "~t" dwRemotePort "~r".
END.
OUTPUT CLOSE.
END.
SET-SIZE(mTcpTable) = 0.
Is there any better way to get the lower 16 bits? I'm not sure why I need to put the byte in a reversed order in to mTempValue, is it releated to little endian of x86 PC?
How to get the right start position to read the value if I run the code on different bitness computer
I think the question can be closed now:
https://community.progress.com/s/feed/0D54Q000088eIeASAU?t=1602495359075&searchQuery
Thank you guys :)
Microsoft's doc usually has the definitions of the structures (see https://learn.microsoft.com/en-us/windows/win32/api/tcpmib/ns-tcpmib-mib_tcptable). Once you know those - and any other that are related/pointed to/etc - you can create and manipulate them using MEMPTR data types and the various functions that operate on them: SET-SIZE(), GET-BYTES() and GET-POINTER-VALUE() are typically used. There is doc on this at https://docs.progress.com/bundle/openedge-programmimg-interfaces/page/Shared-Library-and-DLL-Support.html .

Tracing cases of failure got

I'm trying to populate an integer variable from a character variable. If there is any error found I want to show the error message and trace all the possible cases for failure got.
//Defining variable
Define variable char_value as character no-undo initial "kk".
Define variable int_value as integer no-undo.
define variable ix as integer no-undo.
Assign int_value = integer(char_value) no-error.
IF ERROR-STATUS:ERROR OR ERROR-STATUS:NUM-MESSAGES > 0 THEN
DO:
MESSAGE ERROR-STATUS:NUM-MESSAGES
" errors occurred during conversion." SKIP
"Do you want to view them?"
VIEW-AS ALERT-BOX QUESTION BUTTONS YES-NO
UPDATE view-errs AS LOGICAL.
IF view-errs THEN
DO ix = 1 TO ERROR-STATUS:NUM-MESSAGES:
MESSAGE ERROR-
STATUS:GET-NUMBER(ix)
ERROR-STATUS:GET-
MESSAGE(ix).
END.
END.
There are two conditions which I want to know.
What char value I gave so that no. Of error returns will be more than 1.
How can I trace all the possible cases for failure got.
The built-in conversion routine does not do what you want it to do. So you will need to parse your input prior to attempting to convert it. Something like this:
function isDigit returns logical ( input d as character ):
if length( d ) = 1 then
return ( index( "0123456789", d ) > 0 ).
else
return no.
end.
procedure checkInteger:
define input parameter integerString as character no-undo.
define output parameter errorList as character no-undo.
define output parameter ok as logical no-undo.
define variable i as integer no-undo.
define variable n as integer no-undo.
define variable c as character no-undo.
ok = yes.
n = length( integerString ).
do i = 1 to n:
c = substring( integerString, i, 1 ).
if i = 1 and c = "-" then next.
if isDigit( c ) = no then
do:
ok = no.
errorList = errorList + substitute( "The character '&1' at offset &2 is not a valid integer value~n", c, i ).
end.
end.
errorList = trim( errorList, "~n" ). // remove the trailing newline (if any)
return.
end.
define variable ok as logical no-undo.
define variable errorList as character no-undo.
run checkInteger( "12x34y56z789", output errorList, output ok ).
if ok = yes then
message "string is a properly formed integer, go ahead and convert it".
else
message
"string was not correctly formed, do not try to convert it" skip
errorList
view-as alert-box information
.
Note #1 If the input contains unprintable characters the errorList string will display it literally and it will look kind of funny. You could, of course, encode them to be more readable. Doing so is left as an exercise. Or another question.
Note #2 This code makes no attempt to check that the string value will fit into an integer or an int64. That is also left as an exercise.
While you can make your parsing as complex as you like, I would just keep it simple and ensure the user is provided enough information, which in this case is the complete input value:
def var cc as char initial "kk".
def var ii as int.
ii = integer( cc ).
catch e as progress.lang.error:
message quoter( cc, "'" ) e:getMessage(1) view-as alert-box.
end catch.

How to parse two characters at a time for one Iteration?

I have a written query for matching 2 characters and parse the data but I feel that the way i did is wrong. Let me share my logic with you
DEFINE VARIABLE I AS INTEGER NO-UNDO.
DEFINE VARIABLE cData AS CHARACTER NO-UNDO.
DEFINE VARIABLE cParsData AS CHARACTER NO-UNDO.
ASSIGN
cData = 'PRRSCLPP0123456789'.
DO I = 1 TO LENGTH(cData):
cParsData = SUBSTRING(cData,I).
IF cParsData MATCHES 'PP*' THEN MESSAGE SUBSTRING(cParsData,4,9).
END.
As you see the way i did is wrong and its parsing each character per iteration i think but what i need is it should parse two characters per iteration so that we can matches "PP". You can share or change the logic for different ways to get the same output
It is hard to imagine a reason for iterating through the string one character at a time looking for "PP" and then spitting out characters 4 through 13. It would be much simpler to do this:
define variable myData as character no-undo.
define variable foundIt as integer no-undo.
myData = "PRRSCLPP0123456789".
foundIt = index( myData, "PP" ).
if foundIt > 0 then
message substring( myData, 4, 9 ).
If there is a reason to go through that string one character at a time I think it must not be contained in your code sample or question.
On a side note: MATCHES "PP*" is equivalent to BEGINS "PP". It doesn't matter much in this case but it is a bad habit to needlessly throw MATCHES at string comparisons. Especially if that habit ends up in a WHERE clause. Using MATCHES in WHERE clauses will cause a table scan. Which is almost always a bad idea.
If you are trying to output N characters after the position that "PP" was found (rather than the hard-coded 4 through 13) you would do it like so (assuming that n = 9):
define variable myData as character no-undo.
define variable foundIt as integer no-undo.
myData = "PRRSCLPP0123456789".
foundIt = index( myData, "PP" ).
if foundIt > 0 then
message substring( myData, foundIt + 1, 9 ).
I dont quite understand what you want to do. Do you want to search the string and see if there's "PP" in it? Then you don't need to do it in an iteration. Simply
cData MATCHES "*PP*" will tell you that.
If "PP" is some kind of delimiter and you want to do something with the data before and after you can do:
DEFINE VARIABLE I AS INTEGER NO-UNDO.
DEFINE VARIABLE cData AS CHARACTER NO-UNDO .
DEFINE VARIABLE cParsData AS CHARACTER NO-UNDO.
ASSIGN
cData = 'PRRSCLPP0123456789'.
DO I = 1 TO LENGTH(cData):
cParsData = SUBSTRING(cData,I, 2).
IF cParsData = 'PP' THEN DO:
DISPLAY
SUBSTRING(cData, i + 2) FORMAT "x(20)" LABEL "After PP"
SUBSTRING(cData, 1, i - 1) FORMAT "x(20)" LABEL "Before PP".
END.
END.
This only works for one occurance of "PP" in the string though. You should try to explain better exactly what you are after.
You left a lot more information in a comment on another answer:
If PP really always is position 10 (and 11) or 20 (and 21) and you only want the follwing 10 chars then you can do:
DEFINE VARIABLE cData1 AS CHARACTER NO-UNDO.
DEFINE VARIABLE cData2 AS CHARACTER NO-UNDO.
/* Position 10 and 11 */
cData1 = 'PRRSCLAAAPP0123456789'.
/* Position 20 and 21 */
cData2 = 'PRRSCLAAAPRRSCLAAAPP9876543210AA'.
FUNCTION parse RETURNS CHARACTER
(INPUT cString AS CHARACTER ):
IF INDEX(cString, "PP") > 0 THEN
RETURN SUBSTRING(cString, INDEX(cString, "PP") + 2, 10 ).
ELSE
RETURN "".
END.
MESSAGE cData1 " ->" parse(cData1) SKIP
cData2 " ->" parse(cData2) VIEW-AS ALERT-BOX.

How to convert A00073 value to 9973 in progress 4gl

i have column having multiple value like A0045 ,A00065 . i want to convert it 9945, 9965.
Need to remove all 0 and character value and add 99 before that value.. Please help..
This can be done in many ways. Here is one way (may not be the best). As I don't have a database, I created a temp table.
def temp-table tt
field val as char.
create tt.
tt.val = "A0045".
create tt.
tt.val = "A00065".
for each tt:
run filter_zero(input-output val).
val = replace(val,"A","99").
DISP val.
end.
procedure filter_zero:
define input-output parameter val as character.
// remvoe all zeroes
repeat while val matches("*0*"):
val = replace(val,"0","").
end.
end procedure.
The code below removes the uppercase (A-Z) and lowercase letter (a-z) and "0" as well :
DEF TEMP-TABLE test
FIELD str1 AS CHAR.
DEF VAR newStr AS CHAR NO-UNDO.
DEF VAR i AS INT NO-UNDO.
CREATE test.
ASSIGN test.str1 = "A0045".
CREATE test.
ASSIGN test.str1 = "A00065".
FOR EACH test:
DO i = 65 TO 90: /* A - Z */
IF SUBSTR(test.str1, 1, 1) EQ CHR(i) THEN
DO:
test.str1 = REPLACE(test.str1, CHR(i), "").
END.
END.
DO i = 97 TO 122: /* a - z */
IF SUBSTR(test.str1, 1, 1) EQ CHR(i) THEN
DO:
test.str1 = REPLACE(test.str1, CHR(i), "").
END.
END.
/* Removes all 0 and add 99 before the value */
test.str1 = REPLACE(test.str1, "0", "").
ASSIGN test.str1 = "99" + test.str1.
DISP test.
END.
define variable word as character no-undo.
define variable i as integer no-undo.
assign word = "A00065".
/*******To Remove all the zero********/
word = replace(word,substring(word,index(word,"0"),(r-index(word,"0") - 1)),"").
do i = 65 to 90:
if substring(word,1,1) = chr(i) then
do:
word = replace(word,substring(word,1,1),"99").
leave.
end.
end.
display word.
DEFINE VARIABLE word AS CHARACTER NO-UNDO.
SET word.
word = REPLACE(word,SUBSTRING(word,1,R-INDEX(word,"0")),"99").
/* If you want to remove all words before last zero
(including last zero also) * /
MESSAGE word
VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
Please do let me know if this works for you, Progress not installed so couldn't compile and test.
/* A0045 -> 9945
A00065 -> 9965 */
DEFINE VARIABLE v_RawData AS CHARACTER NO-UNDO.
DEFINE VARIABLE v_InpData AS CHARACTER NO-UNDO.
DEFINE VARIABLE i AS INTEGER NO-UNDO.
DEFINE VARIABLE j AS INTEGER NO-UNDO.
ASSIGN v_RawData = "A0045,A00065".
DO i =1 TO NUM-ENTRIES(v_RawData):
ASSIGN v_InpData = ENTRY(i,v_RawData).
DO j = 1 TO LENGTH(v_InpData):
IF ASC(SUBSTRING(v_InpData,j,1)) > 48 AND
ASC(SUBSTRING(v_InpData,j,1)) < 58 THEN
DO:
LEAVE.
END.
END.
MESSAGE REPLACE(v_InpData,SUBSTRING(v_InpData,1,j - 1),"99").
END.

Resources