注册 登录
编程论坛 VFP论坛

挑战DeepSeek!感觉上,TA写VFP代码没太大问题啊。

cssnet 发布于 2025-04-13 12:51, 875 次点击
提问:
StrExtract(x,"<",">")可以提取两个分隔符之间的字符串,请实现函数StrDelExtract(x,"<",">"),用途是删除两个分隔符之间的字符串(类似于批量删除<XML>标签一类操作)。

回答:
FUNCTION StrDelExtract(cString, cBeginDelim, cEndDelim)
    LOCAL nStartPos, nEndPos, cResult, i, nOccurrence
   
    * 参数检查
    IF VARTYPE(cString) <> "C" OR EMPTY(cString)
        RETURN ""
    ENDIF
    IF VARTYPE(cBeginDelim) <> "C" OR EMPTY(cBeginDelim)
        RETURN cString
    ENDIF
    IF VARTYPE(cEndDelim) <> "C" OR EMPTY(cEndDelim)
        RETURN cString
    ENDIF
    nOccurrence = OCCURS(cBeginDelim, cString)  && 统计匹配数目
    IF nOccurrence = 0
        RETURN cString
    ENDIF
   
    * 初始化
    cResult = cString
   
    * 循环处理所有匹配项
    for i = 1 to nOccurrence
        nStartPos = AT(cBeginDelim, cResult)
        nEndPos = AT(cEndDelim, cResult)
        IF nEndPos = 0
            EXIT
        ENDIF
        * 删除匹配部分
        cResult = STUFF(cResult, nStartPos, nEndPos - nStartPos + LEN(cEndDelim), "")      
    ENDFOR i
   
    RETURN cResult
ENDFUNC

*---------------*
DeepSeek的原始代码用Do...while,随手改为For...Endfor,TA写的这段VFP代码,似乎也没啥好再优化的了。
14 回复
#2
cssnet2025-04-13 14:18
当然,有些容错,TA还是写漏了,比如:
假设我故意将分隔符写成>xxx<,又或者,写成<<xxx>>,又或者……AI毕竟斗不过险恶的人心哪。嘿嘿。


#3
csyx2025-04-13 14:47
能写成这样已经算不错了
不一定要故意写错,像这样一段简单且合法的 xml 就被忽略了
程序代码:
<?xml version="1.0" encoding="gbk"?>
<xml>
    <text>普通文本行</text>
    <numeric>12345</numeric>
    <html><![CDATA[<p>HTML文本</p>]]></html>
</xml>

#4
cssnet2025-04-13 15:08
TA还提供了更简洁的正则表达式(复杂匹配):

* 需引用 VBScript.RegExp
oRegEx = CREATEOBJECT("VBScript.RegExp")
oRegEx.Pattern = "\<.*?\>"  && 非贪婪匹配<...>
oRegEx.Global = .T.
cResult = oRegEx.Replace(cString, "")
……

只是,在我心目中,这样一个函数可能会这么用:

replace all content with StrDelExtract(content,"<",">")

在成千上万行记录中,创建成千上万次对象"VBScript.RegExp",然后再成千上万次销毁,感觉上总有些莫名的奢侈。
摇摇头心想还是算了罢。


#5
csyx2025-04-13 15:40
以下是引用cssnet在2025-4-13 15:08:37的发言:
TA还提供了更简洁的正则表达式(复杂匹配):
。。。
在成千上万行记录中,创建成千上万次对象"VBScript.RegExp",然后再成千上万次销毁,感觉上总有些莫名的奢侈。
摇摇头心想还是算了罢。

你就不能把函数改下?先创建并设置好 oRegEx,然后作为参数传给 StrDelExtract
再或者,创建一个 goRegEx,省得还要传入

[此贴子已经被作者于2025-4-13 15:41编辑过]

#6
cssnet2025-04-13 17:43
以下是引用csyx在2025-4-13 15:40:12的发言:
你就不能把函数改下?先创建并设置好 oRegEx,然后作为参数传给 StrDelExtract
再或者,创建一个 goRegEx,省得还要传入


