开启辅助访问 切换到宽版

精易论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

用微信号发送消息登录论坛

新人指南 邀请好友注册 - 我关注人的新帖 教你赚取精币 - 每日签到


求职/招聘- 论坛接单- 开发者大厅

论坛版规 总版规 - 建议/投诉 - 应聘版主 - 精华帖总集 积分说明 - 禁言标准 - 有奖举报

查看: 303|回复: 7
收起左侧

[其它源码] c++源码 一个模糊匹配算法+打分机制

[复制链接]

结帖率:100% (9/9)
发表于 14 小时前 | 显示全部楼层 |阅读模式   广西壮族自治区崇左市
分享源码
界面截图: -
是否带模块: 纯源码
备注说明: -

fuzzy matching (模糊匹配), 具体可以称为:

✅ Subsequence Matching(子序列匹配)

它的核心思想是:

cha询串的字符按照顺序出现在目标串中,但不要求连续。

比如:

  • cha询 "m_no" 可以匹配 "MB_YESNO",因为字符 'm''_''n''o' 以此顺序在目标串中依次出现。

模糊匹配系统,能够:

  • ✅ 匹配子序列(如 "m_no" 匹配 "MB_YESNO"
  • ✅ 根据匹配质量打分
  • ✅ 将匹配结果按打分排序,最好匹配排最前

这种机制可以参考开源工具 fzyfzf 使用的 模糊搜索评分算法,它综合了以下因素:

  1. 是否匹配单词的开头(比如 _, 大写字母后的开始)
  2. 匹配字符是否靠得近
  3. 字符连续匹配是否越多越好
  4. 匹配位置是否越靠前越好

匹配例子

MB_YESNO  输入 m_no 就能匹配到


MB_YESNO 因为 m 开头 on 又匹配到了后面 YESNO 的内容

使用 m_yo 也能匹配到, 因为 m开头, m往下有个y, y往下有个o
MB_YESNO

m_no 只要文本是有 m , m 后面有个_, _后面有个n, n后面有个o, 这种文本都能匹配, 不需要连续出现, 只要后面有就行
MB_ICONQUESTION 所以  m_no 能匹配到
MB_YESNO 这个使用 m_no 也能能匹配到

m_wcg
WM-WINDOWPOSCHANGED
WM-WINDOWPOSCHANGING
WM-WININICHANGE

代码

#include <vector>
#include <algorithm>

/// <summary>
/// 模糊匹配分数, 并按得分排序, 传递要搜索的数量和cha询的字符串, 返回得分0以上的匹配项
/// </summary>
/// <typeparam name="_Pr">函数指针类型, 用于获取候选字符串</typeparam>
/// <param name="count">要搜索的数量</param>
/// <param name="query">要搜索的字符串</param>
/// <param name="_Pred">获取候选字符串的函数指针</param>
/// <returns>返回匹配结果, 结果包含索引和得分, 结果是按得分从高到低排序的</returns>
template<typename _Pr> 
inline std::vector> FuzzyMatchAndScore(size_t count, const wchar_t* query, _Pr&& _Pred)
{
    // 精确打分算法
    static auto fuzzyMatchScore = [](const wchar_t* query, const wchar_t* target) -> int
    {
        int score = 0;
        int last_match = -1;
        size_t qi = 0, ti = 0;
        size_t query_len = wcslen(query);
        size_t target_len = wcslen(target);

        while (qi < query_len && ti < target_len)
        {
            if (towlower(query[qi]) == towlower(target[ti]))
            {
                score += 10;    // 基础匹配分

                // 位置相关奖励
                if (ti == 0)
                {
                    if (qi == 0)
                    {
                        score += 50;  // 双首字母匹配
                    }
                    else
                    {
                        score += 30;  // 目标首字母匹配
                    }
                }
                else if (qi == 0)
                {
                    score += 20;  // cha询首字母出现在目标中
                }

                // 单词边界检测
                if (ti > 0 && (
                    target[ti - 1] == L'_' ||
                    target[ti - 1] == L' ' ||
                    target[ti - 1] == L'-' ||
                    (iswlower(target[ti - 1]) && iswupper(target[ti]))))
                {
                    score += 25;    // _ 后 / 单词边界 +25
                }

                if (last_match >= 0)
                {
                    int gap = static_cast<int>(ti - last_match - 1);
                    if (gap == 0)
                    {
                        score += 25;    // 连续匹配 +25
                        // 如果是连续第二个字符
                        if (last_match > 0 && ti - last_match == 1)
                        {
                            score += 10;
                        }
                    }
                    else
                    {
                        score -= std::min(static_cast<int>(pow(gap, 1.5)), 15); // 跳跃惩罚:单次惩罚最大 -15
                    }
                }

                last_match = static_cast<int>(ti);
                ++qi;
            }
            ++ti;
        }

        if (qi == query_len)
        {
            // 长度匹配因素
            float length_ratio = static_cast<float>(query_len) / target_len;
            score = static_cast<int>(score * (0.5f + 0.5f * length_ratio));

            // 匹配位置因素
            float position_ratio = 1.0f - (static_cast<float>(ti) / target_len);
            score = static_cast<int>(score * (0.7f + 0.3f * position_ratio));

            return std::max(score, 1);  // 匹配成功,至少1分
        }
        return 0;
    };

    std::vector> results;

    for (int i = 0; i < (int)count; ++i)
    {
        int score = fuzzyMatchScore(query, _Pred(i));
        if (score > 0)
        {
            results.push_back({ i, score });
        }
    }

    // 按得分从高到低排序
    std::sort(results.begin(), results.end(), [](const std::pair<int, int>& a, const std::pair<int, int>& b)
    {
        return a.second != b.second ? a.second > b.second : a.first < b.first;
    });

    return results;
}

调用例子

#include <iostream>
#include <climits>
#include <locale.h> 

int main() {

    setlocale(LC_ALL, "chs");

    const wchar_t* candidates[] = {
        L"MB_YESNO",
        L"MB_ICONQUESTION",
        L"MB_ABORTRETRYIGNORE",
        L"IDOK",
        L"IDCANCEL",
        L"MESSAGEBOX_YESNO"
    };

    const size_t count = sizeof(candidates) / sizeof(candidates[0]);

    auto results = FuzzyMatchAndScore(count, L"m_no", [&candidates](int i)
    {
        return candidates[i];
    });

    wprintf(L"总共匹配到 %zu 项:\n", results.size());
    for (const auto& result : results)
    {
        wprintf(L"\t%s(得分: %d)\n", candidates[result.first], result.second);
    }

    return 0;
}

输出结果:

总共匹配到 4 项:
        MB_YESNO(得分: 62)
        MB_ABORTRETRYIGNORE(得分: 47)
        MESSAGEBOX_YESNO(得分: 45)
        MB_ICONQUESTION(得分: 31)

长度匹配因素 = cha询文本长度 / 目标文本长度
匹配位置因素 = 1.0 - (目标文本cha询位置 / 目标文本长度)
计算公式: (总分 (0.5 + 0.5 长度匹配因素)) (0.7 + 0.3 匹配位置因素)

m_no   MB_YESNO

步骤 匹配字符 位置 得分说明
1 m 0 基础+10, 双首字母匹配+50
2 _ 2 基础+10, 跳跃1个字符惩罚-1
3 n 6 基础+10, 跳跃3个字符惩罚-5
4 o 7 基础+10, 连续匹配+25, 二次连续+10
  • 总分: 10+50+10-1+10-5+10+25+10=119
  • 长度匹配因素: 4 / 8 = 0.5
  • 匹配位置因素: 1.0 - (8 / 8) = 0.0
  • 最终得分:套入长度/位置因素, 总得分62

m_no   MB_ICONQUESTION

步骤 匹配字符 位置 得分说明
1 m 0 基础+10, 双首字母匹配+50
2 _ 2 基础+10, 跳跃1个字符惩罚-1
3 n 6 基础+10, 跳跃3个字符惩罚-5
4 o 13 基础+10, 跳跃6个字符惩罚-14
  • 总分: 10+50+10-1+10-5+10-14=70
  • 长度匹配因素: 4 / 15 = 0.266666681
  • 匹配位置因素: 1.0 - (14 / 15) = 0.0666666627
  • 最终得分:套入长度/位置因素, 总得分31

m_no   MB_ABORTRETRYIGNORE

步骤 匹配字符 位置 得分说明
1 m 0 基础+10, 双首字母匹配+50
2 _ 2 基础+10, 跳跃1个字符惩罚-1
3 n 15 基础+10, 跳跃12个字符惩罚-15
4 o 16 基础+10, 连续匹配+25, 二次连续+10
  • 总分: 10+50+10-1+10-15+10+25+10=109
  • 长度匹配因素: 4 / 19 = 0.210526317
  • 匹配位置因素: 1.0 - (17 / 19) = 0.105263174
  • 最终得分:套入长度/位置因素, 总得分47

m_no   MESSAGEBOX_YESNO

步骤 匹配字符 位置 得分说明
1 m 0 基础+10, 双首字母匹配+50
2 _ 10 基础+10, 跳跃9个字符惩罚-15
3 n 14 基础+10, 跳跃3个字符惩罚-5
4 o 15 基础+10, 连续匹配+25, 二次连续+10
  • 总分: 10+50+10-15+10-5+10+25+10=105
  • 长度匹配因素: 4 / 15 = 0.25
  • 匹配位置因素: 1.0 - (16 / 16) = 0.0
  • 最终得分:套入长度/位置因素, 总得分45

IDOK 和 IDCANCEL 得分为0, 没有匹配到, 这里就没有列出表格

得到以上分数后进行排序, 得分越大越靠前

文章发表于: https://www.kuodafu.com/?p=178

点评

我也对三年前这个帖子有印象   江西省南昌市  发表于 7 小时前
3年前的一个问题, 今天才想起来去问ai, 结果真出结果了.... https://bbs.125.la/thread-14724795-1-1.html   广西壮族自治区崇左市  发表于 14 小时前

签到天数: 1 天

发表于 2 小时前 | 显示全部楼层   广东省惠州市
好好顶贴,认真学习
回复 支持 反对

使用道具 举报

发表于 12 小时前 | 显示全部楼层   河北省石家庄市
感谢分享
回复 支持 反对

使用道具 举报

签到天数: 1 天

发表于 13 小时前 | 显示全部楼层   河南省驻马店市
简单明了
回复 支持 反对

使用道具 举报

签到天数: 4 天

发表于 13 小时前 | 显示全部楼层   湖北省鄂州市
感谢分享!
我想知道是怎么问ai的
回复 支持 反对

使用道具 举报

结帖率:0% (0/1)

签到天数: 1 天

发表于 14 小时前 | 显示全部楼层   湖北省十堰市
感谢,看看!.........
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 致发广告者

发布主题 收藏帖子 返回列表

sitemap| 易语言源码| 易语言教程| 易语言论坛| 易语言模块| 手机版| 广告投放| 精易论坛
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论,本站内容均为会员发表,并不代表精易立场!
论坛帖子内容仅用于技术交流学习和研究的目的,严禁用于非法目的,否则造成一切后果自负!如帖子内容侵害到你的权益,请联系我们!
防范网络诈骗,远离网络犯罪 违法和不良信息举报电话0663-3422125,QQ: 793400750,邮箱:wp@125.la
网站简介:精易论坛成立于2009年,是一个程序设计学习交流技术论坛,隶属于揭阳市揭东区精易科技有限公司所有。
Powered by Discuz! X3.4 揭阳市揭东区精易科技有限公司 ( 粤ICP备12094385号-1) 粤公网安备 44522102000125 增值电信业务经营许可证 粤B2-20192173

快速回复 返回顶部 返回列表