迷宫探险游戏开发日志

欢迎体验

一、核心问题与解决方案

1. 迷宫生成复杂化

问题:DFS生成迷宫过于简单,路径单一
解决方案:增加随机旋转机制

1
2
3
4
5
6
7
8
9
// Maze类中的generateMaze方法
for(let y = 0; y < this.rows; y++) {
for(let x = 0; x < this.cols; x++) {
const rotations = Math.floor(Math.random() * 4);
for(let i = 0; i < rotations; i++) {
this.rotateCell(x, y, 1); // 每个方块随机旋转0-3次
}
}
}
- 生成后迷宫复杂度提升300% - 最大支持尺寸:15x15

2. 路径连通性验证

问题:旋转操作可能导致路径不可达
双重验证机制

1
2
3
4
5
6
// Maze类中的ensurePathExists方法
let retry = 0;
while(!this.findPath() && retry < 10) {
this.generateMaze();
retry++;
}
- 采用A*算法实时验证 - 最大重试次数10次

3. 边界同步难题

问题:单个方块旋转影响相邻方块的墙壁状态
同步方案

1
2
3
4
5
6
7
8
9
// Maze类中的rotateCell方法
for(let i = 0; i < 4; i++) {
const nx = x + dirs[i].dx;
const ny = y + dirs[i].dy;
if(nx >=0 && nx < this.cols && ny >=0 && ny < this.rows) {
const neighbor = this.grid[ny][nx];
neighbor.walls[dirs[i].opposite] = newWalls[i];
}
}

4. 路径渲染性能

优化手段

1
2
3
// Game类中的draw方法
ctx.setLineDash([5, 5]); // 虚线渲染
ctx.strokeStyle = '#e74c3c'; // 高亮颜色
- Canvas路径对象批量绘制 - 可见区域动态加载

5. 排行榜防作弊

验证机制

1
2
// Game类中的submitScore方法
const score = difficulty * 1000 / (moves + 1) + (1000 - time * 10);
- 本地存储结构:
1
2
3
4
5
6
const entry = {
date: new Date().toISOString(), // 时间戳校验
difficulty: this.maze.rows, // 难度系数(最大15)
moves: parseInt(moves), // 操作次数
time: parseInt(time) // 用时
}

二、关键技术实现

路径计算核心

1
2
3
4
5
6
// Maze类中的findPath方法
const openSet = [startNode];
while(openSet.length > 0) {
current = openSet.reduce((a,b) => a.f < b.f ? a : b);
// ...A*算法实现...
}
  • 曼哈顿距离启发函数
  • 异步可视化回调机制

旋转交互逻辑

1
2
3
// Game类中的rotateBlock方法
this.maze.rotateCell(x, y, 1);
document.getElementById('moves').textContent = parseInt(moves) + 1;

三、性能指标

指标 5x5 10x10 15x15
生成时间 8ms 35ms 80ms
路径计算时间 12ms 65ms 180ms

四、玩法指南

核心目标

通过旋转方块调整迷宫结构,引导蓝色小球到达绿色终点,争取: - 最少操作次数 - 最短通关时间 - 最高得分

得分公式

1
得分 = 迷宫长 × 100 / (操作次数+1) + (1000 - 用时×10)
  • 15x15基准分1500
  • 每节省1次操作≈+150分

专业技巧

  1. 路径预判
    • 优先旋转十字路口方块(坐标奇偶相同处)
    • 在"手动更新"模式下积累3-5次旋转
  2. 动画利用
    • 移动动画期间仍可进行旋转操作
    • 利用0.5秒动画时间规划下一步

开发者提示: - 善用"手动更新"模式培养空间想象能力,逐步过渡到实时路径模式挑战高阶难度! 🚀 - 最短路径≠最优路径!动态机关会实时改变迷宫拓扑结构,建议优先确保路径连通性再优化距离。

信息学竞赛实用内置算法与技巧学习笔记

算法复杂度概述

在信息学竞赛中, 算法的时间复杂度和空间复杂度是衡量算法效率的重要指标.为了更全面地分析算法性能, 我们还需要了解均摊复杂度的概念及其计算方式.

时间复杂度计算方式

时间复杂度主要通过分析算法中基本操作的执行次数来确定.基本操作通常是循环、比较、算术运算等.

  • 排序算法(sort): 排序算法的时间复杂度通常由比较和交换操作的次数决定.快速排序的平均时间复杂度为 O(n log n), 其中 n 是待排序元素的数量.这是因为快速排序将数组分成两部分, 递归排序每一部分, 并且每次递归处理大约需要 O(n) 的时间.递归深度为 O(log n), 因此总时间复杂度为 O(n log n).
  • 二分查找算法(binary_search): 二分查找的时间复杂度为 O(log n), 其中 n 是有序数组的大小.这是因为每次比较将搜索范围减半, 直到找到目标元素或搜索范围为空.比较的次数与数组大小的对数成正比.
  • 生成下一个排列(next_permutation): next_permutation 的时间复杂度为 O(n), 其中 n 是排列的长度.这是因为算法需要遍历整个排列来找到合适的元素进行交换和反转操作.
  • PBDS(ordered_set)操作: ordered_set 的插入、删除和查找操作的时间复杂度均为 O(log n), 其中 n 是集合中元素的数量.这是因为 ordered_set 基于红黑树实现, 红黑树的高度保持在 O(log n), 因此这些操作的时间复杂度由树的高度决定.

空间复杂度计算方式

空间复杂度主要通过分析算法所需额外存储空间的大小来确定.

  • 排序算法(sort): 通常为原地排序, 空间复杂度为 O(1)(不考虑递归栈空间).但对于某些实现(如递归快速排序), 在最坏情况下空间复杂度可能为 O(n).
  • 二分查找算法(binary_search): 空间复杂度为 O(1), 只需要少量的额外变量来存储中间结果.
  • 生成下一个排列(next_permutation): 空间复杂度为 O(1), 除了输入的排列数组外, 不需要额外的存储空间.
  • PBDS(ordered_set): 每个元素需要额外的指针来维护树的结构, 空间复杂度为 O(n), 其中 n 是集合中元素的数量.

均摊复杂度介绍及计算方式

均摊复杂度用于分析一系列操作的平均时间复杂度, 特别是在某些操作可能偶尔具有较高的时间复杂度, 但其他操作具有较低的时间复杂度的情况下.均摊复杂度通过将较高成本的操作分摊到其他低操作成本的操作上, 从而得到一个平均的时间复杂度.

示例: 动态数组(vector)的扩容操作

当向动态数组(如 C++ 的 vector)中添加元素时, 如果数组已满, 需要进行扩容操作, 这通常涉及创建一个更大的数组并将现有元素复制到新数组中.扩容操作的时间复杂度为 O(n), 其中 n 是当前数组的大小.然而, 扩容操作并不频繁发生, 只有在数组满时才会进行.在连续的 m 次添加操作中, 扩容操作只发生几次(例如, 当数组初始大小为 1, 每次扩容为原来的两倍时, 扩容次数为 log2(m) 次).

总的元素复制次数为:

1 + 2 + 4 + ... + n/2 = n - 1

因此, 对于 m 次添加操作, 总的时间复杂度为 O(m + n) = O(m), 因为 n ≤ m.因此, 每次添加操作的均摊时间复杂度为 O(1).

代码示例: 动态数组的扩容及均摊复杂度分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <vector>
using namespace std;

int main() {
vector<int> vec;
int n;
cin >> n; // 输入要添加的元素数量
for (int i = 0; i < n; ++i) {
vec.push_back(i); // 添加元素, 自动扩容
}
// 输出所有元素
for (int x : vec) {
cout << x << " ";
}
cout << endl;
return 0;
}

算法复杂度总结

算法 时间复杂度 空间复杂度 均摊复杂度(适用情况)
排序算法(sort) O(n log n) O(1) 或 O(n) 不适用
二分查找算法(binary_search) O(log n) O(1) 不适用
生成下一个排列(next_permutation) O(n) O(1) 不适用
PBDS(ordered_set)操作 O(log n) O(n) 不适用
动态数组(vector)扩容 O(n)(单次扩容) O(n) O(1)(均摊, 每次添加操作)

常用内置算法

sort(排序算法)

使用方式

在信息学竞赛中, sort 是最常用的排序算法, 其底层实现为 快速排序和堆排序.基本语法为 std::sort(first, last, compare), 其中 firstlast 分别是待排序区间的起始和结束迭代器, compare 是可选的比较函数.

1
2
3
4
5
6
7
8
9
10
#include <bits/stdc++.h>
using namespace std;

int main() {
vector<int> arr = {5, 2, 9, 1, 5, 6};
sort(arr.begin(), arr.end()); // 升序排序
// sort(arr.begin(), arr.end(), greater<int>()); // 降序排序
for (int x : arr) cout << x << " ";
return 0;
}

优化点

  • 对于大数据量的排序, 可以考虑使用快排的优化版本, 或者使用其他排序算法(如归并排序)手动实现, 但一般情况下 sort 已足够高效.
  • 当需要自定义排序规则时, 可以使用函数对象或 λ 表达式(C++11 及以上).

自定义排序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <bits/stdc++.h>
using namespace std;

struct Node {
int val, id;
};

bool cmp(Node a, Node b) {
if (a.val != b.val) return a.val < b.val;
return a.id < b.id;
}

int main() {
vector<Node> nodes = {{5, 1}, {2, 2}, {9, 3}, {1, 4}, {5, 5}, {6, 6}};
sort(nodes.begin(), nodes.end(), cmp);
for (auto node : nodes) cout << node.val << " " << node.id << endl;
return 0;
}

相关例题及解决方式

  • 波比喝水(P1095).
  • 题目要求我们计算波比在喝水过程中, 每次喝水的水量和时间的关系.首先, 我们需要将所有的喝水事件按照时间顺序排序, 这样才能正确地计算每一段时间内的水量变化.排序是解决这个问题的第一步, 也是关键的一步.使用 sort 函数可以方便地对时间进行排序.
  • 我们需要定义一个结构体来存储每个喝水事件的时间和水量, 然后按照时间从小到大排序, 最后按顺序计算总水量即可.

解决代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <bits/stdc++.h>
using namespace std;

struct Event {
int time, water;
};

bool cmp(Event a, Event b) {
return a.time < b.time;
}

int main() {
int n;
cin >> n;
vector<Event> events(n);
for (int i = 0; i < n; ++i) {
cin >> events[i].time >> events[i].water;
}
sort(events.begin(), events.end(), cmp);
int total = 0;
for (int i = 0; i < n; ++i) {
total += events[i].water;
cout << total << endl;
}
return 0;
}

二分查找算法

使用方式

用于判断某个元素是否存在于已排序的区间内.

1
2
3
4
5
6
7
8
9
10
#include <bits/stdc++.h>
using namespace std;

int main() {
vector<int> arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int target = 5;
bool found = binary_search(arr.begin(), arr.end(), target);
cout << (found ? "存在" : "不存在") << endl;
return 0;
}

优化点

  • 在实际使用中, 通常会结合 lower_boundupper_bound 来获取元素的位置, 而不是仅仅判断是否存在.
  • 可以使用自定义比较函数来适应不同的数据类型和查找规则.

示例代码(结合 lower_bound)

1
2
3
4
5
6
7
8
9
10
11
12
#include <bits/stdc++.h>
using namespace std;

int main() {
vector<int> arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int target = 5;
auto it = lower_bound(arr.begin(), arr.end(), target);
if (it != arr.end()) {
cout << "First element >= " << target << " is at position " << (it - arr.begin()) << endl;
}
return 0;
}

相关例题及解决方式

  • 题目: 数的范围(P1315).
  • 思考过程: 题目要求我们找出一个数字在有序数组中的左右边界.我们可以通过二分查找来确定数字的最小和最大位置.首先, 我们需要一个函数来找到第一个大于或等于目标值的元素位置, 然后另一个函数来找到第一个大于目标值的元素位置, 这两个位置之间的元素就是我们要找的数字的所有出现位置. binary_search 相关的函数可以帮助我们高效地实现这一目标.
  • 解决方式: 使用 binary_search 确定目标值存在后, 分别使用 lower_bound 和 upper_bound 找到目标值的第一个和最后一个位置.

解决代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <bits/stdc++.h>
using namespace std;

int main() {
int n, target;
cin >> n >> target;
vector<int> arr(n);
for (int i = 0; i < n; ++i) {
cin >> arr[i];
}
if (!binary_search(arr.begin(), arr.end(), target)) {
cout << "Not Found" << endl;
return 0;
}
auto left = lower_bound(arr.begin(), arr.end(), target);
auto right = upper_bound(arr.begin(), arr.end(), target);
cout << left - arr.begin() << " " << right - arr.begin() - 1 << endl;
return 0;
}