这……这……这,假假地,这也算一个通用函数吧?要求调用者事先创建一个oRegEx,然后再调用,万一不留神,忘了这茬咧?这“复用性”……未免也太过强人所难了吧?
#7
sam_jiang2025-04-13 17:51
回复 楼主 cssnet
都已经用strextract,取出那个内容了,删除还需要那么麻烦吗?
直接用strtran就可以了!
STRTRAN(cSearched, cExpressionSought [, cReplacement][, nStartOccurrence] [, nNumberOfOccurrences] [, nFlags])
程序代码:

parameters corgstr,cBeginDelim, cEndDelim
cdelstr=strexact(corgstr,cBeginDelim, cEndDelim)
if #empty(cdelstr)
    corgstr=strtran(corgstr,cdelstr,"")
    *如果需要把2个定位字符串也删除的话,可以改成如下
    *corgstr=strtran(corgstr,cBeginDelim+cdelstr+cEndDelim,"")
endif
return corgstr


如果有多个符合条件的,就设置一个do while循环操作就可以了。do while 通常用于不知道循环次数的操作,而for循环是用于知道确定循环次数的操作。。。
#8
csyx2025-04-13 20:49
以下是引用cssnet在2025-4-13 17:43:33的发言:
这也算一个通用函数吧?要求调用者事先创建一个oRegEx,然后再调用,万一不留神,忘了这茬咧?这“复用性”……未免也太过强人所难了吧?

为了执行效率牺牲一点通用性也算是一种妥协吧,你要是觉得这种强耦合不太优雅,另写一个 GetRegExObject() 也可以啊,如果 goRegEx 存在就直接返回它,否则就用 NewObject 新建一个 goRegEx 并返回也未尝不可
#9
cssnet2025-04-13 21:55
参照DeepSeek的回答,我也挖空心思实现了另一个版本,只可惜忘了妥善处理分隔符嵌套的情形

FUNCTION StrExcise(cString, cBeginDelim, cEndDelim)
    LOCAL nStartPos, nEndPos, cResult, i, nOccurrence
   
    * 参数检查
    IF VARTYPE(cString) <> "C" OR EMPTY(cString)
        RETURN ""
    ENDIF
    IF VARTYPE(cBeginDelim) <> "C" OR EMPTY(cBeginDelim)
        RETURN cString
    ENDIF
    IF VARTYPE(cEndDelim) <> "C" OR EMPTY(cEndDelim)
        RETURN cString
    ENDIF
    nOccurrence = OCCURS(cBeginDelim, cString)  && 统计匹配数目
    IF nOccurrence = 0
        RETURN cString
    ENDIF
   
    * 分割源字符串
    nLines = alines(a_lines, cString, 16, cBeginDelim, cEndDelim, 0h0D0A)
    * 初始化
    cResult = ""

    * 循环处理所有匹配项
    for i=1 to nLines
        nStartPos = AT(cBeginDelim, a_lines[i])
        nEndPos = AT(cEndDelim, a_lines[i])
    Do case
        case nStartPos > 0
           cResult = cResult + left(a_lines[i], nStartPos-1)
        case nEndPos > 0
        otherwise
           cResult = cResult + a_lines[i] + 0h0D0A
        Endcase
    ENDFOR i
   
    RETURN cResult
ENDFUNC

#10
cssnet2025-04-14 10:42
第二个问题受7#启发,相对简单些:
函数strTran(x,"<",">")可以批量删除或替换字符表达式或备注字段中的关键字,请实现函数LineTran(),用途是,将字符表达式或备注字段中含有关键字的一整行,批量替换或删除。

