本期依旧使用豆包辅助完成代码。

从功能到体验的转变

上个版本已经实现了问答系统的核心功能:基于 TF-IDF 算法的问题匹配和回答。它能够读取训练数据,处理用户输入,并返回最相关的答案。但在用户体验方面还有很大提升空间。

让我们看看改进版做了哪些关键优化:

1. 引导系统

上个版本仅在启动时显示简单的 "Hello! 输入 'exit' 结束对话。" 提示,对于初次使用的用户来说不够友好。

改进版增加了:

  • 详细的欢迎信息和功能介绍
  • 专门的showHelp()函数,提供完整的使用指南
  • showTopics()函数,展示系统能回答的问题类型示例

这些引导信息让用户能快速了解系统功能和使用方法,减少了使用障碍。

2. 命令系统

上个版本仅支持 "exit" 一个命令,功能单一。

改进版扩展为多个命令:

  • "exit" 或 "quit":退出程序(支持多种退出方式)
  • "help":查看帮助信息
  • "topics":了解系统能回答的问题类型

多样化的命令让用户能更好地掌控交互过程,提升了系统的可用性。

3.交互提示

上个版本使用简单的 "You:" 作为输入提示,显得生硬。

改进版对此进行了全面优化:

  • 输入提示改为更亲切的 "请输入您的问题:"
  • 机器人回复前缀从 "Robot:" 改为 "机器人:",更符合中文语境
  • 增加空输入处理,当用户输入为空时给予明确提示

这些细节变化让整个交互过程更加自然流畅。

4. 智能的错误处理与引导

上个版本在无法回答问题时,仅简单返回 "I don't know how to answer this question.",没有提供进一步指导。

改进版则提供了的建议:

cout << "机器人: 抱歉,我不太理解这个问题。" << endl;
cout << "您可以尝试:" << endl;
cout << "- 用不同的方式表述问题" << endl;
cout << "- 输入 'topics' 查看我能回答的问题类型" << endl;
cout << "- 输入 'help' 查看帮助信息" << endl;

这种处理方式不仅告知用户问题,还提供了解决方案,大大降低了用户的挫败感。

5. 错误提示

对于文件打开失败等错误情况,改进版提供了更具体的指导:

cout << "无法打开训练文件 training_data.txt" << endl;
cout << "请确保该文件存在于程序运行目录下" << endl;
cout << "程序将退出..." << endl;

相比上个版本简单的错误提示,用户能更清楚地了解问题所在及如何解决。

为什么这些改进很重要?

这些看似细微的变化,实际上对用户体验有着显著影响:

  1. 降低学习成本:良好的引导让新用户能快速上手
  2. 减少挫败感:当系统无法回答时,提供建设性建议
  3. 增强掌控感:丰富的命令系统让用户能更好地控制交互过程
  4. 提升信任度:专业的错误处理和提示让用户更信任系统能力

在 AI 助手和问答系统日益普及的今天,技术实现固然重要,但能否提供自然、友好的交互体验往往是决定产品成败的关键因素。

代码