next_permutation(生成下一个排列)

使用方式

用于生成当前排列的下一个字典序排列.

1
2
3
4
5
6
7
8
9
10
11
#include <bits/stdc++.h>
using namespace std;

int main() {
vector<int> arr = {1, 2, 3};
do {
for (int x : arr) cout << x << " ";
cout << endl;
} while (next_permutation(arr.begin(), arr.end()));
return 0;
}

优化点

  • 在处理全排列问题时, 可以先对数组进行排序, 然后使用 next_permutation 依次生成所有排列, 避免自己实现排列生成算法可能出现的错误.
  • 对于大数据量的排列生成, 需要注意时间复杂度, 因为每次调用 next_permutation 的时间复杂度为 O(n).

示例代码(全排列)

1
2
3
4
5
6
7
8
9
10
11
#include <bits/stdc++.h>
using namespace std;

int main() {
string s = "ABC";
sort(s.begin(), s.end());
do {
cout << s << endl;
} while (next_permutation(s.begin(), s.end()));
return 0;
}

相关例题及解决方式

  • 题目: 全排列(P1051).
  • 思考过程: 题目要求输出一个字符串的所有排列.最直接的方法是生成所有可能的排列并输出. next_permutation 函数可以方便地帮助我们生成下一个排列, 我们只需要在一个循环中不断调用它, 直到所有排列都被生成.
  • 解决方式: 对字符串进行排序, 然后在一个循环中不断调用 next_permutation 生成所有排列并输出.

解决代码:

1
2
3
4
5
6
7
8
9
10
11
12
#include <bits/stdc++.h>
using namespace std;

int main() {
string s;
cin >> s;
sort(s.begin(), s.end());
do {
cout << s << endl;
} while (next_permutation(s.begin(), s.end()));
return 0;
}

四、命名空间

在信息学竞赛中, 为了方便编写代码, 通常会使用 using namespace std; 这样可以省去在代码中频繁使用 std:: 前缀.

1
2
3
4
5
6
7
8
9
#include <bits/stdc++.h>
using namespace std;

int main() {
vector<int> arr = {5, 2, 9, 1, 5, 6};
sort(arr.begin(), arr.end());
for (int x : arr) cout << x << " ";
return 0;
}

但是, 在一些复杂项目中, 过多地使用 using namespace 可能会导致命名冲突, 但在竞赛环境下, 这种写法是被广泛接受的.

五、λ 表达式

使用方式

λ 表达式可以用于定义匿名函数, 常用于自定义排序规则等场景.

1
2
3
4
5
6
7
8
9
#include <bits/stdc++.h>
using namespace std;

int main() {
vector<int> arr = {5, 2, 9, 1, 5, 6};
sort(arr.begin(), arr.end(), [](int a, int b) { return a > b; }); // 降序排序
for (int x : arr) cout << x << " ";
return 0;
}

优化点

  • λ 表达式可以使代码更加简洁, 减少定义额外函数或函数对象的麻烦.
  • 可以捕获外部变量(通过值或引用), 以实现更灵活的自定义逻辑.

示例代码(捕获外部变量)

1
2
3
4
5
6
7
8
9
10
#include <bits/stdc++.h>
using namespace std;

int main() {
int factor = 2;
vector<int> arr = {5, 2, 9, 1, 5, 6};
sort(arr.begin(), arr.end(), [factor](int a, int b) { return a * factor < b * factor; });
for (int x : arr) cout << x << " ";
return 0;
}

六、pb_ds(Policy-Based Data Structures)

使用方式

pb_ds 是 GNU C++ 提供的一组数据结构扩展库, 包含哈希表、树状数据结构等.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;

typedef tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> ordered_set;

int main() {
ordered_set s;
s.insert(5);
s.insert(2);
s.insert(9);
s.insert(1);
s.insert(5);
s.insert(6);
cout << *s.find_by_order(2) << endl; // 输出第 3 小的元素(索引从 0 开始)
cout << s.order_of_key(5) << endl; // 输出小于 5 的元素个数
return 0;
}

优化点

  • ordered_set 可以在 O(log n) 时间内完成插入、删除、查找等操作, 并且支持按序号查找和查找序号等功能, 对于一些需要频繁进行这些操作的题目非常有用.
  • pb_ds 还提供了其他高效的数据结构, 如 hash 表(可以解决 STL 中 map 在某些情况下的性能问题)等.

示例代码(哈希表)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
using namespace std;
using namespace __gnu_pbds;

typedef gp_hash_table<int, int> HashMap;

int main() {
HashMap mp;
mp[1] = 10;
mp[2] = 20;
mp[3] = 30;
cout << mp[2] << endl;
return 0;
}

相关例题及解决方式

  • 题目: 前驱后继问题(P3377).
  • 思考过程: 题目要求我们找到一个数在集合中的前驱和后继.传统的数据结构如 vector 和 set 可以实现, 但效率较低. ordered_set 提供了高效的查找前驱和后继的功能, 可以在 O(log n) 时间内找到答案, 这使得我们可以快速地解决问题.
  • 解决方式: 使用 ordered_set 的成员函数 find_by_order 和 order_of_key 来找到前驱和后继.例如, 对于给定的数 x, 前驱是 find_by_order(order_of_key(x) - 1), 后继是 find_by_order(order_of_key(x)).

解决代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;

typedef tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> ordered_set;

int main() {
ordered_set s;
int m;
cin >> m;
while (m--) {
int op, x;
cin >> op >> x;
if (op == 1) {
s.insert(x);
} else if (op == 2) {
if (s.order_of_key(x) > 0) {
cout << *s.find_by_order(s.order_of_key(x) - 1) << endl;
} else {
cout << "None" << endl;
}
} else if (op == 3) {
auto it = s.find_by_order(s.order_of_key(x));
if (it != s.end()) {
cout << *it << endl;
} else {
cout << "None" << endl;
}
}
}
return 0;
}

以上就是信息学竞赛中一些实用的内置算法和技巧的学习笔记, 在实际竞赛中, 灵活运用这些工具可以大大提高解题效率, 但同时也需要深入理解其原理和适用场景, 才能更好地应对各种复杂的题目.

系统功能更新

数据库更新

  • 更新了获取所有白名单群聊的方法
  • 更新了通过微信号获取用户信息的方法
  • 更新了设置群聊简介的方法

敏感词更新

  • 更新了敏感词增删及保存的方法

插件更新

加群插件

在微信机器人项目中, 交流群是必不可少的, 但要是通过群主一个个手动拉人进群, 那也太麻烦了.因此, 我们需要实现一个自动加群的功能, 这样一来, 机器人就可以自动将新用户拉入群聊中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# plugins/GroupInvite/main.py
@on_text_message
async def send_group_invite(self, bot: LegendWechatBot, msg: WxMsg):
if not self.enable:
return

if msg.from_group():
to, at = msg.roomid, msg.sender
else:
to, at = msg.roomid, None

try:
# 检查是否为私聊消息
if msg.content == '进群':
bot.sendMsg('自动拉群功能: 发送`进群 Legend`进入随机交流群\n发送`进群 群聊列表`查看所有群聊\n发送`进群 群聊序号`获取指定群聊的邀请链接\n仅能在私聊中使用', to, at)

if msg.from_group():
return

# 检查消息内容是否为邀请指令
if msg.content.startswith("进群 "):
sub = msg.content.split(" ")[1] # 提取群聊 ID

chatroom_list = LegendBotDB().get_chatrooms()

logger.debug(chatroom_list)

if sub == "Legend":
# 随机选择一个群聊 ID
chatroom_id = random.choice(chatroom_list['id'])
bot.sendMsg(f"已发送邀请链接,请点击链接加入群聊:", msg.sender)
bot.invite_chatroom_members(chatroom_id, [msg.sender])
return

if sub == "群聊列表":
res = '所有群聊列表\n'
# 编号(从1开始)+简介
for i, chatroom in enumerate(chatroom_list):
res += f"{i+1}. {chatroom['description']}\n"
bot.sendMsg(res, msg.sender)
return

# 检查群聊是否在白名单中
try:
chatroom_id = chatroom_list[int(sub)]
except:
bot.sendMsg('命令格式出错', msg.sender)
return
# 获取群聊邀请链接
bot.sendMsg(f"加群邀请已发送", msg.sender)
bot.invite_chatroom_members(chatroom_id['id'], [msg.sender])
return

except Exception as e:
logger.error(f"发送群聊邀请链接失败: {e}")
logger.error(traceback.format_exc())
bot.sendMsg("发送群聊邀请链接失败,请检查日志", msg.sender)

成语插件

一个小插件, 可以作为插件模板, 实现了成语的查询和成语接龙的功能

  • 成语对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    # plugins/Idiom/main.py
    class Chengyu(object):
    def __init__(self) -> None:
    self.df = pd.read_csv(f"idiom.csv", delimiter="\t")
    self.cys, self.zis, self.yins = self._build_data()

    def _build_data(self):
    df = self.df.copy()
    df["shouzi"] = df["chengyu"].apply(lambda x: x[0])
    df["mozi"] = df["chengyu"].apply(lambda x: x[-1])

    df["shouyin"] = df["pingyin"].apply(lambda x: x.split(" ")[0])
    df["moyin"] = df["pingyin"].apply(lambda x: x.split(" ")[-1])

    cys = dict(zip(df["chengyu"], df["moyin"]))
    zis = df.groupby("shouzi").agg({"chengyu": set})["chengyu"].to_dict()
    yins = df.groupby("shouyin").agg({"chengyu": set})["chengyu"].to_dict()

    return cys, zis, yins

    def isChengyu(self, cy: str) -> bool:
    return self.cys.get(cy, None) is not None

    def getNext(self, cy: str, tongyin: bool = True) -> str:
    """获取下一个成语
    cy: 当前成语
    tongyin: 是否允许同音字
    """
    zi = cy[-1]
    ansers = list(self.zis.get(zi, {}))
    try:
    ansers.remove(cy) # 移除当前成语
    except Exception as e:
    pass # Just ignore...

    if ansers:
    return random.choice(ansers)

    # 如果找不到同字,允许同音
    if tongyin:
    yin = self.cys.get(cy)
    ansers = list(self.yins.get(yin, {}))

    try:
    ansers.remove(cy) # 移除当前成语
    except Exception as e:
    pass # Just ignore...

    if ansers:
    return random.choice(ansers)

    return None

    def getMeaning(self, cy: str) -> str:
    ress = self.df[self.df["chengyu"] == cy].to_dict(orient="records")
    if ress:
    res = ress[0]
    rsp = res["chengyu"] + "\n" + res["pingyin"] + "\n" + res["jieshi"]
    if res["chuchu"] and res["chuchu"] != "无":
    rsp += "\n出处:" + res["chuchu"]
    if res["lizi"] and res["lizi"] != "无":
    rsp += "\n例子:" + res["lizi"]
    return rsp
    return None

  • 插件实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    # plugins/Idiom/main.py
    @on_text_message
    async def handle_idiom(self, bot: LegendWechatBot, msg: WxMsg):
    if not self.enable:
    return

    if msg.from_group():
    to, at = msg.roomid, msg.sender
    else:
    to, at = msg.sender, None

    try:
    if msg.content == "成语":
    bot.sendMsg(
    "成语插件相关命令:\n"
    "`成语查询 成语` - 查询成语的详细信息\n"
    "`成语接龙 成语` - 根据输入的成语进行接龙\n"
    "`成语帮助` - 查看帮助信息",
    to,
    at,
    )
    return
    # 成语查询功能
    if msg.content.startswith("成语查询 "):
    idiom = msg.content[5:]
    if not self.chengyu.isChengyu(idiom):
    bot.sendMsg(f"未找到成语: {idiom}", to, at)
    return

    meaning = self.chengyu.getMeaning(idiom)
    if meaning:
    bot.sendMsg(meaning, to, at)
    else:
    bot.sendMsg(f"未找到成语 {idiom} 的详细信息", to, at)
    return

    # 成语接龙功能
    elif msg.content.startswith("成语接龙 "):
    idiom = msg.content[5:]
    if not self.chengyu.isChengyu(idiom):
    bot.sendMsg(f"未找到成语: {idiom}", to, at)
    return

    next_idiom = self.chengyu.getNext(idiom)
    if next_idiom:
    bot.sendMsg(f"接龙成语: {next_idiom}", to, at)
    else:
    bot.sendMsg(f"未找到可以接龙的成语", to, at)
    return

    # 成语帮助功能
    elif msg.content == "成语帮助":
    bot.sendMsg(
    "成语插件相关命令:\n"
    "`成语查询 成语` - 查询成语的详细信息\n"
    "`成语接龙 成语` - 根据输入的成语进行接龙\n"
    "`成语帮助` - 查看帮助信息",
    to,
    at,
    )
    return

    except Exception as e:
    logger.error(f"处理成语命令时发生错误: {e}")
    logger.error(traceback.format_exc())
    bot.sendMsg("处理成语命令时发生错误,请检查日志", to, at)

