计算不带 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