#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <set>
using namespace std;// 将字符串转换为小写
string toLower(const string& str) {string result = str;for (string::size_type i = 0; i < result.length(); ++i) {result[i] = tolower(result[i]);}return result;
}// 从字符串中提取关键词
vector<string> extractKeywords(const string& text) {vector<string> keywords;string word;for (string::const_iterator it = text.begin(); it != text.end(); ++it) {if (isalnum(*it)) {word += *it;} else if (!word.empty()) {keywords.push_back(toLower(word));word.clear();}}if (!word.empty()) {keywords.push_back(toLower(word));}return keywords;
}// 显示帮助信息
void showHelp() {cout << "\n===== 使用帮助 =====" << endl;cout << "1. 直接输入您的问题,我会尽力为您解答" << endl;cout << "2. 输入 'exit' 或 'quit' 结束对话" << endl;cout << "3. 输入 'help' 查看帮助信息" << endl;cout << "4. 输入 'topics' 查看我能回答的问题类型" << endl;cout << "====================\n" << endl;
}// 显示可回答的话题类型
void showTopics(const map<string, string>& exactAnswers) {if (exactAnswers.empty()) {cout << "暂无可用的话题信息" << endl;return;}cout << "\n===== 我可以回答这些类型的问题 =====" << endl;// 提取部分问题作为示例(最多显示5个)int count = 0;for (map<string, string>::const_iterator it = exactAnswers.begin(); it != exactAnswers.end() && count < 5; ++it, ++count) {string sample = it->first;if (sample.length() > 30) {sample = sample.substr(0, 30) + "...";}cout << "- " << sample << endl;}if (exactAnswers.size() > 5) {cout << "... 还有 " << (exactAnswers.size() - 5) << " 个其他话题" << endl;}cout << "=================================\n" << endl;
}// 计算TF-IDF并返回最佳匹配答案
string getBestAnswerByTFIDF(const vector<string>& userKeywords,const map<string, vector<string> >& qas,const map<string, vector<string> >& questionKeywords,const map<string, double>& idfValues) {// 计算用户问题的TF-IDF向量map<string, double> userTFIDF;for (vector<string>::const_iterator kit = userKeywords.begin(); kit != userKeywords.end(); ++kit) {const string& keyword = *kit;// 计算词频(TF)double tf = 0.0;for (vector<string>::const_iterator it = userKeywords.begin(); it != userKeywords.end(); ++it) {if (*it == keyword) tf++;}tf /= userKeywords.size();// 获取IDF值double idf = 0.0;map<string, double>::const_iterator idfIt = idfValues.find(keyword);if (idfIt != idfValues.end()) {idf = idfIt->second;}// 计算TF-IDFuserTFIDF[keyword] = tf * idf;}// 计算每个问题的相似度map<string, double> similarityScores;for (map<string, vector<string> >::const_iterator pit = questionKeywords.begin(); pit != questionKeywords.end(); ++pit) {const string& question = pit->first;const vector<string>& keywords = pit->second;// 计算问题的TF-IDF向量map<string, double> questionTFIDF;for (vector<string>::const_iterator kit = keywords.begin(); kit != keywords.end(); ++kit) {const string& keyword = *kit;// 计算词频(TF)double tf = 0.0;for (vector<string>::const_iterator it = keywords.begin(); it != keywords.end(); ++it) {if (*it == keyword) tf++;}tf /= keywords.size();// 获取IDF值double idf = 0.0;map<string, double>::const_iterator idfIt = idfValues.find(keyword);if (idfIt != idfValues.end()) {idf = idfIt->second;}// 计算TF-IDFquestionTFIDF[keyword] = tf * idf;}// 计算余弦相似度double dotProduct = 0.0;double userNorm = 0.0;double questionNorm = 0.0;// 计算点积和范数for (map<string, double>::const_iterator uit = userTFIDF.begin(); uit != userTFIDF.end(); ++uit) {const string& keyword = uit->first;double userWeight = uit->second;userNorm += userWeight * userWeight;map<string, double>::const_iterator qit = questionTFIDF.find(keyword);if (qit != questionTFIDF.end()) {dotProduct += userWeight * qit->second;}}for (map<string, double>::const_iterator qit = questionTFIDF.begin(); qit != questionTFIDF.end(); ++qit) {double questionWeight = qit->second;questionNorm += questionWeight * questionWeight;}userNorm = sqrt(userNorm);questionNorm = sqrt(questionNorm);// 计算相似度double similarity = 0.0;if (userNorm > 0 && questionNorm > 0) {similarity = dotProduct / (userNorm * questionNorm);}similarityScores[question] = similarity;}// 找到相似度最高的问题string bestQuestion;double maxSimilarity = 0.0;for (map<string, double>::const_iterator it = similarityScores.begin(); it != similarityScores.end(); ++it) {if (it->second > maxSimilarity) {maxSimilarity = it->second;bestQuestion = it->first;}}// 如果相似度足够高,返回对应的答案if (maxSimilarity >= 0.2) { // 相似度阈值map<string, vector<string> >::const_iterator ansIt = qas.find(bestQuestion);if (ansIt != qas.end() && !ansIt->second.empty()) {return ansIt->second[0]; // 假设第一个答案是最佳答案}}return ""; // 没有找到匹配
}int main() {// 存储训练数据map<string, string> exactAnswers;       // 精确匹配回答map<string, vector<string> > qas;        // 问题-回答映射map<string, vector<string> > questionKeywords; // 问题-关键词映射map<string, int> documentFrequency;     // 关键词-文档频率映射// 加载训练数据ifstream trainingFile("training_data.txt");if (trainingFile.is_open()) {string line;string question = "";bool readingAnswer = false;int totalDocuments = 0;while (getline(trainingFile, line)) {// 空行表示一个问答对结束if (line.empty()) {question = "";readingAnswer = false;continue;}// 问题行以Q:开头if (line.substr(0, 2) == "Q:") {question = line.substr(2);readingAnswer = false;totalDocuments++;}// 回答行以A:开头else if (line.substr(0, 2) == "A:") {if (!question.empty()) {string answer = line.substr(2);// 保存精确匹配回答exactAnswers[question] = answer;// 保存问题-回答映射qas[question].push_back(answer);// 提取关键词并保存vector<string> keywords = extractKeywords(question);questionKeywords[question] = keywords;// 更新文档频率set<string> uniqueKeywords;for (vector<string>::const_iterator it = keywords.begin(); it != keywords.end(); ++it) {uniqueKeywords.insert(*it);}for (set<string>::const_iterator it = uniqueKeywords.begin(); it != uniqueKeywords.end(); ++it) {documentFrequency[*it]++;}}readingAnswer = true;}// 多行回答的后续行else if (readingAnswer && !question.empty()) {exactAnswers[question] += "\n" + line;qas[question].back() += "\n" + line;}}trainingFile.close();cout << "已加载 " << exactAnswers.size() << " 条训练数据" << endl;// 计算IDF值map<string, double> idfValues;for (map<string, int>::const_iterator it = documentFrequency.begin(); it != documentFrequency.end(); ++it) {const string& keyword = it->first;int df = it->second;// IDF公式: log(总文档数 / (包含该词的文档数 + 1)) + 1double idf = log((double)totalDocuments / (df + 1)) + 1;idfValues[keyword] = idf;}// 欢迎信息和初始引导cout << "\n=================================" << endl;cout << "欢迎使用问答系统!我可以回答您的问题" << endl;cout << "输入 'help' 查看可用命令,'exit' 退出程序" << endl;cout << "=================================\n" << endl;// 聊天界面string input;while (true) {cout << "请输入您的问题: ";getline(cin, input);// 处理命令if (input == "exit" || input == "quit") {cout << "机器人: 再见!感谢使用!" << endl;break;}else if (input == "help") {showHelp();continue;}else if (input == "topics") {showTopics(exactAnswers);continue;}else if (input.empty()) {cout << "机器人: 您的输入为空,请重新输入或输入 'help' 查看帮助" << endl;continue;}// 精确匹配map<string, string>::const_iterator exactIt = exactAnswers.find(input);if (exactIt != exactAnswers.end()) {cout << "机器人: " << exactIt->second << endl;continue;}// 关键词匹配 (TF-IDF)vector<string> userKeywords = extractKeywords(input);string bestAnswer = getBestAnswerByTFIDF(userKeywords, qas, questionKeywords, idfValues);if (!bestAnswer.empty()) {cout << "机器人: " << bestAnswer << endl;continue;}// 没有找到匹配,提供引导cout << "机器人: 抱歉,我不太理解这个问题。" << endl;cout << "您可以尝试:" << endl;cout << "- 用不同的方式表述问题" << endl;cout << "- 输入 'topics' 查看我能回答的问题类型" << endl;cout << "- 输入 'help' 查看帮助信息" << endl;}} else {cout << "无法打开训练文件 training_data.txt" << endl;cout << "请确保该文件存在于程序运行目录下" << endl;cout << "程序将退出..." << endl;}return 0;
}