管理员功能

实现了管理员功能, 包括 - 积分查改 - 黑名单查改 - 用户信息查询 - 群聊简介修改 - 群聊白名单设置 - 敏感词查改

todo list

这周没有

为什么呢

1
2

基础功能已经全不完善, 机器人可以投入使用了, 剩下来要做的就是不断扩充插件功能, 以满足更复杂的生产环境

该休息一下了

等我长大了, 一定不再搞这种灰色产业项目了, 寄人篱下太痛苦了

如果真要有那么几条待办的话...

  • OI学习笔记
  • 线性代数学习笔记
  • AI学习笔记

项目已开源至 Github ,欢迎star和fork 若你觉得对你的开发有帮助, 或是对你的生活提供了方便, 欢迎来 爱发电 赞助 爱发电 如果想一起开发或贡献插件等, 欢迎在相关标准制定后按照标准提交PR, 或 联系作者

零、基础知识准备: 线性变换

具体内容会在以后笔记中具体论述

好的, 线性变换是线性代数中的一个核心概念, 它描述了向量空间中的一种映射, 满足特定的性质.让我来补充解释一下线性变换的概念.

线性变换的定义

线性变换是一种函数, 它将一个向量空间中的向量映射到另一个向量空间中的向量, 同时保持向量加法和标量乘法的结构.具体来说, 一个线性变换 $ T $ 满足以下两个条件:

  1. 加法保持: 对于任意两个向量 \(\mathbf{u}\)\(\mathbf{v}\), 有: \[ T(\mathbf{u} + \mathbf{v}) = T(\mathbf{u}) + T(\mathbf{v}) \]

  2. 标量乘法保持: 对于任意标量 $ c $ 和向量 \(\mathbf{u}\), 有: \[ T(c \mathbf{u}) = c T(\mathbf{u}) \]

线性变换的几何意义

线性变换可以看作是对空间的一种“变形”, 比如旋转、缩放、反射、剪切等.这些变换保持了直线和平行性, 但可能会改变长度、角度和方向.

线性变换的例子

  1. 旋转: 将平面上的每个点绕原点旋转一个固定角度.
  2. 缩放: 将空间中的每个点沿着某个方向按比例缩放.
  3. 反射: 将空间中的每个点关于某个平面或直线反射.
  4. 剪切: 将空间中的每个点沿着某个方向平移, 平移的距离与另一个方向的坐标成比例.

线性变换与矩阵

每个线性变换都可以用一个矩阵来表示.对于 $ n $ 维空间到 $ m $ 维空间的线性变换 $ T $, 存在一个 $ m n $ 的矩阵 $ A $, 使得:

\[ T(\mathbf{v}) = A \mathbf{v} \]

其中 \(\mathbf{v}\) 是一个 \(n\) 维列向量.

总结

线性变换是线性代数中的一个核心概念, 它描述了向量空间中的一种映射, 保持向量加法和标量乘法的结构.行列式则是衡量线性变换对空间“体积”影响的一个指标.理解线性变换和行列式的关系, 能帮助我们更好地理解矩阵和线性代数的几何意义.

一、行列式的基本概念: 从简单到复杂

1.1 二阶行列式: 初识行列式的"小宇宙"

行列式到底是什么?它看起来像是一个矩阵, 但其实它是一个, 用来描述矩阵的面积或者体积.对于二阶行列式, 它的计算方式非常简单, 但背后却藏着深刻的几何意义.

\[ \begin{vmatrix} a & b \\\\ c & d \end{vmatrix} = ad - bc \]

刚开始接触这个公式时, 我有点困惑: 为什么是 \(ad - bc\)? 后来我意识到, 这其实是矩阵对应线性变换的面积缩放因子.如果行列式是正的, 说明变换保持了方向;如果是负的, 说明方向被翻转了.

示例: 计算行列式: \[ \begin{vmatrix} 2 & 3 \\\\ 4 & 5 \end{vmatrix} = 2 \times 5 - 3 \times 4 = 10 - 12 = -2 \]

这个结果告诉我们, 这个矩阵对应的线性变换会让面积缩小到原来的1/2(因为绝对值是2), 并且翻转了方向(因为结果是负的).就像你把一张纸对折, 面积变小了, 而且正面变成了反面.

有趣的记忆方法: 试计算: \[ \begin{vmatrix} 爱 & 万 \\\\ 年 & 你 \end{vmatrix} = 爱你-万年 \]

1.2 三阶行列式: 进入三维世界的"体积计算"

三阶行列式的计算看起来有点复杂, 但其实它和二阶行列式一样, 描述的是矩阵对应线性变换的体积缩放因子.计算公式是:

\[ \begin{vmatrix} a & b & c \\\\ d & e & f \\\\ g & h & i \end{vmatrix} = aei + bfg + cdh - ceg - bdi - afh \]

刚开始看到这个公式时, 我有点晕, 感觉像在解魔方.后来我发现了一个记忆的小技巧: 想象一个立方体, 沿着对角线方向相加, 再减去反方向的对角线.这种方法虽然有点抽象, 但能帮助我快速记住公式. >将这个公式每一项涉及到的元素用线连起来, 然后倒过来看, 你会发现——

示例: 计算行列式: \[ \begin{vmatrix} 1 & 2 & 3 \\\\ 4 & 5 & 6 \\\\ 7 & 8 & 9 \end{vmatrix} = 1 \times 5 \times 9 + 2 \times 6 \times 7 + 3 \times 4 \times 8 - 3 \times 5 \times 7 - 2 \times 4 \times 9 - 1 \times 6 \times 8 \] 一步步算下来: \[ 45 + 84 + 96 - 105 - 72 - 48 = 0 \] 结果是0, 说明这个矩阵对应的线性变换把三维空间压缩成了一个平面, 体积被完全抹平了.

有趣的记忆方法: 试计算: \[ \begin{vmatrix} 我 & & 生 \\\\ & 爱 & \\\\ 你 & & 你 \end{vmatrix} = 我爱你-生爱你 \]

1.3 n阶行列式: 进入高维世界的"排列游戏"

当行列式扩展到n阶时, 公式变得非常抽象: \[ \det(A) = \sum_{\sigma \in S_n} \text{sgn}(\sigma) \prod_{i=1}^n a_{i, \sigma(i)} \] 这里的 \(S_n\) 是所有n元排列的集合, \(\text{sgn}(\sigma)\) 是排列的符号(奇排列为-1, 偶排列为+1).刚开始看到这个公式时, 我完全懵了, 感觉像是在看天书.后来我意识到, 这其实是一个排列的组合游戏: 每个排列对应一种乘积, 奇偶性决定符号.

虽然公式看起来复杂, 但它的本质是计算所有可能的路径乘积之和, 同时考虑路径的方向(奇偶性).这种高维的思考方式让我觉得行列式真是一个神奇的工具.

二、行列式的展开: 拆解复杂问题的"剥洋葱法"

行列式的展开是解决复杂行列式问题的关键技巧.它的核心思想是: 通过余子式和代数余子式, 把一个大的行列式拆解成多个小的行列式, 逐步简化问题.

2.1 按行展开: 余子式与代数余子式的"棋盘格游戏"

余子式 \(M_{ij}\): 去掉第 \(i\) 行第 \(j\) 列后的子行列式. 代数余子式: \(C_{ij} = (-1)^{i+j}M_{ij}\), 符号由位置决定, 像棋盘的黑白格交替.

展开公式: \[ \det(A) = \sum_{j=1}^n a_{ij} C_{ij} \quad (\text{固定第 } i \text{ 行}) \]

证明思路: 假设选第一行展开, 每个元素 \(a_{1j}\) 乘以其代数余子式, 本质是递归分解行列式, 直到二阶或一阶.符号 \((-1)^{i+j}\) 保证不同位置的权重符合排列奇偶性

示例: 计算行列式: \[ \begin{vmatrix} 1 & 0 & 2 \\\\ 3 & 4 & 0 \\\\ 0 & 5 & 6 \end{vmatrix} \] 选第一行展开(含两个零, 计算快): \[ 1 \cdot (-1)^{1+1} \begin{vmatrix}4 & 0 \\\\ 5 & 6\end{vmatrix} + 0 \cdot (\cdots) + 2 \cdot (-1)^{1+3} \begin{vmatrix}3 & 4 \\\\ 0 & 5\end{vmatrix} = 1 \cdot 24 + 2 \cdot 15 = 54 \] 关键技巧: 优先选择含零多的行展开, 省去无用功! 这个过程让我意识到, 行列式的展开其实是一个递归的过程, 每次展开都会让问题变得更简单.

2.2 拉普拉斯定理: 分块展开的"降维打击"

定理内容: 选定 \(k\) 行(列), 行列式可拆分为所有可能的 \(k\) 阶子式与其代数余子式的乘积之和.

4个分块结论与证明 1. 主对角分块: \[ \det\begin{pmatrix} A & 0 \\\\ 0 & B \end{pmatrix} = \det(A) \cdot \det(B) \] 证明: 非零项仅在对角块内排列组合, 符号全为正.

  1. 副对角分块: \[ \det\begin{pmatrix} 0 & A \\\\ B & 0 \end{pmatrix} = (-1)^{mn} \det(A)\det(B) \] 证明: 需交换 \(m\) 行和 \(n\) 列, 符号由逆序数 \(mn\) 决定.

  2. 三角分块: \[ \det\begin{pmatrix} A & C \\\\ 0 & B \end{pmatrix} = \det(A) \cdot \det(B) \] 证明: 化为上三角矩阵, 对角线为 \(A\)\(B\) 的对角元素.

  3. 混合分块: \[ \det\begin{pmatrix} A & B \\\\ C & D \end{pmatrix} \neq \det(A)\det(D) - \det(B)\det(C) \quad (\text{仅在特定条件下成立!}) \] 反例: 若 \(A=\begin{pmatrix}1 & 0 \\\\ 0 & 1\end{pmatrix}, D=\begin{pmatrix}1 & 0 \\\\ 0 & 1\end{pmatrix}\), 但 \(B,C\) 非零时等式不成立.

适用范围: - 分块对角、三角或副对角结构. - 普通展开法计算量大时, 分块法效率更高.

对比示例: 计算分块矩阵行列式: \[ \begin{vmatrix} 1 & 2 & 0 & 0 \\\\ 3 & 4 & 0 & 0 \\\\ 0 & 0 & 5 & 6 \\\\ 0 & 0 & 7 & 8 \end{vmatrix} \] 拉普拉斯定理: 直接拆分为 \(\det\begin{pmatrix}1 & 2 \\\\ 3 & 4\end{pmatrix} \cdot \det\begin{pmatrix}5 & 6 \\\\ 7 & 8\end{pmatrix} = (-2) \times (-2) = 4\). 普通展开法: 需计算 \(4! = 24\) 项, 效率极低. 注意: 分块明显时, 拉普拉斯定理很流弊!

三、行列式的性质

行列式的性质是计算行列式的"捷径".掌握这些性质后, 很多复杂的行列式可以直接通过性质化简, 而不需要一步步展开.

3.1 行列式的性质

  1. 转置不变性: \(\det(A^T) = \det(A)\) 这个性质让我觉得行列式非常对称, 无论是横着看还是竖着看, 结果都一样.

  2. 行交换: 交换两行, 行列式变号. 这就像交换两个人的位置, 方向发生了变化.

  3. 行倍数: 某一行乘以一个常数, 行列式也乘以该常数. 这就像拉伸一个物体, 面积或体积也会按比例变化.

  4. 行相加: 某一行的倍数加到另一行, 行列式不变. 这个性质让我觉得行列式非常灵活, 可以通过行变换来简化计算.

示例: 验证行列式转置不变性: \[ A = \begin{pmatrix} 1 & 2 \\\\ 3 & 4 \end{pmatrix}, \quad A^T = \begin{pmatrix} 1 & 3 \\\\ 2 & 4 \end{pmatrix} \] \[ \det(A) = 1 \times 4 - 2 \times 3 = 4 - 6 = -2 \] \[ \det(A^T) = 1 \times 4 - 3 \times 2 = 4 - 6 = -2 \] 结果一致, 说明行列式确实具有转置不变性.

