<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>dashuai009</title><description>Render · Code · Notes</description><link>https://dashuai009.github.io/</link><language>zh_CN</language><item><title>katex-wasm</title><link>https://dashuai009.github.io/blog/056/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/056/</guid><description>katex-wasm 项目的背景、用法与测试实践</description><pubDate>Thu, 05 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;从 JavaScript 到 Rust：katex-wasm 项目的背景、用法与测试实践&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://dashuai009.github.io/katex-wasm/&quot;&gt;katex-wasm&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;在前端数学公式渲染领域，KaTeX 一直是一个非常实用的方案。它以速度快、渲染效果稳定著称，被广泛用于文档系统、在线编
辑器、教育平台和技术博客中。katex-wasm 的目标，则是在这个成熟能力之上，尝试走出另一条路线: 用 Rust 重写核心渲染
逻辑，并通过 WebAssembly 在浏览器中运行。&lt;/p&gt;
&lt;p&gt;这个项目本质上是一个 KaTeX 的 Rust WASM 移植版本。它希望把原本由 JavaScript 完成的 LaTeX 数学公式解析与渲染流
程，迁移到 Rust 侧实现，再以 WebAssembly 的形式提供给 Web 页面使用。这样做的意义不只是“换一种语言重写”，更重要的
是验证 Rust 在解析器、渲染引擎、跨端复用和工程化交付上的能力。对于需要更强类型约束、可维护性，或者希望将渲染逻辑
复用于本地工具链的场景，这种尝试很有价值。&lt;/p&gt;
&lt;h2&gt;项目背景与定位&lt;/h2&gt;
&lt;p&gt;katex-wasm 可以理解为一个“Rust + WebAssembly 版 KaTeX”。它保留了 KaTeX 的核心目标: 将 LaTeX 数学表达式转换为浏览
器可展示的 HTML（以及相关结构），但实现路径转向了 Rust。&lt;/p&gt;
&lt;p&gt;相比单纯封装原版 KaTeX，这个项目的特点在于它同时覆盖了几种使用形态:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;浏览器端直接渲染到 DOM 节点&lt;/li&gt;
&lt;li&gt;输出 HTML 字符串，便于嵌入前端框架或服务端拼接&lt;/li&gt;
&lt;li&gt;提供本地 CLI，支持批量渲染公式文件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这意味着它不只是一个“能跑在浏览器里的 wasm 包”，也是一个带有本地调试、批处理和对比验证能力的工程化项目。&lt;/p&gt;
&lt;p&gt;从架构上看，项目大致延续了 KaTeX 的核心处理链路:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;parse/ 负责 LaTeX 解析&lt;/li&gt;
&lt;li&gt;build/ 负责从解析树构建 DOM/标记结构&lt;/li&gt;
&lt;li&gt;dom_tree/ 提供虚拟 DOM 能力&lt;/li&gt;
&lt;li&gt;mathML_tree/ 负责 MathML 树生成&lt;/li&gt;
&lt;li&gt;settings/ 管理渲染配置&lt;/li&gt;
&lt;li&gt;symbols/、metrics/ 等模块提供符号和字体度量支持&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这类分层也决定了项目不只是“暴露几个 API”，而是已经具备相对完整的公式渲染引擎雏形。&lt;/p&gt;
&lt;h2&gt;对外能力与核心 API&lt;/h2&gt;
&lt;p&gt;当前项目对外暴露的能力比较清晰，主要集中在几个入口函数上。&lt;/p&gt;
&lt;p&gt;浏览器/WASM 侧最核心的两个接口是:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;render(expression, base_node, options)：直接把公式渲染到指定 DOM 节点&lt;/li&gt;
&lt;li&gt;renderToString(expression, settings)：返回渲染后的 HTML 字符串&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rust 侧则保留了纯 Rust 可调用的能力，例如:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;render_to_string(expression, settings)：返回 HTML 字符串&lt;/li&gt;
&lt;li&gt;render_to_dom_tree(...)：适合更高级的自定义输出场景&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这套 API 设计很务实。对于前端集成方，最常见的需求是“直接渲染”或“返回字符串”；对于 Rust 侧开发者，则可以直接复用
底层渲染逻辑，而不必强依赖 JS 环境。&lt;/p&gt;
&lt;p&gt;另外，项目在错误处理上也做了比较实用的设计。遇到非法公式时，可以根据配置选择:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;直接抛错&lt;/li&gt;
&lt;li&gt;降级输出带错误样式的节点或 HTML 片段&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这使得它更适合接入真实业务，而不是只停留在实验性质的 demo。&lt;/p&gt;
&lt;h2&gt;如何构建与运行&lt;/h2&gt;
&lt;h3&gt;1. 构建 WASM 包&lt;/h3&gt;
&lt;p&gt;项目使用 wasm-pack 作为标准构建入口。最基本的构建命令是:&lt;/p&gt;
&lt;p&gt;wasm-pack build&lt;/p&gt;
&lt;p&gt;如果要生成更适合发布的优化版本，可以使用:&lt;/p&gt;
&lt;p&gt;wasm-pack build --release&lt;/p&gt;
&lt;p&gt;从工程配置来看，项目已经针对发布场景做了体积优化，例如 cdylib、opt-level = &quot;s&quot;、lto = true，这些都符合 WASM 包体
积敏感的常见需求。&lt;/p&gt;
&lt;h3&gt;2. 本地运行 Demo&lt;/h3&gt;
&lt;p&gt;仓库内提供了一个 demo 目录，用于在浏览器中验证渲染效果。典型流程如下:&lt;/p&gt;
&lt;p&gt;wasm-pack build
cd demo
npm install
npm start&lt;/p&gt;
&lt;p&gt;这里有一个实际开发中很重要的点: demo 通过 file:../pkg 直接依赖根目录构建出来的 wasm 包，因此每次修改 Rust 代码
后，都需要重新执行一次 wasm-pack build，否则前端不会拿到最新构建产物。&lt;/p&gt;
&lt;h3&gt;3. 使用 CLI 进行批量渲染&lt;/h3&gt;
&lt;p&gt;除了浏览器侧使用方式，项目还提供了一个非常实用的 CLI 工具 katex-rs-cli。它支持读取一个文本文件，并按行渲染其中的
公式，适合做本地调试、批量验证、覆盖率采样等工作。&lt;/p&gt;
&lt;p&gt;常见用法如下:&lt;/p&gt;
&lt;p&gt;cargo run --bin katex-rs-cli -- tests/fixtures/formulas.txt&lt;/p&gt;
&lt;p&gt;如果只想渲染某一段范围:&lt;/p&gt;
&lt;p&gt;cargo run --bin katex-rs-cli -- tests/fixtures/formulas.txt 1 5&lt;/p&gt;
&lt;p&gt;如果只关心最终汇总，不看逐条输出:&lt;/p&gt;
&lt;p&gt;cargo run --bin katex-rs-cli -- tests/fixtures/formulas.txt 1 5 --summary-only&lt;/p&gt;
&lt;p&gt;如果想关闭并行渲染:&lt;/p&gt;
&lt;p&gt;cargo run --bin katex-rs-cli -- tests/fixtures/formulas.txt 1 5 --multi-threaded false&lt;/p&gt;
&lt;p&gt;这个 CLI 的价值不只是“命令行版渲染器”，更重要的是它把项目从单纯的前端 wasm 包扩展成了一个可调试、可批处理、可做
分析的开发工具。&lt;/p&gt;
&lt;h2&gt;测试与正确性验证&lt;/h2&gt;
&lt;p&gt;对于这种“复刻已有成熟库”的项目来说，最大的挑战不是“能跑”，而是“行为是否尽量对齐原版”。katex-wasm 在这方面采用了
比较务实的测试思路。&lt;/p&gt;
&lt;h3&gt;1. 常规测试入口&lt;/h3&gt;
&lt;p&gt;项目文档中给出了两个基础测试入口:&lt;/p&gt;
&lt;p&gt;cargo test&lt;/p&gt;
&lt;p&gt;以及:&lt;/p&gt;
&lt;p&gt;wasm-pack test --headless --firefox&lt;/p&gt;
&lt;p&gt;前者面向 Rust 侧测试，后者是 WASM 场景下的浏览器无头测试入口。当前仓库里可以看到基础测试代码和 wasm-bindgen-test
依赖，说明项目已经为浏览器测试预留了标准路径。&lt;/p&gt;
&lt;p&gt;不过从当前状态看，这部分还不算特别完整，更适合描述为“已有测试入口和基础用例”，而不是“已经形成一整套完善的自动化
测试矩阵”。&lt;/p&gt;
&lt;h3&gt;2. JS 与 Rust 的差异对比: diff_harness&lt;/h3&gt;
&lt;p&gt;这个项目目前最有代表性的验证工具，其实是 tests/diff_harness.mjs。&lt;/p&gt;
&lt;p&gt;它的思路很直接:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;读取一组 LaTeX 公式&lt;/li&gt;
&lt;li&gt;分别调用原版 JS KaTeX 和 Rust WASM 版本进行渲染&lt;/li&gt;
&lt;li&gt;对比两边的 HTML 输出&lt;/li&gt;
&lt;li&gt;输出逐条结果和最终汇总&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用方式如下:&lt;/p&gt;
&lt;p&gt;wasm-pack build &amp;amp;&amp;amp; node --experimental-wasm-modules tests/diff_harness.mjs tests/fixtures/formulas.txt&lt;/p&gt;
&lt;p&gt;也可以指定范围和日志级别:&lt;/p&gt;
&lt;p&gt;wasm-pack build &amp;amp;&amp;amp; node --experimental-wasm-modules tests/diff_harness.mjs tests/fixtures/formulas.txt 1 5
--log-level summary&lt;/p&gt;
&lt;p&gt;这类 diff harness 对移植项目非常重要。因为在“复刻”类工作里，单元测试只能覆盖局部逻辑，真正能说明问题的是: 面对同
一输入，Rust 版本的行为与 JS 版本是否一致。&lt;/p&gt;
&lt;p&gt;值得一提的是，这个工具并不是做“完全字符串零容忍比较”。它对部分 em 单位数值误差做了容忍处理，这也符合跨实现中浮点
和格式细节可能略有差异的现实情况。换句话说，它关注的是“表达含义和渲染结果是否等价”，而不是机械追求字符级完全一
致。&lt;/p&gt;
&lt;h2&gt;覆盖率测试: 有方案，但仍偏手工&lt;/h2&gt;
&lt;p&gt;“覆盖率测试”是很多项目介绍里容易被写得过满的部分。就 katex-wasm 当前状态而言，更准确的说法是:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;仓库已经提供了覆盖率采集说明&lt;/li&gt;
&lt;li&gt;可以在本地生成 Rust 代码覆盖率报告&lt;/li&gt;
&lt;li&gt;但这套流程目前还是手工执行为主，尚未形成 CI 自动化闭环&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;项目中已经有一份专门的文档，说明如何通过 katex-rs-cli 驱动覆盖率统计。核心思路是:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 RUSTFLAGS=&quot;-Cinstrument-coverage&quot; 编译插桩版本&lt;/li&gt;
&lt;li&gt;通过 katex-rs-cli 跑一批公式样本&lt;/li&gt;
&lt;li&gt;生成 .profraw 数据&lt;/li&gt;
&lt;li&gt;用 llvm-profdata 合并&lt;/li&gt;
&lt;li&gt;用 llvm-cov 输出终端摘要或 HTML 报告&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;典型流程类似这样:&lt;/p&gt;
&lt;p&gt;RUSTFLAGS=&quot;-Cinstrument-coverage&quot; &lt;br /&gt;
CARGO_INCREMENTAL=0 &lt;br /&gt;
LLVM_PROFILE_FILE=&quot;coverage/katex-rs-cli-%p-%m.profraw&quot; &lt;br /&gt;
CARGO_TARGET_DIR=&quot;target/coverage&quot; &lt;br /&gt;
cargo run --bin katex-rs-cli -- tests/fixtures/formulas.txt 1 200&lt;/p&gt;
&lt;p&gt;然后再用 LLVM 工具生成报告。&lt;/p&gt;
&lt;p&gt;这种方案的优点是非常贴近真实代码路径。因为它不是只靠零散单元测试覆盖逻辑，而是通过批量渲染真实公式，让解析器、构
建器、错误处理和部分渲染路径一起被执行到。&lt;/p&gt;
&lt;p&gt;但也要如实说明它当前的边界:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;还没有看到固定的自动化脚本被纳入仓库主流程&lt;/li&gt;
&lt;li&gt;还没有稳定的、持续更新的“官方覆盖率基线”&lt;/li&gt;
&lt;li&gt;CI 工作流目前主要用于 demo 构建与部署，不包含覆盖率采集步骤&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以更准确的表述应该是: 项目已经具备本地覆盖率统计能力，并且有清晰文档；但覆盖率体系还处于“可用、可执行、待进一
步自动化”的阶段。&lt;/p&gt;
&lt;h2&gt;工程化方面的亮点&lt;/h2&gt;
&lt;p&gt;如果把这个项目当作一个工程实践来看，除了“Rust 重写 KaTeX”本身，还有几个值得单独写进博客的亮点。&lt;/p&gt;
&lt;p&gt;第一是多入口交付。它既能作为浏览器中的 wasm 包使用，也能通过 CLI 做本地渲染和验证，这比单一库形态更实用。&lt;/p&gt;
&lt;p&gt;第二是部署链路比较完整。仓库已经配置了 GitHub Pages 的 demo 部署流程，支持在推送到 main 后自动构建并发布。对于展
示型项目来说，这能明显降低体验门槛，也有助于外部验证。&lt;/p&gt;
&lt;p&gt;第三是对发布场景做了明确优化。Rust 的 profile 配置已经针对 wasm 体积和发布形态做了处理，这说明项目作者不仅关
注“功能能不能跑”，也在意“产物是否适合真正交付”。&lt;/p&gt;
&lt;p&gt;第四是与原版 KaTeX 的对照意识很强。仓库中保留了 KaTeX/ 目录和差异对比工具，这种做法很符合“复刻项目”的正确姿势:
修复 Rust 逻辑前，先确认 JS 对应行为，而不是凭直觉改。&lt;/p&gt;
&lt;h2&gt;目前阶段的价值与下一步方向&lt;/h2&gt;
&lt;p&gt;就当前状态而言，katex-wasm 已经具备了一个原型级到工程验证级项目的主要特征:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;有明确的目标和边界&lt;/li&gt;
&lt;li&gt;有核心渲染链路&lt;/li&gt;
&lt;li&gt;有浏览器和 CLI 两种使用路径&lt;/li&gt;
&lt;li&gt;有基础测试和跨实现对比工具&lt;/li&gt;
&lt;li&gt;有本地覆盖率采集方案&lt;/li&gt;
&lt;li&gt;有 demo 和自动部署能力&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它最适合的定位，不是宣传成“已经完整替代原版 KaTeX 的生产级实现”，而是一个正在持续推进的 Rust/WASM 移植工程。它的
价值在于:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;验证 Rust 在复杂前端基础库移植中的可行性&lt;/li&gt;
&lt;li&gt;为后续性能分析、功能补齐和行为对齐提供工程基础&lt;/li&gt;
&lt;li&gt;为类似“用 Rust 复刻成熟 JS 库”的实践提供参考样本&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果后续继续演进，我认为几个方向会很关键:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;扩充系统化测试用例，尤其是 wasm 场景下的自动化测试。&lt;/li&gt;
&lt;li&gt;把 diff_harness 纳入更稳定的回归流程，而不只是开发期手工执行。&lt;/li&gt;
&lt;li&gt;将覆盖率采集脚本化，并接入 CI，形成长期可追踪的质量指标。&lt;/li&gt;
&lt;li&gt;持续补齐与原版 KaTeX 的行为差异，优先保证语义一致，再考虑输出细节完全对齐。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;katex-wasm 是一个很典型、也很有代表性的 Rust WebAssembly 项目: 它不是为了“把某段 JS 机械翻译成 Rust”，而是借一个
成熟场景，验证 Rust 在解析、渲染、跨端调用和工程化能力上的整体表现。&lt;/p&gt;
&lt;p&gt;从当前仓库状态来看，这个项目已经不仅仅是一个技术尝试。它已经有了可运行的构建链路、可验证的对比工具、可使用的
CLI、可生成的覆盖率报告，以及可展示的 demo。对于关注 Rust、WebAssembly、前端基础设施，或者对“如何复刻一个成熟 JS
库”感兴趣的开发者来说，这都是一个值得持续跟进的项目。&lt;/p&gt;
</content:encoded></item><item><title>https:// to ssh</title><link>https://dashuai009.github.io/blog/055/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/055/</guid><description>git config</description><pubDate>Tue, 20 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2026,
month: 1,
day: 20,
)
#metadata((
title: &quot;https:// to ssh&quot;,
subtitle: [git],
author: &quot;dashuai009&quot;,
description: &quot;git config&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;== backup&lt;/p&gt;
&lt;p&gt;Replace &lt;code&gt;git://&lt;/code&gt; with &lt;code&gt;https://&lt;/code&gt;
Rewrite any &lt;code&gt;git://&lt;/code&gt; urls to be &lt;code&gt;https://&lt;/code&gt; but, it won&apos;t touch sshurls (&lt;code&gt;git@github.com:&lt;/code&gt;)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git config --global url.&quot;https://github&quot;.insteadOf git://github
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or replace with ssh&lt;/p&gt;
&lt;p&gt;Use ssh instead of &lt;code&gt;https://&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git config --global url.&quot;git@github.com:&quot;.insteadOf &quot;https://github.com/&quot;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>c++20中显式的lambda模板参数列表</title><link>https://dashuai009.github.io/blog/054/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/054/</guid><description>c++20中可以显示支持lambda的模板参数列表，用以改进lambda的实用性</description><pubDate>Sun, 20 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;问题介绍&lt;/h2&gt;
&lt;p&gt;想临时写个lambda函数，但lambda函数需要是模板化的，而且模板化的参数既不是输入参数也不是输出参数，只是中间过程需要用到的一个类型。这时候需要用到“c++20中的显式模板参数列表”。&lt;/p&gt;
&lt;p&gt;先简单写一个结构体，具有类型擦除的特性。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Value {
    int _type = 1;         // 0 for char, 1 for int
    void *_data = nullptr; // the length of _data array is 10.
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Value&lt;/code&gt;类型内部通过一个&lt;code&gt;_type&lt;/code&gt;来枚举 &lt;code&gt;_data&lt;/code&gt; 存储的数据类型。
这里用0表示char数组，1表示int数组。其他值可以留作他用。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;_data&lt;/code&gt; 表示 &lt;code&gt;_type&lt;/code&gt;类型的数据，为了简化问题，这里是长度为10的数组，也就是要么是&lt;code&gt;char [10]&lt;/code&gt;要么是&lt;code&gt;int [10]&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;后续我们会介绍四个函数， 一个初始化&lt;code&gt;init&lt;/code&gt;函数，初始化Value的_data数组。三个用于数组批量加上一个整数的函数&lt;code&gt;add1&lt;/code&gt;、&lt;code&gt;add2&lt;/code&gt;和&lt;code&gt;add3&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;一个“普通”的lambda：init&lt;/h2&gt;
&lt;p&gt;为了初始化&lt;code&gt;Value&lt;/code&gt;类型的&lt;code&gt;_data&lt;/code&gt;，
我们希望用&lt;code&gt;T* init(T d)&lt;/code&gt;返回一个长度为10的、初始值都为d的指针，
可以写成如下lambda：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;auto init = [](auto d) {
    auto x = new decltype(d)[10];
    for (int i = 0; i &amp;lt; 10; ++i) {
        x[i] = d;
    }
    return x;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ok，没啥问题，这是个用auto模板化的lambda。&lt;/p&gt;
&lt;p&gt;初始化的整个代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ...

int main() {
    auto init = [](auto d) {
        auto x = new decltype(d)[10];
        for (int i = 0; i &amp;lt; 10; ++i) {
            x[i] = d;
        }
        return x;
    };

    Value v{._type = 0, ._data = nullptr};

    if (v._type == 0) {
        v._data = (void *)init(42);
    } else if (v._type == 1) {
        v._data = (void *)init(&apos;a&apos;);
    } else {
        std::abort();
    }
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;一个不普通的的lambda和对应的模板函数&lt;/h2&gt;
&lt;p&gt;现在我们希望通过一个add函数，给Value的每个元素加上一个标量。
那么可以将”加“的逻辑抽象出来，不然每个_type用if判断一下，就要复制一遍代码。&lt;/p&gt;
&lt;p&gt;先来用add1表示都加上1，这是一个普通的模板函数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;typename T&amp;gt; void add1(Value data10) {
    T *x = (T *)data10._data;
    for (int i = 0; i &amp;lt; 10; ++i) {
        x[i] += 1;
        std::cout &amp;lt;&amp;lt; x[i] &amp;lt;&amp;lt; &apos; &apos;;
    }
    std::cout &amp;lt;&amp;lt; &apos;\n&apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;前边的模板参数T只是针对函数内部某处逻辑上用到的。既不是函数的返回参数，也不是输入参数。&lt;/p&gt;
&lt;p&gt;普通模板函数没有太大问题。但是这函数可能只会集中在_type是数组的if大法里，这也是我遇到的问题，需要判断这个类型擦除的数据是不是数组，如果是数组则执行add的逻辑，所以希望这个add函数能够离if大法比较近，一眼就能看完lambda的逻辑，而不是跳来跳去。&lt;/p&gt;
&lt;p&gt;但是写lambda会遇到问题，这时不太能用auto来表示模板参数。T既不是入参也不是返回参数。
通过询问万能的群友，我们可以用一个固定输入为nullptr或者别的什么的参数，通过输入参数表示这个中间用到的模板参数T。
代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;auto add2 = [](const Value &amp;amp;data10, auto *_null) {
    auto *x = (decltype(_null))data10._data;
    for (int i = 0; i &amp;lt; 10; ++i) {
        x[i] += 2;
        std::cout &amp;lt;&amp;lt; x[i] &amp;lt;&amp;lt; &apos; &apos;;
    }
    std::cout &amp;lt;&amp;lt; &apos;\n&apos;;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后一个_null表示一个指针，但是不会在运行时用到他的值，只是在编译期间传递入参的类型信息。
函数体第一行&lt;code&gt;auto *x = (decltype(_null))data10._data;&lt;/code&gt;用到了这个参数。&lt;/p&gt;
&lt;p&gt;比如调用时&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (v._type == 1) {
    add1&amp;lt;char&amp;gt;(v);
    add2(v, (char *)nullptr);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用&lt;code&gt;(char *)nullptr&lt;/code&gt;来指示需要特化的lambda模板。&lt;/p&gt;
&lt;h2&gt;最后一个lambda&lt;/h2&gt;
&lt;p&gt;总感觉这个问题还有naiive的方案。搜了一下auto表示lambda的模板参数竟然是c++14的东西，而不是20的，所以c++20对lambda更新了什么？&lt;/p&gt;
&lt;p&gt;然后找到了&lt;a href=&quot;https://stackoverflow.com/questions/50713214/how-to-provide-template-arguments-to-a-lambda-with-a-call-operator-template&quot;&gt;How to provide template arguments to a lambda with a call operator template&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;就有了如下代码，c++20的lambda的&lt;code&gt;[]&lt;/code&gt;和&lt;code&gt;()&lt;/code&gt;之间可以写&lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt;了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;auto add3 = []&amp;lt;typename T&amp;gt;(const Value &amp;amp;data10) {
    auto *x = (T *)data10._data;
    for (int i = 0; i &amp;lt; 10; ++i) {
        x[i] += 3;
        std::cout &amp;lt;&amp;lt; x[i] &amp;lt;&amp;lt; &apos; &apos;;
    }
    std::cout &amp;lt;&amp;lt; &apos;\n&apos;;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;调用时需要写的比较长，也好理解，lambda其实是匿名函数对象，是个对象，不是狭义上的函数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (v._type == 0) {
    add1&amp;lt;int&amp;gt;(v);
    add3.template operator()&amp;lt;int&amp;gt;(v);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;完整代码&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://godbolt.org/z/f5zYbzj35&quot;&gt;https://godbolt.org/z/f5zYbzj35&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;上边是完整的一个代码，可以修改第27行，&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Value v{._type = 1, ._data = nullptr};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;改为&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Value v{._type = 1, ._data = nullptr};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以得到int数组的结果。&lt;/p&gt;
&lt;h2&gt;参考内容&lt;/h2&gt;
&lt;p&gt;上边这些内容能解决我的问题了，lambda其他的内容也没有深入研究，列一下参考的链接吧&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/50713214/how-to-provide-template-arguments-to-a-lambda-with-a-call-operator-template&quot;&gt;How to provide template arguments to a lambda with a call operator template&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.cppreference.com/w/cpp/language/lambda&quot;&gt;Lambda expressions (since C++11)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xiaoweiChen/CXX20-The-Complete-Guide&quot;&gt;c++20 The Complete guide 第11章lambda表达式&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>error LNK2019: unresolved external symbol 的另一种原因：MSVC的mangling导致</title><link>https://dashuai009.github.io/blog/052/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/052/</guid><description>c++中sturct/class的前置声明如果混用，会导致mangling出问题，引起LNK2019</description><pubDate>Thu, 21 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;问题背景&lt;/h2&gt;
&lt;p&gt;今天又又又遇到了&lt;code&gt;LNK 2019&lt;/code&gt;但是死活没找到原因。&lt;/p&gt;
&lt;p&gt;简单来说，有一个&lt;code&gt;struct A&lt;/code&gt;，在另一个文件中前置声明为了&lt;code&gt;class A&lt;/code&gt;，其他文件如果include顺序再玄学一点，就会出现&lt;code&gt;LNK2019&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;问题出现的条件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MSVC&lt;/code&gt;编译器。&lt;/li&gt;
&lt;li&gt;混用了struct/class的前置声明。&lt;/li&gt;
&lt;li&gt;include顺序比较玄学。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最简单的解决方案是从第二点。由于项目需要和项目比较庞大，第一点和第三点无法简单生效。&lt;/p&gt;
&lt;p&gt;看个例子&lt;/p&gt;
&lt;p&gt;&lt;code&gt;foo.h&lt;/code&gt;的代码如下，Foo是个struct&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#pragma once

struct Foo {
  int width;
  int height;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;bar.h&lt;/code&gt;的代码如下，&lt;code&gt;struct Foo&lt;/code&gt; 被前置声明为 &lt;code&gt;class&lt;/code&gt;，后续有函数用到了&lt;code&gt;Foo&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#pragma once

class Foo;

class Bar {
public:
  Bar(const Foo&amp;amp; fo);

  int width;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;bar.cpp&lt;/code&gt;的代码如下，注意这里的include顺序&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &quot;foo.h&quot;
#include &quot;bar.h&quot;

Bar::Bar(const Foo &amp;amp;f) : width(f.width) {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;main.cpp&lt;/code&gt;的代码如下，注意这里的include顺序&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &quot;bar.h&quot;
#include &quot;foo.h&quot;
#include &amp;lt;iostream&amp;gt;


int main() {
  Foo f;
  f.width = 998244353;
  Bar b(f);
  std::cout &amp;lt;&amp;lt; b.width &amp;lt;&amp;lt; &apos;\n&apos;;
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;cmake_minimum_required(VERSION 3.0)
project(link_error CXX)

add_executable(link_error bar.h foo.h bar.cpp main.cpp)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用CMake编译运行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cmake -B .\build\ --build
cmake --build ./build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就会遇到如下错误&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MSBuild version 17.11.9+a69bbaaf5 for .NET Framework

  bar.cpp
C:\Users\15258\work\mangle_error\bar.h(3,7): warning C4099: &apos;Foo&apos;: type name first seen using &apos;struct&apos; now seen using &apos;class&apos; [C:\Users\15258\work\mangle_error\build\link
_error.vcxproj]
  (compiling source file &apos;../bar.cpp&apos;)
      C:\Users\15258\work\mangle_error\foo.h(3,8):
      see declaration of &apos;Foo&apos;

  link_error.vcxproj -&amp;gt; C:\Users\15258\work\mangle_error\build\Debug\link_error.exe
PS C:\Users\15258\work\mangle_error&amp;gt; cmake --build ./build
MSBuild version 17.11.9+a69bbaaf5 for .NET Framework

  bar.cpp
  main.cpp
C:\Users\15258\work\mangle_error\bar.h(3,7): warning C4099: &apos;Foo&apos;: type name first seen using &apos;struct&apos; now seen using &apos;class&apos; [C:\Users\15258\work\mangle_error\build\link
_error.vcxproj]
  (compiling source file &apos;../bar.cpp&apos;)
      C:\Users\15258\work\mangle_error\foo.h(3,8):
      see declaration of &apos;Foo&apos;

C:\Users\15258\work\mangle_error\foo.h(3,8): warning C4099: &apos;Foo&apos;: type name first seen using &apos;class&apos; now seen using &apos;struct&apos; [C:\Users\15258\work\mangle_error\build\link
_error.vcxproj]
  (compiling source file &apos;../main.cpp&apos;)
      C:\Users\15258\work\mangle_error\foo.h(3,8):
      see declaration of &apos;Foo&apos;

  Generating Code...
main.obj : error LNK2019: unresolved external symbol &quot;public: __cdecl Bar::Bar(class Foo const &amp;amp;)&quot; (??0Bar@@QEAA@AEBVFoo@@@Z) referenced in function main [C:\Users\15258\
work\mangle_error\build\link_error.vcxproj]
C:\Users\15258\work\mangle_error\build\Debug\link_error.exe : fatal error LNK1120: 1 unresolved externals [C:\Users\15258\work\mangle_error\build\link_error.vcxproj]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;msvc确实给出两个编译warning和一个error，&lt;em&gt;在我们的项目中由于海量的warning，完全没有注意到这个事情&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;注意，bar.cpp和main.cpp的两处include顺序也比较玄学，如果颠倒其中一处，error会消失，同时颠倒两处include不会出现这个问题。&lt;/p&gt;
&lt;p&gt;本地测试过wsl的gcc，没有编译错误，听群里大佬说“能混用前置声明的struct/class，msvc的特殊mangling的问题”&lt;/p&gt;
&lt;p&gt;可以dump一下bar.obj看看导出的符号：&lt;code&gt;dumpbin.exe /SYMBOLS .\build\link_error.dir\Debug\bar.obj&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;File Type: COFF OBJECT

COFF SYMBOL TABLE
000 0105854B ABS    notype       Static       | @comp.id
001 80010190 ABS    notype       Static       | @feat.00
002 00000003 ABS    notype       Static       | @vol.md
003 00000000 SECT1  notype       Static       | .drectve
    Section length   30, #relocs    0, #linenums    0, checksum        0
005 00000000 SECT2  notype       Static       | .debug$S
    Section length  2A8, #relocs    4, #linenums    0, checksum        0
007 00000000 SECT3  notype       Static       | .text$mn
    Section length   20, #relocs    0, #linenums    0, checksum 2E748664
009 00000000 SECT3  notype ()    External     | ??0Bar@@QEAA@AEBUFoo@@@Z (public: __cdecl Bar::Bar(struct Foo const &amp;amp;))
00A 00000000 UNDEF  notype ()    External     | _RTC_InitBase
00B 00000000 UNDEF  notype ()    External     | _RTC_Shutdown
00C 00000000 SECT3  notype       Label        | $LN3
00D 00000000 SECT4  notype       Static       | .xdata
    Section length    8, #relocs    0, #linenums    0, checksum 86016890
00F 00000000 SECT4  notype       Static       | $unwind$??0Bar@@QEAA@AEBUFoo@@@Z
010 00000000 SECT5  notype       Static       | .pdata
    Section length    C, #relocs    3, #linenums    0, checksum F9766256
012 00000000 SECT5  notype       Static       | $pdata$??0Bar@@QEAA@AEBUFoo@@@Z
013 00000000 SECT6  notype       Static       | .rtc$IMZ
    Section length    8, #relocs    1, #linenums    0, checksum        0, selection    2 (pick any)
015 00000000 SECT6  notype       Static       | _RTC_InitBase.rtc$IMZ
016 00000000 SECT7  notype       Static       | .rtc$TMZ
    Section length    8, #relocs    1, #linenums    0, checksum        0, selection    2 (pick any)
018 00000000 SECT7  notype       Static       | _RTC_Shutdown.rtc$TMZ
019 00000000 SECT8  notype       Static       | .debug$T
    Section length   64, #relocs    0, #linenums    0, checksum        0
01B 00000000 SECT9  notype       Static       | .chks64
    Section length   48, #relocs    0, #linenums    0, checksum        0

String Table Size = 0xA6 bytes

  Summary

          48 .chks64
         2A8 .debug$S
          64 .debug$T
          30 .drectve
           C .pdata
           8 .rtc$IMZ
           8 .rtc$TMZ
          20 .text$mn
           8 .xdata
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过对比error中的&lt;code&gt;??0Bar@@QEAA@AEBVFoo@@@Z&lt;/code&gt;不同于obj中的 &lt;code&gt;??0Bar@@QEAA@AEBUFoo@@@Z&lt;/code&gt;，只有一个字符的差别。看不懂也没关系，编译器和dump都给出了解释&lt;code&gt;public: __cdecl Bar::Bar(class Foo const &amp;amp;)&lt;/code&gt;和&lt;code&gt;public: __cdecl Bar::Bar(struct Foo const &amp;amp;)&lt;/code&gt;。这时候终于发现是class不同于struct。而Foo实际应该是struct。&lt;/p&gt;
&lt;p&gt;我比较喜欢全局搜一些东西，搜Foo的时候，有点印象Foo应该是struct。这才发现有地方写成了&lt;code&gt;class Foo&lt;/code&gt;。唉，不管warning的要命。&lt;/p&gt;
&lt;h2&gt;error LNK2019: unresolved external symbol&lt;/h2&gt;
&lt;p&gt;这里列一下error lnk2019的其他可能，复制自gpt。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;LNK2019: unresolved external symbol&lt;/code&gt;&lt;/strong&gt; 是一个常见的链接错误，表示链接器找不到某个符号（函数、变量等）的定义。通常发生在编译阶段通过了，但链接阶段失败。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;错误的本质&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;编译器&lt;/strong&gt;：将代码翻译为中间代码（如 &lt;code&gt;.obj&lt;/code&gt; 文件）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;链接器&lt;/strong&gt;：将所有 &lt;code&gt;.obj&lt;/code&gt; 文件及库文件链接成可执行文件或动态链接库。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;LNK2019&lt;/code&gt; 表示：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;声明&lt;/strong&gt;了一个符号（函数、变量等），但链接器找不到它的实现（定义）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;常见原因及解决方法&lt;/strong&gt;&lt;/h3&gt;
&lt;h4&gt;&lt;strong&gt;1. 函数或变量未定义&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;函数或变量的声明（如 &lt;code&gt;extern&lt;/code&gt; 或头文件中声明）存在，但实现缺失。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;解决方法&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;确保提供了函数或变量的实现。&lt;pre&gt;&lt;code&gt;// 声明 (通常在头文件)
void myFunction();

// 定义 (通常在 .cpp 文件)
void myFunction() {
    // Implementation
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h4&gt;&lt;strong&gt;2. 忘记链接目标文件或库&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;编译的 &lt;code&gt;.cpp&lt;/code&gt; 文件未包含在项目中，或未链接所需的库。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;解决方法&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;确保 &lt;code&gt;.cpp&lt;/code&gt; 文件包含在项目中：
&lt;ul&gt;
&lt;li&gt;检查 &lt;strong&gt;项目设置&lt;/strong&gt;，确认所有相关源文件已加入项目。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果使用第三方库：
&lt;ul&gt;
&lt;li&gt;确保在项目设置中正确添加库路径和库文件：
&lt;ul&gt;
&lt;li&gt;在 &lt;strong&gt;Visual Studio&lt;/strong&gt; 中：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;配置属性&lt;/strong&gt; → &lt;strong&gt;链接器&lt;/strong&gt; → &lt;strong&gt;输入&lt;/strong&gt; → &lt;strong&gt;附加依赖项&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;添加所需的 &lt;code&gt;.lib&lt;/code&gt; 文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h4&gt;&lt;strong&gt;3. 函数名或符号修饰不匹配&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;C++ 的 &lt;strong&gt;符号修饰（Name Mangling）&lt;/strong&gt; 不一致。可能发生于以下情况：
&lt;ul&gt;
&lt;li&gt;声明和定义的参数列表不匹配。&lt;/li&gt;
&lt;li&gt;函数声明为 &lt;code&gt;extern &quot;C&quot;&lt;/code&gt;，但定义未加修饰。&lt;/li&gt;
&lt;li&gt;链接库与项目的编译器调用约定不同。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;解决方法&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;确保声明与定义的函数签名完全一致。&lt;pre&gt;&lt;code&gt;// 声明
extern &quot;C&quot; void myFunction();

// 定义
extern &quot;C&quot; void myFunction() {
    // Implementation
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h4&gt;&lt;strong&gt;4. 缺少模板定义&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果使用模板类或函数，声明在头文件中，但实现没有在头文件中定义。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;解决方法&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将模板实现移到头文件中。&lt;pre&gt;&lt;code&gt;// 在头文件中：
template&amp;lt;typename T&amp;gt;
void myFunction(T value) {
    // Implementation
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h4&gt;&lt;strong&gt;5. 静态库或动态库缺失&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;链接器找不到提供符号实现的 &lt;code&gt;.lib&lt;/code&gt; 或 &lt;code&gt;.dll&lt;/code&gt; 文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;解决方法&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;确保已添加必要的库文件路径：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;配置属性&lt;/strong&gt; → &lt;strong&gt;VC++ 目录&lt;/strong&gt; → &lt;strong&gt;库目录&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;配置属性&lt;/strong&gt; → &lt;strong&gt;链接器&lt;/strong&gt; → &lt;strong&gt;输入&lt;/strong&gt; → &lt;strong&gt;附加依赖项&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;添加相关的 &lt;code&gt;.lib&lt;/code&gt; 文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h4&gt;&lt;strong&gt;6. 调用约定不匹配&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;函数声明和定义的调用约定（如 &lt;code&gt;__stdcall&lt;/code&gt;, &lt;code&gt;__cdecl&lt;/code&gt;）不同。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;解决方法&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;明确声明和定义使用相同的调用约定：&lt;pre&gt;&lt;code&gt;void __stdcall myFunction(); // 声明
void __stdcall myFunction() { // 定义
    // Implementation
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h4&gt;&lt;strong&gt;7. 编译器设置不一致&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;编译器版本或设置不匹配，例如编译器优化、C++ 标准等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;解决方法&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;确保所有源文件使用相同的编译器和设置。&lt;/li&gt;
&lt;li&gt;在 Visual Studio 中，检查：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;C/C++&lt;/strong&gt; → &lt;strong&gt;语言&lt;/strong&gt; → &lt;strong&gt;C++ 标准&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;确保链接的库与项目的编译器兼容。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;常见示例&lt;/strong&gt;&lt;/h3&gt;
&lt;h4&gt;&lt;strong&gt;示例 1：函数未定义&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;错误代码&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// main.cpp
void myFunction(); // 声明
int main() {
    myFunction(); // 调用
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;解决方法&lt;/strong&gt;：
在某个 &lt;code&gt;.cpp&lt;/code&gt; 文件中定义 &lt;code&gt;myFunction&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void myFunction() {
    // 实现
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h4&gt;&lt;strong&gt;示例 2：忘记链接库&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;错误代码&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;cmath&amp;gt;
int main() {
    double result = sin(3.14); // 使用了数学库
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;解决方法&lt;/strong&gt;：
在链接器中添加 &lt;code&gt;libm.lib&lt;/code&gt;（Windows 下默认 &lt;code&gt;msvcrt.lib&lt;/code&gt; 已包含）。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;排查步骤&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;确认符号是否已定义，定义的源文件是否加入项目。&lt;/li&gt;
&lt;li&gt;检查函数签名、调用约定是否一致。&lt;/li&gt;
&lt;li&gt;确认第三方库是否已正确链接。&lt;/li&gt;
&lt;li&gt;如果涉及模板，确保实现位于头文件中。&lt;/li&gt;
&lt;li&gt;查看错误符号具体内容（如 &lt;code&gt;dumpbin&lt;/code&gt; 工具分析 &lt;code&gt;.obj&lt;/code&gt; 文件中的符号表）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通过这些方法，可以有效解决 &lt;code&gt;LNK2019&lt;/code&gt; 错误。&lt;/p&gt;
</content:encoded></item><item><title>配置电脑</title><link>https://dashuai009.github.io/blog/051/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/051/</guid><description>一些软件的配置记录</description><pubDate>Tue, 22 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2024,
month: 10,
day: 22,
)
#metadata((
title: &quot;配置电脑&quot;,
subtitle: [CEF],
author: &quot;dashuai009&quot;,
description: &quot;一些软件的配置记录&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;== typst&lt;/p&gt;
&lt;p&gt;#link(&quot;https://github.com/typst/typst&quot;)[官方github]
windows 安装和更新&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;winget install --id Typst.Typst
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;github release里下载t&lt;code&gt;ypst-x86_64-unknown-linux-musl.tar.xz&lt;/code&gt;，然后解压缩，&lt;code&gt;chmod +x typst&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;== docker&lt;/p&gt;
&lt;p&gt;#link(&quot;https://docs.docker.com/engine/install/ubuntu/&quot;)[docker ubuntu 安装]&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done


# Add Docker&apos;s official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  &quot;deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release &amp;amp;&amp;amp; echo &quot;$VERSION_CODENAME&quot;) stable&quot; | \
  sudo tee /etc/apt/sources.list.d/docker.list &amp;gt; /dev/null
sudo apt-get update


sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>你的电脑上有多少CEF？</title><link>https://dashuai009.github.io/blog/050/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/050/</guid><description>你的电脑上有多少CEF？</description><pubDate>Sun, 20 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2024,
month: 10,
day: 20,
)
#metadata((
title: &quot;你的电脑上有多少CEF？&quot;,
subtitle: [CEF],
author: &quot;dashuai009&quot;,
description: &quot;你的电脑上有多少CEF？&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;最近电脑C盘爆了，&lt;/p&gt;
&lt;p&gt;#figure(
image(&quot;050/imgs1.png&quot;, width: 50%),
)
想起来有个项目#link(&quot;https://github.com/ShirasawaSama/CefDetectorX&quot;)[【升级版-Electron】Check how many CEFs are on your computer. 检测你电脑上有几个CEF.]能检测电脑上装了多少个#link(&quot;https://bitbucket.org/chromiumembedded/cef/&quot;)[CEF (Chromium Embedded Framework)]。然后。。。。134个。。。。。。。。&lt;/p&gt;
&lt;p&gt;#figure(
image(&quot;050/imgs2.png&quot;),
)&lt;/p&gt;
</content:encoded></item><item><title>黑神话</title><link>https://dashuai009.github.io/blog/048/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/048/</guid><pubDate>Sun, 22 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2024,
month: 9,
day: 22,
)
#metadata((
title: &quot;黑神话&quot;,
subtitle: [qwen,win32com,python-docx,textextract],
author: &quot;dashuai009&quot;,
description: &quot;&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;要打大石敢当，没有精魄&lt;code&gt;浪里个波&lt;/code&gt;，回第一章去打青蛙，打完没钱升级。&lt;/p&gt;
&lt;p&gt;查攻略刷钱，&lt;code&gt;碎玉池&lt;/code&gt;可以。&lt;/p&gt;
&lt;p&gt;发现没有变化&lt;code&gt;灰蛰&lt;/code&gt;，回沙村门口，这才村口把这个门打开。&lt;/p&gt;
&lt;p&gt;发现没有&lt;code&gt;三王子&lt;/code&gt;，回浮屠界舔图。&lt;/p&gt;
&lt;p&gt;恶心地图，找半天&lt;code&gt;魔将·莲眼&lt;/code&gt;，之前就是不想在浮屠界呆着，赶紧跑了。&lt;/p&gt;
&lt;p&gt;再回最下面交给三王子。就有了 huizhe，一次三千二灵蕴。&lt;/p&gt;
&lt;p&gt;升级精魄，还缺个青蛙，查了一下是 浪波波。巴拉巴拉，走到langbobo的门口，进不去，得收集战车。&lt;/p&gt;
&lt;p&gt;好好好，马霸天对话，&lt;/p&gt;
&lt;p&gt;马哥在五个章节都会出现，必须完成上一章节对话才能在下章节遇到，第五章击败守护道士之前可以回补。&lt;/p&gt;
&lt;p&gt;递归失败&lt;/p&gt;
</content:encoded></item><item><title>使用yalantinglibs中的反射库格式化自定义struct/enum</title><link>https://dashuai009.github.io/blog/012/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/012/</guid><pubDate>Sat, 20 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2024,
month: 7,
day: 20,
)
#metadata((
&quot;title&quot;: &quot;使用yalantinglibs中的反射库格式化自定义struct/enum&quot;,
&quot;author&quot;: &quot;dashuai009&quot;,
description: &quot;&quot;,
pubDate: date.display(),
subtitle: [modules,模块],
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;#date.display();&lt;/p&gt;
&lt;p&gt;#outline()&lt;/p&gt;
&lt;p&gt;yalantinglibs是一个c++20的库集合。
std::formatter是c++20的新特性，用来格式化内容。&lt;/p&gt;
&lt;p&gt;这里尝试使用ylt里的反射库实现自定义struct/enum的格式化输出&lt;/p&gt;
&lt;p&gt;== std::format的基本用法
=== 基本用法
头文件&lt;code&gt;#include &amp;lt;format&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;一眼看起来和python rust的格式化语法很类似。实际用起来，受限制很多。比如std::format的控制字符串需要编译期检查，没法运行时生成，也没有python的&lt;code&gt;print(f&quot;a = {a}&quot;)&lt;/code&gt;（这叫啥？），参数只能放到后面&lt;code&gt;std::println(&quot;a = {}&quot;, a)&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;s&gt;总之，比没有好&lt;/s&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;format&amp;gt;
#include &amp;lt;string&amp;gt;


int main(){
    int a = 42;
    std::string b{&quot;Hello World!&quot;};
    float c = 10.24;
    std::cout &amp;lt;&amp;lt; std::format(&quot;a = {}, b = {}, c = {:.4f}\n&quot;, a, b, c);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出内容&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;a = 42, b = Hello World!, c = 10.2400

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;=== 扩展用法&lt;/p&gt;
&lt;p&gt;自定义的struct/enum都没有默认支持，需要自定义&lt;code&gt;std::formatter&amp;lt;T&amp;gt;&lt;/code&gt;来实现。&lt;/p&gt;
&lt;p&gt;具体来说，需要特化&lt;code&gt;std::formatter&amp;lt;T&amp;gt;&lt;/code&gt;，并实现他的两个函数&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;parse()&lt;/code&gt; 实现如何解析类型的格式字符串说明符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;format()&lt;/code&gt; 为自定义类型的对象/值执行实际格式化&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;format&amp;gt;
#include &amp;lt;string&amp;gt;

struct Person {
    std::string name;
    int age;
};

// 定义Person的formatter
template&amp;lt;&amp;gt;
struct std::formatter&amp;lt;Person&amp;gt; {
    static constexpr auto parse(std::format_parse_context&amp;amp; ctx) {
        return ctx.begin();
    }

    auto format(const Person&amp;amp; p, std::format_context&amp;amp; ctx) const {
        return std::format_to(ctx.out(), &quot;Person(name: {}, age: {})&quot;, p.name, p.age);
    }
};

int main() {
    Person p{&quot;Alice&quot;, 30};
    std::string s = std::format(&quot;{}&quot;, p);
    std::cout &amp;lt;&amp;lt; s &amp;lt;&amp;lt; std::endl;

    return 0;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Person(name: Alice, age: 30)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;=== 其他用法&lt;/p&gt;
&lt;p&gt;太多了，不给自己挖坑了。推荐看这个#link(&quot;https://github.com/xiaoweiChen/CXX20-The-Complete-Guide&quot;)[c++20 compelte guide]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;std::vformat() 和 vformat_to()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;格式字符串的语法&lt;/li&gt;
&lt;li&gt;标准格式说明符：&lt;code&gt;fill align sign # 0 width .prec L type&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;全局化，语言环境&lt;/li&gt;
&lt;li&gt;错误处理&lt;/li&gt;
&lt;li&gt;自定义格式&lt;/li&gt;
&lt;li&gt;自定义格式的解析格式字符串&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;== yalantinglibs的reflection&lt;/p&gt;
&lt;p&gt;官方示例#link(&quot;https://github.com/alibaba/yalantinglibs/blob/main/src/reflection/tests/test_reflection.cpp&quot;)[test_reflection]&lt;/p&gt;
&lt;p&gt;文章#link(&quot;http://www.purecpp.cn/detail?id=2435&quot;)[C++20 非常好用的编译期反射库，划重点【没有宏，没有侵入式】]&lt;/p&gt;
&lt;p&gt;=== 几个api&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;constexpr auto sz = ylt::reflection::members_count&amp;lt;S&amp;gt;();&lt;/code&gt;获取S的成员个数。编译期计算。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;template &amp;lt;typename T, typename Visit&amp;gt; inline constexpr void for_each(Visit func)&lt;/code&gt;遍历&lt;code&gt;struct T&lt;/code&gt;的每一个成员。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Visit func可以是&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[](auto&amp;amp; field) {}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[](auto &amp;amp;field, auto name, auto index) {}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[](auto&amp;amp; field, auto name) {}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;constexpr auto type_name = ylt::reflection::type_string&amp;lt;S&amp;gt;();&lt;/code&gt;获取S的类型字符串&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;有这几个就可以写出struct的formatter了。&lt;/p&gt;
&lt;p&gt;=== int[]的formatter&lt;/p&gt;
&lt;p&gt;直接放代码吧&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我遇到的struct都是C的API，所以没有太复杂的类型。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt;int T&amp;gt;
struct std::formatter&amp;lt;int[T]&amp;gt; {
    constexpr auto parse(std::format_parse_context &amp;amp;ctx) {
        return ctx.begin();
    }

    template&amp;lt;typename FormatContext&amp;gt;
    auto format(const int p[T], FormatContext &amp;amp;ctx) const {
        ctx.out() = std::format_to(ctx.out(), &quot;int[{}]{{&quot;, T);
        for (int i = 0; i &amp;lt; T; ++i) {
            if (i == T - 1) {
                ctx.out() = std::format_to(ctx.out(), &quot;{}&quot;, p[i]);
            } else {
                ctx.out() = std::format_to(ctx.out(), &quot;{}, &quot;, p[i]);
            }
        }
        ctx.out() = std::format_to(ctx.out(), &quot;}}&quot;);
        return ctx.out();
    }

};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以将int[]数组格式化掉。&lt;/p&gt;
&lt;p&gt;=== struct的format函数&lt;/p&gt;
&lt;p&gt;这里把formatter的parse函数单独实现为一个模板函数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt;typename S, typename FormatContext&amp;gt;
auto my_struct_format(const S &amp;amp;p, FormatContext &amp;amp;ctx) {
    constexpr auto struct_name = ylt::reflection::type_string&amp;lt;S&amp;gt;();
    ctx.out() = std::format_to(ctx.out(), &quot;{}{{ &quot;, struct_name);
    constexpr auto sz = ylt::reflection::members_count&amp;lt;S&amp;gt;();
    ylt::reflection::for_each(p, [&amp;amp;ctx](auto &amp;amp;field, auto name, auto index) {
        if (index &amp;lt; sz - 1) {
            ctx.out() = std::format_to(ctx.out(), &quot;{}: {}, &quot;, name, field);
        } else {
            ctx.out() = std::format_to(ctx.out(), &quot;{}: {} }}&quot;, name, field);
        }
    });
    return ctx.out();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首先获取struct的名字，获取结构体成员个数。&lt;/p&gt;
&lt;p&gt;再利用&lt;code&gt;ylt::reflection::for_each&lt;/code&gt;遍历每个成员和每个值。特判是否是最后一个成员，处理&lt;code&gt;,&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;=== enum的format函数&lt;/p&gt;
&lt;p&gt;enum没有出现在官方例子里，&lt;s&gt;我这瞎搞了半天，看样子是对的&lt;/s&gt;。&lt;/p&gt;
&lt;p&gt;直接贴一下关键API&lt;code&gt;get_enum_arr&lt;/code&gt;的实现吧&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Enumerate the numbers in a integer sequence to see if they are legal enum
// value
template &amp;lt;typename E, std::int64_t... Is&amp;gt;
constexpr inline auto get_enum_arr(
    const std::integer_sequence&amp;lt;std::int64_t, Is...&amp;gt; &amp;amp;) {
  constexpr std::size_t N = sizeof...(Is);
  std::array&amp;lt;std::string_view, N&amp;gt; enum_names = {};
  std::array&amp;lt;E, N&amp;gt; enum_values = {};
  std::size_t num = 0;
  (([&amp;amp;]() {
     constexpr auto res = try_get_enum_name&amp;lt;E, static_cast&amp;lt;E&amp;gt;(Is)&amp;gt;();
     if constexpr (res.first) {
       //  the Is is a valid enum value
       enum_names[num] = res.second;
       enum_values[num] = static_cast&amp;lt;E&amp;gt;(Is);
       ++num;
     }
   })(),
   ...);
  return std::make_tuple(num, enum_values, enum_names);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;三个返回值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;num: enum的成员个数&lt;/li&gt;
&lt;li&gt;enum_values：每个成员的值，是一个数组，大小是可变模板参数Is的多少。&lt;/li&gt;
&lt;li&gt;enum_names: 每个成员的名称，是一个数组，大小同上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个函数要求传入定长的integer sequence，一般情况enum的枚举个数不一定一样。我这里直接传个20，假设我要格式化的enum的枚举个数都不超过20。看上边这个代码，20这个参数用来申请内存空间的，多了无所谓，预留默认空值。&lt;/p&gt;
&lt;p&gt;这样，enum的格式化函数如下。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt;typename S, typename FormatContext&amp;gt;
auto my_enum_format(const S &amp;amp;p, FormatContext &amp;amp;ctx) {
    constexpr auto index_enum_str = ylt::reflection::get_enum_arr&amp;lt;S&amp;gt;(std::make_integer_sequence&amp;lt;int64_t, 20&amp;gt;{});
    constexpr auto enum_name = ylt::reflection::type_string&amp;lt;S&amp;gt;();
    for (int i = 0; i &amp;lt; 20; ++i) {
        if (std::get&amp;lt;1&amp;gt;(index_enum_str)[i] == p) {
            ctx.out() = std::format_to(
                    ctx.out(),
                    &quot;{}::{}(value = {})&quot;,
                    // 这个p别忘了强制转为p，不然无限递归了
                    enum_name, std::get&amp;lt;2&amp;gt;(index_enum_str)[i], (int) p
            );
            break;
        }
    }
    return ctx.out();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;=== 测试&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct S1 {
    int a;
    float b;
    int c[6];
    float d[3];
};

enum E1 {
    XX = 10,
    YY,
    MAX
};

int main() {
    S1 s{.a = 42, .b = 10.24, .c = {0, 1, 4, 9, 16, 25}, .d = {1.1, 2.2, 3.3}};
    std::cout &amp;lt;&amp;lt; std::format(&quot;s = {}\n&quot;, s);
    E1 y = E1::MAX;
    std::cout &amp;lt;&amp;lt; std::format(&quot;y = {}&quot;, y);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出结果如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;s = S1{ a: 42, b: 10.24, c: int[6]{0, 1, 4, 9, 16, 25}, d: float[3]{1.1, 2.2, 3.3} }
y = E1::MAX(value = 12)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== 所有代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cmake_minimum_required(VERSION 3.15)
project(test_formatter)

set(CMAKE_CXX_STANDARD 20)


include(FetchContent)

FetchContent_Declare(
        yalantinglibs
        GIT_REPOSITORY https://github.com/alibaba/yalantinglibs.git
        GIT_TAG &quot;0.3.5&quot; # optional ( default master / main )
        GIT_SHALLOW 1 # optional ( --depth=1 )
)

FetchContent_MakeAvailable(yalantinglibs)


add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} PRIVATE yalantinglibs::yalantinglibs)
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;#link(&quot;https://pastebin.ubuntu.com/p/kr2T7hDpbs/&quot;)[main.cpp pastebin]&lt;/p&gt;
&lt;p&gt;== 后&lt;/p&gt;
&lt;p&gt;小坑小点还是挺多的。比如std::formatter的特化没法放到某个namespace里。即&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;namespace test_space{
    struct S{};
    templace&amp;lt;&amp;gt;
    struct std::formatter&amp;lt;S&amp;gt;{
        // ...
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;会编译失败。&lt;/p&gt;
&lt;p&gt;虽然达到我想要的结果了，但是有几个点感觉不是清晰。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;int[T]的格式化，能再写成个模板好了，可以迭代的容器的格式化。不知道view行不行。&lt;/li&gt;
&lt;li&gt;enum的格式化，感觉应该有个api获取enum的格式，在编译期计算，这样传给get_enum_arr就不会多余或者不够了。应该是我没找到相关内容。&lt;/li&gt;
&lt;li&gt;每个struct都要写一个宏。&lt;code&gt;MY_STRUCT_FORMAT(S1);&lt;/code&gt; &lt;code&gt;MY_ENUM_FORMAT(E1);&lt;/code&gt;这样。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;唉，不是不能用。&lt;/p&gt;
</content:encoded></item><item><title>clash 配置chatgpt的rule</title><link>https://dashuai009.github.io/blog/009/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/009/</guid><description>这是一种将C++更加现代的代码组织方式。 模块是一组源代码文件，独立于导入它们的翻译单元进行编译。</description><pubDate>Sun, 23 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2024,
month: 6,
day: 23,
)&lt;/p&gt;
&lt;p&gt;#metadata((
&quot;title&quot;: &quot;clash 配置chatgpt的rule&quot;,
&quot;author&quot;: &quot;dashuai009&quot;,
description: &quot;这是一种将C++更加现代的代码组织方式。 模块是一组源代码文件，独立于导入它们的翻译单元进行编译。&quot;,
pubDate: date.display(),
subtitle: [clash rule],
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;#date.display();&lt;/p&gt;
&lt;p&gt;#outline()&lt;/p&gt;
&lt;p&gt;= 问题&lt;/p&gt;
&lt;p&gt;openai.com检查的内容很多，clash的rule模式一般过不去。我这里必须切到golbal模式才可以访问。找了半天，clash可以配置Parser，在机场提供的配置文件外，添加规则配置。自定义规则可以单独设置，我想要gpt相关的访问全部走美国节点。&lt;/p&gt;
&lt;p&gt;= 使用Parser&lt;/p&gt;
&lt;p&gt;Parser是针对机场给的配置文件，新增一些规则，这样机场节点少了也可能还可以复用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;clash &amp;gt; Profiles &amp;gt; 右键配置 &amp;gt; Parsers &amp;gt; Edit parsers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;向打开的文件添加以下内容。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;parsers: # array
  - url: YOUR_URL
    yaml:
      prepend-proxy-groups:
        - name: 🚀 OpenAI
          type: select
          proxies:
            - 美国 A
            - 美国 B
            - 美国 C
            - 美国 D [0.5x]
            - 美国 E [0.5x]
      prepend-rules:
        - DOMAIN,browser-intake-datadoghq.com,🚀 OpenAI
        - DOMAIN,static.cloudflareinsights.com,🚀 OpenAI
        - DOMAIN-SUFFIX,ai.com,🚀 OpenAI
        - DOMAIN-SUFFIX,algolia.net,🚀 OpenAI
        - DOMAIN-SUFFIX,api.statsig.com,🚀 OpenAI
        - DOMAIN-SUFFIX,auth0.com,🚀 OpenAI
        - DOMAIN-SUFFIX,chatgpt.com,🚀 OpenAI
        - DOMAIN-SUFFIX,chatgpt.livekit.cloud,🚀 OpenAI
        - DOMAIN-SUFFIX,client-api.arkoselabs.com,🚀 OpenAI
        - DOMAIN-SUFFIX,events.statsigapi.net,🚀 OpenAI
        - DOMAIN-SUFFIX,featuregates.org,🚀 OpenAI
        - DOMAIN-SUFFIX,host.livekit.cloud,🚀 OpenAI
        - DOMAIN-SUFFIX,identrust.com,🚀 OpenAI
        - DOMAIN-SUFFIX,intercom.io,🚀 OpenAI
        - DOMAIN-SUFFIX,intercomcdn.com,🚀 OpenAI
        - DOMAIN-SUFFIX,launchdarkly.com,🚀 OpenAI
        - DOMAIN-SUFFIX,oaistatic.com,🚀 OpenAI
        - DOMAIN-SUFFIX,oaiusercontent.com,🚀 OpenAI
        - DOMAIN-SUFFIX,observeit.net,🚀 OpenAI
        - DOMAIN-SUFFIX,segment.io,🚀 OpenAI
        - DOMAIN-SUFFIX,sentry.io,🚀 OpenAI
        - DOMAIN-SUFFIX,stripe.com,🚀 OpenAI
        - DOMAIN-SUFFIX,turn.livekit.cloud,🚀 OpenAI
        - DOMAIN-SUFFIX,openai.com,🚀 OpenAI
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;url后的YOUR_URL就是机场提供的配置链接。&lt;/li&gt;
&lt;li&gt;prepend-proxy-groups是将之后定义的组加到已有groxy-groups的前面。新建一个“🚀 OpenAI”的组，之后在Proxies里就能看到新建了这个组。复制想要的proxies的名字列表。&lt;/li&gt;
&lt;li&gt;prepend-rules的配置来自#link(&quot;https://github.com/blackmatrix7/ios_rule_script/blob/master/rule/Clash/OpenAI/OpenAI.yaml&quot;)[这里]&lt;/li&gt;
&lt;li&gt;注意：我的配置文件跟上边不太同，去掉了最后三个个IP的配置和DOMAIN-KEYWORD的配置，报了个错忘了是啥了；加上了&lt;code&gt;- DOMAIN-SUFFIX,openai.com,🚀 OpenAI&lt;/code&gt;这一行，让&lt;code&gt;openai.com&lt;/code&gt;域名也走美国节点。&lt;/li&gt;
&lt;li&gt;可能还需要重启一下clash&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;= Parser配置说明&lt;/p&gt;
&lt;p&gt;#table(
columns: (1fr, auto),
inset: 10pt,
align: horizon,
table.header(&quot;键&quot;, &quot;操作&quot;),
&quot;append-rules&quot;, &quot;数组合并至原配置 rules 数组后&quot;,
&quot;prepend-rules&quot;, &quot; 数组合并至原配置 rules 数组前&quot;,
&quot;append-proxies&quot;, &quot; 数组合并至原配置 proxies 数组后&quot;,
&quot;prepend-proxies&quot;, &quot; 数组合并至原配置 proxies 数组前&quot;,
&quot;append-proxy-groups&quot;, &quot; 数组合并至原配置 proxy-groups 数组后&quot;,
&quot;prepend-proxy-groups&quot;, &quot; 数组合并至原配置 proxy-groups 数组前&quot;,
&quot;mix-proxy-providers &quot;, &quot;对象合并至原配置 proxy-providers 中&quot;,
&quot;mix-rule-providers&quot;, &quot; 对象合并至原配置 rule-providers 中&quot;,
&quot;mix-object&quot;, &quot; 对象合并至原配置最外层中&quot;,
&quot;commands &quot;, &quot;在上面操作完成后执行简单命令操作配置文件&quot;,
)&lt;/p&gt;
</content:encoded></item><item><title>modules 模块</title><link>https://dashuai009.github.io/blog/003/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/003/</guid><description>这是一种将C++更加现代的代码组织方式。 模块是一组源代码文件，独立于导入它们的翻译单元进行编译。</description><pubDate>Mon, 01 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2024,
month: 1,
day: 1,
)
#metadata((
&quot;title&quot;: &quot;modules 模块&quot;,
&quot;author&quot;: &quot;dashuai009&quot;,
description: &quot;这是一种将C++更加现代的代码组织方式。 模块是一组源代码文件，独立于导入它们的翻译单元进行编译。&quot;,
pubDate: date.display(),
subtitle: [modules,模块],
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;这是一种将C++更加现代的代码组织方式。 模块是一组源代码文件，独立于导入它们的翻译单元进行编译。&lt;/p&gt;
&lt;p&gt;= 模块试图解决的问题&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;头文件
&lt;ul&gt;
&lt;li&gt;module几乎解决了头文件编译的难题，比如头文件的重复替换 （复杂项目中，如果一个底层头文件被修改，上层引用的头文件都会被重新编译）。 使用module之后，这种问题会得到极大的改善。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;封装：对外接口的暴露
&lt;ul&gt;
&lt;li&gt;在模块中声明的宏、预处理器指令和未导出的名称在模块外部不可见。 它们对导入模块的翻译单元的编译没有影响。 您可以按任意顺序导入模块，而无需考虑宏重定义。导入翻译单元中的声明不参与导入模块中的重载解析或名称查找。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;编译速度
&lt;ul&gt;
&lt;li&gt;模块编译一次后，结果将存储在描述所有导出的类型、函数和模板的二进制文件中。 编译器处理该文件的速度比头文件快得多。 而且，编译器可以在项目中导入模块的每个位置重用它。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;= 实现细节&lt;/p&gt;
&lt;p&gt;todo&lt;/p&gt;
&lt;p&gt;= 常见库支持状态&lt;/p&gt;
&lt;p&gt;#link(&quot;https://arewemodulesyet.org/&quot;)[&quot;arewemodulesyet&quot;]&lt;/p&gt;
&lt;p&gt;Nope!&lt;/p&gt;
&lt;p&gt;= 编译器支持状态&lt;/p&gt;
&lt;p&gt;todo&lt;/p&gt;
&lt;p&gt;= 基本示例&lt;/p&gt;
&lt;p&gt;假设我们现在有如下cpp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int square(int i); // 声明一个函数
class Square {
private:
  int value;

public:
  explicit Square(int i) : value{square(i)} {}
  int getValue() const { return value; }
};
template &amp;lt;typename T&amp;gt; Square toSquare(const T &amp;amp;x) { return Square{x}; }
int square(int i) { // 实现平方函数
  return i * i;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只需要两步就可以转成一个模块单元： 1. 在第一行添加&lt;code&gt;export module Square;&lt;/code&gt;，这相当于导出一个名为Square的模块。 2. 把想要导出的函数前面加上export关键字。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/** modules/mod0/square.ixx **/
export module Square; // declare module  Square
int square(int i);
export class Square {
private:
  int value;

public:
  explicit Square(int i) : value{square(i)} {}
  int getValue() const { return value; }
};
export template &amp;lt;typename T&amp;gt; Square toSquare(const T &amp;amp;x) { return Square{x}; }
int square(int i) { return i * i; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在主文件中，就可以愉快地&lt;code&gt;import Square&lt;/code&gt;啦~。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/*== mod0/main.cpp ==*/
import std;
import Square; // import module `Square`
int main() {
  Square x = toSquare(42);
  std::cout &amp;lt;&amp;lt; x.getValue() &amp;lt;&amp;lt; &apos;\n&apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;= 我就喜欢写头文件？模块实现单元（module implementation unit）和模块声明单元（module interface unit）&lt;/p&gt;
&lt;p&gt;像上边这个例子，属于实现和声明放在一起了，可以写的很pythonic。 模块由若干个模块单元（module units）组成。而且模块可以进行分区（partition），或分为子模块。模块单元是包含模块声明的翻译单元（源文件）。类似.h和.cpp，模块分为&lt;strong&gt;接口单元&lt;/strong&gt;和&lt;strong&gt;实现单元&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;有几种类型的模块单元：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;模块接口单元（module interface unit）包含关键词&lt;code&gt;export module&lt;/code&gt;，用于导出模块名称和分区（partition）。和.h文件类似。&lt;/li&gt;
&lt;li&gt;模块实现单元（implementation unit）是不导出模块名称、分区名称。顾名思义，它用于实现模块。和cpp文件类似。&lt;/li&gt;
&lt;li&gt;主模块接口单元（primary module interface unit）是导出模块名称的模块接口单元。 模块中必须有一个且只能有一个主模块接口单元。上边这个例子里还没有模块分区，直接&lt;code&gt;export module Square&lt;/code&gt;，算是主模块单元。&lt;/li&gt;
&lt;li&gt;模块分区接口单元（partition interface unit）是导出模块分区名称的模块接口单元。&lt;/li&gt;
&lt;li&gt;模块分区实现单元（partition implementation unit）是模块实现单元，其模块声明中具有模块分区名称，但没有export关键字。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;export&lt;/code&gt;关键字仅在接口文件中使用。实现文件可以&lt;code&gt;import&lt;/code&gt;另一个模块，但不能&lt;code&gt;export&lt;/code&gt;任何名称。 实现文件可以具有任何扩展名（这一点编译器还有些特殊处理，好像标准里没有规定）。&lt;/p&gt;
&lt;p&gt;= 模块和namespace&lt;/p&gt;
&lt;p&gt;不像其它语言，模块不会自动添加一个namespace。 就像第一个例子，import Square之后，Square里导出的所有东西都被直接暴露。 有两种方法可以处理一下，让代码更加规范：&lt;/p&gt;
&lt;p&gt;定义一个和模块同名的namespace，&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;导出命名空间中需要的接口，则命名空间也会被隐式导出，但是命名空间中，没有被导出的接口则不会暴露&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;export module Square; // declare module ”Square”
namespace Square {
    int square(int i);// not exported
    export class Square {
        // bala bala ...
    };
    export template&amp;lt;typename T&amp;gt;
    Square toSquare(const T&amp;amp; x) {
        // ...
    }
    int square(int i) { // not exported
        // ...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;显式导出整个命名空间，则命名空间中的所有声明都会被导出。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;export module Square; // declare module ”Square”
int square(int i);
export namespace Square {
    class Square {
        // ...
    };
    template&amp;lt;typename T&amp;gt;
    Square toSquare(const T&amp;amp; x) {
        // ...
    }
}
int square(int i) { // not exported
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样，在主文件中都可以达到以下效果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Square;// 这个是模块名，需要和export匹配
int main(){
    Square::Square x = Square::toSquare(42);
    // ::前Square是命名空间的名字，::Square是类名，这块搞清楚，之后写模块就简单了
    std::cout &amp;lt;&amp;lt; x.getValue() &amp;lt;&amp;lt; &apos;\n&apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;= 多文件&lt;/p&gt;
&lt;p&gt;== 模块接口单元与模块实现单元&lt;/p&gt;
&lt;p&gt;模块接口单元用于导出模块名、模块接口。按照模块分区（partition），分为主模块接口单元和分区模块接口单元。直观上，如果整个模块叫做mod1，则含有export module mod1;的文件就是主接口单元；其他文件不能导出mod1这个名字。如果其他文件导出了mod1的子模块export module mod1:submod;则该文件是分区接口单元。&lt;/p&gt;
&lt;p&gt;先来说没有分区的模块接口单元：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export module Mod1;  // module declaration

import std;

struct Order {
    int count;
    std::string name;
    double price;

    Order(int c, const std::string&amp;amp; n, double p)
            : count{c}, name{n}, price{p} {
    }
};

export class Customer {
private:
    std::string name;
    std::vector&amp;lt;Order&amp;gt; orders;
public:
    Customer(const std::string&amp;amp; n)
            : name{n} {
    }
    void buy(const std::string&amp;amp; ordername, double price) {
        orders.push_back(Order{1, ordername, price});
    }
    void buy(int num, const std::string&amp;amp; ordername, double price) {
        orders.push_back(Order{num, ordername, price});
    }
    double sumPrice() const;
    double averagePrice() const;
    void print() const;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第一行，定义并导出模块的名字：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export module Mod1;  // module declaration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第二行，导入标准模块std：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import std;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后就是常见的类型声明部分，声明一个订单结构体 struct Order，和一个顾客类。相比于普通头文件，唯一多的就是 class Customer前前面的export关键字，表示导出这个类。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Order {
    // ...
}

export class Customer {
    // ....
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;简单来说，首先声明并导出一个模块，导入需要的模块，声明需要类型，导出必要的类型。&lt;/p&gt;
&lt;p&gt;接下来，需要实现这些类型（或者函数），类似cpp文件，这里我们分成两个实现单元：&lt;/p&gt;
&lt;p&gt;实现单元第一行，标明所属模块，&lt;code&gt;module Mod1;&lt;/code&gt;表示该单元属于Mod1模块。接着，这个单元实现&lt;code&gt;void Customer::print() const{}&lt;/code&gt;函数。具体用到了format函数，这也是c++20的新功能。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module Mod1;         // implementation unit of module Mod1
import std;

void Customer::print() const
{
    // print name:
    std::cout &amp;lt;&amp;lt; name &amp;lt;&amp;lt; &quot;:\n&quot;;
    // print order entries:
    for (const auto&amp;amp; od : orders) {
        std::cout &amp;lt;&amp;lt; std::format(&quot;{:3} {:14} {:6.2f} {:6.2f}\n&quot;,
                                 od.count, od.name, od.price, od.count * od.price);
    }
    // print sum:
    std::cout &amp;lt;&amp;lt; std::format(&quot;{:25} ------\n&quot;, &apos; &apos;);
    std::cout &amp;lt;&amp;lt; std::format(&quot;{:25} {:6.2f}\n&quot;, &quot;    Sum:&quot;, sumPrice());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;与上边这个类似，这是另一个实现单元：标明属于Mod1模块，实现了两个函数&lt;code&gt;double Customer::sumPrice() const {}&lt;/code&gt; 和另一个函数&lt;code&gt;double Customer::averagePrice() const {}&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module Mod1;         // implementation unit of module Mod1

double Customer::sumPrice() const {
    double sum = 0.0;
    for (const Order &amp;amp;od: orders) {
        sum += od.count * od.price;
    }
    return sum;
}

double Customer::averagePrice() const {
    if (orders.empty()) {
        return 0.0;
    }
    return sumPrice() / orders.size();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实现单元和cpp文件类似。以上三个文件完整定义并实现了Mod1模块。&lt;/p&gt;
&lt;p&gt;具体使用模块如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import std;
import Mod1;

int main() {
    Customer c1{&quot;Kim&quot;};
    // Order b1(1, &quot;buy&quot;, 590.0); // error Order并没有被导出。

    c1.buy(&quot;table&quot;, 59.90);
    c1.buy(4, &quot;chair&quot;, 9.20);

    c1.print();
    std::cout &amp;lt;&amp;lt; &quot;  Average: &quot; &amp;lt;&amp;lt; c1.averagePrice() &amp;lt;&amp;lt; &apos;\n&apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出结果为&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;上边这种定义与实现分离的方式，cmake支持好像还不太好，没编译过去&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;== 模块内部（internal）单元，模块分区&lt;/p&gt;
&lt;p&gt;有接口和实现单元还不够，c++20的module特性还支持模块划分，每个模块还可以被划分为子模块。类似python中&lt;code&gt;import a.b.c;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;分区接口的开头如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export module Example:part1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分区实现单元的开头如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module Example:part1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;若要访问另一个分区中的声明，分区必须导入它，但它只能使用分区名称，而不是模块名称：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module Example:part2;
import :part1;// 额外注意：这里不能带模块名，Example:part1不行。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主接口单元必须导入并重新导出模块的所有接口分区文件，如下所示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export module Example;
// export import :part1; // 如果part1不被export，则part1的所有内容对外不可见（包括export的接口）
export import :part2;// 所有part2导出的接口，都会被导出到Example中。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主接口单元可以导入分区实现文件，但无法导出它们。 不允许这些文件导出任何名称。 这一限制使模块能够在模块内部保留实现详细信息。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;具体的例子看modules/mod2&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;= 子模块submodule&lt;/p&gt;
&lt;p&gt;标准里面没有子模块的规定，但是模块名支持&lt;code&gt;.&lt;/code&gt; ，他在模块名中没有特殊含义，但是可以从逻辑上划分模块之间的关系。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export module a;
// export import a.c; 没有被export，如果只import a是看不到a.c的。
export import a.b.c;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;= 模块和include&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;While in theory modules could replace all traditional header files with all their flaws, in practice this will never happen&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;=== global module fragment&lt;/p&gt;
&lt;p&gt;现在，我们可以确定，有export module xxx的就是模块接口单元，没有export关键字，但是有module xxx都是模块实现单元。&lt;/p&gt;
&lt;p&gt;c++为了和旧代码兼容(&lt;s&gt;为了留住用户&lt;/s&gt;)，特地兼容了普通的头文件。用到了 全局模块片段(global module fragment)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module; // start module unit with global module fragment
#include &amp;lt;string&amp;gt;
#include &amp;lt;vector&amp;gt;
export module Mod1; // module declaration

// bula bula bula
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;前面三行就是全局模块片段，不看前三行，和前面说过的模块单元是一样的。但是&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;include进来的 指令 （&lt;code&gt;#define&lt;/code&gt; and &lt;code&gt;#include&lt;/code&gt;）可以正常使用和被看到。&lt;/li&gt;
&lt;li&gt;不会导出任何东西（包括macros, declarations, definitions）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;== importable header&lt;/p&gt;
&lt;p&gt;可被导入的头文件。&lt;/p&gt;
&lt;p&gt;比如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import &amp;lt;vector&amp;gt;;
import &quot;myheader.h&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;include头文件和import模块之间的主要区别在于：头文件中的任何预处理器定义在语句之后的导入程序中都可见。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;= 其他&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;模块的使用可以提高代码的可读性和可维护性，解决了头文件编译的难题，同时也提高了编译速度。&lt;/li&gt;
&lt;li&gt;模块分为接口单元和实现单元，可以通过定义同名的命名空间来更加规范地使用模块。&lt;/li&gt;
&lt;li&gt;全局模块片段和可导入的头文件也是模块的一部分，可以提高代码的兼容性和可读性。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>gltf-transform使用记录</title><link>https://dashuai009.github.io/blog/045/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/045/</guid><pubDate>Wed, 11 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2023,
month: 10,
day: 11,
)
#metadata((
title: &quot;gltf-transform使用记录&quot;,
subtitle: [gltf],
author: &quot;dashuai009&quot;,
description: &quot;&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; -&amp;gt; ~ gltf-transform --help
node:internal/modules/cjs/loader:1075
  const err = new Error(message);
              ^

Error: Cannot find module &apos;call-bind&apos;
Require stack:
- /home/dashuai/.nvm/versions/node/v18.15.0/lib/node_modules/@gltf-transform/cli/node_modules/@ljharb/through/index.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1075:15)
    at Module._load (node:internal/modules/cjs/loader:920:27)
    at Module.require (node:internal/modules/cjs/loader:1141:19)
    at require (node:internal/modules/cjs/helpers:110:18)
    at Object.&amp;lt;anonymous&amp;gt; (/home/dashuai/.nvm/versions/node/v18.15.0/lib/node_modules/@gltf-transform/cli/node_modules/@ljharb/through/index.js:4:16)
    at Module._compile (node:internal/modules/cjs/loader:1254:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1308:10)
    at Module.load (node:internal/modules/cjs/loader:1117:32)
    at Module._load (node:internal/modules/cjs/loader:958:12)
    at ModuleWrap.&amp;lt;anonymous&amp;gt; (node:internal/modules/esm/translators:169:29) {
  code: &apos;MODULE_NOT_FOUND&apos;,
  requireStack: [
    &apos;/home/dashuai/.nvm/versions/node/v18.15.0/lib/node_modules/@gltf-transform/cli/node_modules/@ljharb/through/index.js&apos;
  ]
}

Node.js v18.15.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上错误是因为call-bind没有找到，只需要&lt;code&gt;npm install call-bind&lt;/code&gt;即可&lt;/p&gt;
</content:encoded></item><item><title>使用python和qwen处理doc文件</title><link>https://dashuai009.github.io/blog/047/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/047/</guid><pubDate>Sun, 20 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2023,
month: 8,
day: 20,
)
#metadata((
title: &quot;使用python和qwen处理doc文件&quot;,
subtitle: [qwen,win32com,python-docx,textextract],
author: &quot;dashuai009&quot;,
description: &quot;&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;今天帮同学处理了一些docx文件，主要是一些会议通知的文件，总共有一千多份doc、docx、pdf、wps等文件。&lt;/p&gt;
&lt;p&gt;目标是提取这些会议通知中的时间、地点、名称、主办单位。&lt;/p&gt;
&lt;p&gt;这些文本的格式相对统一，比如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;通知
一、会议时间：
xxx年xx月xx日xx点
二、会议地点
xxxx
三、xxx

xxx

   【通知单位】
   年月日​
这种算是比较好的，还有啥

通知
一、请xxx于xxx月xx日到xxx参加xxx
xxx
xx​
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两千份通知，横跨半年，至少四种文件格式（pdf、doc、docx、wps），大体格式至少十几种。&lt;/p&gt;
&lt;p&gt;手写regex规则是不可能了，这辈子也不可能了。正好想到最近疯狂开源的大模型，文本直接喂给他们，可以帮我总结这些文本。&lt;/p&gt;
&lt;p&gt;首先不考虑chatgpt，死贵死贵，2023年8月19日，&lt;code&gt;16K context $0.003 / 1K tokens&lt;/code&gt;，两百万文本就要110人民币。其次，数据传到境外，明显不太安全，这里还是有些敏感数据的。&lt;/p&gt;
&lt;p&gt;找了一圈，发现qwen和llama2还可以（早一个月遇到这个问题可能还没这好弄，感谢开源人士）。后边这个中文一般般，有#link(&quot;https://huggingface.co/LinkSoul/Chinese-Llama-2-7b&quot;)[LinkSoul微调的模型]，我用下来效果一般，我这个任务可能不太对付的问题。前边qwen是阿里开源的，看他们公布的对比测试，很能打，阿里出品，看起来靠谱。（注：我只是对于我的文件进行测试，不代表两个相对好坏）&lt;/p&gt;
&lt;p&gt;这两个试用都很方便，hugginface真是大大滴好。qwen文档：#link(&quot;https://github.com/QwenLM/Qwen&quot;)[Qwen-7B/README_CN.md at main · QwenLM/Qwen-7B (github.com)]
第一次用huggingface，还以为要手动一个个下载&lt;code&gt;pytorch_model-0001-0008.bin&lt;/code&gt;文件，原来代码里都处理好这些琐事了。直接跑，自动下载。
qwen还依赖flash-attention，官方推荐&lt;code&gt;git clone -b v1.0.8&lt;/code&gt; ，就别用flash-attention2了，安装会遇到编译问题。
== 文件处理
找了半天没找到合适的库去读doc文件&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;textract #link(&quot;https://textract.readthedocs.io/en/stable/&quot;)[textract 1.6.1 documentation] 这个说是啥都能读，安装好之后会遇到后边这个报错，调用antiword命令处理时有问题。&lt;/li&gt;
&lt;li&gt;其他好多库都不太支持doc，只支持docx&lt;/li&gt;
&lt;li&gt;批量转#link(&quot;https://www.bilibili.com/read/cv3416134/&quot;)[【真技术】批量将doc转为docx的方法 - 哔哩哔哩 (bilibili.com)] 有vba和ps的代码&lt;/li&gt;
&lt;li&gt;python的win32com库可以实现上一种方法，具体如后边代码&lt;/li&gt;
&lt;li&gt;python-docx可以修改和读取docx&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;Traceback (most recent call last):
  File &quot;C:\Users\15258\.conda\envs\llm\Lib\site-packages\textract\parsers\utils.py&quot;, line 87, in run
    pipe = subprocess.Popen(
           ^^^^^^^^^^^^^^^^^
  File &quot;C:\Users\15258\.conda\envs\llm\Lib\subprocess.py&quot;, line 1026, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File &quot;C:\Users\15258\.conda\envs\llm\Lib\subprocess.py&quot;, line 1538, in _execute_child
    hp, ht, pid, tid = _winapi.CreateProcess(executable, args,
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [WinError 2] The system cannot find the file specified
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;处理pdf、doc都可以同word打开，然后重新saveas，pdf用word打开时会有个提示，按理来说可以在python加个-y的参数，自动选yes，没找到资料，跑脚本的时候手动点的yes，还好也就二三十个。有些pdf有加密，不让其他程序读取内容，这些处理不了，只能人工。&lt;/p&gt;
&lt;p&gt;== 提示QWEN去提取信息
简单，直接加用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;f&quot;找出以下通知的会议名称、会议时间、会议地点和主办单位，结果用逗号隔开\n{text}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;text是docx文本。&lt;/p&gt;
&lt;p&gt;也就三个步骤：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;把pdf、doc转成docx&lt;/li&gt;
&lt;li&gt;读取docx&lt;/li&gt;
&lt;li&gt;文本加提示词喂给qwen
代码&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import os
from win32com import client as wc #导入模块
import textract


word = wc.Dispatch(&quot;Word.Application&quot;) # 打开word应用程序


base_dir = &apos;./docs&apos;

def find_all_file(base, extension):
    for root, ds, fs in os.walk(base):
        for f in fs:
            fullname = os.path.join(root, f)
            if f.endswith(extension):
                yield os.path.abspath(fullname)


for file in find_all_file(base_dir, &quot;.pdf&quot;):
    print(file)
    _file = file.replace(&quot;pdf&quot;, &quot;docx&quot;)
    print(_file)
    if not os.path.exists(_file):
        doc = word.Documents.Open(file) #打开word文件
        doc.SaveAs(_file, 12)#另存为后缀为&quot;.docx&quot;的文件，其中参数12指docx文件
        doc.Close() #关闭原来word文件

for file in find_all_file(base_dir, &quot;.doc&quot;):
    print(file)
    _file = file.replace(&quot;doc&quot;, &quot;docx&quot;)
    print(_file)
    if not os.path.exists(_file):
        doc = word.Documents.Open(file) #打开word文件
        doc.SaveAs(_file, 12)#另存为后缀为&quot;.docx&quot;的文件，其中参数12指docx文件
        doc.Close() #关闭原来word文件

word.Quit()

import os
from docx import Document
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.generation import GenerationConfig

# 请注意：分词器默认行为已更改为默认关闭特殊token攻击防护。
tokenizer = AutoTokenizer.from_pretrained(&quot;Qwen/Qwen-7B-Chat&quot;, trust_remote_code=True)

# 打开bf16精度，A100、H100、RTX3060、RTX3070等显卡建议启用以节省显存
# model = AutoModelForCausalLM.from_pretrained(&quot;Qwen/Qwen-7B-Chat&quot;, device_map=&quot;auto&quot;, trust_remote_code=True, bf16=True).eval()
# 打开fp16精度，V100、P100、T4等显卡建议启用以节省显存
# model = AutoModelForCausalLM.from_pretrained(&quot;Qwen/Qwen-7B-Chat&quot;, device_map=&quot;auto&quot;, trust_remote_code=True, fp16=True).eval()
# 使用CPU进行推理，需要约32GB内存
# model = AutoModelForCausalLM.from_pretrained(&quot;Qwen/Qwen-7B-Chat&quot;, device_map=&quot;cpu&quot;, trust_remote_code=True).eval()
# 默认使用自动模式，根据设备自动选择精度
model = AutoModelForCausalLM.from_pretrained(&quot;Qwen/Qwen-7B-Chat&quot;, device_map=&quot;auto&quot;, trust_remote_code=True).eval()

# 可指定不同的生成长度、top_p等相关超参
model.generation_config = GenerationConfig.from_pretrained(&quot;Qwen/Qwen-7B-Chat&quot;, trust_remote_code=True)


base_dir = &apos;./docs&apos; # 通知文件的文件夹
out_file = open(&quot;./doc_out2.txt&quot;, &quot;w&quot;) #结果输出文件
def find_all_file(base): #和上边代码重复了
    for root, ds, fs in os.walk(base):
        for f in fs:
            fullname = os.path.join(root, f)
            if f.endswith(&apos;.docx&apos;):
                yield fullname

cnt = 0
for i in find_all_file(base_dir):
    document = Document(i) #读取docx
    text = &quot;&quot;
    for p in document.paragraphs:
        text += p.text #拼接文本
    text_format = f&quot;找出以下通知的会议名称、会议时间、会议地点和主办单位，结果用逗号隔开\n{text}&quot;
    # 问模型
    response, _ = model.chat(tokenizer, text_format, history = None)
    cnt += 1
    print(cnt, i)
    out_file.write(&quot;\n&quot; + &quot;==&quot; * 10 + &quot;\n&quot;)
    out_file.write(f&quot;file_name = {i}\n&quot;) # 输出结果
    out_file.write(response)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== 总结
有了large language model，这种任务一下子简单很多。&lt;/p&gt;
&lt;p&gt;结果上，对于比较规整的文件，提取结果非常好。不太规整的文件，人力不好做，大模型也整不明白。&lt;/p&gt;
&lt;p&gt;以我的经验，gpt4能把这个任务做的很好，qwen这些开源的7b模型，还是不如人意，比如&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输出了多余的提示信息，”您好”、”结果如下“等等&lt;/li&gt;
&lt;li&gt;分不清楚主办单位和被通知人，源文件确实复杂&lt;/li&gt;
&lt;li&gt;吃内存，4090 24G显存，也就处理4k左右的中文文本，多了爆显存&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>windows只用键盘如何打开蓝牙，连接蓝牙鼠标？</title><link>https://dashuai009.github.io/blog/044/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/044/</guid><pubDate>Sat, 17 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2023,
month: 6,
day: 17,
)
#metadata((
title: &quot;windows只用键盘如何打开蓝牙，连接蓝牙鼠标？&quot;,
subtitle: [windows],
author: &quot;dashuai009&quot;,
description: &quot;&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;== 事故描述
今天手残了，用蓝牙鼠标把蓝牙关掉了，鼠标直接切断了。电脑上只有有线键盘还连着。&lt;/p&gt;
&lt;p&gt;== 恢复
我用的windows11，win+i打开设置，用tab和上下键挪到打开蓝牙的地方，用#strong[空格键]打开蓝牙。
之前用enter死活打不开，还是上网搜出来，应该用空格键。&lt;/p&gt;
</content:encoded></item><item><title>使用c++扩展python</title><link>https://dashuai009.github.io/blog/046/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/046/</guid><pubDate>Sat, 17 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2023,
month: 6,
day: 17,
)
#metadata((
title: &quot;使用c++扩展python&quot;,
subtitle: [cpython,c++],
author: &quot;dashuai009&quot;,
description: &quot;&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;= 简介
c++可以编写python的扩展库。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Extending Python with C or C++ — Python 3.11.4 documentation&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;= 开发环境&lt;/p&gt;
&lt;p&gt;linux 下需要安装python3-dev&lt;/p&gt;
&lt;p&gt;= CMAKE编写
这里给出test.cpp编写的python模块和测试用 的test.py&lt;/p&gt;
&lt;p&gt;编译与执行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir build

cmake -S . -B ./build

cmake --build ./build

python3 test.py
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;cmake_minimum_required(VERSION 3.12) # 3.12才可以find_package(python)
project(test_cp)

find_package (Python REQUIRED Interpreter Development)

set(test_cpp_demo_name &quot;test_cpp&quot;) # 定义一个库的名字，同时作为py模块的名字
add_definitions(-DPY_MODULE_NAME_STR=&quot;${test_cpp_demo_name}&quot;)
add_definitions(-DPY_MODULE_NAME=PyInit_${test_cpp_demo_name})


add_library(${test_cpp_demo_name} SHARED test.cpp)
set_target_properties(
        ${test_cpp_demo_name}
        PROPERTIES
        PREFIX &quot;&quot; # 输出前缀没有了
        OUTPUT_NAME ${test_cpp_demo_name}.cpython-310-x86_64-linux-gnu # 注意后边的python版本、平台版本、编译器组织
)
target_include_directories(${test_cpp_demo_name} PRIVATE
        ${Python_INCLUDE_DIRS})

target_link_directories(${test_cpp_demo_name} PRIVATE
        ${Python_LIBRARY_DIRS})

target_link_libraries(${test_cpp_demo_name} PRIVATE
        ${Python_LIBRARIES})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;= TEST.CPP
具体可以参考上边给出的链接&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &quot;Python.h&quot;

//  起一个命名空间
namespace test_cpp {
constexpr int N = 1000;
int f[N];
bool flag = false;
int Fib_impl(int n) {
    if (flag &amp;amp;&amp;amp; 0 &amp;lt;= n &amp;amp;&amp;amp; n &amp;lt; N) {
        return f[n];
    }
    f[1] = 1;
    for (int i = 2; i &amp;lt; N; ++i) {
        f[i] = f[i - 1] + f[i - 2];
    }
    flag = true;
    return f[n];
}
} // namespace test_cpp

// 给python导出接口
static PyObject *Fib(PyObject * /* unused module reference */, PyObject *o) {
    int n = PyLong_AsLong(o);
    int fn = test_cpp::Fib_impl(n);
    return Py_BuildValue(&quot;i&quot;, fn);
}

//定义python模块中有哪些函数
static struct PyMethodDef test_cpp_methods[] = {
    {&quot;fast_fib&quot;, Fib, METH_O, &quot;fast fib&quot;},
    // Terminate the array with an object containing nulls.
    {nullptr, nullptr, 0, nullptr}};

// 定义模块
static struct PyModuleDef test_cpp_module = {
    PyModuleDef_HEAD_INIT,
    PY_MODULE_NAME_STR, /* name of module */ // python里可以import 这个名字
    nullptr, /* module documentation, may be NULL */
    -1,      /* size of per-interpreter state of the module,
                or -1 if the module keeps state in global variables. */
    test_cpp_methods
    };
//定义模块的初始化函数，import之后会自动执行


// .so的名字要和这个函数的名字、和PyModuleDef里的name对齐
PyMODINIT_FUNC PY_MODULE_NAME(void) {
    PyObject *m;
    m = PyModule_Create(&amp;amp;test_cpp_module);
    if (m == NULL)
        return NULL;
    return m;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;= PYTHON使用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import sys
# sys.path.append(&quot;./build/lib.linux-x86_64-3.10&quot;)
sys.path.append(&quot;./build&quot;)
import test_cpp

a = test_cpp.fast_fib(10);
print(a)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>cpp maya plugin example</title><link>https://dashuai009.github.io/blog/043/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/043/</guid><description>the examples of cpp maya plugin </description><pubDate>Wed, 24 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2023,
month: 5,
day: 24,
)
#metadata((
title: &quot;cpp maya plugin example&quot;,
subtitle: [MayaPlugin],
author: &quot;dashuai009&quot;,
description: &quot;the examples of cpp maya plugin &quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;#link(&quot;https://github.com/dashuai009/cpp-maya-plugins&quot;)[dashuai009/cpp-maya-plugins]&lt;/p&gt;
&lt;p&gt;This is an example collection of cpp maya plugins, sourced from#link(&quot;https://help.autodesk.com/view/MAYAUL/2022/ENU/?guid=Maya_SDK_cpp_ref_index_html&quot;)[autodesk website];.&lt;/p&gt;
&lt;p&gt;#link(&quot;https://help.autodesk.com/view/MAYAUL/2022/ENU/?guid=Setting-up-your-build&quot;)[Setting-up-your-build]&lt;/p&gt;
&lt;p&gt;== how can I get these code?
&amp;lt;how-can-i-get-these-code&amp;gt;
I use a Python crawler, in &lt;code&gt;cpp_maya_plugins&lt;/code&gt; to get these code.&lt;/p&gt;
&lt;p&gt;== Develop
&amp;lt;develop&amp;gt;
First,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cmake_minimum_required(VERSION 3.26)
project(maya_plugins)

set(CMAKE_CXX_STANDARD 17)

# set maya include path
set(MAYA_PATH &quot;C:/Program Files/Autodesk/Maya2022&quot;)
include_directories(${MAYA_PATH}/include)
link_directories(${MAYA_PATH}/lib)
set(LINK_FLAGS &quot;/export:initializePlugin /export:uninitializePlugin&quot;)
# Maya plug-ins that consist of a single file and are intended for your own individual use or for use within your organization can be distributed by placing the files in a shared directory pointed at by the MAYA_PLUG_IN_PATH in users&apos; Maya.env files.

# You can add several directories to MAYA_PLUG_IN_PATH. Each directory must be separated by a semicolon (;) on Windows or by a colon (:) on macOS and Linux.

set(MAYA_PLUG_IN_PATH &quot;C:/Users/User/work/maya-plugins/out&quot;)

add_subdirectory(helloWorld)
add_subdirectory(pickCmd)
add_subdirectory(pluginCallbacks)
add_subdirectory(zoomCameraCmd)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;for every single plugin in subdirectry:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cmake_minimum_required(VERSION 3.26)
project(pickCmd)

message(${PROJECT_NAME})
# output a dll
add_library(${PROJECT_NAME} SHARED pickCmd.cpp)

# set the suffix of the output dynamic library to .mll,
# and directly put it into ourself plugin dirs
set_target_properties(
        ${PROJECT_NAME}
        PROPERTIES
            SUFFIX &quot;.mll&quot;
            RUNTIME_OUTPUT_DIRECTORY_DEBUG &quot;${MAYA_PLUG_IN_PATH}/plug-ins&quot;) # debug

#set_target_properties(${PROJECT_NAME} PROPERTIES RELEASE_POSTFIX &quot;.mll&quot;)

# These two libs are only the core modules of Maya. If you need to use other modules, you need to add other libs, such as OpenMayaUI.lib. You can find all of them in `${MAYA_PATH}/lib`
target_link_libraries(${PROJECT_NAME} Foundation OpenMaya)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== Build
&amp;lt;build&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>list命令</title><link>https://dashuai009.github.io/blog/007/007/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/007/007/</guid><pubDate>Sat, 20 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2023,
month: 5,
day: 20,
)
#metadata((
&quot;title&quot;: &quot;list命令&quot;,
&quot;author&quot;: &quot;dashuai009&quot;,
description: &quot;&quot;,
pubDate: date.display(),
subtitle: [linux,list命令],
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;= 实验任务&lt;/p&gt;
&lt;p&gt;编程实现程序myList.c，列表普通磁盘文件，包括文件名和文件大小。要求能够处理以下几个选项&lt;/p&gt;
&lt;p&gt;#table(
columns: (auto, auto),
table.header(&quot;参数&quot;, &quot;含义&quot;),
&lt;code&gt;-r&lt;/code&gt;, [递归方式列出子目录],
&lt;code&gt;-a&lt;/code&gt;, [列出文件名第一个字符为圆点的普通文件],
&lt;code&gt;-l &amp;lt;bytes&amp;gt;&lt;/code&gt;, [限定文件大小的最小值（字节）],
&lt;code&gt;-h &amp;lt;bytes&amp;gt;&lt;/code&gt;, [限定文件大小的最大值（字节）],
&lt;code&gt;-m &amp;lt;integer&amp;gt;&lt;/code&gt;, [限定文件的最近修改时间必须在n天内],
&lt;code&gt;--&lt;/code&gt;, [显式地终止命令选项分析],
)&lt;/p&gt;
&lt;p&gt;= fstat、stat和lstat的区别：&lt;/p&gt;
&lt;p&gt;三个函数原型如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int fstat(int filedes, struct stat *buf);

int stat(const char *path, struct stat *buf);

int lstat(const char *path, struct stat *buf);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;fstat系统调用接受的是 一个“文件描述符”，而另外两个则直接接受“文件全路径”。文件描述符是需要我们用open系统调用后才能得到的，而文件全路经直接写就可以了。&lt;/li&gt;
&lt;li&gt;stat和lstat的区别：当文件是一个符号链接时，lstat返回的是该符号链接本身的信息；而stat返回的是该链接指向的文件的信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;= 实现步骤&lt;/p&gt;
&lt;p&gt;== 选用lstat&lt;/p&gt;
&lt;p&gt;&lt;em&gt;能够处理“死链接”&lt;/em&gt;，对于符号链接文件，如果链接目标文件不存在，在使用stat函数的情况下，会访问链接的目标文件，导致输出&quot;No such file or directory&quot;。实际上，链接文件link依旧存在，文件大小不为0。而使用fstat不会有这种情况。fstat不会访问目标文件。&lt;/p&gt;
&lt;p&gt;== 处理参数&lt;/p&gt;
&lt;p&gt;设置以下标志位&lt;/p&gt;
&lt;p&gt;#table(
columns: (auto, auto),
table.header(&quot;参数&quot;, &quot;含义&quot;),
&lt;code&gt;int flag_recurse = 0;&lt;/code&gt;, [是否递归遍历子目录，默认为0],
&lt;code&gt;int flag_show_hidden = 0;&lt;/code&gt;, [是否显示隐藏文件（文件名以.开头）],
&lt;code&gt;int min_size = 0;&lt;/code&gt;, [文件大小的最小值],
&lt;code&gt;int max_size = -1;&lt;/code&gt;, [文件大小的最大值，-1表示不受限制],
&lt;code&gt;int last_n_days = -1;&lt;/code&gt;, [只显示最近n天的文件],
)&lt;/p&gt;
&lt;p&gt;== 全局变量
#table(
columns: (auto, auto),
table.header(&quot;参数&quot;, &quot;含义&quot;),
&lt;code&gt;char *target[256];&lt;/code&gt;, [输入的目录集合，最多处理256个输入目录],
&lt;code&gt;int cntTarget;&lt;/code&gt;, [输入目录的个数],
&lt;code&gt;char current_dir[] = &quot;.&quot;;&lt;/code&gt;, [当前目录为&apos;.&apos;，当无输入目录时，默认为当前目录],
)&lt;/p&gt;
&lt;p&gt;== 所需函数，具体代码在文末&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**

- 满足flag的条件下，输出错误信息并退出整个程序，错误信息存储在可变参数列表...中
- 否则继续正常执行
*/
void handle_error(int flag, int num, ...)

/**
 * 考虑-a参数（是否显示隐藏文件），判断dir是否需要输出
 */
int checkDot(const char *dir)


/**

- 判断文件大小是否符合要求
*/
int checkSize(int fileSize)

/**
 * 判断文件日期是否符合要求
 */
int checkDate(struct timespec ti)

/**

- 输出dir目录，dep是目录深度，这里暂时没有用到
- 输出结果放在一行里
- 输出效果是彩色的。
*/
void myPrint(const char *dir, int dep)

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 输入是一个目录，
 * 如果设置了-r标志，需要递归遍历
 * 否则只需要输出当前目录
 * 这里使用了lstat，而不是stat，可以处理死链接的情况
 */
void myList(const char *dir, int dep)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;/**

- 初始化传入的参数
*/
void init(int argc, char **argv)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主函数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int main(int argc, char **argv) {
    init(argc, argv);
    for (int i = 0; i &amp;lt; cntTarget; ++i) {
        printf(&quot;\033[93mSearching  %s\n&quot;, target[i]);
        struct stat st;
        int ret = lstat(target[i], &amp;amp;st);
        handle_error(ret == -1, 2, target[i], strerror(errno));
        if (S_ISDIR(st.st_mode)) {//保证遍历的是目录
            myList(target[i]);
        } else {
            myPrint(target[i]);
        }
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;= 编译命令&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gcc myList.c -o list&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;= 运行示例&lt;/p&gt;
&lt;p&gt;== &lt;code&gt;./list&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;列出默认路径&lt;code&gt;./&lt;/code&gt;的文件和文件夹&lt;/p&gt;
&lt;p&gt;// &amp;lt;img src=&quot;list命令/2021-05-07 17-11-34 的屏幕截图.png&quot; style=&quot;zoom:67%;&quot; alt=&quot;./list&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;== &lt;code&gt;./list -r&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;递归遍历当前目录&lt;/p&gt;
&lt;p&gt;// &amp;lt;img src=&quot;list命令/2021-05-07 17-11-23 的屏幕截图.png&quot; style=&quot;zoom:50%;&quot; alt=&quot;./list -r&quot;/&amp;gt;&lt;/p&gt;
&lt;p&gt;== &lt;code&gt;./list ./test/test1 ./test/test2 -a&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;显示test/test1和test/test2目录下的所有文件，包括隐藏文件&lt;/p&gt;
&lt;p&gt;// &amp;lt;img src=&quot;list命令/2021-05-02 21-21-43 的屏幕截图.png&quot; style=&quot;zoom:50%;&quot; alt=&quot;./list ./test/test1 ./test/test2 -a&quot;/&amp;gt;&lt;/p&gt;
&lt;p&gt;== &lt;code&gt;./list .. -l 1000000 -h 3000000&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;列出父级目录中大于1000000字节，小于3000000字节的文件。通过名利&lt;code&gt;ls -al .. -S&lt;/code&gt;可以将父级目录中的文件从大到小排列，得出结果，简单比对即可。&lt;/p&gt;
&lt;p&gt;// &amp;lt;img src=&quot;list命令/2021-05-02 21-27-58 的屏幕截图.png&quot; style=&quot;zoom:50%;&quot; alt=&quot;./list .. -l 1000000 -h 3000000&quot;/&amp;gt;&lt;/p&gt;
&lt;p&gt;== &lt;code&gt;./list -r -m 15 ..&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;递归便利父级目录，列出15天内修改的文件。通过&lt;code&gt;find .. -ctime -15&lt;/code&gt;验证，可以的出一样的结果。&lt;/p&gt;
&lt;p&gt;// &amp;lt;img src=&quot;list命令/2021-05-07 17-12-27 的屏幕截图.png&quot; style=&quot;zoom:50%;&quot; alt=&quot;./list -r -m 15 ..&quot;/&amp;gt;&lt;/p&gt;
&lt;p&gt;== &lt;code&gt;./list ./test/test1 ./test2 -- .. ../..&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;使用--截断之后的参数分析，之列出前两个参数中的文件。&lt;/p&gt;
&lt;p&gt;// &amp;lt;img src=&quot;list命令/2021-05-02 21-34-37 的屏幕截图.png&quot; style=&quot;zoom:50%;&quot; alt=&quot;./list ./test/test1 ./test2 -- .. ../..&quot;/&amp;gt;&lt;/p&gt;
&lt;p&gt;= 代码&lt;/p&gt;
&lt;p&gt;链接如下&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://pastebin.ubuntu.com/p/9GB324Rrr6/&quot;&gt;ubuntu paste bin&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;dirent.h&amp;gt;
#include &amp;lt;errno.h&amp;gt;
#include &amp;lt;stdarg.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;sys/stat.h&amp;gt;
#include &amp;lt;sys/time.h&amp;gt;
#include &amp;lt;sys/types.h&amp;gt;

int flag_recurse = 0;
int flag_show_hidden = 0;
int min_size = 0;
int max_size = -1;
int last_n_days = -1;
char *target[256];
int cntTarget;
char current_dir[] = &quot;.&quot;;
/**

- 满足flag的条件下，输出错误信息并退出整个程序，
- 否则继续正常执行
*/
void handle_error(int flag, int num, ...) {
    if (flag) {
        va_list ap;        // (1) 定义参数列表
        va_start(ap, num); // (2) 初始化参数列表
        printf(&quot;\033[91m%sError:&quot;);
        for (int i = 0; i &amp;lt; num; ++i) {
            printf(&quot;%s &quot;, va_arg(ap, char *));
        }
        printf(&quot;\n&quot;);
        va_end(ap);
        exit(0);
    }
}

/**

- 考虑-a参数（是否显示隐藏文件），判断dir是否需要输出
*/
int checkDot(const char *dir) {
    if (flag_show_hidden) {
        return 1;
    }
    const char *ptr = strrchr(dir, &apos;/&apos;);
    if (ptr == NULL) {
        return dir[0] != &apos;.&apos;;
    } else {
        return ptr[1] != &apos;.&apos;;
    }
}

/**

- 判断文件大小是否符合要求
*/
int checkSize(int fileSize) {
    return min_size &amp;lt;= fileSize &amp;amp;&amp;amp; (max_size == -1 || fileSize &amp;lt;= max_size);
}

/**

- 判断文件日期是否符合要求
*/
int checkDate(struct timespec ti) {
    int lastChange = ti.tv_sec;
    struct timeval t;
    gettimeofday(&amp;amp;t, NULL);
    int currentTime = t.tv_sec;
    return last_n_days == -1 ||
           (currentTime - lastChange) &amp;lt;= 24 * 60 * 60 * last_n_days;
}

/**

- 输出dir目录，
- 输出结果放在一行里，
- 输出效果是彩色的。
*/
void myPrint(const char *dir) { // output in one line
    struct stat st;
    int ret = lstat(dir, &amp;amp;st);
    handle_error(ret == -1, 2, dir, strerror(errno));

    int dotPosition = -1;
    if (checkDot(dir) &amp;amp;&amp;amp; checkSize(st.st_size) &amp;amp;&amp;amp; checkDate(st.st_ctim)) {
        switch (st.st_mode &amp;amp; S_IFMT) {
        case S_IFBLK:
            printf(&quot;\033[32m%15d %s\n&quot;, st.st_size, dir); // block device
            break;
        case S_IFCHR:
            printf(&quot;\033[33m%15d %s\n&quot;, st.st_size, dir); // character device
            break;
        case S_IFDIR:
            printf(&quot;\033[95m%15d %s\n&quot;, st.st_size, dir); // directory
            break;
        case S_IFIFO:
            printf(&quot;\033[32m%15d %s\n&quot;, st.st_size, dir); // FIFO/pipe
            break;
        case S_IFLNK:
            printf(&quot;\033[32m%15d %s\n&quot;, st.st_size, dir); // symlink
            break;
        case S_IFREG:
            printf(&quot;\033[96m%15d %s\n&quot;, st.st_size, dir); // regular file
            break;
        case S_IFSOCK:
            printf(&quot;\033[32m%15d %s\n&quot;, st.st_size, dir); // socket
            break;
        default:
            printf(&quot;\033[32munknown?\n&quot;);
            break;
        }
    }
}

/**

- 输入是一个目录，
- 如果设置了-r标志，需要递归遍历
- 否则只需要输出当前目录
- 这里使用了lstat，而不是stat，可以处理死链接的情况
*/
void myList(const char *dir) {
    DIR *fd = opendir(dir);
    struct dirent *entry;
    while (entry = readdir(fd)) {
        char entry_dir[256];
        sprintf(entry_dir, &quot;%s/%s\0&quot;, dir, entry-&amp;gt;d_name);

        myPrint(entry_dir);

        struct stat st;
        int ret = lstat(entry_dir, &amp;amp;st);
        handle_error(ret == -1, 2, entry_dir, strerror(errno));
        if (S_ISDIR(st.st_mode)) {
            // printf(&quot;%s\n&quot;, entry-&amp;gt;d_name);
            if (strcmp(entry-&amp;gt;d_name, &quot;.&quot;) != 0 &amp;amp;&amp;amp;
                strcmp(entry-&amp;gt;d_name, &quot;..&quot;) != 0 &amp;amp;&amp;amp; flag_recurse) {
                myList(entry_dir);
            }
        }
    }
    closedir(fd);
}

/**

- 初始化传入的参数
*/
void init(int argc, char **argv) {
    for (int i = 1; i &amp;lt; argc; ++i) {
        if (strcmp(argv[i], &quot;--&quot;) == 0) {
            break;
        } else if (strcmp(argv[i], &quot;-r&quot;) == 0) {
            flag_recurse = 1;
        } else if (strcmp(argv[i], &quot;-a&quot;) == 0) {
            flag_show_hidden = 1;
        } else if (strcmp(argv[i], &quot;-l&quot;) == 0) {
            sscanf(argv[i + 1], &quot;%d&quot;, &amp;amp;min_size);
            handle_error(min_size &amp;lt; 0, 1,
                         &quot;The minimum value cannot be less than 0!&quot;);
            ++i;
        } else if (strcmp(argv[i], &quot;-h&quot;) == 0) {
            sscanf(argv[i + 1], &quot;%d&quot;, &amp;amp;max_size);
            handle_error(max_size &amp;lt; 0, 1,
                         &quot;The maximum value cannot be less than 0!&quot;);
            ++i;
        } else if (strcmp(argv[i], &quot;-m&quot;) == 0) {
            sscanf(argv[i + 1], &quot;%d&quot;, &amp;amp;last_n_days);
            handle_error(last_n_days &amp;lt; 0, 1,
                         &quot;The time value cannot be less than 0!&quot;);
            ++i;
        } else {
            target[cntTarget++] = argv[i];
        }
    }
    if (cntTarget == 0) {
        target[cntTarget++] = current_dir;
    }
}
int main(int argc, char **argv) {
    init(argc, argv);
    for (int i = 0; i &amp;lt; cntTarget; ++i) {
        printf(&quot;\033[93mSearching  %s\n&quot;, target[i]);
        struct stat st;
        int ret = lstat(target[i], &amp;amp;st);
        handle_error(ret == -1, 2, target[i], strerror(errno));
        if (S_ISDIR(st.st_mode)) {
            myList(target[i]);
        } else {
            myPrint(target[i]);
        }
    }
    return 0;
}

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>c++23的std::print简单示例</title><link>https://dashuai009.github.io/blog/042/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/042/</guid><pubDate>Thu, 18 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2023,
month: 5,
day: 18,
)
#metadata((
title: &quot;c++23的std::print简单示例&quot;,
subtitle: [c++23],
author: &quot;dashuai009&quot;,
description: &quot;&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;== 介绍
cpp23中加入了一个标准库std::print，类似python中的print。据说不是基于std::cout，性能应该有保障。&lt;/p&gt;
&lt;p&gt;== 支持的编译器
目前只有msvc支持了这一标准库。在2023/05/17发布的vs2022 17.7.0 preview1中，加入了#link(&quot;https://github.com/microsoft/STL/pull/3337&quot;)[相关代码]。&lt;/p&gt;
&lt;p&gt;== 示例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import std;

int main() {
    std::vector test{ 10, 2, 3 };
    std::ranges::sort(test);
    for (auto const&amp;amp; [index, value] : std::ranges::enumerate_view(test)) {
        std::println(&quot;index = {}, val = {}&quot;, index, value);
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;index = 0, val = 2

index = 1, val = 3

index = 2, val = 10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== 看起来不错
看起来有点pythonic&lt;/p&gt;
</content:encoded></item><item><title>[USACO Feb. Gold] Problem1. EQUAL SUM SUBARRAYS</title><link>https://dashuai009.github.io/blog/041/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/041/</guid><pubDate>Fri, 03 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2023,
month: 3,
day: 3,
)
#metadata((
title: &quot;[USACO Feb. Gold] Problem1. EQUAL SUM SUBARRAYS&quot;,
subtitle: [usaco],
author: &quot;dashuai009&quot;,
description: &quot;&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;== 题目链接
&amp;lt;题目链接&amp;gt;
#link(&quot;http://www.usaco.org/index.php?page=viewproblem2&amp;amp;cpid=1305&quot;)[USACO]&lt;/p&gt;
&lt;p&gt;== 题目描述
&amp;lt;题目描述&amp;gt;
有一个长度为$N$的数组$a$，a的$frac(N (N + 1), 2)$的连续子序列的和各不相同。对于每一个$i$，输出“最少需修改（增大或减少）$a_i$，使得$a$的两个连续子序列的和相等”。&lt;/p&gt;
&lt;p&gt;== 题目解析
&amp;lt;题目解析&amp;gt;
首先求出$frac(N (N + 1), 2)$个子数组的和$s_i$，进行排序。每个和对应一个子数组。
对于每一个$s_i$，对应于原数组的区间$[l_i , s_i]$，如果通过最少修改$s_i$，让$s_i$等于另一个$s_j$，则$s_j$只能是$s_(i - 1)$或$s_(i + 1)$。维护结果数组$r e s_i$，初始化为最大值，对于每个$s_i$，用$min { lr(|s_i - s_(i - 1)|) , lr(|s_i - s_(i + 1)|) }$去更新$[l_i , r_i]$。最后输出结果即可。&lt;/p&gt;
</content:encoded></item><item><title>UE4模块系统</title><link>https://dashuai009.github.io/blog/040/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/040/</guid><description>详细介绍ue中的模块系统</description><pubDate>Mon, 28 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 11,
day: 28,
)
#metadata((
title: &quot;UE4模块系统&quot;,
subtitle: [UnrealEngine],
author: &quot;dashuai009&quot;,
description: &quot;详细介绍ue中的模块系统&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;参考资料&lt;/p&gt;
&lt;p&gt;#link(&quot;https://www.youtube.com/watch?v=DqqQ_wiWYOw&quot;)[UE4 Modules - YouTube]&lt;/p&gt;
&lt;p&gt;#link(&quot;https://docs.unrealengine.com/4.27/zh-CN/ProductionPipelines/BuildTools/UnrealBuildTool/&quot;)[虚幻编译工具 | 虚幻引擎文档 (unrealengine.com)]&lt;/p&gt;
&lt;p&gt;== 简介
&amp;lt;简介&amp;gt;
众所周知，c++17及之前中没有modules概念。模块化的代码的优势有#strong[代码分割];、#strong[代码重用];和#strong[加快编译速度];等。&lt;/p&gt;
&lt;p&gt;ue中每个模块都被编译为一个dll，据传ue源代码中有一千多个模块~
ue使用c#编译构建源代码，而不是vs的sln文件。sln仅仅是为了vs方便。所以，如果新建的模块写进C#构建文件后，ubt就可以编译构建，即使sln中不显示新建的ue模块。每次更改.build.cs或者更改了文件夹结构，为了生成sln文件，双击项目目录下的GenerateProject.bat。&lt;/p&gt;
&lt;p&gt;== 创建一个模块
&amp;lt;创建一个模块&amp;gt;
每个模块位于一个同名文件夹下，一般来说包含一个Public文件夹、Private文件夹和一个C#构建类。我们创建一个Foo模块，比如说位于Foo文件夹下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Foo
|--Private
|--Public
|--Foo.build.cs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Public包含可供其他模块引用的头文件（导出类），Private包含cpp源文件及私有头文件。
每个.buidl.cs中的模块类需要继承自ModuleRules。Foo.build.cs的最基本的实现如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using UnrealBuildTool;

public class Foo : ModuleRules
{
  public FooBar(ReadOnlyTargetRules Target) : base(Target)
  {
     PrivateDependencyModuleNames.AddRange(new string[] {&quot;Core&quot;});
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== build.cs 中的ModuleRules详解
&amp;lt;build.cs-中的modulerules详解&amp;gt;
=== 依赖dependency
&amp;lt;依赖dependency&amp;gt;
&lt;code&gt;PrivateDependencyModuleNames (List&amp;lt;String&amp;gt;)&lt;/code&gt;
#strong[私有依赖];”：我们的私有代码依赖这些模块，但我们的公共代码不依赖这些模块。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;PublicDependencyModuleNames (List&amp;lt;String&amp;gt;)&lt;/code&gt;
#strong[公共依赖];（不需要路径）（自动执行私有/公共包含）。这些是我们的公共源文件所需要的模块。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;添加到依赖中的模块：添加引用模块的public目录到includePath，链接对应的dll（相应的导出函数、类等被引入）。&lt;/li&gt;
&lt;li&gt;private依赖不会传递导出的符号：A private/public depends on B，B private depends in C，则A不能看到C的头文件，也不会导入C导出的符号。如果A private/public depends on B，B public depends in C，则A能看到C的头文件，但不会导入C导出的符号。也就是说，B可以public的C的头文件，但始终不能转发链接符号。&lt;/li&gt;
&lt;li&gt;如果只是private中的cpp/h依赖了外部模块，建议添加到private dependence中，可以加快编译速度。&lt;/li&gt;
&lt;li&gt;Forward declare when you can so you don’t need to mark something as a public dependency.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;=== 其他othter
&amp;lt;其他othter&amp;gt;
#link(&quot;https://docs.unrealengine.com/4.27/zh-CN/ProductionPipelines/BuildTools/UnrealBuildTool/ModuleFiles/&quot;)[模块 | 虚幻引擎文档 (unrealengine.com)]&lt;/p&gt;
&lt;p&gt;== 实现一个模块
&amp;lt;实现一个模块&amp;gt;
现在我们有了一个Foo.build.cs，我们需要用cpp实现它。&lt;/p&gt;
&lt;p&gt;=== module.h
&amp;lt;module.h&amp;gt;
一般来说，我们要在&lt;code&gt;[YourModuleName]Module.h&lt;/code&gt;中声明如下代码（以Foo模块为例）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &quot;Modules/ModuleInterface.h&quot;

class FFooModule : public IModuleInterface
{
public:
    /**
     * Called right after the plugin DLL has been loaded and the plugin object has been created
     */
    virtual void StartupModule();

    /**
     * Called before the plugin is unloaded, right before the plugin object is destroyed.
     */
    virtual void ShutdownModule();

    void DoFoo();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;FFooModule&lt;/code&gt;必须继承自&lt;code&gt;IModuleInterface&lt;/code&gt;（位于&lt;code&gt;Modules/ModuleInterface.h&lt;/code&gt;）中&lt;/p&gt;
&lt;p&gt;=== module.cpp
&amp;lt;module.cpp&amp;gt;
在&lt;code&gt;private/FooModule.cpp&lt;/code&gt;中实现以下代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &quot;FooModule.h&quot;
#include &quot;Modules/ModuleManager.h&quot;

IMPLEMENT_MODULE( FFooModule, Foo );

Foo::StartupModule(){
    //do sth right after the dll has been loaded and the static object has been created
    //可以在module中定义一个单例，并在这里初始化
}

Foo::ShutdownModule(){
    // 单例在这析构
}

Foor::DoFoo(){
     //自定义函数
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;IMPLEMENT_MODULE( F[YourModule]Module, Foo)&lt;/code&gt;
宏，一个参数是类名（推荐命名方式&lt;code&gt;F[YourModule]Module&lt;/code&gt;，第二个是模块名&lt;/li&gt;
&lt;li&gt;#strong[IMPLEMENT_GAME_MODULE]&lt;/li&gt;
&lt;li&gt;#strong[IMPLEMENT_PRIMARY_GAME_MODULE]&lt;/li&gt;
&lt;li&gt;&quot;Modules/ModuleManager.h&quot; 和 &quot;Modules/ModuleInterface.h&quot;
位于Core模块中，这是我们最少依赖的模块。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;=== 模块的使用方式
&amp;lt;模块的使用方式&amp;gt;
&lt;code&gt;FModuleManager::Get().LoadModuleChecked&amp;lt;FFooModule&amp;gt;(TEXT(&quot;Foo&quot;)).DoFoo();&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;== PCH(precompiled headers)预编译头文件
&amp;lt;pchprecompiled-headers预编译头文件&amp;gt;
#strong[不推荐使用PCH，初学者可以直接跳过这一节。]
仅适用于代码库比较庞大的情况。&lt;/p&gt;
&lt;p&gt;#link(&quot;https://learn.microsoft.com/zh-cn/cpp/build/creating-precompiled-header-files?view=msvc-170&quot;)[预编译的头文件 | Microsoft Learn]&lt;/p&gt;
&lt;p&gt;=== 私有的预编译头文件。
&amp;lt;私有的预编译头文件&amp;gt;
为自己的模块创建的自定义 PCH。使用 PrivatePCHHeaderFile 属性在 Build.cs
文件中定义它。 按照惯例，它应该命名为&lt;code&gt;[your-module-name]PrivatePCH.h&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using UnrealBuildTool;

public class Foo : ModuleRules
{
  public FooBar(ReadOnlyTargetRules Target) : base(Target)
  {
    PrivatePCHHeaderFile = &quot;FooPrivatePCH.h&quot;;//每个模块最多一个pch
    PrivateDependencyModuleNames.AddRange(new string[] {&quot;Core&quot;});
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unreal Build Tool 会自动为模块中的所有编译文件注入它。&lt;/p&gt;
&lt;p&gt;=== 共享的预编译头文件。
&amp;lt;共享的预编译头文件&amp;gt;
共享 PCH
是指一个模块定义了#strong[给其他依赖模块使用的];预编译头文件。模块本身不能使用它。基础虚幻引擎模块中比较常见（UnrealEd, Engine, Slate, CoreUObject, and Core）。只有引擎模块（engine
module可以创建shared PCH）。&lt;/p&gt;
&lt;p&gt;您无法选择使用哪一个，虚幻引擎会根据优先级分数从您的模块依赖项之一中为您选择。该优先级分数按模块排序，取决于它依赖的具有共享PCH的其他模块的数量。这是一个有点奇怪的解释，但上面的模块列表已经按优先级排序。例如，如果您的模块依赖于所有五个模块，Unreal将在编译时选择 UnrealEd 的共享 PCH 用于您的模块。&lt;/p&gt;
&lt;p&gt;=== 那么什么时候使用哪种预编译头类型呢？
&amp;lt;那么什么时候使用哪种预编译头类型呢&amp;gt;
你实际上有三个选择。&lt;/p&gt;
&lt;p&gt;您可以创建自己的私有预编译头文件。&lt;/p&gt;
&lt;p&gt;这对于代码库非常大的模块很有用，大型游戏中的主要游戏模块通常就是这种情况。&lt;/p&gt;
&lt;p&gt;您必须决定在 PCH 中放入什么以及如何自己平衡它。&lt;/p&gt;
&lt;p&gt;您可以使用共享引擎 PCH，这对于所有其他较小的模块来说都是一个不错的选择。&lt;/p&gt;
&lt;p&gt;最后，不推荐使用任何 PCH。&lt;/p&gt;
&lt;p&gt;这不是很实用，我不知道你为什么要这样做，除非你正在做一些极端的编译调试。&lt;/p&gt;
&lt;p&gt;但是你有选择。&lt;/p&gt;
&lt;p&gt;因此，让我们看看您在哪里配置这些 PCH 构建设置。&lt;/p&gt;
&lt;p&gt;=== 在模块的 Build.cs 文件中设置。
&amp;lt;在模块的-build.cs-文件中设置&amp;gt;
有两个相关的设置来配置它&lt;/p&gt;
&lt;p&gt;采用 PCHUsageMode 枚举的 PCHUsage 属性&lt;/p&gt;
&lt;p&gt;当您想使用私有 PCH 时，还可以选择 PrivatePCHHeaderFile 属性&lt;/p&gt;
&lt;p&gt;这只是标题的字符串路径。&lt;/p&gt;
&lt;p&gt;那么，设置PCHUsageMode，我们应该使用哪个设置呢？&lt;/p&gt;
&lt;p&gt;嗯，它比 Enum 看起来要简单一些，因为&lt;/p&gt;
&lt;p&gt;这三个是遗留的，您应该始终选择 UseExplicitOrSharedPCHs。&lt;/p&gt;
&lt;p&gt;该选项默认使用共享 PCH，如果您已通过 PrivatePCHHeaderFile
属性设置，则使用私有 PCH。&lt;/p&gt;
&lt;p&gt;从 4.24.2 开始，它实际上已经是默认设置&lt;/p&gt;
&lt;p&gt;因此，如果您使用的是在该版本或更高版本上创建的项目，那么您甚至不必设置它。&lt;/p&gt;
&lt;p&gt;此设置将来可能会逐步淘汰。&lt;/p&gt;
&lt;p&gt;== Include What You Use，通常缩写为 IWYU
&amp;lt;include-what-you-use通常缩写为-iwyu&amp;gt;
遵循四个原则&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有头文件包含其所需的依赖性。
#strong[CoreMinimal];头文件包含了UE核心编程环境的常见类型（包括FString、FName、TArray等）。这样，在头文件中首先include该文件，就可以引入这些东西。&lt;/li&gt;
&lt;li&gt;Foo.cpp文件首先包含其匹配的Foo.h文件。
否则ubt将发出警告（如要禁用，可在模块的*.build.cs文件中将
&lt;code&gt;bEnforceIWYU&lt;/code&gt; 设为 &lt;code&gt;false&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;PCH文件已不再是显式包含。&lt;/li&gt;
&lt;li&gt;不再包含单块头文件（monolithic header，比如Engine.h、UnrealEd.h）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;=== 启用IWYU
&amp;lt;启用iwyu&amp;gt;
在version&amp;lt;=4.23中，IWYU默认不启用，在 .Build.cs 文件中将 PCHMode
属性设置为 UseExplicitOrSharedPCHs 来打开 IWYU。&lt;/p&gt;
&lt;p&gt;在version&amp;gt;=4.24.2中，默认启用。具体来说，默认启用是通过#strong[DefaultBuildSettings
= BuildSettingsVersion.V2];设置的。该选项做了三件事&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PCHUsage gets set to PCHUsageMode.UseExplicitOrSharedPCHs;&lt;/li&gt;
&lt;li&gt;#strong[bLegacyPublicIncludePaths] 被设为false，虚幻构建工具会从公共包含路径中省略子文件夹，以减少编译器命令行长度并缩短编译时间。现在必须让每个包含路径都正确。之前你可以只包含Actor.h，现在你必须包含GameFramework/Actor.h。#link(&quot;https://docs.unrealengine.com/4.27/zh-CN/ProductionPipelines/BuildTools/UnrealBuildTool/IWYU/IncludeTool/&quot;)[包含工具 | 虚幻引擎文档 (unrealengine.com)]&lt;/li&gt;
&lt;li&gt;#strong[ShadowVariableWarningLevel gets set to WarningLevel.Error;]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;=== 模块日志
&amp;lt;模块日志&amp;gt;
=== 声明模块日志的类别：
&amp;lt;声明模块日志的类别&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DECLARE_LOG_CATEGORY_EXTERN(CategoryName, DefaultVerbosity, CompileTimeVerbosity);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Commonly (Log[ModuleName], Display, All), see Logging/LogVerbosity.h
for more.&lt;/li&gt;
&lt;li&gt;Declares a category class that extends FLogCategory.&lt;/li&gt;
&lt;li&gt;Most practical to put it in its own header file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;=== 定义模块日志的类别（方便过滤）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DEFINE_LOG_CATEGORY(CategoryName);&lt;/li&gt;
&lt;li&gt;Instantiates an instance of that log category class, which registers
itself with the log suppression system in the constructor.&lt;/li&gt;
&lt;li&gt;Put it in the same place where you called IMPLEMENT_MODULE.
=== 使用模块日志&lt;/li&gt;
&lt;li&gt;UE_LOG(Log[ModuleName] Display, TEXT(&quot;A wild log appeared!&quot;));&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>cmake解决visual studio 运行时库链接错误</title><link>https://dashuai009.github.io/blog/039/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/039/</guid><description>在visual studio中，连接其他库时会遇到runtime library连接错误，cmake可以设置该参数</description><pubDate>Sat, 12 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 11,
day: 12,
)
#metadata((
title: &quot;cmake解决visual studio 运行时库链接错误&quot;,
subtitle: [visual stduio],
author: &quot;dashuai009&quot;,
description: &quot;在visual studio中，连接其他库时会遇到runtime library连接错误，cmake可以设置该参数&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;== 错误信息
&amp;lt;错误信息&amp;gt;&lt;/p&gt;
&lt;p&gt;Error LNK2038 mismatch detected for &apos;RuntimeLibrary&apos;: value
&apos;MT_StaticRelease&apos; doesn’t match value &apos;MD_DynamicRelease&apos; in file.obj&lt;/p&gt;
&lt;p&gt;在vs中，链接其他库时会遇到这种错误，这是运行时库的版本没有和已有项目对齐。&lt;/p&gt;
&lt;p&gt;== RuntimeLibrary是什么
&amp;lt;runtimelibrary是什么&amp;gt;&lt;/p&gt;
&lt;p&gt;CRT：C Runtime Library
，C语言运行时库，系统自动为程序加载该库，以便访问C标准函数。同样，C++也有类似的东西。&lt;/p&gt;
&lt;p&gt;该库有动态和静态两个版本，现在windows推荐使用动态库。之前还分单线程和线程两种，现在只推荐多线程库。同时，根据是否能调试，总共分为四种库：#strong[多线程静态库，多线程动态库，多线程静态库debug版，多线程动态库debug版];。在vs中通过编译选项控制。&lt;/p&gt;
&lt;p&gt;作用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;提供C标准库(如memcpy、printf、malloc等)、C++标准库（STL）的支持。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;应用程序添加启动函数，启动函数的主要功能为将要进行的程序初始化，对全局变量进行赋初值，加载用户程序的入口函数。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;== 在sln中设置runtimelibrary
&amp;lt;在sln中设置runtimelibrary&amp;gt;
项目-&amp;gt; properties -&amp;gt; c/c++ -&amp;gt; code generation -&amp;gt;
改为Multi-threaded(/MT)&lt;/p&gt;
&lt;p&gt;#quote[
我这边的主项目没法修改，默认是/MT。只能把第三方的库编译时选则#strong[静态多线程 运行时库];。这样就能接入主项目。
]&lt;/p&gt;
&lt;p&gt;== 在cmake中设置runtimelibrary
&amp;lt;在cmake中设置runtimelibrary&amp;gt;
cmake中提供了一个变量#link(&quot;https://cmake.org/cmake/help/latest/variable/CMAKE_MSVC_RUNTIME_LIBRARY.html&quot;)[CMAKE_MSVC_RUNTIME_LIBRARY];可以设置该值。该值有四种值可选&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MultiThreaded ，-MT选项&lt;/li&gt;
&lt;li&gt;MultiThreadedDLL，-MD选项&lt;/li&gt;
&lt;li&gt;MultiThreadedDebug ，-MTd选项&lt;/li&gt;
&lt;li&gt;MultiThreadedDebugDLL，-MDd选项&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果设置不正确，会报错（即使是在其他平台下）。 设置方法如下：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;set(CMAKE_MSVC_RUNTIME_LIBRARY &quot;MultiThreaded$&amp;lt;$&amp;lt;CONFIG:Debug&amp;gt;:Debug&amp;gt;&quot;)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;（ 图不放了，很好找&lt;/p&gt;
&lt;p&gt;这样在debug/release里使用不同的配置，，在我的问题中，写死&lt;code&gt;set(CMAKE_MSVC_RUNTIME_LIBRARY &quot;MultiThreaded&quot;)&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;== note！！！！！！！！
&amp;lt;note&amp;gt;
上边的都可以不看，这个必须注意&lt;/p&gt;
&lt;p&gt;#quote[
Note: This variable has effect only when policy CMP0091 is set to NEW prior to the first project() or enable_language() command that enables a language using a compiler targeting the MSVC ABI.
]&lt;/p&gt;
&lt;p&gt;该值必须在第一个project()或enable_language()#strong[之前]
设置规则&lt;code&gt;CMP0091&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;也就是说，想要设置该规则，必须在顶级项目（子项目中不行，为了统一所有项目的该值）之前启用该规则。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cmake_mininum_required(VERSION 3.15.0)
if(POLICY CMP0091) # 检测是否可用，也就是cmake的版本是否大于等于3.15
 cmake_policy(SET CMP0091 NEW)
endif()
project(balabala)

if (CMAKE_SYSTEM_NAME  matches &quot;Windows&quot;)
 set(CMAKE_MSVC_RUNTIME_LIBRARY &quot;MultiThreaded&quot;)
endif()
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>windows cmd切换盘符</title><link>https://dashuai009.github.io/blog/038/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/038/</guid><description>在windwos里，cmd命令行中，直接输入D:回车，就可以切换盘符。</description><pubDate>Fri, 04 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 11,
day: 4,
)
#metadata((
title: &quot;windows cmd切换盘符&quot;,
subtitle: [cmd],
author: &quot;dashuai009&quot;,
description: &quot;在windwos里，cmd命令行中，直接输入D:回车，就可以切换盘符。&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;在windwos里，cmd命令行中，直接输入D:回车，就可以切换盘符。&lt;/p&gt;
&lt;p&gt;之前在win10里死活cd不进D盘，powershell应该没有这种问题。win11没试过。&lt;/p&gt;
&lt;p&gt;注意别忘了D后边的冒号。&lt;/p&gt;
</content:encoded></item><item><title>cmake对c++20 module的支持</title><link>https://dashuai009.github.io/blog/037/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/037/</guid><description>在cmake中，通过target_source增加了对c++20 module特性的支持。到了3.25版本，这一命令得到大幅改进。看起来是对c++20 module做了应有的支持（编译器不支持的语法，cmake也没办法）。</description><pubDate>Tue, 25 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 10,
day: 25,
)
#metadata((
title: &quot;cmake对c++20 module的支持&quot;,
subtitle: [c++20,modules,cmake],
author: &quot;dashuai009&quot;,
description: &quot;在cmake中，通过target_source增加了对c++20 module特性的支持。到了3.25版本，这一命令得到大幅改进。看起来是对c++20 module做了应有的支持（编译器不支持的语法，cmake也没办法）。&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;== 相关链接
&amp;lt;相关链接&amp;gt;
#link(&quot;https://zhuanlan.zhihu.com/p/350136757&quot;)[知乎 孙孟越]
介绍比较详细了，还介绍了 C++ 之父介绍了 modules 的发展进程.&lt;/p&gt;
&lt;p&gt;#link(&quot;https://github.com/royjacobson/modules-report&quot;)[github modules-reportmodules-report]&lt;/p&gt;
&lt;p&gt;#link(&quot;https://cmake.org/cmake/help/latest/command/target_sources.html&quot;)[cmake target_sources]&lt;/p&gt;
&lt;p&gt;#link(&quot;https://www.youtube.com/watch?v=hkefPcWySzI&quot;)[target_sources CMake 2022 C++ Modules and More - Bill Hoffman - CppNow 2022]
22年7月就分享了如何支持module,一直到10月份才出来。&lt;/p&gt;
&lt;p&gt;== target_sources
&amp;lt;target_sources&amp;gt;
简单示例，上边youtube中的。&lt;/p&gt;
&lt;p&gt;#figure(
image(&quot;037/cmake.png&quot;),
caption: [
cmake target_source
],
)&lt;/p&gt;
&lt;p&gt;== demo
&amp;lt;demo&amp;gt;
#link(&quot;https://www.kitware.com/import-cmake-c20-modules/&quot;)[import CMake; C++20 Modules (kitware.com)]&lt;/p&gt;
&lt;p&gt;上边是cmake官方给出的例子，比较简单。&lt;/p&gt;
&lt;p&gt;我在vs2022 preview 17.6、cmake3.26下测试，模块分区（module internal
partition）还不被支持。 目前来说（2023.4）c++20的构建系统还不是很完善。&lt;/p&gt;
</content:encoded></item><item><title>2020ccpc-Weihai威海-D-ABC_Conjecture</title><link>https://dashuai009.github.io/blog/001/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/001/</guid><pubDate>Tue, 04 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 10,
day: 4,
)
#metadata((
&quot;title&quot;: &quot;2020ccpc-Weihai威海-D-ABC_Conjecture&quot;,
&quot;author&quot;: &quot;dashuai009&quot;,
description: &quot;&quot;,
pubDate: date.display(),
subtitle: [CCPC, 数论],
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;#date.display();&lt;/p&gt;
&lt;p&gt;// #outline()&lt;/p&gt;
&lt;p&gt;= 题意&lt;/p&gt;
&lt;p&gt;Given a positive integer c, determine if there exists positive integers a, b, such that a + b = c and rad(abc) &amp;lt; c.&lt;/p&gt;
&lt;p&gt;where&lt;/p&gt;
&lt;p&gt;$ &quot;rad&quot; (n) = product_(p divides n\ p in upright(&quot;Prime&quot;)) p $&lt;/p&gt;
&lt;p&gt;is the product of all distinct prime divisors of n.&lt;/p&gt;
&lt;p&gt;== 数据范围&lt;/p&gt;
&lt;p&gt;$ 1 lt.eq.slant T lt.eq.slant 10, 1 lt.eq.slant c lt.eq.slant 10^(18) $&lt;/p&gt;
&lt;p&gt;= 解析&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;c=1
输出no&lt;/li&gt;
&lt;li&gt;c的质因子的幂均为1, $mu(c)=-1$
输出no&lt;/li&gt;
&lt;li&gt;否则，c的质因子中存在一个幂大于等于2的，输出yes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$
c=p^2k, a=p k, b=p(p-1)k,&lt;br /&gt;
a+b=c&lt;br /&gt;
a b c= p^4 (p-1) k^3&lt;br /&gt;
&quot;rad&quot;(a b c) = &quot;rad&quot;(p^4 (p-1) k^3) =&quot;rad&quot;(p(p-1)k) lt.eq (p-1)k &amp;lt; c
$&lt;/p&gt;
&lt;p&gt;以上，先筛掉$c$的$10^6$之内的质因子p，如果有输出yes;
否则，$c=p^2$
k中k只能为1，这样判断一下$sqrt(c)$是不是素数即可。&lt;/p&gt;
&lt;p&gt;== code&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;bits/stdc++.h&amp;gt;
#define LL long long
const int N = 1e6 + 20;

LL prime[N];
bool bo[N];
int pcnt;

namespace MillerRabin {
long long Mul(long long a, long long b, long long mo) {
    long long tmp = a * b - (long long)((long double)a / mo * b + 1e-8) * mo;
    return (tmp % mo + mo) % mo;
}

long long Pow(long long a, long long b, long long mo) {
    long long res = 1;
    for (; b; b &amp;gt;&amp;gt;= 1, a = Mul(a, a, mo))
        if (b &amp;amp; 1)
            res = Mul(res, a, mo);
    return res;
}

bool IsPrime(long long n) {
    if (n == 2)
        return 1;
    if (n &amp;lt; 2 || !(n &amp;amp; 1))
        return 0;
    static const auto tester = {2, 3, 5, 7, 11, 13, 17, 19, 23};
    long long x = n - 1;
    int t = 0;
    for (; !(x &amp;amp; 1); x &amp;gt;&amp;gt;= 1)
        ++t;
    for (int p : tester) {
        long long a = p % (n - 1) + 1, res = Pow(a % n, x, n), last = res;
        for (int j = 1; j &amp;lt;= t; ++j) {
            res = Mul(res, res, n);
            if (res == 1 &amp;amp;&amp;amp; last != 1 &amp;amp;&amp;amp; last != n - 1)
                return 0;
            last = res;
        }
        if (res != 1)
            return 0;
    }
    return 1;
}
} // namespace MillerRabin

namespace PollardRho {
using namespace MillerRabin;
unsigned long long seed;

long long Rand(long long mo) { return (seed += 4179340454199820289ll) % mo; }

long long F(long long x, long long c, long long mo) {
    return (Mul(x, x, mo) + c) % mo;
}

long long gcd(long long a, long long b) { return b ? gcd(b, a % b) : a; }

long long Get(long long c, long long n) {
    long long x = Rand(n), y = F(x, c, n), p = n;
    for (; x != y &amp;amp;&amp;amp; (p == n || p == 1);
         x = F(x, c, n), y = F(F(y, c, n), c, n))
        p = x &amp;gt; y ? gcd(n, x - y) : gcd(n, y - x);
    return p;
}

void Divide(long long n, long long p[]) {
    if (n &amp;lt; 2)
        return;
    if (IsPrime(n)) {
        p[++*p] = n;
        return;
    }
    for (;;) {
        long long tmp = Get(Rand(n - 1) + 1, n);
        if (tmp != 1 &amp;amp;&amp;amp; tmp != n) {
            Divide(tmp, p);
            Divide(n / tmp, p);
            return;
        }
    }
}
} // namespace PollardRho
void makePrime() {
    for (int i = 2; i &amp;lt; N; ++i) {
        if (!bo[i]) {
            prime[++pcnt] = i;
        }
        for (int j = 1; j &amp;lt;= pcnt &amp;amp;&amp;amp; i * prime[j] &amp;lt; N; ++j) {
            bo[i * prime[j]] = true;
            if (i % prime[j]) {
                break;
            }
        }
    }
}

int main() {
    makePrime();
    int T;
    std::cin &amp;gt;&amp;gt; T;
    while (T--) {
        LL c;
        std::cin &amp;gt;&amp;gt; c;
        if (c == 1) {
            std::cout &amp;lt;&amp;lt; &quot;no\n&quot;;
        } else {
            bool flag = false;
            for (int i = 1; i &amp;lt;= pcnt; ++i) {
                int cnt = 0;
                while (c % prime[i] == 0) {
                    ++cnt;
                    c /= prime[i];
                }
                if (cnt &amp;gt; 1) {
                    flag = true;
                    break;
                }
            }
            if (flag) {
                std::cout &amp;lt;&amp;lt; &quot;yes\n&quot;;
            } else {
                long long p = sqrt(c);
                if (p * p == c &amp;amp;&amp;amp; MillerRabin::IsPrime(p)) {
                    std::cout &amp;lt;&amp;lt; &quot;yes\n&quot;;
                } else {
                    std::cout &amp;lt;&amp;lt; &quot;no\n&quot;;
                }
            }
        }
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>梅森素数与完全数</title><link>https://dashuai009.github.io/blog/036/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/036/</guid><description>什么是梅森素数，欧几里得完全数公式，$delta$函数的定义，欧拉完全数定理</description><pubDate>Sun, 02 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 10,
day: 2,
)
#metadata((
title: &quot;梅森素数与完全数&quot;,
subtitle: [数论],
author: &quot;dashuai009&quot;,
description: &quot;什么是梅森素数，欧几里得完全数公式，$delta$函数的定义，欧拉完全数定理&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;== 引入梅森素数
&amp;lt;引入梅森素数&amp;gt;
#quote[
对于整数$a gt.eq 2 , n gt.eq 2$， 若$a^n - 1$ 是素数，则$a = 2 , n$
为素数。
]&lt;/p&gt;
&lt;p&gt;注意反之不一定成立。&lt;/p&gt;
&lt;p&gt;注意到，几何级数求和公式
$ a^n - 1 = (a - 1) (a^(n - 1) + a^(n - 2) + dots.h + a + 1) $&lt;/p&gt;
&lt;p&gt;若a&amp;gt;2，则$a - 1 &amp;gt; 1 , a - 1 | (a^n - 1)$，与$a^n - 1$是素数冲突。&lt;/p&gt;
&lt;p&gt;若n为合数，假设$n = m k$，由上式，&lt;/p&gt;
&lt;p&gt;$
a^n - 1 &amp;amp; = (a^m)^k - 1&lt;br /&gt;
&amp;amp; = ((a^m) - 1) ((a^m)^(k - 1) + dots.h + a^m + 1)
$&lt;/p&gt;
&lt;p&gt;所以$a^m - 1 &amp;gt; 1 , a^m - 1 | (a^n - 1)$，冲突。&lt;/p&gt;
&lt;p&gt;得证。&lt;/p&gt;
&lt;p&gt;#quote[
对于整数$a gt.eq 2 , n gt.eq 2$， 若$a^n + 1$ 是素数，则$n$ 为2的幂次。
]&lt;/p&gt;
&lt;p&gt;证明类似，用到$a^n+1$的公式。&lt;/p&gt;
&lt;p&gt;== 梅森素数&lt;/p&gt;
&lt;p&gt;我们将形容如$a^p - 1$的素数称为梅森素数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;有无穷多个梅森素数吗？现在仍不知道&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;== 什么是完全数&lt;/p&gt;
&lt;p&gt;#quote[
真因数之和等于他本身。
]&lt;/p&gt;
&lt;p&gt;比如6=1+2+3，28=1+2+4+7+14&lt;/p&gt;
&lt;p&gt;== 欧几里得完全数公式&lt;/p&gt;
&lt;p&gt;#quote[
如果$2^p - 1$是素数 ， 则$2^(p - 1) (2^p - 1)$是完全数
]&lt;/p&gt;
&lt;p&gt;== $sigma$函数&lt;/p&gt;
&lt;p&gt;=== 定义
$ sigma (n) = n 的 所 有 因 数 之 和 （ 包 括 1 和 n ） $&lt;/p&gt;
&lt;p&gt;=== 性质
&amp;lt;性质&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果p是素数，$k gt.eq 1$，则&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$ sigma (p^k) = sum_(i = 0)^n p^i = frac(p^(k + 1) - 1, p - 1) $&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果$gcd (m , n) = 1$，则$sigma (m n) = sigma (n) sigma (m)$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;=== 证明&lt;/p&gt;
&lt;p&gt;瞪眼法&lt;/p&gt;
&lt;p&gt;== 欧拉完全数定理
&amp;lt;欧拉完全数定理&amp;gt;
如果n是偶完全数，则n形如$n = 2^(p - 1) (2^p - 1)$，其中
$2^p - 1$是梅森素数。&lt;/p&gt;
&lt;p&gt;=== 证明
&amp;lt;证明&amp;gt;
将偶数中的2都分解出来，则$n = 2^k m$，其中$k gt.eq 1$且m是奇数。则
$
sigma (n) = &amp;amp; sigma (2^k m)&lt;br /&gt;
= &amp;amp; sigma (2^k) sigma (m)&lt;br /&gt;
= &amp;amp; (2^(k + 1) - 1) sigma (m)
$&lt;/p&gt;
&lt;p&gt;又n为完全数，则$sigma (n) = 2 n = 2^(k + 1) m$。所以，&lt;/p&gt;
&lt;p&gt;$ 2^(k + 1) m = (2^(k + 1) - 1) sigma (m) $&lt;/p&gt;
&lt;p&gt;即$2^(k + 1) | (2^(k + 1) - 1) sigma (m)$，
即$2^(k + 1) | sigma (m)$。
也就是说存在整倍数c，使得$sigma (m) = 2^(k + 1) c$。&lt;/p&gt;
&lt;p&gt;#set math.equation(numbering: &quot;(1)&quot;)
即 $
2^{k+1}m=(2^{k+1}-1)\sigma(m)=(2^{k+1}-1)2^{k+1}c
$&lt;/p&gt;
&lt;p&gt;即&lt;/p&gt;
&lt;p&gt;$ m = (2^(k + 1) - 1) c med 且 med sigma (m) = 2^(k + 1) c $&lt;/p&gt;
&lt;p&gt;下面，我们来证明c=1.反证法，先假设c&amp;gt;1。&lt;/p&gt;
&lt;p&gt;则m至少有三个不同的因数$1 , c , m$。
$ sigma (m) gt.eq 1 + c + m = 1 + c + (2^(k + 1) - 1) c = 1 + 2^(k + 1) c $&lt;/p&gt;
&lt;p&gt;又由(1)得， $ 2^(k + 1) c gt.eq 1 + 2^(k + 1) c $
显然，这是荒谬的。故假设不成立，c因该等于1 。 这样，我们得知
$ m = (2^(k + 1) - 1) ， 且 sigma (m) = 2^(k + 1) = 1 + m $&lt;/p&gt;
&lt;p&gt;后半部分说明m为质数（因数只有1，m）。 所以，如果n为偶完全数，则
$ n = 2^k (2^(k + 1) - 1) ， 其 中 2^(k + 1) - 1 是 素 数 。 $
。再由梅森素数的定义，原定理得证。&lt;/p&gt;
&lt;p&gt;== 奇完全数&lt;/p&gt;
&lt;p&gt;存在吗？现在不知。&lt;/p&gt;
&lt;p&gt;== 定理&lt;/p&gt;
&lt;p&gt;设 $p$ 和 $q$ 是奇素数。若 $p$ 整除 $M_q$ ，则 $p e.not 1 (mod p)$ 且 $p eq.not plus.minus 1 (mod 8)$。&lt;/p&gt;
&lt;p&gt;=== 证明&lt;/p&gt;
&lt;p&gt;== Pollard&apos;s p - 1 algorithm&lt;/p&gt;
</content:encoded></item><item><title>linux缺页中断次数统计</title><link>https://dashuai009.github.io/blog/006/006/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/006/006/</guid><pubDate>Fri, 09 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 9,
day: 9,
)
#metadata((
&quot;title&quot;: &quot;linux缺页中断次数统计&quot;,
&quot;author&quot;: &quot;dashuai009&quot;,
description: &quot;&quot;,
pubDate: date.display(),
subtitle: [linux,缺页中断],
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;#date.display();
#outline()&lt;/p&gt;
&lt;p&gt;= 描述&lt;/p&gt;
&lt;p&gt;修改内核源代码，使得内核自启动以后，统计发生缺页的次数。&lt;/p&gt;
&lt;p&gt;编写程序，输出内核启动时间，和内核统计到的缺页中断次数。&lt;/p&gt;
&lt;p&gt;= 实验环境&lt;/p&gt;
&lt;p&gt;&lt;code&gt;openeuler20.03LTS  qemu x86_64&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;= 统计次数&lt;/p&gt;
&lt;p&gt;== 声明计数变量&lt;/p&gt;
&lt;p&gt;在内核文件arch/x86/mm/fault.c中定义了&lt;code&gt;do_page_fault()&lt;/code&gt;函数，这是操作系统处理缺页中断的入口。&lt;/p&gt;
&lt;p&gt;只需要在这里添加一个计数变量&lt;code&gt;unsigned long pfcount&lt;/code&gt;，并在每次执行&lt;code&gt;do_page_fault()&lt;/code&gt;时，使计数变量加一。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog-picture-1305172231.cos.ap-beijing.myqcloud.com/6/2021-05-07%2009-19-22%20%E7%9A%84%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE.png&quot; alt=&quot;声明计数变量pfcount&quot; /&gt;&lt;/p&gt;
&lt;p&gt;== 声明为全局变量&lt;/p&gt;
&lt;p&gt;在内核文件include/linux/mm.h中，将&lt;code&gt;pfcount&lt;/code&gt;声明为全局变量。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog-picture-1305172231.cos.ap-beijing.myqcloud.com/6/2021-05-07%2009-20-56%20%E7%9A%84%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE.png&quot; alt=&quot;pfcount&quot; /&gt;&lt;/p&gt;
&lt;p&gt;== 导出到内核变量全局符号表中&lt;/p&gt;
&lt;p&gt;在内核文件kernel/kallsyms.c中，最后一行添加&lt;code&gt;EXPORT_SYMBOL_GPL(pfcount)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog-picture-1305172231.cos.ap-beijing.myqcloud.com/6/2021-05-07%2009-21-44%20%E7%9A%84%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE.png&quot; alt=&quot;EXPORT_SYMBOL_GPL&quot; /&gt;&lt;/p&gt;
&lt;p&gt;EXPORT_SYMBOL_GPL 导出的符号只能被GPL协议的模块读取。
= 重新编译内核&lt;/p&gt;
&lt;p&gt;&lt;code&gt;make &amp;amp; make modules_install &amp;amp; make install&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这一步真是一言难尽～～之前三步的修改会导致&lt;em&gt;整个内核几乎重新编译一遍。&lt;/em&gt;（修改的内容太底层，不是模块方式加载进去）&lt;/p&gt;
&lt;p&gt;= 编写模块输出统计变量&lt;/p&gt;
&lt;p&gt;编写如下pf.c文件&lt;/p&gt;
&lt;p&gt;这里将当前日期和缺页次数，输出到了/proc/pfcount。这里用到了/proc文件系统。之后可以直接通过&lt;code&gt;cat /proc/pfcount&lt;/code&gt;直接查看当前的信息。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;linux/fs.h&amp;gt;
#include &amp;lt;linux/mm.h&amp;gt;
#include &amp;lt;linux/proc_fs.h&amp;gt;
#include &amp;lt;linux/rtc.h&amp;gt;
#include &amp;lt;linux/time.h&amp;gt;
#include &amp;lt;linux/module.h&amp;gt;
#define BUFSIZE 512
MODULE_LICENSE(&quot;GPL&quot;);

struct timeval tv;
struct rtc_time tm;

static struct proc_dir_entry *pf;

static ssize_t read_pfcount(struct file *f, char __user *ubuf, size_t count,
                            loff_t *ppos) {

        char buf[BUFSIZE];
        int len=0;
    int year, mon, day, hour, min, sec;
        //printk( KERN_DEBUG &quot;read handler\n&quot;);
        if(*ppos &amp;gt; 0 || count &amp;lt; BUFSIZE)
                return 0;

    do_gettimeofday(&amp;amp;tv);
    rtc_time_to_tm(tv.tv_sec, &amp;amp;tm);
    year = tm.tm_year + 1900;
    mon = tm.tm_mon + 1;
    day = tm.tm_mday;
    hour = tm.tm_hour + 8;
    min = tm.tm_min;
    sec = tm.tm_sec;
    len=sprintf(buf,&quot;Current time: %d-%02d-%02d %02d:%02d:%02d pfcount = %ld\n&quot;, year,
           mon, day, hour, min, sec, pfcount);

        if(copy_to_user(ubuf,buf,len))
                return -EFAULT;
        *ppos = len;
    return len;
}
static ssize_t write_pfcount(struct file *f, const char __user *buf, size_t len,
                             loff_t *off) {
    printk(KERN_DEBUG &quot;test\n&quot;);
    return -1;
}
static const struct file_operations myProc = {
    .owner = THIS_MODULE,
    .write = write_pfcount,
    .read = read_pfcount,
};

static int __init myproc_init(void) {
    printk(&quot;Start pf modules...\n&quot;);

    pf = proc_create(&quot;pfcount&quot;, 0660, NULL, &amp;amp;myProc);
    if (pf == NULL)
        return -ENOMEM;
    return 0;
}

static void __exit myproc_exit(void) {
    printk(&quot;Exit pf module...\n&quot;);
    proc_remove(pf);
}

module_init(myproc_init);
module_exit(myproc_exit);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;= 加载并执行模块&lt;/p&gt;
&lt;p&gt;执行结果&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog-picture-1305172231.cos.ap-beijing.myqcloud.com/6/2021-05-07%2009-32-18%20%E7%9A%84%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE.png&quot; alt=&quot;执行结果&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到系统在09:22:17~09:31:13的536秒中，缺页中断发生了2826616-1693154=1133462次。&lt;/p&gt;
&lt;p&gt;= 本次实验的缺点&lt;/p&gt;
&lt;p&gt;== volatile&lt;/p&gt;
&lt;p&gt;有地方这样写声明全局变量
&lt;code&gt;extern unsigned long volatile pfcount;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.runoob.com/w3cnote/c-volatile-keyword.html&quot;&gt;volatile 的作用&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我这里没有加volatile关键字，不知道有没有影响。&lt;s&gt;（编译一次太费劲了）&lt;/s&gt;&lt;/p&gt;
</content:encoded></item><item><title>使用g++编译cpp20的module文件</title><link>https://dashuai009.github.io/blog/035/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/035/</guid><description>本文简单给出了两个文件，使用了c++20 的module特性，并说明了如何使用g++进行编译。</description><pubDate>Tue, 06 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 9,
day: 6,
)&lt;/p&gt;
&lt;p&gt;#metadata((
title: &quot;使用g++编译cpp20的module文件&quot;,
subtitle: [c++20],
author: &quot;dashuai009&quot;,
description: &quot;本文简单给出了两个文件，使用了c++20 的module特性，并说明了如何使用g++进行编译。&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;== 简介
&amp;lt;简介&amp;gt;
简单记录一下初次尝试c++20中module特性的过程。包括一个main.cpp和子模块
test_include.cxx。编译器为gnu的g++12.2。编译环境是wsl2-arch，glibc&amp;gt;=2.36（g++12.2要求的）。&lt;/p&gt;
&lt;p&gt;== demo结构
&amp;lt;demo结构&amp;gt;
两个文件，main.cpp 和 子模块 test_include.cxx。直接上代码：&lt;/p&gt;
&lt;p&gt;#quote[
main.cpp
]&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import TestInclude;//引入TestInclude模块

int main(){
    TestInclude::push(1);// 这个TestInclude是命名空间的名字，不是模块名！！！
    TestInclude::push(3);
    TestInclude::push(2);
    TestInclude::print();
    // std::cout &amp;lt;&amp;lt; &quot;error\n&quot;; //美滋滋地把std::cout 隐藏起来， 如果用include这个cout会自动引入
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;#quote[
test_include.cxx
]&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//本模块定义一个私有的全局vector，对外导出两个接口，push和print
module;//这一行不能少！
#include&amp;lt;vector&amp;gt;
#include&amp;lt;iostream&amp;gt;

export module TestInclude; //声明一下模块名字

std::vector&amp;lt;int32_t&amp;gt; staticVec;//

export namespace TestInclude{//导出接口外边套一层namespace
    void push(int32_t x){
        staticVec.push_back(x);
    }

    void print(){
        for (auto x:staticVec){
            std::cout &amp;lt;&amp;lt; x &amp;lt;&amp;lt; &apos; &apos;;
        }
        std::cout &amp;lt;&amp;lt; &apos;\n&apos;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== 编译运行
&amp;lt;编译运行&amp;gt;
编译命令 &lt;code&gt;g++ test_include.cxx main.cpp -o o -std=c++20 -fmodules-ts&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;执行&lt;code&gt;./o&lt;/code&gt; 可以如期输出&lt;code&gt;1 3 2&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;== 总结
&amp;lt;总结&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;编译命令需要添加&lt;code&gt;-std=c++20&lt;/code&gt;和&lt;code&gt;-fmodules-rs&lt;/code&gt;。现在g++编译错误提示还可以，#strike[&lt;code&gt;-fmodule-ts&lt;/code&gt;我现在还不知道为什么]&lt;/li&gt;
&lt;li&gt;main.cpp是找不到std::cout的，这种module的特性应该大有可为！！封装c库？&lt;/li&gt;
&lt;li&gt;test_include.cxx
中手动控制了#strong[模块名];和一个#strong[默认导出的命名空间];同为&lt;code&gt;TestInclude&lt;/code&gt;，算是强行伪装了rust中文件名即模块名的特点。利用一个默认导出的命名空间可以规范模块导出的接口，这个意义更为重要，估计会出现在很多编码规范中！（或者限制在某一个命名空间中导出，效果类似，个人更倾向上边这种，export个数少）&lt;/li&gt;
&lt;li&gt;g++还可以使用&lt;code&gt;g++ -c -xc++ test_include.cxx -fmodules-ts&lt;/code&gt;单独编译某个模块。会生成&lt;code&gt;gcm.cache/TestInclude.gcm&lt;/code&gt;和
&lt;code&gt;test_include.o&lt;/code&gt;的缓存文件，据说可以加快编译速度。emmm期待编译工具链的支持。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>素数的计数</title><link>https://dashuai009.github.io/blog/034/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/034/</guid><description>存在无求多个素数、存在无穷多个模4余3的素数、算术级数的素数狄利克雷定理、素数定理、哥得巴赫猜想、孪生素数猜想、N方+1猜想</description><pubDate>Sat, 03 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 9,
day: 3,
)
#metadata((
title: &quot;素数的计数&quot;,
subtitle: [math,数论],
author: &quot;dashuai009&quot;,
description: &quot;存在无求多个素数、存在无穷多个模4余3的素数、算术级数的素数狄利克雷定理、素数定理、哥得巴赫猜想、孪生素数猜想、N方+1猜想&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;== 无穷多个素数定理
&amp;lt;无穷多个素数定理&amp;gt;
存在无穷多个素数&lt;/p&gt;
&lt;p&gt;=== 证明
&amp;lt;证明&amp;gt;
瞪眼法&lt;/p&gt;
&lt;p&gt;== 模4余3定理
&amp;lt;模4余3定理&amp;gt;
存在无穷多个模4余3的素数&lt;/p&gt;
&lt;p&gt;=== 证明
&amp;lt;证明-1&amp;gt;
假设我们已经有几个模4余3的素数，证明思路：从这几个素数出发可以#strong[不断找到新的];模4余3的素数。&lt;/p&gt;
&lt;p&gt;假设初始表为$P_r = { 3 , p_1 , p_2 , p_3 , dots.h , p_r }$，考虑
#set math.equation(numbering: &quot;(1)&quot;)
$
A=4p_1p_2...p_r+3
$
，先将其分解为若干个素数乘积，
$
A=q_1q_2...q_s,
$
，我们可以得到以下来两个断言。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;素数$q_1 , q_2 , dots.h , q_r$至少有一个必定是模4余3&lt;/p&gt;
&lt;p&gt;如果该断言不成立，则这s个素数模4余1，那么A应该模4余1，这与(1)相悖&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;假设$q_i equiv 3 (m o d med 4)$，则$q_i$不在最初的表中，&lt;/p&gt;
&lt;p&gt;从(2)可以得到$q_i | A$，但是$3 , p_1 , dots.h , p_r$没有一个整除A的。所以我们得到了一个新的模4余3的素数。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;重复以上两步，我们可以得到#strong[无穷多个模4余3的素数]&lt;/p&gt;
&lt;p&gt;// #set math.equation(numbering: &quot;&quot;)
=== &quot;模4余1&quot;还能这样求吗？&lt;/p&gt;
&lt;p&gt;不能，试一下就可以发现。无法得到无穷多个。&lt;/p&gt;
&lt;p&gt;== 算数级数的素数狄利克雷定理
&amp;lt;算数级数的素数狄利克雷定理&amp;gt;
假设a和m是整数，$g c d (a , m) = 1$，则存在无穷多个素数模m余a,即存在无穷多个素数p满足
$ p equiv a (m o d med p) $&lt;/p&gt;
&lt;p&gt;=== 证明
&amp;lt;证明-2&amp;gt;
前边证明了(a,m)=(4,1)的情况。其他情况，我不会。&lt;/p&gt;
&lt;p&gt;== 素数定理
&amp;lt;素数定理&amp;gt;
当x很大时，小于x的素数的个数近似等于$x / ln (x)$.也就是
$ lim_(x arrow.r oo) frac(pi (x), x / ln (x)) = 1 $&lt;/p&gt;
&lt;p&gt;== 三个猜想
&amp;lt;三个猜想&amp;gt;
=== 歌德巴赫猜想
&amp;lt;歌德巴赫猜想&amp;gt;
每个偶数$n gt.eq 1$可以表示为两个素数之和。&lt;/p&gt;
&lt;p&gt;=== 孪生素数猜想
&amp;lt;孪生素数猜想&amp;gt;
存在无穷多个素数p使得p+2也是素数。&lt;/p&gt;
&lt;p&gt;=== $N^2 + 1$猜想
&amp;lt;n21猜想&amp;gt;
存在无穷多个形如$N^2 + 1$的素数。&lt;/p&gt;
</content:encoded></item><item><title>欧拉函数与中国剩余定理</title><link>https://dashuai009.github.io/blog/033/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/033/</guid><description>欧拉函数与中国剩余定理</description><pubDate>Thu, 01 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 9,
day: 1,
)
#metadata((
title: &quot;欧拉函数与中国剩余定理&quot;,
subtitle: [math,数论,中国剩余定理],
author: &quot;dashuai009&quot;,
description: &quot;欧拉函数与中国剩余定理&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;== $phi.alt$函数公式
&amp;lt;phi函数公式&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果p是素数，且$k gt.eq 1$，则 $ phi.alt (p^k) = p^k - p^(k - 1) $&lt;/li&gt;
&lt;li&gt;如果$g c d (m , n) = 1$，则$phi.alt (m n) = phi.alt (m) phi.alt (n)$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;== 证明
&amp;lt;证明&amp;gt;
瞪眼法可得证。&lt;/p&gt;
&lt;p&gt;== 中国剩余定理
&amp;lt;中国剩余定理&amp;gt;
设$a_1 , a_2 , dots.h , a_k$是任意整数，且$n_1 , n_2 , dots.h , n_k$两两互素，则同余方程组
$
{(
x equiv a_1 (#h(0em) mod med n_1)&lt;br /&gt;
x equiv a_2 (#h(0em) mod med n_2)&lt;br /&gt;
dots.h&lt;br /&gt;
x equiv a_k (#h(0em) mod med n_k)
)
$
有解。令$N = product_(i = 1)^k n_i$，则同余方程组的解在模N下，唯一。&lt;/p&gt;
&lt;p&gt;唯一为：&lt;/p&gt;
&lt;p&gt;$ x = (sum_(i = 1)^k a_i N / n_i m_i) #h(0em) mod med N $&lt;/p&gt;
&lt;p&gt;其中$m_i$为$N / n_i$在模$n_i$下的逆元&lt;/p&gt;
</content:encoded></item><item><title>欧拉公式</title><link>https://dashuai009.github.io/blog/032/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/032/</guid><description>欧拉公式及其证明</description><pubDate>Mon, 29 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 8,
day: 29,
)
#metadata((
title: &quot;欧拉公式&quot;,
subtitle: [math,数论],
author: &quot;dashuai009&quot;,
description: &quot;欧拉公式及其证明&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;== 欧拉函数
&amp;lt;欧拉函数&amp;gt;
在1~m中与m互素的整数的个数，记作$phi.alt (m)$&lt;/p&gt;
&lt;p&gt;$ phi.alt (m) = {a | 1 lt.eq a lt.eq m , gcd (a , m) = 1} $&lt;/p&gt;
&lt;p&gt;显然，$phi.alt (1) = 0 , phi.alt (p) = p - 1$&lt;/p&gt;
&lt;p&gt;== 欧拉公式&lt;/p&gt;
&lt;p&gt;若$gcd (a , m) = 1$，则 $ a^(phi.alt (m)) equiv 1 (mod med m) . $&lt;/p&gt;
&lt;p&gt;== 证明
&amp;lt;证明&amp;gt;
先来说明一个引理&lt;/p&gt;
&lt;p&gt;=== 引理 令数列
$1 lt.eq b_1 &amp;lt; b_2 &amp;lt; dots.h &amp;lt; b_(phi.alt (m)) &amp;lt; m$是1~m中与m互素的$phi.alt (m)$个整数。&lt;/p&gt;
&lt;p&gt;若$g c d (a , m) = 1$，则数列$ b_1 a , b_2 a , b_3 a , dots.h , b_(phi.alt (m)) a med med (mod med m) , $于数列 $ 1 , 2 , 3 , dots.h , b_(phi.alt (m)) med med (mod med m) , $
相同（忽略次序）。&lt;/p&gt;
&lt;p&gt;=== 引理证明
&amp;lt;引理证明&amp;gt;
证明过程与费马小定理证明过程类似。&lt;/p&gt;
&lt;p&gt;注意到$g c d (b_i , m) = g c d (a , m) = 1$，则$g c d (b_i a , m) = 1$.&lt;/p&gt;
&lt;p&gt;从而数列(1)与同余于数列(2)每一个数。这两个数列都含有$phi.alt (m)$个数字。&lt;/p&gt;
&lt;p&gt;从数列(1)中任取两个数$b_j a$和$b_k a$，假设它们同余：
$ b_j a equiv b_k a (mod med p) $&lt;/p&gt;
&lt;p&gt;则$p | (j - k) a$，又因为$a equiv.not 0 (mod med m)$，所以$m | (b_j - b_k)$。&lt;/p&gt;
&lt;p&gt;而$1 lt.eq b_j , b_k lt.eq m - 1$，则
$0 lt.eq lr(|b_j - b_k|) lt.eq m - 2$，仅有$b_j = b_k$时，$p | 0$。&lt;/p&gt;
&lt;p&gt;所以，$b_1 a , b_2 a , b_3 a , dots.h , b_(phi.alt (m)) a$模m不同余。&lt;/p&gt;
&lt;p&gt;$phi.alt (m)$个数模m不同余，这$phi.alt (m)$个余数一定就是数列(2)。得证。&lt;/p&gt;
&lt;p&gt;== 证明欧拉公式
&amp;lt;证明欧拉公式&amp;gt;
引理中的两个数列连乘得：&lt;/p&gt;
&lt;p&gt;$ b_1 a dot.op (b_2 a) dots.h dot.op b_(phi.alt (m)) a equiv b_1 dot.op b_2 dots.h dot.op b_(phi.alt (m)) (mod m) $&lt;/p&gt;
&lt;p&gt;即&lt;/p&gt;
&lt;p&gt;$ a^(phi.alt (m)) B equiv B (mod med m) $&lt;/p&gt;
&lt;p&gt;又有$B$与$m$互素，两边消掉得&lt;/p&gt;
&lt;p&gt;$a^(phi.alt (m)) equiv 1 (mod med m)$&lt;/p&gt;
&lt;p&gt;得证。&lt;/p&gt;
&lt;p&gt;== 卡歇米尔数&lt;/p&gt;
&lt;p&gt;如果对于每个满足$gcd (a , m) = 1$的整数a，同余式$a^(m - 1) equiv 1 (#h(0em) mod med m)$成立，则称#strong[合数m为卡歇米尔数];。&lt;/p&gt;
&lt;p&gt;例如$m = 561 = 3 * 11 * 17$是卡歇米尔数。&lt;/p&gt;
</content:encoded></item><item><title>为存有Box&lt;dyn T&gt;的结构体实现Clone</title><link>https://dashuai009.github.io/blog/030/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/030/</guid><description>为存有Box&lt;dyn T&gt;的结构体实现Clone how to clone a struct storing a boxed trait object</description><pubDate>Sun, 28 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 8,
day: 28,
)&lt;/p&gt;
&lt;p&gt;#metadata((
title: &quot;为存有Box&amp;lt;dyn T&amp;gt;的结构体实现Clone&quot;,
subtitle: [rust],
author: &quot;dashuai009&quot;,
description: &quot;为存有Box&amp;lt;dyn T&amp;gt;的结构体实现Clone how to clone a struct storing a boxed trait object&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;== 问题描述：
&amp;lt;问题描述&amp;gt;
无法为以下结构体实现Clone：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
pub trait GetName {
    fn get_name(&amp;amp;self) -&amp;gt; String;
}

#[device(Clone)]
struct Node{
    name: String,
    children: Vec&amp;lt;Box&amp;lt;dyn GetName&amp;gt;&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== 参考链接
&amp;lt;参考链接&amp;gt;
#link(&quot;https://stackoverflow.com/questions/30353462/how-to-clone-a-struct-storing-a-boxed-trait-object&quot;)[stackoverflow how-to-clone-a-struct-storing-a-boxed-trait-object]&lt;/p&gt;
&lt;p&gt;== 具体实现
&amp;lt;具体实现&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 先为GetName继承一个能clone的trait
pub trait GetName: NodeClone{
    fn get_name(&amp;amp;self) -&amp;gt; String;
}

//自定义一个clone,里面套默认clone
pub trait NodeClone {
    fn clone_box(&amp;amp;self) -&amp;gt; Box&amp;lt;dyn GetName&amp;gt;;
}

//自定义clone接口，where限制这个NodeClone只是用在GetName上，
//如果不借助这个NodeClone,直接pub tarit GetName:Clone{}是不行的
//没法放在Box&amp;lt;&amp;gt;里
impl&amp;lt;T&amp;gt; NodeClone for T
where
    T: &apos;static + GetName + Clone,
{
    fn clone_box(&amp;amp;self) -&amp;gt; Box&amp;lt;dyn GetName&amp;gt; {
        Box::new(self.clone())
    }
}


//为Box&amp;lt;dyn T&amp;gt;实现Clone,调用dyn GetName的自定义Clone
impl Clone for Box&amp;lt;dyn GetName&amp;gt; {
    fn clone(&amp;amp;self) -&amp;gt; Box&amp;lt;dyn GetName&amp;gt; {
        self.clone_box()
    }
}

#[derive(Clone)]
pub struct Node {
    name: String,
    children: Vec&amp;lt;Box&amp;lt;dyn GetName&amp;gt;&amp;gt;//子元素只要能GetName就可以
}

impl GetName for Node{
    fn get_name(&amp;amp;self) -&amp;gt; String{
        return self.name.clone();
    }
}

impl Node{
    //将子元素的name拼起来打印
    pub fn print_children(&amp;amp;self)-&amp;gt;String{
        let s = self.children.iter()
        .map(|n|n.get_name())
        .collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;()
        .join(&quot;,&quot;);
        format!(&quot;[{}]&quot;,s)
    }
}

fn main() {

    let a1= Box::new(Node{
        name: &quot;a1111&quot;.to_string(),
        children:vec![]
    });
    let a2= Box::new(Node{
        name: &quot;a2&quot;.to_string(),
        children:vec![]
    });

    let b = Node{
        name: &quot;bbb&quot;.to_string(),
        //测试一下，to_vec()会调用每个元素的Clone
        //a1、a2具体类型是Node,具有GetName、Clone接口，
        children:[a1 as Box&amp;lt;dyn GetName&amp;gt;,a2 as Box&amp;lt;dyn GetName&amp;gt;].to_vec()
    };

    println!(&quot;b={}&quot;,b.print_children());


}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>费马小定理</title><link>https://dashuai009.github.io/blog/031/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/031/</guid><description>费马小定理及其证明</description><pubDate>Sun, 28 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 8,
day: 28,
)
#metadata((
title: &quot;费马小定理&quot;,
subtitle: [math,数论],
author: &quot;dashuai009&quot;,
description: &quot;费马小定理及其证明&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;== 费马小定理
&amp;lt;费马小定理&amp;gt;
设p为素数，a是任意整数，且$a equiv.not 0 (m o d med p)$，则
$ a^(p - 1) equiv 1 (m o d med p) . $&lt;/p&gt;
&lt;p&gt;== 证明
&amp;lt;证明&amp;gt;
先来说明一个引理
=== 引理&lt;/p&gt;
&lt;p&gt;设p是素数，a是任何整数，且$a equiv.not 0 (m o d med p)$，则数列
$ a , 2 a , 3 a , dots.h , (p - 1) a med med (m o d med p) $ 于数列
$ 1 , 2 , 3 , dots.h , (p - 1) med med (m o d med p) $&lt;/p&gt;
&lt;p&gt;相同（忽略次序）。&lt;/p&gt;
&lt;p&gt;=== 引理证明
&amp;lt;引理证明&amp;gt;
数列$a , 2 a , 3 a , dots.h , (p - 1) a$，含有$p - 1$ 个数字。&lt;/p&gt;
&lt;p&gt;任取两个数$j a$和$k a$，假设它们同余： $ j a equiv k a (m o d med p) $&lt;/p&gt;
&lt;p&gt;则$p | (j - k) a$，又因为$a equiv.not 0 (m o d med p)$，所以$p | (j - k)$。&lt;/p&gt;
&lt;p&gt;而$1 lt.eq j , k lt.eq p - 1$，则
$0 lt.eq lr(|j - k|) lt.eq q - 2$，仅有$j = k$时，$p | 0$。&lt;/p&gt;
&lt;p&gt;所以，$a , 2 a , 3 a , dots.h , (p - 1) a$模p不同余。&lt;/p&gt;
&lt;p&gt;p-1个数模p不同余，这p-1个余数一定就是$1 , 2 , dots.h , p - 1$。得证。&lt;/p&gt;
&lt;p&gt;== 证明费马小定理
&amp;lt;证明费马小定理&amp;gt;
引理中的两个数列连乘得：&lt;/p&gt;
&lt;p&gt;$ a dot.op (2 a) dots.h dot.op (p - 1) a equiv 1 dot.op 2 dots.h dot.op (p - 1) (m o d p) $&lt;/p&gt;
&lt;p&gt;即&lt;/p&gt;
&lt;p&gt;$ a^(p - 1) (p - 1) ! equiv (p - 1) ! (m o d med p) $&lt;/p&gt;
&lt;p&gt;又有$(p - 1) !$与$p$互素，两边消掉得&lt;/p&gt;
&lt;p&gt;$a^(p - 1) equiv 1 (m o d med p)$&lt;/p&gt;
&lt;p&gt;得证。&lt;/p&gt;
&lt;p&gt;== 利用费马小定理简单判断一个数是不是质数&lt;/p&gt;
&lt;p&gt;a可以随便取，比如取2，只要计算出$2^(m - 1) #h(0em) mod med m eq.not 1$，那就说明m不是质数。
比如&lt;/p&gt;
&lt;p&gt;当$m = 10^6 + 7$时，$2^(m - 1) #h(0em) mod med m = 399097$，所以$1 e 6 + 7$不是质数。&lt;/p&gt;
&lt;p&gt;注意，若 $2^(m-1) mod m eq 1 $，#strong[m不一定是质数]&lt;/p&gt;
</content:encoded></item><item><title>显卡更新驱动失败黑屏</title><link>https://dashuai009.github.io/blog/011/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/011/</guid><pubDate>Fri, 08 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2024,
month: 6,
day: 26,
)
#metadata((
&quot;title&quot;: &quot;显卡更新驱动失败黑屏&quot;,
&quot;author&quot;: &quot;dashuai009&quot;,
description: &quot;&quot;,
pubDate: &quot;&apos;Jul 08 2022&apos;&quot;,
subtitle: [4090],
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;#date.display();&lt;/p&gt;
&lt;p&gt;#outline()&lt;/p&gt;
&lt;p&gt;== 起因&lt;/p&gt;
&lt;p&gt;PrtSc截图时，图片色彩失真，偏黄，好像是HDR的问题。我想着显卡驱动还是23年中（去年的），可能更新一下显卡驱动就好。结果更新完， 重启后屏幕黑屏。很奇怪，只是显示器没输出，操作系统能正常进去。好像是掉驱动了。&lt;/p&gt;
&lt;p&gt;== 网上的答案&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;换一个显示器。pass。没这个钱。&lt;/li&gt;
&lt;li&gt;换数据线的接口。拔插一下。我这没效果。&lt;/li&gt;
&lt;li&gt;进安全模式，卸载重装驱动。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;== 安全模式卸载驱动&lt;/p&gt;
&lt;p&gt;试了一天，电脑重启了无数次，最后卸载驱动重新装的。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把显卡拆掉。这时候能正常进入系统，一切显示正常。&lt;/li&gt;
&lt;li&gt;去nvidia官网下载驱动。最新的就行，我这应该时驱动安装错误。&lt;/li&gt;
&lt;li&gt;win+r，msconfig，boot，safe boot下次会进入安全模式。&lt;/li&gt;
&lt;li&gt;关机，安装显卡，开机进入安全模式，设备管理器，找到显卡，卸载驱动。重复3，关掉安全模式。&lt;/li&gt;
&lt;li&gt;重启，应该能进入系统，但是无法识别到显卡。安装驱动。显卡被识别到。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其他问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;拆掉显卡后找不到设备，没法卸载驱动，就很尴尬。&lt;/li&gt;
&lt;li&gt;不安装显卡，没法安装驱动，导致我这反复拔插显卡好几次。&lt;/li&gt;
&lt;li&gt;我这显示器绝对有问题，但是没钱换。安全模式，一旦显示器的输入切别的口进来，再切回来的时候，系统直接不再识别，黑屏。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>使用python爬取并分析疫情数据</title><link>https://dashuai009.github.io/blog/013/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/013/</guid><description>这是一种将C++更加现代的代码组织方式。 模块是一组源代码文件，独立于导入它们的翻译单元进行编译。</description><pubDate>Fri, 08 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2023,
month: 5,
day: 14,
)
#metadata((
&quot;title&quot;: &quot;使用python爬取并分析疫情数据&quot;,
&quot;author&quot;: &quot;dashuai009&quot;,
description: &quot;这是一种将C++更加现代的代码组织方式。 模块是一组源代码文件，独立于导入它们的翻译单元进行编译。&quot;,
pubDate: &quot;&apos;Jul 08 2022&apos;&quot;,
subtitle: [python,疫情数据分析],
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;#outline()&lt;/p&gt;
&lt;p&gt;= 数据获取
&amp;lt;数据获取&amp;gt;
选取#link(&quot;https://en.wikipedia.org/w/index.php?title=Template:COVID-19_pandemic_data&quot;)[wikipedia];上的疫情数据，总数据页如下&lt;/p&gt;
&lt;p&gt;#figure(
image(&quot;013/data0.png&quot;),
caption: [
总数据页
],
)&lt;/p&gt;
&lt;p&gt;每个国家的数据都可以从表格中解析出来，例如United
States的详细数据的地址为
#link(&quot;https://en.wikipedia.org/wiki/COVID-19_pandemic_in_the_United_States&quot;)[Unitedd States];。
具体数据可以从下图中的表格解析：&lt;/p&gt;
&lt;p&gt;#figure(
image(&quot;013/data1.png&quot;),
caption: [
United States数据页
],
)&lt;/p&gt;
&lt;p&gt;仔细分析之后我们可以写以下爬虫代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import scrapy
from ..items import FordataItem

class mySpider(scrapy.spiders.Spider):
    name = &quot;forData&quot;

    def start_requests(self):
        beginUrl= &quot;https://en.wikipedia.org/w/index.php?title=Template:COVID-19_pandemic_data&quot;
        myHeader = {
            &apos;Content-Type&apos;: &apos;application/json&apos;,
            &apos;User-Agent&apos;:&apos;Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0&apos;
        }
        #先爬出数据首页，找到有哪些国家
        yield scrapy.Request(url=beginUrl,callback=self.parseCountryUrl,headers=myHeader)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;    def parseCountryUrl(self,response):
        cnt=0
        for each in response.xpath(&apos;/html/body/div[3]/div[3]/div[5]/div[1]/div[4]/div[2]/table/tbody/tr&apos;):
            countryName=each.xpath(&apos;th[2]/a/text()&apos;).get()
            countryUrl=each.xpath(&apos;th[2]/a/@href&apos;).extract()
            if(countryName!=None and len(countryUrl)&amp;gt;0):
                cnt=cnt+1
                print(countryName,countryUrl)
                countryDataUrl= &quot;https://en.wikipedia.org&quot;+countryUrl[0]
                myHeader = {
                    &apos;Content-Type&apos;: &apos;application/json&apos;,
                    &apos;User-Agent&apos;:&apos;Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0&apos;
                }
                yield scrapy.Request(url=countryDataUrl,headers=myHeader,callback=lambda response,name=countryName:self.parseCountryCase(response,name))
                #再分别爬取每个国家的数据
        print(cnt)

    def parseCountryCase(self,response,name):
        ct=0
        for tr in response.css(&apos;tr.mw-collapsible&apos;):
            item=FordataItem()
            item[&apos;country&apos;]=name
            item[&apos;date&apos;]=tr.xpath(&apos;td[1]/text()&apos;).get()
            item[&apos;cases&apos;]=tr.xpath(&apos;td[3]/span[1]/text()&apos;).get()
            item[&apos;deaths&apos;]=tr.xpath(&apos;td[4]/span[1]/text()&apos;).get()
            #解析出每个国家的名字、日期、病例、死亡数
            if(&apos;2020&apos; in item[&apos;date&apos;]):
                ct=ct+1
                yield(item)
        if(ct==0):
            print(&quot;没有单日数据的的国家：&quot;,name)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;数据前几行如下：&lt;/p&gt;
&lt;p&gt;#figure(
image(&quot;013/data2.png&quot;, width: 90%),
caption: [
疫情数据
],
)&lt;/p&gt;
&lt;p&gt;另外，这个网页上并没有人口总数的数据可以从#link(&quot;https://en.wikipedia.org/wiki/List_of_countries_and_dependencies_by_population&quot;);上继续爬取。&lt;/p&gt;
&lt;p&gt;#figure(
image(&quot;013/data3.png&quot;, width: 90%),
caption: [
国家人口对应表的数据来源
],
)&lt;/p&gt;
&lt;p&gt;可以写出如下爬虫&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import scrapy
from ..items import ForpopulationItem

class mySpider(scrapy.spiders.Spider):
    name = &quot;forPopulation&quot;

    def start_requests(self):
        beginUrl= &quot;https://en.wikipedia.org/wiki/List_of_countries_and_dependencies_by_population&quot;
        myHeader = {
            &apos;Content-Type&apos;: &apos;application/json&apos;,
            &apos;User-Agent&apos;:&apos;Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0&apos;
        }
        #网页请求
        yield scrapy.Request(url=beginUrl,callback=self.parsePopulation,headers=myHeader)

    def parsePopulation(self,response):
        for tr in response.xpath(&apos;/html/body/div[3]/div[3]/div[5]/div[1]/table/tbody/tr&apos;):
            #数据解析
            item=ForpopulationItem()
            item[&apos;country&apos;]=tr.xpath(&apos;td[1]/a/text()&apos;).get()
            item[&apos;population&apos;]=tr.xpath(&apos;td[2]/text()&apos;).get()
            if(item[&apos;country&apos;] and item[&apos;population&apos;]):
                item[&apos;population&apos;].replace(&apos;,&apos;,&apos;&apos;)
                print(item)
                yield item
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;爬取结果如下&lt;/p&gt;
&lt;p&gt;#figure(
image(&quot;013/data4.png&quot;, width: 90%),
caption: [
国家人口对应表
],
)&lt;/p&gt;
&lt;p&gt;= 数据分析和展示
&amp;lt;数据分析和展示&amp;gt;
== 数据读入和清洗
&amp;lt;数据读入和清洗&amp;gt;
从前面爬取的数据文件&lt;code&gt;myData.csv&lt;/code&gt;和&lt;code&gt;population.csv&lt;/code&gt;中读取数据。
对于疫情数据，将空值填充为0，只保留日起在&lt;code&gt;2020-11-30&lt;/code&gt;到&lt;code&gt;2020-12-15&lt;/code&gt;之间的数据。
将病例数&lt;code&gt;cases&lt;/code&gt;和&lt;code&gt;deaths&lt;/code&gt;格式转换为&lt;code&gt;int64&lt;/code&gt;。 根据&lt;code&gt;country&lt;/code&gt;和&lt;code&gt;date&lt;/code&gt;
进行去重。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import pandas
import numpy
import matplotlib
import matplotlib.pyplot as plt
from brokenaxes import brokenaxes
import seaborn

org=pandas.read_csv(&apos;myData.csv&apos;)
org=org.fillna(&apos;0&apos;)
org[&apos;cases&apos;]=org[&apos;cases&apos;].str.replace(&apos;\D+&apos;,&apos;&apos;).astype(int)
org[&apos;deaths&apos;]=org[&apos;deaths&apos;].str.replace(&apos;\D+&apos;,&apos;&apos;).astype(int)
org=org[org[&apos;date&apos;]&amp;lt;&apos;2020-12-16&apos;]
org=org[org[&apos;date&apos;]&amp;gt;&apos;2020-11-29&apos;]
org.drop_duplicates(subset = [&apos;country&apos;,&apos;date&apos;],keep=&apos;first&apos;,inplace=True)
myData=org
population=pandas.read_csv(&apos;population.csv&apos;)
population[&apos;population&apos;]=population[&apos;population&apos;].str.replace(&apos;\D+&apos;,&apos;&apos;).astype(int)

print(&quot;收集到的&quot;,len(myData[&apos;country&apos;].value_counts(dropna=False)),&quot;个国家的数据&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;收集到的 135 个国家的数据&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## 15天中，全球新冠疫情的总体变化趋势


####################################
#a) 15 天中,全球新冠疫情的总体变化趋势#
####################################

print(&quot;\n\n====15 天中,全球新冠疫情的总体变化趋势======&quot;)

#这十六天内的全球累计病例
totalCase=[0]*16

#这16天内的全球死亡累计病例
totalDeaths=[0]*16

#December表示日期，用字符串存储，方便图例生成
December=[&apos;&apos;]*16

#根据国家进行分组，全球数量=个国家数量之和，计算累计病例和死亡病例
casesSumByDay=myData.pivot_table(index=&apos;date&apos;,values=[&apos;cases&apos;],aggfunc=sum)
deathsSumByDay=myData.pivot_table(index=&apos;date&apos;,values=[&apos;deaths&apos;],aggfunc=sum)

December[0]=&apos;2020-11-30&apos;
totalCase[0]=casesSumByDay.loc[&apos;2020-11-30&apos;][&apos;cases&apos;]
totalDeaths[0]=deathsSumByDay.loc[&apos;2020-11-30&apos;][&apos;deaths&apos;]

for i in range(1,16):
December[i]=&apos;2020-12-&apos;+(&apos;0&apos; if i&amp;lt;10 else &apos;&apos;)+str(i)
totalCase[i]=casesSumByDay.loc[December[i]][&apos;cases&apos;]
totalDeaths[i]=deathsSumByDay.loc[December[i]][&apos;deaths&apos;]

print(&quot;11-30到12-15感染病例：&quot;,totalCase)
print(&quot;11-30到12-15死亡病例：&quot;,totalDeaths)

# matplotlib绘图显示中文

plt.rcParams[&quot;font.family&quot;]=&quot;SimHei&quot;

#画图部分
mycolors = [&apos;tab:red&apos;, &apos;tab:blue&apos;]
fig, ax = plt.subplots(1,1,figsize=(16, 9), dpi= 300)
x  = December
y = numpy.vstack([totalDeaths,totalCase])
labs=[&apos;累积死亡数&apos;,&apos;累积感染病例&apos;]
ax = plt.gca()
ax.stackplot(x, y, labels=labs, colors=mycolors, alpha=0.8)
plt.title(&quot;15 天中,全球新冠疫情的总体变化趋势&quot;)
ax.legend(fontsize=10, ncol=4)
plt.xticks(rotation=30,fontsize=10)  # 这里是调节横坐标的倾斜度
plt.gca().spines[&quot;top&quot;].set_alpha(0)
plt.gca().spines[&quot;bottom&quot;].set_alpha(.3)
plt.gca().spines[&quot;right&quot;].set_alpha(0)
plt.gca().spines[&quot;left&quot;].set_alpha(.3)
plt.savefig(&apos;a.png&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;====15 天中,全球新冠疫情的总体变化趋势======
11-30到12-15感染病例： [58806605, 59867806, 59924956, 60557860, 61163129, 59527002, 60033395, 62585050, 62008006, 63602677, 65008985, 65672468, 63968990, 64505434, 67261669, 68293227]
11-30到12-15死亡病例： [1471087, 1492594, 1494629, 1506452, 1517510, 1475608, 1482627, 1541590, 1516612, 1564328, 1576069, 1588076, 1541701, 1549901, 1611817, 1633944]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;#figure(
image(&quot;013/a.png&quot;, width: 100%),
caption: [
15 天中,全球新冠疫情的总体变化趋势
],
)&lt;/p&gt;
&lt;p&gt;== 累计确诊数排名前 20 的国家名称及其数量
&amp;lt;累计确诊数排名前-20-的国家名称及其数量&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;####################################################
#b) 累计确诊数排名前 20 的国家名称及其数量;           #
####################################################
print(&quot;\n\n=======累计确诊数排名前 20 的国家名称及其数量=========&quot;)

#总的累计数量只需要看最后一天的数量
casesMaxByCountry=myData.pivot_table(index=&apos;country&apos;,values=[&apos;cases&apos;],aggfunc=max)
casesMaxByCountry=casesMaxByCountry.sort_values(&apos;cases&apos;, ascending=False)

print(casesMaxByCountry.head(20))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;=======累计确诊数排名前 20 的国家名称及其数量=========
cases
country
United States   16022297
India            9906165
Brazil           6970034
Russia           2707945
France           2391447
Turkey           1898447
United Kingdom   1888116
Italy            1870576
Spain            1762212
Argentina        1510186
Colombia         1444646
Germany          1351510
Mexico           1267202
Poland           1147446
Iran             1123474
Peru              987675
Ukraine           919704
South Africa      873679
Indonesia         629429
Netherlands       628577
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== 15 天中，每日新增确诊数累计排名前 10 个国家的每日新增确诊数据的曲线图
&amp;lt;天中每日新增确诊数累计排名前-10-个国家的每日新增确诊数据的曲线图&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;##########################################################################
#c）15 天中，每日新增确诊数累计排名前 10 个国家的每日新增确诊数据的曲线图;     #
##########################################################################
mycolors=[&apos;tab:blue&apos;,&apos;tab:orange&apos;,&apos;tab:green&apos;,&apos;tab:red&apos;,&apos;tab:purple&apos;,&apos;tab:brown&apos;,&apos;tab:pink&apos;,&apos;tab:gray&apos;,&apos;tab:olive&apos;,&apos;tab:cyan&apos;]
plt.clf()

#每个国家的12-1之前的累计数量
casesMinByCountry=myData.pivot_table(index=&apos;country&apos;,values=[&apos;cases&apos;],aggfunc=min)

#每个国家12-1到12-15的新增数量
decCaseMaxByCountry=(casesMaxByCountry-casesMinByCountry).sort_values(&apos;cases&apos;, ascending=False)

cnt=0
countryCName=[]
figC=[0]*10
for index,row in decCaseMaxByCountry.iterrows():
cnt=cnt+1
countryCName.append(index)
yi=myData[myData[&apos;country&apos;]==index][&apos;cases&apos;].tolist()
for j in range(15,0,-1):#得到每日变化数据
yi[j]=yi[j]-yi[j-1]
figC[cnt-1],=plt.plot(December[1:],yi[1:],color=mycolors[cnt-1],linewidth=2.0,linestyle=&apos;-.&apos;)
if(cnt&amp;gt;=10):#只保留前十个国家
break

plt.title(&quot;每日新增确诊排名前 10 的国家的数据曲线图&quot;)
plt.legend(handles=figC,labels=countryCName)
plt.savefig(&apos;c.png&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;#figure(
image(&quot;013/c.png&quot;, width: 100%),
caption: [
每日新增确诊排名前 10 的国家的数据曲线图
],
)&lt;/p&gt;
&lt;p&gt;== 累计确诊人数占国家总人口比例最高的 10 个国家
&amp;lt;累计确诊人数占国家总人口比例最高的-10-个国家&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;################################################
#d） 累计确诊人数占国家总人口比例最高的 10 个国家  #
################################################
print(&quot;\n\n=====累计确诊人数占国家总人口比例最高的 10 个国家======&quot;)

#这会用到populaton.csv的数据
#累计确诊比例
casesRatio=[]
for index,row in casesMaxByCountry.iterrows():
tmp=population[population[&apos;country&apos;]==index][&apos;population&apos;]
if(len(tmp)&amp;gt;0):#这里需要注意没有人口数据的国家
casesRatio.append({&apos;casesRatio&apos;:row[&apos;cases&apos;]/tmp.iloc[0],&apos;name&apos;:index})

#升序排序取最后10个
casesRatio.sort(key=lambda x:x[&apos;casesRatio&apos;])
for i in casesRatio[-1:-10:-1]:
print(i)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;=====累计确诊人数占国家总人口比例最高的 10 个国家======
{&apos;casesRatio&apos;: 0.09561146718594844, &apos;name&apos;: &apos;Andorra&apos;}
{&apos;casesRatio&apos;: 0.06748037079864816, &apos;name&apos;: &apos;Luxembourg&apos;}
{&apos;casesRatio&apos;: 0.05548972112860494, &apos;name&apos;: &apos;Czech Republic&apos;}
{&apos;casesRatio&apos;: 0.05271911310260917, &apos;name&apos;: &apos;Belgium&apos;}
{&apos;casesRatio&apos;: 0.05243676244828293, &apos;name&apos;: &apos;Georgia&apos;}
{&apos;casesRatio&apos;: 0.05198869490976536, &apos;name&apos;: &apos;Qatar&apos;}
{&apos;casesRatio&apos;: 0.04872524937150579, &apos;name&apos;: &apos;Moldova&apos;}
{&apos;casesRatio&apos;: 0.04842282774074547, &apos;name&apos;: &apos;United States&apos;}
{&apos;casesRatio&apos;: 0.046797668330376366, &apos;name&apos;: &apos;Slovenia&apos;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== 死亡率（累计死亡人数/累计确诊人数）最低的 10 个国家
&amp;lt;死亡率累计死亡人数累计确诊人数最低的-10-个国家&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#########################################################
#e) 死亡率(累计死亡人数/累计确诊人数)最低的 10 个国家;      #
#########################################################
print(&quot;\n\n====死亡率(累计死亡人数/累计确诊人数)最低的 10 个国家=======&quot;)

#每个国家死亡人数
deathsMaxByCountry=myData.pivot_table(index=&apos;country&apos;,values=[&apos;deaths&apos;],aggfunc=max)
#每个国家的死亡率
deathsRatio=deathsMaxByCountry.rename(columns={&apos;deaths&apos;:&apos;deathsRatio&apos;})
deathsRatio=deathsRatio/casesMaxByCountry.rename(columns={&apos;cases&apos;:&apos;deathsRatio&apos;})
print((deathsRatio.sort_values(&apos;deathsRatio&apos;)).head(10))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;====死亡率(累计死亡人数/累计确诊人数)最低的 10 个国家=======
deathsRatio
country
Seychelles                           0.000000
Guyana                               0.000000
Saint Lucia                          0.000000
Faroe Islands                        0.000000
Saint Kitts and Nevis                0.000000
Falkland Islands                     0.000000
Brunei                               0.000000
Dominica                             0.000000
Saint Vincent and The Grenadines     0.000000
Singapore                            0.000497
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== 用饼图展示各个国家的累计确诊人数的比例
&amp;lt;用饼图展示各个国家的累计确诊人数的比例&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
########################################
#f) 用饼图展示各个国家的累计确诊人数的比例#
########################################
print(&quot;\n\n=====各个国家的累计确诊人数的比例=======&quot;)

#把占比最大的几个国家列出来，直到这些国家占比超过75%，其他国家表示为other
cnt=0
figData=[]
for index,row in casesMaxByCountry.iterrows():
cnt=cnt+row[&apos;cases&apos;]
if cnt/totalCase[15]&amp;gt;0.75:
break
figData.append((index,row[&apos;cases&apos;]))
figData.append((&apos;others&apos;,(totalCase[15]-cnt)))

#输出一下具体数据
print(figData)

#画图部分
plt.clf()
fig, ax = plt.subplots(figsize=(10, 5), ncols=2,dpi=300)
ax1, ax2 = ax.ravel()
patches, texts = ax1.pie(dict(figData).values(),
shadow=True,startangle=90,
labels=[ str(country)+&apos;,&apos;+str(&apos;%.2lf&apos;%(tot*100/totalCase[15]))+&apos;%&apos; for country, tot in figData])
ax1.set_title(&apos;各个国家的累计确诊人数的比例&apos;)
ax2.axis(&apos;off&apos;)
ax2.legend(patches, [i for i,j in figData], loc=&apos;center left&apos;)
plt.tight_layout()
plt.savefig(&apos;f.png&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;#figure(
image(&quot;013/f.png&quot;, width: 100%),
caption: [
各个国家的累计确诊人数的比例
],
)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;=====各个国家的累计确诊人数的比例=======
[(&apos;United States&apos;, 16022297), (&apos;India&apos;, 9906165), (&apos;Brazil&apos;, 6970034), (&apos;Russia&apos;, 2707945), (&apos;France&apos;, 2391447), (&apos;Turkey&apos;, 1898447), (&apos;United Kingdom&apos;, 1888116), (&apos;Italy&apos;, 1870576), (&apos;Spain&apos;, 1762212), (&apos;Argentina&apos;, 1510186), (&apos;Colombia&apos;, 1444646), (&apos;Germany&apos;, 1351510), (&apos;Mexico&apos;, 1267202), (&apos;others&apos;, 16154998)]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== 展示全球各个国家累计确诊人数的箱型图，有平均值
&amp;lt;展示全球各个国家累计确诊人数的箱型图有平均值&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;################################################
#g) 展示全球各个国家累计确诊人数的箱型图,要有平均值#
################################################
print(&quot;\n\n=====全球各个国家累计确诊人数的箱型图=====&quot;)

plt.clf()
fig, ax = plt.subplots(figsize=(5, 10),dpi=300)
#图中的黄点是平均数据值
seaborn.stripplot(y=&apos;cases&apos;,data={&apos;cases&apos;:[totalCase[15]/135]}, color=&quot;orange&quot;, size=2.5)
seaborn.boxplot(data=casesMaxByCountry,linewidth=0.3,fliersize=1)
plt.title(&quot;全球各个国家累计确诊人数的箱型图&quot;)
plt.savefig(&apos;g.png&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;#figure(
image(&quot;013/g.png&quot;, width: 70%),
caption: [
全球各个国家累计确诊人数的箱型图
],
)&lt;/p&gt;
&lt;p&gt;== 另外一些数据
&amp;lt;另外一些数据&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;15日新增确诊占累积确诊病例最小的20个国家

########################################

# 15日新增确诊占累积确诊病例最小的20个国家#

########################################
minRatio=(decCaseMaxByCountry/casesMaxByCountry).sort_values(&apos;cases&apos;)
print(minRatio.head(20))
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;
cases
country
Comoros                0.000000
Singapore              0.002108
Australia              0.005099
Malawi                 0.008553
Isle of Man            0.010724
Suriname               0.012823
Brunei                 0.013158
Guernsey               0.013746
Sierra Leone           0.015912
Qatar                  0.017265
Ivory Coast            0.017774
New Zealand            0.019084
Bolivia                0.020363
Nepal                  0.022645
Benin                  0.024272
Peru                   0.024370
São Tomé and Príncipe  0.024752
Maldives               0.026706
Kuwait                 0.027776
Iceland                0.029580
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;= 全世界应对新冠疫情最好的10个国家
&amp;lt;全世界应对新冠疫情最好的10个国家&amp;gt;
注意：爬取的数据没有包含中国的数据。&lt;/p&gt;
&lt;p&gt;显而易见的，感染人数过多（大于一百万）的国家应对疫情肯定不是最好的。
感染人数较少（小于100）也不需要考虑，这可能疫情并没有爆发，只是几个境外输入病例。
我们只考虑疫情严重的几个国家。这之后按照人口递减排序，取前10个。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
#####################
#最好的10个国家      #
#####################
print(&quot;\n\n======应对疫情最好的10个国家========&quot;)
good=[]
for index,rows in casesMaxByCountry.iterrows():
tmp=population[population[&apos;country&apos;]==index][&apos;population&apos;]
if((100&amp;lt;rows[&apos;cases&apos;]&amp;lt;1000000) and len(tmp)&amp;gt;0):
good.append({&apos;population&apos;:tmp.iloc[0],&apos;name&apos;:index})

good.sort(key=lambda x:-x[&apos;population&apos;])

for i in range(10):
print(good[i][&apos;name&apos;])
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;======应对疫情最好的10个国家========
Indonesia
Pakistan
Nigeria
Japan
Philippines
Ethiopia
Egypt
Vietnam
South Africa
Myanmar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;= 预测分析
&amp;lt;预测分析&amp;gt;
== 预测方法
&amp;lt;预测方法&amp;gt;
采用线性回归的方法进行预测。
新冠疫情存在一定的趋势，但目前数据比较少（只有10天），可以假设不存在季节性。
这样，可以采用&lt;code&gt;线性回归&lt;/code&gt;的方法进行预测。&lt;/p&gt;
&lt;p&gt;== 预测程序
&amp;lt;预测程序&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
###################################

# 预测分析                        #

###################################

from sklearn import linear_model
regr = linear_model.LinearRegression()

# 拟合

regr.fit(numpy.array([i for i in range(10)]).reshape(-1, 1), totalCase[1:11])

# 得到直线的斜率、截距

a, b = regr.coef_, regr.intercept_

# 给出待预测日期

predictDate = numpy.array([i for i in range(10,16)]).reshape(-1,1)

#输出一下预测值
print(regr.predict(predictDate))

#画图部分
plt.clf()
fig, ax = plt.subplots(figsize=(16, 10),dpi=300)
plt.scatter(December, totalCase, color=&apos;blue&apos;)
plt.plot(December[1:], regr.predict(numpy.array([i for i in range(15)]).reshape(-1,1)), color=&apos;red&apos;, linewidth=4)
plt.savefig(&apos;i.png&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;======预测分析========
[64229138.06666667 64738456.51515152 65247774.96363637 65757093.41212121
66266411.86060607 66775730.30909091]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== 结果以及分析
&amp;lt;结果以及分析&amp;gt;
#figure(
image(&quot;013/i.png&quot;),
caption: [
预测分析
],
)&lt;/p&gt;
&lt;p&gt;最后五天的数据拟合程度较好。&lt;/p&gt;
&lt;p&gt;对于短期的数据，线性回归应该是较好的一种预测方法。
然而，再实际问题中，还需要考虑各种影响因素。
比如，各个国家政策的调整，病毒的变异等等。&lt;/p&gt;
&lt;p&gt;= 代码网址
&amp;lt;代码网址&amp;gt;
#link(&quot;https://paste.ubuntu.com/p/KkGVZwhg7N/&quot;)[code]&lt;/p&gt;
</content:encoded></item><item><title>文本分类</title><link>https://dashuai009.github.io/blog/019/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/019/</guid><description>bupt-人工智能课大作业</description><pubDate>Fri, 08 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 3,
day: 14,
)
#metadata((
&quot;title&quot;: &quot;文本分类&quot;,
&quot;author&quot;: &quot;dashuai009&quot;,
description: &quot;bupt-人工智能课大作业&quot;,
pubDate: &quot;&apos;Jul 08 2022&apos;&quot;,
subtitle: [人工智能,文本分类],
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;== 1. 实验目的
&amp;lt;实验目的&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;掌握数据预处理的方法，对训练集数据进行预处理；&lt;/li&gt;
&lt;li&gt;掌握文本建模的方法，对语料库的文档进行建模；&lt;/li&gt;
&lt;li&gt;掌握分类算法的原理，基于有监督的机器学习方法，训练文本分类器；&lt;/li&gt;
&lt;li&gt;利用学习的文本分类器，对未知文本进行分类判别；&lt;/li&gt;
&lt;li&gt;掌握评价分类器性能的评估方法。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;== 2. 实验类型
&amp;lt;实验类型&amp;gt;
数据挖掘算法的设计与编程实现。&lt;/p&gt;
&lt;p&gt;== 3. 实验要求
&amp;lt;实验要求&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文本类别数：&amp;gt;=10类；&lt;/li&gt;
&lt;li&gt;训练集文档数：&amp;gt;=50000篇；每类平均5000篇。&lt;/li&gt;
&lt;li&gt;测试集文档数：&amp;gt;=50000篇；每类平均5000篇。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;== 4. 实验内容
&amp;lt;实验内容&amp;gt;
利用分类算法实现对文本的数据挖掘，主要包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;语料库的构建，主要包括利用爬虫收集Web文档等；&lt;/li&gt;
&lt;li&gt;语料库的数据预处理，包括文档建模，如去噪，分词，建立数据字典，使用词袋模型或主题模型表达文档等；&lt;/li&gt;
&lt;li&gt;选择分类算法（朴素贝叶斯（必做）、SVM/其他#strike[#strong[实际验收有几个不做的？];];等），训练文本分类器，理解所选的分类算法的建模原理、实现过程和相关参数的含义；&lt;/li&gt;
&lt;li&gt;对测试集的文本进行分类&lt;/li&gt;
&lt;li&gt;对测试集的分类结果利用正确率和召回率进行分析评价：计算每类正确率、召回率，计算总体正确率和召回率。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;== 5. 实验准备
&amp;lt;实验准备&amp;gt;
=== 5.1. 实验环境
&amp;lt;实验环境&amp;gt;
#strong[fedora 33]&lt;/p&gt;
&lt;p&gt;个人比较喜欢linux环境，windows环境应该也可以完成下面的步骤。一些文件路径可能会修改。
另外，硬盘读取速度等都会影响实验结果（速度等方面）。&lt;/p&gt;
&lt;p&gt;=== 数据集准备&lt;/p&gt;
&lt;p&gt;#link(&quot;http://thuctc.thunlp.org/message&quot;)[THUCNews]&lt;/p&gt;
&lt;p&gt;数据集大小：1.45GB&lt;/p&gt;
&lt;p&gt;样本数量：80多万&lt;/p&gt;
&lt;p&gt;数据集详情链接：#link(&quot;http://thuctc.thunlp.org&quot;)[thuctc]&lt;/p&gt;
&lt;p&gt;在解压出的目录下新建两个文件夹&lt;code&gt;trainData&lt;/code&gt;和&lt;code&gt;testData&lt;/code&gt;。
注意：Linux下解压会多一层目录并多一个&lt;code&gt;__MACOSX&lt;/code&gt;，这个并不影响实验。&lt;/p&gt;
&lt;p&gt;解压结果如下&lt;/p&gt;
&lt;p&gt;#figure(
image(&quot;019/thu.png&quot;, width: 200pt),
caption: [
文本分类实验 解压结果
],
)&lt;/p&gt;
&lt;p&gt;=== cppjieba
&amp;lt;cppjieba&amp;gt;
#link(&quot;https://github.com/yanyiwu/cppjieba&quot;)[cppjieba];是”结巴(Jieba)“中文分词的C++版本。
这个要比python快10倍，解决本实验足够好用!&lt;/p&gt;
&lt;p&gt;用法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;依赖软件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;gt; &lt;code&gt;g++ (version &amp;gt;= 4.1 is recommended) or clang++;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;gt; &lt;code&gt;cmake (version &amp;gt;= 2.6 is recommended);&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;下载和编译
&amp;gt;
&lt;code&gt;git clone --depth=10 --branch=master git://github.com/yanyiwu/cppjieba.git&lt;/code&gt;
&amp;gt; &amp;gt; &lt;code&gt;cd cppjieba&lt;/code&gt;
&amp;gt; &amp;gt; &lt;code&gt;mkdir build&lt;/code&gt; &amp;gt; &amp;gt; &lt;code&gt;cd build&lt;/code&gt; &amp;gt; &amp;gt;
&lt;code&gt;cmake ..&lt;/code&gt; &amp;gt; &amp;gt; &lt;code&gt;make&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;结果如下&lt;/p&gt;
&lt;p&gt;#figure(
image(&quot;019/jieba.png&quot;, width: 200pt),
caption: [
文本分类实验
],
)&lt;/p&gt;
&lt;p&gt;我们只需要修改&lt;code&gt;cppjieba\test\demo.cpp}&lt;/code&gt;，
但我们要在&lt;code&gt;cppjieba\build&lt;/code&gt;下执行&lt;code&gt;make&lt;/code&gt;进行编译，
并执行&lt;code&gt;./demo&lt;/code&gt;运行分词程序。&lt;/p&gt;
&lt;p&gt;=== 朴素贝叶斯分类器
&amp;lt;朴素贝叶斯分类器&amp;gt;
贝叶斯方法的分类目标是在给定描述实例的属性值
$&amp;lt; a_1 , a_2 , . . . , a_n &amp;gt;$下，得到最可能的目标值$V_(M A P)$。&lt;/p&gt;
&lt;p&gt;$
v_(M A P) &amp;amp; = a r g max_(v_j) P (v_j | a_1 , a_2 , . . . , a_n)&lt;br /&gt;
&amp;amp; = a r g max_(v_j in V) frac(P (a_1 , a_2 , . . . , a_n) P (v_j), P (a_1 , a_2 , . . . , a_n))&lt;br /&gt;
&amp;amp; = a r g max_(v_j in V) P (a_1 , a_2 , . . . , a_n) P (v_j)&lt;br /&gt;
&amp;amp; = a r g max_(v_j in V) P (v_j) product_i P (a_i | v_j)
$&lt;/p&gt;
&lt;p&gt;采纳m-估计方法，即有统一的先验概率并且m等于词汇表的 大小，因此
$ P (w_k | v_j) = frac(v_k + 1, n + lr(|V o c a b u l a r y|)) $&lt;/p&gt;
&lt;p&gt;定义如下函数：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Learn_Naive_Bayes_Text( Examples, V )&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Examples为一组文本文档以及它们的目标值。V为所有可能目标值的集合。此函数作用是学
习概率项$P (w_k | v_j)$和$P (v_j)$。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;收集Examples中所有的单词、标点符号以及其他记号

Vocabulary &amp;lt;= 在Examples中任意文本文档中出现的所有单词及记号的集合
    计算所需要的概率项$P(vj)$和$P(wk|vj)$
        对V中每个目标值vj
            $docs_j\leftarrow$Examples中目标值为$v_j$的文档子集
            $P(v_j)\leftarrow|docs_j| / |Examples|$
            $Text_j\leftarrow$将$docs_j$中所有成员连接起来建立的单个文档
            $n\leftarrow$在$Text_j$中不同单词位置的总数
            对Vocabulary中每个单词$w_k$
                $n_k\leftarrow$单词$w_k$出现在$Text_j$中的次数
                $P(w_k|v_j)\leftarrow(n_k+1) / (n+|Vocabulary|)$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== $chi^2$检验
&amp;lt;chi2检验&amp;gt;
$ chi^2 (t , c) = frac(N (A D - B C)^2, (A + B) (A + C) (C + D) (B + D)) $&lt;/p&gt;
&lt;p&gt;=== svm
&amp;lt;svm&amp;gt;
下载地址：#link(&quot;http://www.csie.ntu.edu.tw/~cjlin/cgi-bin/libsvm.cgi?+http://www.csie.ntu.edu.tw/~cjlin/libsvm+tar.gz&quot;)[libsvm]&lt;/p&gt;
&lt;p&gt;之后&lt;code&gt;make&lt;/code&gt;一下，就会用三个可执行文件，&lt;code&gt;svm-scale  svm-train  svm-train&lt;/code&gt;。
分别是数据整理，模型训练和数据预测。&lt;/p&gt;
&lt;p&gt;具体参数可以直接输入&lt;code&gt;./svm-scale&lt;/code&gt;获得。&lt;/p&gt;
&lt;p&gt;更多内容可以自行百度。&lt;/p&gt;
&lt;p&gt;== 代码实现
&amp;lt;代码实现&amp;gt;
总共分为五个部分&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;trainingData();
makeDict();
makeSvm();
testingData();
printAns();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;=== 变量声明
&amp;lt;变量声明&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#define MYMODE_RW (S_IRUSR | S_IWUSR)
using namespace std;

const char *const DICT_PATH = &quot;../dict/jieba.dict.utf8&quot;;
const char *const HMM_PATH = &quot;../dict/hmm_model.utf8&quot;;
const char *const USER_DICT_PATH = &quot;../dict/user.dict.utf8&quot;;
const char *const IDF_PATH = &quot;../dict/idf.utf8&quot;;
const char *const STOP_WORD_PATH = &quot;../dict/stop_words.utf8&quot;;

const string dataSet = &quot;../../THUCNews/THUCNews/&quot;;
// const string dataSet = &quot;../../THUCNews/&quot;;
const string svmTrainData = &quot;../../THUCNews/trainData/&quot;;
const string svmTestData = &quot;../../THUCNews/testData/&quot;;

const int MAXS = 8e6 + 10;  //文件大小限制

//Vtot 总的类别数量
//maxCommon取相关性的前200的词组成词袋
//testCnt每个类别如果有2*testCnt篇文章，
//就取testCnt篇作为测试数据，
//再取testCnt篇作为训练数据

const int Vtot = 11, maxCommon = 200, testCnt = 5000;


//所有种类
string V[Vtot] = {&quot;体育&quot;, &quot;娱乐&quot;, &quot;家居&quot;, &quot;彩票&quot;, &quot;房产&quot;, &quot;教育&quot;,
                  &quot;时政&quot;, &quot;游戏&quot;, &quot;社会&quot;, &quot;科技&quot;, &quot;财经&quot;};

//dic[i]第i类文章中出现的所有词
map&amp;lt;string, int&amp;gt; dic[Vtot + 1];

//p[i][word] p(word|vj=i)
//第i类文章中，word出现的概率，做平滑处理
map&amp;lt;string, int&amp;gt; p[Vtot];

//kappa[i][word] word和第i类文章的kappa^2值
map&amp;lt;string, int&amp;gt; kappa[Vtot + 1];

//在处理svm数据集时，每个单词的编号
map&amp;lt;string, int&amp;gt; svmIndex;

//idf值
map&amp;lt;string, double&amp;gt; idf;

//fileList[i]第i类文章列表
vector&amp;lt;string&amp;gt; fileList[Vtot];

//混淆矩阵
int confusionMatrix[Vtot][Vtot];
int docs[Vtot + 1];  // docs[i]:第i类文章训练或测试的数目

int n[Vtot + 1];     // n[i]:第i类有多少个单词（有重复）

int precision[Vtot], recall[Vtot];  //准确率和回归率

//多线程id
long unsigned int thrd[Vtot];


#define dictAll dic[Vtot]

//结巴分词器
cppjieba::Jieba jieba(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH,
                      STOP_WORD_PATH);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;=== {训练数据部分}
&amp;lt;训练数据部分&amp;gt;
下面是处理数据的多线程函数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 多线程函数
 * 处理数据
 * 读入文件-&amp;gt;分词-&amp;gt;计算dic[type]-&amp;gt;计算kappa[type]
 */
void *trainingData(void *t) {
    int type = (long long)t;
    auto &amp;amp;dict = dic[type];
    auto &amp;amp;kap = kappa[type];
    char sentence[MAXS];
    for (int d = 0; d &amp;lt; docs[type]; ++d) {

        //读入文件
        string fileName = dataSet + V[type] + &quot;/&quot; + fileList[type][d];
        int fd = open(fileName.c_str(), O_RDONLY);
        int len = pread(fd, sentence, MAXS, 0);
        close(fd);
        //如果文件大小超出MAXS会出错
        if (len &amp;lt; 0) continue;
        sentence[len] = &apos;\0&apos;;
        vector&amp;lt;pair&amp;lt;string, string&amp;gt;&amp;gt; tagres;
        //分词
        jieba.Tag(sentence, tagres);

        //计算dic[type]-&amp;gt;计算kappa[type]
        set&amp;lt;string&amp;gt; thisArticle;//本篇文章单词集合
        for (auto it : tagres) {
            const auto &amp;amp;word = it.first;  //单词
            if (strstr(it.second.c_str(), &quot;n&quot;) != NULL &amp;amp;&amp;amp;
                word.length() &amp;gt; 2) {  //名词 且 长度大于一
                dict.find(word) != dict.end() ? dict[word]++ : dict[word] = 1;
                thisArticle.insert(word);
            }
        }
        for (auto it : thisArticle) {
            kap.find(it) != kap.end() ? kap[it]++ : kap[it] = 1;
        }
    }
    cout &amp;lt;&amp;lt; V[type] &amp;lt;&amp;lt; &quot;\tDone\n&quot;;
    return NULL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;想要调用上面的线程函数，需要先读取文件列表，如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//读取文件列表
void readFileLists(string dir, vector&amp;lt;string&amp;gt; &amp;amp;fileList) {
    // cout&amp;lt;&amp;lt;dir&amp;lt;&amp;lt;&apos;\n&apos;;
    DIR *d = opendir(dir.c_str());
    struct dirent *entry;
    while ((entry = readdir(d)) != NULL) {
        if (strstr(entry-&amp;gt;d_name, &quot;.txt&quot;) != NULL)
            fileList.push_back(entry-&amp;gt;d_name);
    }
    closedir(d);
}
//读取文件列表并处理训练数据
void trainingData() {
    for (int i = 0; i &amp;lt; Vtot; ++i) {
        readFileLists(dataSet + V[i], fileList[i]);
    }

    for (int i = 0; i &amp;lt; Vtot; ++i) {
        docs[i] = min(int(fileList[i].size()) / 2, testCnt);
        // testCnt方便测试

        cout &amp;lt;&amp;lt; V[i] &amp;lt;&amp;lt; &quot; 类的文件个数为 &quot; &amp;lt;&amp;lt; docs[i] &amp;lt;&amp;lt; &apos;\n&apos;;

        docs[Vtot] += docs[i];
        int res = pthread_create(&amp;amp;thrd[i], NULL, trainingData, (void *)i);
        if (res) {
            printf(&quot;Create thress NO.%d failed\n&quot;, i);
            exit(res);
        }
    }
    for (int i = 0; i &amp;lt; Vtot; ++i) {
        pthread_join(thrd[i], NULL);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;=== 生成字典部分
&amp;lt;生成字典部分&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//抽取词典，使用kappa^2检验
void makeDict() {
    for (int i = 0; i &amp;lt; Vtot; ++i) {
        vector&amp;lt;pair&amp;lt;double, string&amp;gt;&amp;gt; mostCommon;
        for (auto it : kappa[i]) {
            double A = 0, B = 0, C = 0, D = 0;
            A = it.second;
            C = docs[i] - A;
            const auto &amp;amp;word = it.first;
            for (int j = 0; j &amp;lt; Vtot; ++j)
                if (i != j) {
                    if (kappa[j].find(word) != kappa[j].end()) {
                        B += kappa[j][word];
                    }
                }
            D = docs[Vtot] - docs[i] - B;
            double k = docs[Vtot] * (A * D - B * C) * (A * D - B * C) /
                       ((A + C) * (A + B) * (B + D) * (C + D));
            mostCommon.push_back({k, word});
        }
        sort(mostCommon.begin(), mostCommon.end());

        reverse(mostCommon.begin(), mostCommon.end());
        int item = 0;

        cout &amp;lt;&amp;lt; V[i] &amp;lt;&amp;lt; &apos;:&apos;;

        for (auto it : mostCommon) {
            ++item;
            if (item &amp;gt; maxCommon) break;
            const auto &amp;amp;word = it.second;
            const int cnt = dic[i][word];
            n[i] += cnt;
            dictAll.find(word) != dictAll.end() ? dictAll[word] += cnt
                                                : dictAll[word] = cnt;
        }
        cout &amp;lt;&amp;lt; V[i] &amp;lt;&amp;lt; &quot;单词个数&quot; &amp;lt;&amp;lt; n[i] &amp;lt;&amp;lt; &apos;\n&apos;;
        n[Vtot] += n[i];

        /*for (int j = max(0, (int)mostCommon.size() - 20);
             j &amp;lt; (int)mostCommon.size(); ++j) {
            cout &amp;lt;&amp;lt; &quot;(&quot; &amp;lt;&amp;lt; mostCommon[j].first &amp;lt;&amp;lt; &apos; &apos; &amp;lt;&amp;lt; mostCommon[j].second
                 &amp;lt;&amp;lt; &quot;) &quot;;
        }
        cout &amp;lt;&amp;lt; &apos;\n&apos;;*/
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;=== 生成svm的训练数据文件和测试数据文件
&amp;lt;生成svm的训练数据文件和测试数据文件&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
/**
 * 多线程函数
 * 整理svm用到的数据文件
 * 包括测试文件和输出文件
 * 要按照libsvm要求的数据格式来
 */

void *svmData(void *t) {
    int type = (long long)t;
    //先打开训练数据文件
    int fdout = open((svmTrainData + V[type]).c_str(), O_RDWR | O_CREAT, MYMODE_RW);
    if (fdout == -1) {
        cout &amp;lt;&amp;lt; errno &amp;lt;&amp;lt; &apos;\n&apos;;
        perror(&quot;open&quot;);
    }
    auto &amp;amp;dict = dic[type];
    auto &amp;amp;kap = kappa[type];
    string outBuf;
    char sentence[MAXS];
    int fdoffset = 0;

    for (int d = 0; d &amp;lt; (int)fileList[type].size() &amp;amp;&amp;amp; d &amp;lt; 2 * docs[type]; ++d) {
        if (d == docs[type]) {  //这之后是测试数据集
            close(fdout);
            fdout = open((svmTestData + V[type]).c_str(), O_RDWR | O_CREAT, MYMODE_RW);
            fdoffset = 0;
            if (fdout == -1) {
                cout &amp;lt;&amp;lt; errno &amp;lt;&amp;lt; &apos;\n&apos;;
                perror(&quot;open&quot;);
            }
        }

        //读取文件并进行分词
        string fileName = dataSet + V[type] + &quot;/&quot; + fileList[type][d];
        int fd = open(fileName.c_str(), O_RDONLY);
        long long int len = pread(fd, sentence, MAXS, 0);
        close(fd);
        if (len &amp;lt; 0) continue;  //如果文件大小超出MAXS会出错
        sentence[len] = &apos;\0&apos;;
        // cout &amp;lt;&amp;lt; sentence &amp;lt;&amp;lt; &apos;\n&apos;;
        vector&amp;lt;pair&amp;lt;string, string&amp;gt;&amp;gt; tagres;
        jieba.Tag(sentence, tagres);

        //统计这篇文章中的词频
        map&amp;lt;string, int&amp;gt; art;
        int totArt = 0;
        for (auto it : tagres) {
            const auto &amp;amp;word = it.first;  //单词
            if (dictAll.find(word) != dictAll.end()) {
                art.find(word) != art.end() ? art[word]++ : art[word] = 1;
                totArt++;
            }
        }

        outBuf = to_string(type) + &quot; &quot;;

        for (auto it : art) {
            auto &amp;amp;word = it.first;
            double tf = it.second * 1.0 / totArt;  // tf-idf=tf*idf
            outBuf += to_string(svmIndex[word]) + &quot;:&quot; +
                      to_string(tf * idf[word]) + &quot; &quot;;
        }
        outBuf += &apos;\n&apos;;
        pwrite(fdout, outBuf.c_str(), outBuf.length(), fdoffset);
        fdoffset += outBuf.length();
    }
    close(fdout);
    return NULL;
}
/**
 * 处理svm用到的数据
 */
void makeSvm() {
    //对单词进行编号，并计算idf值
    int item = 0;
    for (auto it : dictAll) {
        auto &amp;amp;word = it.first;
        svmIndex[word] = ++item;
        for (int i = 0; i &amp;lt; Vtot; ++i) {
            if (kappa[i].find(word) != kappa[i].end()) {
                idf.find(word) != idf.end() ? idf[word] += kappa[i][word]
                                            : idf[word] = kappa[i][word];
            }
        }
    }
    for (auto &amp;amp;it : idf) {
        it.second = log10(docs[Vtot] * 2 / (it.second + 1));
    }

    //多线程处理数据，每个线程处理一个类别的数据
    for (int i = 0; i &amp;lt; Vtot; ++i) {
        int res = pthread_create(&amp;amp;thrd[i], NULL, svmData, (void *)i);
        if (res) {
            printf(&quot;Create thress NO.%d failed\n&quot;, i);
            exit(res);
        }
    }
    for (int i = 0; i &amp;lt; Vtot; ++i) {
        pthread_join(thrd[i], NULL);
    }

    //处理完成之后，将所有类别的数据合到一起
    char buf[MAXS];
    int fdout =
        open((svmTrainData + &quot;trainData.txt&quot;).c_str(), O_RDWR | O_CREAT, MYMODE_RW);
    if (fdout == -1) {
        cout &amp;lt;&amp;lt; errno &amp;lt;&amp;lt; &apos;\n&apos;;
        perror(&quot;open&quot;);
    }

    off_t off = 0;
    for (int i = 0; i &amp;lt; Vtot; ++i) {
        int fd = open((svmTrainData + V[i]).c_str(), O_RDONLY);
        int len = pread(fd, buf, MAXS, 0);
        close(fd);
        if (len &amp;lt; 0) continue;
        buf[len] = &apos;\0&apos;;
        pwrite(fdout, buf, strlen(buf), off);
        cout &amp;lt;&amp;lt; strlen(buf) &amp;lt;&amp;lt; &apos;\n&apos;;
        off += strlen(buf);
        cout &amp;lt;&amp;lt; V[i] &amp;lt;&amp;lt; &apos; &apos; &amp;lt;&amp;lt; strlen(buf) &amp;lt;&amp;lt; &apos; &apos; &amp;lt;&amp;lt; off &amp;lt;&amp;lt; &apos;\n&apos;;
    }
    close(fdout);

    fdout = open((svmTestData + &quot;testData.txt&quot;).c_str(), O_RDWR | O_CREAT, MYMODE_RW);
    off = 0;
    for (int i = 0; i &amp;lt; Vtot; ++i) {
        int fd = open((svmTestData + V[i]).c_str(), O_RDONLY);
        int len = pread(fd, buf, MAXS, 0);
        close(fd);
        if (len &amp;lt; 0) continue;
        buf[len] = &apos;\0&apos;;
        pwrite(fdout, buf, strlen(buf), off);
        off += strlen(buf);
        cout &amp;lt;&amp;lt; V[i] &amp;lt;&amp;lt; &apos; &apos; &amp;lt;&amp;lt; strlen(buf) &amp;lt;&amp;lt; &apos; &apos; &amp;lt;&amp;lt; off &amp;lt;&amp;lt; &apos;\n&apos;;
    }
    close(fdout);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;=== 进行文本分类
&amp;lt;进行文本分类&amp;gt;
先预处理出p的值，&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 朴素贝叶斯分类器
 */
void *bayes(void *t) {
    int type = (long long)t;
    cout &amp;lt;&amp;lt; &quot;classfiying &quot; &amp;lt;&amp;lt; V[type] &amp;lt;&amp;lt; &quot;\n&quot;;
    char sentence[MAXS];

    for (int f = docs[type]; f &amp;lt; (int)fileList[type].size(); ++f) {
        if (f - docs[type] &amp;gt; docs[type]) break;

        //读取文件
        string fileName = dataSet + V[type] + &quot;/&quot; + fileList[type][f];
        int fd = open(fileName.c_str(), O_RDONLY);
        int len = pread(fd, sentence, MAXS, 0);
        close(fd);
        //分词
        if (len &amp;lt; 0) continue;  //读入出错，buf不够之类的
        sentence[len] = &apos;\0&apos;;
        vector&amp;lt;pair&amp;lt;string, string&amp;gt;&amp;gt; tagres;
        jieba.Tag(string(sentence), tagres);

        //计算argmax(p)
        pair&amp;lt;double, int&amp;gt; ans[Vtot];
        for (int i = 0; i &amp;lt; Vtot; ++i) {
            ans[i] = {docs[i] * 1.0 / docs[Vtot], i};
        }
        for (auto it : tagres) {
            if (dictAll.find(it.first) != dictAll.end()) {
                for (int i = 0; i &amp;lt; Vtot; ++i) {
                    ans[i].first += p[i][it.first];
                }
            }
        }
        sort(ans, ans + Vtot);
        confusionMatrix[type][ans[Vtot - 1].second] += 1;
    }
    cout &amp;lt;&amp;lt; &quot;classfiy &quot; &amp;lt;&amp;lt; V[type] &amp;lt;&amp;lt; &quot;\tdone\n&quot;;
    return NULL;
}

//训练数据
void testingData() {

    //预处理p[i][word]值
    cout &amp;lt;&amp;lt; &quot;字典长度=&quot; &amp;lt;&amp;lt; dictAll.size() &amp;lt;&amp;lt; &apos;\n&apos;;
    for (int vj = 0; vj &amp;lt; Vtot; ++vj) {
        for (auto it : dictAll) {
            const auto &amp;amp;word = it.first;
            if (dic[vj].find(word) != dic[vj].end()) {
                p[vj][word] =
                    log10(dic[vj][word] + 1) - log10(n[vj] + dictAll.size());
            } else {
                p[vj][word] = log10(1) - log10(n[vj] + dictAll.size());
            }
        }
    }

    //多线程进行文本分类
    for (int i = 0; i &amp;lt; Vtot; ++i) {
        int res = pthread_create(&amp;amp;thrd[i], NULL, bayes, (void *)i);
        if (res) {
            printf(&quot;Create thress NO.%d failed\n&quot;, i);
            exit(res);
        }
    }
    for (int i = 0; i &amp;lt; Vtot; ++i) {
        pthread_join(thrd[i], NULL);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;=== 输出结果
&amp;lt;输出结果&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//输出想要的答案
void printAns() {
    double avrPre = 0, avrRecall = 0;
    for (int v1 = 0; v1 &amp;lt; Vtot; ++v1) {
        cout &amp;lt;&amp;lt; V[v1] &amp;lt;&amp;lt; &quot;:\t&quot;;
        for (int v2 = 0; v2 &amp;lt; Vtot; ++v2) {
            precision[v2] += confusionMatrix[v1][v2];
            recall[v1] += confusionMatrix[v1][v2];
            cout &amp;lt;&amp;lt; confusionMatrix[v1][v2] &amp;lt;&amp;lt; &apos;\t&apos;;
        }
        cout &amp;lt;&amp;lt; &apos;\n&apos;;
    }
    for (int v = 0; v &amp;lt; Vtot; ++v) {
        cout &amp;lt;&amp;lt; V[v] &amp;lt;&amp;lt; &apos;\n&apos;;
        cout &amp;lt;&amp;lt; &quot;准确率=&quot; &amp;lt;&amp;lt; confusionMatrix[v][v] * 100.0 / precision[v]
             &amp;lt;&amp;lt; &quot;% &quot;;
        avrPre += confusionMatrix[v][v] * 100.0 / precision[v];
        cout &amp;lt;&amp;lt; &quot;召回率=&quot; &amp;lt;&amp;lt; confusionMatrix[v][v] * 100.0 / recall[v] &amp;lt;&amp;lt; &quot;%\n&quot;;
        avrRecall += confusionMatrix[v][v] * 100.0 / recall[v];
    }
    cout &amp;lt;&amp;lt; &quot;平均准确率=&quot; &amp;lt;&amp;lt; avrRecall / Vtot &amp;lt;&amp;lt; &quot;% &quot;
         &amp;lt;&amp;lt; &quot;平均召回率=&quot; &amp;lt;&amp;lt; avrRecall / Vtot &amp;lt;&amp;lt; &quot;\n&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== 实验结果
&amp;lt;实验结果&amp;gt;
先输出训练的文件个数，训练完成之后，统计每个类别有多少单词。
之后，得出字典长度为2037。&lt;/p&gt;
&lt;p&gt;#figure(
image(&quot;019/ans1.png&quot;, width: 300pt),
caption: [
结果
],
)&lt;/p&gt;
&lt;p&gt;预测完成之后是混淆矩阵。&lt;/p&gt;
&lt;p&gt;#figure(
image(&quot;019/ans2.png&quot;),
caption: [
文本分类实验 混淆矩阵
],
)&lt;/p&gt;
&lt;p&gt;输出召回率和准确率，最后输出运行时间。
#strong[可以看到最后运行时间仅为12秒，这就是c++的长处之一。]
总共分析了55000篇文章，从读入到分词，到其他计算。总共执行时间非常短。
相比于python的10min左右要好得多。&lt;/p&gt;
&lt;p&gt;#figure(
image(&quot;019/ans3.png&quot;, width: 250pt),
caption: [
结果
],
)&lt;/p&gt;
&lt;p&gt;最后的实验结果也比较理想。准确率和召回率都比较高。&lt;/p&gt;
&lt;p&gt;== svm
&amp;lt;svm-1&amp;gt;
这里我并没有用到梯度预测、n折交叉验证（训练时间太长，实验时间不够）。
但我们完整实现了用libsvm的工具进行文本分类的必要步骤。以下均在&lt;code&gt;libsvm&lt;/code&gt;目录下。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据整理。
&lt;code&gt;../THUCNews/trainData/trainData.txt&lt;/code&gt;是待训练数据的原始形式，&lt;code&gt;../THUCNews/testData/testData.txt&lt;/code&gt;是待预测数据。用&lt;code&gt;./svm-scale -l 0 -r train.scale ../THUCNews/trainData/trainData.txt&lt;/code&gt;
和&lt;code&gt;./svm-scale -l 0 -r test.scale ../THUCNews/testData/testData.txt&lt;/code&gt;命令,
可以将训练数据、测试数据整理为标准格式，且下界为0.&lt;/li&gt;
&lt;li&gt;模型训练
&lt;code&gt;./svm-train -h 0 -g 0.001 -c 10 train.scale my.model&lt;/code&gt;得到模型文件。&lt;/li&gt;
&lt;li&gt;数据预测 &lt;code&gt;./svm-predict test.scale my.model ans.out&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;结果如下&lt;/p&gt;
&lt;p&gt;#figure(
image(&quot;019/svmans.png&quot;),
caption: [
svman
],
)&lt;/p&gt;
&lt;p&gt;实验结果较为理想。当然，该方法还有很多可以改进的空间，这里就不深究。&lt;/p&gt;
&lt;p&gt;== {代码}
&amp;lt;代码&amp;gt;
#link(&quot;https://paste.ubuntu.com/p/nGWbQWmttr/&quot;)[超链接]&lt;/p&gt;
&lt;p&gt;#link(&quot;https://paste.ubuntu.com/p/p9KQmh8Q2T/&quot;)[之前一版]&lt;/p&gt;
</content:encoded></item><item><title>康托展开及康托逆展开</title><link>https://dashuai009.github.io/blog/021/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/021/</guid><description>康托展开及康托逆展开</description><pubDate>Fri, 08 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 3,
day: 14,
)
#metadata((
&quot;title&quot;: &quot;康托展开及康托逆展开&quot;,
&quot;author&quot;: &quot;dashuai009&quot;,
description: &quot;康托展开及康托逆展开&quot;,
pubDate: &quot;&apos;Jul 08 2022&apos;&quot;,
subtitle: [math],
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;== 题目描述
&amp;lt;题目描述&amp;gt;
小 G 喜欢玩排列。现在他手头有两个 $n$ 的排列。$n$ 的排列是由
$0 , 1 , 2 , . . . , n - 1$这 n 的数字组成的。对于一个排列
$p$，$O r d e r (p)$ 表示 $p$ 是字典序第 $O r d e r (p)$ 小的&lt;/p&gt;
&lt;p&gt;排列（从 0 开始计数）。对于小于 $n !$ 的非负数 $x$，$P e r m (x)$
表示字典序第 $x$ 小的排列。&lt;/p&gt;
&lt;p&gt;现在，小 G 想求一下他手头两个排列的和。两个排列 $p$ 和 $q$ 的和为
$s u m = P e r m ((O r d e r (p) + O r d e r (q)) % n !)$&lt;/p&gt;
&lt;p&gt;== 输入输出格式
&amp;lt;输入输出格式&amp;gt;
=== 输入格式：
&amp;lt;输入格式&amp;gt;
输入文件第一行一个数字 n，含义如题。&lt;/p&gt;
&lt;p&gt;接下来两行，每行 n 个用空格隔开的数字，表示小 G 手头的两个排列。&lt;/p&gt;
&lt;p&gt;=== 输出格式：
&amp;lt;输出格式&amp;gt;
输出一行 n 个数字，用空格隔开，表示两个排列的和&lt;/p&gt;
&lt;p&gt;== 输入输出样例
&amp;lt;输入输出样例&amp;gt;
=== 输入样例#1：
&amp;lt;输入样例1&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2
0 1
1 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;=== 输出样例#1：
&amp;lt;输出样例1&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;=== 输入样例#2：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
1 2 0
2 1 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;=== 输出样例#2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 0 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== 说明
&amp;lt;说明&amp;gt;
1、2、3、4 测试点，$1 lt.eq n lt.eq 10$。&lt;/p&gt;
&lt;p&gt;5、6、7 测试点，$1 lt.eq n lt.eq 5000$，保证第二个排列的
$O r d e r lt.eq 10^5$ 。&lt;/p&gt;
&lt;p&gt;8、9、10 测试点，$1 lt.eq n lt.eq 5000$&lt;/p&gt;
&lt;p&gt;== code&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;cstdio&amp;gt;
#include &amp;lt;cstring&amp;gt;
#include &amp;lt;cmath&amp;gt;
#include &amp;lt;cstdlib&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;algorithm&amp;gt;
using namespace std;

const int max_n = 5e4 + 10;
int cnt[max_n], p1[max_n], p2[max_n], pp1[max_n], pp2[max_n], ans[max_n];
int n;

inline int getnum()
{
    int ans = 0; char c; bool flag = false;
    while ((c = getchar()) == &apos; &apos; || c == &apos;\n&apos; || c == &apos;\r&apos;);
    if (c == &apos;-&apos;) flag = true; else ans = c - &apos;0&apos;;
    while (isdigit(c = getchar())) ans = ans * 10 + c - &apos;0&apos;;
    return ans * (flag ? -1 : 1);
}

int main()
{
    n = getnum();
    for (int i = n; i &amp;gt;= 1; i--) p1[i] = getnum();
    for (int i = n; i &amp;gt;= 1; i--) p2[i] = getnum();

    for (int i = 1; i &amp;lt;= n; i++)
        for (int j = 1; j &amp;lt; i; j++)
            if (p1[j] &amp;lt; p1[i]) pp1[i]++;
    for (int i = 1; i &amp;lt;= n; i++)
        for (int j = 1; j &amp;lt; i; j++)
            if (p2[j] &amp;lt; p2[i]) pp2[i]++;

    for (int i = 2; i &amp;lt;= n; i++)
    {
        ans[i] += pp1[i] + pp2[i];
        ans[i + 1] += ans[i] / i;
        ans[i] %= i;
    }

    for (int i = n; i &amp;gt;= 1; i--)
    {
        int _ = -1;
        while (ans[i] &amp;gt;= 0)
        {
            _++;
            if (!cnt[_]) ans[i]--;
        }
        printf(&quot;%d &quot;, _);
        cnt[_] = 1;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== 康拓展开：
&amp;lt;康拓展开&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const int PermSize = 12;
long long factory[PermSize] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800 };
long long Cantor(string buf) {
  int i, j, counted;
  long long result = 0;
  for (i = 0; i&amp;lt;PermSize; ++i) {
    counted = 0;
    for (j = i + 1; j&amp;lt;PermSize; ++j){
      if (buf[i]&amp;gt;buf[j])++counted;
    }
    result = result + counted*factory[PermSize - i - 1];
  }
  return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== 康托逆展开：
&amp;lt;康托逆展开&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/*返回由前n个数[1, n]组成的全排列中第m大的排列。*/
vector&amp;lt;int&amp;gt; deCantor(int n, int m) {
    vector&amp;lt;int&amp;gt; res;
    long long buf = 0;
    m--;
    for(int f = 0; n &amp;gt; 0; n--) {
        f = m / facts[n - 1];
        m = m % facts[n - 1];
        while(buf &amp;amp; (1 &amp;lt;&amp;lt; (f + 1)))f++;
        res.push_back(f + 1);
        buf |= (1 &amp;lt;&amp;lt; (f + 1));
    }
    return res;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>卡特兰数</title><link>https://dashuai009.github.io/blog/020/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/020/</guid><description>简单介绍卡特兰数</description><pubDate>Fri, 08 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 3,
day: 14,
)
#metadata((
&quot;title&quot;: &quot;卡特兰数&quot;,
&quot;author&quot;: &quot;dashuai009&quot;,
description: &quot;简单介绍卡特兰数&quot;,
pubDate: &quot;&apos;Jul 08 2022&apos;&quot;,
subtitle: [Catalan,math],
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;以下搬运自#link(&quot;https://zh.wikipedia.org/wiki/%E5%8D%A1%E5%A1%94%E5%85%B0%E6%95%B0&quot;)[wikipedia]&lt;/p&gt;
&lt;p&gt;== 卡特兰数
&amp;lt;卡特兰数&amp;gt;
$C_n= 1 / (n+1) binom(2n, n)= (2n)! / ((n+1)!n!),n=1,2,3,...$&lt;/p&gt;
&lt;p&gt;== 另一种表示形式
&amp;lt;另一种表示形式&amp;gt;
$C_n= binom(2n, n) - binom(2n, n+1)$&lt;/p&gt;
&lt;p&gt;所以，Cn是一个自然数；这一点在先前的通项公式中并不显而易见。&lt;/p&gt;
&lt;p&gt;== 递推关系
&amp;lt;递推关系&amp;gt;
$
C_0 &amp;amp; = 1&lt;br /&gt;
C_(n + 1) &amp;amp; = sum_(i = 0)^n C_i C_(n - i)
$&lt;/p&gt;
&lt;p&gt;$
C_0 &amp;amp; = 1&lt;br /&gt;
C_(n + 1) &amp;amp; = frac(2 (2 n + 1), n + 1) C_n
$&lt;/p&gt;
&lt;p&gt;== 卡塔兰数的渐近增长
&amp;lt;卡塔兰数的渐近增长&amp;gt;
$C_n tilde.op frac(4^n, n^(3 / 2) sqrt(pi))$&lt;/p&gt;
&lt;p&gt;== 所有的奇卡塔兰数$C_n$都满足$n = 2^k - 1$。所有其他的卡塔兰数都是偶数。
&amp;lt;所有的奇卡塔兰数c_n都满足displaystyle-n2k-1所有其他的卡塔兰数都是偶数&amp;gt;
== emm
&amp;lt;emm&amp;gt;
$C_n = integral_0^4 x^n frac(1, 2 pi) sqrt(4 / x - 1)$&lt;/p&gt;
&lt;p&gt;== 母函数
&amp;lt;母函数&amp;gt;
$
M (x) &amp;amp; = sum_(n gt.eq 0) C_n X^n&lt;br /&gt;
&amp;amp; = 1 + sum_(n gt.eq 1) sum_(i = 0)^(n - 1) C_i x^i C_(n - i - 1) x^(n - i - 1) x&lt;br /&gt;
&amp;amp; = 1 + x sum_(i gt.eq 0) C_i x^i sum_(n gt.eq 0) C_n x^n&lt;br /&gt;
&amp;amp; = 1 + x M^2 (x)&lt;br /&gt;
M (x) &amp;amp; = 1 + x M (x)^2&lt;br /&gt;
M (x) &amp;amp; = frac(2, 1 + sqrt(1 - 4 x)) = frac(1 - sqrt(1 - 4 x), 2 x)
$&lt;/p&gt;
&lt;p&gt;生成式的另一个解可以用M(0)特判掉。&lt;/p&gt;
&lt;p&gt;== 广义二项式定理
&amp;lt;广义二项式定理&amp;gt;
二项式定理: $(x + y)^n = sum_(k = 0)^n (binom(n, k) x^(n - k) y^k)$,
其中$binom(n, k)$是组合数.&lt;/p&gt;
&lt;p&gt;当$n$不是正整数时, $k$无法正好求和到$n$, 因此将一直求和至正无穷,
这样形式上就得到了广义二项式定理:
$(x + y)^alpha = sum_(k = 0)^oo binom(alpha, k) x^(alpha - k) y^k$,
其中$binom(alpha, k) = frac(alpha (alpha - 1) . . . (alpha - k + 1), k !) = frac((alpha)^(underline(k)), k !)$是形式上的组合数.&lt;/p&gt;
&lt;p&gt;== 展开形式
&amp;lt;展开形式&amp;gt;
先展开$sqrt(1 - 4 x)$,&lt;/p&gt;
&lt;p&gt;$
(1 - 4 x)^(1 / 2) &amp;amp; = sum_(n gt.eq 0) binom(1 / 2, n) (- 4 x)^n&lt;br /&gt;
&amp;amp; = 1 + sum_(n gt.eq 1) frac(1 / 2^(underline(n)), n !) (- 4 x)^n&lt;br /&gt;
$&lt;/p&gt;
&lt;p&gt;其中，&lt;/p&gt;
&lt;p&gt;$
(1 / 2)^(underline(n)) &amp;amp; = 1 / 2 frac(- 1, 2) frac(- 3, 2) . . . frac(- (2 n - 3), 2)&lt;br /&gt;
&amp;amp; = frac((- 1)^(n - 1) (2 n - 3) ! !, 2^n)&lt;br /&gt;
&amp;amp; = frac((- 1)^(n - 1) (2 n - 2) !, 2^n (2 n - 2) ! !)&lt;br /&gt;
&amp;amp; = frac((- 1)^(n - 1) (2 n - 2) !, 2^(2 n - 1) (n - 1) !)&lt;br /&gt;
$&lt;/p&gt;
&lt;p&gt;则&lt;/p&gt;
&lt;p&gt;$
(1 - 4 x)^(1 / 2) &amp;amp; = 1 + sum_(n gt.eq 1) frac((- 1)^(n - 1) (2 n - 2) !, 2^(2 n - 1) (n - 1) ! n !) (- 4 x)^n&lt;br /&gt;
&amp;amp; = 1 - sum_(n gt.eq 1) frac((2 n - 2) !, (n - 1) ! n !) 2 x^n&lt;br /&gt;
&amp;amp; = 1 - sum_(n gt.eq 1) binom(2 n - 1, n) frac(1, 2 n - 1) 2 x^n&lt;br /&gt;
$&lt;/p&gt;
&lt;p&gt;带回&lt;/p&gt;
&lt;p&gt;$
M (x) &amp;amp; = frac(1 - sqrt(1 - 4 x), 2 x)&lt;br /&gt;
&amp;amp; = frac(1, 2 x) sum_(n gt.eq 1) binom(2 n - 1, n) frac(1, 2 n - 1) 2 x^n&lt;br /&gt;
&amp;amp; = sum_(n gt.eq 1) binom(2 n - 1, n) frac(1, 2 n - 1) x^(n - 1)&lt;br /&gt;
&amp;amp; = sum_(n gt.eq 0) binom(2 n + 1, n + 1) frac(1, 2 n + 1) x^n&lt;br /&gt;
&amp;amp; = sum_(n gt.eq 0) binom(2 n, n) frac(1, n + 1) x^n
$&lt;/p&gt;
&lt;p&gt;即可得到通项公式&lt;/p&gt;
&lt;p&gt;== 应用
&amp;lt;应用&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cn表示长度2n的dyck
word[7]的个数。Dyck词是一个有n个X和n个Y组成的字串，且所有的前缀字串皆满足X的个数大于等于Y的个数。&lt;/li&gt;
&lt;li&gt;将上例的X换成左括号，Y换成右括号，Cn表示所有包含n组括号的合法运算式的个数：&lt;/li&gt;
&lt;li&gt;Cn表示有n个节点组成不同构二叉树的方案数。&lt;/li&gt;
&lt;li&gt;Cn表示有2n+1个节点组成不同构满二叉树的方案数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;#strong[证明];：
令1表示进栈，0表示出栈，则可转化为求一个2n位、含n个1、n个0的二进制数，满足从左往右扫描到任意一位时，经过的0数不多于1数。显然含n个1、n个0的2n位二进制数共有$binom(2 n, n)$个，下面考虑不满足要求的数目。&lt;/p&gt;
&lt;p&gt;考虑一个含n个1、n个0的2n位二进制数，扫描到第2m+1位上时有m+1个0和m个1（容易证明一定存在这样的情况），则后面的0-1排列中必有n-m个1和n-m-1个0。将2m+2及其以后的部分0变成1、1变成0，则对应一个n+1个0和n-1个1的二进制数。反之亦然（相似的思路证明两者一一对应）。&lt;/p&gt;
&lt;p&gt;从而$C_n = binom(2 n, n) - binom(2 n, n + 1) = frac(1, n + 1) binom(2 n, n)$。证毕。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cn表示所有在n × n格点中不越过对角线的单调路径的个数。&lt;/li&gt;
&lt;li&gt;Cn表示通过连结顶点而将n + 2边的凸多边形分成三角形的方法个数。&lt;/li&gt;
&lt;li&gt;Cn表示对{1, …, n}依序进出栈的置换个数。一个置换w是依序进出栈的当S(w) =
(1, …, n),其中S（w）递归定义如下：令w =
unv，其中n为w的最大元素，u和v为更短的数列；再令S(w) = S(u)S(v)n，其中S
为所有含一个元素的数列的单位元。&lt;/li&gt;
&lt;li&gt;Cn表示集合{1, …, n}的不交叉划分的个数。那么, Cn永远不大于第n项贝尔数.
Cn也表示集合{1, …,
2n}的不交叉划分的个数，其中每个段落的长度为2。综合这两个结论，可以用数学归纳法证明：在
魏格纳半圆分布定律 中度数大于2的情形下，所有 自由的 累积量s 为零。
该定律在自由概率论和随机矩阵理论中非常重要。&lt;/li&gt;
&lt;li&gt;Cn表示用n个长方形填充一个高度为n的阶梯状图形的方法个数。&lt;/li&gt;
&lt;li&gt;Cn表示表为2×n的矩阵的标准杨氏矩阵的数量。 也就是说，它是数字 1, 2, …,
2n
被放置在一个2×n的矩形中并保证每行每列的数字升序排列的方案数。同样的，该式可由勾长公式的一个特殊情形推导得出。&lt;/li&gt;
&lt;li&gt;Cn表示n个无标号物品的半序的个数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;== 汗科尔矩阵
&amp;lt;汗科尔矩阵&amp;gt;
== 超级卡特兰数（大施罗德数）
&amp;lt;超级卡特兰数大施罗德数&amp;gt;
=== 递推式
&amp;lt;递推式&amp;gt;
$
&amp;amp; S_1 = 1&lt;br /&gt;
&amp;amp; forall i gt.eq 2 , s_i = S_(i - 1) + sum_(i = 1)^(n - 1) S_i S_(n - i)
$&lt;/p&gt;
&lt;p&gt;=== 生成函数
&amp;lt;生成函数&amp;gt;
$ S (x) = x S (x) + S^2 (x) + x $&lt;/p&gt;
&lt;p&gt;解得&lt;/p&gt;
&lt;p&gt;$ S (x) = frac(1 - x - sqrt(x^2 - 6 x + 1), 2) $&lt;/p&gt;
&lt;p&gt;=== 化简求通向公式
&amp;lt;化简求通向公式&amp;gt;
广义二项式&lt;/p&gt;
&lt;p&gt;=== 通项公式
&amp;lt;通项公式&amp;gt;
$ S_n = sum_(i = 1)^n binom(n + i - 1, 2 i) C_i $&lt;/p&gt;
&lt;p&gt;=== 一种方法
&amp;lt;一种方法&amp;gt;
接下来给出的一种方法,可以在$O (n k)$的复杂度快速求出$k$次多项式开方后前$n$项的值&lt;/p&gt;
&lt;p&gt;设要开方的多项式为$P (x)$,开方后的多项式为$F (x)$.&lt;/p&gt;
&lt;p&gt;$ F (x) = P (x)^(1 / 2) = sum_(i = 0)^oo f_i x^i $&lt;/p&gt;
&lt;p&gt;两边求导,可得&lt;/p&gt;
&lt;p&gt;$ F prime (x) = 1 / 2 P (x)^(- 1 / 2) P prime (x) $&lt;/p&gt;
&lt;p&gt;得&lt;/p&gt;
&lt;p&gt;$ F (x) P prime (x) = 2 F prime (x) P (x) $&lt;/p&gt;
&lt;p&gt;对比每一项系数,不难得到$k + 1$项的递推式.&lt;/p&gt;
&lt;p&gt;=== 递推式
&amp;lt;递推式-1&amp;gt;
$ (n + 1) f_(n + 1) = (6 n - 3) f_n - (n - 2) f_(n - 1) $&lt;/p&gt;
&lt;p&gt;不过要注意这个递推公式除了第一项其他的项都是超级卡特兰数的$1 / 2$&lt;/p&gt;
&lt;p&gt;=== 组合意义
&amp;lt;组合意义&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;$n + 1$边形的任意剖分方案数。#link(&quot;http://noi.openjudge.cn/ch0206/9282/&quot;)[例题链接]
还是考虑枚举一条多边形上的边,那么有可能这条边仍然与另一个点拉成一个三角形,也有可能这条边上没有,那么把这两种加起来就是定义的递推式&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;括号序列,每个位置可能左括号,右括号和0.括号对数与0的个数之和为$n - 1$,
问合法的括号序列数. 同样枚举第一个是左括号还是0.
值得一提的是,可以看成先插括号再插0,那么枚举括号对数i,0有$n - i - 1$个,需要放入$2 i + 1$个位置中,那么不难得到上面通过广义二项式推出来的式子.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;从$(0 , 0)$到$(n - 1 , n - 1)$,每次能往右,往上,往右上走,求不超过$y = x$这条直线的方案数.
枚举第一次是走右上还是走右.
同样使用容斥思想,把答案转化为从$(0 , 0)$到$(n - 1 , n - 1)$减去(-1,1)到$(n - 1 , n - 1)$.
那么从$(0 , 0)$到$(n , m)$的路径条数怎么求呢.可以考虑枚举往右上的次数,然后还是考虑先走右和上,然后再把走右上的插入即可.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>错排问题</title><link>https://dashuai009.github.io/blog/023/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/023/</guid><description>考虑一个有n个元素的排列，若一个排列中所有的元素都不在自己原来的位置上，那么这样的排列就称为原排列的一个错排。</description><pubDate>Fri, 08 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 3,
day: 14,
)
#metadata((
title: &quot;错排问题&quot;,
subtitle: [错排问题,欧拉信封问题],
author: &quot;dashuai009&quot;,
abstract: &quot;&quot;,
description: &quot;考虑一个有n个元素的排列，若一个排列中所有的元素都不在自己原来的位置上，那么这样的排列就称为原排列的一个错排。&quot;,
pubDate: &quot;&apos;Jul 08 2022&apos;&quot;,
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;== 定义
&amp;lt;定义&amp;gt;
考虑一个有$n$个元素的排列，若一个排列中所有的元素都不在自己原来的位置上，那么这样的排列就称为原排列的一个错排。
$n$个元素的错排数记为$D_n$或$! n$。&lt;/p&gt;
&lt;p&gt;== 例子
&amp;lt;例子&amp;gt;
伯努利-欧拉的装错信封的问题。&lt;/p&gt;
&lt;p&gt;== 形式化定义
&amp;lt;形式化定义&amp;gt;
记$D_n$为${ 1 , 2 , dots.h , n }$上没有不动点的排列(即$phi.alt : { 1 , 2 , dots.h , n } arrow.r { 1 , 2 , dots.h , n } , med phi.alt (i) eq.not i , med forall 1 lt.eq i lt.eq n$的个数，$D_n$的值如下：（由$n = 1$起）
$0 , 1 , 2 , 9 , 44 , 265 , 1854 , 14833 , 133496 , 1334961 , 14684570 , 176214841 , 2290792932 . . .$&lt;/p&gt;
&lt;p&gt;== 规律
&amp;lt;规律&amp;gt;
$D_n = n D_(n - 1) + (- 1)^n$&lt;/p&gt;
&lt;p&gt;== 递推数列法
&amp;lt;递推数列法&amp;gt;
显然$D_1 = 0 , D_2 = 1$。&lt;/p&gt;
&lt;p&gt;当$n gt.eq 3$时，不妨设n排在了第k位，其中$k eq.not n$，也就是$1 lt.eq k lt.eq n - 1$。那么我们现在考虑$k$的情况。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当k排在第$n$位时，除了$n$和$k$以外还有$n - 2$个数，其错排数为$D_(n - 2)$。&lt;/li&gt;
&lt;li&gt;当$k$不排在第$n$位时，那么将第$n$位重新考虑成一个新的“第$k$位”，这时的包括$k$在内的剩下$n - 1$个数的每一种错排，都等价于只有$n - 1$个数时的错排（只是其中的第k位会换成第n位）。其错排数为$D_(n - 1)$。
所以当n排在第$k$位时共有$D_(n - 2) + D_(n - 1)$种错排方法，又$k$有从$1$到$n - 1$共$n - 1$种取法，我们可以得到：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$D_n = (n - 1) (D_(n - 1) + D_(n - 2))$&lt;/p&gt;
&lt;p&gt;== 生成函数&lt;/p&gt;
&lt;p&gt;=== 一
&amp;lt;一&amp;gt;
根据规律 $D_n = n D_(n - 1) + (- 1)^n$可以得到如下：&lt;/p&gt;
&lt;p&gt;$
D (x) &amp;amp; = x D (x) + e^(- x)&lt;br /&gt;
&amp;amp; = frac(e^(- x), 1 - x)&lt;br /&gt;
&amp;amp; = frac(1 - x + frac(x^2, 2 !) + . . . + (- 1)^n frac(x^n, n !) + . . ., 1 - x)
$&lt;/p&gt;
&lt;p&gt;=== 二
&amp;lt;二&amp;gt;
从置换环的角度考虑，错排就是指置换环中不存在自环的排列。也就是说不存在长度为
$1$ 的置换环。后者的指数生成函数是&lt;/p&gt;
&lt;p&gt;$ sum_(n gt.eq 2) x^n / n = - ln (1 - x) - x $&lt;/p&gt;
&lt;p&gt;因此错排数的指数生成函数就是 $exp (- ln (1 - x) - x)$。&lt;/p&gt;
&lt;p&gt;== 减掉非错排的情况
&amp;lt;减掉非错排的情况&amp;gt;
非错排：存在$p_i = i$的排列&lt;/p&gt;
&lt;p&gt;只存在一个：$C_n^1 D_(n - 1)$,&lt;/p&gt;
&lt;p&gt;只存在二个：$C_n^2 D_(n - 2)$,&lt;/p&gt;
&lt;p&gt;只存在三个：$C_n^3 D_(n - 3)$,&lt;/p&gt;
&lt;p&gt;…&lt;/p&gt;
&lt;p&gt;只存在n个：$C_n^n D_(n - n)$,&lt;/p&gt;
&lt;p&gt;所以，
$
D_n &amp;amp; = n ! - (C_n^1 D_(n - 1) + C_n^2 D_(n - 2) + C_n^3 D_(n - 3) + . . . + C_n^n D_(n - n))&lt;br /&gt;
&amp;amp; = sum_(k = 0)^n binom(n, k) (n - k) !
$&lt;/p&gt;
&lt;p&gt;#strike[那一定是你程序写错了]&lt;/p&gt;
&lt;p&gt;== 通向公式
&amp;lt;通向公式&amp;gt;
令$D_n = n ! M_n$&lt;/p&gt;
&lt;p&gt;$M_n - M_(n - 1) = (- 1)^n frac(1, n !)$&lt;/p&gt;
&lt;p&gt;$D_n = n ! (frac(1, 2 !) - frac(1, 3 !) + frac(1, 4 !) - . . . + (- 1)^n frac(1, n !))$&lt;/p&gt;
&lt;p&gt;== #link(&quot;https://zh.wikipedia.org/wiki/%E9%94%99%E6%8E%92%E9%97%AE%E9%A2%98#%E5%A4%9A%E9%A1%B9%E5%BC%8F%E6%A8%A1%E6%8B%9F&quot;)[多项式模拟]
&amp;lt;多项式模拟&amp;gt;
== $D_n$最接近 $frac(n !, e)$ 的整数
&amp;lt;d_n最接近-displaystyle-frac-ne-的整数&amp;gt;
考虑指数函数在 0 处的泰勒展开：&lt;/p&gt;
&lt;p&gt;$
e^(- 1) &amp;amp; = 1 + frac((- 1)^1, 1 !) + frac((- 1)^2, 2 !) + frac((- 1)^3, 3 !) + . . . + frac((- 1)^n, n !) + frac(e^(- c), (n + 1) !) (
c - 1
)^n&lt;br /&gt;
&amp;amp; = frac(D_n, n !) + R_n
$ 其中
$ lr(|R_n|) lt.eq frac(e^0, (n + 1) !) = frac(1, (n + 1) !) $ 所以
$ lr(|frac(n !, e) - D_n|) lt.eq frac(n !, (n + 1) !) = frac(1, n + 1) $
得 $ D_n = ⌊frac(n !, e) + 0.5⌋ $&lt;/p&gt;
</content:encoded></item><item><title>A angular binding for lexical</title><link>https://dashuai009.github.io/blog/025/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/025/</guid><description>Create a lexical&apos;s decorate node using angular custom element. We can build a text editor for angular based on lexical.</description><pubDate>Fri, 08 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#metadata((
title: &quot;A angular binding for lexical&quot;,
subtitle: [lexical,angular],
author: &quot;dashuai009&quot;,
description: &quot;Create a lexical&apos;s decorate node using angular custom element. We can build a text editor for angular based on lexical.&quot;,
pubDate: &quot;&apos;Jul 08 2022&apos;&quot;,
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;#link(&quot;https://angular.io/guide/element&quot;)[angular custom element]&lt;/p&gt;
&lt;p&gt;== create a normal angular compment
&amp;lt;create-a-normal-angular-compment&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Component({
  selector:&apos;lexical-hr&apos;,
  template:&apos;&amp;lt;hr/&amp;gt;&apos;
})
export class LexicalHr{
  constructor() {
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== declare golcal interface
&amp;lt;declare-golcal-interface&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;declare global {
  interface HTMLElementTagNameMap {
    &apos;my-dialog&apos;: NgElement &amp;amp; WithProperties&amp;lt;{content: string}&amp;gt;;
    &apos;my-other-element&apos;: NgElement &amp;amp; WithProperties&amp;lt;{foo: &apos;bar&apos;}&amp;gt;;
    // &apos;lexical-image&apos;:NgElement &amp;amp; WithProperties&amp;lt;ImageComponent&amp;gt;
    &apos;lexical-hr&apos;:NgElement &amp;amp; WithProperties&amp;lt;LexicalHr&amp;gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== Register the custom element with the browser
&amp;lt;register-the-custom-element-with-the-browser&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export class AppComponent{
  constructor(private injector:Injector) {
    const LexicalHrElement = createCustomElement(LexicalHr, {injector});
    customElements.define(&apos;lexical-hr&apos;, LexicalHrElement);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;== create lexical’s decorate node
&amp;lt;create-lexicals-decorate-node&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const INSERT_HORIZONTAL_RULE_COMMAND: LexicalCommand&amp;lt;void&amp;gt; =
  createCommand();

export class HorizontalRuleNode extends DecoratorNode&amp;lt;NgElement &amp;amp; WithProperties&amp;lt;LexicalHr&amp;gt;&amp;gt; {
  static getType(): string {
    return &apos;horizontalrule&apos;;
  }

  static clone(node: HorizontalRuleNode): HorizontalRuleNode {
    return new HorizontalRuleNode(node.__key);
  }

  static importDOM(): DOMConversionMap | null {
    return {
      hr: (node: Node) =&amp;gt; ({
        conversion: convertHorizontalRuleElement,
        priority: 0,
      }),
    };
  }

  override exportDOM(): DOMExportOutput {
    return {element: document.createElement(&apos;hr&apos;)};
  }

  override createDOM(): HTMLElement {
    const div = document.createElement(&apos;div&apos;);
    div.style.display = &apos;contents&apos;;
    return div;
  }

  override getTextContent() {
    return &apos;\n&apos;;
  }

  override isTopLevel() {
    return true;
  }

  override updateDOM() {
    return false;
  }

  override decorate():any{
    const myHrNode = document.createElement(&apos;lexical-hr&apos;);
    return myHrNode;
  }
}

function convertHorizontalRuleElement(): DOMConversionOutput {
  return {node: $createHorizontalRuleNode()};
}

export function $createHorizontalRuleNode(): HorizontalRuleNode {
  return new HorizontalRuleNode();
}

export function $isHorizontalRuleNode(node: LexicalNode | null) {
  return node instanceof HorizontalRuleNode;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The type of &lt;code&gt;myHrNode&lt;/code&gt; returned by decorate() is
&lt;code&gt;NgElement &amp;amp; WithProperties&amp;lt;LexicalHr&amp;gt;&lt;/code&gt;, these custom elements will have
a property for each input of the corresponding component.&lt;/p&gt;
&lt;p&gt;== register decorator listener
&amp;lt;register-decorator-listener&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    this.editor.registerDecoratorListener((decorator) =&amp;gt; {
      for (let i in decorator) {
        this.editor.getElementByKey(i)?.replaceChildren(decorator[i])
      }
    })
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>wsl2-arch</title><link>https://dashuai009.github.io/blog/029/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/029/</guid><description>这是一种将C++更加现代的代码组织方式。 模块是一组源代码文件，独立于导入它们的翻译单元进行编译。</description><pubDate>Mon, 20 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 6,
day: 20,
)&lt;/p&gt;
&lt;p&gt;#metadata((
title: &quot;wsl2-arch&quot;,
subtitle: [wsl2,archlinux],
author: &quot;dashuai009&quot;,
abstract: &quot;个人癖好：所有软件都要最新的！arch完美戳中了我。ArchWSL！开个blog记录新机都要装什么环境。&quot;,
description: &quot;这是一种将C++更加现代的代码组织方式。 模块是一组源代码文件，独立于导入它们的翻译单元进行编译。&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;#outline()
== ArchWSL
&amp;lt;archwsl&amp;gt;
#link(&quot;https://github.com/yuk7/ArchWSL&quot;)[ArchWSL]&lt;/p&gt;
&lt;p&gt;！！！&lt;/p&gt;
</content:encoded></item><item><title>rust过程宏</title><link>https://dashuai009.github.io/blog/028/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/028/</guid><description>使用过程宏自定义一个struct format。</description><pubDate>Fri, 10 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 6,
day: 10,
)
#metadata((
title: &quot;rust过程宏&quot;,
subtitle: [rust],
author: &quot;dashuai009&quot;,
description: &quot;使用过程宏自定义一个struct format。&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;我们有这样一个结构体&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct TestStruct{
    a:String,
    b:String
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们希望自定义一个Display。针对&lt;code&gt;TestStruct{a:String::from(&quot;aaa&quot;),b:String::from(&quot;bbb&quot;)}&lt;/code&gt;形式化输出&lt;code&gt;a:aaa;b:bbb;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;利用过程宏可以做到这一点，编译时生成&lt;code&gt;std::fmt::Display&lt;/code&gt;的trait。&lt;/p&gt;
&lt;p&gt;#quote[
刚开始学习过程宏，简单记录自己怎么实现的和一些坑
]&lt;/p&gt;
&lt;p&gt;直接上代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;extern crate proc_macro;
use proc_macro::TokenStream;

use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields, Ident};

#[proc_macro_derive(format)]
pub fn derive_format(input: TokenStream) -&amp;gt; TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let struct_name = input.ident;
    let struct_str = Ident::new(&quot;struct_str&quot;, struct_name.span());
    let expended = if let Data::Struct(r#struct) = input.data {
        if let Fields::Named(ref fields_name) = r#struct.fields {
            let get_selfs: Vec&amp;lt;_&amp;gt; = fields_name
                .named
                .iter()
                .map(|field| {
                    let f = field.ident.as_ref().unwrap();

                    quote! {
                        stringify!(#f),&amp;amp;self.#f
                    }
                })
                .collect();

            let format_string = &quot;{}:{};&quot;.repeat(get_selfs.len());
            let format_literal = proc_macro2::Literal::string(format_string.as_str());
            let struct_fields = quote! {
                #(#get_selfs),*
            };

            quote! {
                impl std::fmt::Display for #struct_name{
                    fn fmt(&amp;amp;self,f:&amp;amp;mut std::fmt::Formatter)-&amp;gt;std::fmt::Result{
                        write!(f , #format_literal , #struct_fields)
                    }
                }
            }
        } else {
            panic!(&quot;sorry, may it&apos;s a complicated struct.&quot;)
        }
    } else {
        panic!(&quot;sorry, Show is not implemented for union or enum type.&quot;)
    };
    expended.into()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;难点有两个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;proc_macro2::Literal&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;format!(&quot;{}&quot;,str);&lt;/code&gt;语句中格式控制字符串&lt;code&gt;&quot;{}&quot;&lt;/code&gt;是一个字面量&lt;code&gt;literal&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;quote内部使用的第三方库&lt;code&gt;proc_macro2&lt;/code&gt;，quote语法&lt;code&gt;#ident&lt;/code&gt;只接受&lt;code&gt;proc_macro2&lt;/code&gt;的&lt;code&gt;{TokenStream,Ident,....}&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;在上边代码中，我们需要将一个String变量转为一个字面量。这个一定要用&lt;code&gt;proc_macro2::Literal&lt;/code&gt;，用默认库&lt;code&gt;proc_macro::Literal&lt;/code&gt;，会报错#strong[mismatches types];。#strike[要了命了才看出来这么个事]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;#(#get_selfs),*&lt;/code&gt; quote的语法糖&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;write!(f , #format_literal , #struct_fields)&lt;/code&gt; 写出&lt;code&gt;write!(f , &quot;{:?}&quot; , (#(#get_selfs),*))&lt;/code&gt;这样可以过编译，最后这个参数会生成一个元组，这又没法自定义格式了。 写成&lt;code&gt;write!(f , #format_literal , #(#get_selfs),*)&lt;/code&gt;又过不了编译。 所以用&lt;code&gt;let struct_fields = quote!{#(#get_selfs),*}&lt;/code&gt;套了一层。&lt;/p&gt;
</content:encoded></item><item><title>rust减少编译警告</title><link>https://dashuai009.github.io/blog/027/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/027/</guid><description>项目前期开发中，可以通过RUSTFLAGS减少rust编译时给出的警告。后期去掉这些选项即可。</description><pubDate>Thu, 09 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 6,
day: 9,
)
#metadata((
title: &quot;rust减少编译警告&quot;,
subtitle: [rust],
author: &quot;dashuai009&quot;,
description: &quot;项目前期开发中，可以通过RUSTFLAGS减少rust编译时给出的警告。后期去掉这些选项即可。&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;== rust编译信息
&amp;lt;rust编译信息&amp;gt;
#link(&quot;https://doc.rust-lang.org/reference/attributes/diagnostics.html&quot;)[diagnostics attributes]&lt;/p&gt;
&lt;p&gt;#link(&quot;https://doc.rust-lang.org/rustc/lints/levels.html&quot;)[lints levels]&lt;/p&gt;
&lt;p&gt;rust分为四种lint信息：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;#strong[allow( C )] overrides the check for C so that violations will go unreported,&lt;/li&gt;
&lt;li&gt;#strong[warn( C )] warns about violations of C but continues compilation.&lt;/li&gt;
&lt;li&gt;#strong[deny( C )] signals an error after encountering a violation of C,&lt;/li&gt;
&lt;li&gt;#strong[forbid( C )] is the same as deny( C ), but also forbids changing the lint level afterwards&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;默认情况下rust编译会给出#strong[大量警告信息];，比如变量命名不规范、未使用的变量等等。比较烦人，有很多种方法可以减少这些信息。&lt;/p&gt;
&lt;p&gt;== 查看所有lint
&amp;lt;查看所有lint&amp;gt;
&lt;code&gt;rustc -W help&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;== 通过lint check属性减少编译警告
&amp;lt;通过lint-check属性减少编译警告&amp;gt;
（lint check attribute 这不知道咋翻译了&lt;/p&gt;
&lt;p&gt;将前边提到的C替换成&lt;code&gt;rustc -W help&lt;/code&gt;看到的东西。比如给函数添加&lt;code&gt;#[allow(missing_docs)]&lt;/code&gt;可以允许不写相关文档。&lt;/p&gt;
&lt;p&gt;== 设置RUSTFLAGS
&amp;lt;设置rustflags&amp;gt;
执行rustc之前可以在export一些环境变量。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;export RUSTFLAGS=&quot;$RUSTFLAGS -A unused&quot;; rustc balbalbaba&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;在intellij系列的IDE中，可给运行/调试配置中自动添加这些环境变量。
不过，配置里#strong[不需要添加引号”];，他会自动加上。&lt;/p&gt;
&lt;p&gt;#figure(
image(&quot;027/clion.png&quot;),
caption: [
示意图
],
)&lt;/p&gt;
&lt;p&gt;上边这个脚本配置相当于这条命令：
&lt;code&gt;export RUSTFLAGS=&quot;$RUSTFLAGS -A dead_code -A unused -A non_snake_case -A non_upper_case_globals -A non_camel_case_types&quot;; wasm-pack build&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这里我用到了以下几个&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;-A dead_code&lt;/li&gt;
&lt;li&gt;-A unused 允许未使用（变量、函数等，一个lint group）&lt;/li&gt;
&lt;li&gt;-A non_snake_case 允许这三种不推荐的命名方式&lt;/li&gt;
&lt;li&gt;-A non_upper_case_globals&lt;/li&gt;
&lt;li&gt;-A non_camel_case_types&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;全给他allow。#strike[美滋滋，编译结果清净了]&lt;/p&gt;
</content:encoded></item><item><title>核酸检测管里的红色液体是什么？为啥是红色的？</title><link>https://dashuai009.github.io/blog/026/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/026/</guid><description>喜提管长几次，刷知乎遇到这个问题，感觉之前从来没想过这个问题。液体是病毒保存液，红色是酚红。</description><pubDate>Wed, 25 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 5,
day: 25,
)&lt;/p&gt;
&lt;p&gt;#metadata((
title: &quot;核酸检测管里的红色液体是什么？为啥是红色的？&quot;,
subtitle: [核酸检测],
author: &quot;dashuai009&quot;,
description: &quot;喜提管长几次，刷知乎遇到这个问题，感觉之前从来没想过这个问题。液体是病毒保存液，红色是酚红。&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;转自知乎！&lt;/p&gt;
&lt;p&gt;#link(&quot;https://www.zhihu.com/question/532237774/answer/2489720812&quot;)[知乎作者：比克曼生物]&lt;/p&gt;
&lt;p&gt;#link(&quot;https://www.zhihu.com/question/532237774/answer/2480186488&quot;)[知乎作者：Morpho Asagiri]&lt;/p&gt;
&lt;p&gt;红色液体是病毒保存液，分为灭活型和非灭火型。&lt;/p&gt;
&lt;p&gt;灭活型体系内含有#strong[高效病毒裂解盐];，采样后灭活病毒，有效防止二次感染风险，保障运输及检测人员安全。适用于新冠病毒、流感病毒、手足口病毒等常见病毒样本采集保存运输工作，可采集咽拭子鼻试子或特定部位组织样本，储存的样本可用于后续的核酸提取或纯化等临床试验。&lt;/p&gt;
&lt;p&gt;非灭活型采样管能够保护病毒的蛋白和核酸，使采样的病毒具有其原始性，完整性更好，检出率更高。除了核酸检测外，样本还可以用于其他的病毒研究试验。&lt;/p&gt;
&lt;p&gt;保存液显红色是因为溶液里含有酚红，被作为PH指示剂，在酸性时显黄色，中性时显红色，碱性时显紫色。&lt;/p&gt;
&lt;p&gt;焦碳酸二乙酯&lt;/p&gt;
&lt;p&gt;#figure(
image(&quot;026/400px-Diethyl_pyrocarbonate.svg.png&quot;),
caption: [
焦碳酸二乙酯
],
)&lt;/p&gt;
&lt;p&gt;== 2024后记&lt;/p&gt;
&lt;p&gt;这篇博客写于2022年，摘要里有一句“喜提管长几次”。现在回看的时候想了半天管长是啥，还在想这句是不是乱码。&lt;/p&gt;
&lt;p&gt;管长：做核酸时，每十个人一组。每个人先排队去领棉签，再依次去被捅嗓子。每十个人，会有一个人领到一个红色液体的管子（上文说的东西），这十个人的棉签会放到这个管子里。这个人称为管长。&lt;/p&gt;
</content:encoded></item><item><title>angular服务端渲染</title><link>https://dashuai009.github.io/blog/005/005/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/005/005/</guid><pubDate>Sat, 14 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 5,
day: 14,
)
#metadata((
&quot;title&quot;: &quot;angular服务端渲染&quot;,
&quot;author&quot;: &quot;dashuai009&quot;,
description: &quot;&quot;,
pubDate: date.display(),
subtitle: [angular,angular_universal,ssr],
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;#date.display();
#outline()&lt;/p&gt;
&lt;p&gt;== Angular 统一平台简介&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://angular.cn/guide/universal&quot;&gt;Angular Universal：Angular 统一平台简介&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本指南讲的是Angular Universal（统一平台），一项在服务端运行 Angular 应用的技术。&lt;/p&gt;
&lt;p&gt;标准的 Angular 应用会运行在浏览器中，它会在 DOM 中渲染页面，以响应用户的操作。 而Angular Universal 会在服务端运行，生成一些静态的应用页面，稍后再通过客户端进行启动。 这
意味着该应用的渲染通常会更快，让用户可以在应用变得完全可交互之前，先查看应用的布局。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;== 参考文档&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://angular.cn/guide/universal&quot;&gt;Angular Universal：Angular 统一平台简介&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.google.com/web/updates/2019/02/rendering-on-the-web&quot;&gt;Rendering on web&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.angular-university.io/angular-universal/&quot;&gt;Angular Universal: a Complete Practical Guide&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;== 示例代码&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/angular-university/angular-universal-course&quot;&gt;Angular Universal: a Complete Practical Guide&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/dashuai009/angularUniversal&quot;&gt;个人代码&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;== 为何需要服务端渲染？&lt;/p&gt;
&lt;p&gt;有三个主要的理由来为你的应用创建一个 Universal 版本。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通过搜索引擎优化(SEO)来帮助网络爬虫。&lt;/li&gt;
&lt;li&gt;提升在手机和低功耗设备上的性能&lt;/li&gt;
&lt;li&gt;迅速显示出第一个支持首次内容绘制(FCP)的页面&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;== 案例&lt;/p&gt;
&lt;p&gt;以我的博客渲染过程为例。&lt;/p&gt;
&lt;p&gt;这里给出一个比较大的例子，为了方便展示setTitle等部分。&lt;/p&gt;
&lt;p&gt;如果单纯在服务端向&lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;插入一段&lt;code&gt;html&lt;/code&gt;代码也可以。&lt;/p&gt;
&lt;p&gt;== 如何使用&lt;/p&gt;
&lt;p&gt;要创建服务端应用模块 app.server.module.ts，请运行以下 CLI 命令。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ng add @nguniversal/express-engine
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该命令会创建如下文件夹结构。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
src/
index.html                 app web page
main.ts                    bootstrapper for client app
main.server.ts             * bootstrapper for server app
style.css                  styles for the app
app/ ...                   application code
app.server.module.ts     * server-side application module
server.ts                    * express web server
tsconfig.json                TypeScript base configuration
tsconfig.app.json            TypeScript browser application configuration
tsconfig.server.json         TypeScript server application configuration
tsconfig.spec.json           TypeScript tests configuration

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://blog-picture-1305172231.cos.ap-beijing.myqcloud.com/5/2021-05-15%2018-17-06%20%E7%9A%84%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE.png&quot; alt=&quot;被修改的文件&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在我的package.json中有如下定义&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
{
  &quot;script&quot;:{
    &quot;dev:ssr&quot;: &quot;ng run blog:serve-ssr&quot;,
    &quot;serve:ssr&quot;: &quot;node dist/blog/server/main.js&quot;,
    &quot;build:ssr&quot;: &quot;ng build --prod &amp;amp;&amp;amp; ng run blog:server:production&quot;,
    &quot;prerender&quot;: &quot;ng run blog:prerender&quot;
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后运行&lt;code&gt;npm run dev:ssr&lt;/code&gt;(与运行&lt;code&gt;ng run blog:serve-ssr&lt;/code&gt;等价)，可以在本地测试。&lt;/p&gt;
&lt;p&gt;生产环境下可以用&lt;code&gt;npm run build:ssr&lt;/code&gt;编译项目。和 &lt;code&gt;npm run srve:ssr&lt;/code&gt; 运行项目。&lt;/p&gt;
&lt;p&gt;== 目标&lt;/p&gt;
&lt;p&gt;打开一个article的时候&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;直接从服务器段拿到已经把md渲染为html&lt;/li&gt;
&lt;li&gt;前端无需再去请求md文档&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;== 重复获取数据&lt;/p&gt;
&lt;p&gt;=== 了解状态转移API&lt;/p&gt;
&lt;p&gt;有了App Shell，现在让我们讨论另一个常见的服务器端渲染优化：客户端启动时服务器到客户端的状态转移。&lt;/p&gt;
&lt;p&gt;首先让我们谈谈State Transfer API解决的问题。当我们的Angular Universal应用程序启动时，页面的很大一部分已经被渲染，并且从一开始就对用户可见。&lt;/p&gt;
&lt;p&gt;但是请记住，此服务器端呈现的应用程序将从服务器中提取普通的客户端应用程序，然后该应用程序将接管页面。&lt;/p&gt;
&lt;p&gt;然后，此Angular客户端应用程序将启动，并且它将做的第一件事是什么？它会联系服务器并再次获取所有数据！&lt;/p&gt;
&lt;p&gt;客户端应用程序甚至会在加载数据时打开加载指示器。对于用户来说这很奇怪，因为来自服务器的页面中已经有数据，那么为什么应用程序又要加载它呢？&lt;/p&gt;
&lt;p&gt;然后，客户端将重新呈现所有数据，并将其再次传递给页面，并将其显示给用户。&lt;/p&gt;
&lt;p&gt;所有这一切都有一个问题：服务器刚刚检索了数据并呈现了数据，那么为什么还要在客户端上再次重复相同的过程呢？这是多余的，它查询服务器两次，并且不能提供良好的用户体验，这是我们 首先使用Universal的主要原因。&lt;/p&gt;
&lt;p&gt;=== Transfer API如何工作？&lt;/p&gt;
&lt;p&gt;为了解决重复数据获取的问题，我们需要的是通用应用程序将其数据存储在页面上某个位置，然后使其可用于客户端应用程序的方式，而无需再次调用服务器。&lt;/p&gt;
&lt;p&gt;这正是State Transfer API允许我们执行的操作！State Transfer API为我们提供了一个存储容器，用于在服务器和客户端应用程序之间轻松地传输数据，从而避免了客户端应用程序必须与服务 器联系以获取数据的需求。&lt;/p&gt;
&lt;p&gt;=== 添加transferStateModule&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://angular.cn/api/platform-browser/TransferState&quot;&gt;Here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;angular提供了一种在服务器和浏览器之间，进行数据传输的一个接口。数据是附加在html后边中的一个script的标签中。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我踩的坑：这个数据自动进行json的解码转码。不要再对数据进行JSON.parse和JSON.stringify，无法处理html的标签，&quot;&amp;lt;&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;分别在app.server.module.ts和app.module.ts中，分别添加&lt;code&gt;ServerTransferStateModule&lt;/code&gt;和&lt;code&gt;BrowserTransferStateModule&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog-picture-1305172231.cos.ap-beijing.myqcloud.com/5/2021-05-15%2020-56-04%20%E7%9A%84%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE.png&quot; alt=&quot;server&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog-picture-1305172231.cos.ap-beijing.myqcloud.com/5/2021-05-15%2020-56-20%20%E7%9A%84%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE.png&quot; alt=&quot;browser&quot; /&gt;&lt;/p&gt;
&lt;p&gt;=== 编写resovler&lt;/p&gt;
&lt;p&gt;在app.module.ts中声明（provider）resoler.ts模块。&lt;/p&gt;
&lt;p&gt;之后，就可以在该文件中利用TransferState模块处理数据。&lt;/p&gt;
&lt;p&gt;== 服务端是否渲染&lt;/p&gt;
&lt;p&gt;进行一些细粒度的控制，可以使用自定义结构指令来实现：appShellRender和appShellNoRender。&lt;/p&gt;
&lt;p&gt;#table(
columns: (1fr, auto),
inset: 10pt,
align: horizon,
&quot;appShellRender&quot;, &quot;服务端渲染&quot;,
&quot;appShellNoRender&quot;, &quot;服务端不渲染&quot;,
)&lt;/p&gt;
&lt;p&gt;请注意，appShellRender和appShellNoRender对客户端没有任何影响！在浏览器中，每次我们浏览单个页面应用程序时，整个模板都会呈现出来。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/angular-university/angular-universal-course/tree/2-express-engine-finished/src/app/directives&quot;&gt;代码示例&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;== 编写服务端不渲染的组件&lt;/p&gt;
&lt;p&gt;服务端渲染的用法简单，且就这样了。
如果想写服务端不加载的组件，又不想在组件里适配，有一种hack的写法，这里简单记录一下。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;my-comp *ngif = &quot;show | sync&quot;&amp;gt;&amp;lt;/my-comp&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;show可以是上层组件中的&lt;code&gt;Observable&amp;lt;boolean&amp;gt;&lt;/code&gt;，在服务端传递&lt;code&gt;of(false)&lt;/code&gt;即可。这样服务端不会渲染&lt;code&gt;my-comp&lt;/code&gt;组件。&lt;/p&gt;
</content:encoded></item><item><title>ToDo</title><link>https://dashuai009.github.io/blog/004/004/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/004/004/</guid><pubDate>Sun, 01 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 5,
day: 1,
)
#metadata((
&quot;title&quot;: &quot;ToDo&quot;,
&quot;author&quot;: &quot;dashuai009&quot;,
description: &quot;&quot;,
pubDate: date.display(),
subtitle: [kotlin,Android],
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;#date.display();&lt;/p&gt;
&lt;p&gt;= 相关技术&lt;/p&gt;
&lt;p&gt;== kotlin&lt;/p&gt;
&lt;p&gt;在Google I/O 2017中，Google 宣布 Kotlin 成为 Android官方开发语言。官方文档中，java和kotlin是并列出现的。作为一种新型语言，相比与java， 它有以下优势：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;简洁：大大减少样板代码的数量（没有new关键字，没有分号等）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;安全: 避免空指针异常等整个类的错误。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;互操作性: 充分利用 JVM、Android 和浏览器的现有库。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;工具友好: 可用任何 Java IDE 或者使用命令行构建。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;== room可持久库&lt;/p&gt;
&lt;p&gt;Room 在 SQLite 上提供了一个抽象层，以便在充分利用 SQLite的强大功能的同时，能够流畅地访问数据库。更重要的，&lt;strong&gt;Google官方强烈建议我们使用Room（而不是SQLite）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Room 包含 3 个主要组件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;数据库：包含数据库持有者，并作为应用已保留的持久关系型数据的底层连接的主要接入点。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;使用 &lt;code&gt;@Database&lt;/code&gt; 注释的类应满足以下条件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;是扩展 RoomDatabase 的抽象类。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在注释中添加与数据库关联的实体列表。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;包含具有 0 个参数且返回使用&lt;code&gt;@Dao&lt;/code&gt; 注释的类的抽象方法。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在运行时，您可以通过调用 &lt;code&gt;Room.databaseBuilder()&lt;/code&gt; 或&lt;code&gt;Room.inMemoryDatabaseBuilder()&lt;/code&gt; 获取 &lt;code&gt;Database&lt;/code&gt; 的实例。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Entity：表示数据库中的表。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DAO：包含用于访问数据库的方法。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;应用使用 Room 数据库来获取与该数据库关联的数据访问对象(DAO)。然后，应用使用每个 DAO从数据库中获取实体，然后再将对这些实体的所有更改保存回数据库中。最后，应用使用实体来获取和设置与数据库中的表列相对应的值。&lt;/p&gt;
&lt;p&gt;Room 不同组件之间的关系如图 1 所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog-picture-1305172231.cos.ap-beijing.myqcloud.com/4/room_architecture.png&quot; alt=&quot;Room 架构图&quot; /&gt;&lt;/p&gt;
&lt;p&gt;== Android架构&lt;/p&gt;
&lt;p&gt;在大多数情况下，桌面应用将桌面或程序启动器当做单个入口点，然后作为单个整体流程运行。Android应用则不然，它们的结构要复杂得多。典型的 Android应用包含多个应用组件，包括Activity、Fragment、Service、内容提供程序和广播接收器。&lt;/p&gt;
&lt;p&gt;移动设备的资源也很有限，因此操作系统可能会随时终止某些应用进程，以便为新的进程腾出空间。&lt;/p&gt;
&lt;p&gt;鉴于这种环境条件，应用组件可以不按顺序地单独启动，并且操作系统或用户可以随时销毁它们。由于这些事件不受您的控制，因此&lt;strong&gt;我们不应在应用组件中存储任何应用数据或状态&lt;/strong&gt;，并且应 用组件不应相互依赖。&lt;/p&gt;
&lt;p&gt;== 常见的架构原则&lt;/p&gt;
&lt;p&gt;=== 分离关注点&lt;/p&gt;
&lt;p&gt;要遵循的最重要的原则是&lt;strong&gt;分离关注点&lt;/strong&gt;。 一种常见的错误是在一个 Activity或 Fragment 中编写所有代码。这些基于界面的类应仅包含处理界面和操作系统交互的逻辑。应使这些类尽可能保 持精简，这样可以避免许多与生命周期相关的问题。&lt;/p&gt;
&lt;p&gt;请注意，我们并非拥有 Activity 和 Fragment 的实现； 它们只是表示 Android操作系统与应用之间关系的粘合类。操作系统可能会根据用户互动或因内存不足等系统条件随时销毁它们。为了提供令人满意的用户体验和更易于管理的应用维护体验，最好尽量减少对它们的依赖。&lt;/p&gt;
&lt;p&gt;=== 通过模型驱动界面&lt;/p&gt;
&lt;p&gt;另一个重要原则是您应该通过模型驱动界面（最好是持久性模型）。模型是负责处理应用数据的组件。它们独立于应用中的View 对象和应用组件，因此不受应用的生命周期以及相关的关注点的影响。&lt;/p&gt;
&lt;p&gt;持久性是理想之选，原因如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果 Android 操作系统销毁应用以释放资源，用户不会丢失数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当网络连接不稳定或不可用时，应用会继续工作。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;应用所基于的模型类应明确定义数据管理职责，这样将使应用更可测试且更一致。&lt;/p&gt;
&lt;p&gt;== ToDo的应用结构&lt;/p&gt;
&lt;p&gt;Google官方推荐如下架构，在我的ToDo应用中也实现了如下架构，非常amazing。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog-picture-1305172231.cos.ap-beijing.myqcloud.com/4/final-architecture.png&quot; alt=&quot;ToDo架构图&quot; /&gt;&lt;/p&gt;
&lt;p&gt;= 系统功能要求&lt;/p&gt;
&lt;p&gt;== 用户需求&lt;/p&gt;
&lt;p&gt;普通用户需要一个待办记录软件，希望我们实现如下功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;记录并展示待办事项。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可编辑每个事项。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可以对事项进行排序。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;== 需求分析&lt;/p&gt;
&lt;p&gt;通过对用户需求的调查，我们可以得出如下具体的需求。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;将每个事项抽象为包括&lt;strong&gt;id(编号)、content(待办内容)、priority(优先级)、date(截止日期)、Done(是否完成)&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将所有事项依次展示，形成一个列表。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;添加一个事项。&lt;/p&gt;
&lt;p&gt;我们可以实现一个添加按钮，用户点击之后，可以添加一个事项，并实时展示相应内容。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;编辑事项。&lt;/p&gt;
&lt;p&gt;当用户点击列表中的某个事项，可以对该事项进行编辑。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;排序。&lt;/p&gt;
&lt;p&gt;设定两种排序方式，一种按照&lt;code&gt;id&lt;/code&gt;字段进行排序，另一种按照&lt;code&gt;priority,date&lt;/code&gt;两个字段进行排序。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;= 系统设计与实现&lt;/p&gt;
&lt;p&gt;总共设计了&lt;code&gt;MainActivity&lt;/code&gt;和&lt;code&gt;NoteActivity&lt;/code&gt;两个Acticity组件，&lt;code&gt;db&lt;/code&gt;和&lt;code&gt;ui&lt;/code&gt;两个模块。&lt;/p&gt;
&lt;p&gt;== MainActicity&lt;/p&gt;
&lt;p&gt;这是整个程序的主入口。&lt;/p&gt;
&lt;p&gt;主要有如下几个函数和接口&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;onCreate&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这是MainActivity的生命周期中的开始部分。绑定视图等内容就是在这里定义和实现。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;扩展了&lt;code&gt;NoteListAdapter.NoteListener&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;对于ListView中的每条目，实现三个接口。&lt;code&gt;onContentClick(curNote: Note)&lt;/code&gt;,&lt;br /&gt;
&lt;code&gt;onCheckBoxClick(curNote: Note)&lt;/code&gt;,&lt;code&gt;onDeleteBtnClick(curNote: Note)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;refresh&lt;/p&gt;
&lt;p&gt;用于更新RecylerView的模块，是否排序等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;private lateinit var mSharedPreferences: SharedPreferences&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;用户保存用户配置：是否排序。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;onOptionsItemSelected&lt;/p&gt;
&lt;p&gt;右上角给出用户是否排序的选项。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;onActivityResult&lt;/p&gt;
&lt;p&gt;接收 NoteActivity的返回结果，根据id段是否为0分别进行插入和删除操作。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;== NoteActivity&lt;/p&gt;
&lt;p&gt;该组件提供给用户编辑条目的界面。当用户新建条目或编辑条目时都会跳转至该界面。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;var editText: EditText&lt;/code&gt;用于编辑和显示事项的内容。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;var dateText: TextView;var timeText: TextView&lt;/code&gt;显示选择的日期和时间。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;var addBtn: Button&lt;/code&gt;提交按钮。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;var prioritySpinner: Spinner&lt;/code&gt;选择优先级，0,1,2&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;直接通过xml定义timePicker和datePicker。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;== db&lt;/p&gt;
&lt;p&gt;这里实现了数据库，使用到了&lt;em&gt;room可持久库&lt;/em&gt;，有如下四个部分。&lt;/p&gt;
&lt;p&gt;=== dao&lt;/p&gt;
&lt;p&gt;定义了NoteDao类，这里实现了数据库的sql语句。&lt;/p&gt;
&lt;p&gt;=== entity&lt;/p&gt;
&lt;p&gt;有两个类。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Note&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这是数据库条目的具体定义，并扩展了&lt;code&gt;Parcelable&lt;/code&gt;，利用kotlin的序列化，可以非常方便的在Activity之间传递&lt;code&gt;note&lt;/code&gt;类。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;DateToLongConverters&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;android内的SQLite无法直接保存Date类型，定一个转换器，将Date自动转化为Long类型，自动将Long转化回Date类型。&lt;/p&gt;
&lt;p&gt;=== NoteRepository&lt;/p&gt;
&lt;p&gt;存储库可管理查询，且允许您使用多个后端。在最常见的示例中，存储库可实现对以下任务做出决定时所需的逻辑：是否从网络中提取数据；是否使用缓存在本地数据库中的结果。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog-picture-1305172231.cos.ap-beijing.myqcloud.com/4/cdfae5b9b10da57f.png&quot; alt=&quot;存储库&quot; /&gt;&lt;/p&gt;
&lt;p&gt;=== Room数据库：TodoDateBase&lt;/p&gt;
&lt;p&gt;什么是 Room 数据库？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Room 是 SQLite 数据库之上的一个数据库层。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Room 负责您平常使用 SQLiteOpenHelper 所处理的单调乏味的任务。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Room 使用 DAO 向其数据库发出查询请求。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为避免界面性能不佳，默认情况下，Room
不允许在主线程上发出查询请求。当 Room 查询返回 Flow时，这些查询会在后台线程上自动异步运行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Room 提供 SQLite 语句的编译时检查。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;== ui&lt;/p&gt;
&lt;p&gt;这里定义了几个关于界面的组件。&lt;/p&gt;
&lt;p&gt;=== NoteListAdapter&lt;/p&gt;
&lt;p&gt;ListView的适配器。绑定了试图的操作，根据&lt;code&gt;分离关注点&lt;/code&gt;的原则，我定义了几个接口，这些接口将在MainActivity中实现（这样比较方便而已，最好是定义在一个单独的类中）。&lt;/p&gt;
&lt;p&gt;=== NoteViewModel&lt;/p&gt;
&lt;p&gt;ViewModel 的作用是向界面提供数据，不受配置变化的影响。ViewModel充当存储库和界面之间的通信中心。您还可以使用 ViewModel 在 fragment之间共享数据。ViewModel 是 Lifecycle 库的 一部分。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog-picture-1305172231.cos.ap-beijing.myqcloud.com/4/72848dfccfe5777b.png&quot; alt=&quot;ViewModel&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ViewModel以一种可以感知生命周期的方式保存应用的界面数据，不受配置变化的影响。它会将应用的界面数据与Activity 和 Fragment 类区分开，让您更好地遵循单一责任原则：activity 和fragment 负责将数据绘制到屏幕上，ViewModel则负责保存并处理界面所需的所有数据。&lt;/p&gt;
&lt;p&gt;LiveData 是一种可观察的数据存储器，每当数据发生变化时，都会收到通知。与Flow 不同，LiveData 具有生命周期感知能力，即遵循其他应用组件（如activity 或 fragment）的生命周期。LiveData会根据负责监听变化的组件的生命周期自动停止或恢复观察。因此，LiveData适用于界面使用或显示的可变数据。&lt;/p&gt;
&lt;p&gt;ViewModel 会将存储库中的数据从 Flow 转换为 LiveData，并将字词列表作为LiveData传递给界面。这样可以确保每次数据库中的数据发生变化时，界面都会自动更新。&lt;/p&gt;
&lt;p&gt;在 Kotlin 中，所有协程都在 CoroutineScope中运行。范围用于控制协程在整个作业过程中的生命周期。如果取消某一范围内的作业，该范围内启动的所有协程也将取消。&lt;/p&gt;
&lt;p&gt;AndroidX lifecycle-viewmodel-ktx 库将 viewModelScope 添加为 ViewModel类的扩展函数，使您能够使用范围。&lt;/p&gt;
&lt;p&gt;=== 两个Fragment&lt;/p&gt;
&lt;p&gt;分别是DatePicker和DatePicker。&lt;/p&gt;
&lt;p&gt;= 软件可能的扩展&lt;/p&gt;
&lt;p&gt;== 存储库&lt;/p&gt;
&lt;p&gt;在这里我们定义并实现了NoteResposity。在当前这个应用中，数据库的来源只有本地的数据库。这个存储库可以实现网络数据的拉取，而不需要更改其他代码。&lt;/p&gt;
&lt;p&gt;== 功能更新&lt;/p&gt;
&lt;p&gt;可以将事项添加到日历，并添加闹钟。但作为一个普通应用，而不是系统内置应用，访问日历有隐私问题。暂不考虑。&lt;/p&gt;
&lt;p&gt;分类。可以将不同的事项分为不同的类。同类产品中有这种功能，实际体验中，用户需求度不高。&lt;/p&gt;
&lt;p&gt;== 应用迭代&lt;/p&gt;
&lt;p&gt;代码和apk发布在如下github仓库。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/dashuai009/ToDo&quot;&gt;github&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如果有更新会提交的这里。&lt;/p&gt;
&lt;p&gt;= 总结体会&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;课程体会。本门课程与实际生活关联很大，不仅有用户体验方面的，还有技术实现上。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;技术总结。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;kotlin。
非常的简洁，也避免的null错误等。&lt;/li&gt;
&lt;li&gt;room可持久库。
避免使用 SQLiteOpenHelper 所处理的单调乏味的任务。&lt;/li&gt;
&lt;li&gt;android设计原则。
虽然在这次编程中实现的不是很好，但可以明显的看出遵循原则之后，代码比较优美。&apos;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>生成函数</title><link>https://dashuai009.github.io/blog/022/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/022/</guid><description>生成函数</description><pubDate>Mon, 14 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 3,
day: 14,
)
#metadata((
title: &quot;生成函数&quot;,
subtitle: [生成函数,math],
author: &quot;dashuai009&quot;,
description: &quot;生成函数&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;== 普通生成函数
&amp;lt;普通生成函数&amp;gt;
$ F (x) = sum_n a_n a^n (n gt.eq 0) $&lt;/p&gt;
&lt;p&gt;=== 等比数列
&amp;lt;等比数列&amp;gt;
数列$&amp;lt; 1 , p , p^2 , p^3 , . . . , p^n , . . . &amp;gt;$的生产成函数$F (x) = sum_(n gt.eq 0) p^n x^n$的封闭式为$F (x) = frac(1, 1 - p x)$&lt;/p&gt;
&lt;p&gt;=== $a = &amp;lt; 1 , 2 , 3 , 4 . . . &amp;gt;$
&amp;lt;a1234...&amp;gt;
生成函数 $ F (x) &amp;amp; = sum_(n gt.eq 0) (n + 1) x^n&lt;br /&gt;
&amp;amp; = sum_(n gt.eq 1) n x^(n - 1)&lt;br /&gt;
&amp;amp; = sum_(n gt.eq 0) (x^n) prime&lt;br /&gt;
&amp;amp; = (frac(1, 1 - x)) prime&lt;br /&gt;
&amp;amp; = 1 / (1 - x)^2 $&lt;/p&gt;
&lt;p&gt;=== $binom(m, n)$,($n gt.eq 0 , m$是常数)
&amp;lt;m-choose-nngeq-0m是常数&amp;gt;
$ F (x) = sum_(n gt.eq 0) binom(m, n) x^n = (1 + x)^m $&lt;/p&gt;
&lt;p&gt;=== $binom(m + n, n)$
&amp;lt;mn-choose-n&amp;gt;
$ F (x) = sum_(n gt.eq 0) binom(m + n, n) x^n = 1 / (1 - x)^(m + 1) $&lt;/p&gt;
&lt;p&gt;=== 斐波那其数列 $a_0 = 0 , a_1 = 1 , a_n = a_(n - 1) + a_(n - 2) (n &amp;gt; 1)$
&amp;lt;斐波那其数列-a_00a_11a_na_n-1a_n-2n1&amp;gt;
$
F (x) &amp;amp; = x F (x) + x^2 F (x) - a_0 x + a_1 x + a_0&lt;br /&gt;
&amp;amp; = frac(x, 1 - x - x^2)
$&lt;/p&gt;
&lt;p&gt;通过求解 $ frac(A, 1 - a x) + frac(B, 1 - b x) = frac(x, 1 - x - x^2) $&lt;/p&gt;
&lt;p&gt;得&lt;/p&gt;
&lt;p&gt;$ frac(x, 1 - x - x^2) = sum_(n gt.eq 0) x^n 1 / sqrt(5) ((frac(1 + sqrt(5), 2))^n - (frac(1 - sqrt(5), 2))^n) $&lt;/p&gt;
&lt;p&gt;== 指数生产成函数(exponential generating function，EGF)
&amp;lt;指数生产成函数exponential-generating-functionegf&amp;gt;
$ hat(F) (x) = sum_(n gt.eq 0) a_n frac(x^n, n !) $&lt;/p&gt;
&lt;p&gt;=== 运算&lt;/p&gt;
&lt;p&gt;$
hat(F) (x) hat(G) (x) &amp;amp; = sum_(i gt.eq 0) a_i frac(x^i, i !) sum_(j gt.eq 0) b_j frac(x^j, j !)&lt;br /&gt;
&amp;amp; = sum_(n gt.eq 0) x^n sum_(i = 1)^n a_i b_(n - i) frac(1, i ! (n - i) !)&lt;br /&gt;
&amp;amp; = sum_(n gt.eq 0) frac(x^n, n !) sum_(i = 0)^n binom(n, i) a_i b_(n - i)
$&lt;/p&gt;
&lt;p&gt;因此 $hat(F) (x) hat(G) (x)$ 是序列
$&amp;lt; sum_(i = 0)^n binom(n, i) a_i b_(n - i) &amp;gt;$ 的指数生成函数。&lt;/p&gt;
&lt;p&gt;=== $&amp;lt; 1 , 1 , 1 , 1 , . . . &amp;gt;$
&amp;lt;section&amp;gt;
$ sum_(n gt.eq 1) frac(x^n, n !) = e^x $&lt;/p&gt;
&lt;p&gt;=== $&amp;lt; 1 , p , p^2 , . . . &amp;gt;$
&amp;lt;pp2...&amp;gt;
$ sum_(n gt.eq 1) frac(p^n x^n, n !) = e^(p x) $&lt;/p&gt;
&lt;p&gt;=== $a_n = n !$
&amp;lt;a_nn&amp;gt;
$ hat(P) (x) = sum_(n gt.eq 0) frac(n ! x^n, n !) = sum_(n gt.eq 0) x^n = frac(1, 1 - x) $&lt;/p&gt;
&lt;p&gt;=== 圆排列
&amp;lt;圆排列&amp;gt;
$ hat(Q) (x) = sum_(n gt.eq 1) frac((n - 1) ! x^n, n !) = sum_(n gt.eq 1) x^n / n = - ln (1 - x) = ln (frac(1, 1 - x)) $&lt;/p&gt;
&lt;p&gt;=== 圆排列与排列的关系
&amp;lt;圆排列与排列的关系&amp;gt;
=== 生成树计数
&amp;lt;生成树计数&amp;gt;
如果 $n$ 个点 #strong[带标号] 生成树的 EGF 是 $hat(F) (x)$ ，那么 $n$
个点 #strong[带标号] 生成森林的 EGF 就是$e x p hat(F) (x)$
——直观理解为，将 $n$
个点分成若干个集合，每个集合构成一个生成树的方案数之积。&lt;/p&gt;
&lt;p&gt;=== 图
&amp;lt;图&amp;gt;
如果 $n$ 个点带标号无向连通图的 EGF 是 $hat(F) (x)$，那么 $n$
个点带标号无向图的 EGF 就是 $e x p hat(F) (x)$，后者可以很容易计算得到
$e x p hat(F) (x) = sum_(n gt.eq 0) 2^(binom(n, 2)) frac(x^n, n !)$。因此要计算前者，只需要一次多项式 $ln$ 即可。&lt;/p&gt;
&lt;p&gt;=== 错排数:长度为 $n$ 的一个错排是满足 $p_i eq.not i$ 的排列。
&amp;lt;错排数长度为-n-的一个错排是满足-p_ine-i-的排列&amp;gt;
=== 不动点：
&amp;lt;不动点&amp;gt;
有多少个映射
$f : 1 , 2 , dots.h.c , n arrow.r 1 , 2 , dots.h.c , n$，使得&lt;/p&gt;
&lt;p&gt;$
underbrace(f circle.stroked.tiny f circle.stroked.tiny dots.h.c circle.stroked.tiny f, k) = underbrace(f circle.stroked.tiny f circle.stroked.tiny dots.h.c circle.stroked.tiny f, k - 1)
$&lt;/p&gt;
&lt;p&gt;$n k lt.eq 2 times 10^6 , 1 lt.eq k lt.eq 3$。&lt;/p&gt;
&lt;p&gt;考虑 $i$ 向 $f (i)$ 连边。相当于我们从任意一个 $i$ 走 $k$ 步和走 $k - 1$
步到达的是同一个点。也就是说基环树的环是自环且深度不超过
$k$（根结点深度为
$1$）。把这个基环树当成有根树是一样的。因此我们的问题转化为：$n$
个点带标号，深度不超过 $k$ 的有根树森林的计数。&lt;/p&gt;
&lt;p&gt;考虑 $n$ 个点带标号深度不超过 $k$ 的有根树，假设它的生成函数是
$hat(F_k) (x) = sum_(n gt.eq 0) f_(n , k) frac(x^n, n !)$。&lt;/p&gt;
&lt;p&gt;考虑递推求 $hat(F_k) (x)$。深度不超过 $k$ 的有根树，实际上就是深度不超过
$k - 1$ 的若干棵有根树，把它们的根结点全部连到一个结点上去。因此&lt;/p&gt;
&lt;p&gt;$ hat(F_k) (x) = x exp hat(F)_(k - 1) (x) $&lt;/p&gt;
&lt;p&gt;那么答案的指数生成函数就是 $exp hat(F)_k (x)$。求它的第 $n$ 项即可。&lt;/p&gt;
&lt;p&gt;=== Lust
&amp;lt;lust&amp;gt;
给你一个 $n$ 个数的序列 $a_1 , a_2 , dots.h.c , a_n$，和一个初值为 $0$
的变量 $s$，要求你重复以下操作 $k$ 次：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 $1 , 2 , dots.h.c , n$ 中等概率随机选择一个 $x$。&lt;/li&gt;
&lt;li&gt;令 $s$ 加上 $product_(i eq.not x) a_i$。&lt;/li&gt;
&lt;li&gt;令 $a_x$ 减一。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;求 $k$ 次操作后 $s$ 的期望。&lt;/p&gt;
&lt;p&gt;$1 lt.eq n lt.eq 5000 , 1 lt.eq k lt.eq 10^9 , 0 lt.eq a_i lt.eq 10^9$。
假设 $k$ 次操作后 $a_i$ 减少了 $b_i$，那么实际上&lt;/p&gt;
&lt;p&gt;$ s = product_(i = 1)^n a_i - product_(i = 1)^n (a_i - b_i) $&lt;/p&gt;
&lt;p&gt;因此实际上我们的问题转化为，求 $k$ 次操作后
$product_(i = 1)^n (a_i - b_i)$ 的期望。&lt;/p&gt;
&lt;p&gt;不妨考虑计算每种方案的的 $product_(i = 1)^n (a_i - b_i)$ 的和，最后除以
$n^k$。&lt;/p&gt;
&lt;p&gt;而 $k$ 次操作序列中，要使得 $i$ 出现了 $b_i$ 次的方案数是&lt;/p&gt;
&lt;p&gt;$ frac(k !, b_1 ! b_2 ! dots.h.c b_n !) $&lt;/p&gt;
&lt;p&gt;这与指数生成函数乘法的系数类似。&lt;/p&gt;
&lt;p&gt;设 $a_j$ 的指数生成函数是&lt;/p&gt;
&lt;p&gt;$ F_j (x) = sum_(i gt.eq 0) (a_j - i) frac(x^i, i !) $&lt;/p&gt;
&lt;p&gt;那么答案就是&lt;/p&gt;
&lt;p&gt;$ [x^k] product_(j = 1)^n F_j (x) $&lt;/p&gt;
&lt;p&gt;为了快速计算答案，我们需要将 $F_j (x)$ 转化为封闭形式：&lt;/p&gt;
&lt;p&gt;$
F_j (
x
) &amp;amp; = sum_(i gt.eq 0) a_j frac(x^i, i !) - sum_(i gt.eq 1) frac(x^i, (i - 1) !) med &amp;amp; = a_j e^x - x e^x med &amp;amp; = (
a_j - x
) e^x
$&lt;/p&gt;
&lt;p&gt;因此我们得到&lt;/p&gt;
&lt;p&gt;$ product_(j = 1)^n F_j (x) = e^(n x) product_(j = 1)^n (a_j - x) $&lt;/p&gt;
&lt;p&gt;其中 $product_(j = 1)^n (a_j - x)$ 是一个 $n$
次多项式，可以暴力计算出来。假设它的展开式是
$sum_(i = 0)^n c_i x^i$，那么&lt;/p&gt;
&lt;p&gt;$
product_(j = 1)^n F_j (x) &amp;amp; = (sum_(i gt.eq 0) frac(n^i x^i, i !)) (sum_(i = 0)^n c_i x^i) med &lt;br /&gt;
&amp;amp; = sum_(i gt.eq 0) sum_(j = 0)^i c_j x^j frac(n^(i - j) x^(i - j), (i - j) !) med &lt;br /&gt;
&amp;amp; = sum_(i gt.eq 0) frac(x^i, i !) sum_(j = 0)^i n^(i - j) i^(underline(j)) c_j
$&lt;/p&gt;
&lt;p&gt;计算这个多项式的 $x^k$ 项系数即可。&lt;/p&gt;
</content:encoded></item><item><title>陪审团人选</title><link>https://dashuai009.github.io/blog/024/</link><guid isPermaLink="true">https://dashuai009.github.io/blog/024/</guid><description>poj上一道DP题。</description><pubDate>Mon, 14 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;#let date = datetime(
year: 2022,
month: 3,
day: 14,
)
#metadata((
title: &quot;陪审团人选&quot;,
subtitle: [DP],
author: &quot;dashuai009&quot;,
abstract: &quot;&quot;,
description: &quot;poj上一道DP题。&quot;,
pubDate: date.display(),
))&amp;lt;frontmatter&amp;gt;&lt;/p&gt;
&lt;p&gt;#import &quot;../__template/style.typ&quot;: conf
#show: conf&lt;/p&gt;
&lt;p&gt;== Description
&amp;lt;description&amp;gt;
In Frobnia, a far-away country, the verdicts in court trials are
determined by a jury consisting of members of the general public. Every
time a trial is set to begin, a jury has to be selected, which is done
as follows. First, several people are drawn randomly from the public.
For each person in this pool, defence and prosecution assign a grade
from 0 to 20 indicating their preference for this person. 0 means total
dislike, 20 on the other hand means that this person is considered
ideally suited for the jury.&lt;/p&gt;
&lt;p&gt;Based on the grades of the two parties, the judge selects the jury. In
order to ensure a fair trial, the tendencies of the jury to favour
either defence or prosecution should be as balanced as possible. The
jury therefore has to be chosen in a way that is satisfactory to both
parties.&lt;/p&gt;
&lt;p&gt;We will now make this more precise: given a pool of n potential jurors
and two values di (the defence’s value) and pi (the prosecution’s value)
for each potential juror i, you are to select a jury of m persons. If J
is a subset of {1,…, n} with m elements, then D(J ) = sum(dk) k belong
to J and P(J) = sum(pk) k belong to J are the total values of this jury
for defence and prosecution.&lt;/p&gt;
&lt;p&gt;For an optimal jury J , the value |D(J) - P(J)| must be minimal. If
there are several jurys with minimal |D(J) - P(J)|, one which maximizes
D(J) + P(J) should be selected since the jury should be as ideal as
possible for both parties.&lt;/p&gt;
&lt;p&gt;You are to write a program that implements this jury selection process
and chooses an optimal jury given a set of candidates.&lt;/p&gt;
&lt;p&gt;== Input
&amp;lt;input&amp;gt;
The input file contains several jury selection rounds. Each round starts
with a line containing two integers n and m. n is the number of
candidates and m the number of jury members.&lt;/p&gt;
&lt;p&gt;These values will satisfy 1&amp;lt;=n&amp;lt;=200, 1&amp;lt;=m&amp;lt;=20 and of course
m&amp;lt;=n.~The following n lines contain the two integers pi and di for i =
1,…,n.~A blank line separates each round from the next.&lt;/p&gt;
&lt;p&gt;The file ends with a round that has n = m = 0.&lt;/p&gt;
&lt;p&gt;== Output
&amp;lt;output&amp;gt;
For each round output a line containing the number of the jury selection
round (&apos;Jury #1&apos;, &apos;Jury #2&apos;, etc.).&lt;/p&gt;
&lt;p&gt;On the next line print the values D(J ) and P (J ) of your jury as shown
below and on another line print the numbers of the m chosen candidates
in ascending order. Output a blank before each individual candidate
number.&lt;/p&gt;
&lt;p&gt;Output an empty line after each test case.&lt;/p&gt;
&lt;p&gt;=== Sample Input
&amp;lt;sample-input&amp;gt;
#quote[
4 2&lt;/p&gt;
&lt;p&gt;1 2&lt;/p&gt;
&lt;p&gt;2 3&lt;/p&gt;
&lt;p&gt;4 1&lt;/p&gt;
&lt;p&gt;6 2&lt;/p&gt;
&lt;p&gt;0 0
]&lt;/p&gt;
&lt;p&gt;=== Sample Output
#quote[
Jury #1
]&lt;/p&gt;
&lt;p&gt;Best jury has value 6 for prosecution and value 4 for defence: 2 3&lt;/p&gt;
&lt;p&gt;== Hint
&amp;lt;hint&amp;gt;
If your solution is based on an inefficient algorithm, it may not
execute in the allotted time.&lt;/p&gt;
&lt;p&gt;== Source
&amp;lt;source&amp;gt;
Southwestern European Regional Contest 1996&lt;/p&gt;
&lt;p&gt;== 中文：
&amp;lt;中文&amp;gt;
描述&lt;/p&gt;
&lt;p&gt;在遥远的国家佛罗布尼亚，嫌犯是否有罪，须由陪审团决定。陪审团是由法官从公众中挑选的。先随机挑选n个人作为陪审团的候选人，然后再从这n个人中选m人组成陪审团。选m人的办法是：&lt;/p&gt;
&lt;p&gt;控方和辩方会根据对候选人的喜欢程度，给所有候选人打分，分值从0到20。为了公平起见，法官选出陪审团的原则是：选出的m个人，必须满足辩方总分和控方总分的差的绝对值最小。如果有多种选择方案的辩方总分和控方总分的之差的绝对值相同，那么选辩控双方总分之和最大的方案即可。&lt;/p&gt;
&lt;p&gt;输入&lt;/p&gt;
&lt;p&gt;输入包含多组数据。每组数据的第一行是两个整数n和m，n是候选人数目，m是陪审团人数。注意，$1 &amp;lt; = n &amp;lt; = 200 , 1 &amp;lt; = m &amp;lt; = 20 , m &amp;lt; = n$。接下来的n行，每行表示一个候选人的信息，它包含2个整数，先后是控方和辩方对该候选人的打分。候选人按出现的先后从1开始编号。两组有效数据之间以空行分隔。最后一组数据$n = m = 0$&lt;/p&gt;
&lt;p&gt;输出&lt;/p&gt;
&lt;p&gt;对每组数据，先输出一行，表示答案所属的组号,如 &apos;Jury #1&apos;, &apos;Jury #2&apos;,
等。接下来的一行要象例子那样输出陪审团的控方总分和辩方总分。再下来一行要以升序输出陪审团里每个成员的编号，两个成员编号之间用空格分隔。每组输出数据须以一个空行结束。&lt;/p&gt;
&lt;p&gt;样例输入&lt;/p&gt;
&lt;p&gt;#quote[
4 2&lt;/p&gt;
&lt;p&gt;1 2&lt;/p&gt;
&lt;p&gt;2 3&lt;/p&gt;
&lt;p&gt;4 1&lt;/p&gt;
&lt;p&gt;6 2&lt;/p&gt;
&lt;p&gt;0 0
]&lt;/p&gt;
&lt;p&gt;样例输出&lt;/p&gt;
&lt;p&gt;#quote[
Jury #1
]&lt;/p&gt;
&lt;p&gt;Best jury has value 6 for prosecution and value 4 for defence: 2 3&lt;/p&gt;
&lt;p&gt;为叙述问题方便，现将任一选择方案中，辩方总分和控方总分之差简称为“#strong[辩控差];”，辩方总分和控方总分之和称为“#strong[辩控和];”。第i个候选人的辩方总分和控方总分之差记为$V (i)$，辩方总分和控方总分之和记为$S (i)$。现用$f (j , k)$表示，取j个候选人，使其辩控差为k的所有方案中，辩控和最大的那个方案（该方案称为“方案$f (j , k)$”）的辩控和。并且，我们还规定，如果没法选j个人，使其辩控差为k，那么$f (j , k)$的值就为-1，也称方案$f (j , k)$不可行。本题是要求选出m个人，那么，如果对k的所有可能的取值，求出了所有的$f (m , k) (- 20 times m lt.eq k lt.eq 20 times m)$，那么陪审团方案自然就很容易找到了。&lt;/p&gt;
&lt;p&gt;问题的关键是建立递推关系。需要从哪些已知条件出发，才能求出$f (j , k)$呢？显然，方案$f (j , k)$是由某个可行的方案$f (j - 1 , x) (- 20 times m lt.eq x lt.eq 20 times m)$演化而来的。可行方案$f (j - 1 , x)$能演化成方案$f (j , k)$的必要条件是：存在某个候选人i，i在方案$f (j - 1 , x)$中没有被选上，且$x + V (i) = k$。在所有满足该必要条件的$f (j - 1 , x)$中，选出$f (j - 1 , x) + S (i)$的值最大的那个，那么方案$f (j - 1 , x)$再加上候选人i，就演变成了方案$f (j , k)$。这中间需要将一个方案都选了哪些人都记录下来。不妨将方案$f (j , k)$中最后选的那个候选人的编号，记在二维数组的元素$p a t h [j] [k]$中。那么方案$f (j , k)$的倒数第二个人选的编号，就是$p a t h [j - 1] [ k - V [p a t h [j] [k]]$。假定最后算出了解方案的辩控差是k，那么从$p a t h [m] [k]$出发，就能顺藤摸瓜一步步求出所有被选中的候选人。初始条件，只能确定$f (0 , 0) = 0$。由此出发，一步步自底向上递推，就能求出所有的可行方案$f (m , k) (- 20 times m lt.eq k lt.eq 20 times m)$。实际解题的时候，会用一个二维数组f来存放$f (j , k)$的值。而且，由于题目中辩控差的值k可以为负数，而程序中数租下标不能为负数，所以，在程序中不妨将辩控差的值都加上400，以免下标为负数导致出错，即题目描述中，如果辩控差为0，则在程序中辩控差为400。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include&amp;lt;stdio.h&amp;gt;
#include&amp;lt;stdlib.h&amp;gt;
#include&amp;lt;iostream&amp;gt;
#include&amp;lt;string.h&amp;gt;
usingnamespace std;
int f[30][1000];
//f[j,k]表示：取j个候选人，使其辩控差为k的方案中
//辩控和最大的那个方案（该方案称为“方案f(j,k)”)的控辩和
int Path[30][1000];
//Path数组用来记录选了哪些人
//方案f(j,k)中最后选的那个候选人的编号，记在Path[j][k]中
int P[300];//控方打分
int D[300]; //辩方打分

int Answer[30];//存放最终方案的人选

int cmp(constvoid*a,constvoid*b)
{
    return*(int*)a-*(int*)b;
}

int main()
{
   int i,j,k;
    int t1,t2;
    int n,m;
    int nMinP_D;//辩控双方总分一样时的辩控差
    int iCase;//测试数据编号
    iCase=0;
    while(scanf(&quot;%d %d&quot;,&amp;amp;n,&amp;amp;m))
    {
        if(n==0&amp;amp;&amp;amp;m==0)break;
        iCase++;
        for(i=1;i&amp;lt;=n;i++)
           scanf(&quot;%d %d&quot;,&amp;amp;P[i],&amp;amp;D[i]);
        memset(f,-1,sizeof(f));
        memset(Path,0,sizeof(Path));
        nMinP_D=m*20;//题目中的辩控差为0，对应于程序中的辩控差为m*20
        f[0][nMinP_D]=0;
        for(j=0;j&amp;lt;m;j++)//每次循环选出第j个人，共要选出m人
        {
            for(k=0;k&amp;lt;=nMinP_D*2;k++)//可能的辩控差为[0，nMinP_D*2]
            if(f[j][k]&amp;gt;=0)//方案f[j,k]可行
               {
                   for(i=1;i&amp;lt;=n;i++)
                       if(f[j][k]+P[i]+D[i]&amp;gt;f[j+1][k+P[i]-D[i]])
                       {
                           t1=j;t2=k;
                           while(t1&amp;gt;0&amp;amp;&amp;amp;Path[t1][t2]!=i)//验证i是否在前面出现过
                           {
                               t2-=P[Path[t1][t2]]-D[Path[t1][t2]];
                               t1--;
                           }
                           if(t1==0)
                           {
                               f[j+1][k+P[i]-D[i]]=f[j][k]+P[i]+D[i];
                               Path[j+1][k+P[i]-D[i]]=i;
                           }
                       }
               }
        }
        i=nMinP_D;
        j=0;
        while(f[m][i+j]&amp;lt;0&amp;amp;&amp;amp;f[m][i-j]&amp;lt;0)  j++;
        if(f[m][i+j]&amp;gt;f[m][i-j])  k=i+j;
        else k=i-j;
        printf(&quot;Jury #%d\n&quot;,iCase);
        printf(&quot;Best jury has value %d for prosecution and value %d for defence:\n&quot;,(k-nMinP_D+f[m][k])/2,(f[m][k]-k+nMinP_D)/2);
        for(i=1;i&amp;lt;=m;i++)
        {
            Answer[i]=Path[m-i+1][k];
            k-=P[Answer[i]]-D[Answer[i]];
        }
        qsort(Answer+1,m,sizeof(int),cmp);
        for(i=1;i&amp;lt;=m;i++)
          printf(&quot; %d&quot;,Answer[i]);
        printf(&quot;\n\n&quot;);

    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item></channel></rss>