总结

这个案例展示了如何通过关注用户体验细节,将一个功能性的程序转变为一个易用、友好的工具。这些改进不需要复杂的技术实现,却能显著提升用户满意度。

在实际开发中,我们应该始终记住:代码是写给机器执行的,但最终是给人使用的。良好的用户体验设计,应该贯穿于软件开发的每一个环节。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/90565.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/90565.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/90565.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android UI 控件详解实践

一、UI 开发基础概念&#xff08;初学者必看&#xff09; 在学习具体控件前&#xff0c;先理解以下核心概念&#xff0c;能大幅降低后续学习难度&#xff1a; 1. View 与 ViewGroup 的关系 View&#xff1a;所有 UI 控件的基类&#xff08;如 Button、TextView&#xff09;&…

关于linux运维 出现高频的模块认知

一、Linux 基础核心&#xff08;必掌握&#xff09;核心工具&#xff1a;Shell 脚本、Systemd、用户权限管理、日志分析&#xff08;journalctl、rsyslog&#xff09;企业需求&#xff1a;中小型公司&#xff1a;需独立完成系统部署、故障排查&#xff0c;对脚本开发&#xff0…

手语式映射:Kinova Gen3 力控机械臂自适应控制的研究与应用

近日&#xff0c;美国明尼苏达大学研究团队在《从人手到机械臂&#xff1a;遥操作中运动技能具身化研究》中&#xff0c;成功开发出基于​​Kinova的7轴力控机械臂Gen3的智能控制系统。这项创新性技术通过人工智能算法&#xff0c;实现了人类手臂动作到机械臂运动的精准映射&am…