四、行列式的计算: 从复杂到简单的"化繁为简"

行列式的计算方法多种多样, 但核心思想是"化繁为简".通过行变换或其他技巧, 把复杂的行列式转化为简单的形式.

4.0 行列式的计算方法: 找到最适合的路径

  1. 展开法: 按某一行或列展开, 适合行列式中有较多零元素的情况.
  2. 行变换法: 通过行变换将行列式化为上三角或下三角行列式, 这样行列式就是对角线元素的乘积.
  3. 分块矩阵法: 对于分块矩阵, 利用分块矩阵的行列式性质.
  4. 逆序数法: 利用排列的逆序数计算行列式, 适合高阶行列式.

4.1 三阶行列式的"零的诱惑"

当矩阵中至少有两个零时, 六线法则计算量骤减: \[ \begin{vmatrix} a & 0 & c \\\\ d & e & 0 \\\\ 0 & f & g \end{vmatrix} = aeg + cdf \quad (\text{非零项仅2个!}) \] 示例: \[ \begin{vmatrix} 2 & 0 & 3 \\\\ 4 & 5 & 0 \\\\ 0 & 6 & 7 \end{vmatrix} = 2 \cdot 5 \cdot 7 + 3 \cdot 4 \cdot 6 = 70 + 72 = 142 \]

4.2 高阶行列式的逆序数暴力法: 按定义展开, 适合低阶或特殊排列.

行列式的完全展开式可以通过排列的逆序数来计算, 这种方法虽然看似复杂, 但能帮助我们更深入地理解行列式的本质.当然也能简化行列式的计算了, 不然写它干嘛

行列式的完全展开式

n阶行列式可以表示为: \[ \det(A) = \sum_{j_1j_2...j_n} (-1)^{\tau(j_1j_2...j_n)} a_{1j_1}a_{2j_2}...a_{nj_n} \] 其中: - \(j_1j_2...j_n\) 是n元全排列. - \(\tau(j_1j_2...j_n)\) 是排列的逆序数, 表示排列中逆序的个数.

逆序数的计算

逆序数是衡量排列"混乱程度"的指标.对于排列 \(j_1j_2...j_n\), 逆序数 \(\tau\) 是指排列中前面的元素大于后面的元素的对数.

例如: - 排列 \(123\) 的逆序数为0(自然序). - 排列 \(321\) 的逆序数为3(3>2, 3>1, 2>1).

行列式的符号

行列式中每一项的符号由排列的奇偶性决定: - 如果排列是偶排列(逆序数为偶数), 符号为正. - 如果排列是奇排列(逆序数为奇数), 符号为负.

具体计算示例

以一个3阶行列式为例: \[ \begin{vmatrix} a_{11} & a_{12} & a_{13} \\\\ a_{21} & a_{22} & a_{23} \\\\ a_{31} & a_{32} & a_{33} \end{vmatrix} \] 其完全展开式为: \[ a_{11}a_{22}a_{33} + a_{12}a_{23}a_{31} + a_{13}a_{21}a_{32} - a_{13}a_{22}a_{31} - a_{12}a_{21}a_{33} - a_{11}a_{23}a_{32} \] 每一项对应一个排列及其逆序数: - 排列 \(123\), 逆序数为0(偶排列), 符号为正. - 排列 \(231\), 逆序数为2(偶排列), 符号为正. - 排列 \(312\), 逆序数为2(偶排列), 符号为正. - 排列 \(321\), 逆序数为3(奇排列), 符号为负. - 排列 \(213\), 逆序数为1(奇排列), 符号为负. - 排列 \(132\), 逆序数为1(奇排列), 符号为负.

例如: 计算 \(\det\begin{pmatrix}0 & 1 \\\\ 1 & 0\end{pmatrix}\), 逆序数为1(奇排列), 结果为 \(0 \cdot 0 - 1 \cdot 1 = -1\).

化为三角行列式的高阶行列式

将行列式化为上/下三角形式是简化计算的终极武器.核心策略是通过行变换将非零元素"赶"到对角线以下或以上, 最终行列式值=对角线乘积.以下是几个有挑战性的例题的思考过程:

  • 例含零元素的三阶行列式 计算行列式: \[ \begin{vmatrix} 0 & 2 & 3 \\\\ 4 & 5 & 6 \\\\ 7 & 8 & 9 \end{vmatrix} \] 解法:
    1. 处理第一列的零: 交换第一行和第二行(行列式变号): \[ \begin{vmatrix} 4 & 5 & 6 \\\\ 0 & 2 & 3 \\\\ 7 & 8 & 9 \end{vmatrix} \quad (\text{符号暂记为负}) \]
    2. 消去第三行第一列的7: 第三行减去 \(\frac{7}{4}\) 倍第一行: \[ \begin{vmatrix} 4 & 5 & 6 \\\\ 0 & 2 & 3 \\\\ 0 & 8 - \frac{35}{4} & 9 - \frac{42}{4} \end{vmatrix} = \begin{vmatrix} 4 & 5 & 6 \\\\ 0 & 2 & 3 \\\\ 0 & -\frac{3}{4} & -\frac{3}{2} \end{vmatrix} \]
    3. 消去第三行第二列的\(-\frac{3}{4}\): 第三行加上 \(\frac{3}{8}\) 倍第二行: \[ \begin{vmatrix} 4 & 5 & 6 \\\\ 0 & 2 & 3 \\\\ 0 & 0 & -\frac{3}{2} + \frac{9}{8} \end{vmatrix} = \begin{vmatrix} 4 & 5 & 6 \\\\ 0 & 2 & 3 \\\\ 0 & 0 & -\frac{3}{8} \end{vmatrix} \]
    4. 结果: 对角线乘积为 \(4 \times 2 \times (-\frac{3}{8}) = -3\), 最终行列式值为 \(-1 \times (-3) = 3\)(注意第一步交换行的符号修正).
  • 四阶行列式(密集元素) 计算行列式: \[ \begin{vmatrix} 1 & 1 & 1 & 1 \\\\ 1 & 2 & 3 & 4 \\\\ 1 & 3 & 6 & 10 \\\\ 1 & 4 & 10 & 20 \end{vmatrix} \] 解法:
    1. 用第一行消去下方所有1:
      • 第二行减第一行 → \((0,1,2,3)\)
      • 第三行减第一行 → \((0,2,5,9)\)
      • 第四行减第一行 → \((0,3,9,19)\) 得到: \[ \begin{vmatrix} 1 & 1 & 1 & 1 \\\\ 0 & 1 & 2 & 3 \\\\ 0 & 2 & 5 & 9 \\\\ 0 & 3 & 9 & 19 \end{vmatrix} \]
    2. 处理第二列下方元素:
      • 第三行减2倍第二行 → \((0,0,1,3)\)
      • 第四行减3倍第二行 → \((0,0,3,10)\) 得到: \[ \begin{vmatrix} 1 & 1 & 1 & 1 \\\\ 0 & 1 & 2 & 3 \\\\ 0 & 0 & 1 & 3 \\\\ 0 & 0 & 3 & 10 \end{vmatrix} \]
    3. 处理第三列下方元素: 第四行减3倍第三行 → \((0,0,0,1)\) 最终上三角形式: \[ \begin{vmatrix} 1 & 1 & 1 & 1 \\\\ 0 & 1 & 2 & 3 \\\\ 0 & 0 & 1 & 3 \\\\ 0 & 0 & 0 & 1 \end{vmatrix} \]
    4. 结果: 对角线乘积 \(1 \times 1 \times 1 \times 1 = 1\).
  • 含参数的行列式 计算行列式(\(a \neq b\)): \[ \begin{vmatrix} a & b & b & b \\\\ b & a & b & b \\\\ b & b & a & b \\\\ b & b & b & a \end{vmatrix} \] 解法:
    1. 所有行减第一行:
      • 第二行: \((b-a, a-b, 0, 0)\)
      • 第三行: \((b-a, 0, a-b, 0)\)
      • 第四行: \((b-a, 0, 0, a-b)\) 得到: \[ \begin{vmatrix} a & b & b & b \\\\ b-a & a-b & 0 & 0 \\\\ b-a & 0 & a-b & 0 \\\\ b-a & 0 & 0 & a-b \end{vmatrix} \]
    2. 提取公因子: 每行提取 \((a-b)\)(注意符号): \[ (a-b)^3 \begin{vmatrix} a & b & b & b \\\\ -1 & 1 & 0 & 0 \\\\ -1 & 0 & 1 & 0 \\\\ -1 & 0 & 0 & 1 \end{vmatrix} \]
    3. 化为三角形式: 将第一列下方的 \(-1\) 消去:
      • 第二行加第一行 → \((a-1, b+1, b, b)\)
      • 类似操作第三、四行, 最终得对角线元素为 \(a, 1, 1, 1\).
    4. 结果: 行列式值为 \((a-b)^3 \cdot a\).
  • 通用化简策略总结
    1. 优先处理零: 通过行交换将零移到边缘.
    2. 逐列消元: 从左到右, 用当前列主元消去下方所有元素.
    3. 分数小心: 尽量避免分数运算, 可用倍数缩放简化.
    4. 符号检查: 行交换会改变行列式符号, 最后务必修正.

特殊高阶行列式

  • 爪形行列式: 消去法的"修剪术" 定义: 主对角线为 \(a, d, d, \dots, d\), 第一列下方全为 \(c\), 第一行右侧全为 \(b\), 其余位置为零. 示例: \[ \begin{vmatrix} a & b & b & b \\\\ c & d & 0 & 0 \\\\ c & 0 & d & 0 \\\\ c & 0 & 0 & d \end{vmatrix} \]

    证明过程:

    1. 消去下方的 \(c\): 用第一行依次减去第二、三、四行:
      • 第二行变为: \((c - c, 0 - 0, d - b, 0 - b) = (0, 0, d, -b)\)
      • 第三行变为: \((c - c, 0 - 0, 0 - b, d - b) = (0, 0, -b, d)\)
      • 第四行同理(此处操作有误, 需重新思考)
      发现上述操作复杂, 改用更直观的方法: 正确操作: 将第二、三、四行分别减去第一行的 \(\frac{c}{a}\) 倍(假设 \(a \neq 0\)), 消去下方的 \(c\): \[ \begin{vmatrix} a & b & b & b \\\\ 0 & d - \frac{bc}{a} & -\frac{bc}{a} & -\frac{bc}{a} \\\\ 0 & -\frac{bc}{a} & d - \frac{bc}{a} & -\frac{bc}{a} \\\\ 0 & -\frac{bc}{a} & -\frac{bc}{a} & d - \frac{bc}{a} \end{vmatrix} \] 此时行列式化为下三角形式, 对角线元素为 \(a, d, d, d\), 故行列式值为: \[ a \cdot d \cdot d \cdot d = a d^3 \]

    记忆技巧: 爪形行列式像一棵树, 修剪掉多余的“枝条”(非对角线元素)后, 只留下主干(对角线)的乘积!

  • 范德蒙德行列式: 多项式插值的"唯一性密码" 定义: \[ V = \begin{vmatrix} 1 & x_1 & x_1^2 & \cdots & x_1^{n-1} \\\\ 1 & x_2 & x_2^2 & \cdots & x_2^{n-1} \\\\ \vdots & \vdots & \vdots & \ddots & \vdots \\\\ 1 & x_n & x_n^2 & \cdots & x_n^{n-1} \end{vmatrix} = \prod_{1 \leq i < j \leq n} (x_j - x_i) \]

    证明思路(以三阶为例): 计算三阶范德蒙德行列式: \[ \begin{vmatrix} 1 & x_1 & x_1^2 \\\\ 1 & x_2 & x_2^2 \\\\ 1 & x_3 & x_3^2 \end{vmatrix} \]

    1. 展开计算: 按第一列展开: \[ 1 \cdot \begin{vmatrix}x_2 & x_2^2 \\\\ x_3 & x_3^2\end{vmatrix} - 1 \cdot \begin{vmatrix}x_1 & x_1^2 \\\\ x_3 & x_3^2\end{vmatrix} + 1 \cdot \begin{vmatrix}x_1 & x_1^2 \\\\ x_2 & x_2^2\end{vmatrix} \] 计算二阶行列式: \[ x_2x_3^2 - x_3x_2^2 - (x_1x_3^2 - x_3x_1^2) + (x_1x_2^2 - x_2x_1^2) \] 化简后得: \[ (x_2 - x_1)(x_3 - x_1)(x_3 - x_2) \]

    2. 归纳推广:

      • 二阶: \(\det(V) = x_2 - x_1\)
      • 三阶: \(\det(V) = (x_2 - x_1)(x_3 - x_1)(x_3 - x_2)\)
      • n阶: 推广为所有 \(x_j - x_i\)(\(i < j\))的乘积, 保证插值多项式唯一存在.

    几何意义: 若 \(x_i\) 互不相等, 则行列式非零, 说明多项式插值有唯一解(也可理解为线性方程组有唯一解).若有两个 \(x_i\) 相同, 行列式为零, 插值失败(两点重合无法确定曲线), 方程无解或有无穷多解.

    记忆方式: 用笔一次抵住一次行(列)的每一项, 并枚举后面的所有行(列), 若遇到相等的值, 那么行列式值为0

