计算不带 SQL 的字符串变量中字符的出现次数
Count occurrences of character in string variable without SQL
我正在寻找一种方法来计算一个字符在字符串中出现的次数,而无需使用 SQL。
我是 RPGLE 的新手,我创建了一个测试程序,它接受字符格式的用户输入,通过验证,并将成功的数据转换为数字。这些输入之一可以是正整数或负整数。在进行验证时,我测试“-”是否位于第一个位置,并使用 %CHECK 确保输入为 0-9 或“-”。 (例如“-10”通过,“1-0”失败)
但是,如果输入多次出现“-”符号,例如“-1-1-1-1”,它会通过验证并在程序尝试转换为数字时崩溃。
我知道我可以在我的 DDS 中使用编辑代码让系统处理这个问题,但我正在尝试学习不同的方法来让我的程序控制验证。在我的研究中,我发现 TestN 和 Module/Convert/%Error 是可用于确保输出为数字的方法,但我无法针对此特定实例进行测试,因此我无法提供有意义的反馈。
有没有办法计算'-'的出现次数以便我测试它?
由于我的意图似乎有些混乱,我将添加另一个示例。如果我想找出字母 'L' 在单词 'HELLO' 中出现的次数,最好的方法是什么。
%scan() bif 接受 3 个参数 - 起始位置。因此,您可以从最后一次命中的位置开始进行多次扫描。
但是,我不喜欢这种手动验证。从性能的角度来看,假设大多数数据都很好,那么您就是在浪费周期。更重要的是,您所布置的测试要求“-”位于第一个位置意味着“-10”会失败;
如果需要,我更喜欢简单地进行捕获异常的转换。
monitor;
myValue = %dec(myString);
on-error;
// let the user know
endmon;
最后,TESTN
已过时,应避免使用。无论如何,它可能无法按照您想要的方式工作。例如 (IIRC),“5A”通过了 TESTN 测试。
RPG 手册本身对 TESTN 有这样的说法:
自由格式语法 -(不允许 - 在使用变量之前不要测试变量,而是在 MONITOR 组中对变量的用法进行编码,并使用 ON-ERROR 处理任何错误。请参阅错误-处理操作。)
RPG是强类型语言,所以一般来说,如果需要数字,就用数字字段。不要使用字符字段,然后测试并转换为数字。显示文件(使用 DDS)旨在简化此任务(要求用户输入数字)。
也就是说,有时您无法控制该输入。您可能正在处理 EDI 交易或其他文件传输,其中另一方将文本放入字段中,而您需要提取数字部分。对于这种情况,如果您收到类似“-$45,907.12”的信息,您需要做的不仅仅是计算减号的数量。
IBM 的 Barbara Morris 有 posted the following code,这是一个从字符字段中提取数值的例子。它理解减号、小数分隔符、小数点和货币符号。
<-----* /COPY 文件的原型从这里开始 ----->
*---------------------------------------------------------
* getNum - procedure to read a number from a string
* and return a 30p 9 value
* Parameters:
* I: string - character value of number
* I:(opt) decComma - decimal point and digit separator
* I:(opt) currency - currency symbol for monetary amounts
* Returns: packed(30,9)
*
* Parameter details:
* string: the string may have
* - blanks anywhere
* - sign anywhere
* accepted signs are: + - cr CR ()
* (see examples below)
* - digit separators anywhere
* - currency symbol anywhere
* decComma: if not passed, this defaults to
* decimal point = '.'
* digit separator = ','
* currency: if not passed, defaults to ' '
*
* Examples of input and output (x means parm not passed):
*
* string | dec | sep | cursym | result
* ---------------+-----+-----+--------+------------
* 123 | x | x | x | 123
* +123 | x | x | x | 123
* 123+ | x | x | x | 123
* -123 | x | x | x | -123
* 123- | x | x | x | -123
* (123) | x | x | x | -123
* 12,3 | , | . | x | 12.3
* 12.3 | x | x | x | 12.3
* 1,234,567.3 | x | x | x | 1234567.3
* ,234,567.3 | . | , | $ | 1234567.3
* .234.567,3 | , | . | $ | 1234567.3
* 123.45CR | x | x | x | -123.45
*
* Author: Barbara Morris, IBM Toronto Lab
* Date: March, 2000
*---------------------------------------------------------
D getNum pr 30p 9
D string 100a const varying
D decComma 2a const options(*nopass)
D currency 1a const options(*nopass)
<-----* /COPY 文件的原型在此结束 ----->
<-----* 测试程序从这里开始----->
* Copy prototype for procedure getNum
D/COPY GETNUM_P
D res s like(getNum)
D msg s 52a
C *entry plist
C parm p 32
C parm dc 2
C parm c 1
C select
C when %parms = 1
C eval res = getNum(p)
C when %parms = 2
C eval res = getNum(p : dc)
C when %parms = 3
C eval res = getNum(p : dc : c)
C endsl
C eval msg = '<' + %char(res) + '>'
C msg dsply
C return
<-----* 测试程序到此结束----->
<-----* 模块 GETNUM 从这里开始 ----->
H NOMAIN
* Copy prototype for procedure getNum
D/COPY GETNUM_P
p getNum b
D getNum pi 30p 9
D string 100a const varying
D decComma 2a const options(*nopass)
D currency 1a const options(*nopass)
* defaults for optional parameters
D decPoint s 1a inz('.')
D comma s 1a inz(',')
D cursym s 1a inz(' ')
* structure for building result
D ds
D result 30s 9 inz(0)
D resChars 30a overlay(result)
* variables for gathering digit information
* pNumPart points to the area currently being gathered
* (the integer part or the decimal part)
D pNumPart s *
D numPart s 30a varying based(pNumPart)
D intPart s 30a varying inz('')
D decPart s 30a varying inz('')
* other variables
D intStart s 10i 0
D decStart s 10i 0
D sign s 1a inz('+')
D i s 10i 0
D len s 10i 0
D c s 1a
* override defaults if optional parameters were passed
C if %parms > 1
C eval decPoint = %subst(decComma : 1 : 1)
C eval comma = %subst(decComma : 2 :1)
C endif
C if %parms > 2
C eval cursym = currency
C endif
* initialization
C eval len = %len(string)
* begin reading the integer part
C eval pNumPart = %addr(intPart)
* loop through characters
C do len i
C eval c = %subst(string : i : 1)
C select
* ignore blanks, digit separator, currency symbol
C when c = comma or c = *blank or c = cursym
C iter
* decimal point: switch to reading the decimal part
C when c = decPoint
C eval pNumPart = %addr(decPart)
C iter
* sign: remember the most recent sign
C when c = '+' or c = '-'
C eval sign = c
C iter
* more signs: cr, CR, () are all negative signs
C when c = 'C' or c = 'R' or
C c = 'c' or c = 'r' or
C c = '(' or c = ')'
C eval sign = '-'
C iter
* a digit: add it to the current build area
C other
C eval numPart = numPart + c
C endsl
C enddo
* copy the digit strings into the correct positions in the
* zoned variable, using the character overlay
C eval decStart = %len(result) - %decPos(result)
C + 1
C eval intStart = decStart - %len(intPart)
C eval %subst(resChars
C : intStart
C : %len(intPart))
C = intPart
C eval %subst(resChars
C : decStart
C : %len(decPart))
C = decPart
* if the sign is negative, return a negative value
C if sign = '-'
C return - result
* otherwise, return the positive value
C else
C return result
C endif
p e
<-----* 模块 GETNUM 在此结束 ----->
根据评论和编辑,让我们忽略任何特定的用例,只回答 "how do I count occurrences of a particular character in a string using RPG without embedded SQL?"。
解决评论
I was just curious as to whether or not their was something similar to a BIF that would return a result much easier than using a SCAN
答案是:目前没有任何BIF可以直接给你结果。也就是说,没有像
这样的东西
occurrences = %COUNT(needle:haystack);
如果您使用的是 7.1 或更高版本,最优雅的方法可能是使用 %SCANRPL
,这类似于 SQL 的 REPLACE
。假设 needle
是单个字符而 haystack
是一个可变长度的字符串,它会像
occurrences = %LEN(haystack) - %LEN(%SCANRPL(needle:'':haystack));
也就是说,如果删除所有出现的 needle
,看看 haystack
会缩短多少。 (您可以通过将此结果除以 needle
的长度来将其概括为长度超过一个字符的针。)
如果您使用的是较早的版本,那么您的决定可能是像您所做的那样重复 %SCAN
,或者逐个字符循环 haystack
。前者可能效率更高一些,尤其是在 "needle density" 非常低的情况下,但后者编码更简单,可以说更容易阅读和维护。
此时我想指出,"no SQL" 约束是一个非常人为的约束,就像您在学校作业中遇到的一样。在现实世界的系统中,您不太可能会访问 RPG 而不能访问 RPG-with-embedded-SQL,因此如果有一个优雅、可读的 SQL 解决方案,那么对于真实的 -世界通用,没有理由排除它。
SCAN 操作代码 (OpCode) [与内置的 %SCAN 相比] 具有几乎 满足对字符出现次数进行计数的要求的能力在一个字符串中;通过指定一个数组作为结果字段[或者,用 MI 的说法,接收者;参考来自 RPG 参考和近乎等效的 MI 指令的文档片段]。但是需要第二步。
http://www.ibm.com/support/knowledgecenter/api/content/ssw_ibm_i_71/rzasd/sc092508999.htm#zzscan
SCAN (Scan String)
Free-Form Syntax (not allowed ...
...
The SCAN operation scans a string (base string) ...
...
http://www.ibm.com/support/knowledgecenter/ssw_ibm_i_71/rzatk/SCAN.htm
Scan (SCAN)
以下代码源能够被编译为可调用的绑定 RPGLE 程序[到 v5r1 和可能之前的几个版本];调用时,接受一个 32 字节的输入字符串 [因此可以通过命令行轻松调用 CALL CHARCOUNT PARM('string specified' 'i') /* 显示 结果是:DSPLY 3 */] 和一个单字节字符值作为参数。第一个参数是字符串,根据该字符串计算指定为第二个参数的字符的出现次数。输出是通过存储的 DSPLY 操作码将输入字符串重写为已编辑的出现次数。按原样提供,没有进一步评论,只是声明 %lookup 取决于对数组的顺序搜索:
H dftactgrp(*no) actgrp(*CALLER)
D CHARCOUNT PR ExtPgm('CHARCOUNT')
D inpstring 32A
D findchar 1A
D CHARCOUNT PI
D inpstring 32A
D findchar 1A
D clen C const(32)
D decary S 5S00 dim(clen)
D i S 2P00
c findchar scan inpstring decary
/free
// locate first zero-value array element
i = %lookup(0:decary) - 1 ;
inpstring = %editc(i:'3') ;
DSPLY inpstring ;
*INLR = *ON ;
/end-free
我正在寻找一种方法来计算一个字符在字符串中出现的次数,而无需使用 SQL。
我是 RPGLE 的新手,我创建了一个测试程序,它接受字符格式的用户输入,通过验证,并将成功的数据转换为数字。这些输入之一可以是正整数或负整数。在进行验证时,我测试“-”是否位于第一个位置,并使用 %CHECK 确保输入为 0-9 或“-”。 (例如“-10”通过,“1-0”失败)
但是,如果输入多次出现“-”符号,例如“-1-1-1-1”,它会通过验证并在程序尝试转换为数字时崩溃。
我知道我可以在我的 DDS 中使用编辑代码让系统处理这个问题,但我正在尝试学习不同的方法来让我的程序控制验证。在我的研究中,我发现 TestN 和 Module/Convert/%Error 是可用于确保输出为数字的方法,但我无法针对此特定实例进行测试,因此我无法提供有意义的反馈。
有没有办法计算'-'的出现次数以便我测试它?
由于我的意图似乎有些混乱,我将添加另一个示例。如果我想找出字母 'L' 在单词 'HELLO' 中出现的次数,最好的方法是什么。
%scan() bif 接受 3 个参数 - 起始位置。因此,您可以从最后一次命中的位置开始进行多次扫描。
但是,我不喜欢这种手动验证。从性能的角度来看,假设大多数数据都很好,那么您就是在浪费周期。更重要的是,您所布置的测试要求“-”位于第一个位置意味着“-10”会失败;
如果需要,我更喜欢简单地进行捕获异常的转换。
monitor;
myValue = %dec(myString);
on-error;
// let the user know
endmon;
最后,TESTN
已过时,应避免使用。无论如何,它可能无法按照您想要的方式工作。例如 (IIRC),“5A”通过了 TESTN 测试。
RPG 手册本身对 TESTN 有这样的说法:
自由格式语法 -(不允许 - 在使用变量之前不要测试变量,而是在 MONITOR 组中对变量的用法进行编码,并使用 ON-ERROR 处理任何错误。请参阅错误-处理操作。)
RPG是强类型语言,所以一般来说,如果需要数字,就用数字字段。不要使用字符字段,然后测试并转换为数字。显示文件(使用 DDS)旨在简化此任务(要求用户输入数字)。
也就是说,有时您无法控制该输入。您可能正在处理 EDI 交易或其他文件传输,其中另一方将文本放入字段中,而您需要提取数字部分。对于这种情况,如果您收到类似“-$45,907.12”的信息,您需要做的不仅仅是计算减号的数量。
IBM 的 Barbara Morris 有 posted the following code,这是一个从字符字段中提取数值的例子。它理解减号、小数分隔符、小数点和货币符号。
<-----* /COPY 文件的原型从这里开始 ----->
*---------------------------------------------------------
* getNum - procedure to read a number from a string
* and return a 30p 9 value
* Parameters:
* I: string - character value of number
* I:(opt) decComma - decimal point and digit separator
* I:(opt) currency - currency symbol for monetary amounts
* Returns: packed(30,9)
*
* Parameter details:
* string: the string may have
* - blanks anywhere
* - sign anywhere
* accepted signs are: + - cr CR ()
* (see examples below)
* - digit separators anywhere
* - currency symbol anywhere
* decComma: if not passed, this defaults to
* decimal point = '.'
* digit separator = ','
* currency: if not passed, defaults to ' '
*
* Examples of input and output (x means parm not passed):
*
* string | dec | sep | cursym | result
* ---------------+-----+-----+--------+------------
* 123 | x | x | x | 123
* +123 | x | x | x | 123
* 123+ | x | x | x | 123
* -123 | x | x | x | -123
* 123- | x | x | x | -123
* (123) | x | x | x | -123
* 12,3 | , | . | x | 12.3
* 12.3 | x | x | x | 12.3
* 1,234,567.3 | x | x | x | 1234567.3
* ,234,567.3 | . | , | $ | 1234567.3
* .234.567,3 | , | . | $ | 1234567.3
* 123.45CR | x | x | x | -123.45
*
* Author: Barbara Morris, IBM Toronto Lab
* Date: March, 2000
*---------------------------------------------------------
D getNum pr 30p 9
D string 100a const varying
D decComma 2a const options(*nopass)
D currency 1a const options(*nopass)
<-----* /COPY 文件的原型在此结束 ----->
<-----* 测试程序从这里开始----->
* Copy prototype for procedure getNum
D/COPY GETNUM_P
D res s like(getNum)
D msg s 52a
C *entry plist
C parm p 32
C parm dc 2
C parm c 1
C select
C when %parms = 1
C eval res = getNum(p)
C when %parms = 2
C eval res = getNum(p : dc)
C when %parms = 3
C eval res = getNum(p : dc : c)
C endsl
C eval msg = '<' + %char(res) + '>'
C msg dsply
C return
<-----* 测试程序到此结束----->
<-----* 模块 GETNUM 从这里开始 ----->
H NOMAIN
* Copy prototype for procedure getNum
D/COPY GETNUM_P
p getNum b
D getNum pi 30p 9
D string 100a const varying
D decComma 2a const options(*nopass)
D currency 1a const options(*nopass)
* defaults for optional parameters
D decPoint s 1a inz('.')
D comma s 1a inz(',')
D cursym s 1a inz(' ')
* structure for building result
D ds
D result 30s 9 inz(0)
D resChars 30a overlay(result)
* variables for gathering digit information
* pNumPart points to the area currently being gathered
* (the integer part or the decimal part)
D pNumPart s *
D numPart s 30a varying based(pNumPart)
D intPart s 30a varying inz('')
D decPart s 30a varying inz('')
* other variables
D intStart s 10i 0
D decStart s 10i 0
D sign s 1a inz('+')
D i s 10i 0
D len s 10i 0
D c s 1a
* override defaults if optional parameters were passed
C if %parms > 1
C eval decPoint = %subst(decComma : 1 : 1)
C eval comma = %subst(decComma : 2 :1)
C endif
C if %parms > 2
C eval cursym = currency
C endif
* initialization
C eval len = %len(string)
* begin reading the integer part
C eval pNumPart = %addr(intPart)
* loop through characters
C do len i
C eval c = %subst(string : i : 1)
C select
* ignore blanks, digit separator, currency symbol
C when c = comma or c = *blank or c = cursym
C iter
* decimal point: switch to reading the decimal part
C when c = decPoint
C eval pNumPart = %addr(decPart)
C iter
* sign: remember the most recent sign
C when c = '+' or c = '-'
C eval sign = c
C iter
* more signs: cr, CR, () are all negative signs
C when c = 'C' or c = 'R' or
C c = 'c' or c = 'r' or
C c = '(' or c = ')'
C eval sign = '-'
C iter
* a digit: add it to the current build area
C other
C eval numPart = numPart + c
C endsl
C enddo
* copy the digit strings into the correct positions in the
* zoned variable, using the character overlay
C eval decStart = %len(result) - %decPos(result)
C + 1
C eval intStart = decStart - %len(intPart)
C eval %subst(resChars
C : intStart
C : %len(intPart))
C = intPart
C eval %subst(resChars
C : decStart
C : %len(decPart))
C = decPart
* if the sign is negative, return a negative value
C if sign = '-'
C return - result
* otherwise, return the positive value
C else
C return result
C endif
p e
<-----* 模块 GETNUM 在此结束 ----->
根据评论和编辑,让我们忽略任何特定的用例,只回答 "how do I count occurrences of a particular character in a string using RPG without embedded SQL?"。
解决评论
I was just curious as to whether or not their was something similar to a BIF that would return a result much easier than using a SCAN
答案是:目前没有任何BIF可以直接给你结果。也就是说,没有像
这样的东西occurrences = %COUNT(needle:haystack);
如果您使用的是 7.1 或更高版本,最优雅的方法可能是使用 %SCANRPL
,这类似于 SQL 的 REPLACE
。假设 needle
是单个字符而 haystack
是一个可变长度的字符串,它会像
occurrences = %LEN(haystack) - %LEN(%SCANRPL(needle:'':haystack));
也就是说,如果删除所有出现的 needle
,看看 haystack
会缩短多少。 (您可以通过将此结果除以 needle
的长度来将其概括为长度超过一个字符的针。)
如果您使用的是较早的版本,那么您的决定可能是像您所做的那样重复 %SCAN
,或者逐个字符循环 haystack
。前者可能效率更高一些,尤其是在 "needle density" 非常低的情况下,但后者编码更简单,可以说更容易阅读和维护。
此时我想指出,"no SQL" 约束是一个非常人为的约束,就像您在学校作业中遇到的一样。在现实世界的系统中,您不太可能会访问 RPG 而不能访问 RPG-with-embedded-SQL,因此如果有一个优雅、可读的 SQL 解决方案,那么对于真实的 -世界通用,没有理由排除它。
SCAN 操作代码 (OpCode) [与内置的 %SCAN 相比] 具有几乎 满足对字符出现次数进行计数的要求的能力在一个字符串中;通过指定一个数组作为结果字段[或者,用 MI 的说法,接收者;参考来自 RPG 参考和近乎等效的 MI 指令的文档片段]。但是需要第二步。
http://www.ibm.com/support/knowledgecenter/api/content/ssw_ibm_i_71/rzasd/sc092508999.htm#zzscan
SCAN (Scan String)
Free-Form Syntax (not allowed ...
...
The SCAN operation scans a string (base string) ...
...
http://www.ibm.com/support/knowledgecenter/ssw_ibm_i_71/rzatk/SCAN.htm
Scan (SCAN)
以下代码源能够被编译为可调用的绑定 RPGLE 程序[到 v5r1 和可能之前的几个版本];调用时,接受一个 32 字节的输入字符串 [因此可以通过命令行轻松调用 CALL CHARCOUNT PARM('string specified' 'i') /* 显示 结果是:DSPLY 3 */] 和一个单字节字符值作为参数。第一个参数是字符串,根据该字符串计算指定为第二个参数的字符的出现次数。输出是通过存储的 DSPLY 操作码将输入字符串重写为已编辑的出现次数。按原样提供,没有进一步评论,只是声明 %lookup 取决于对数组的顺序搜索:
H dftactgrp(*no) actgrp(*CALLER)
D CHARCOUNT PR ExtPgm('CHARCOUNT')
D inpstring 32A
D findchar 1A
D CHARCOUNT PI
D inpstring 32A
D findchar 1A
D clen C const(32)
D decary S 5S00 dim(clen)
D i S 2P00
c findchar scan inpstring decary
/free
// locate first zero-value array element
i = %lookup(0:decary) - 1 ;
inpstring = %editc(i:'3') ;
DSPLY inpstring ;
*INLR = *ON ;
/end-free