P5535 【XR-3】小道消息

题目描述 小 X 想探究小道消息传播的速度有多快&#xff0c;于是他做了一个社会实验。 有 n 个人&#xff0c;其中第 i 个人的衣服上有一个数 i1。小 X 发现了一个规律&#xff1a;当一个衣服上的数为 i 的人在某一天知道了一条信息&#xff0c;他会在第二天把这条信息告诉衣…

ChatGPT Agent架构深度解析:OpenAI如何构建统一智能体系统

引言&#xff1a;AI智能体的范式跃迁 2025年7月17日&#xff0c;OpenAI发布的ChatGPT Agent标志着对话式AI从“被动应答”向主动执行的历史性转变。这款融合Operator网页操作与Deep Research信息分析能力的新型智能体&#xff0c;通过统一架构设计实现了复杂任务的端到端自主执…

计算机网络(第八版)— 第2章课后习题参考答案

2-01 物理层要解决哪些问题&#xff1f;物理层的主要特点是什么&#xff1f;答&#xff1a;物理层要解决的主要问题&#xff1a;&#xff08;1&#xff09;物理层要尽可能地屏蔽掉物理设备和传输媒体&#xff0c;通信手段的不同&#xff0c;使数据链路层感觉不到这些差异&#…

Hive【Hive架构及工作原理】

✨博客主页&#xff1a; https://blog.csdn.net/m0_63815035?typeblog &#x1f497;《博客内容》&#xff1a;.NET、Java.测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识 &#x1f4e2;博客专栏&#xff1a; https://blog.csdn.net/m0_63815035/cat…

数据管理能力成熟度评估模型(DCMM)详解

数据管理能力成熟度评估模型(DCMM)详解 1. DCMM概述 数据管理能力成熟度评估模型(Data Management Capability Maturity Assessment Model, DCMM)是我国首个数据管理领域的国家标准(GB/T 36073-2018)&#xff0c;由国家工业信息安全发展研究中心牵头制定。该模型为我国企业数据…

学习C++、QT---34(使用QT库实现串口调试助手01:解决串口调试助手的UI)

&#x1f31f; 嗨&#xff0c;我是热爱嵌入式的涛涛同学&#xff01;每日一言别害怕改变&#xff0c;走出舒适圈才能遇见更好的自己。串口调试助手项目好的现在我们来学习串口调试助手的项目&#xff0c;我们依旧是项目引领学习好的我们最后就是要做成一个类似我们市面上的串口…

Dockerfile 文件及指令详解