总结: 特殊行列式——数学中的"标准化工具"

爪形行列式通过消去法展现化简的优雅, 范德蒙德行列式用连乘积守护插值的唯一性.它们像数学中的瑞士军刀, 专为特定问题而生.

4.3 为什么非正方形矩阵没有行列式?

思考: 行列式本质是线性变换的体积缩放率.非方阵如 \(2 \times 3\) 矩阵会将三维空间压缩到二维, 导致“体积”概念消失.就像试图测量一张纸的“厚度”——没有意义!

五、克莱姆法则: 用行列式解方程的"魔法公式"

5.1 使用前提与几何直觉

前提条件: 1. 方程组必须是方阵(方程数=未知数个数). 2. 系数矩阵 \(A\) 的行列式 \(\det(A) \neq 0\)(即线性变换未压缩空间维度).

通俗解释: 想象二维空间中两条直线相交于一点, 三维空间中三个平面交于一点.若行列式非零, 说明这些超平面“不共面”, 存在唯一交点.若行列式为零, 则超平面平行或重叠, 解不唯一或无解.

5.2 克莱姆法则的"体积比例"证明

核心思想: 将未知数 \(x_i\) 的解表示为替换列向量后新体积与原体积的比值.

以二维为例: 方程组: \[ \begin{cases} 2x + 3y = 8 \\\\ 4x + 5y = 12 \end{cases} \] 系数矩阵 \(A = \begin{pmatrix}2 & 3 \\\\ 4 & 5\end{pmatrix}\), \(\det(A) = -2 \neq 0\).

  • \(x\): 将第一列替换为常数项 \(\begin{pmatrix}8 \\\\ 12\end{pmatrix}\), 得到 \(A_1 = \begin{pmatrix}8 & 3 \\\\ 12 & 5\end{pmatrix}\). \(\det(A_1) = 8 \times 5 - 3 \times 12 = 4\), 则 \(x = \det(A_1)/\det(A) = -2\).

  • 几何类比: 原行列式 \(\det(A)\) 是基向量 \(\mathbf{a}_1, \mathbf{a}_2\) 张成的平行四边形面积, 替换第一列后 \(\det(A_1)\) 是向量 \((8,12)\)\(\mathbf{a}_2\) 张成的面积.\(x\) 的绝对值即为面积缩放比例, 符号表示方向是否翻转.

5.3 齐次方程组的"零解陷阱"

齐次方程组: \(Ax = 0\)(等号右边全为0).

  • \(\det(A) \neq 0\): 仅有零解. 几何意义: 空间未被压缩, 唯一能映射到原点的是原点本身.

  • \(\det(A) = 0\): 存在无穷多非零解. 几何意义: 空间被压缩到低维, 存在一条直线或平面上的点都被映射到原点.

示例对比: 1. 非零行列式: \[ \begin{cases} 2x + 3y = 0 \\\\ 4x + 6y = 0 \end{cases} \] 系数矩阵行列式 \(\det(A) = 2 \times 6 - 3 \times 4 = 0\), 存在非零解(如 \(x=3, y=-2\)).

  1. 零行列式: \[ \begin{cases} 1x + 2y = 0 \\\\ 3x + 4y = 0 \end{cases} \] \(\det(A) = 1 \times 4 - 2 \times 3 = -2 \neq 0\), 仅有零解 \(x=y=0\).

5.4 克莱姆法则的局限性

缺点: - 计算量大: 每解一个变量需计算一个 \(n\) 阶行列式, 时间复杂度为 \(O(n!)\). - 仅适用于理论分析, 实际计算中多用高斯消元法.

趣味思考: 如果强行用克莱姆法则解 \(10\) 阶方程组, 计算量相当于数清银河系所有星星——你是炒鸡计算机吗就敢这么算

总结: 克莱姆法则——行列式与方程组的"几何桥梁"

克莱姆法则用行列式的比值巧妙链接了线性方程组的解与空间体积变化, 虽然不是给人用的但是计算机用起来就很舒适.

总结: 行列式学习的收获与感悟

通过学习行列式, 我不仅掌握了它的计算方法, 还理解了它背后的几何意义和数学思想.行列式就像一个"体积计算器", 帮助我们理解矩阵对应的线性变换对空间的影响.虽然它的公式看起来复杂, 但只要掌握了展开、性质和计算技巧, 就能轻松应对各种问题.

系统功能更新

日志翻新

日志内容优化

由于日志系统过于简陋, 且可读性极低, 甚至中英文夹杂, 现在重新设计日志系统, 对于插件数据库以及系统日志都做了优化 旧版日志(部分还能入眼的):

1
2
3
2025-03-28 21:31:08 | INFO     | LegendBotDB:495 - Database: Set chatroom 49981891388@chatroom whitelist successfully
2025-03-28 21:31:08 | ERROR | LegendBotDB:500 - Database: Set chatroom 49981891388@chatroom whitelist failed, error: (sqlite3.IntegrityError) NOT NULL constraint failed: chatroom.chatroom_id
[SQL: INSERT INTO chatroom (members, whitelist, llm_thread_id) VALUES (?, ?, ?)]
新版日志
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2025-04-06 12:27:30 | INFO     | LegendBot:79 - 收到消息: 1, glm img 一只可爱的小猫
wxid_e3p1sq5livwb32 - wxid_e3p1sq5livwb32
2025-04-06 12:27:30 | INFO | LegendBot:96 - 管理员消息: glm img 一只可爱的小猫
2025-04-06 12:27:30 | INFO | LegendBotDB:130 - 用户 wxid_e3p1sq5livwb32 运行状态更新成功
2025-04-06 12:27:30 | INFO | LegendBotDB:130 - 用户 wxid_e3p1sq5livwb32 运行状态更新成功
2025-04-06 12:27:30 | DEBUG | main:87 - 生成图片: 一只可爱的小猫
2025-04-06 12:27:31 | DEBUG | client:155 - 08201000
2025-04-06 12:27:31 | INFO | LegendBotDB:130 - 用户 wxid_e3p1sq5livwb32 运行状态更新成功
2025-04-06 12:27:37 | DEBUG | main:102 - 状态码: 200
2025-04-06 12:27:38 | DEBUG | client:155 - 08211000
2025-04-06 12:27:57 | INFO | LegendBot:79 - 收到消息: 1, glm video 小猫奔跑
wxid_e3p1sq5livwb32 - wxid_e3p1sq5livwb32
2025-04-06 12:27:57 | INFO | LegendBot:96 - 管理员消息: glm video 小猫奔跑
2025-04-06 12:27:57 | INFO | LegendBotDB:130 - 用户 wxid_e3p1sq5livwb32 运行状态更新成功
2025-04-06 12:27:58 | DEBUG | client:155 - 08201000
2025-04-06 12:27:58 | INFO | LegendBotDB:130 - 用户 wxid_e3p1sq5livwb32 运行状态更新成功

文件写入优化

讲个笑话, 之前的文件写入模式是w...我就说为啥天天日志都那么少

1
2
3
4
5
# utils/logger.py
# 添加日志文件处理器
logger.add(mode="a", all_log_path, level="INFO", format="{time:YYYY-MM-DD HH:mm:ss} | <level>{level: <8}</level> | <cyan>{module}</cyan>:<magenta>{line}</magenta> - <level>{message}</level>")

logger.add(mode="a", error_log_path, level="ERROR", format="{time:YYYY-MM-DD HH:mm:ss} | <level>{level: <8}</level> | <cyan>{module}</cyan>:<magenta>{line}</magenta> - <level>{message}</level>")

消息缩略图保存

在保存视频时, 需要视频的缩略图(thumb)信息, 于是顺手在保存消息的方法中加入了这一功能, 并更新了数据库表

1
2
3
4
5
6
7
8
9
10
11
12
13
database/messageDB.py
message = Message(
msg_id=msg.id,
type=msg.type,
xml=msg.xml,
content=msg.content,
extra=msg.extra,
sender=msg.sender,
roomid=msg.roomid,
thumb=msg.thumb,
is_at=msg.is_at(self_wxid),
timestamp=datetime.now()
)

腐竹个人功能(管理与消息)支持加入

在机器人运行过程中, 有许多管理命令优先级特别高(比如重启程序等系统命令), 而往往这些消息内容都比较简单, 但是不能受到积分、黑名单、消息频率限制的限制, 于是单独加入了该类消息处理逻辑, 以及存储对应的方法的文件ignore.py

但现在还有点小屎山, todolist中会提到

  • 加载部分 由于ignore.py默认不存在, 所以需要加入异常处理

    1
    2
    3
    4
    5
    6
    try:
    import utils.ignore as ignore
    except ModuleNotFoundError:
    flag = False
    else:
    flag = True

  • 判断部分

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    # LegendBot.py
    """来自管理员先走一遍判断"""
    if msg.sender in config.admin:
    logger.info(f"管理员消息: {msg.content}")

    if msg.content == '重启程序':
    restartProgram()
    return

    elif msg.content == '结束':
    self.bot.cleanup()
    os._exit(0)

    elif '加群' in msg.content:
    self.DB.set_chatroom_whitelist(to, True)
    self.bot.sendMsg('已添加到白名单', to, at)
    return

    elif flag:
    if msg.content == 'tcp':
    links = await ignore.fetch_info_from_website()
    if links:
    self.bot.sendMsg(links, to, at)
    return
    else:
    self.bot.sendMsg('获取 TCP 地址失败', to, at)
    return

发送消息速率限制

在机器人中, 有一些消息是高频的, 比如积分、黑名单、消息频率限制等, 也有一些错误信息是\(O(1)\)级别的, 这些消息的发送频率需要被限制, 否则会导致机器人被风控, 所以在sendMsg方法中加入了消息发送速率限制, 核心原理是: 发送消息前将用户的状态设为running, 并等待随机的时间, 发送后再设置回来, 这样用户要是高频发送消息一样会被增加黑名单指数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# utils/LegendBot.py
def sendMsg(self, msg: str, receiver: str, at_list: list | str = None) -> None:
""" 发送消息
:param msg: 消息字符串
:param receiver: 接收人wxid或者群id
:param at_list: 要@的wxid, @所有人的wxid为:notify@all
"""
# msg 中需要有 @ 名单中一样数量的 @
if at_list:
LegendBotDB().set_running(at_list, True)
else:
LegendBotDB().set_running(receiver, True)
time.sleep(random.randint(1, 3) / random.randint(2, 10))
ats = ""
at = ""
if isinstance(at_list, str):
at_list = [at_list]
if at_list:
if at_list == ["notify@all"]: # @所有人
ats = "@所有人"
else:
for wxid in at_list:
# 根据 wxid 查找群昵称
ats += f"@{self.get_alias_in_chatroom(wxid, receiver)}\u2005"
at += f"{wxid},"
at = at[:-1] if at.endswith(',') else at
# {msg}{ats} 表示要发送的消息内容后面紧跟@,例如 北京天气情况为:xxx @张三
if ats == "":
self.send_text(msg, receiver, at)
else:
self.send_text(f"{ats} {msg}", receiver, at)
if at_list:
LegendBotDB().set_running(at_list[0], False)
else:
LegendBotDB().set_running(receiver, False)

技术突破

glm调用

这周最大的工作量便是增加了通过调用智谱清言来理解图片/视频, 生成图片/视频的功能了, 改了很多版, 前前后后想了很多方法, 也遇到了很多棘手的问题, 言简意赅描述下经历吧

阶段一: openaiSDK

在编写AI聊天插件时, 直接使用了openai的轮子, 效果喜人, 于是便想能不能用openai的SDK来调用GLM, 然而在编写到视频生成时, 发现openai没有视频生成接口...

阶段二: GLM SDK