回答:
FUNCTION LineTran(cMemoText, cKeyLine, cReplaceTo)
    LOCAL cResult, nLineCount, i, cLine
   
    * 参数检查
    IF VARTYPE(cMemoText) <> "C" OR EMPTY(cMemoText)
        RETURN ""
    ENDIF
    IF VARTYPE(cKeyLine) <> "C" OR EMPTY(cKeyLine)
        RETURN cMemoText
    ENDIF
    IF VARTYPE(cReplaceTo) <> "C"
        cReplaceTo = ""
    ENDIF
   
    * 按行处理
    cResult = ""
    nLineCount = ALINES(a_Lines, cMemoText)
   
    FOR i = 1 TO nLineCount
        cLine = a_Lines[i]
        * 判断是否包含关键字(区分大小写)
        IF at(cKeyLine, cLine) > 0
        if ! empty(cReplaceTo)
        cResult = cResult + cReplaceTo + IIF(i < nLineCount, CHR(13) + CHR(10), "")
        endif
        ELSE
            cResult = cResult + cLine + IIF(i < nLineCount, CHR(13) + CHR(10), "")
        ENDIF
    ENDFOR
   
    RETURN cResult
ENDFUNC

*---------------*
注:同上,DeepSeek的原始代码用Do...while,随手改为For...Endfor。
#11
sam_jiang2025-04-14 19:42
回复 10楼 cssnet
用deepseek确实可以做到事半功倍
#12
cssnet2025-04-26 14:25
问:
设“金银花”的配料表为“胆酸 珍珠母 栀子 水牛角 板蓝根 黄芩苷”,“板蓝根”的配料表为“麻黄 地龙 胆酸 栀子 五味子 黄芩苷 蝉蜕 金银花 ”,用VFP实现一个函数,计算两个输入参数(以空格作为分隔符的字符串)的匹配度。