什么是Dockerfile 文件Dockerfile 文件是用于构建 docker 镜像的脚本文件&#xff0c;由一系列的指令构成。通过 docker build 命令构建镜像时&#xff0c;Dockerfile 文件中的指令会由上到下执行&#xff0c;每条 指令都将会构建出一个镜像层&#xff0c;这就是镜像的分层。因…

主流Java Redis客户端对决:Jedis、Lettuce与Redisson性能特性深度评测

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 持续学习&#xff0c;不断…

刷题日记0725

今日计划5道。2/5晚上被一些事影响了心神不宁&#xff0c;再加上感觉睡前做完时间有点紧&#xff0c;逃避的念头出现了。代码意思不进脑子了。做一道是一道。21. 合并两个有序链表默认构造​​&#xff1a;用于创建​​值为0的孤立节点​​&#xff08;不连接其他节点&#xff…

从数据脱敏到SHAP解释:用Streamlit+XGBoost构建可复现的川崎病诊断系统

基于机器学习的川崎病辅助诊断工具&#xff0c;结合了数据预处理、模型训练、特征解释和交互式可视化。以下是深度解读&#xff1a;1. 技术架构框架&#xff1a;使用 Streamlit 构建 Web 应用&#xff0c;适合快速开发交互式数据科学应用。核心算法&#xff1a;XGBoost&#xf…

【C++详解】模板进阶 非类型模板参数,函数模板特化,类模板全特化、偏特化,模板分离编译

文章目录一、非类型模板参数应用场景二、模板的特化函数模板特化类模板特化全特化偏特化三、模板分离编译解决方法四、模板总结一、非类型模板参数 先前介绍的函数模板和类模板都是针对类型的类模板参数&#xff0c;非类型模板参数有哪些使用场景呢&#xff1f;我们先来看下面这…

10BASE-T1S核心机制——PLCA参数详解

导语&#xff1a; PLCA是10BASE-T1S的核心机制&#xff0c;了解PLCA才能更好地使用10BASE-T1。 本文将通过介绍具体配置&#xff0c;以及实战例子&#xff0c;带你掌握PLCA。 以下测试内容使用KUNHONG-U10BT1S-EVB设备测试&#xff0c; 设备符合IEEE 802.3cg标准&#xff0…

uniapp vue apk那边输入法遮挡页面内容

解决办法&#xff1a;pages.json配置如下{"globalStyle": {"app-plus": {"softinputMode": "adjustResize"}} }效果&#xff1a; 键盘弹出时自动调整窗口大小&#xff0c;所有内容上推&#xff08;兼容性最佳&#xff09;文件内容如下…

2507C++,系统服务0与1

原文 窗口上的系统调用通过,每个由系统调用(x64)或sysenter(x86)CPU指令调用的NTDLL.dll,如NTDLL的NtCreateFile的以下输出所示: 这里 0:000> u ntdll!NtCreateFile: 00007ffcc07fcb50 4c8bd1 mov r10,rcx 00007ffcc07fcb53 b855000000 mov eax,55h…

人工智能冗余:大语言模型为何有时表现不佳(以及我们能做些什么)

像 GPT - 4 这样的大语言模型&#xff08;LLMs&#xff09;彻底改变了我们与技术交互的方式。它们可以撰写文章、生成代码、回答问题&#xff0c;甚至帮助我们构思创意。但任何花时间使用过这些模型的人都知道&#xff0c;它们的输出有时会让人感觉……不太对劲。表述冗长、格式…

Cursor替代品亚马逊出品Kiro下载

Cursor替代品亚马逊出品Kiro下载 支持Claude Sonnet4.0与3.7 点击下载 备用链接&#xff1a;https://pan.xunlei.com/s/VOW-nBmVgR3ewIIAm7jDsf99A1?pwd6bqu#

MySQL 事务管理

一、前言 CURD 不加控制&#xff0c;会有什么问题&#xff1f; CURD 满足什么属性&#xff0c;能解决上述问题&#xff1f; 买票的过程得是原子的。买票应该不能受互相的影响。买完票应该要永久有效。买前和买后都要是确定的状态。 什么是事务&#xff1f; 事务就是一组 DML 语…