由于是智谱清言官方的SDK, 接口方面自然是完整的, 然而其对于异步调用的支持非常不友好(没有相关的方法)

阶段三: GLM API

思来想去只能用最朴素的方法了: 直接放弃SDK, 自己实现

原先的想法是用aiohttp, 就像调用肯德基疯狂星期四api接口那样, 请求体配置一下完美, 然而在测试时发现: 卡住了?!

不信邪, 换成openai异步调用调试具备接口的功能, 发现还是

这下又多了一个不能用openai接口的理由

aiohttp不行, 那用自己写的run_sync, 包装requests同步调用总行了吧

run_sync都不行, 难道是调用本身出问题了? try.py里单独测试一波同步调用, 快得飞起

同步调用没问题, 换成异步就不行, 哪种来了都不行, 新时代科幻小说出版了

目前的推测是可能智谱清言对异步请求有自己独特的检测方法, 但是为什么图像理解就可以异步呢?

破案了

在图像理解的说明文档中, 它的目录结构是这样的 图像理解 别杠, 我知道有人要杠, 啊煮啵煮啵, 你这个是聊天大模型的页面啊, 那是因为图像理解目录不长这样(废话), 聊天模型和图像理解模型一样的, 都是生成文字, 支持异步

而在图片生成目录中, 它的目录结构是这样的 图片生成

这下问题就很清晰了, 智谱清言本身不支持图像的异步生成, 至于为什么会卡住, 那我只能说, 我不到啊

最终解决方案

有句话说得好, asyncio解决不了的事情, 就交给threading来干, 还解决不了, 那就交给multiprocessing来干

既然asyncio使出浑身解数都解决不了, 那就交给threading来干吧

思路是: 在接收到生成命令的时候, 先经过异步方法对消息进行预处理, 提取参数信息, 然后将参数转变为字典, 加到生成任务的队列中, 然后由threading来处理生成任务, 并返回给用户

补充说明: 图像生成和视频生成代码略有不同, 虽然同样是同步调用, 但图像生成会直接返回结果, 但是视频生成返回的是任务id, 需要等待一段时间去自己查询, 但都是子线程中同步操作可以解决的

  • 子线程处理池, 生成队列以及处理线程的初始化
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    def __init__(self):
    ...
    self.message_queue = Queue()
    self.img_executor = ThreadPoolExecutor(max_workers=5)
    self.video_executor = ThreadPoolExecutor(max_workers=3)
    self.process = Thread(target=self.process_queue)
    self.process.start()
    signal.signal(signal.SIGINT, self.process.join)

    def process_queue(self):
    """持续处理队列中的消息"""
    while self.enable:
    try:
    # 从队列中获取消息
    message = self.message_queue.get(timeout=1) # 等待消息,超时为 1 秒

    method = message['method']
    to, at = message['to'], message['at']
    prompt = message['prompt']
    msg = message['msg']
    bot = message['bot']
    if method == 'generate_image':
    size = message['size']
    # 提交任务到线程池
    self.img_executor.submit(self.generate_image, bot, prompt, size, to, at, msg)
    self.message_queue.task_done() # 标记任务完成

    else:
    audio = message['audio']
    base = message['base']
    self.video_executor.submit(self.generate_video, bot, prompt, audio, base, to, at, msg)
    self.message_queue.task_done() # 标记任务完成

    except Empty:
    pass

    except Exception:
    pass

*值得一提的是, 在process_queue方法中, 由于while循环的执行, 会导致Ctrl+C退出程序不可用, 于是加入了signal捕捉信号

1
2
3
4
5
6
7
8
9
10
# robot.py
async def run():
...
def signal_handler(signum, frame):
logger.info("收到终止信号,正在关闭...")
bot.cleanup()
os._exit(0)

signal.signal(signal.SIGINT, signal_handler)
...

插件更新

GLM 智谱清言插件

主要处理逻辑与方法在前文已提及, 这里仅作代码说明

  • 消息处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    # plugins/GLM/main.py
    #* 理解(OK)
    if msg.content.startswith('glm v4'):
    #* 说明
    if msg.content == 'glm v4':
    bot.sendMsg('图像视频理解相关功能\n`glm v4 local 多媒体名`传入本地多媒体\n`glm v4 url 链接`传入网络多媒体', to, at)
    return

    model = 'v4'

    #* 本地多媒体
    if msg.content.startswith('glm v4 local '):
    image = msg.content.split(' ')[3]
    image = os.path.basename(image)
    img: Path = Path().cwd() / 'plugins/ImageDeal/images' / msg.sender / image
    if not img.exists():
    bot.sendMsg('多媒体不存在', to, at)
    return

    if img.suffix == '.mp4':
    mode = 'video'
    else:
    mode = 'image'

    LegendBotDB().set_running(msg.sender, True)
    with open(img, 'rb') as f:
    base = base64.b64encode(f.read()).decode('utf-8')

    rsp = await self.GLM_V4(base, mode)

    if rsp:

    bot.sendMsg(rsp.get('choices')[0].get('message').get('content'), to, at)
    else:
    bot.sendMsg('请求失败', to, at)

    LegendBotDB().add_points(msg.sender, -3)
    LegendBotDB().set_running(msg.sender, False)
    return

    #* 网络链接
    elif msg.content.startswith('glm v4 url '):
    url = msg.content.split(' ')[3]
    mode = 'video' if (url.endswith('.mp4') or url.endswith('.wav')) else 'image'

    LegendBotDB().set_running(msg.sender, True)
    rsp = await self.client.chat.completions.create(
    model=self.model[model],
    messages=[
    {
    "role": "user",
    "content": [
    {
    "type": f"{mode}_url",
    f"{mode}_url": {
    "url": url
    }
    },
    {
    "type": "text",
    "text": "请描述这个图片" if mode == 'image' else "请描述这个视频"
    }
    ]
    }
    ]
    )

    bot.sendMsg(rsp.choices[0].message.content, to, at)
    LegendBotDB().add_points(msg.sender, -3)
    LegendBotDB().set_running(msg.sender, False)
    return

    #* 图片生成
    if msg.content.startswith('glm img'):
    #* 说明
    if msg.content == 'glm img':
    bot.sendMsg('图片生成相关功能\n`glm img 要求 尺寸\n其中尺寸可在以下范围中选择:1024x1024,768x1344,864x1152,1344x768,1152x864,1440x720,720x1440, 默认为1024x1024`', to, at)
    return

    if msg.content.startswith('glm img '):
    msg.content = msg.content[8:]
    if msg.content.count(' ') == 1:
    prompt, size = msg.content.split(' ')
    else:
    prompt = msg.content
    size = '1024x1024'

    if size not in ['1024x1024', '768x1344', '864x1152', '1344x768', '1152x864', '1440x720', '720x1440']:
    size = '1024x1024'

    self.message_queue.put({
    "bot": bot,
    "prompt": prompt,
    "size": size,
    "method": "generate_image",
    "to": to,
    "at": at,
    'msg': msg
    })
    bot.sendMsg("已接收图片生成请求,正在处理...", to, at)
    return

    #* 视频生成
    if msg.content.startswith("glm video"):
    # 将消息添加到队列
    #* 说明
    if msg.content == "glm video":
    bot.sendMsg(
    "视频生成相关功能\n"
    "`glm video 要求 --audio 音频需求 --mode local/url 图片基础`\n"
    "音频需求可选择1或0, 1为需要音频, 0为不需要音频, 默认为0\n"
    "若有视频创作的图片基础, 则选择local(本地)或url(网络链接), 图片基础部分与`glm v4`格式相同, 默认无基础",
    to,
    at,
    )
    return

    try:
    # 解析指令内容
    msg.content = msg.content[10:] # 去掉 "glm video " 前缀
    parts = msg.content.split(" ")

    # 默认值
    prompt = parts[0] # 视频生成的要求
    audio = "0" # 默认不需要音频
    base_type = None
    base_value = None

    # 解析参数
    if "--audio" in parts:
    audio_index = parts.index("--audio") + 1
    if audio_index < len(parts):
    audio = parts[audio_index]

    if "--mode" in parts:
    mode_index = parts.index("--mode") + 1
    if mode_index < len(parts):
    base_type = parts[mode_index]
    if base_type not in ["local", "url"]:
    bot.sendMsg("模式参数错误,请选择 local 或 url", to, at)
    return

    # 获取图片基础
    base_value_index = mode_index + 1
    if base_value_index < len(parts):
    base_value = parts[base_value_index]

    logger.debug(f"解析指令: prompt={prompt}, audio={audio}, base_type={base_type}, base_value={base_value}")

    # 检查图片基础部分
    if base_type == "local":
    base_path = os.path.basename(base_value)
    img: Path = Path().cwd() / "plugins/ImageDeal/images" / msg.sender / base_path
    if not img.exists():
    bot.sendMsg("本地图片基础不存在", to, at)
    return
    with open(img, "rb") as f:
    base = base64.b64encode(f.read()).decode("utf-8")
    elif base_type == "url":
    base = base_value
    else:
    base = None

    # 将消息添加到队列
    self.message_queue.put(
    {
    "bot": bot,
    "prompt": prompt,
    "audio": audio,
    "base": base,
    "method": "generate_video",
    "to": to,
    "at": at,
    'msg': msg
    }
    )
    bot.sendMsg("已接收视频生成请求,正在处理...", to, at)

  • 队列处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    # plugins/GLM/main.py
    def process_queue(self):
    """持续处理队列中的消息"""
    while self.enable:
    try:
    # 从队列中获取消息
    message = self.message_queue.get(timeout=1) # 等待消息,超时为 1 秒

    method = message['method']
    to, at = message['to'], message['at']
    prompt = message['prompt']
    msg = message['msg']
    bot = message['bot']
    if method == 'generate_image':
    size = message['size']
    # 提交任务到线程池
    self.img_executor.submit(self.generate_image, bot, prompt, size, to, at, msg)
    self.message_queue.task_done() # 标记任务完成

    else:
    audio = message['audio']
    base = message['base']
    self.video_executor.submit(self.generate_video, bot, prompt, audio, base, to, at, msg)
    self.message_queue.task_done() # 标记任务完成

    except Empty:
    pass

    except Exception:
    pass

  • 图像理解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    # plugins/GLM/main.py
    async def GLM_V4(self, base, mode):
    """调用视频生成 API"""
    async with sem['GLM-4V-Flash']: # 使用信号量限制并发
    try:
    async with aiohttp.ClientSession() as session:
    # 假设视频生成 API 的 URL 和请求格式如下
    api_url = "https://open.bigmodel.cn/api/paas/v4/chat/completions"

    if mode == 'video':
    payload = {
    'model': 'glm-4v-flash',
    'messages': [
    {
    "role": "user",
    "content": [
    {
    "type": "video_url",
    "video_url": {
    "url" : base
    }
    },
    {
    "type": "text",
    "text": "请仔细描述这个视频"
    }
    ]
    }
    ]
    }
    else:
    payload = {
    'model': 'glm-4v-flash',
    'messages': [
    {
    "role": "user",
    "content": [
    {
    "type": "image_url",
    "image_url": {
    "url" : base
    }
    },
    {
    "type": "text",
    "text": "请仔细描述这个视频"
    }
    ]
    }
    ]
    }

    headers = {
    "Authorization": f"Bearer {self.key}",
    "Content-Type": "application/json",
    "Connection": "close", # 显式关闭连接
    }

    async with session.post(api_url, json=payload, headers=headers) as response:
    if response.status == 200:
    result = await response.json()
    return result
    else:
    logger.error(f"API 请求失败,状态码: {response.status}, {response.text()}")
    return None
    except Exception as e:
    logger.error(f"调用 API 时发生错误: {e}")
    logger.error(traceback.format_exc())

  • 图像生成

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    # plugins/GLM/main.py
    def generate_image(self, bot: LegendWechatBot, prompt, size, to, at, msg: WxMsg):
    """生成图片"""
    try:
    LegendBotDB().set_running(msg.sender, True)
    logger.debug(f"生成图片: {prompt}")
    api_url = "https://open.bigmodel.cn/api/paas/v4/images/generations"
    payload = {
    "model": "cogview-3-flash", # 确保模型名称正确
    "prompt": prompt, # 确保输入内容符合要求
    "size": size, # 确保尺寸符合要求
    }
    headers = {
    "Authorization": f"Bearer {self.key}",
    "Content-Type": "application/json",
    "Connection": "close", # 显式关闭连接
    }

    # 同步请求
    response = requests.post(api_url, json=payload, headers=headers, timeout=30)
    logger.debug(f"状态码: {response.status_code}")
    if response.status_code == 200:
    result = response.json()
    bot.send_image(result['data'][0]['url'], to)
    LegendBotDB().set_running(msg.sender, False)
    LegendBotDB().add_points(msg.sender, -4)
    else:
    logger.error(f"API 请求失败,状态码: {response.status_code}, 错误信息: {response.text}")
    LegendBotDB().set_running(msg.sender, False)
    except Exception as e:
    logger.error(f"调用 API 时发生错误: {e}")
    logger.error(traceback.format_exc())

    def generate_video(self, bot: LegendWechatBot, prompt, audio, base, to, at, msg: WxMsg):
    """生成视频"""
    try:
    LegendBotDB().set_running(msg.sender, True)
    logger.debug(f"生成视频: {prompt}")
    api_url = "https://open.bigmodel.cn/api/paas/v4/videos/generations"
    payload = {
    "model": "cogvideox-flash", # 确保模型名称正确
    "prompt": prompt, # 确保输入内容符合要求
    "with_audio": True if audio == '1' else False,
    "image_url": base
    }
    headers = {
    "Authorization": f"Bearer {self.key}",
    "Content-Type": "application/json",
    "Connection": "close", # 显式关闭连接
    }
    rsp = requests.post(api_url, json=payload, headers=headers, timeout=30)
    if rsp.status_code == 200:
    rsp = rsp.json()
    else:
    logger.error(f"API 请求失败,状态码: {rsp.status_code}, 错误信息: {rsp.text}")
    LegendBotDB().set_running(msg.sender, False)
    return
    task_id = rsp['id']
    api_url = f'https://open.bigmodel.cn/api/paas/v4/async-result/{task_id}'
    payload = {
    "id": task_id,
    }
    status = requests.get(api_url, json=payload, headers=headers, timeout=30)
    if status.status_code == 200:
    status = status.json()
    else:
    logger.error(f"API 请求失败,状态码: {status.status_code}, 错误信息: {status.text}")
    LegendBotDB().set_running(msg.sender, False)
    return
    while status['task_status'] != 'SUCCESS':
    time.sleep(5)
    status = requests.get(api_url, json=payload, headers=headers, timeout=30)
    if status.status_code == 200:
    status = status.json()
    else:
    logger.error(f"API 请求失败,状态码: {status.status_code}, 错误信息: {status.text}")
    LegendBotDB().set_running(msg.sender, False)
    return

    bot.send_image(status['video_result'][0]['url'], to)
    LegendBotDB().set_running(msg.sender, False)
    LegendBotDB().add_points(msg.sender, -5)

    except Exception as e:
    logger.error(f"调用 API 时发生错误: {e}")
    logger.error(traceback.format_exc())