答:
*-----注:DeepSeek给出的代码,个别行可能有误!------*
FUNCTION IngredientMatch
LPARAMETERS tcList1, tcList2
    * 函数功能:计算两个配料表的匹配度
    * 参数:tcList1 - 第一个配料表字符串(空格分隔)
    *       tcList2 - 第二个配料表字符串(空格分隔)
    * 返回值:匹配度百分比(0-100)
   
    * 分割字符串为数组
    LOCAL lnCount1, lnCount2, lnMatchCount, i, j
    LOCAL ARRAY laList1[1], laList2[1]
   
    * 分割第一个配料表
    lnCount1 = ALINES(laList1, STRTRAN(ALLTRIM(tcList1), "  ", " "))
    lnCount1 = ALINES(laList1, tcList1, " ")
   
    * 分割第二个配料表
    lnCount2 = ALINES(laList2, STRTRAN(ALLTRIM(tcList2), "  ", " ")
    lnCount2 = ALINES(laList2, tcList2, " ")
   
    * 计算匹配的配料数量
    lnMatchCount = 0
   
    * 检查laList1中的配料是否在laList2中
    FOR i = 1 TO lnCount1
        FOR j = 1 TO lnCount2
            IF UPPER(ALLTRIM(laList1[i])) == UPPER(ALLTRIM(laList2[j]))
                lnMatchCount = lnMatchCount + 1
                EXIT
            ENDIF
        ENDFOR
    ENDFOR
   
    * 计算匹配度百分比(基于两个列表的平均长度)
    LOCAL lnAvgLength, lnMatchPercent
    lnAvgLength = (lnCount1 + lnCount2) / 2
   
    IF lnAvgLength > 0
        lnMatchPercent = (lnMatchCount / lnAvgLength) * 100
    ELSE
        lnMatchPercent = 0
    ENDIF
   
    * 四舍五入到两位小数
    lnMatchPercent = ROUND(lnMatchPercent, 2)
   
    RETURN lnMatchPercent
ENDFUNC


#13
cssnet2025-04-26 14:29
问:
设配料表中的子配料,如“胆酸”,也由若干配料构成:“栀子 地龙 麻黄 前胡 枇杷叶”,“黄芩苷”的配料:“牛蒡子 胆酸 麻黄 紫苏”……,重构此函数,以类递归方式计算出更复杂的匹配度。

答:
<略>

问:
上面的函数以纯数组方式实现,无疑将问题复杂化了。现改用数据表Ingredient.dbf储存3000多种子配料信息,表结构为3个字段:name C (20),detail M,ID I。字段name是子配料名称,字段detail是以空格分隔的具体的子配料字符串,字段ID是整型数值。递归遍历此1000多种子配料表,若计算得到2个、3个、4个……或更多的子配料匹配度>=66.66%,则将其ID设为相同数值,即,将同一类的子配料,归为同一组。请重构函数,实现此算法。

答:
*----------注:deepSeek给出的代码有些复杂,我老人家此刻正在研读中……
*-- 主函数:配料匹配与分组
FUNCTION GroupIngredients
LPARAMETERS tnThreshold
    * 参数:tnThreshold - 分组匹配度阈值(51%)
    * 功能:递归分析配料表,将匹配度>=阈值的配料归为同一组
   
    LOCAL lnGroupID, lcSQL, lnCount
   
    * 初始化分组ID
    SELECT MAX(ID) FROM Ingredient INTO ARRAY laMaxID
    lnGroupID = IIF(ISNULL(laMaxID[1]), 1, laMaxID[1] + 1)
   
    * 创建临时表存储匹配结果
    CREATE CURSOR TempMatches (;
        Name1 C(20),;
        Name2 C(20),;
        MatchPercent N(5,2);
    )
   
    * 比较所有配料组合
    lcSQL = "SELECT A.Name AS Name1, B.Name AS Name2 ";
          + "FROM Ingredient A, Ingredient B ";
          + "WHERE A.Name < B.Name"  // 避免重复比较
   
    &lcSQL INTO CURSOR TempPairs
   
    SELECT TempPairs
    SCAN
        * 计算两个配料的匹配度
        lnMatch = CalculateMatch(TempPairs.Name1, TempPairs.Name2)
        
        * 存储匹配结果
        INSERT INTO TempMatches VALUES ;
            (TempPairs.Name1, TempPairs.Name2, lnMatch)
    ENDSCAN
   
    * 根据匹配结果分组
    SELECT * FROM TempMatches ;
        WHERE MatchPercent >= tnThreshold ;
        ORDER BY MatchPercent DESC ;
        INTO CURSOR TempValidMatches
   
    SELECT TempValidMatches
    SCAN
        * 查找两个配料的当前组ID
        LOCAL lnID1, lnID2
        SELECT ID FROM Ingredient WHERE Name = TempValidMatches.Name1 INTO ARRAY laID1
        SELECT ID FROM Ingredient WHERE Name = TempValidMatches.Name2 INTO ARRAY laID2
        
        lnID1 = IIF(_TALLY > 0, laID1[1], 0)
        lnID2 = IIF(_TALLY > 0, laID2[1], 0)
        
        * 分组逻辑
        DO CASE
        CASE lnID1 = 0 AND lnID2 = 0
            * 两个配料都未分组,赋予新组ID
            UPDATE Ingredient SET ID = lnGroupID ;
                WHERE Name = TempValidMatches.Name1 OR Name = TempValidMatches.Name2
            lnGroupID = lnGroupID + 1
            
        CASE lnID1 = 0
            * 配料1未分组,加入配料2的组
            UPDATE Ingredient SET ID = lnID2 WHERE Name = TempValidMatches.Name1
            
        CASE lnID2 = 0
            * 配料2未分组,加入配料1的组
            UPDATE Ingredient SET ID = lnID1 WHERE Name = TempValidMatches.Name2
            
        CASE lnID1 <> lnID2
            * 两个配料属于不同组,合并两组
            UPDATE Ingredient SET ID = lnID1 WHERE ID = lnID2
        ENDCASE
    ENDSCAN
   
    * 返回分组数量
    SELECT COUNT(DISTINCT ID) FROM Ingredient WHERE ID > 0 INTO ARRAY laGroupCount
    RETURN laGroupCount[1]
ENDFUNC

*-- 递归计算两个配料的匹配度
FUNCTION CalculateMatch
LPARAMETERS tcName1, tcName2, tnDepth
    * 参数:tcName1, tcName2 - 要比较的配料名称
    *       tnDepth - 当前递归深度
   
    * 初始化参数
    tnDepth = IIF(EMPTY(tnDepth), 1, tnDepth)
    IF tnDepth > 5  && 限制递归深度
        RETURN 0
    ENDIF
   
    * 获取配料详情
    LOCAL lcDetail1, lcDetail2
    SELECT detail FROM Ingredient WHERE Name = tcName1 INTO ARRAY laDetail1
    SELECT detail FROM Ingredient WHERE Name = tcName2 INTO ARRAY laDetail2
   
    lcDetail1 = IIF(_TALLY > 0, laDetail1[1], "")
    lcDetail2 = IIF(_TALLY > 0, laDetail2[1], "")
   
    * 如果都没有子配料,直接比较名称
    IF EMPTY(lcDetail1) AND EMPTY(lcDetail2)
        RETURN IIF(UPPER(tcName1) == UPPER(tcName2), 100, 0)
    ENDIF
   
    * 分割子配料
    LOCAL lnCount1, lnCount2, lnMatchCount, i, j
    LOCAL ARRAY laItems1[1], laItems2[1]
   
    lnCount1 = ALINES(laItems1, lcDetail1, " ")
    lnCount2 = ALINES(laItems2, lcDetail2, " ")
   
    * 计算直接匹配
    lnMatchCount = 0
    FOR i = 1 TO lnCount1
        FOR j = 1 TO lnCount2
            IF UPPER(ALLTRIM(laItems1[i])) == UPPER(ALLTRIM(laItems2[j]))
                lnMatchCount = lnMatchCount + 1
                EXIT
            ENDIF
        ENDFOR
    ENDFOR
   
    * 计算递归匹配
    LOCAL lnRecursiveMatch, lnTotalWeight
    lnRecursiveMatch = 0
    lnTotalWeight = 0
   
    FOR i = 1 TO lnCount1
        FOR j = 1 TO lnCount2
            * 跳过已直接匹配的
            IF UPPER(ALLTRIM(laItems1[i])) == UPPER(ALLTRIM(laItems2[j]))
                LOOP
            ENDIF
            
            * 计算子配料匹配度
            LOCAL lnSubMatch
            lnSubMatch = CalculateMatch(laItems1[i], laItems2[j], tnDepth + 1)
            
            * 加权累加
            IF lnSubMatch > 0
                lnRecursiveMatch = lnRecursiveMatch + lnSubMatch
                lnTotalWeight = lnTotalWeight + 1
            ENDIF
        ENDFOR
    ENDFOR
   
    * 计算总匹配度
    LOCAL lnTotalMatch, lnAvgLength
    lnAvgLength = (lnCount1 + lnCount2) / 2
   
    IF lnTotalWeight > 0
        lnRecursiveMatch = lnRecursiveMatch / lnTotalWeight  && 平均递归匹配度
    ENDIF
   
    lnTotalMatch = lnMatchCount + lnRecursiveMatch
   
    IF lnAvgLength > 0
        lnTotalMatch = (lnTotalMatch / lnAvgLength) * 100
    ELSE
        lnTotalMatch = 0
    ENDIF
   
    RETURN ROUND(lnTotalMatch, 2)
ENDFUNC

#14
cssnet2025-04-26 16:41
讲真,13#的算法比较复杂,我只是突发奇想设计了一个古怪题目,结果DeepSeek毫不费力将代码写出来了。

想要看懂这段代码,还真不是件容易的事情!再者,我手头也并没有数据表去测试DeepSeek实现的函数。

若挑战的不是DeepSeek,而是人,能秒提供一份更好的算法方案吗?
#15
sam_jiang2025-04-27 21:19
Deepseek给的方案很正确,不难读懂,解释一下:
1,用aline得到每个配料表配料的数量;
2,对配料多的那个做一个for循环,对少的那个配料表做strtran,如果那个配料表变化了,说明有相同配料,记数;
3,直到循环结束,看看计数累计到多少?
4,用计数除以总配料数,就是匹配度,不是吗?
1