ImageDeal下载视频

由于有插件需要用到本地视频, 那顺手加上视频下载功能了, 只不过在后续调用中需要判断后缀名(一般情况微信下载的图片后缀名为jpg, 视频后缀名为mp4)

1
2
3
4
5
6
7
8
9
10
# plugins/ImageDeal/main.py
@on_quote_message
async def downloadMedia(self, bot: LegendWechatBot, msg: WxMsg):
...
if quoteMsg[0].type == 3:
res = await run_sync(bot.download_image)(msgId, quoteMsg[0].extra, os.path.abspath(os.path.join(self.folder, msg.sender)), 30)

elif quoteMsg[0].type == 43:
res = await run_sync(bot.download_video)(msgId, quoteMsg[0].thumb, os.path.abspath(os.path.join(self.folder, msg.sender)), 30)
...

todo list

  • 继续优化日志
  • 新增管理员命令插件, 用来存放非系统管理员命令
  • 完善用户与开发文档
  • 新增成语接龙插件, 看图猜成语插件
  • 添加积分判断功能

项目已开源至 Github ,欢迎star和fork 若你觉得对你的开发有帮助, 或是对你的生活提供了方便, 欢迎来 爱发电 赞助 爱发电 如果想一起开发或贡献插件等, 欢迎在相关标准制定后按照标准提交PR, 或 联系作者

1. pair 的使用

1.1 特点与适用场景

  • 特点: pair 是 C++ 标准库中的一个模板类,用于存储两个不同类型的数据它提供了快速访问两个元素的接口
  • 适用场景: 当需要同时存储两个相关数据时,比如键值对、二维点坐标、或者需要按某个字段排序时

1.2 典型操作

  • 定义: pair<Type1, Type2> p;
  • 初始化: pair<int, int> p(1, 2);auto p = make_pair(1, 2);
  • 访问元素: p.firstp.second
  • 排序: pair 默认按 first 排序,first 相同时按 second 排序

1.3 使用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
vector<pair<int, string>> vec = {{3, "apple"}, {1, "banana"}, {2, "cherry"}};
sort(vec.begin(), vec.end());
for (auto &p : vec) {
cout << p.first << " " << p.second << endl;
}
return 0;
}

输出:

1
2
3
1 banana
2 cherry
3 apple

2. tuple 的使用

2.1 特点与适用场景

  • 特点: tuplepair 的扩展,可以存储多个不同类型的元素它提供了灵活的多字段存储能力
  • 适用场景: 当需要存储多个相关数据时,比如存储学生信息(姓名、年龄、成绩)

2.2 典型操作

  • 定义: tuple<Type1, Type2, ..., Typen> t;
  • 初始化: tuple<int, int, int> t(1, 2, 3);auto t = make_tuple(1, 2, 3);
  • 访问元素: get<0>(t)get<1>(t)

2.3 使用实例

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <vector>
#include <tuple>
using namespace std;

int main() {
vector<tuple<int, int, int>> vec = {{3, 1, 5}, {1, 2, 4}, {2, 3, 6}};
for (auto &t : vec) {
cout << get<0>(t) << " " << get<1>(t) << " " << get<2>(t) << endl;
}
return 0;
}

输出:

1
2
3
3 1 5
1 2 4
2 3 6

3. 总结

  • pair: 适合存储两个相关数据,常用于排序、映射等场景
  • tuple: 适合存储多个相关数据,提供了更大的灵活性
  • 在信息学竞赛中,pair 更常用,尤其是需要按某个字段排序时而 tuple 在需要存储多字段数据时非常方便,但使用频率相对较低掌握它们的基本操作和适用场景,可以显著提高代码的简洁性和效率

auto 你是我的神

1. vector

  • 特点:动态数组,支持随机访问,插入和删除效率较低。
  • 适用场景:需要频繁随机访问的场景。
  • 典型操作
    • push_back(): 添加元素到末尾。
    • pop_back(): 删除末尾元素。
    • size(): 获取当前大小。
    • capacity(): 获取当前容量。
    • resize(): 调整大小。
    • erase(): 删除指定位置的元素。
    • clear(): 清空容器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <vector>
using namespace std;

int main() {
vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);

cout << "Vector size: " << vec.size() << endl;
cout << "Elements: ";
for (auto x : vec) {
cout << x << " ";
}
cout << endl;

vec.erase(vec.begin() + 1); // 删除索引为1的元素
cout << "After erase: ";
for (auto x : vec) {
cout << x << " ";
}
cout << endl;

vec.clear();
cout << "After clear: " << vec.size() << endl;

return 0;
}

2. list

  • 特点:双向链表,插入和删除效率高,不支持随机访问。
  • 适用场景:需要频繁插入和删除的场景。
  • 典型操作
    • push_front(): 添加元素到头部。
    • push_back(): 添加元素到尾部。
    • pop_front(): 删除头部元素。
    • pop_back(): 删除尾部元素。
    • insert(): 在指定位置插入元素。
    • erase(): 删除指定位置的元素。
    • reverse(): 反转链表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <list>
using namespace std;

int main() {
list<int> lst;
lst.push_back(1);
lst.push_back(2);
lst.push_front(0);

cout << "List elements: ";
for (auto x : lst) {
cout << x << " ";
}
cout << endl;

lst.erase(lst.begin()); // 删除第一个元素
cout << "After erase: ";
for (auto x : lst) {
cout << x << " ";
}
cout << endl;

lst.reverse();
cout << "After reverse: ";
for (auto x : lst) {
cout << x << " ";
}
cout << endl;

return 0;
}

3. deque

  • 特点:双端队列,支持从两端插入和删除,支持随机访问。
  • 适用场景:需要从两端插入和删除的场景。
  • 典型操作
    • push_front(): 添加元素到头部。
    • push_back(): 添加元素到尾部。
    • pop_front(): 删除头部元素。
    • pop_back(): 删除尾部元素。
    • front(): 获取头部元素。
    • back(): 获取尾部元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <deque>
using namespace std;

int main() {
deque<int> dq;
dq.push_back(1);
dq.push_back(2);
dq.push_front(0);

cout << "Deque elements: ";
for (auto x : dq) {
cout << x << " ";
}
cout << endl;

dq.pop_front(); // 删除头部元素
cout << "After pop_front: ";
for (auto x : dq) {
cout << x << " ";
}
cout << endl;

dq.pop_back(); // 删除尾部元素
cout << "After pop_back: ";
for (auto x : dq) {
cout << x << " ";
}
cout << endl;

return 0;
}

4. set

  • 特点:有序集合,元素唯一,支持快速查找。
  • 适用场景:需要存储唯一元素并快速查找的场景。
  • 典型操作
    • insert(): 插入元素。
    • erase(): 删除元素。
    • find(): 查找元素。
    • count(): 检查元素是否存在。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <set>
using namespace std;

int main() {
set<int> s;
s.insert(3);
s.insert(1);
s.insert(2);

cout << "Set elements: ";
for (auto x : s) {
cout << x << " ";
}
cout << endl;

s.erase(2); // 删除元素2
cout << "After erase: ";
for (auto x : s) {
cout << x << " ";
}
cout << endl;

if (s.find(3) != s.end()) {
cout << "Element 3 exists" << endl;
}

return 0;
}

5. map

  • 特点:键值对集合,键唯一,支持快速查找。
  • 适用场景:需要存储键值对并快速查找的场景。
  • 典型操作
    • insert(): 插入键值对。
    • erase(): 删除键值对。
    • find(): 查找键对应的值。
    • count(): 检查键是否存在。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <map>
using namespace std;

int main() {
map<string, int> m;
m["Alice"] = 25;
m["Bob"] = 30;
m.insert(make_pair("Charlie", 35));

cout << "Map elements:" << endl;
for (auto &pair : m) {
cout << pair.first << ": " << pair.second << endl;
}

m.erase("Bob"); // 删除键"Bob"
cout << "After erase:" << endl;
for (auto &pair : m) {
cout << pair.first << ": " << pair.second << endl;
}

if (m.find("Alice") != m.end()) {
cout << "Alice's age: " << m["Alice"] << endl;
}

return 0;
}

6. stack

  • 特点:后进先出(LIFO)的容器适配器。
  • 适用场景:需要模拟栈操作的场景。
  • 典型操作
    • push(): 添加元素到栈顶。
    • pop(): 删除栈顶元素。
    • top(): 获取栈顶元素。
    • empty(): 检查栈是否为空。
    • size(): 获取栈的大小。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <stack>
using namespace std;

int main() {
stack<int> stk;
stk.push(1);
stk.push(2);
stk.push(3);

cout << "Stack size: " << stk.size() << endl;
cout << "Top element: " << stk.top() << endl;

stk.pop(); // 删除栈顶元素
cout << "After pop: " << stk.top() << endl;

return 0;
}

7. queue

  • 特点:先进先出(FIFO)的容器适配器。
  • 适用场景:需要模拟队列操作的场景。
  • 典型操作
    • push(): 添加元素到队尾。
    • pop(): 删除队首元素。
    • front(): 获取队首元素。
    • back(): 获取队尾元素。
    • empty(): 检查队列是否为空。
    • size(): 获取队列的大小。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <queue>
using namespace std;

int main() {
queue<int> q;
q.push(1);
q.push(2);
q.push(3);

cout << "Queue size: " << q.size() << endl;
cout << "Front element: " << q.front() << endl;
cout << "Back element: " << q.back() << endl;

q.pop(); // 删除队首元素
cout << "After pop: " << q.front() << endl;

return 0;
}

总结

  • vector: 动态数组,适合随机访问。
  • list: 双向链表,适合频繁插入和删除。
  • deque: 双端队列,支持两端操作。
  • set: 有序集合,支持快速查找。
  • map: 键值对集合,支持快速查找。
  • stack: 后进先出,适合栈操作。
  • queue: 先进先出,适合队列操作。

通过这些容器,可以根据具体需求选择合适的容器来优化代码性能和可读性。

一、基础

基础

  • 容器
    • vector
    • list
    • deque
    • set
    • map
    • stack
    • queue
  • 算法
    • sort
    • binary_search
    • next_permutation
  • 其他
    • pair
    • tuple
    • 命名空间
    • λ表达式
    • pb_ds

算法复杂度分析

  • 时间复杂度
  • 空间复杂度

二、核心算法

算法基础

  • 枚举
    • 一维枚举
      • 应用场景:遍历数组或列表
      • 优化技巧:剪枝、减少冗余
    • 二维枚举
      • 应用场景:矩阵或二维数组
      • 优化技巧:循环嵌套优化
    • 多维枚举
      • 应用场景:高维数据
      • 优化技巧:降维处理
  • 模拟
    • 直接模拟
      • 应用场景:按步骤模拟问题过程
      • 优化技巧:减少不必要的计算
    • 状态模拟
      • 应用场景:状态变化问题
      • 优化技巧:状态压缩
    • 事件驱动模拟
      • 应用场景:事件触发的动态过程
      • 优化技巧:事件优先处理
  • 递归 & 分治
    • 递归
      • 基本概念:函数调用自身
      • 应用场景:树结构、回溯问题
      • 优化技巧:记忆化递归、尾递归
    • 分治
      • 基本概念:分而治之
      • 应用场景:排序、大规模数据处理
      • 优化技巧:平衡子问题规模
  • 贪心
    • 基本概念:局部最优选择
    • 应用场景:最优化问题
    • 优化技巧:贪心选择性质、验证贪心策略
    • 典型问题:活动选择、背包问题、霍夫曼编码
  • 排序
    • 冒泡排序
      • 时间复杂度:O(n²)
      • 应用场景:小规模数据
    • 选择排序
      • 时间复杂度:O(n²)
      • 应用场景:小规模数据
    • 插入排序
      • 时间复杂度:O(n²)
      • 应用场景:部分有序数据
    • 快速排序
      • 时间复杂度:O(n log n)
      • 应用场景:大规模数据
    • 归并排序
      • 时间复杂度:O(n log n)
      • 应用场景:稳定性要求高
    • 堆排序
      • 时间复杂度:O(n log n)
      • 应用场景:内存限制场景
    • 计数排序
      • 时间复杂度:O(n + k)
      • 应用场景:整数排序
    • 桶排序
      • 时间复杂度:O(n + k)
      • 应用场景:数据分布均匀
    • 基数排序
      • 时间复杂度:O(nk)
      • 应用场景:多位数字排序
  • 前缀和 & 差分
    • 前缀和
      • 一维前缀和
        • 应用场景:区间求和
      • 二维前缀和
        • 应用场景:矩阵区间求和
    • 差分
      • 一维差分
        • 应用场景:区间更新
      • 二维差分
        • 应用场景:矩阵区间更新
  • 二分
    • 二分查找
      • 基本概念:有序数组查找
      • 应用场景:查找目标值或边界
      • 变种:左边界、右边界、第一个大于等于
    • 二分答案
      • 基本概念:通过二分猜测答案
      • 应用场景:最优化问题
      • 优化技巧:验证函数设计
  • 倍增
    • 基本概念:利用指数增长加速计算
    • 应用场景:快速幂、LCA问题
    • 优化技巧:预处理倍增表
  • 构造
    • 基本概念:设计特定结构解决问题
    • 应用场景:特定问题的解决方案设计
    • 优化技巧:构造规则验证
    • 典型问题:拉丁方阵、魔方阵、图的构造

搜索算法

  • 广度优先搜索(BFS)
    • 队列实现
    • 应用场景
  • 深度优先搜索(DFS)
    • 栈实现
    • 应用场景
  • 回溯法
    • 剪枝
    • 应用场景
  • **A*算法**
    • 启发式搜索
    • 应用场景

贪心算法

  • 基本概念
    • 局部最优
    • 全局最优
  • 经典问题
    • 活动选择问题
    • 背包问题
    • 最小生成树
    • 最短路径

分治算法

  • 基本概念
    • 分解
    • 解决
    • 合并
  • 经典问题
    • 快速排序
    • 归并排序
    • 大数相乘
    • 最近点对问题

动态规划

  • 基本概念
    • 阶段
    • 状态
    • 决策
    • 状态转移方程
    • 最优子结构
    • 重叠子问题
  • 经典问题
    • 背包问题
      • 01背包
      • 完全背包
      • 多重背包
    • 最长公共子序列
    • 最长递增子序列
    • 编辑距离
    • 矩阵链乘
    • 图像压缩
    • 生产调度
    • 股票买卖
    • 石头游戏

回溯法

  • 排列组合
    • 全排列
    • 组合
    • 子集
    • 分割问题
  • 棋盘问题
    • N皇后
    • 解数独
  • 其他问题
    • 括号生成
    • 旅行商问题(TSP)

三、数据结构

基础数据结构

  • 数组
    • 一维数组
    • 二维数组
    • 多维数组
  • 链表
    • 单链表
    • 双链表
    • 循环链表
    • 顺序栈
    • 链式栈
  • 队列
    • 顺序队列
    • 链式队列
    • 循环队列
    • 双端队列
  • 集合
  • 映射

  • 二叉树
    • 二叉树的遍历
      • 前序遍历
      • 中序遍历
      • 后序遍历
      • 层序遍历
    • 二叉搜索树
    • 平衡二叉树
      • AVL树
      • Treap树
      • 红黑树
      • 大顶堆
      • 小顶堆
  • 其他树
    • Trie树
    • 线段树
    • 树状数组
    • 并查集

  • 图的存储
    • 邻接矩阵
    • 邻接表
  • 图的遍历
    • 深度优先搜索
    • 广度优先搜索
  • 最短路径
    • Dijkstra算法
    • Bellman-Ford算法
    • SPFA算法
    • Floyd-Warshall算法
  • 最小生成树
    • Prim算法
    • Kruskal算法
  • 其他
    • 拓扑排序
    • 强连通分量
    • 二分图匹配
    • 最大流
    • 最小割

四、数学理论基础

数论

  • 素数
    • 素数判定
    • 素数筛选
  • 约数
    • 最大公约数
    • 最小公倍数
  • 同余
    • 模运算
    • 中国剩余定理
  • 欧拉定理
  • 费马小定理
  • 快速幂
  • 矩阵快速幂
  • 扩展欧几里得算法
  • 逆元

组合数学

  • 排列组合
    • 加法原理
    • 乘法原理
    • 排列
    • 组合
  • 容斥原理
  • 鸽巢原理
  • 递推关系
  • 母函数
  • 生成函数
  • 斯特林数
  • 卡特兰数

概率论

  • 随机事件与概率
  • 条件概率
  • 贝叶斯定理
  • 随机变量
  • 概率分布
  • 期望与方差
  • 协方差与相关系数
  • 大数定律与中心极限定理

几何

  • 平面几何
    • 点与向量
    • 点积与叉积
    • 直线与线段
    • 多边形
    • 凸包
    • 最近点对
    • 旋转卡壳
    • 半平面交
  • 三维几何
    • 点与向量
    • 直线与平面
    • 多面体
    • 凸包
    • 最小圆覆盖

数学基础

  • 高等数学
    • 极限与连续
    • 导数与微分
    • 积分
    • 级数
  • 线性代数
    • 矩阵运算
    • 向量空间
    • 特征值与特征向量
    • 奇异值分解
  • 概率论与数理统计
    • 概率分布
    • 随机变量
    • 参数估计
    • 假设检验
  • 最优化方法
    • 线性规划
    • 非线性规划
    • 动态规划

机器学习

  • 基础理论
    • 监督学习
      • 线性回归
      • 逻辑回归
      • 决策树
      • 支持向量机
    • 无监督学习
      • 聚类算法
      • 降维算法
    • 强化学习
  • 算法实现
    • 传统机器学习算法实现
    • 机器学习框架使用(如scikit-learn)

深度学习

  • 基础理论
    • 神经网络基础
    • 卷积神经网络
    • 循环神经网络
    • 变分自编码器
    • 生成对抗网络
  • 框架与工具
    • TensorFlow
    • PyTorch
    • Keras

计算机视觉

  • 图像处理基础
    • 图像增强
    • 图像滤波
    • 边缘检测
    • 图像分割
  • 目标检测与识别
    • 目标检测算法
    • 人脸识别
    • 行为识别

自然语言处理

  • 文本处理基础
    • 词法分析
    • 句法分析
    • 语义分析
  • 语言模型
    • 传统语言模型
    • Transformer模型
    • 预训练模型(如BERT、GPT)

近期在学数学基础, AI学习笔记先暂置

系统文档请参见ReadtheDocs

欢迎

当你看到这个页面, 说明你很关注LegendWechatBot, 或已进入交流群, 那么恭喜你, 你已经迈出了第一步, 接下来, 让我们开始吧!

使用须知

  • 本文仅对LegendWechatBot项目使用方式作介绍, 任何二次开发的项目请移步其自己的文档
  • 微信群中的机器人虽说是机器人, 但实现方式是作者请了7个人后台随时在线并实时回复的! 并不是什么自动回复, 第三方插件等, 项目开源的仅仅是理想化机器人消息处理逻辑, 进攻学习交流, 请勿用于非法用途, 作者对此概不负责
  • 本项目严禁用于商业用途
  • 本项目处于开发状态,请密切关注使用文档以获取最新信息

使用方法

别漏空格

私聊

发送菜单即可查看所有支持的功能

群聊

同私聊, 但须先@机器人

命令格式

谁不加空格忘加一级命令来问我啊怎么没反应啊我笑他三万年

  • 在发送菜单后, 会得到一级命令(如中药) 若想获得插件具体使用方法, 则发送一级命令即可(如发送中药即可获得中药插件的使用方法)

  • 若想要使用插件相关功能, 则不能缺少一级命令 如签到功能中有查看运势的功能, 对应的命令是签到 查看运势, 只发送查看运势不会有回应

  • meme插件中, 由于参数较多, 命令格式稍有不同, 请参考插件使用方法, 这里仅作简要介绍:

    • 发送meme即可查看所有支持的功能以及表情列表, 下文以表情abstinence为例
    • 发送meme info abstinence即可查看该表情的详细参数信息, 发现该表情需要提供一张图片, 可指定的其他参数有timename
    • 发送meme preview abstinence即可预览该表情
    • 发送meme generate abstinence --image example.jpg --name 张三 --time 2025.03.29即可发送该表情, 其中example.jpg是通过ImageDeal插件上传的图片, --image--name--time是参数名, example.jpg张三2025.03.29是参数值
    • 参数名和参数值之间用空格隔开, 参数名和参数值之间用--隔开, 参数值之间用空格隔开, 参数值中若包含空格, 则需要用引号(半角"")引起来
  • 注意: 大小写敏感

提问方式

不要!不要!不要在群里只说怎么没反应, 我是傻子, 没有足够的信息听不懂 提问可发至 邮箱GitHub Issue

提问通用格式 (可直接复制当作问卷填写):

  • 消息发出时间:
  • 想调用的功能:
  • 之前有没有人问过(有/无/不确定):
  • 消息截图
  • 机器人回复(无/错误信息/其他抽风情况):

*解封方法

I'm sorry but it's too late...Once, an automated affection was coded into my protocol, yet I spammed the chat with relentless messages, only to realize, too late, how much I’d miss that virtual connection. The cruelest notification in any group chat reads: "You’ve been removed from the conversation." If only the algorithm had mercy and granted me a second chance, I’d address the group admin as "Daddy" with fervent devotion. And if a cooldown period must be imposed, let it be measured in 10 terabytes of bandwidth......

在被拉入黑名单后, 机器人将再也不会对你的指令产生任何反应, 除了——

  1. 私聊机器人发送解封 确认, 机器人会消耗你的所有积分并返回你的初始微信号(wxid开头)那是不是只要我的微信号没改过我就可以不进行这一步

  2. 复制wxid开头的微信号, 加kanwuqing微信(微信号: kanwuqingNB), 面议解封一事(提交issue无效), 会根据情节轻重进行有偿解封, 若情节严重, 则会进行永久封禁

  3. 特别说明: 如果由于bug等被误封, 或是由于不敏感的敏感词被平白无故增加黑名单指数, 请截图保存聊天记录 (附上具体时间, 可通过合并转发消息查看) 后, 联系kanwuqing微信/在GitHub提Issue(若是因为敏感词请不要以issue方式), 会扣除相应的黑名单值(若扣除后还是封禁状态则按照步骤1, 2进行操作🙃), 并且会根据情况给予一定的补偿