-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.json
1 lines (1 loc) · 463 KB
/
index.json
1
[{"categories":null,"contents":"我认为, 在开发时, 单元测试的编写时间要早于或等于功能代码的编写时间.\n第一个测试 假如你现在有一个项目, 名为 HelloCpp, 你期望的是, 该项目编译运行后在命令行打印一行文字你好, 世界., 项目代码如下:\nmain.py\n1 2 3 4 5 6 7 8 #include \u0026lt;iostream\u0026gt; int main() { std::cout \u0026lt;\u0026lt; \u0026#34;Hello, World.\\n\u0026#34;; return 0; } 代码很简单, 可以很明显发现代码的运行结果和我们的期望不符, 会在终端打印文字Hello, World.而不是你好, 世界.. 但我们现在假装没能肉眼发现这个错误. 接下来通过单元测试来发现它.\nVisual Studio 菜单栏, File - New - Project, 打开 Create a new project 窗口, 选择Native Unit Test Project, 点击Next.\n自由设定Project name, 我这里设的是HelloCppUnitTest; 把Solution由Create new solution改为Add to solution; 点击Create.\n创建好后, 在解决方案里就能发现多出来一个项目, 就是你刚才创建的单元测试项目.\n在这个单元测试项目的References上点右键, 选Add Reference, 勾选要进行单元测试的项目, 点击OK. 这样在单元测试项目中就能访问\u0026quot;被测项目\u0026quot;的代码了.\n在创建好单元测试项目后, 默认会打开一个与项目同名的.cpp文件, 我们的单元测试就是写在这里. 默认会提供一个空壳子给我们:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include \u0026#34;pch.h\u0026#34; #include \u0026#34;CppUnitTest.h\u0026#34; using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace HelloCppUnitTest { TEST_CLASS(HelloCppUnitTest) { public: TEST_METHOD(TestMethod1) { } }; } 我们在文件顶部把要测试的代码文件 include 进来, 在 TEST_METHOD 的花括号里编写单元测试:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include \u0026#34;pch.h\u0026#34; #include \u0026#34;CppUnitTest.h\u0026#34; // 导入要进行测试的代码所在文件 #include \u0026#34;../CppHello/main.cpp\u0026#34; // 需要哪些头文件, 导入进来 #include \u0026lt;iostream\u0026gt; using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace HelloCppUnitTest { TEST_CLASS(HelloCppUnitTest) { public: TEST_METHOD(TestMethod1) { std::string expected = \u0026#34;你好, 世界.\\n\u0026#34;; std::stringstream buffer; std::streambuf* sbuf = std::cout.rdbuf(); // Save cout\u0026#39;s buffer // 保存 cout 的原始缓冲区的指针, 以便一会儿再重定向回来 std::cout.rdbuf(buffer.rdbuf()); // Redirect cout to the stringstream buffer // 将 cout 重定向到字符串流缓冲区 // 调用要测试的 main() 函数 // Call main() in your test int result = main(); // 完成后, 将 cout 还重定向到原来的缓冲区 // When finished, redirect cout to the original buffer std::cout.rdbuf(sbuf); std::cout \u0026lt;\u0026lt; \u0026#34;std original buffer: \\n\u0026#34;; std::cout \u0026lt;\u0026lt; buffer.get(); // 测试 // Test Assert::AreEqual(expected, buffer.str()); } }; } 单元测试编写好后, 在菜单栏选 Test - Test Explorer, 打开测试资源管理器, 选窗口顶部最左侧那个绿色箭头Run All Tests In View, 即可运行单元测试.\n测试结束后, 可以在测试资源管理器查看测试详情, 对比期望的结果和实际结果的差异.\n第二个测试 现在要测试下面代码里的类 MyClass 是否能正确初始化.\nmain.py\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include \u0026lt;iostream\u0026gt; #include \u0026lt;string\u0026gt; #include \u0026lt;string_view\u0026gt; #include \u0026lt;format\u0026gt; class MyClass { public: MyClass(std::string\u0026amp; nm) : name{ nm } {} std::string getName() { return name; } private: std::string name; }; int main() { std::string name{ \u0026#34;Aoyu\u0026#34; }; MyClass a{ name }; std::cout \u0026lt;\u0026lt; std::format(\u0026#34;{} 是我最好的朋友.\u0026#34;, a.getName()) \u0026lt;\u0026lt; std::endl; return 0; } 单元测试代码如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include \u0026#34;pch.h\u0026#34; #include \u0026#34;CppUnitTest.h\u0026#34; // 导入要进行测试的代码所在文件 #include \u0026#34;../CppHello/main.cpp\u0026#34; // 需要哪些头文件, 导入进来 #include \u0026lt;string\u0026gt; using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace HelloCppUnitTest { TEST_CLASS(HelloCppUnitTest) { public: TEST_METHOD(TestClassInit) { std::string name = \u0026#34;Aoyu\u0026#34;; MyClass mc(name); Assert::AreEqual(name, mc.getName()); } }; } 多个单元测试对应多个 TEST_METHOD. 如果把上面两个单元测试写一起, 就是下面这个样子:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include \u0026#34;pch.h\u0026#34; #include \u0026#34;CppUnitTest.h\u0026#34; // 导入要进行测试的代码所在文件 #include \u0026#34;../CppHello/main.cpp\u0026#34; // 需要哪些头文件, 导入进来 #include \u0026lt;string\u0026gt; using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace HelloCppUnitTest { TEST_CLASS(HelloCppUnitTest) { public: TEST_METHOD(TestMethod1) { std::string expected = \u0026#34;你好, 世界.\\n\u0026#34;; std::stringstream buffer; std::streambuf* sbuf = std::cout.rdbuf(); // Save cout\u0026#39;s buffer // 保存 cout 的原始缓冲区的指针, 以便一会儿再重定向回来 std::cout.rdbuf(buffer.rdbuf()); // Redirect cout to the stringstream buffer // 将 cout 重定向到字符串流缓冲区 // 调用要测试的 main() 函数 // Call main() in your test int result = main(); // 完成后, 将 cout 还重定向到原来的缓冲区 // When finished, redirect cout to the original buffer std::cout.rdbuf(sbuf); std::cout \u0026lt;\u0026lt; \u0026#34;std original buffer: \\n\u0026#34;; std::cout \u0026lt;\u0026lt; buffer.get(); // 测试 // Test Assert::AreEqual(expected, buffer.str()); } TEST_METHOD(TestClassInit) { std::string name = \u0026#34;Aoyu\u0026#34;; MyClass mc(name); Assert::AreEqual(name, mc.getName()); } }; } 很显然, TEST_METHOD(TestClassInit)可以测试通过; TEST_METHOD(TestMethod1)不能测试通过, 因为我们在main.cpp里修改了函数main()的代码. 在实际的开发中, 我们可以通过单元测试来发现哪些后续写的代码\u0026quot;不小心\u0026quot;让之前写的代码产生了异常.\n参考 单元测试入门\n在 Visual Studio 中编写 C/C++ 单元测试\n","date":"Oct 15","permalink":"https://o5o.me/post/cpp_msvc_unit_test/","tags":["C++"],"title":"MSVC编写C++代码的单元测试"},{"categories":["PHP"],"contents":"效果: 用户向公众号发送消息, 公众号后端服务器接收到消息后, 调用 RoamResearch 的 API, 创建一个新 block, 将用户的笔记保存到 RR Graph 内.\n代码如下, 目前还比较简陋:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 use Log; // ... Log::info($message); if ($message[\u0026#34;FromUserName\u0026#34;] == \u0026#34;我的微信标识\u0026#34; \u0026amp;\u0026amp; $message[\u0026#34;MsgType\u0026#34;] == \u0026#34;text\u0026#34;) { $today = date(\u0026#39;m-d-Y\u0026#39;); $data_create_block = [ \u0026#34;action\u0026#34; =\u0026gt; \u0026#34;create-block\u0026#34;, \u0026#34;location\u0026#34; =\u0026gt; [\u0026#34;parent-uid\u0026#34; =\u0026gt; $today, \u0026#34;order\u0026#34; =\u0026gt; \u0026#34;last\u0026#34;], \u0026#34;block\u0026#34; =\u0026gt; [\u0026#34;string\u0026#34; =\u0026gt; $message[\u0026#34;Content\u0026#34;]], ]; $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_URL =\u0026gt; \u0026#34;https://api.roamresearch.com/api/graph/graph名称/write\u0026#34;, CURLOPT_RETURNTRANSFER =\u0026gt; true, CURLOPT_FOLLOWLOCATION =\u0026gt; true, # important CURLOPT_UNRESTRICTED_AUTH =\u0026gt; true, # important CURLOPT_ENCODING =\u0026gt; \u0026#34;\u0026#34;, CURLOPT_MAXREDIRS =\u0026gt; 10, CURLOPT_TIMEOUT =\u0026gt; 30000, CURLOPT_HTTP_VERSION =\u0026gt; CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST =\u0026gt; \u0026#34;POST\u0026#34;, CURLOPT_POSTFIELDS =\u0026gt; json_encode($data_create_block), CURLOPT_HTTPHEADER =\u0026gt; array( \u0026#34;Content-Type: application/json\u0026#34;, \u0026#34;Authorization: \u0026#34; . \u0026#34;Bearer \u0026#34; . \u0026#34;你在RR申请的key, 开头是roam-graph-token-\u0026#34;, \u0026#34;Accept: application/json\u0026#34;, ), )); $response = curl_exec($curl); $err = curl_error($curl); $http_info = curl_getinfo($curl); curl_close($curl); if ($err) { Log::info(\u0026#34;fail\u0026#34; . $err); return \u0026#34;fail\u0026#34;; } if ($http_info[\u0026#34;http_code\u0026#34;] == 200) { Log::info(\u0026#34;success\u0026#34;); return \u0026#34;success\u0026#34;; } return \u0026#34;fail\u0026#34;; } return \u0026#34;你不该在这里\u0026#34;; 和普通的 POST 请求不同, 向 api 发送 POST 请求后, url 会进行重定向, 因此, 要为 cURL 添加选项, 使其支持重定向, 并且在向重定向后的 url 发送请求时, 需要请求头依然附带 Authorization 字段. 因此下面这两个选项非常关键:\n1 2 CURLOPT_FOLLOWLOCATION =\u0026gt; true, # important CURLOPT_UNRESTRICTED_AUTH =\u0026gt; true, # important 第一个选项CURLOPT_FOLLOWLOCATION, PHP手册中是这样写的: true 时将会根据服务器返回 HTTP 头中的 \u0026ldquo;Location: \u0026quot; 重定向。(注意:这是递归的,\u0026ldquo;Location: \u0026quot; 发送几次就重定向几次,除非设置了 CURLOPT_MAXREDIRS,限制最大重定向次数)。\n第二个选项CURLOPT_UNRESTRICTED_AUTH, PHP手册中是这样写的: true 在使用CURLOPT_FOLLOWLOCATION重定向 header 中的多个 location 时继续发送用户名和密码信息,哪怕主机名已改变。\n我在使用 GuzzleHttp 时没有发现有类似的设置项.\n","date":"Jul 13","permalink":"https://o5o.me/post/php_curl_post_roamresearch_api_create_block/","tags":null,"title":"PHP cURL 调用 RoamResearch API 创建 Block"},{"categories":null,"contents":"挺喜欢 Keras 在模型训练时显示的那个进度条, 打算移植到 PyTorch 的训练代码上.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import sys import time def print_msg(message, line_break=False): \u0026#34;\u0026#34;\u0026#34;打印消息到标准输出\u0026#34;\u0026#34;\u0026#34; if line_break: sys.stdout.write(\u0026#34;\\r\u0026#34; + message + \u0026#34;\\n\u0026#34;) else: sys.stdout.write(\u0026#34;\\r\u0026#34; + message) sys.stdout.flush() def time_duration_value(): return \u0026#34;6s 12ms/step\u0026#34; def loss_value(): return \u0026#34;loss: 0.0377\u0026#34; def accuracy_value(): return \u0026#34;accuracy: 0.9890\u0026#34; epoch_count = 5 for i in range(epoch_count): print_msg(f\u0026#34;Epoch {i+1:{len(str(epoch_count))}}/{epoch_count}\u0026#34;, line_break=True) batch_count = 10 for j in range(batch_count): message = f\u0026#34;{j+1:{len(str(batch_count))}}/{batch_count} [{\u0026#39;=\u0026#39; * (j+1):{batch_count}}]\u0026#34; message += \u0026#34; - \u0026#34; + time_duration_value() message += \u0026#34; - \u0026#34; + loss_value() message += \u0026#34; - \u0026#34; + accuracy_value() print_msg(message) time.sleep(0.1) print_msg(message, line_break=True) 效果:\n1 2 3 4 5 6 Epoch 1/5 10/10 [==========] - 6s 12ms/step - loss: 0.0377 - accuracy: 0.9890 Epoch 2/5 10/10 [==========] - 6s 12ms/step - loss: 0.0377 - accuracy: 0.9890 Epoch 3/5 5/10 [===== ] - 6s 12ms/step - loss: 0.0377 - accuracy: 0.9890 ","date":"Jun 29","permalink":"https://o5o.me/post/keras_training_progress_bar/","tags":["Keras"],"title":"模仿Keras的进度条"},{"categories":["Python"],"contents":"在导入包后, 往往想要打印出包的版本, 如:\n1 2 import torch import torchvision 想要打印出:\n1 2 torch.__version__: 2.0.1+cu117 torchvision.__version__: 0.15.2+cu117 但如果像下面这样写, 就太不优雅了:\n1 2 print(f\u0026#34;torch.__version__: {torch.__version__}\u0026#34;) print(f\u0026#34;torchvision.__version__: {torchvision.__version__}\u0026#34;) 也就是在代码里, 每想查看一个包的版本号, 实际上要写两遍__version__属性名, 一个用于打印普通字符串, 一个用于取属性值.\n有没有这样一个函数, 把变量名(或属性名)传进去, 就能打印出名称: 值的字符串呢?\n我在网上搜索这个问题, 找到了解决办法, 并根据同样的思路, 写了一个自己的版本.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import re import inspect def print_name_and_value(variable_a): \u0026#34;\u0026#34;\u0026#34; 打印变量名(传递给函数的实参名), 及其值 \u0026#34;\u0026#34;\u0026#34; current_func_info = inspect.getframeinfo(inspect.currentframe().f_back) codes: list = current_func_info[3] # 取得code_context, 也就是调用该函数的那行代码的文本 r_list = re.findall(r\u0026#39;\\bprint_name_and_value\\(\\s*(.*?)\\s*\\)\u0026#39;, codes[0], re.S) # \\b, 单词的边界 # \\s, 任何一个空白字符(等价于`[\\f\\n\\r\\t\\v]`) # re.S, 包括换行符在内的任意字符 if r_list: print(f\u0026#34;{r_list[0]}: {variable_a}\u0026#34;) # return (r_list[0], variable_a) return None 运行函数:\n1 2 print_name_and_value(torch.__version__) print_name_and_value(torchvision.__version__) 函数运行后, print_name_and_value函数拿到调用它的那行代码的原文, 然后用正则表达式从中取出函数名后面那个括号里的内容.\n上述代码我暂时只在Jupyter里进行了测试.\n2023 年 7 月 11 日 更新:\n属实是我孤陋寡闻了, Python 3.8 中, f-strings 新增了 = 说明符, 如果想要以 表达式文本 = 表达式的值 的形式打印文本, 只需要这样来写(以 torch.__version__ 为例):\n1 2 3 import torch print(f\u0026#34;{torch.__version__ = }\u0026#34;) 运行结果为:\ntorch.__version__ = \u0026#39;2.0.1\u0026#39; ","date":"Jun 29","permalink":"https://o5o.me/post/python_print_both_variable_name_and_value/","tags":null,"title":"Python打印变量名(属性名)及其值"},{"categories":["Android"],"contents":"众所周知, 小米/红米手机在root前需解BL锁, 如果从官方渠道解锁, 需要等一周甚至更长的时间. 好在联发科芯片的手机可以强制解BL. 解BL锁需让手机进入brom模式, 方法如下:\n打开解BL的软件, 等待手机连接. 数据线一段先连接到电脑上, 之后, 同时按住手机的音量+和音量-键, 然后将数据线另一端插到手机上(不要松开按着手机音量键的手指), 这样就进入brom模式了, 软件会提示已连接到手机(这时再松开按着按键的手指). 需要注意的是, 手机在该模式下是黑屏的状态, 屏幕上没有任何提示.\n我在 Redmi9 上测试通过.\n","date":"Jun 05","permalink":"https://o5o.me/post/android_mi_brom_mode/","tags":["Redmi"],"title":"红米手机进入brom模式的方法"},{"categories":["DeepLearning"],"contents":"本地信息: winidows11, 命令行软件是Windows Terminal, 安装有wsl, 系统是22.04.\n服务器信息: Ubuntu 22.04, 将安装pytorch, 用显卡来训练模型.\n基本环境安装 SSH登录 1 ssh username@ip 更新系统 1 sudo apt update 安装Python 1 sudo apt install python3 安装Miniconda 从页面Miniconda — conda documentation复制下载地址.\n到服务器用下面的命令下载文件:\n1 wget https://repo.anaconda.com/miniconda/Miniconda3-py310_23.3.1-0-Linux-x86_64.sh 安装:\n1 bash Miniconda3-py310_23.3.1-0-Linux-x86_64.sh 安装完毕后, 再运行bash, 就进入base环境了.\n安装基础依赖 1 sudo apt install build-essential 安装nvidia driver 搜索驱动包\n1 sudo apt search nvidia-driver | grep nvidia-driver 安装最新的:\n1 sudo apt install nvidia-driver-530 安装成功后就可以用nvidia-smi看到系统信息了.\n用pip安装torch等包 我这里用的pip, 也可以到pytorch官网看推荐的安装命令. 听李沐说似乎用conda更好一点. 不过既然用pip装了, 那我就这样来用了.\n1 pip install jupyter d2l torch torchvision 测试显卡是否可用 1 2 3 4 python import torch torch.cuda.is_available() 关闭SSH连接 快捷键: ctrl+d, 也可以通过在命令行输入exit来退出.\n好的小Tips 端口映射 把服务器端口8888映射到本地8888端口, 这样在服务器运行jupyter notebook的时候, 就可以在本地直接打开链接了.\n1 ssh -L8888:localhost:8888 username@ip 配置免密登录 在本地, 命令行输入ssh-keygen, 一路默认, 生成密钥. 最后将有提示, 里面有两个关键的路径:\n1 2 - Your identification has been saved in C:\\Users\\xxx/.ssh/id_rsa - Your public key has been saved in C:\\Users\\xxx/.ssh/id_rsa.pub 接下来将公钥发送到服务器. 因为windows没有ssh-copy-id这个命令, 因此用下面的命令\n1 cat ~/.ssh/id_rsa.pub | ssh username@ip \u0026#34;cat \u0026gt;\u0026gt; ~/.ssh/authorized_keys\u0026#34; 之后再ssh登录服务器就不需要输入密码了.\n如果是linux系统, 可以用下面的命令:\n1 sshpass -p\u0026#39;你的密码\u0026#39; ssh-copy-id -i ~/.ssh/id_rsa.pub -f -o StrictHostKeyChecking=no username@ip 注意, 如果你的密码里有特殊字符, 那么密码两边的单引号是必不可少的.\n用vscode连接远程服务器 我习惯了用vscode来写代码.\n安装vscode插件\u0026quot;Remote - SSH\u0026quot;.\n启用插件后, 在软件窗口左侧找到一个像\u0026quot;电脑\u0026quot;的小图标, 在侧栏中点\u0026quot;齿轮\u0026quot;, 打开设置, 配置文件有几个位置, 我选的是用户目录下的那个配置文件. 写入下面的内容:\n1 2 3 4 5 Host 名字 # 给主机起一个名字 HostName 服务器ip User 用户名 Port 22 IdentityFile \u0026#34;C:\\Users\\xxx/.ssh/id_rsa\u0026#34; 保存配置, 刷新一下远程主机列表(有一个小按钮, 仔细瞅瞅), 看到添加的服务器后, 连接, 应该可以直接连上.\n将本地文件上传到服务器 上传数据集. 我是先在本地压缩成zip文件, 然后用rsync同步到服务器. 由于windows用rsync不方便, 因此这一步我是在wsl里做的. 命令:\n1 rsync /mnt/d/xxx/dataset.zip username@ip:/home/ubuntu/d2l --info=progress2 参数--info=progress2是为了在传输过程中显示进度的, 不然的话啥都不显示, 不知道传了多少了, 也不知道还需要传多长时间.\n解压zip压缩包. 文件上传到服务器后, 可用下面的命令来解压.\n1 unzip dataset.zip 可以加参数: -d, 解压到指定文件夹, -q, 安静模式, 解压时不显示任何信息.\n最后 本文主要参考李沐的视频03 安装【动手学深度学习v2】_哔哩哔哩_bilibili 和 环境安装,BERT、GPT、T5 性能测试,和横向对比【100亿模型计划】_哔哩哔哩_bilibili, 在此表示感谢.\n","date":"May 29","permalink":"https://o5o.me/post/deep_learning_environment_basic/","tags":["Pytorch"],"title":"深度学习环境安装"},{"categories":["Computer"],"contents":"这台电脑是今年3月份买的(联想小新Pro14 2022 锐龙版), 刚买回来就出现了触控板和蓝牙鼠标突然失灵的问题, 具体表现是: 电脑正用的好好的, 突然触控板就不能用了, 蓝牙鼠标突然就断连了, 但键盘还可以输入. 长按电源键重启后又能恢复正常.\n当时从网上搜到了一个教程, 设置后就没了这个问题, 但随着系统自动更新, 这个问题前不久又出现了(我猜是我的设置被重置为默认设置了).\n我还想去网上找之前看的那篇教程来进行设置, 但怎么找都找不到. 今天再次遇到这个问题, 于是花了一些时间把它解决了, 记录下来以防万一.\n打开设备管理器(快捷键Win + X, 选\u0026quot;设备管理器\u0026quot;), 在列表里找到\u0026quot;人体学输入设备\u0026quot;, 下面有两个设备, 名字相同, 叫做: 符合蓝牙低能耗GATT的HID设备, 这两个设备都进行如下设置: 在设备名称上面点鼠标右键, 点击\u0026quot;属性\u0026quot;, 选\u0026quot;电源管理\u0026quot;菜单, 取消勾选\u0026quot;允许计算机关闭此设备以节约电源\u0026quot;, 点击\u0026quot;确定\u0026quot;.\n这个问题产生的原因, 由于知识匮乏, 我没办法深究, 只能简单归结为联想的问题.\n","date":"May 18","permalink":"https://o5o.me/post/lenovo_computer_xiaoxin_pro_14_touchpad_mouse_out_of_control/","tags":["Lenovo"],"title":"联想小新Pro14触控板和蓝牙鼠标突然失灵的解决办法"},{"categories":["Python"],"contents":" 题目 现有一份关于巧克力评价的数据集:\n1 2 df = pd.read_csv(\u0026#39;../data/chocolate.csv\u0026#39;) df.head(3) 把列索引名中的\\n替换为空格。 巧克力Rating评分为1至5,每0.25分一档,请选出2.75分及以下且可可含量Cocoa Percent高于中位数的样本。 将Review Date和Company Location设为索引后,选出Review Date在2012年之后且Company Location不属于France, Canada, Amsterdam, Belgium的样本。 第 1 问 1 2 df.columns = df.columns.map(lambda x: x.replace(\u0026#39;\\n\u0026#39;, \u0026#39; \u0026#39;)) df.head() 第 2 问 1 2 3 cocoa_percent_median = df[\u0026#34;Cocoa Percent\u0026#34;].apply(lambda x: float(x[:-1]) / 100).median() df[(df.Rating \u0026lt;= 2.75) \u0026amp; (df[\u0026#34;Cocoa Percent\u0026#34;].apply(lambda x: float(x[:-1]) / 100) \u0026gt; cocoa_percent_median)] 第 3 问 将Review Date和Company Location设为索引后,选出Review Date在2012年之后且Company Location不属于France, Canada, Amsterdam, Belgium的样本。\n1 2 df.set_index([\u0026#34;Review Date\u0026#34;, \u0026#34;Company Location\u0026#34;], inplace=True) df.query(\u0026#39;(`Review Date` \u0026gt; 2012) \u0026amp; (`Company Location` not in [\u0026#34;France\u0026#34;, \u0026#34;Canada\u0026#34;, \u0026#34;Amsterdam\u0026#34;, \u0026#34;Belgium\u0026#34;])\u0026#39;) 我尝试用loc去做这一小题, 但没能做到.\n","date":"May 17","permalink":"https://o5o.me/post/joyful_pandas_3_2_indexes/","tags":["Pandas"],"title":"Joyful Pandas 索引 练习3.2 巧克力数据集"},{"categories":["Python"],"contents":" 题目 现有一份公司员工数据集:\n1 2 df = pd.read_csv(\u0026#39;../data/company.csv\u0026#39;) df.head(3) 分别只使用query和loc选出年龄不超过四十岁且工作部门为Dairy或Bakery的男性。 选出员工ID号为奇数所在行的第1、第3和倒数第2列。 按照以下步骤进行索引操作: 把后三列设为索引后交换内外两层 恢复中间层索引 修改外层索引名为Gender 用下划线合并两层行索引 把行索引拆分为原状态 修改索引名为原表名称 恢复默认索引并将列保持为原表的相对位置 第 1 问 使用query\n1 df.query(\u0026#39;(age \u0026lt;= 40) \u0026amp; (department in [\u0026#34;Dairy\u0026#34;, \u0026#34;Bakery\u0026#34;]) \u0026amp; (gender == \u0026#34;M\u0026#34;)\u0026#39;) 使用loc\n1 2 3 4 5 condition_1 = df.age \u0026lt;= 40 condition_2_1 = df.department == \u0026#34;Dairy\u0026#34; condition_2_2 = df.department == \u0026#34;Bakery\u0026#34; condition_3 = df.gender == \u0026#34;M\u0026#34; df.loc[condition_1 \u0026amp; (condition_2_1 | condition_2_2) \u0026amp; condition_3] 第 2 问 题目里这句有点不好理解: 选出员工ID号为奇数所在行的第1、第3和倒数第2列。\n我理解的是:\n员工ID号为奇数 选出这些记录后, 输出第1、第3和倒数第2列的值 1 df.loc[df.EmployeeID % 2 != 0].iloc[:, [1, 3, -2]] 第 3 问 把后三列设为索引后交换内外两层\n1 2 3 # 把后三列设为索引 index_list = df.iloc[0,-3:].index.to_list() df.set_index(index_list, inplace=True) 1 2 # 交换内外两层, 我理解的是交换第1个索引和第3个索引的位置 df = df.swaplevel(0, 2, axis=0) 恢复中间层索引\n1 2 3 # 我理解的是, 把第2个索引再变成正常的列 # 这个只是删除索引, 错的: df = df.droplevel(1, axis=0) df = df.reset_index([\u0026#34;job_title\u0026#34;]) 修改外层索引名为Gender\n1 df.rename_axis(index={\u0026#39;gender\u0026#39;: \u0026#39;Gender\u0026#39;}, inplace=True) 用下划线合并两层行索引\n1 2 3 4 5 new_idx = df.index.map(lambda x: (\u0026#39;_\u0026#39;.join(x))) df.index = new_idx df.head() 把行索引拆分为原状态\n1 2 3 4 5 new_idx = df.index.map(lambda x: tuple(x.split(\u0026#39;_\u0026#39;))) df.index = new_idx df.head() 修改索引名为原表名称\n1 df.index.set_names([\u0026#34;gender\u0026#34;, \u0026#34;department\u0026#34;], inplace=True) 恢复默认索引并将列保持为原表的相对位置\n1 2 df = df.reset_index() df = df.iloc[:, [3, 4, 5, 6, 1, 2, 0]] ","date":"May 17","permalink":"https://o5o.me/post/joyful_pandas_3_1_indexes/","tags":["Pandas"],"title":"Joyful Pandas 索引 练习3.1 公司员工数据集"},{"categories":["Python"],"contents":"从某信读书下载下来一本电子书, 是一个html文件, 里面包含所有的文字内容, 但里面的图片依然都是在线链接. 本文的目的是把这些图片都下载到本地, 并且把html页面里面的图片链接都替换成本地的链接.\n导入预计用到的模块 1 2 3 4 5 6 # 导包 import requests import os import pathlib from lxml import etree import re 读取html文件 1 2 3 4 5 6 base_dir = pathlib.Path(r\u0026#34;D:\\Projects\\book_hands_on_machine_learning\u0026#34;) file_path = base_dir / \u0026#34;book.html\u0026#34; with open(file_path, \u0026#39;r\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: html_content = f.read() XPATH解析得到图片的xpath对象列表 1 2 3 4 5 6 def parse_xpath(html, xpath_stat): parse_html = etree.HTML(html) result_list = parse_html.xpath(xpath_stat) return result_list img_xpath_list = parse_xpath(html_content, \u0026#39;//img\u0026#39;) 得到图片链接的列表 1 2 3 4 5 6 7 8 9 10 11 12 13 def get_img_list(xpath_list): img_url_list = [] for item in xpath_list: url = item.xpath(\u0026#39;./@src\u0026#39;)[0] img_url_list.append(url) # 链接去重并保留原顺序 img_url_list_new = list(set(img_url_list)) img_url_list_new.sort(key=img_url_list.index) return img_url_list_new img_url_list = get_img_list(img_xpath_list) 下载图片到本地 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 def save_img(img_urls: list): headers = { \u0026#39;User-Agent\u0026#39;: \u0026#39;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36\u0026#39;, \u0026#39;Cookie\u0026#39;: \u0026#39;填自己的\u0026#39; } url_file_path_tuple_list = [] img_list_length = len(img_urls) img_dir = base_dir / \u0026#39;imgs\u0026#39; if not os.path.exists(img_dir): os.makedirs(img_dir) for i, url in enumerate(img_urls): img_name = url.split(\u0026#39;/\u0026#39;)[-1] + \u0026#39;.jpg\u0026#39; img_path = img_dir / img_name res = requests.get(url=url, headers=headers).content with open(img_path, \u0026#39;wb\u0026#39;) as f: f.write(res) print(f\u0026#34;进度: {(i + 1) / img_list_length * 100:.2f}% 图片 {url} 下载成功.\u0026#34;) url_file_path_tuple_list.append((url, os.path.relpath(img_path, base_dir))) return url_file_path_tuple_list url_file_path_tuple_list = save_img(img_url_list) 替换html页面里的图片链接为本地链接 1 2 3 4 5 6 7 def replace_img_url(html_content, url_file_path_tuple_list): for url, img_path in url_file_path_tuple_list: html_content = html_content.replace(f\u0026#39;src=\u0026#34;{url}\u0026#34;\u0026#39;, f\u0026#39;src=\u0026#34;{img_path}\u0026#34;\u0026#39;) return html_content new_parsed_html = replace_img_url(html_content, url_file_path_tuple_list) html其他调整 图片img标签外包裹有一层a标签, 点击图片会打开一个链接. 因此我选择把html里的所有a标签都去掉.\n1 2 3 4 5 new_parsed_html = re.sub(r\u0026#39;href=\u0026#34;http://popImage\\?src=.*?\u0026#34;\u0026#39;, \u0026#39;href=\u0026#34;#\u0026#34;\u0026#39;, new_parsed_html) new_parsed_html = re.sub(r\u0026#39;\u0026lt;a .*?\u0026gt;\u0026#39;, \u0026#39;\u0026#39;, new_parsed_html) new_parsed_html = re.sub(r\u0026#39;\u0026lt;/a\u0026gt;\u0026#39;, \u0026#39;\u0026#39;, new_parsed_html) 保存文件 1 2 with open(base_dir / \u0026#39;result.html\u0026#39;, \u0026#39;w\u0026#39;, encoding=\u0026#34;utf-8\u0026#34;) as f: f.write(new_parsed_html) 最后就得到了一本可以纯本地阅读的电子书. 进一步地, 还可以把它转换成epub格式的电子书. 这就是后面的话题了.\n","date":"May 16","permalink":"https://o5o.me/post/download_images_html/","tags":["requests"],"title":"下载网页图片到本地并替换网页内的图片链接"},{"categories":["Python"],"contents":"所用环境为Colab.\n定义任务目标 拿到某地区的一些信息, 如犯罪率, 地方房产税率等, 预测该地区的房价.\n数据收集 使用 波士顿房价 数据集.\n1 2 3 # 加载数据集(需联网) from tensorflow.keras.datasets import boston_housing (train_data, train_targets), (test_data, test_targets) = boston_housing.load_data() 数据可视化 建立对数据形状的感受.\n1 2 3 4 5 6 # 检查数据 train_data.shape, train_targets.shape, test_data.shape, test_targets.shape, train_data.dtype # ((404, 13), (404,), (102, 13), (102,), dtype(\u0026#39;float64\u0026#39;)) type(train_data), type(train_data[0]), train_data.ndim # (numpy.ndarray, numpy.ndarray, 2) 看一下数据集里的数据到底长什么样子, 数据对应的标签是什么.\n1 2 3 4 5 train_data[0] # array([ 1.23247, 0. , 8.14 , 0. , 0.538 , 6.142 , 91.7 , 3.9769 , 4. , 307. , 21. , 396.9 , 18.72 ]) train_targets[0] # 15.2 数据标注 已标注好\n数据清理 无需清理\n数据预处理 数据向量化和标准化 1 2 3 4 5 6 7 8 9 # 数据标准化 mean = train_data.mean(axis=0) train_data -= mean std = train_data.std(axis=0) train_data /= std test_data -= mean # 注意, 测试数据标准化所用的均值和标准差值也是从训练数据里得到的, 而不是用测试数据的. test_data /= std 处理缺失值 不需要处理缺失值.\n数据划分: 训练集, 验证集, 测试集 数据量太少了, 使用K折交叉验证.\n选择模型评估方法 评估方法选择 这里选择平均绝对误差(mean absolute error, MAE)作为模型评估的指标. 它是预测值与目标值之差的绝对值.\n如果这个问题的MAE等于0.5, 就表示预测房价与实际价格平均相差500美元.\n确定简单基准(模型应能超越这个基准) 无\n构建第一个模型 特征选择(过滤没有信息量的特征; 开发新特征) 在本例中特征就是该地区各房屋周边的相关信息了.\n选择架构 两个中间层, 每层64个单元. 第三层输出预测值.\n层1: Dense层, \u0026ldquo;表示空间\u0026quot;的维数units为64, 激活函数activation为relu.\n层2: Dense层, \u0026ldquo;表示空间\u0026quot;的维数units为64, 激活函数activation为relu.\n层3: Dense层, \u0026ldquo;表示空间\u0026quot;的维数units为1, 它是一个线性层, 没有激活函数. 这是标量回归(预测单一连续值的回归)的典型设置.\n训练配置(损失函数, 批量大小, 学习率) 优化器 optimizer 这里选rmsprop\n损失函数 loss function 这里选均方误差(mean squared error, MSE)损失函数mse, 预测值与目标值之差的平方.\n训练轮数 这里训练100轮.\n数据批量大小 批量大小设为16.\n模型构建代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from tensorflow import keras from tensorflow.keras import layers # 由于需要将同一个模型多次实例化, 因此用一个函数来构建模型. def build_model(): # 构建模型 model = keras.Sequential([ layers.Dense(64, activation=\u0026#34;relu\u0026#34;), layers.Dense(64, activation=\u0026#34;relu\u0026#34;), layers.Dense(1) ]) # 编译模型 model.compile(optimizer=\u0026#34;rmsprop\u0026#34;, loss=\u0026#34;mse\u0026#34;, metrics=[\u0026#34;mae\u0026#34;]) return model 拟合模型 k折交叉验证 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import numpy as np k = 4 # 分为4折, 3折作为训练数据, 1折作为验证数据 num_val_samples = len(train_data) // k # 多少条数据作为验证数据 num_epochs = 100 # 训练100轮 all_scores = [] for i in range(k): print(f\u0026#34;Processing fold #{i}\u0026#34;) val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples] # 准备验证数据: 第i个分区的数据 val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples] # 验证数据目标值: 第i个分区的数据 partial_train_data = np.concatenate([train_data[:i * num_val_samples], train_data[(i+1) * num_val_samples:]], axis=0) partial_train_targets = np.concatenate([train_targets[:i * num_val_samples], train_targets[(i+1) * num_val_samples:]], axis=0) model = build_model() # 构建模型(已编译) model.fit(partial_train_data, partial_train_targets, epochs=num_epochs, batch_size=16, verbose=0) # 训练模型(静默模式, verbose=0) val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=0) # 在验证数据上评估模型 all_scores.append(val_mae) 输出:\nProcessing fold #0 Processing fold #1 Processing fold #2 Processing fold #3 训练时各轮的所有分数:\n1 2 3 4 5 all_scores # [1.963736653327942, 2.7470197677612305, 2.514702558517456, 2.423671007156372] np.mean(all_scores) # 2.41228249669075 改进模型 上面的模型, 最后得到的是在每一折上训练100轮后的得分(mae). 现在修改模型, 在每一折上训练500轮, 把每一轮的得分都记录下来. 最后计算每一轮在每一折上的平均得分. 最后得到的应该是500个值.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import numpy as np k = 4 # 分为4折, 3折作为训练数据, 1折作为验证数据 num_val_samples = len(train_data) // k # 多少条数据作为验证数据 num_epochs = 500 # 训练100轮 all_mae_histories = [] for i in range(k): print(f\u0026#34;Processing fold #{i}\u0026#34;) val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples] # 准备验证数据: 第i个分区的数据 val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples] # 验证数据目标值: 第i个分区的数据 partial_train_data = np.concatenate([train_data[:i * num_val_samples], train_data[(i+1) * num_val_samples:]], axis=0) partial_train_targets = np.concatenate([train_targets[:i * num_val_samples], train_targets[(i+1) * num_val_samples:]], axis=0) model = build_model() # 构建模型(已编译) history = model.fit(partial_train_data, partial_train_targets, validation_data=(val_data, val_targets), epochs=num_epochs, batch_size=16, verbose=0) # 训练模型(静默模式, verbose=0) mae_history = history.history[\u0026#34;val_mae\u0026#34;] all_mae_histories.append(mae_history) # Processing fold #0 # Processing fold #1 # Processing fold #2 # Processing fold #3 结果:\n1 2 3 4 5 6 len(all_mae_histories) # 4 average_mae_history = [np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)] len(average_mae_history) # 500 可视化拟合结果 绘制验证MAE曲线 1 2 3 4 5 6 import matplotlib.pyplot as plt plt.plot(range(1, len(average_mae_history) + 1), average_mae_history) plt.xlabel(\u0026#34;Epochs\u0026#34;) plt.ylabel(\u0026#34;Validation MAE\u0026#34;) plt.show() 前面几轮的验证MAE远大于后面的轮次, 很难从图中发现规律. 忽略前10个数据点.\n绘制验证MAE曲线(剔除前10个数据点) 1 2 3 4 5 6 7 8 import matplotlib.pyplot as plt truncated_mae_history = average_mae_history[10:] plt.plot(range(1, len(truncated_mae_history) + 1), truncated_mae_history) plt.xlabel(\u0026#34;Epochs\u0026#34;) plt.ylabel(\u0026#34;Validation MAE\u0026#34;) plt.show() 从图中看出, 当训练到120轮之后验证数据的MAE就不再显著降低, 之后就开始过拟合.\n训练最终模型 1 2 3 model = build_model() model.fit(train_data, train_targets, epochs=130, batch_size=16, verbose=0) # 在所有训练数据上训练模型 test_mse_score, test_mae_score = model.evaluate(test_data, test_targets) 结果:\n1 2 test_mae_score # 2.342545747756958 评价模型 上面得到的MAE为2.34, 也就是说, 房价预测值和实际值平均相差了2340美元. 差距依旧很大, 不过比最开始的模型有了一点点进步.\n利用模型进行预测 拿到一栋房屋的各项特征数据, 把它像上面那样进行数据预处理, 之后用model.predict()进行预测.\n1 2 predictions = model.predict(test_data) predictions[0] 结果:\n4/4 [==============================] - 0s 4ms/step array([7.722374], dtype=float32) 预测得到的值为7.722374, 表明模型认为, 测试数据里第一所房子的价格约为7722美元.\n1 2 test_targets[0] # 7.2 房屋的实际价格是7200美元, 预测结果和真实价格相差了500美元.\n部署模型 不部署.\n","date":"May 06","permalink":"https://o5o.me/post/keras_case_regression_house_price_forecast/","tags":["Keras","回归"],"title":"keras回归问题: 房价预测"},{"categories":["Python"],"contents":"所用环境为Colab.\n定义任务目标 拿到一些新闻报道, 这些新闻报道都只属于某一个主题, 而不会同时属于多个主题. 把这些新闻报道归类到对应的主题下. 这是一个单标签多分类问题.\n数据收集 使用 路透社 数据集.\n1 2 3 # 加载数据集(需联网) from tensorflow.keras.datasets import reuters (train_data, train_labels), (test_data, test_labels) = reuters.load_data(num_words=10000) # num_words=10000, 仅保留数据中前10000个最常出现的单词 数据可视化 建立对数据形状的感受.\n1 2 3 4 5 6 # 检查数据 train_data.shape, train_labels.shape, test_data.shape, test_labels.shape, train_data.dtype # ((8982,), (8982,), (2246,), (2246,), dtype(\u0026#39;O\u0026#39;)) type(train_data), type(train_data[0]), train_data.ndim # (numpy.ndarray, list, 1) 看一下数据集里的数据到底长什么样子, 数据对应的标签是什么.\n1 2 3 4 5 6 7 8 9 10 # 查看第1条新闻的前10个单词, 以及它的标签(所属主题) train_data[0][:10] # 新闻里的单词被转换为一个个数字. # [1, 2, 2, 8, 43, 10, 447, 5, 25, 207] train_labels[0] # 3 该新闻属于第3个分类 # 最长的一条新闻的长度是多少 max([max(item) for item in train_data]) # 先得到每条新闻的长度, 再得到长度的最大值 数据集里的评论被编码为了数字, 解码成正常文本看看.\n1 2 3 4 5 6 word_index = reuters.get_word_index() # word_index 是一个字典, 键是单词, 值是对应的一个整数. reverse_word_index = dict([(value, key) for (key, value) in word_index.items()]) # 一个新字典, 键是一个整数, 值是该整数对应的单词 decoded_newswire = \u0026#34; \u0026#34;.join([reverse_word_index.get(i - 3, \u0026#34;?\u0026#34;) for i in train_data[0]]) # 把训练集中的第一条新闻解码成正常文本. 索引减3是因为, 训练集里的单词对应的整数, 相比于字典, 都向右偏移了3. decoded_newswire 解码出来的文本:\n? ? ? said as a result of its december acquisition of space co it expects earnings per share in 1987 of 1 15 to 1 30 dlrs per share up from 70 cts in 1986 the company said pretax net should rise to nine to 10 mln dlrs from six mln dlrs in 1986 and rental operation revenues to 19 to 22 mln dlrs from 12 5 mln dlrs it said cash flow per share this year should be 2 50 to three dlrs reuter 3 数据标注 已标注好\n数据清理 无需清理\n选择模型评估方法 评估方法选择 这里选择精度(accuracy)作为模型评估的指标. 精度, 即正确分类的新闻条数所占比例.\n怎样用? 构建好模型后, 在模型编译阶段, 将model.compile()的metrics参数值设定为[\u0026quot;accuracy\u0026quot;]\n确定简单基准(模型应能超越这个基准) 如果为一条新闻随机指定分类, 能分类正确的概率理论上为$\\frac{1}{46} \\approx 2.17%$, 因此建立的模型的分类正确率应该超过该值.\n对一批数据进行随机分类, 分类正确的比例是多少? 计算一下:\n1 2 3 4 5 6 7 8 9 import copy test_labels_copy = copy.copy(test_labels) np.random.shuffle(test_labels_copy) hits_array = np.array(test_labels) == np.array(test_labels_copy) hits_array.mean() # 0.1731967943009795 把标签随机打乱, 和原来的标签进行比较, 得到分类正确的比率为17.32%, 因此, 建立的模型的正确率应超过该值.\n数据预处理 数据向量化和规范化 数据向量化 对文本列表进行multi-hot编码, 将其转换为由0和1组成的向量. 把每条新闻都转换为一个10000维向量, 如果一个单词在该评论里出现, 就把该单词的索引(单词对应的那个整数)对应位置的元素设为1.\n1 2 3 4 5 6 7 8 9 10 11 import numpy as np def vectorize_sequences(sequences, dimension=10000): results = np.zeros((len(sequences), dimension)) # 创建一个零矩阵 for i, sequence in enumerate(sequences): for j in sequence: results[i, j] = 1. return results x_train = vectorize_sequences(train_data) # 将训练数据向量化 x_test = vectorize_sequences(test_data) # 将测试数据向量化 标签向量化和规范化 使用one-hot编码(也叫 分类编码 categorical encoding)将标签向量化, 即将每个标签表示为维数为标签总类别数(标签一共有46个类别, 则向量的维数就是46)的向量. 该向量的值的特点: 标签索引对应的元素为1, 其余元素均设为0.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 方法1 def to_one_hot(labels, dimension=46): results = np.zeros((len(labels), dimension)) for i, label in enumerate(labels): results[i, label] = 1. return results y_train = to_one_hot(train_labels) # 将训练标签向量化 y_test = to_one_hot(test_labels) # 将测试标签向量化 # 方法2 from tensorflow.keras.utils import to_categorical y_train = to_categorical(train_labels) y_test = to_categorical(test_labels) 处理后 1 2 3 4 5 6 7 8 9 10 11 y_train = np.asarray(train_labels).astype(\u0026#34;float32\u0026#34;) # 将标签向量化 y_test = np.asarray(test_labels).astype(\u0026#34;float32\u0026#34;) x_train.shape, x_test.shape, x_train.ndim # ((8982, 10000), (2246, 10000), 2) x_train[0] # 第1条评论现在变成了什么样子 # array([0., 1., 1., ..., 0., 0., 0.]) y_train.shape, y_test.shape # ((8982,), (2246,)) 处理缺失值 不需要处理缺失值.\n数据划分: 训练集, 验证集, 测试集 从训练集中分出一部分作为验证集.\n1 2 3 4 5 x_val = x_train[:1000] partial_x_train = x_train[1000:] y_val = y_train[:1000] partial_y_train = y_train[1000:] 构建第一个模型 特征选择(过滤没有信息量的特征; 开发新特征) 在本例中特征就是新闻里编码后的单词了.\n选择架构 两个中间层, 每层64个单元. 第三层输出预测值.\n层1: Dense层, \u0026ldquo;表示空间\u0026quot;的维数units为64, 激活函数activation为relu.\n层2: Dense层, \u0026ldquo;表示空间\u0026quot;的维数units为64, 激活函数activation为relu.\n层3: Dense层, \u0026ldquo;表示空间\u0026quot;的维数units为46, 激活函数activation为softmax. 输出是一个数组, 数组元素为46个概率值(总和为1), 表示样本目标值等于各类别的可能性.\n训练配置(损失函数, 批量大小, 学习率) 优化器 optimizer 这里选rmsprop\n损失函数 loss function 这里选分类交叉熵损失函数categorical_crossentropy\n训练轮数 这里训练20轮.\n数据批量大小 批量大小设为512.\n模型构建代码 1 2 3 4 5 6 7 8 9 10 11 12 from tensorflow import keras from tensorflow.keras import layers # 构建模型 model = keras.Sequential([ layers.Dense(64, activation=\u0026#34;relu\u0026#34;), layers.Dense(64, activation=\u0026#34;relu\u0026#34;), layers.Dense(46, activation=\u0026#34;softmax\u0026#34;) ]) # 编译模型 model.compile(optimizer=\u0026#34;rmsprop\u0026#34;, loss=\u0026#34;categorical_crossentropy\u0026#34;, metrics=[\u0026#34;accuracy\u0026#34;]) 拟合模型 1 history = model.fit(partial_x_train, partial_y_train, epochs=20, batch_size=512, validation_data=(x_val, y_val)) 输出:\nEpoch 1/20 16/16 [==============================] - 3s 94ms/step - loss: 2.7054 - accuracy: 0.5150 - val_loss: 1.8234 - val_accuracy: 0.6300 Epoch 2/20 16/16 [==============================] - 1s 58ms/step - loss: 1.5491 - accuracy: 0.6840 - val_loss: 1.4136 - val_accuracy: 0.7010 Epoch 3/20 16/16 [==============================] - 1s 59ms/step - loss: 1.2007 - accuracy: 0.7417 - val_loss: 1.2068 - val_accuracy: 0.7470 Epoch 4/20 16/16 [==============================] - 1s 58ms/step - loss: 0.9851 - accuracy: 0.7928 - val_loss: 1.1092 - val_accuracy: 0.7520 Epoch 5/20 16/16 [==============================] - 1s 58ms/step - loss: 0.8261 - accuracy: 0.8235 - val_loss: 1.0294 - val_accuracy: 0.7940 Epoch 6/20 16/16 [==============================] - 1s 59ms/step - loss: 0.6971 - accuracy: 0.8502 - val_loss: 0.9827 - val_accuracy: 0.7970 Epoch 7/20 16/16 [==============================] - 1s 59ms/step - loss: 0.5887 - accuracy: 0.8711 - val_loss: 0.9479 - val_accuracy: 0.8000 Epoch 8/20 16/16 [==============================] - 1s 79ms/step - loss: 0.4959 - accuracy: 0.8925 - val_loss: 0.9140 - val_accuracy: 0.8050 Epoch 9/20 16/16 [==============================] - 2s 100ms/step - loss: 0.4289 - accuracy: 0.9053 - val_loss: 0.8881 - val_accuracy: 0.8160 Epoch 10/20 16/16 [==============================] - 1s 77ms/step - loss: 0.3663 - accuracy: 0.9182 - val_loss: 0.8877 - val_accuracy: 0.8250 Epoch 11/20 16/16 [==============================] - 1s 55ms/step - loss: 0.3154 - accuracy: 0.9305 - val_loss: 0.8755 - val_accuracy: 0.8110 Epoch 12/20 16/16 [==============================] - 1s 56ms/step - loss: 0.2739 - accuracy: 0.9361 - val_loss: 0.8825 - val_accuracy: 0.8140 Epoch 13/20 16/16 [==============================] - 1s 60ms/step - loss: 0.2457 - accuracy: 0.9430 - val_loss: 0.9108 - val_accuracy: 0.8180 Epoch 14/20 16/16 [==============================] - 1s 58ms/step - loss: 0.2229 - accuracy: 0.9469 - val_loss: 0.8906 - val_accuracy: 0.8170 Epoch 15/20 16/16 [==============================] - 1s 54ms/step - loss: 0.1983 - accuracy: 0.9499 - val_loss: 0.9395 - val_accuracy: 0.8050 Epoch 16/20 16/16 [==============================] - 1s 54ms/step - loss: 0.1820 - accuracy: 0.9534 - val_loss: 0.9114 - val_accuracy: 0.8170 Epoch 17/20 16/16 [==============================] - 1s 55ms/step - loss: 0.1712 - accuracy: 0.9531 - val_loss: 0.9610 - val_accuracy: 0.8070 Epoch 18/20 16/16 [==============================] - 1s 53ms/step - loss: 0.1546 - accuracy: 0.9550 - val_loss: 0.9263 - val_accuracy: 0.8180 Epoch 19/20 16/16 [==============================] - 1s 65ms/step - loss: 0.1473 - accuracy: 0.9557 - val_loss: 0.9865 - val_accuracy: 0.7990 Epoch 20/20 16/16 [==============================] - 1s 52ms/step - loss: 0.1391 - accuracy: 0.9562 - val_loss: 0.9480 - val_accuracy: 0.8160 可以看到, 训练了20轮后, 虽然在训练集上的精度达到了0.9480, 但在验证集上, 精度却只有0.8160.\n1 2 3 4 history_dict = history.history # 一个字典, 键是\u0026#34;指标\u0026#34;; 值是列表, 指标在每轮训练时的值. history_dict.keys() # dict_keys([\u0026#39;loss\u0026#39;, \u0026#39;accuracy\u0026#39;, \u0026#39;val_loss\u0026#39;, \u0026#39;val_accuracy\u0026#39;]) 可视化拟合结果 绘制训练损失和验证损失 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import matplotlib.pyplot as plt history_dict = history.history loss = history_dict[\u0026#34;loss\u0026#34;] val_loss = history_dict[\u0026#34;val_loss\u0026#34;] epochs = range(1, len(loss) + 1) plt.plot(epochs, loss, \u0026#34;bo\u0026#34;, label=\u0026#34;Training loss\u0026#34;) # \u0026#34;bo\u0026#34;表示蓝色圆点 plt.plot(epochs, val_loss, \u0026#34;b\u0026#34;, label=\u0026#34;Validation loss\u0026#34;) # \u0026#34;b\u0026#34;表示蓝色实线 plt.title(\u0026#34;Training and validation loss\u0026#34;) plt.xlabel(\u0026#34;Epochs\u0026#34;) plt.ylabel(\u0026#34;Loss\u0026#34;) plt.legend() # 用于为图表添加图例 plt.show() 绘制训练精度和验证精度 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # import matplotlib.pyplot as plt # history_dict = history.history plt.clf() # 清空图像 acc = history_dict[\u0026#34;accuracy\u0026#34;] val_acc = history_dict[\u0026#34;val_accuracy\u0026#34;] # epochs = range(1, len(loss_values) + 1) plt.plot(epochs, acc, \u0026#34;bo\u0026#34;, label=\u0026#34;Training acc\u0026#34;) # \u0026#34;bo\u0026#34;表示蓝色圆点 plt.plot(epochs, val_acc, \u0026#34;b\u0026#34;, label=\u0026#34;Validation acc\u0026#34;) # \u0026#34;b\u0026#34;表示蓝色实线 plt.title(\u0026#34;Training and validation accuracy\u0026#34;) plt.xlabel(\u0026#34;Epochs\u0026#34;) plt.ylabel(\u0026#34;Accuracy\u0026#34;) plt.legend() # 用于为图表添加图例 plt.show() 从图中看出, 模型在训练到第9轮后, 开始出现过拟合的现象.\n改进模型 让模型在训练9轮后停止 因为模型在训练到第9轮后就开始过拟合, 因此让模型训练9轮, 之后再次评估模型.\n1 2 # 注意, 这次拟合时没有再从训练集中分出一部分做验证, 而是全部用来训练 history = model.fit(x_train, y_train, epochs=9, batch_size=512) 评价模型 前面选择精度(accuracy)作为模型评估的指标, 因此在对模型效果进行评价时, 使用模型在整个测试集上的平均精度.\n1 2 3 test_loss, test_acc = model.evaluate(x_test, y_test) print(f\u0026#34;测试精度: {test_acc}\u0026#34;) # 71/71 [==============================] - 0s 4ms/step - loss: 1.0796 - accuracy: 0.7916 测试精度: 0.7916295528411865 可以看到, 模型在测试集上的精度为0.7916, 相比于基准精度而言, 表明建立的模型确实是有效果的.\n利用模型进行预测 拿到一条评论, 对它像在上面那样把它编码为一个由0和1组成的向量, 然后用model.predict()进行预测.\n1 2 3 4 5 6 7 predictions = model.predict(x_test) predictions.shape # (2246, 46) predic_result = [item.argmax() for item in predictions] min(predic_result), max(predic_result) 部署模型 不部署.\n","date":"May 05","permalink":"https://o5o.me/post/keras_case_multi_classify_report_class/","tags":["Keras","多分类"],"title":"keras多分类问题: 新闻主题分类"},{"categories":["Python"],"contents":"所用环境为Colab.\n定义任务目标 拿到一些对电影的评论, 判断出来哪些评论是正面的, 哪些是负面的.\n数据收集 数据收集 使用 IMDB 数据集.\n1 2 3 # 加载数据集(需联网) from tensorflow.keras.datasets import imdb (train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000) # num_words=10000, 仅保留数据中前10000个最常出现的单词 数据可视化 建立对数据形状的感受.\n1 2 3 4 5 6 # 检查数据 train_data.shape, train_labels.shape, test_data.shape, test_labels.shape, train_data.dtype # ((25000,), (25000,), (25000,), (25000,), dtype(\u0026#39;O\u0026#39;)) type(train_data), type(train_data[0]), train_data.ndim # (numpy.ndarray, list, 1) 看一下数据集里的数据到底长什么样子, 数据对应的标签是什么.\n1 2 3 4 5 6 7 8 9 10 # 查看第1条评论的前10个单词, 以及它的标签(正面还是负面) train_data[0][:10] # 电影评论里的单词被转换为一个个数字. # [1, 14, 22, 16, 43, 530, 973, 1622, 1385, 65] train_labels[0] # 1 # 最长的一条电影评论的长度是多少 max([max(item) for item in train_data]) # 先得到每条评论的长度, 再得到长度的最大值 数据集里的评论被编码为了数字, 解码成正常文本看看.\n1 2 3 4 5 6 7 8 word_index = imdb.get_word_index() # word_index 是一个字典, 键是单词, 值是对应的一个整数. word_index[\u0026#34;hello\u0026#34;] # hello这个单词对应的整数是4822 reverse_word_index = dict([(value, key) for (key, value) in word_index.items()]) # 一个新字典, 键是一个整数, 值是该整数对应的单词 reverse_word_index[4822] # \u0026#39;hello\u0026#39; decoded_review = \u0026#34; \u0026#34;.join([reverse_word_index.get(i - 3, \u0026#34;?\u0026#34;) for i in train_data[0]]) # 把训练集中的第一条评论解码成正常文本. 索引减3是因为, 训练集里的单词对应的整数, 相比于字典, 都向右偏移了3. decoded_review 解码出来的文本:\n? this film was just brilliant casting location scenery story direction everyone\u0026#39;s really suited the part they played and you could just imagine being there robert ? is an amazing actor and now the same being director ? father came from the same scottish island as myself so i loved the fact there was a real connection with this film the witty remarks throughout the film were great it was just brilliant so much that i bought the film as soon as it was released for ? and would recommend it to everyone to watch and the fly fishing was amazing really cried at the end it was so sad and you know what they say if you cry at a film it must have been good and this definitely was also ? to the two little boy\u0026#39;s that played the ? of norman and paul they were just brilliant children are often left out of the ? list i think because the stars that play them all grown up are such a big profile for the whole film but these children are amazing and should be praised for what they have done don\u0026#39;t you think the whole story was so lovely because it was true and was someone\u0026#39;s life after all that was shared with us all 很显然这是一条正面评论. 这条评论的标签是\u0026quot;1\u0026quot;, 这时我们也清楚了, 标签为\u0026quot;1\u0026quot;代表该评论是正面的, 为\u0026quot;0\u0026quot;代表该评论是负面的.\n数据标注 已标注好\n数据清理 无需清理\n选择模型评估方法 确定简单基准(模型应能超越这个基准) 如果为评论随机指定分类, 能分类正确的概率为50%, 因此建立的模型的分类正确率应该超过50%.\n评估方法选择 这里选择精度(accuracy)作为模型评估的指标. 精度, 即正确分类的图像所占比例.\n怎样用? 构建好模型后, 在模型编译阶段, 将model.compile()的metrics参数值设定为[\u0026quot;accuracy\u0026quot;]\n数据预处理 数据向量化和规范化 对文本列表进行multi-hot编码, 将其转换为由0和1组成的向量. 把每条评论都转换为一个10000维向量, 如果一个单词在该评论里出现, 就把该单词的索引(单词对应的那个整数)对应位置的元素设为1.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import numpy as np def vectorize_sequences(sequences, dimension=10000): results = np.zeros((len(sequences), dimension)) # 创建一个零矩阵 for i, sequence in enumerate(sequences): for j in sequence: results[i, j] = 1. return results x_train = vectorize_sequences(train_data) # 将训练数据向量化 x_test = vectorize_sequences(test_data) # 将测试数据向量化 y_train = np.asarray(train_labels).astype(\u0026#34;float32\u0026#34;) # 将标签向量化 y_test = np.asarray(test_labels).astype(\u0026#34;float32\u0026#34;) x_train.shape, x_test.shape, x_train.ndim # ((25000, 10000), (25000, 10000), 2) x_train[0] # 第1条评论现在变成了什么样子 # array([0., 1., 1., ..., 0., 0., 0.]) y_train.shape, y_test.shape # ((25000,), (25000,)) 处理缺失值 不需要处理缺失值.\n数据划分: 训练集, 验证集, 测试集 从训练集中分出一部分作为验证集.\n1 2 3 4 5 x_val = x_train[:10000] partial_x_train = x_train[10000:] y_val = y_train[:10000] partial_y_train = y_train[10000:] 构建第一个模型 特征选择(过滤没有信息量的特征; 开发新特征) 在本例中特征就是评论里编码后的单词了.\n选择架构 两个中间层, 每层16个单元. 第三层输出标量预测值, 代表一条评论的情感类别(正面, 负面).\n层1: Dense层, \u0026ldquo;表示空间\u0026quot;的维数units为16, 激活函数activation为relu.\n层2: Dense层, \u0026ldquo;表示空间\u0026quot;的维数units为16, 激活函数activation为relu.\n层3: Dense层, \u0026ldquo;表示空间\u0026quot;的维数units为1, 激活函数activation为sigmoid. 输出是一个介于0~1之间的概率值, 表示样本目标值等于\u0026quot;1\u0026quot;的可能性, 即评论为正面的可能性.\n训练配置(损失函数, 批量大小, 学习率) 优化器 optimizer 这里选rmsprop\n损失函数 loss function 这里选二元交叉熵损失函数binary_crossentropy\n训练轮数 这里训练20轮.\n数据批量大小 批量大小设为512.\n模型构建代码 1 2 3 4 5 6 7 8 9 10 11 12 from tensorflow import keras from tensorflow.keras import layers # 构建模型 model = keras.Sequential([ layers.Dense(16, activation=\u0026#34;relu\u0026#34;), layers.Dense(16, activation=\u0026#34;relu\u0026#34;), layers.Dense(1, activation=\u0026#34;sigmoid\u0026#34;) ]) # 编译模型 model.compile(optimizer=\u0026#34;rmsprop\u0026#34;, loss=\u0026#34;binary_crossentropy\u0026#34;, metrics=[\u0026#34;accuracy\u0026#34;]) 拟合模型 1 history = model.fit(partial_x_train, partial_y_train, epochs=20, batch_size=512, validation_data=(x_val, y_val)) 输出:\nEpoch 1/20 30/30 [==============================] - 3s 77ms/step - loss: 0.5066 - accuracy: 0.7891 - val_loss: 0.3772 - val_accuracy: 0.8726 Epoch 2/20 30/30 [==============================] - 1s 40ms/step - loss: 0.3095 - accuracy: 0.8973 - val_loss: 0.3270 - val_accuracy: 0.8708 Epoch 3/20 30/30 [==============================] - 1s 41ms/step - loss: 0.2321 - accuracy: 0.9209 - val_loss: 0.2788 - val_accuracy: 0.8913 Epoch 4/20 30/30 [==============================] - 1s 44ms/step - loss: 0.1899 - accuracy: 0.9361 - val_loss: 0.2989 - val_accuracy: 0.8797 Epoch 5/20 30/30 [==============================] - 1s 49ms/step - loss: 0.1579 - accuracy: 0.9477 - val_loss: 0.2835 - val_accuracy: 0.8862 Epoch 6/20 30/30 [==============================] - 2s 58ms/step - loss: 0.1358 - accuracy: 0.9569 - val_loss: 0.3212 - val_accuracy: 0.8725 Epoch 7/20 30/30 [==============================] - 2s 65ms/step - loss: 0.1184 - accuracy: 0.9631 - val_loss: 0.3048 - val_accuracy: 0.8793 Epoch 8/20 30/30 [==============================] - 2s 61ms/step - loss: 0.1000 - accuracy: 0.9695 - val_loss: 0.3149 - val_accuracy: 0.8811 Epoch 9/20 30/30 [==============================] - 1s 41ms/step - loss: 0.0861 - accuracy: 0.9749 - val_loss: 0.3366 - val_accuracy: 0.8834 Epoch 10/20 30/30 [==============================] - 2s 56ms/step - loss: 0.0746 - accuracy: 0.9780 - val_loss: 0.3619 - val_accuracy: 0.8708 Epoch 11/20 30/30 [==============================] - 2s 55ms/step - loss: 0.0660 - accuracy: 0.9827 - val_loss: 0.3676 - val_accuracy: 0.8783 Epoch 12/20 30/30 [==============================] - 2s 52ms/step - loss: 0.0575 - accuracy: 0.9855 - val_loss: 0.3926 - val_accuracy: 0.8737 Epoch 13/20 30/30 [==============================] - 1s 44ms/step - loss: 0.0465 - accuracy: 0.9896 - val_loss: 0.4083 - val_accuracy: 0.8760 Epoch 14/20 30/30 [==============================] - 1s 40ms/step - loss: 0.0418 - accuracy: 0.9895 - val_loss: 0.4366 - val_accuracy: 0.8732 Epoch 15/20 30/30 [==============================] - 1s 40ms/step - loss: 0.0373 - accuracy: 0.9906 - val_loss: 0.4559 - val_accuracy: 0.8735 Epoch 16/20 30/30 [==============================] - 2s 73ms/step - loss: 0.0281 - accuracy: 0.9953 - val_loss: 0.4732 - val_accuracy: 0.8741 Epoch 17/20 30/30 [==============================] - 2s 53ms/step - loss: 0.0269 - accuracy: 0.9951 - val_loss: 0.5042 - val_accuracy: 0.8730 Epoch 18/20 30/30 [==============================] - 2s 54ms/step - loss: 0.0247 - accuracy: 0.9955 - val_loss: 0.5203 - val_accuracy: 0.8721 Epoch 19/20 30/30 [==============================] - 2s 54ms/step - loss: 0.0180 - accuracy: 0.9980 - val_loss: 0.5411 - val_accuracy: 0.8714 Epoch 20/20 30/30 [==============================] - 1s 43ms/step - loss: 0.0154 - accuracy: 0.9983 - val_loss: 0.5657 - val_accuracy: 0.8704 可以看到, 训练了20轮后, 虽然在训练集上的精度达到了0.9983, 但在验证集上, 精度却只有0.8704.\n1 2 3 4 history_dict = history.history # 一个字典, 键是\u0026#34;指标\u0026#34;; 值是列表, 指标在每轮训练时的值. history_dict.keys() # dict_keys([\u0026#39;loss\u0026#39;, \u0026#39;accuracy\u0026#39;, \u0026#39;val_loss\u0026#39;, \u0026#39;val_accuracy\u0026#39;]) 可视化拟合结果 绘制训练损失和验证损失 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import matplotlib.pyplot as plt history_dict = history.history loss_values = history_dict[\u0026#34;loss\u0026#34;] val_loss_values = history_dict[\u0026#34;val_loss\u0026#34;] epochs = range(1, len(loss_values) + 1) plt.plot(epochs, loss_values, \u0026#34;bo\u0026#34;, label=\u0026#34;Training loss\u0026#34;) # \u0026#34;bo\u0026#34;表示蓝色圆点 plt.plot(epochs, val_loss_values, \u0026#34;b\u0026#34;, label=\u0026#34;Validation loss\u0026#34;) # \u0026#34;b\u0026#34;表示蓝色实线 plt.title(\u0026#34;Training and validation loss\u0026#34;) plt.xlabel(\u0026#34;Epochs\u0026#34;) plt.ylabel(\u0026#34;Loss\u0026#34;) plt.legend() # 用于为图表添加图例 plt.show() 绘制训练精度和验证精度 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # import matplotlib.pyplot as plt # history_dict = history.history plt.clf() # 清空图像 acc = history_dict[\u0026#34;accuracy\u0026#34;] val_acc = history_dict[\u0026#34;val_accuracy\u0026#34;] # epochs = range(1, len(loss_values) + 1) plt.plot(epochs, acc, \u0026#34;bo\u0026#34;, label=\u0026#34;Training acc\u0026#34;) # \u0026#34;bo\u0026#34;表示蓝色圆点 plt.plot(epochs, val_acc, \u0026#34;b\u0026#34;, label=\u0026#34;Validation acc\u0026#34;) # \u0026#34;b\u0026#34;表示蓝色实线 plt.title(\u0026#34;Training and validation accuracy\u0026#34;) plt.xlabel(\u0026#34;Epochs\u0026#34;) plt.ylabel(\u0026#34;Accuracy\u0026#34;) plt.legend() # 用于为图表添加图例 plt.show() 从图中看出, 模型在训练到第4轮后, 开始出现过拟合的现象.\n改进模型 让模型在训练4轮后停止 因为模型在训练到第4轮后就开始过拟合, 因此让模型训练4轮, 之后再次评估模型.\n1 2 # 注意, 这次拟合时没有再从训练集中分出一部分做验证, 而是全部用来训练 history = model.fit(x_train, y_train, epochs=4, batch_size=512) 评价模型 前面选择精度(accuracy)作为模型评估的指标, 因此在对模型效果进行评价时, 使用模型在整个测试集上的平均精度.\n1 2 3 4 test_loss, test_acc = model.evaluate(x_test, y_test) print(f\u0026#34;测试精度: {test_acc}\u0026#34;) # 782/782 [==============================] - 2s 3ms/step - loss: 0.4298 - accuracy: 0.8632 测试精度: 0.8631600141525269 可以看到, 模型在测试集上的精度为0.8632, 表明模型还应进一步改进..\n利用模型进行预测 拿到一条评论, 对它像在上面那样把它编码为一个由0和1组成的向量, 然后用model.predict()进行预测.\n1 2 3 4 5 6 7 8 predictions = model.predict(x_test) predic_result = [0 if item \u0026lt; 0.5 else 1 for item in predictions[:10]] predic_result[:10] # 预测值 [0, 1, 0, 1, 1, 1, 1, 0, 1, 1] list(y_test[0:10].astype(int)) # 实际值 [0, 1, 1, 0, 1, 1, 1, 0, 0, 1] 部署模型 不部署.\n","date":"May 05","permalink":"https://o5o.me/post/keras_case_two_classify_film_review/","tags":["Keras","二分类"],"title":"keras二分类问题: 影评是正面还是负面的"},{"categories":["Python"],"contents":"所用环境为Colab.\n定义任务目标 拿到一些手写数字的图片, 每张图片上含有一个手写的数字, 把这些数字识别出来, 划分到10个类别中(从0到9).\n数据收集 数据收集 使用 MNIST 数据集.\n1 2 3 4 5 6 7 8 9 # 加载数据集(需联网) from tensorflow.keras.datasets import mnist (train_images, train_labels), (test_images, test_labels) = mnist.load_data() # 检查数据 train_images.shape, train_labels.shape, test_images.shape, test_labels.shape, train_images.dtype # ((60000, 28, 28), (60000,), (10000, 28, 28), (10000,), dtype(\u0026#39;uint8\u0026#39;)) train_images.ndim # 训练集张量的轴的个数 数据可视化 看一下数据集里的图片到底长什么样子, 图片对应的标签是什么.\n1 2 3 4 5 6 7 8 9 import matplotlib.pyplot as plt # 显示训练集中的第5张图片 digit = train_images[4] plt.imshow(digit, cmap=plt.cm.binary) plt.show() # 查看训练集中的第5张图片的标签 train_labels[4] 数据标注 已标注好\n数据清理 无需清理\n选择模型评估方法 确定简单基准(模型应能超越这个基准) 数字一共有10类(0~9), 如果为手写数字图片随机指定分类, 能分类正确的概率为10%, 因此建立的模型的分类正确率应该超过10%.\n评估方法选择 这里选择精度(accuracy)作为模型评估的指标. 精度, 即正确分类的图像所占比例.\n怎样用? 构建好模型后, 在模型编译阶段, 将model.compile()的metrics参数值设定为[\u0026quot;accuracy\u0026quot;]\n构建第一个模型 特征选择(过滤没有信息量的特征; 开发新特征) 在本例中特征就是图片的张量值了.\n选择架构 两个密集连接层(全连接层).\n层1: Dense层, 输出空间的维数units为512, 激活函数activation为relu.\n层2: Dense层, 输出空间的维数units为10, 激活函数activation为softmax. 这是一个10路的softmax分类层, 它的输出是一个数组, 数组元素为10个概率值(总和为1), 表示图像分别属于10个类别(数字)的概率值.\n训练配置(损失函数, 批量大小, 学习率) 优化器 optimizer 这里选rmsprop\n损失函数 loss function 这里选sparse_categorical_crossentropy\n训练轮数 这里训练5轮.\n数据批量大小 批量大小设为128.\n模型构建代码 1 2 3 4 5 6 7 8 9 10 11 from tensorflow import keras from tensorflow.keras import layers # 构建模型 model = keras.Sequential([ layers.Dense(512, activation=\u0026#34;relu\u0026#34;), layers.Dense(10, activation=\u0026#34;softmax\u0026#34;) ]) # 编译模型 model.compile(optimizer=\u0026#34;rmsprop\u0026#34;, loss=\u0026#34;sparse_categorical_crossentropy\u0026#34;, metrics=[\u0026#34;accuracy\u0026#34;]) 数据预处理 数据划分: 训练集, 验证集, 测试集 mnist数据集已划分为训练集和测试集. 这里不再划分训练集和验证集.\n数据向量化 从前面知训练集的shape为(60000, 28, 28), shape为(60000, 28 * 28), 即一条记录(一个向量)表示一张图片.\n1 2 3 4 train_images = train_images.reshape((60000, 28 * 28)) test_images = test_images.reshape((10000, 28 * 28)) train_images.shape, test_images.shape # ((60000, 784), (10000, 784)) 数据规范化 从前面知训练集的数据类型为uint8, 将其变换为一个float32数组.\n数据点(相当于图片上的一个像素点的值)的取值范围是0~255, 把值缩放到0~1.\n1 2 3 4 5 train_images = train_images.astype(\u0026#34;float32\u0026#34;) / 255 test_images = test_images.astype(\u0026#34;float32\u0026#34;) / 255 train_images.dtype, test_images.dtype, train_images.min(), train_images.max() # (dtype(\u0026#39;float32\u0026#39;), dtype(\u0026#39;float32\u0026#39;), 0.0, 1.0) 处理缺失值 不需要处理缺失值.\n拟合模型 1 history = model.fit(train_images, train_labels, epochs=5, batch_size=128) 输出:\nEpoch 1/5 469/469 [==============================] - 7s 13ms/step - loss: 0.2651 - accuracy: 0.9239 Epoch 2/5 469/469 [==============================] - 5s 11ms/step - loss: 0.1070 - accuracy: 0.9686 Epoch 3/5 469/469 [==============================] - 5s 10ms/step - loss: 0.0701 - accuracy: 0.9792 Epoch 4/5 469/469 [==============================] - 6s 12ms/step - loss: 0.0510 - accuracy: 0.9844 Epoch 5/5 469/469 [==============================] - 5s 10ms/step - loss: 0.0377 - accuracy: 0.9890 可以看到, 训练了5轮后, 在训练集上的精度达到了0.9890.\n1 2 3 history.history # 一个字典, 键是\u0026#34;指标\u0026#34;; 值是列表, 指标在每轮训练时的值. # {\u0026#39;loss\u0026#39;: [0.26506927609443665, 0.10701579600572586, 0.07010699808597565, 0.051034048199653625, 0.03765270486474037], \u0026#39;accuracy\u0026#39;: [0.9239166378974915, 0.968583345413208, 0.979200005531311, 0.9843833446502686, 0.9889833331108093]} 利用模型进行预测 1 2 3 4 5 6 7 8 9 10 11 test_digits = test_images[0:10] # 只预测测试集中的前10张图片 predictions = model.predict(test_digits) predictions[0] # 对第1张图片的预测结果, 数组, 各元素为概率值 predictions[0].argmax() # 第1张图片的预测结果, 最大的元素的索引为7, 表明这张图片的预测结果是7 predictions[0][7] # 索引7对应的元素的值(该图片对应数字是7的概率值) test_labels[0] # 查看测试集第一张图片的标签, 看预测结果是否和实际结果一样 评价模型 前面选择精度(accuracy)作为模型评估的指标, 因此在对模型效果进行评价时, 使用模型在整个测试集上的平均精度.\n1 2 3 4 test_loss, test_acc = model.evaluate(test_images, test_labels) print(f\u0026#34;测试精度: {test_acc}\u0026#34;) # 313/313 [==============================] - 1s 3ms/step - loss: 0.0642 - accuracy: 0.9804 # 测试精度: 0.980400025844574 可以看到, 模型在测试集上的精度为0.9804, 比在训练集上略低.\n改进模型 模型在测试集上的精度能达到98%, 已经很不错了, 就暂时不改进了.\n部署模型 不部署.\n","date":"May 04","permalink":"https://o5o.me/post/keras_case_mnist_handwriting_digits_recognition/","tags":["Keras","多分类"],"title":"keras实现手写数字识别"},{"categories":["Python"],"contents":"在开发环境中为了数据安全, 数据库信息不要直接写在代码里, 比较好的做法是将相关信息放在环境变量里, 连接数据库时再从环境变量里读取.\n将信息写入环境变量 创建文件.SECRET_KEY, 写入类似下面的内容:\nSECRET_HOST=\u0026#39;127.0.0.1\u0026#39; SECRET_DBNAME=\u0026#39;dbname\u0026#39; SECRET_DBUSER=\u0026#39;username\u0026#39; SECRET_DBPASSWD=\u0026#39;123456\u0026#39; 用python包dotenv把文件内容写入环境变量, 安装:\n1 pip install python-dotenv 使用:\n1 2 3 4 5 6 7 8 9 import os from dotenv import load_dotenv def load_environ_key(path): key_file_path = path if os.path.exists(key_file_path): load_dotenv(key_file_path, override=True) load_environ_key(\u0026#39;/www/xxx/.SECRET_KEY\u0026#39;) 从环境变量读取信息 从环境变量里读取信息举例:\n1 2 3 4 5 import os import pymysql db = pymysql.connect(host=os.environ[\u0026#34;SECRET_HOST\u0026#34;], user=os.environ[\u0026#34;SECRET_DBUSER\u0026#34;], passwd=os.environ[\u0026#34;SECRET_DBPASSWD\u0026#34;], database=os.environ[\u0026#34;SECRET_DBNAME\u0026#34;], charset=\u0026#39;utf8\u0026#39;) cursor = db.cursor() ","date":"Apr 28","permalink":"https://o5o.me/post/python_environment_variable_database_info/","tags":null,"title":"Python从环境变量中获取数据库信息"},{"categories":["Python"],"contents":"同一套代码, 既可以在本地开发环境跑, 也可以在服务器生产环境跑, 是多么美的一件事.\n我根据\u0026quot;主机名\u0026quot;来判断是生产环境还是开发环境.\n1 2 3 4 5 6 7 8 9 import socket ay_dp_value = socket.gethostname().startswith(\u0026#39;DESKTOP\u0026#39;) if not ay_dp_value: # 生产环境相关代码 path = \u0026#34;/www/xxx/produc_environ\u0026#34; else: # 开发环境相关代码 path = r\u0026#34;C:\\Users\\admin\\Desktop\\test_environ\u0026#34; 程序运行时如果主机名以DESKTOP开头, 就表明是本地开发环境(我开发用的电脑名字是\u0026quot;DESKTOP_XXX\u0026quot;), 反之则认为是生产环境.\n","date":"Apr 28","permalink":"https://o5o.me/post/python_separation_development_production_environments/","tags":null,"title":"Python开发环境和生产环境分离"},{"categories":["Python"],"contents":"编写一个叫 gcd 的函数,接受两个参数 a 和 b,并返回二者的最大公约数。\n致谢:这道习题基于 Abelson 和 Sussman 编写的 《Structure and Interpretation of Computer Programs》 其中的例子。\n我的解答 算法是辗转相除法.\n1 2 3 4 5 6 7 8 def gcd(a, b): a, b = (b, a) if a \u0026lt; b else (a, b) if b != 0: a, b = gcd(b, a % b) return a, b result, _ = gcd(12,6) print(result) 上面这个解答, 对递归理解得还不够.\n1 2 3 4 5 6 def gcd(a, b): a, b = (b, a) if a \u0026lt; b else (a, b) return a if b == 0 else gcd(b, a % b) result = gcd(12,6) print(result) 这样就很好了.\n","date":"Apr 16","permalink":"https://o5o.me/post/think_python_exercise_6.6/","tags":null,"title":"Think Python Exercise 6.6"},{"categories":null,"contents":"从Windows粘贴文本到vim后, 发现每行文本的末尾都有一个^M, 这是由于win和linux换行符不同导致的, 解决方案:\n保存文件:w 输入:e ++ff=dos 输入:set ff=unix ","date":"Apr 11","permalink":"https://o5o.me/post/vim_paste_line_break_character/","tags":["vim"],"title":"vim从Windows粘贴的文本每行末尾都有一个^M"},{"categories":["Linux"],"contents":"在终端输入cat /etc/resolv.conf, 看到我这里nameserver为172.19.0.1.\n而系统代理的端口是7890, 因此在终端输入:\n1 export ALL_PROXY=\u0026#34;http://172.19.0.1:7890\u0026#34; 这样就把代理配置好了, 来测试一下:\n1 curl http://google.com 有返回值表明代理已生效.\n","date":"Apr 11","permalink":"https://o5o.me/post/wsl_proxy/","tags":["WSL"],"title":"WSL使用系统代理"},{"categories":["SRS"],"contents":"我之前一直用的是 SuperMemo 的懒人包,有一个让我很不爽的点:点击卡片编辑时,文字总要偏移一些距离。而用官方软件,点击卡片进行编辑时,只是会出现一个蓝色的边框,文字不会偏移。\n今天下午发现,是懒人包的软件设置里一个选项没勾选上。至于说偏移好还是不偏移好,这就见仁见智了,我个人不喜欢这种偏移效果。\n菜单栏 - Toolkit - Options :- 勾选\u0026quot;Component status borders\u0026quot;\n","date":"Apr 09","permalink":"https://o5o.me/post/supermemo_edit_card_interface_option/","tags":["SuperMemo"],"title":"SuperMemo编辑卡片界面细节设置"},{"categories":["SRS"],"contents":" 第一次打开 Question of the Day 第一次打开SuperMemo,在主界面前面有一个弹窗“Question of the Day”,里面是一些“使用中的常见问题”,对应的是文件SuperMemo\\bin\\tips.txt。点击“Close”即可关闭。\n激活 如果SM未激活,软件界面上方会有一个大大的红色横幅。点击红色横幅,在弹窗中点“Password”,输入激活码O4W54S31SM即可激活。\nLevel 默认的Level是“Beginner”,修改为“Professional”会有更多的选项可使用。\n菜单栏 :- File - Level :- Professional\nHints 提示 开启Hints(提示)后,将光标放在按钮上,会显示对应的提示,方便了解按钮的作用。\n菜单栏 :- Window :- Hints\n评分按钮 点击 Learn 开始学习和复习. 如果展示给你的是一道题, 下方会有5个评分按钮. 默认的评分按钮是图标, 但在我这里显示有\u0026quot;锯齿感\u0026quot;, 因此我习惯将它们更改为文字.\n菜单栏 - Toolkit - Options :- SuperMemo - 去掉勾选Greade icons - OK\nLayout 布局 我在复习卡片的时候比较喜欢在左侧打开“Contents”(目录树)。因此我一般会把布局调整成这个样子:\n调整软件界面布局为 Warrior layout:菜单栏 :- Window - Layout :- Warrior layout\n关闭左侧的 Statistics。\n打开目录树:软件界面最上方菜单栏下面一行按钮里点“Contents”,或快捷键Alt + C。\n用鼠标将软件各窗口手动调整成上图的位置。\n保存布局:菜单栏 :- Window - Layout :- Save as default,自己给布局起一个名字。\n自定义软件界面显示 修改软件字体 菜单栏 - Toolkit :- Options - Fonts\nSuperMemo 有很多的字体设置项, Interface font 软件界面字体, Question font问答卡片中Question部分的字体, Answer font 问答卡片中Answer部分的字体, Contents font 目录树的字体, Highlight font 高亮文本的字体.\n这里的\u0026quot;问答卡片\u0026quot;, 指的是点击\u0026quot;Add new\u0026quot;按钮添加的有\u0026quot;上下两个框\u0026quot;的卡片.\n软件另外有两处修改字体的地方: 菜单栏 - Toolkit :- Options - Language, Translation font, Phonetic transcription.\n修改字体的同时, 字符集确保是\u0026quot;中文GB2312\u0026quot;, 我之前曾遇到乱码问题(导出卡片到另一个collection, 结果发现汉字乱码了), 怀疑和字符集有关.\n修改卡片内容字体 把以上这些设置项都设定好后, 可以发现最重要的部分: 卡片内容的字体, 还是老样子. 卡片的字体等样式由css控制, 文件位于SuperMemo\\bin\\supermemo.css. 如果是设置字体的话, 比较建议在软件内通过选项修改, 不建议直接修改CSS文件. 保存后SuperMemo会添加相关的代码到该css文件.\n相关选项的位置: 菜单栏 - Toolkit :- Options - Fonts - Stylesheet\n字体的控制选项非常多, 默认是\u0026quot;Text\u0026quot;, 可以通过下拉菜单修改其他部分的字体. 如果不知道每一个选项控制的是哪一部分, 可以先修改字体(不要改字体大小), 获得好的体验, 等后面SM用熟练了, 自然就知道选项指的是什么了.\n等后面对SM非常熟悉了, 想获得更好的体验, 那时候再直接修改CSS. 如果打开css文件后发现中文乱码, 将文件编码修改为\u0026quot;gb2312\u0026quot;后重新打开文件即可.\n其他字体 我发现在 supermemo.ini (SuperMemo\\bin\\supermemo.ini) 中也有字体的设置项, 经测试, 控制的是 \u0026ldquo;Question of the Day\u0026rdquo; 弹窗中按钮的字体(也许还有其他部分).\n如果这个文件打开后中文乱码, 同样的, 将文件编码修改为\u0026quot;gb2312\u0026quot;后重新打开文件即可.\n","date":"Apr 09","permalink":"https://o5o.me/post/supermemo_init/","tags":["SuperMemo"],"title":"新安装的Supermemo界面调教优化"},{"categories":["Python"],"contents":"这个文件是显示在浏览器网站标题旁边的那个小图标。\n网站一般会把它放在网站根目录,也有的是在网页里指定它的路径。\n代码1:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import requests from fake_useragent import UserAgent from lxml import etree import os url = \u0026#34;https://www.w1w.cc\u0026#34; #末尾不要带斜杠 ua = UserAgent() headers = { \u0026#39;UserAgent\u0026#39;: ua.random, } res = requests.get(url, headers=headers) html = res.content.decode(\u0026#39;utf-8\u0026#39;) # print(html) # 解析html,尝试从标签中获取favicon xpath_dbs = \u0026#39;/html/head/link[contains(@rel,\u0026#34;icon\u0026#34;)]/@href\u0026#39; parse_html = etree.HTML(html) parse_favicon_list = parse_html.xpath(xpath_dbs) # 取url主域名作为文件夹名 dir = url.split(\u0026#39;/\u0026#39;)[2] if not os.path.exists(dir): os.makedirs(dir) for parse_url in parse_favicon_list: favicon_url = parse_url if parse_url.startswith(\u0026#39;http\u0026#39;) else url+parse_url html_bytes = requests.get(url=favicon_url,headers=headers).content filename = favicon_url.split(\u0026#39;/\u0026#39;)[-1] dir_favicon = dir + \u0026#39;/\u0026#39; + filename with open(dir_favicon, \u0026#39;wb\u0026#39;) as f: f.write(html_bytes) print(\u0026#39;从html标签中获取favicon %s 成功\u0026#39; % filename) 代码2\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import requests from fake_useragent import UserAgent from lxml import etree import os url = \u0026#34;http://oa.com\u0026#34; #末尾不要带斜杠 favicon_url = url + \u0026#34;/\u0026#34; +\u0026#34;favicon.ico\u0026#34; ua = UserAgent() headers = { \u0026#39;UserAgent\u0026#39;: ua.random, } html_bytes = requests.get(favicon_url, headers=headers).content if html_bytes: #如果获取到了数据 # 取url主域名作为文件夹名 dir = url.split(\u0026#39;/\u0026#39;)[2] if not os.path.exists(dir): os.makedirs(dir) dir_favicon = dir + \u0026#39;/\u0026#39; + \u0026#34;favicon.ico\u0026#34; with open(dir_favicon, \u0026#39;wb\u0026#39;) as f: f.write(html_bytes) print(\u0026#39;从网站根目录直接获取favicon %s 成功\u0026#39; % filename) else: print(\u0026#34;从网站根目录直接获取favicon失败,将尝试另一种方式\u0026#34;) 最后,写成类的形式:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 import requests from fake_useragent import UserAgent from lxml import etree import os class SpiderFavicon: def __init__(self, site): self.url = site ua = UserAgent() self.headers = { \u0026#39;UserAgent\u0026#39;: ua.random, } def get_html_bytes(self, url=None): res = requests.get(url, headers=self.headers) if url else requests.get(self.url, headers=self.headers) html_bytes = res.content return html_bytes def parse_html(self, xpath_dbs): html = self.get_html_bytes().decode(\u0026#39;utf-8\u0026#39;) # 解析html,尝试从标签中获取favicon # xpath_dbs = \u0026#39;/html/head/link[contains(@rel,\u0026#34;icon\u0026#34;)]/@href\u0026#39; try: parse_html = etree.HTML(html) parse_favicon_list = parse_html.xpath(xpath_dbs) return parse_favicon_list except: return [] def root_favicon(self): favicon_url = self.url + \u0026#34;/\u0026#34; + \u0026#34;favicon.ico\u0026#34; return True if self.save([favicon_url]) else False def html_favicon(self): # 解析html,尝试从标签中获取favicon xpath_dbs = \u0026#39;/html/head/link[contains(@rel,\u0026#34;icon\u0026#34;)]/@href\u0026#39; return True if self.save(self.parse_html(xpath_dbs)) else False def save(self, favicon_url_list): if not favicon_url_list: return False for parse_url in favicon_url_list: favicon_url = parse_url if parse_url.startswith(\u0026#39;http\u0026#39;) else self.url + parse_url html_bytes = self.get_html_bytes(favicon_url) if html_bytes: filename = favicon_url.split(\u0026#39;/\u0026#39;)[-1] dir_favicon = self.dir + \u0026#39;/\u0026#39; + filename with open(dir_favicon, \u0026#39;wb\u0026#39;) as f: f.write(html_bytes) else: return False return True def run(self): self.dir = self.url.split(\u0026#39;/\u0026#39;)[2] if not os.path.exists(self.dir): os.makedirs(self.dir) if self.root_favicon(): print(\u0026#39;从网站根目录直接获取favicon成功\u0026#39;) else: print(\u0026#34;从网站根目录直接获取favicon失败,将尝试另一种方式\u0026#34;) if self.html_favicon(): print(\u0026#39;从html标签中获取favicon成功\u0026#39;) else: print(\u0026#39;从html标签中获取favicon失败,请采用其他方式\u0026#39;) if __name__ == \u0026#39;__main__\u0026#39;: url = \u0026#34;https://www.douban.com\u0026#34; # 末尾不要带斜杠 spider = SpiderFavicon(url) spider.run() 经我测试,在获取豆瓣的favicon时遇到了问题,其他正常网站可以爬取。\n","date":"Mar 05","permalink":"https://o5o.me/post/crawler_requests_websites_favicon_ico/","tags":null,"title":"爬取网站的favicon.ico"},{"categories":["Python"],"contents":"我印象中不是第一次遇到这个报错了,因此就有了价值把它记录下来。\n报错的原因在于,我在定义类的时候,把def __init__写成了def __int__。\n在PyCharm中,写的时候如果写def in,之后会有代码补全提示,它的第一项是def __int__,如果不仔细看就敲了回车,错误就产生了。\n正确的写法是老老实实把def __init__(self):一个字一个字地敲完。\n","date":"Mar 05","permalink":"https://o5o.me/post/python_class_unexpected_arguments/","tags":null,"title":"Python类:意外实参 TypeError: OBJ() takes no arguments"},{"categories":["Python"],"contents":"在Django中,如果要通过url将参数传递给视图,有两种路由配置方式。如下两种方式等效:\nurlpatterns = [ path(\u0026#39;article/\u0026lt;int:id\u0026gt;/\u0026#39;, views.article_detail, name = \u0026#39;article_detail\u0026#39;), re_path(r\u0026#39;^article/(?P\u0026lt;id\u0026gt;\\d+)/$\u0026#39;, views.article_detail, name=\u0026#39;article_detail\u0026#39;), ] 都可以匹配:http://.../article/3/。其中的(?P\u0026lt;id\u0026gt;\\d+)作何理解?\n这是一个命名组,将\\d+匹配到的数字记录到名字为id的这个命名组中。\n参考:Named Capturing Groups and Backreferences What does P mean in /(?P\u0026lt;topic_id\u0026gt;\\d+)$\n","date":"Feb 24","permalink":"https://o5o.me/post/django_urlconf_re_path/","tags":["Django"],"title":"如何理解Django路由配置URLConf中的正则表达式"},{"categories":["Crawler"],"contents":"写博客很多年了,以前用的都是WordPress,一个传统的PHP+MySQL的CMS。在2022年底开了一个新博客,用Hugo配合Obsidian来写,经过我的配置,在本地写好文章后只需git推送到github,github actions自动部署生成静态页面发送到服务器。很优雅的写作方式,所以从文章数量就能看出来,换用Hugo之后我是相当的高产。\n所以我就想将这些年用WordPress写的文章都统一整理出来,集中用Hugo进行管理。很自然地我就想到了,使用python爬虫将所有文章和文章配图都爬取下来,将内容转换为Hugo能识别的Markdown格式。\n分析 要爬取的网站:xiake.me(写本文时还是WordPress,未来绑定到Hugo博客) 使用的WP主题:Twenty Fifteen 文章列表URL:https://xiake.me/page/2/,其中2是指第2页。 文章URL:https://xiake.me/2020/08/06/一个傅里叶级数展开式的图象/,日期+文章标题或略缩名slug 文章配图URL:https://xiake.me/wp-content/uploads/2020/08/fuliye.png,日期+文件名 要获取的数据:\n文章标题 文章slug(文章略缩名,用于固定链接) 文章URL 文章文字内容 文章配图 文章发布日期 思路:访问文章列表URL,获取文章标题、文章slug、文章URL,访问文章URL获取文章内容、文章发布日期,下载文章配图,并将图片链接替换为本地相对链接。\n获取文章标题、URL这些数据使用XPATH,替换图片链接使用正则表达式。\n用IPython踩坑 最终我们是要将程序用面向对象的方式来写,也就是把爬虫封装成一个类。但我们可以先用IPython把爬虫的运行过程走一遍,等各个环节都理顺了,把坑都存踩完了,再改造成想要的样子。\n在这个环节,我先用Jupyter Lab用面向过程的思路把代码敲一遍。每写好一个代码块(cell)都可以按shift+enter(或ctrl+enter)运行一下,检查错误很方便。\n先导入一些必定用到的模块,其他模块用到的时候再导入:\n1 2 3 import requests from fake_useragent import UserAgent from lxml import etree 定义URL、请求头、获取响应对象:\n1 2 3 4 5 6 url = \u0026#39;https://xiake.me\u0026#39; ua = UserAgent() headers = { \u0026#39;UserAgent\u0026#39;: ua.random, } res = requests.get(url, headers=headers) 将获取到的html内容打印出来看一看是不是成功获取到了:\n1 2 html = res.content.decode(\u0026#39;utf-8\u0026#39;) print(html) 用XPATH解析页面,得到xpath对象:\n1 2 3 4 5 # 解析html,得到文章元素 xpath_dbs = \u0026#39;/html/body/div/div[2]/div/main/article/header/h2/a\u0026#39; parse_html = etree.HTML(html) post_list = parse_html.xpath(xpath_dbs) post_list是一个列表,列表里的各个元素是xpath对象,接下来从第一个元素中取数据。\n取文章标题:\n1 2 post_title = post_list[0].xpath(\u0026#39;./text()\u0026#39;)[0] print(post_title) 取文章slug:\n1 2 3 post_slug = post_list[0].xpath(\u0026#39;./@href\u0026#39;)[0].split(\u0026#39;/\u0026#39;)[-2] print(post_slug) 取文章链接并根据链接访问文章页面(二级页面):\n1 2 url1 = post_list[0].xpath(\u0026#39;./@href\u0026#39;)[0] html2 = requests.get(url1,headers=headers).content.decode(\u0026#39;utf-8\u0026#39;) 写取文章正文内容和文章发表日期的xpath:\n1 2 3 4 # 文章内容xpath xpath_dbs2 = \u0026#39;/html/body/div/div[2]/div/main/article/div\u0026#39; # 文章日期、作者xpath xpath_dbs3 = \u0026#39;/html/body/div/div[2]/div/main/article/footer\u0026#39; xpath解析二级页面,取文章正文内容:\n1 2 3 4 parse_html2 = etree.HTML(html2) post_content = parse_html2.xpath(xpath_dbs2) print(post_content) 1 2 import html as htmlP post_con_html = htmlP.unescape(etree.tostring(post_content[0]).decode()) 注意这里我又导入了一个html模块。因为之前没想到用它,我用html来命名了一个变量,因此我只好把这里导入的这个模块取别名为htmlP\n取文章发表日期:\n1 2 3 post_footer = parse_html2.xpath(xpath_dbs3) post_pubdate=post_footer[0].xpath(\u0026#39;./span[1]/a/time[1]/@datetime\u0026#39;)[0] print(post_pubdate) 这段代码的运行结果(文章发表日期),格式和Hugo文章里的日期格式一样,因此就不用转换日期格式了。\n将文章中的图片下载下来,并替换文章中的图片链接:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # 将文章里的图片下载下来,并替换html中的图片链接 import re pattern = re.compile(r\u0026#39;\u0026lt;img .*?src=\u0026#34;(.*?)\u0026#34; alt=.*?\u0026gt;\u0026#39;, re.S) img_list = pattern.findall(post_con_html) import os dir = \u0026#39;xiake/\u0026#39; for img_link in img_list: html_bytes = requests.get(url=img_link,headers=headers).content filename_list = img_link.split(\u0026#39;uploads/\u0026#39;)[-1].split(\u0026#39;/\u0026#39;) dir_img = dir + \u0026#39;/\u0026#39;.join(filename_list[:2])+\u0026#39;/\u0026#39; if not os.path.exists(dir_img): os.makedirs(dir_img) filename = dir_img + filename_list[-1] with open(filename, \u0026#39;wb\u0026#39;) as f: f.write(html_bytes) print(\u0026#39;%s 下载成功\u0026#39; % filename) # 图片下载成功后把html代码里的图片链接替换成本地链接 post_con_html = post_con_html.replace(img_link,filename) 这里是用正则来取图片链接,下载图片依然是用的requests。\n图片下载下来后我是打算放到代码同级目录下的一个叫xiake的文件夹里,并按日期来嵌套文件夹(即xiake/年/月/日/图片文件.jpg),因此如果这个文件夹不存在就需要提前新建它。\n图片下载成功后,用replace()方法把html里的链接替换掉。\n看看图片链接是不是都替换成功了:\n1 print(post_con_html) 接下来要将html转换为markdown,这里我用到了一个第三方包:markdownify,所以使用之前需要安装它:\n1 pip install markdownify 在Jupyter Lab里可以直接运行上面这句命令。\n1 2 3 4 from markdownify import markdownify as md post_con_md = md(post_con_html,heading_style=\u0026#39;ATX\u0026#39;) post_con_md.strip().replace(\u0026#39;\\n\\n\\n\u0026#39;,\u0026#39;\\n\u0026#39;) print(post_con_md) 参数heading_style='ATX'的作用是,在转换后,文章小标题用#来标识。转换成markdown格式之后,文章里的空行有些多,所以去掉。\n将转换后的文章内容整理成符合hugo格式要求的文本:\n1 2 3 4 5 6 7 8 9 10 11 12 # 将文章保存为hugo需要的markdown格式, text = \u0026#34;\u0026#34;\u0026#34;--- title: \u0026#34;{}\u0026#34; date: {} categories: - tags: - --- {} \u0026#34;\u0026#34;\u0026#34;.format(post_title,post_pubdate,post_con_md) print(text) 最后保存成文件:\n1 2 3 4 5 6 7 8 dir_post = \u0026#39;content/post/\u0026#39; if not os.path.exists(dir_post): os.makedirs(dir_post) post_filename = dir_post+\u0026#39;wp_\u0026#39;+post_slug+\u0026#39;.md\u0026#39; with open(post_filename, \u0026#39;w\u0026#39;) as f: f.write(text) print(\u0026#39;%s 文章保存成功\u0026#39; % post_filename) 文件名用获取的文章slug来命名,前面再加上一个wp_前缀。文章保存在了代码同级目录下的content/post/文件夹里。\n踩坑过程结束了。看上面的过程,似乎挺顺利的样子。真实情况是,总体很顺利,但也确实踩了不少坑。我已经很努力在还原真实的写代码过程了,如果把每个坑都仔细描述一下,这篇文章的长度我觉得还能再增加一倍。\n封装 在改写代码之前,通过上面写代码的过程,我又想到几个可以改进的点:\n程序写成交互式的,这样不仅可以爬这一个wp站,其他的也可以; 图片和markdown文件可以放到同一个文件夹里。 上面的代码都是理想情况下的样子,增加错误处理代码,提高容错性。 从同一篇文章里爬取的数据放到一个字典里,更整齐。 下面开始写代码了。\n主函数:\n1 2 3 4 5 if __name__ == \u0026#39;__main__\u0026#39;: spider_url = input(\u0026#34;你想爬取哪一个WordPress博客?\\n\u0026#34;) spider_dir = input(\u0026#34;你想将爬取到的内容放在哪一个文件夹里面?\\n\u0026#34;) spider = WordPressSpider(spider_url, spider_dir) spider.run() 接下来在代码最上方调用一堆模块、定义WordPressSpider类:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import html import os import re import requests from fake_useragent import UserAgent from lxml import etree from markdownify import markdownify as md from urllib import parse class WordPressSpider: \u0026#34;\u0026#34;\u0026#34; 将WordPress博客“xiake.me”上的文章爬取下来,并下载文章图片到本地,替换文章中的图片链接为 本地链接,最后需将文章转为Hugo需要的Markdown格式。 \u0026#34;\u0026#34;\u0026#34; 先说明,下面的代码都是在上面这个WordPressSpider类里面的方法定义。不要当成普通函数定义了。\n初始化:\n1 2 3 4 def __init__(self,spider_url,spider_dir): self.url = (spider_url if spider_url[-1] != \u0026#39;/\u0026#39; else spider_url[:-1]) self.baseurl = (spider_url if spider_url[-1] != \u0026#39;/\u0026#39; else spider_url[:-1]) + \u0026#39;/page/{}/\u0026#39; self.spider_dir = spider_dir 我用了两个属性self.url、self.baseurl,这是为了让程序可以爬取多个网站所做的妥协。属性声明里用了条件解析式,这样的话无论你输入的网址是不是带最后的那个/都能正确处理。\nget_proxies()方法:\n1 2 3 4 5 6 def get_proxies(self): proxies = { \u0026#39;http\u0026#39;: \u0026#39;http://127.0.0.1:7890\u0026#39;, \u0026#39;https\u0026#39;: \u0026#39;http://127.0.0.1:7890\u0026#39;, } return proxies 调用requests时可以指定一个参数(那两个字我不敢说,因为我的网站备案了),这样就可以顺畅访问“加载慢”的网站了。\n请求头:\n1 2 3 4 5 6 def get_headers(self): ua = UserAgent() headers = { \u0026#39;UserAgent\u0026#39;: ua.random, } return headers 如果只是爬我自己的网站的话,没有反爬手段,其实可以不用使用fake_useragent,为了增加程序的普适性,用上还是最好的。\nparse_html():\n1 2 3 4 5 def parse_html(self, xpath_dbs): res = requests.get(url=self.url, headers=self.get_headers(), proxies=self.get_proxies()) html_source = res.content.decode(\u0026#39;utf-8\u0026#39;) parse_html = etree.HTML(html_source) return parse_html.xpath(xpath_dbs) 这里就用上了上面的get_proxies()方法。接受一个xpath表达式,解析页面,返回xpath对象(列表)。\n获得文章列表:\n1 2 3 4 def get_post_list(self): xpath_dbs = \u0026#39;/html/body/div/div[2]/div/main/article/header/h2/a\u0026#39; post_list = self.parse_html(xpath_dbs) return post_list 获得总页码(文章一共有几页):\n1 2 3 4 def get_page_nums(self): xpath_dbs = \u0026#39;/html/body/div/div[2]/div/main/nav/div/a[2]/text()\u0026#39; num = int(self.parse_html(xpath_dbs)[0]) return num 这个页码数字是直接从html页面里取的。文章列表下方有一串数字,第1页……第n页,直接用xpath取这个n即可。\n遍历文章列表:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def get_post_info(self): post_list = self.get_post_list() post_info = dict() article_xpath_dbs = \u0026#39;/html/body/div/div[2]/div/main/article\u0026#39; for post in post_list: post_info[\u0026#39;title\u0026#39;] = post.xpath(\u0026#39;./text()\u0026#39;)[0] post_info[\u0026#39;url\u0026#39;] = post.xpath(\u0026#39;./@href\u0026#39;)[0] post_info[\u0026#39;slug\u0026#39;] = parse.unquote(post_info[\u0026#39;url\u0026#39;].split(\u0026#39;/\u0026#39;)[-2]) self.url = post_info[\u0026#39;url\u0026#39;] article = self.parse_html(article_xpath_dbs) post_info[\u0026#39;pub_date\u0026#39;] = article[0].xpath(\u0026#39;./footer/span[1]/a/time[1]/@datetime\u0026#39;)[0] post_content = article[0].xpath(\u0026#39;./div\u0026#39;)[0] post_con_html = html.unescape(etree.tostring(post_content).decode()) post_con_html = self.get_images(post_con_html) post_info[\u0026#39;content_md\u0026#39;] = self.convert_html_to_markdown(post_con_html) self.save_post(post_info) 这里又再一次调用了parse_html()方法,用于获取文章正文内容。一篇文章的各个数据都保存到了一个字典里,显得很整齐。每遍历一次,都调用convert_html_to_markdown()转换正文格式,调用get_images()保存图片,调用save_post()保存文章,这些方法是在下面定义的。\nhtml转markdown:\n1 2 3 4 def convert_html_to_markdown(self, html_content): md_content = md(html_content, heading_style=\u0026#39;ATX\u0026#39;).strip().replace(\u0026#39;\\n\\n\\n\u0026#39;, \u0026#39;\\n\u0026#39;) # md_content = md(html_content, heading_style=\u0026#39;ATX\u0026#39;) return md_content 保存图片:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 def get_images(self, html_content): pattern = re.compile(r\u0026#39;\u0026lt;img .*?src=\u0026#34;(.*?)\u0026#34; alt=.*?\u0026gt;\u0026#39;, re.S) img_list = pattern.findall(html_content) if not img_list: return html_content dir_name = self.spider_dir + \u0026#39;/\u0026#39; for img_link in img_list: if \u0026#39;greenhand\u0026#39; in img_link: # 文章中部分图片链接失效 print(\u0026#39;失效链接跳过\u0026#39;) break try: html_bytes = requests.get(url=img_link, headers=self.get_headers(), proxies=self.get_proxies()).content filename_list = img_link.split(\u0026#39;uploads/\u0026#39;)[-1].split(\u0026#39;/\u0026#39;) dir_img = dir_name + \u0026#39;/\u0026#39;.join(filename_list[:2]) + \u0026#39;/\u0026#39; if not os.path.exists(dir_img): os.makedirs(dir_img) filename = dir_img + filename_list[-1] with open(filename, \u0026#39;wb\u0026#39;) as f: f.write(html_bytes) print(\u0026#39;%s 下载成功\u0026#39; % filename) # 图片下载成功后把html代码里的图片链接替换成本地链接 html_content = html_content.replace(img_link, filename) except: print(\u0026#39;图片下载出错,跳过\u0026#39;) return html_content 这一部分代码相比上面用jupyter lab写的代码,增加了错误处理,如果requests报错,就捕获错误并让程序继续运行,而不是直接退出。\n上面代码里的:\n1 2 3 if \u0026#39;greenhand\u0026#39; in img_link: # 文章中部分图片链接失效 print(\u0026#39;失效链接跳过\u0026#39;) break 是用来跳过一些失效链接,这些图片链接里的共同特征是含有“greenhand”,我翻了好多份备份文件,都没找到这些图片,所有也没能补上,只好跳过它们。\n还有的情况是,即便图片失效了,requests依然不会报错,而是下载下来一个空文件。这种情况,requests文档里应该有办法解决,但我是选择了手动处理。\n保存文章:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def save_post(self, post_info_dict): text = \u0026#34;\u0026#34;\u0026#34;--- title: \u0026#34;{}\u0026#34; date: {} categories: - tags: - --- {} \u0026#34;\u0026#34;\u0026#34;.format(post_info_dict[\u0026#39;title\u0026#39;], post_info_dict[\u0026#39;pub_date\u0026#39;], post_info_dict[\u0026#39;content_md\u0026#39;]) dir_post = self.spider_dir + \u0026#39;/post/\u0026#39; if not os.path.exists(dir_post): os.makedirs(dir_post) post_filename = dir_post + \u0026#39;wp_\u0026#39; + post_info_dict[\u0026#39;slug\u0026#39;] + \u0026#39;.md\u0026#39; with open(post_filename, \u0026#39;w\u0026#39;) as f: f.write(text) print(\u0026#39;%s 文章保存成功\u0026#39; % post_filename) 注意一定不要为了代码整齐好看而让文档字符串跟着代码一起缩进,不然你用来缩进的那些空格都会进入到最后保存的文件当中,从而让文件内容变得杂乱。\n最后是run()方法,我把这个方法比喻作开车时“点火启动”这个步骤:\n1 2 3 4 def run(self): for page_num in range(1,self.get_page_nums()+1): self.url = self.baseurl.format(page_num) self.get_post_info() 在这个方法里,首先调用get_page_nums()方法获得总页码,然后一页页遍历文章列表。\n最后 写代码是很有成就感的一个过程,如果每遇到一个困难都能顺畅解决,那就更有成就感啦。最后推荐一些帮我解决问题的网站:\n菜鸟教程 - 学的不仅是技术,更是梦想!,我经常在这里直接搜python语法、函数的用法,linux命令等。 Stack Overflow - Where Developers Learn, Share, \u0026amp; Build Careers,如果你的程序报错,在这个网站里绝对有人遇到和你一样的报错信息。不过这个网站的搜索有些弱,最好是在Google搜(site:stackoverflow.com)。 Github,你想“造轮子”之前,先去看一看是不是有已经造好的轮子。比如上面用到的markdownify。 python包的官方网站或仓库主页。一般会有用法示范。 ","date":"Feb 13","permalink":"https://o5o.me/post/crawler_requests_wordpress_site_convert_to_hugo_markdown/","tags":["WordPress"],"title":"爬取WordPress网站:以爬虫的方式迁移WordPress至Hugo"},{"categories":["Django"],"contents":"在项目开发环境和生产环境,配置往往是不同的,例如在前者往往我们要打开DEBUG模式、为方便会使用sqlite数据库、使用Django自带的WSGIServer;但在生产环境,我们会关闭DEBUG模式,使用MySQL、PostgreSQL这样的数据库、使用Gunicorn和Nginx代替默认web服务、采用更为严格的访问限制。\n项目往往是要同时运行在开发环境和生产环境中的,那么怎样实现两套配置分开呢?\n在将项目部署至Platform.sh时,两环境分离的做法是,在settings.py文件中做一个条件判断,如果config.is_valid_platform()为真,就用生产环境中的配置覆盖默认配置。该做法给了我启发。\n如果是将项目部署在普通VPS上的话,我们可以设一个环境变量,如果该环境变量存在或为某个值,就表明是生产环境,应用某些配置。\n在服务器上项目的根目录,运行下述命令:\n1 2 3 4 5 6 7 8 #写入文件 echo \u0026#34;export SECRET_ENVIRON=\u0026#39;produc\u0026#39;\u0026#34; \u0026gt;\u0026gt; .DJANGO_SECRET cat .DJANGO_SECRET #查看文件内容 source .DJANGO_SECRET # 读取并导入环境变量 env #查看系统环境变量 在项目settings.py文件中,就可以对应这样写:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 根据环境变量判断为生产环境还是开发环境 import os --snip-- try: environ = os.environ[\u0026#34;SECRET_ENVIRON\u0026#34;] except KeyError as e: raise RuntimeError(\u0026#34;Could not find a SECRET_KEY in environment\u0026#34;) from e if environ: # 如果环境变量存在 DEBUG = False DATABASES = {} SECURE_REFERRER_POLICY = \u0026#34;\u0026#34; # 等等 先尝试读取环境变量,如果没有该变量就引发错误,有的话就用新设置项覆盖settings.py前面的设置项。\n另外我从网上看到有人是根据“主机名”来判断的:\n1 2 3 4 5 6 7 8 9 10 11 import socket --snip-- # 根据主机名判断为生产环境还是开发环境 ay_dp_value = socket.gethostname().startswith(\u0026#39;aoyu\u0026#39;) if not ay_dp_value: DEBUG = False DATABASES = {} SECURE_REFERRER_POLICY = \u0026#34;\u0026#34; # 等等 我开发环境的主机名是“aoyu”开头的,如果所在环境的主机名不是,那么就说明是生产环境了。\n参考资料:\nHow do you configure Django for simple development and deployment?\n","date":"Feb 13","permalink":"https://o5o.me/post/django_separate_dev_and_prod_environment/","tags":null,"title":"Django开发环境和生产环境分离"},{"categories":["Linux"],"contents":"我在访问一些网页的时候,有些域名会跳到同一个网站,即便访问那些尚未被注册的域名,也会跳转到同一个网站,所以我就在想我是不是有必要更换一下DNS。\n系统版本:Ubuntu22.10\n目的:将电脑DNS改为阿里公共DNS,223.5.5.5、223.6.6.6\n编辑文件sudo vim /etc/systemd/resolved.conf,在最后添加这两行:\n# /etc/systemd/resolved.conf DNS=223.5.5.5 DNS=223.6.6.6 重启服务:systemctl restart systemd-resolved.service\n查看状态:resolvectl status,可以看到DNS已经是修改后的了:\n$ resolvectl status Global Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported resolv.conf mode: stub Current DNS Server: 223.5.5.5 DNS Servers: 223.5.5.5 223.6.6.6 本文这些命令未来有可能会过时,所以如果按照这里的方法修改DNS不成功的话,这很正常,继续去谷歌、百度即可。事实上本文的这个方法就是我从几个地方找到的资料拼凑出来的。\n参考:\nUbuntu 18.04 永久修改DNS的方法 systemd-resolve command not found in Ubuntu 22.04 desktop ","date":"Feb 12","permalink":"https://o5o.me/post/ubuntu22.10_change_dns/","tags":["Ubuntu"],"title":"Ubuntu22.10修改DNS"},{"categories":["Linux"],"contents":"今天我有一个需求是要将某文件夹下所有文件开头的wp_去掉,网上很多答案都过时了,例如:\n1 2 3 $ rename \u0026#39;wp_\u0026#39; \u0026#39;\u0026#39; *.md Bareword \u0026#34;wp_\u0026#34; not allowed while \u0026#34;strict subs\u0026#34; in use at line 1, in: wp_ 想要批量重命名可以这样写:\n1 2 3 4 $ rename -n \u0026#39;s/^wp_//\u0026#39; * rename(wp_cancel-domain-name-beian.md, cancel-domain-name-beian.md) rename(wp_cloudreve_webdav_obsidian_synchronization.md, cloudreve_webdav_obsidian_synchronization.md) rename(wp_down-epub-oreilly.md, down-epub-oreilly.md) 加上-n只会列出重命名前后的文件名对比,而不会真正重命名,方便你不断调整合适的命令写法。真正重命名时应该把-n去掉:\n1 $ rename \u0026#39;s/^wp_//\u0026#39; * ","date":"Feb 11","permalink":"https://o5o.me/post/shell_rename_files/","tags":null,"title":"Shell给文件批量重命名"},{"categories":["Python"],"contents":"在我写的项目里,称类似文章的文本为entry。我想要在entry编辑页面(edit_entry)上添加一个删除按钮,点击后这条entry会被删除。\n添加视图 文件~/应用/views.py\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from django.shortcuts import render, redirect from django.contrib.auth.decorators import login_required from django.http import Http404 from .models import Entry -- snip -- # 删除entry @login_required def delete_entry(request, entry_id): \u0026#34;\u0026#34;\u0026#34;删除既有条目\u0026#34;\u0026#34;\u0026#34; # 根据id获取需要删除的entry entry = Entry.objects.get(id=entry_id) # 只有该entry的拥有者可以将其删除 check_entry_owner(request, entry) # 调用.delete()方法删除文章 entry.delete() # 完成删除后返回entry列表 return redirect(\u0026#39;aoyu_blog_logs:topic\u0026#39;, topic_id=entry.topic.id) 装饰器@login_required确保只有已登录用户才能删除entry。\n调用的函数check_entry_owner()确保只有entry拥有者可以将其删除。代码如下:\n1 2 3 4 5 6 7 8 from django.http import Http404 -- snip -- def check_entry_owner(request, entry): \u0026#34;\u0026#34;\u0026#34;核实entry关联到的用户是否为当前用户\u0026#34;\u0026#34;\u0026#34; if entry.topic.owner != request.user: raise Http404 添加URL模式 文件~/应用/urls.py\n1 2 3 4 5 6 --snip-- urlpatterns = [ --snip-- # 用于删除条目的页面 path(\u0026#39;delete_entry/\u0026lt;int:entry_id\u0026gt;/\u0026#39;, views.delete_entry, name=\u0026#39;delete_entry\u0026#39;), ] 修改模板 文件~/应用/templates/应用/edit_entry.html\n1 2 3 4 5 6 7 8 9 --snip-- \u0026lt;form action=\u0026#34;{% url \u0026#39;aoyu_blog_logs:delete_entry\u0026#39; entry.id %}\u0026#34;\u0026gt; {% csrf_token %} {% buttons %} \u0026lt;button type=\u0026#34;submit\u0026#34; class=\u0026#34;btn btn-outline-danger\u0026#34;\u0026gt;删除该Entry\u0026lt;/button\u0026gt; {% endbuttons %} \u0026lt;/form\u0026gt; --snip-- 我没有使用a标签,而是使用了一个表单。由于使用了Bootstrap,我不需要再去调CSS。\n可以改进的地方是,点击删除按钮后弹出一个“二次提示”信息。现在是点击按钮后entry直接就被删除了。\n","date":"Feb 09","permalink":"https://o5o.me/post/django_add_function_delete_entry/","tags":["Django"],"title":"Django添加删除文章功能"},{"categories":["Python"],"contents":"在Django中,一个模型就是一个类,模型里的字段就是类的属性,因此本文的标题也可以写作:Django重命名类属性。\n如果模型刚写好,还没在数据库中创建表,把代码里所有的旧字段名改为新字段名即可。\n如果已经修改了数据库,首先在代码里将旧字段名修改为新字段名;然后需要在数据库里对表列进行重命名。在Django里不用直接操作数据库,在终端里运行命令makemigrations,Django会识别出来你是想给data_added重命名,之后再在终端运行命令migrate即可。\n1 2 3 4 5 6 7 8 9 10 11 (/home/aoyu/AoyuCondaEnv/AoyuBlog) aoyu@aoyuSurface:~/AoyuPythonProjects/AoyuBlog$ python manage.py makemigrations aoyu_blog_logs Was topic.data_added renamed to topic.date_added (a DateTimeField)? [y/N] y Migrations for \u0026#39;aoyu_blog_logs\u0026#39;: aoyu_blog_logs/migrations/0004_rename_data_added_topic_date_added.py - Rename field data_added on topic to date_added (/home/aoyu/AoyuCondaEnv/AoyuBlog) aoyu@aoyuSurface:~/AoyuPythonProjects/AoyuBlog$ python manage.py migrate Operations to perform: Apply all migrations: admin, aoyu_blog_logs, auth, contenttypes, sessions Running migrations: Applying aoyu_blog_logs.0004_rename_data_added_topic_date_added... OK ","date":"Feb 09","permalink":"https://o5o.me/post/django_rename_field/","tags":["Django"],"title":"Django给模型字段重命名"},{"categories":["Python"],"contents":"这是一道很有趣的题目。我感觉这一题的代码我写得还不够优雅。\nExercise 5.6. 科赫曲线 (Koch Curve) 是一个看起来类似不规则碎片的几何体 (fractal)。要画一个长度为 x 的科赫曲线,你只需要:\n画一个长度为 x/3 的科赫曲线。 左转 60 度。 画一个长度为 x/3 的科赫曲线。 右转 60 度。 画一个长度为 x/3 的科赫曲线。 左转 60 度。 画一个长度为 x/3 的科赫曲线。 例外情况是 x 小于 3 的情形:此时,你只需要画一道长度为 x 的直线。\n写一个名为 koch 的函数,接受一个海龟和一个长度作为形参,然后使用海龟画一条给定长度的科赫曲线。 写一个名为 snowflake 的函数,画出三条科赫曲线,构成雪花的轮廓。 科赫曲线能够以多种方式泛化。 点击此处查看例子,并实现你最喜欢的那种方式。 我的答案:\n第一小题,\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import turtle def koch(t, l): \u0026#34;\u0026#34;\u0026#34; 画出来一条科赫曲线 :param t: 一个turtle实例 :param l: 曲线长度 :return: None \u0026#34;\u0026#34;\u0026#34; if l \u0026lt; 3: t.fd(l) else: l = l / 3 koch(t,l) t.lt(60) # 左转60度 koch(t,l) t.rt(120) # 右转120度 koch(t, l) t.lt(60) # 左转60度 koch(t, l) bob = turtle.Turtle() koch(bob, 1000) turtle.mainloop() 这一题的代码是完全按书上的要求来写的。有可能是我的屏幕分辨率比较高,画出来的曲线看起来比较小。\n第二小题,\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import turtle def koch(t, l): \u0026#34;\u0026#34;\u0026#34; 画出来一条科赫曲线 :param t: 一个turtle实例 :param l: 曲线长度 :return: None \u0026#34;\u0026#34;\u0026#34; if l \u0026lt; 10: t.fd(l) else: l = l / 3 koch(t,l) t.lt(60) # 左转60度 koch(t,l) t.rt(120) # 右转120度 koch(t, l) t.lt(60) # 左转60度 koch(t, l) def snowflake(t,l,n): \u0026#34;\u0026#34;\u0026#34; 画出n条科赫曲线,构成雪花的形状。 :param t: 一个turtle实例 :param l: 单条科赫曲线的长度 :param n: 科赫曲线的条数。 :return: None \u0026#34;\u0026#34;\u0026#34; # 先将turtle移到屏幕左边缘处 t.pu() # 抬笔 t.lt(180) t.fd(600) t.lt(180) t.pd() # 落笔 # 让bob加速 bob.speed(0) for i in range(n): koch(bob, l) # 画完一条后,左转360/n度后再接着画下一条 bob.lt(360/n) bob = turtle.Turtle() snowflake(bob,500,6) turtle.mainloop() 为了让画出的几条科赫曲线能闭合,每画完一条,就让turtle左转(360/n)度。\nturtle画画的速度太慢,所以修改了它的速度。\n三条科赫曲线构成的封闭“雪花”看起来比较怪异,所以在上面的代码里我让turtle画出6条科赫曲线。\n画出来的图是这样的:\n挺好看的,对吧。\n第三题这里就先不画了。\n","date":"Feb 06","permalink":"https://o5o.me/post/think_python_exercise_5.6_koch_curve/","tags":null,"title":"Think Python Exercise 5.6 科赫曲线"},{"categories":["Python"],"contents":"题目:SQL19 分组过滤练习题\n这一题是将行(用户、record)根据university进行分组,最后返回的不是用户的数据,而是分组(university、大学)的数据。\n因此在过滤的时候,不是过滤行,而是过滤分组,所以要用having操作符,而不是where操作符。\n返回的结果还应该保留三位小数,所以使用round()函数。\n我的解答:\n1 2 3 4 SELECT university, ROUND(AVG(question_cnt),3) as avg_question_cnt, ROUND(AVG(answer_cnt),3) as avg_answer_cnt FROM user_profile GROUP BY university HAVING avg_question_cnt \u0026lt;5 or avg_answer_cnt \u0026lt; 20 ","date":"Feb 05","permalink":"https://o5o.me/post/nowcoder_sql19/","tags":null,"title":"SQL分组过滤"},{"categories":["Python"],"contents":" 描述 牛牛在门头沟大学学习,一学年过去了,需要根据他的成绩计算他的平均绩点,假如绩点与等级的对应关系如下表所示。请根据输入的等级和学分数,计算牛牛的均绩(每门课学分乘上单门课绩点,求和后对学分求均值)。\nA 4.0 B 3.0 C 2.0 D 1.0 F 0 这一题来自牛客网,题目链接:here\n输入描述: 连续输入一行等级一行学分,遇到等级为False则结束输入。\n输出描述: 均绩保留两位小数。\n示例1 输入: A 3 B 4 C 2 False 输出: 3.11 我的解答 最初的解答:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 d={\u0026#39;A\u0026#39;:4.0,\u0026#39;B\u0026#39;:3.0,\u0026#39;C\u0026#39;:2.0,\u0026#39;D\u0026#39;:1.0,\u0026#39;F\u0026#39;:0} b=[] c=[] i=1 while True: a = input() if a != \u0026#34;False\u0026#34; and i%2 == 1: b.append(a) i += 1 elif a != \u0026#34;False\u0026#34; and i%2 == 0: c.append(int(a)) i += 1 elif a == \u0026#34;False\u0026#34;: break else: break e=0 for i,j in zip(b,c): e += d[i]*j print(\u0026#34;%.2f\u0026#34; % (e/sum(c))) 很不优雅。我看到高赞解答后,真的是惊为天人,太优雅了!\n我又动手写了一个优雅版的:\n1 2 3 4 5 6 7 8 9 d={\u0026#39;A\u0026#39;:4.0,\u0026#39;B\u0026#39;:3.0,\u0026#39;C\u0026#39;:2.0,\u0026#39;D\u0026#39;:1.0,\u0026#39;F\u0026#39;:0} t1 = 0 t2 = 0 while (rank:=d.get(input(),False)) is not False: score = int(input()) t1 += rank * score t2 += score print(\u0026#34;%.2f\u0026#34; % (t1/t2)) 用到了海象运算服,用到了字典的get()方法,关键是它还很短。\nis not False是必不可少的(或者可以用其他办法)。因为,如果输入的是“F”,那么rank的值就是0,在条件判断中就相当于False,while循环就没办法继续了。所以就只好想办法不让Python把0当成False。\n在(rank:=d.get(input(),False))两边的括号也是必不可少的,不然会有优先级问题。\n","date":"Feb 03","permalink":"https://o5o.me/post/nowcoder_np47/","tags":null,"title":"Python练习题NP47牛牛的绩点"},{"categories":["SRS"],"contents":"Anki更新2.1.57版本后,内置支持自建同步服务。之前都是需要以插件的形式实现,且搭建后往往只适用于旧版本的Anki,选择了自建同步服务就意味着放弃追求新版本和新功能。\n只需要几行代码就可以搭建自己的Anki同步服务了:\nscreen -S anki-aoyu python3 -m venv ~/syncserver ~/syncserver/bin/pip install anki SYNC_USER1=aoyu:123456 ~/syncserver/bin/python -m anki.syncserver 我的系统版本是Ubuntu22.04,安装过程是相当顺畅的。很大概率你需要先运行apt install python3.10-venv,之后才能python3 -m venv ~/syncserver,不过你不需要提前考虑到这一点,报错后会提醒你安装的。\nSYNC_USER1后面跟的是账户名和密码,在Anki里用这个账号登录。\n你需要在Anki软件设置Self-hosted sync server项里填写http://服务器ip:8080/,之后就可以使用自己的同步服务了。\n可以用 Nginx 做一个反代,我能想到的优点有:不让8080端口暴露到网络上;给anki的同步服务绑定一个域名,好记;可以启用https。这里就不写了,懒。\nscreen命令的使用可参考:SSH远程会话管理工具 - screen使用教程 - VPS侦探 (vpser.net)\n安装参考:Sync Server - Anki Manual (ankiweb.net)\n","date":"Jan 31","permalink":"https://o5o.me/post/anki_self_hosted_sync_server_ubuntu/","tags":["Anki"],"title":"Ubuntu自建Anki同步服务"},{"categories":["LeetCode"],"contents":"题目:183. 从不订购的客户\n我的解答 1 2 3 4 5 SELECT Name AS Customers FROM Customers WHERE NOT ID IN ( SELECT CustomerId FROM Orders); ","date":"Jan 31","permalink":"https://o5o.me/post/leetcode_183_customers_who_never_order/","tags":["SQL"],"title":"Leetcode 183 从不订购的客户"},{"categories":["LeetCode"],"contents":"题目:182. 查找重复的电子邮箱\n我的解答 1 2 3 4 5 6 7 8 # Write your MySQL query statement below SELECT name AS Employee FROM ( SELECT a.name AS name, a.salary AS mySalary, b.salary AS manaSalary FROM Employee a, Employee b WHERE a.managerId = b.id ) as b WHERE mySalary \u0026gt; manaSalary; 更好的答案:\n自联结 1 2 3 4 SELECT e1.name AS Employee FROM Employee AS e1, Employee AS e2 WHERE e1.managerID = e2.id AND e1.salary \u0026gt; e2.salary; 子查询 1 2 3 4 5 6 SELECT name AS Employee FROM Employee as e WHERE salary \u0026gt; ( SELECT salary FROM Employee WHERE id = e.managerId); ","date":"Jan 31","permalink":"https://o5o.me/post/leetcode_182_duplicate_emails/","tags":["SQL"],"title":"Leetcode 182 查找重复的电子邮箱"},{"categories":["LeetCode"],"contents":"题目:181. 超过经理收入的员工\n我的解答 1 2 3 4 5 6 7 8 # Write your MySQL query statement below SELECT name AS Employee FROM ( SELECT a.name AS name, a.salary AS mySalary, b.salary AS manaSalary FROM Employee a, Employee b WHERE a.managerId = b.id ) as b WHERE mySalary \u0026gt; manaSalary; 更好的答案:\n自联结 1 2 3 4 SELECT e1.name AS Employee FROM Employee AS e1, Employee AS e2 WHERE e1.managerID = e2.id AND e1.salary \u0026gt; e2.salary; 子查询 1 2 3 4 5 6 SELECT name AS Employee FROM Employee as e WHERE salary \u0026gt; ( SELECT salary FROM Employee WHERE id = e.managerId); ","date":"Jan 31","permalink":"https://o5o.me/post/leetcode_181_employees_earning_more_than-their_managers/","tags":["SQL"],"title":"Leetcode 181 超过经理收入的员工"},{"categories":["LeetCode"],"contents":"题目:180. 连续出现的数字\n我的解答 使用变量\n1 2 3 4 5 6 7 8 9 SELECT DISTINCT num AS ConsecutiveNums FROM ( SELECT num, (CASE WHEN @prev = num THEN @cur := @cur +1 WHEN @prev := num THEN @cur := 1 END) AS ran FROM Logs, (SELECT @prev := NULL, @cur := 0) AS r ) AS t WHERE t.ran \u0026gt;= 3 ","date":"Jan 31","permalink":"https://o5o.me/post/leetcode_180_consecutive_numbers/","tags":["SQL"],"title":"Leetcode 180 连续出现的数字"},{"categories":["LeetCode"],"contents":"题目:178. 分数排名\n我的解答 使用了MySQL提供的函数 1 2 3 SELECT score, DENSE_RANK() OVER(ORDER BY score DESC) AS \u0026#39;rank\u0026#39; FROM Scores; 因为别名rank和rank函数冲突了,所以加个引号。\n使用变量 SELECT score, CAST((CASE WHEN @prev = score THEN @curRank WHEN @prev := score THEN @curRank := @curRank +1 WHEN score = 0 THEN @curRank := @curRank + 1 END) AS SIGNED) AS \u0026#39;rank\u0026#39; FROM scores, (SELECT @curRank := 0, @prev := NULL) AS r ORDER BY score DESC; 变量prev指向的是先前一行记录里的score,变量curRank是当前这个分数的排名。\n这一段的描述可能不够准确:首先查询得到每一条记录的score并按降序排序,而后,从上到下,第一个score是4.00,设为1,第二个score也是4.00,也设为1,第三个是3.85,设为1+1=2,依次类推。根据我所了解的,SQL的执行顺序中,ORDER BY是在SELECT执行之后才执行的。但是,上述过程很明显利用了排序后的结果,所以这一点我就很困惑。如果用主查询和子查询来理解,主查询执行完之后,对于返回的每一行结果再执行子查询,似乎可行,但代码中的子查询是出现在FROM后面的,而条件判断是主查询里面的。这里我依旧很困惑。\n(SELECT @curRank := 0, @prev := NULL) AS r,是一个子查询,可以看做是为这两个变量设定初始值。对于派生出来的表,必须指定别名(Every derived table must have its own alias)。这里指定了别名r。\ncast(字段 as signed)将数据转换为整型,如果不使用这个函数,选出来的排名有双引号。\n参考:MySQL自定义变量的语法,Case When语法及用法 教你用SQL实现统计排名\n使用自联结 SELECT a.score AS score, COUNT(DISTINCT b.score) AS \u0026#39;rank\u0026#39; FROM scores AS a, scores AS b WHERE a.score \u0026lt;= b.score GROUP BY a.id ORDER BY a.score DESC ","date":"Jan 31","permalink":"https://o5o.me/post/leetcode_178_rank_scores/","tags":["SQL"],"title":"Leetcode 178 分数排名"},{"categories":["LeetCode"],"contents":"题目:177. 第N高的薪水\n我的解答 1 2 3 4 5 6 7 8 9 10 11 12 CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT BEGIN SET N = N - 1; RETURN ( # Write your MySQL query statement below. SELECT IFNULL( (SELECT DISTINCT salary FROM Employee ORDER BY salary DESC LIMIT 1 OFFSET N),NULL) AS getNthHighestSalary ); END 在 LIMIT 和 OFFSET 内不能进行运算,所以在开头首先让N减1。\n注意语句末尾的分号。\n注意指定别名语句AS getNthHighestSalary末尾并没有括号。\n","date":"Jan 28","permalink":"https://o5o.me/post/leetcode_177_nth_highest_salary/","tags":["SQL"],"title":"Leetcode 177 第N高的薪水"},{"categories":["LeetCode"],"contents":"题目:176. 第二高的薪水\n我的解答 下面这个最初的解答被判定为错,乐扣没显示是哪些测试用例错了,所以也没办法改进。\n1 2 3 4 5 6 7 # Write your MySQL query statement below SELECT MAX(salary) AS SecondHignestSalary FROM Employee WHERE salary \u0026lt; ( SELECT MAX(salary) FROM Employee ); 下面这个解答是成功通过的:\n1 2 3 4 5 SELECT IFNULL( (SELECT DISTINCT salary FROM Employee ORDER BY salary DESC LIMIT 1 OFFSET 1),NULL) AS SecondHighestSalary; DISTINCT参数用于让查询结果中相同的值只出现一次。\nDESC关键字用于让查询结果按降序进行排序。\nIFNULL()函数用于判断第一个表达式是否为 NULL,如果为 NULL 则返回第二个参数的值。\nLIMIT 和 OFFSET 用于对查询结果进行分页,每次返回查询结果的一部分。\n","date":"Jan 28","permalink":"https://o5o.me/post/leetcode_176_second_highest_salary/","tags":["SQL"],"title":"Leetcode 176 第二高的薪水"},{"categories":["LeetCode"],"contents":"题目:175. 组合两个表\n我的解答 1 2 3 # Write your MySQL query statement below SELECT FirstName AS firstName, LastName AS lastName, City AS city, State AS state From Person LEFT OUTER JOIN Address ON Person.PersonId = Address.PersonId; ","date":"Jan 28","permalink":"https://o5o.me/post/leetcode_175_combine_two_tables/","tags":["SQL"],"title":"Leetcode 175 组合两个表"},{"categories":["SRS"],"contents":"我日常用于复习的 SuperMemo 是中文懒人包版本,前几天打算学 AdvEng2018 牌组,担心合并牌组时导致文件损坏,所以就又用了另一个SM软件来单独学AdvEng2018,同时也想是用用英文SM,汉化版软件里有些翻译的中文选项我不理解,可以对照着看。\n复习时经常出现一个对话框:Which interval do you want to use?\n用SM的懒人包版本复习时则没这个弹窗。\n出现这个弹窗的原因是,软件想告诉你,按照默认的算法,下次安排这张卡片的复习要到几十天之后了,这个时间间隔太长了,问你要不要换个时间间隔短一点的算法。\n让它不显示的办法:默认是,如果一张卡片的复习间隔超过14天就会显示这个弹窗,我们把这个天数调大一些就好了。\n流程:Toolkit - Options -\u0026gt; Learning - Algorithm SM-18 Alerts - Interval [days],默认是14,改大一点,例如改为3000.\n参考:Which interval do you want to use - SuperMemopedia\n","date":"Jan 27","permalink":"https://o5o.me/post/supermemo_which_interval_do_you_want_to_use/","tags":["SuperMemo"],"title":"Supermemo出现弹窗Which interval do you want to use?"},{"categories":["Python"],"contents":"运行下述代码后报错:\n1 2 import pygame pygame.init() 完整报错:\nlibGL error: MESA-LOADER: failed to open iris: /usr/lib/dri/iris_dri.so: cannot open shared object file: No such file or directory (search paths /usr/lib/x86_64-linux-gnu/dri:\\$${ORIGIN}/dri:/usr/lib/dri, suffix _dri) libGL error: failed to load driver: iris libGL error: MESA-LOADER: failed to open swrast: /usr/lib/dri/swrast_dri.so: cannot open shared object file: No such file or directory (search paths /usr/lib/x86_64-linux-gnu/dri:\\$${ORIGIN}/dri:/usr/lib/dri, suffix _dri) libGL error: failed to load driver: swrast X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 149 (GLX) Minor opcode of failed request: 3 (X_GLXCreateContext) Value in failed request: 0x0 Serial number of failed request: 101 Current serial number in output stream: 102 提示在/usr/lib/dri/找不到iris和swrast驱动。而在/usr/lib/x86_64-linux-gnu/dri这个目录下,两个驱动都有。\n解决办法:在上面第一个目录中建立到第二个目录中文件的软链接。\naoyu@aoyuSurface:~$ sudo mkdir /usr/lib/dri aoyu@aoyuSurface:~$ cd /usr/lib/dri aoyu@aoyuSurface:/usr/lib/dri$ sudo ln -s /usr/lib/x86_64-linux-gnu/dri/iris_dri.so iris_dri.so sudo ln -s /usr/lib/x86_64-linux-gnu/dri/swrast_dri.so swrast_dri.so 运行程序,再次报错:\nlibGL error: MESA-LOADER: failed to open iris: /home/aoyu/AoyuCondaEnv/alien_invasion/bin/../lib/libstdc++.so.6: version `GLIBCXX_3.4.30\u0026#39; not found (required by /lib/x86_64-linux-gnu/libLLVM-15.so.1) (search paths /usr/lib/x86_64-linux-gnu/dri:\\$${ORIGIN}/dri:/usr/lib/dri, suffix _dri) libGL error: failed to load driver: iris libGL error: MESA-LOADER: failed to open swrast: /home/aoyu/AoyuCondaEnv/alien_invasion/bin/../lib/libstdc++.so.6: version `GLIBCXX_3.4.30\u0026#39; not found (required by /lib/x86_64-linux-gnu/libLLVM-15.so.1) (search paths /usr/lib/x86_64-linux-gnu/dri:\\$${ORIGIN}/dri:/usr/lib/dri, suffix _dri) libGL error: failed to load driver: swrast X Error of failed request: BadValue (integer parameter out of range for operation) Major opcode of failed request: 149 (GLX) Minor opcode of failed request: 3 (X_GLXCreateContext) Value in failed request: 0x0 Serial number of failed request: 101 Current serial number in output stream: 102 其中/home/aoyu/AoyuCondaEnv/alien_invasion/是我的Python解释器所在位置。\n报错的原因是GLIBCXX_3.4.30' not found。在libstdc++.so.6中看一看所有有关GLIBCXX的信息12:\naoyu@aoyuSurface:~$ strings /home/aoyu/AoyuCondaEnv/alien_invasion/lib/libstdc++.so.6 | grep GLIBCXX GLIBCXX_3.4 GLIBCXX_3.4.1 GLIBCXX_3.4.2 GLIBCXX_3.4.3 GLIBCXX_3.4.4 GLIBCXX_3.4.5 GLIBCXX_3.4.6 GLIBCXX_3.4.7 GLIBCXX_3.4.8 GLIBCXX_3.4.9 GLIBCXX_3.4.10 GLIBCXX_3.4.11 GLIBCXX_3.4.12 GLIBCXX_3.4.13 GLIBCXX_3.4.14 GLIBCXX_3.4.15 GLIBCXX_3.4.16 GLIBCXX_3.4.17 GLIBCXX_3.4.18 GLIBCXX_3.4.19 GLIBCXX_3.4.20 GLIBCXX_3.4.21 GLIBCXX_3.4.22 GLIBCXX_3.4.23 GLIBCXX_3.4.24 GLIBCXX_3.4.25 GLIBCXX_3.4.26 GLIBCXX_3.4.27 GLIBCXX_3.4.28 GLIBCXX_3.4.29 GLIBCXX_DEBUG_MESSAGE_LENGTH _ZNKSt14basic_ifstreamIcSt11char_traitsIcEE7is_openEv@GLIBCXX_3.4 _ZNSt13basic_istreamIwSt11char_traitsIwEE6ignoreEv@@GLIBCXX_3.4.5 _ZNKSbIwSt11char_traitsIwESaIwEE11_M_disjunctEPKw@GLIBCXX_3.4 _ZNKSt14basic_ifstreamIwSt11char_traitsIwEE7is_openEv@@GLIBCXX_3.4.5 GLIBCXX_3.4.21 GLIBCXX_3.4.9 _ZSt10adopt_lock@@GLIBCXX_3.4.11 GLIBCXX_3.4.10 GLIBCXX_3.4.16 GLIBCXX_3.4.1 _ZNSt19istreambuf_iteratorIcSt11char_traitsIcEEppEv@GLIBCXX_3.4 GLIBCXX_3.4.28 _ZNSs7_M_copyEPcPKcm@GLIBCXX_3.4 GLIBCXX_3.4.25 _ZNSt19istreambuf_iteratorIcSt11char_traitsIcEEppEv@@GLIBCXX_3.4.5 _ZNSs7_M_moveEPcPKcm@@GLIBCXX_3.4.5 _ZNKSt13basic_fstreamIwSt11char_traitsIwEE7is_openEv@GLIBCXX_3.4 _ZNKSt13basic_fstreamIcSt11char_traitsIcEE7is_openEv@GLIBCXX_3.4 _ZNSbIwSt11char_traitsIwESaIwEE4_Rep26_M_set_length_and_sharableEm@@GLIBCXX_3.4.5 _ZNSs4_Rep26_M_set_length_and_sharableEm@GLIBCXX_3.4 _ZSt10defer_lock@@GLIBCXX_3.4.11 _ZN10__gnu_norm15_List_node_base4swapERS0_S1_@@GLIBCXX_3.4 _ZNSs9_M_assignEPcmc@@GLIBCXX_3.4.5 _ZNKSbIwSt11char_traitsIwESaIwEE15_M_check_lengthEmmPKc@@GLIBCXX_3.4.5 _ZNKSt14basic_ifstreamIcSt11char_traitsIcEE7is_openEv@@GLIBCXX_3.4.5 _ZNSbIwSt11char_traitsIwESaIwEE7_M_moveEPwPKwm@GLIBCXX_3.4 GLIBCXX_3.4.24 _ZNVSt9__atomic011atomic_flag12test_and_setESt12memory_order@@GLIBCXX_3.4.11 GLIBCXX_3.4.20 _ZNSt11char_traitsIwE2eqERKwS2_@@GLIBCXX_3.4.5 GLIBCXX_3.4.12 _ZNSi6ignoreEv@@GLIBCXX_3.4.5 GLIBCXX_3.4.2 _ZNSt11char_traitsIcE2eqERKcS2_@@GLIBCXX_3.4.5 GLIBCXX_3.4.6 GLIBCXX_3.4.15 _ZNKSt13basic_fstreamIcSt11char_traitsIcEE7is_openEv@@GLIBCXX_3.4.5 _ZNSs9_M_assignEPcmc@GLIBCXX_3.4 GLIBCXX_3.4.19 _ZNKSt14basic_ofstreamIwSt11char_traitsIwEE7is_openEv@GLIBCXX_3.4 _ZNSt19istreambuf_iteratorIwSt11char_traitsIwEEppEv@GLIBCXX_3.4 GLIBCXX_3.4.27 _ZN10__gnu_norm15_List_node_base7reverseEv@@GLIBCXX_3.4 _ZN10__gnu_norm15_List_node_base4hookEPS0_@@GLIBCXX_3.4 _ZNSt11char_traitsIwE2eqERKwS2_@GLIBCXX_3.4 _ZNSbIwSt11char_traitsIwESaIwEE7_M_copyEPwPKwm@GLIBCXX_3.4 _ZNSbIwSt11char_traitsIwESaIwEE7_M_copyEPwPKwm@@GLIBCXX_3.4.5 GLIBCXX_3.4.23 GLIBCXX_3.4.3 GLIBCXX_3.4.7 _ZNSi6ignoreEl@@GLIBCXX_3.4.5 _ZNKSbIwSt11char_traitsIwESaIwEE11_M_disjunctEPKw@@GLIBCXX_3.4.5 _ZNSt13basic_istreamIwSt11char_traitsIwEE6ignoreEv@GLIBCXX_3.4 _ZNKSt13basic_fstreamIwSt11char_traitsIwEE7is_openEv@@GLIBCXX_3.4.5 _ZNSbIwSt11char_traitsIwESaIwEE7_M_moveEPwPKwm@@GLIBCXX_3.4.5 GLIBCXX_3.4.18 _ZNSbIwSt11char_traitsIwESaIwEE4_Rep26_M_set_length_and_sharableEm@GLIBCXX_3.4 _ZNSt13basic_istreamIwSt11char_traitsIwEE6ignoreEl@@GLIBCXX_3.4.5 _ZSt15future_category@@GLIBCXX_3.4.14 _ZNSi6ignoreEl@GLIBCXX_3.4 GLIBCXX_3.4.29 _ZNSt11char_traitsIcE2eqERKcS2_@GLIBCXX_3.4 _ZNKSs15_M_check_lengthEmmPKc@GLIBCXX_3.4 _ZN10__gnu_norm15_List_node_base8transferEPS0_S1_@@GLIBCXX_3.4 _ZNSbIwSt11char_traitsIwESaIwEE9_M_assignEPwmw@GLIBCXX_3.4 _ZNVSt9__atomic011atomic_flag5clearESt12memory_order@@GLIBCXX_3.4.11 _ZNKSt14basic_ofstreamIcSt11char_traitsIcEE7is_openEv@@GLIBCXX_3.4.5 _ZNKSt14basic_ofstreamIcSt11char_traitsIcEE7is_openEv@GLIBCXX_3.4 _ZNSs7_M_moveEPcPKcm@GLIBCXX_3.4 _ZNSt13basic_istreamIwSt11char_traitsIwEE6ignoreEl@GLIBCXX_3.4 _ZNSbIwSt11char_traitsIwESaIwEE9_M_assignEPwmw@@GLIBCXX_3.4.5 _ZNKSbIwSt11char_traitsIwESaIwEE15_M_check_lengthEmmPKc@GLIBCXX_3.4 _ZNKSs11_M_disjunctEPKc@@GLIBCXX_3.4.5 _ZN10__gnu_norm15_List_node_base6unhookEv@@GLIBCXX_3.4 GLIBCXX_3.4.22 _ZNSt19istreambuf_iteratorIwSt11char_traitsIwEEppEv@@GLIBCXX_3.4.5 _ZNSi6ignoreEv@GLIBCXX_3.4 _ZNSs7_M_copyEPcPKcm@@GLIBCXX_3.4.5 GLIBCXX_3.4.8 GLIBCXX_3.4.13 _ZSt11try_to_lock@@GLIBCXX_3.4.11 _ZNKSt14basic_ofstreamIwSt11char_traitsIwEE7is_openEv@@GLIBCXX_3.4.5 GLIBCXX_3.4.17 GLIBCXX_3.4.4 _ZNKSs15_M_check_lengthEmmPKc@@GLIBCXX_3.4.5 _ZNKSt14basic_ifstreamIwSt11char_traitsIwEE7is_openEv@GLIBCXX_3.4 _ZNSs4_Rep26_M_set_length_and_sharableEm@@GLIBCXX_3.4.5 GLIBCXX_3.4.26 _ZNKSs11_M_disjunctEPKc@GLIBCXX_3.4 确实,最高版本是GLIBCXX_3.4.29。\n(这里为了方便,我用sudo su -切换为了root)\n看一下libstdc++.so.6这个文件的信息:\n(base) root@aoyuSurface:~# ls -l /home/aoyu/AoyuCondaEnv/alien_invasion/lib/libstdc++.so.6 lrwxrwxrwx 1 aoyu aoyu 19 1月 26 13:25 /home/aoyu/AoyuCondaEnv/alien_invasion/lib/libstdc++.so.6 -\u0026gt; libstdc++.so.6.0.29 (base) root@aoyuSurface:~# ls -l /home/aoyu/AoyuCondaEnv/alien_invasion/lib/libstdc++.so.6.0.29 -rwxrwxr-x 3 aoyu aoyu 17981480 6月 1 2022 /home/aoyu/AoyuCondaEnv/alien_invasion/lib/libstdc++.so.6.0.29 是一个指向libstdc++.so.6.0.29的链接。\n找找电脑里有没有libstdc++.so新一点的版本:\n(base) root@aoyuSurface:~# find / -name \u0026#34;libstdc++.so*\u0026#34; /opt/anaconda3/lib/libstdc++.so /opt/anaconda3/lib/libstdc++.so.6.0.29 /opt/anaconda3/lib/libstdc++.so.6 /opt/anaconda3/pkgs/libstdcxx-ng-11.2.0-h1234567_1/lib/libstdc++.so /opt/anaconda3/pkgs/libstdcxx-ng-11.2.0-h1234567_1/lib/libstdc++.so.6.0.29 /opt/anaconda3/pkgs/libstdcxx-ng-11.2.0-h1234567_1/lib/libstdc++.so.6 /home/aoyu/.conda/pkgs/libstdcxx-ng-11.2.0-h1234567_1/lib/libstdc++.so /home/aoyu/.conda/pkgs/libstdcxx-ng-11.2.0-h1234567_1/lib/libstdc++.so.6.0.29 /home/aoyu/.conda/pkgs/libstdcxx-ng-11.2.0-h1234567_1/lib/libstdc++.so.6 /home/aoyu/AoyuCondaEnv/alien_invasion/lib/libstdc++.so /home/aoyu/AoyuCondaEnv/alien_invasion/lib/libstdc++.so.6.0.29 /home/aoyu/AoyuCondaEnv/alien_invasion/lib/libstdc++.so.6 find: ‘/run/user/1000/doc’: 权限不够 find: ‘/run/user/1000/gvfs’: 权限不够 /usr/lib/gcc/x86_64-linux-gnu/12/libstdc++.so /usr/lib/x86_64-linux-gnu/libstdc++.so.6 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30 /usr/share/gdb/auto-load/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30-gdb.py /snap/core20/1778/usr/lib/x86_64-linux-gnu/libstdc++.so.6 /snap/core20/1778/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28 /snap/core20/1778/usr/share/gdb/auto-load/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28-gdb.py /snap/core20/1623/usr/lib/x86_64-linux-gnu/libstdc++.so.6 /snap/core20/1623/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28 /snap/core20/1623/usr/share/gdb/auto-load/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28-gdb.py (base) root@aoyuSurface:~# 看到最新的版本是/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30。\n看一下里面有没有GLIBCXX_3.4.30:\n(base) root@aoyuSurface:~# strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30 | grep GLIBCXX GLIBCXX_3.4 GLIBCXX_3.4.1 GLIBCXX_3.4.2 GLIBCXX_3.4.3 GLIBCXX_3.4.4 GLIBCXX_3.4.5 GLIBCXX_3.4.6 GLIBCXX_3.4.7 GLIBCXX_3.4.8 GLIBCXX_3.4.9 GLIBCXX_3.4.10 GLIBCXX_3.4.11 GLIBCXX_3.4.12 GLIBCXX_3.4.13 GLIBCXX_3.4.14 GLIBCXX_3.4.15 GLIBCXX_3.4.16 GLIBCXX_3.4.17 GLIBCXX_3.4.18 GLIBCXX_3.4.19 GLIBCXX_3.4.20 GLIBCXX_3.4.21 GLIBCXX_3.4.22 GLIBCXX_3.4.23 GLIBCXX_3.4.24 GLIBCXX_3.4.25 GLIBCXX_3.4.26 GLIBCXX_3.4.27 GLIBCXX_3.4.28 GLIBCXX_3.4.29 GLIBCXX_3.4.30 GLIBCXX_DEBUG_MESSAGE_LENGTH 可以看到里面有GLIBCXX_3.4.30,那接下来就用这个libstdc++.so替换旧的:\n(base) root@aoyuSurface:~# cp /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30 /home/aoyu/AoyuCondaEnv/alien_invasion/lib/ (base) root@aoyuSurface:~# rm /home/aoyu/AoyuCondaEnv/alien_invasion/lib/libstdc++.so.6.0.29 (base) root@aoyuSurface:~# rm /home/aoyu/AoyuCondaEnv/alien_invasion/lib/libstdc++.so.6 (base) root@aoyuSurface:~# ln -s /home/aoyu/AoyuCondaEnv/alien_invasion/lib/libstdc++.so.6.0.30 /home/aoyu/AoyuCondaEnv/alien_invasion/lib/libstdc++.so.6 来确认一下替换libstdc++.so.6是不是成功了:\n(base) root@aoyuSurface:~# strings /home/aoyu/AoyuCondaEnv/alien_invasion/lib/libstdc++.so.6 | grep GLIBCXX GLIBCXX_3.4 GLIBCXX_3.4.1 GLIBCXX_3.4.2 GLIBCXX_3.4.3 GLIBCXX_3.4.4 GLIBCXX_3.4.5 GLIBCXX_3.4.6 GLIBCXX_3.4.7 GLIBCXX_3.4.8 GLIBCXX_3.4.9 GLIBCXX_3.4.10 GLIBCXX_3.4.11 GLIBCXX_3.4.12 GLIBCXX_3.4.13 GLIBCXX_3.4.14 GLIBCXX_3.4.15 GLIBCXX_3.4.16 GLIBCXX_3.4.17 GLIBCXX_3.4.18 GLIBCXX_3.4.19 GLIBCXX_3.4.20 GLIBCXX_3.4.21 GLIBCXX_3.4.22 GLIBCXX_3.4.23 GLIBCXX_3.4.24 GLIBCXX_3.4.25 GLIBCXX_3.4.26 GLIBCXX_3.4.27 GLIBCXX_3.4.28 GLIBCXX_3.4.29 GLIBCXX_3.4.30 GLIBCXX_DEBUG_MESSAGE_LENGTH 里面含有GLIBCXX_3.4.30,确实成功了。\n运行程序试试,程序成功运行。\n参考资料 conda + openai-gym + 核显 报错:libGL error: MESA-LOADER: failed to open iris\nAnaconda libstdc++.so.6: version `GLIBCXX_3.4.20\u0026rsquo; not found\nhttps://www.cnblogs.com/klb561/p/10765464.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.runoob.com/linux/linux-comm-grep.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"Jan 26","permalink":"https://o5o.me/post/python_libgl_error_driver_iris_swrast/","tags":null,"title":"Python报错libGL error: failed to load driver"},{"categories":null,"contents":"搜狗输入法基于fcitx。所有的JetBrains的IDE对所有的fcitx输入法都有这个问题。\n解决方法,从这里下载文件直接替换PyCharm安装路径里的jbr目录(我的在/opt/pycharm-community-2022.3.1/jbr/)。\n经测试有效。不过候选框会出现在光标的上方,而不是光标下方,还行,能用。\n","date":"Jan 26","permalink":"https://o5o.me/post/pycharm_fcitx_sogou_not_follow_cursor/","tags":null,"title":"Pycharm内搜狗输入法候选框不跟随光标"},{"categories":["SRS"],"contents":"首先在目录树(Alt+C)中移动卡片分支到正确的概念组下面。\n接下来设定Reference信息中的“Concept group”为正确信息:\n在卡片分支上点鼠标右键,“处理选中分支”,“设置概念分组”,在弹出的窗口中,在窗口左侧给正确的概念组打对钩,在窗口下方那一排按钮中点击“确定”。\n完成。\n","date":"Jan 24","permalink":"https://o5o.me/post/supermemo_wrong_branch/","tags":["SuperMemo"],"title":"Supermemo将内容导入到了错误的概念组怎么办"},{"categories":["Python"],"contents":"在PyCharm中,快捷键Ctrl+Alt+T的作用是“环绕方式\u0026hellip;,使用一些模板代码环绕所选代码段”。\n而在Ubuntu中,快捷键Ctrl+Alt+T的作用是“打开终端”。\n解决冲突的方式是修改二者其中之一的快捷键。\n修改PyCharm的这一个快捷键:文件-设置-\u0026gt;按键映射-搜索“环绕”-在快捷键那一项上点鼠标右键-移除Ctrl+Alt+T-添加键盘快捷键。\n修改Ubuntu(22.10)的这一个快捷键:设置-\u0026gt;键盘-\u0026gt;键盘快捷键-查看及自定义快捷键-\u0026gt;在搜索框中搜索“ctrl+”找到快捷键“Ctrl+Alt+T”-在那一项上面单击鼠标左键-在键盘上输入以设定新快捷键。\n我的选择是修改Ubuntu的这一个快捷键,因为我截止目前还没养成用快捷键打开终端的习惯。\n","date":"Jan 24","permalink":"https://o5o.me/post/pycharm_ubuntu_shortcut_conflict/","tags":null,"title":"Pycharm和Ubuntu快捷键冲突解决"},{"categories":["SRS"],"contents":"标题中的“新卡片”指的是在SuperMemo的“目录窗口(Contents Window)”中被标记为“浅蓝色”的卡片。\n我想要学习 Advanced English 2018 牌组包里的“Grammar”分支下的卡片,这个牌组包里的“新卡片”非常非常多,有几万张,包含单词、语法、发音、拼写等等,如果按默认复习队列一锅粥地学,很不系统,因此我才想着先学Grammar语法部分。\n一番尝试及朋友推荐,得到下面几种实现的方法。方法1就不推荐了,方法2和方法3按需取用。我选择的是方法2。\n方法1 一番尝试及在朋友帮助下,目前找到的可行的方法是将“新卡片”(浅蓝色)变为“已记”(问答卡片,深蓝色)。\n操作过程:打开“目录窗口”(Alt+C) -\u0026gt; 展开至想要学习的分支 -\u0026gt; 在那个分支标题上点鼠标右键 -\u0026gt; “处理选中分支(Process branch)” -\u0026gt; “已记()”。在弹出的新窗口中(Which interval should be chosen in scheduling)选~~“One day interval”,那个分支下的卡片就会在一天内出现~~(我这里的理解可能是错误的)。\n总结:把未学的卡片设置为已学过的卡片,进入复习队列。\n方法2 由于我想要学的AdvEng2018是一个单独的kno文件,我把想要学的部分卡片从里面导出到另一个kno文件(我日常学习用的)里面,那也就实现了本文标题所称的效果。\n操作过程:打开“目录窗口”(Alt+C) -\u0026gt; 展开至想要学习的分支 -\u0026gt; 在那个分支标题上点鼠标右键 -\u0026gt; “导出(export)” -\u0026gt; “Transfer elements Shift+Ctrl+T” -\u0026gt; 在弹出的窗口中选想要导入的那个kno文件 -\u0026gt; 问你 Do you want to transfer elements? 选yes -\u0026gt; 问你 Do you want to integrate all files with the target file system? 选yes。这样就导入成功了。\n总结:把某一个“牌组”下的部分卡片导入到另一个“牌组”当中。\n方法3 思路:SuperMemo的作用是给一张张卡片排序形成“队列”,按排定的顺序将卡片推送给用户进行学习。那么我们只要将某一分支下的卡片的顺序调整到“队列”前面,就达到了学习某一分支下的卡片的目的。\n这个答案(如何在 SuperMemo 中复习/学习指定的folder/branch? - 叶峻峣的回答 - 知乎)符合上面的思路,但有更好的方法:\n操作过程:打开“目录窗口”(Alt+C) -\u0026gt; 展开至想要学习的分支 -\u0026gt; 在那个分支标题上点鼠标右键 -\u0026gt; “查看(view)”-“分支元素(Branch)”-\u0026gt;在弹出的窗口中点最上方工具栏第一个按钮 -\u0026gt; 在打开的菜单中点“工具(Tools)” -\u0026gt; “保存未学部分(Save pending)”。这样就调整好了。\n不信?在SuperMemo主菜单中点“查看(View)”- \u0026ldquo;查看其他(Other)\u0026rdquo; - “尚未学习(Pending)”,在弹出的窗口中可以看到,你选定的那一个分支下的卡片,都跑到了队列的前面(队列第一张可能还是原来的,不过无伤大雅)。\n其他 FAQ: Advanced English with SuperMemo (super-memory.com)记录了一个官方的操作方法,但经我测试,在我使用的SuperMemo版本(18.05)上无法实现想要的效果,学了一张新卡片后,后续显示的是仍是正常学习队列的卡片,而不是选定分支下的后续卡片。\n","date":"Jan 24","permalink":"https://o5o.me/post/supermemo_learn_new_elements_that_belong_to_a_given_branch/","tags":["SuperMemo"],"title":"Supermemo学习某分支下的新卡片"},{"categories":["Python"],"contents":"在PyCharm的代码编辑区域显示一条竖线,提醒自己每行的代码尽量不要超过80字符。这是PEP8中有关行长的指南。\n文件-设置-\u0026gt;编辑器-代码样式-\u0026gt;常规-“视觉参考线”,设为80, 120,然后保存。在编辑代码时,右侧就会显示两条竖线,一个在第80个字符的位置,一个在第120个字符的位置。\n可以勾选“强制换行位置”(默认为120字符)后的“键入时换行”,这样,当行长到达120字符时就会强制换行。\n","date":"Jan 22","permalink":"https://o5o.me/post/pycharm_pep8_code_editor_vertical_line/","tags":null,"title":"在Pycharm的代码编辑区显示一条竖线提醒换行"},{"categories":["Python"],"contents":"主要是练习列表解析式。以下练习均使用列表解析式完成。\n练习4.3 数到20,使用一个for循环打印数1~20(含)。\n1 print([value for value in range(1,21)]) 练习4.4 一百万\n创建一个包含数 1~1 000 000 的列表,再使用一个 for 循环将这些数打印出来。(如果输出的时间太长,按 Ctrl + C 停止输出或关闭输出窗口。)\nprint([value for value in range(1,1_000_001)]) 不要尝试去运行上面的代码,能理解即可。\n练习4.5 一百万求和\n创建一个包含数 1~1 000 000的列表,再使用 min()和 max() 核实该列表确实是从 1 开始、到 1 000 000 结束的。另外,对这个列表调用函数 sum(), 看看 Python 将一百万个数相加需要多长时间。\n1 2 3 4 list1 = [i for i in range(1,1_000_001)] print(min(list1)) print(max(list1)) print(sum(list1)) 练习4.6 奇数\n通过给函数 range() 指定第三个参数来创建一个列表,其中包含 1~20 的奇数,再使用一个 for 循环将这些数打印出来。\n1 2 3 4 list1 = [i for i in range(1,20,2)] for i in list1: print(i) 练习4.7 3 的倍数\n创建一个列表,其中包含 3~30 能被 3 整除的数,再使用一 个 for 循环将这个列表中的数打印出来。\n1 2 3 4 list1 = [i for i in range(3,31,3)] for i in list1: print(i) 练习4.8 立方\n将同一个数乘三次称为立方。例如,在 Python 中,2 的立方用 2**3 表示。请创建一个列表,其中包含前 10 个整数(1~10)的立方,再使用一个 for 循环将这些立方数打印出来。\n1 2 3 4 list1 = [value**3 for value in range(1,11)] for i in list1: print(i) 练习4.9 立方解析\n使用列表解析生成一个列表,其中包含前 10 个整数的立方。\n1 list1 = [value**3 for value in range(1,11)] 和练习4.8相同。\n","date":"Jan 22","permalink":"https://o5o.me/post/python_crash_course_4/","tags":null,"title":"Python Crash Course 4"},{"categories":null,"contents":"我今天新装了ubuntu,把hugo博客仓库clone到本地,运行hugo server后,终端里显示貌似一切正常,但打开http://localhost:1313却是一片空白。\n原因是我只把博客内容克隆了下来,但子模块却忘记了克隆。\n补救措施:在仓库里运行git submodule update --init即可。\n由于我本地的public目录还是空的,所以我需要运行hugo server --disableFastRender,然后就一切正常了。\n参考:Git 工具 - 子模块\n","date":"Jan 18","permalink":"https://o5o.me/post/hugo_server_localhost_page_empty/","tags":["hugo"],"title":"本地运行hugo server时页面空白"},{"categories":null,"contents":"原因是我没有把anaconda安装在默认位置(/home目录里),而是装在了/opt目录里,我以普通用户的身份运行PyCharm,没有权限访问/opt目录,所以有了这个错误提示。\n解决办法:在/home/用户目录里我建了一个文件夹来放conda环境。\n","date":"Jan 18","permalink":"https://o5o.me/post/pycharm_condaerror_anaconda/","tags":["Anaconda"],"title":"PyCharm创建conda项目时提示CondaError: Unable to create prefix directory"},{"categories":["Python"],"contents":"今天写的程序运行时提示:AttributeError: 'BaiduImageSpider' object has no attribute 'url',让我百思不得其解。\n最后发现是我把__init__写成了__int__,DeBug也没发现这个错误。\n","date":"Jan 17","permalink":"https://o5o.me/post/python_attributeerror_object_has_no_attribute/","tags":null,"title":"AttributeError: object has no attribute"},{"categories":null,"contents":"我觉得我遇到了 Baader-Meinhof 现象1。\n今天在写爬虫的时候,突然想到,如果Chrome的网页源代码页面的代码能自动换行就好了。我就去网上搜有没有方法能让源代码自动换行显示。\n一番搜寻没有结果,然后我就打算放弃,然后我不经意往一个已经打开的网页源代码页面一瞟,赫然有一个“自动换行”复选框静静显示在那里。\n天地良心,我以前真的是没有注意到过那里还有那么一个按钮,而现在它却那么显眼。\nFrequency illusion - Wikipedia\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"Jan 13","permalink":"https://o5o.me/post/chrome_view_source/","tags":["Chrome"],"title":"Chrome网页源代码自动换行"},{"categories":null,"contents":"写了一个简单的爬虫程序,从百度图片下载图片,但运行时报错:UnicodeEncodeError: 'latin-1' codec can't encode characters in position 41-44: ordinal not in range(256)\n程序反反复复检查了好几遍,最终确定是cookie的问题。\n我是从自己的 Chrome 浏览器里直接拷贝的cookie值粘贴到程序里的,没注意从Chrome直接复制的cookie会先解码再给你。就是说,程序里需要的cookie是不带中文的(中文先进行编码,比如把古力娜扎编码为%E5%8F%A4%E5%8A%9B%E5%A8%9C%E6%89%8E),但从Chrome复制出来的cookie带中文。\nChrome这是好心办坏事吗。\n","date":"Jan 13","permalink":"https://o5o.me/post/spider_unicodeencodeerror_latin-1/","tags":["爬虫"],"title":"爬虫运行报错:UnicodeEncodeError: 'latin-1' codec can't encode characters"},{"categories":["Python"],"contents":"注意:请使用我们目前学过的语句和特性来完成本题。\n编写一个能画出如下网格 (grid) 的函数: + - - - - + - - - - + | | | | | | | | | | | | + - - - - + - - - - + | | | | | | | | | | | | + - - - - + - - - - + 提示:你可以使用一个用逗号分隔的值序列,在一行中打印出多个值:\nprint(\u0026#39;+\u0026#39;, \u0026#39;−\u0026#39;) print 函数默认会自动换行,但是你可以阻止这个行为,只需要像下面这样将行结尾变成一个空格:\nprint(\u0026#39;+\u0026#39;, end=\u0026#39; \u0026#39;) print(\u0026#39;−\u0026#39;) 这两个语句的输出结果是+ −。\n一个没有传入实参的print 语句会结束当前行,跳到下一行。\n编写一个能够画出四行四列的类似网格的函数。 致谢: 这个习题基于 Practical C Programming, Third Edition 一书中的习题改编,该书由 O’Reilly 出版社于 1997 年出版。\n第一小题\n一行行来写\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def paintgrid2x2(): print(\u0026#34;+\u0026#34;,\u0026#34;- \u0026#34;*4,end=\u0026#34;\u0026#34;) print(\u0026#34;+\u0026#34;,\u0026#34;- \u0026#34;*4,end=\u0026#34;+\\n\u0026#34;) print(\u0026#34;|\u0026#34;,\u0026#34; \u0026#34;*7,\u0026#34;|\u0026#34;, \u0026#34; \u0026#34;*7,\u0026#34;|\u0026#34;) print(\u0026#34;|\u0026#34;,\u0026#34; \u0026#34;*7,\u0026#34;|\u0026#34;, \u0026#34; \u0026#34;*7,\u0026#34;|\u0026#34;) print(\u0026#34;|\u0026#34;,\u0026#34; \u0026#34;*7,\u0026#34;|\u0026#34;, \u0026#34; \u0026#34;*7,\u0026#34;|\u0026#34;) print(\u0026#34;|\u0026#34;,\u0026#34; \u0026#34;*7,\u0026#34;|\u0026#34;, \u0026#34; \u0026#34;*7,\u0026#34;|\u0026#34;) print(\u0026#34;+\u0026#34;,\u0026#34;- \u0026#34;*4,end=\u0026#34;\u0026#34;) print(\u0026#34;+\u0026#34;,\u0026#34;- \u0026#34;*4,end=\u0026#34;+\\n\u0026#34;) print(\u0026#34;|\u0026#34;,\u0026#34; \u0026#34;*7,\u0026#34;|\u0026#34;, \u0026#34; \u0026#34;*7,\u0026#34;|\u0026#34;) print(\u0026#34;|\u0026#34;,\u0026#34; \u0026#34;*7,\u0026#34;|\u0026#34;, \u0026#34; \u0026#34;*7,\u0026#34;|\u0026#34;) print(\u0026#34;|\u0026#34;,\u0026#34; \u0026#34;*7,\u0026#34;|\u0026#34;, \u0026#34; \u0026#34;*7,\u0026#34;|\u0026#34;) print(\u0026#34;|\u0026#34;,\u0026#34; \u0026#34;*7,\u0026#34;|\u0026#34;, \u0026#34; \u0026#34;*7,\u0026#34;|\u0026#34;) print(\u0026#34;+\u0026#34;,\u0026#34;- \u0026#34;*4,end=\u0026#34;\u0026#34;) print(\u0026#34;+\u0026#34;,\u0026#34;- \u0026#34;*4,end=\u0026#34;+\\n\u0026#34;) paintgrid2x2() 第二小题\n用了for循环,一行行来写太多了。\n1 2 3 4 5 6 7 8 9 10 11 12 13 def paintgrid3x3(): for j in range(3): print(\u0026#34;+\u0026#34;,\u0026#34;- \u0026#34;*4,end=\u0026#34;\u0026#34;) print(\u0026#34;+\u0026#34;,\u0026#34;- \u0026#34;*4,end=\u0026#34;\u0026#34;) print(\u0026#34;+\u0026#34;,\u0026#34;- \u0026#34;*4,end=\u0026#34;+\\n\u0026#34;) for i in range(4): print(\u0026#34;|\u0026#34;,\u0026#34; \u0026#34;*8,end=\u0026#34;\u0026#34;) print(\u0026#34;|\u0026#34;,\u0026#34; \u0026#34;*8,end=\u0026#34;\u0026#34;) print(\u0026#34;|\u0026#34;,\u0026#34; \u0026#34;*8,end=\u0026#34;|\\n\u0026#34;) print(\u0026#34;+\u0026#34;,\u0026#34;- \u0026#34;*4,end=\u0026#34;\u0026#34;) print(\u0026#34;+\u0026#34;,\u0026#34;- \u0026#34;*4,end=\u0026#34;\u0026#34;) print(\u0026#34;+\u0026#34;,\u0026#34;- \u0026#34;*4,end=\u0026#34;+\\n\u0026#34;) paintgrid3x3() 效果:\n+ - - - - + - - - - + - - - - + | | | | | | | | | | | | | | | | + - - - - + - - - - + - - - - + | | | | | | | | | | | | | | | | + - - - - + - - - - + - - - - + | | | | | | | | | | | | | | | | + - - - - + - - - - + - - - - + ","date":"Jan 10","permalink":"https://o5o.me/post/think_python_exercise_3.3/","tags":null,"title":"Think Python Exercise 3.3"},{"categories":["Python"],"contents":"函数对象是一个可以赋值给变量的值, 也可以作为实参传递。 例如, do_twice 函数接受函数对象作为实参,并调用这个函数对象两次:\ndef do_twice(f): f() f() 下面这个示例使用 do_twice 来调用名为 print_spam 的函数两次。\ndef print_spam (): print(\u0026#39;spam \u0026#39;) do_twice(print_spam) 将这个示例写入脚本,并测试。 修改do_twice,使其接受两个实参,一个是函数对象,另一个是值。然后调用这一函数对象两次,将那个值传递给函数对象作为实参。 从本章前面一些的示例中, 将 print_twice 函数的定义复制到脚本中。 使用修改过的 do_twice ,调用print_twice 两次,将 spam 传递给它作为实参。 定义一个名为 do_four 的新函数,其接受一个函数对象和一个值作为实参。调用这个函数对象四次,将那个值作为形参传递给它。函数体中应该只有两条语句,而不是四条。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 def do_twice(f, argus): f(argus) f(argus) def print_twice(bruce): print(bruce) print(bruce) def do_four(f, argus): do_twice(f, argus) do_twice(f, argus) do_twice(print_twice, \u0026#39;spam\u0026#39;) #do_four(print_twice, \u0026#34;spam\u0026#34;) ","date":"Jan 10","permalink":"https://o5o.me/post/think_python_exercise_3.2/","tags":null,"title":"Think Python Exercise 3.2"},{"categories":["Python"],"contents":"编写一个名为 right_justify 的函数, 函数接受一个名为 s 的字符串作为形参, 并在打印足够多的前导空格 (leading space) 之后打印这个字符串,使得字符串的最后一个字母位于显示屏的第 70 列。\n提示: 使用字符串拼接 (string concatenation) 和重复。 另外, Python 提供了一个名叫len 的内建函数,可以返回一个字符串的长度,因此len(\u0026lsquo;allen\u0026rsquo;) 的值是 5。\n1 2 3 4 5 6 7 def right_justify(s): print(\u0026#39; \u0026#39;*(70-len(s)),s) s1 = \u0026#34;good morning\u0026#34; s2 = \u0026#34;nice to meet you\u0026#34; right_justify(s1) right_justify(s2) ","date":"Jan 10","permalink":"https://o5o.me/post/think_python_exercise_3.1/","tags":null,"title":"Think Python Exercise 3.1"},{"categories":["Python"],"contents":"这道习题中包含了 Python 中最常见、最难找出来的错误。编写一个叫 Kangaroo 的类,包含以下方法:\n一个__init__ 方法,初始化一个叫 pounch_contents 的属性为空列表。 一个叫put_in_pounch 的方法,将一个任意类型的对象加入pounch_contents。 一个__str__ 方法,返回 Kangaroo 对象的字符串表示和 pounch 中的内容。 创建两个 Kangaroo 对象, 将它们命名为 kanga 和 roo , 然后将 roo 加入 kanga 的 pounch 列表, 以此测试你写的代码。\n这是我在不完全理解题目时写的代码(请不要参考):\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Kangaroo: def __init__(self): self.pounch_contents = [] def put_in_pounch(self, ayobj): self.pounch_contents.append(ayobj) def __str__(self): return str(self.pounch_contents) kanga = Kangaroo() roo = Kangaroo() kanga.put_in_pounch(roo) print(kanga) 其实这是一个找bug的练习。作者想让我们使用pylint这个工具找出他的代码里的bug。\n作者的代码(含bug):\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 \u0026#34;\u0026#34;\u0026#34;This module contains a code example related to Think Python, 2nd Edition by Allen Downey http://thinkpython2.com Copyright 2015 Allen Downey License: http://creativecommons.org/licenses/by/4.0/ \u0026#34;\u0026#34;\u0026#34; from __future__ import print_function, division \u0026#34;\u0026#34;\u0026#34; WARNING: this program contains a NASTY bug. I put it there on purpose as a debugging exercise, but you DO NOT want to emulate this example! \u0026#34;\u0026#34;\u0026#34; class Kangaroo: \u0026#34;\u0026#34;\u0026#34;A Kangaroo is a marsupial.\u0026#34;\u0026#34;\u0026#34; def __init__(self, name, contents=[]): \u0026#34;\u0026#34;\u0026#34;Initialize the pouch contents. name: string contents: initial pouch contents. \u0026#34;\u0026#34;\u0026#34; self.name = name self.pouch_contents = contents def __str__(self): \u0026#34;\u0026#34;\u0026#34;Return a string representaion of this Kangaroo. \u0026#34;\u0026#34;\u0026#34; t = [ self.name + \u0026#39; has pouch contents:\u0026#39; ] for obj in self.pouch_contents: s = \u0026#39; \u0026#39; + object.__str__(obj) t.append(s) return \u0026#39;\\n\u0026#39;.join(t) def put_in_pouch(self, item): \u0026#34;\u0026#34;\u0026#34;Adds a new item to the pouch contents. item: object to be added \u0026#34;\u0026#34;\u0026#34; self.pouch_contents.append(item) kanga = Kangaroo(\u0026#39;Kanga\u0026#39;) roo = Kangaroo(\u0026#39;Roo\u0026#39;) kanga.put_in_pouch(\u0026#39;wallet\u0026#39;) kanga.put_in_pouch(\u0026#39;car keys\u0026#39;) kanga.put_in_pouch(roo) print(kanga) # If you run this program as is, it seems to work. # To see the problem, trying printing roo. # Hint: to find the problem try running pylint. 用pylint分析一番:\n% pylint /Users/aoyu/Downloads/ThinkPython2-master/code/BadKangaroo.py ************* Module BadKangaroo Downloads/ThinkPython2-master/code/BadKangaroo.py:24:0: C0303: Trailing whitespace (trailing-whitespace) Downloads/ThinkPython2-master/code/BadKangaroo.py:1:0: C0103: Module name \u0026#34;BadKangaroo\u0026#34; doesn\u0026#39;t conform to snake_case naming style (invalid-name) Downloads/ThinkPython2-master/code/BadKangaroo.py:14:0: W0105: String statement has no effect (pointless-string-statement) Downloads/ThinkPython2-master/code/BadKangaroo.py:25:4: W0102: Dangerous default value [] as argument (dangerous-default-value) Downloads/ThinkPython2-master/code/BadKangaroo.py:37:8: C0103: Variable name \u0026#34;t\u0026#34; doesn\u0026#39;t conform to snake_case naming style (invalid-name) Downloads/ThinkPython2-master/code/BadKangaroo.py:39:12: C0103: Variable name \u0026#34;s\u0026#34; doesn\u0026#39;t conform to snake_case naming style (invalid-name) ------------------------------------------------------------------ Your code has been rated at 7.00/10 (previous run: 7.00/10, +0.00) 其他的都是小毛病,关键在于:\nDownloads/ThinkPython2-master/code/BadKangaroo.py:25:4: W0102: Dangerous default value [] as argument (dangerous-default-value) 说把空列表作为参数的默认值,很危险。怎么就危险了呢?看一个简单的例子就明白了:\n\u0026gt;\u0026gt;\u0026gt; a = [] \u0026gt;\u0026gt;\u0026gt; b = a \u0026gt;\u0026gt;\u0026gt; c = a \u0026gt;\u0026gt;\u0026gt; b.append(\u0026#39;a\u0026#39;) \u0026gt;\u0026gt;\u0026gt; a [\u0026#39;a\u0026#39;] \u0026gt;\u0026gt;\u0026gt; b [\u0026#39;a\u0026#39;] \u0026gt;\u0026gt;\u0026gt; c [\u0026#39;a\u0026#39;] a, b, c三个变量都是空列表的引用,其中一个改变,其他的变量也跟着改变。\n作者的代码的bug也是同理。每一个 Kangaroo 对象的 pouch_contents 属性的默认值都是一个空列表的引用,一个对象的 pouch_contents 改变,其他对象的 pouch_contents 也跟着改变。\n作者的解决办法:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 \u0026#34;\u0026#34;\u0026#34;This module contains a code example related to Think Python, 2nd Edition by Allen Downey http://thinkpython2.com Copyright 2015 Allen Downey License: http://creativecommons.org/licenses/by/4.0/ \u0026#34;\u0026#34;\u0026#34; from __future__ import print_function, division \u0026#34;\u0026#34;\u0026#34; WARNING: this program contains a NASTY bug. I put it there on purpose as a debugging exercise, but you DO NOT want to emulate this example! \u0026#34;\u0026#34;\u0026#34; class Kangaroo: \u0026#34;\u0026#34;\u0026#34;A Kangaroo is a marsupial.\u0026#34;\u0026#34;\u0026#34; def __init__(self, name, contents=[]): \u0026#34;\u0026#34;\u0026#34;Initialize the pouch contents. name: string contents: initial pouch contents. \u0026#34;\u0026#34;\u0026#34; # The problem is the default value for contents. # Default values get evaluated ONCE, when the function # is defined; they don\u0026#39;t get evaluated again when the # function is called. # In this case that means that when __init__ is defined, # [] gets evaluated and contents gets a reference to # an empty list. # After that, every Kangaroo that gets the default # value gets a reference to THE SAME list. If any # Kangaroo modifies this shared list, they all see # the change. # The next version of __init__ shows an idiomatic way # to avoid this problem. self.name = name self.pouch_contents = contents def __init__(self, name, contents=None): \u0026#34;\u0026#34;\u0026#34;Initialize the pouch contents. name: string contents: initial pouch contents. \u0026#34;\u0026#34;\u0026#34; # In this version, the default value is None. When # __init__ runs, it checks the value of contents and, # if necessary, creates a new empty list. That way, # every Kangaroo that gets the default value gets a # reference to a different list. # As a general rule, you should avoid using a mutable # object as a default value, unless you really know # what you are doing. self.name = name if contents == None: contents = [] self.pouch_contents = contents def __str__(self): \u0026#34;\u0026#34;\u0026#34;Return a string representaion of this Kangaroo. \u0026#34;\u0026#34;\u0026#34; t = [ self.name + \u0026#39; has pouch contents:\u0026#39; ] for obj in self.pouch_contents: s = \u0026#39; \u0026#39; + object.__str__(obj) t.append(s) return \u0026#39;\\n\u0026#39;.join(t) def put_in_pouch(self, item): \u0026#34;\u0026#34;\u0026#34;Adds a new item to the pouch contents. item: object to be added \u0026#34;\u0026#34;\u0026#34; self.pouch_contents.append(item) kanga = Kangaroo(\u0026#39;Kanga\u0026#39;) roo = Kangaroo(\u0026#39;Roo\u0026#39;) kanga.put_in_pouch(\u0026#39;wallet\u0026#39;) kanga.put_in_pouch(\u0026#39;car keys\u0026#39;) kanga.put_in_pouch(roo) print(kanga) print(roo) # If you run this program as is, it seems to work. # To see the problem, trying printing roo. 代码里面的object.__str__(obj)怎样理解?\n返回它的参数obj所指的对象的字符串表现形式。\n举例:\n\u0026gt;\u0026gt;\u0026gt; a = \u0026#39;abc\u0026#39; \u0026gt;\u0026gt;\u0026gt; object.__str__(a) \u0026#34;\u0026#39;abc\u0026#39;\u0026#34; \u0026gt;\u0026gt;\u0026gt; print(a) abc \u0026gt;\u0026gt;\u0026gt; b = 123 \u0026gt;\u0026gt;\u0026gt; object.__str__(b) \u0026#39;123\u0026#39; \u0026gt;\u0026gt;\u0026gt; print(b) 123 \u0026gt;\u0026gt;\u0026gt; class Point: ... \u0026#34;\u0026#34;\u0026#34;test\u0026#34;\u0026#34;\u0026#34; ... \u0026gt;\u0026gt;\u0026gt; object.__str__(a) \u0026#39;\u0026lt;__main__.Point object at 0x7f7db642b700\u0026gt;\u0026#39; ","date":"Jan 10","permalink":"https://o5o.me/post/think_python_exercise_17.2/","tags":null,"title":"Think Python Exercise 17.2"},{"categories":["Python"],"contents":"我们做个练习, 将 time_to_int (见 16.4 节) 重写为方法。你或许也想将 int_to_time 改写为方法,但是那样做并没有什么意义,因为没有调用它的对象。\n原 int_to_time 函数见练习16.0。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Time: \u0026#34;\u0026#34;\u0026#34;Represents the time of day.\u0026#34;\u0026#34;\u0026#34; def print_time(self): print(\u0026#34;%.2d:%.2d:%.2d\u0026#34; % (self.hour, self.minute, self.second)) def time_to_int(self): minutes = self.hour * 60 + self.minute seconds = minutes * 60 + self.second return seconds start = Time() start.hour = 9 start.minute = 45 start.second = 20 start.time_to_int() 我们做个练习,为 Point 类写一个 init 方法,使用 x 和 y 作为可选参数,然后赋值给对应的属性。\n原相关代码见练习15.0。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Point: \u0026#34;\u0026#34;\u0026#34;表示一个二维的点 \u0026#34;\u0026#34;\u0026#34; def __init__(self, x=0, y=0): self.x = x self.y = y def print_point(self): print(\u0026#39;(%g, %g)\u0026#39; % (self.x, self.y)) Po1 = Point() Po2 = Point(100,75) Po1.print_point() Po2.print_point() 我们做个练习,为 Point 类写一个 str 方法。然后创建一个 Point 对象并打印。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Point: \u0026#34;\u0026#34;\u0026#34;表示一个二维的点 \u0026#34;\u0026#34;\u0026#34; def __init__(self, x=0, y=0): self.x = x self.y = y def __str__(self): return \u0026#39;(%g, %g)\u0026#39; % (self.x, self.y) Po1 = Point() Po2 = Point(100,75) print(Po1) print(Po2) 我们来做个练习,为 Point 类编写一个 add 方法。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Point: \u0026#34;\u0026#34;\u0026#34;表示一个二维的点 \u0026#34;\u0026#34;\u0026#34; def __init__(self, x=0, y=0): self.x = x self.y = y def __str__(self): return \u0026#39;(%g, %g)\u0026#39; % (self.x, self.y) def __add__(self, other): return Point(self.x+other.x, self.y+other.y) Po1 = Point(200,80) Po2 = Point(100,75) print(Po1+Po2) 我们做个练习,为 Points 编写一个 add 方法,使其既适用 Point 对象,也适用元组:\n如果第二个运算数是一个 Point , 该方法将返回一个新的 Point, x 坐标是两个运算数的 x 的和,y 以此类推。\n如果第二个运算数是一个元组,该方法将把元组的第一个元素与 x 相加,第二个元素与 y 相加,然后返回以相关结果为参数的新的 Point。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Point: \u0026#34;\u0026#34;\u0026#34;表示一个二维的点 \u0026#34;\u0026#34;\u0026#34; def __init__(self, x=0, y=0): self.x = x self.y = y def __str__(self): return \u0026#39;(%g, %g)\u0026#39; % (self.x, self.y) def __add__(self, other): if isinstance(other, Point): return Point(self.x+other.x, self.y+other.y) else: return Point(self.x+other[0], self.y+other[1]) Po1 = Point(200,80) Po2 = Point(100,75) print(Po1+Po2) print(Po1+(12,2)) ","date":"Jan 10","permalink":"https://o5o.me/post/think_python_exercise_17.0/","tags":null,"title":"Think Python Exercise 17.0"},{"categories":["Python"],"contents":"修改 Time 类的属性, 使用一个整数代表自午夜零点开始的秒数。然后修改类的方法 (和int_to_time 函数),使其适用于新的实现。你不用修改 main 函数中的测试代码。完成之后, 程序的输出应该和之前保持一致。\n分析:作者的意思是,Time 类的属性,修改后只有“秒”这一个(小时、分钟都没了),同时,修改后Time类接收的参数和返回的值都保持不变。\n作者提供的用于修改的代码:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 \u0026#34;\u0026#34;\u0026#34;This module contains a code example related to Think Python, 2nd Edition by Allen Downey http://thinkpython2.com Copyright 2015 Allen Downey License: http://creativecommons.org/licenses/by/4.0/ \u0026#34;\u0026#34;\u0026#34; from __future__ import print_function, division class Time: \u0026#34;\u0026#34;\u0026#34;Represents the time of day. attributes: hour, minute, second \u0026#34;\u0026#34;\u0026#34; def __init__(self, hour=0, minute=0, second=0): \u0026#34;\u0026#34;\u0026#34;Initializes a time object. hour: int minute: int second: int or float \u0026#34;\u0026#34;\u0026#34; self.hour = hour self.minute = minute self.second = second def __str__(self): \u0026#34;\u0026#34;\u0026#34;Returns a string representation of the time.\u0026#34;\u0026#34;\u0026#34; return \u0026#39;%.2d:%.2d:%.2d\u0026#39; % (self.hour, self.minute, self.second) def print_time(self): \u0026#34;\u0026#34;\u0026#34;Prints a string representation of the time.\u0026#34;\u0026#34;\u0026#34; print(str(self)) def time_to_int(self): \u0026#34;\u0026#34;\u0026#34;Computes the number of seconds since midnight.\u0026#34;\u0026#34;\u0026#34; minutes = self.hour * 60 + self.minute seconds = minutes * 60 + self.second return seconds def is_after(self, other): \u0026#34;\u0026#34;\u0026#34;Returns True if t1 is after t2; false otherwise.\u0026#34;\u0026#34;\u0026#34; return self.time_to_int() \u0026gt; other.time_to_int() def __add__(self, other): \u0026#34;\u0026#34;\u0026#34;Adds two Time objects or a Time object and a number. other: Time object or number of seconds \u0026#34;\u0026#34;\u0026#34; if isinstance(other, Time): return self.add_time(other) else: return self.increment(other) def __radd__(self, other): \u0026#34;\u0026#34;\u0026#34;Adds two Time objects or a Time object and a number.\u0026#34;\u0026#34;\u0026#34; return self.__add__(other) def add_time(self, other): \u0026#34;\u0026#34;\u0026#34;Adds two time objects.\u0026#34;\u0026#34;\u0026#34; assert self.is_valid() and other.is_valid() seconds = self.time_to_int() + other.time_to_int() return int_to_time(seconds) def increment(self, seconds): \u0026#34;\u0026#34;\u0026#34;Returns a new Time that is the sum of this time and seconds.\u0026#34;\u0026#34;\u0026#34; seconds += self.time_to_int() return int_to_time(seconds) def is_valid(self): \u0026#34;\u0026#34;\u0026#34;Checks whether a Time object satisfies the invariants.\u0026#34;\u0026#34;\u0026#34; if self.hour \u0026lt; 0 or self.minute \u0026lt; 0 or self.second \u0026lt; 0: return False if self.minute \u0026gt;= 60 or self.second \u0026gt;= 60: return False return True def int_to_time(seconds): \u0026#34;\u0026#34;\u0026#34;Makes a new Time object. seconds: int seconds since midnight. \u0026#34;\u0026#34;\u0026#34; minutes, second = divmod(seconds, 60) hour, minute = divmod(minutes, 60) time = Time(hour, minute, second) return time def main(): start = Time(9, 45, 00) start.print_time() end = start.increment(1337) #end = start.increment(1337, 460) end.print_time() print(\u0026#39;Is end after start?\u0026#39;) print(end.is_after(start)) print(\u0026#39;Using __str__\u0026#39;) print(start, end) start = Time(9, 45) duration = Time(1, 35) print(start + duration) print(start + 1337) print(1337 + start) print(\u0026#39;Example of polymorphism\u0026#39;) t1 = Time(7, 43) t2 = Time(7, 41) t3 = Time(7, 37) total = sum([t1, t2, t3]) print(total) if __name__ == \u0026#39;__main__\u0026#39;: main() 修改后:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 class Time: \u0026#34;\u0026#34;\u0026#34;Represents the time of day. attributes: second, 自午夜零点开始的秒数 \u0026#34;\u0026#34;\u0026#34; def __init__(self, hour=0, minute=0, second=0): minutes = hour * 60 + minute self.second = minutes * 60 + second def __str__(self): \u0026#34;\u0026#34;\u0026#34;Returns a string representation of the time.\u0026#34;\u0026#34;\u0026#34; minutes, sec = divmod(self.second, 60) hour, minute = divmod(minutes, 60) return \u0026#39;%.2d:%.2d:%.2d\u0026#39; % (hour, minute, sec) def print_time(self): \u0026#34;\u0026#34;\u0026#34;Prints a string representation of the time.\u0026#34;\u0026#34;\u0026#34; print(str(self)) def time_to_int(self): \u0026#34;\u0026#34;\u0026#34;Computes the number of seconds since midnight.\u0026#34;\u0026#34;\u0026#34; return self.second def is_after(self, other): \u0026#34;\u0026#34;\u0026#34;Returns True if t1 is after t2; false otherwise.\u0026#34;\u0026#34;\u0026#34; return self.second \u0026gt; other.second def __add__(self, other): \u0026#34;\u0026#34;\u0026#34;Adds two Time objects or a Time object and a number. other: Time object or number of seconds \u0026#34;\u0026#34;\u0026#34; if isinstance(other, Time): return self.add_time(other) else: return self.increment(other) def __radd__(self, other): \u0026#34;\u0026#34;\u0026#34;Adds two Time objects or a Time object and a number.\u0026#34;\u0026#34;\u0026#34; return self.__add__(other) def add_time(self, other): \u0026#34;\u0026#34;\u0026#34;Adds two time objects.\u0026#34;\u0026#34;\u0026#34; assert self.is_valid() and other.is_valid() seconds = self.second + other.second return int_to_time(seconds) def increment(self, seconds): \u0026#34;\u0026#34;\u0026#34;Returns a new Time that is the sum of this time and seconds.\u0026#34;\u0026#34;\u0026#34; seconds += self.second return int_to_time(seconds) def is_valid(self): \u0026#34;\u0026#34;\u0026#34;Checks whether a Time object satisfies the invariants.\u0026#34;\u0026#34;\u0026#34; return self.second \u0026gt;= 0 and self.second \u0026lt; 24*60*60 def int_to_time(seconds): \u0026#34;\u0026#34;\u0026#34;Makes a new Time object. seconds: int seconds since midnight. \u0026#34;\u0026#34;\u0026#34; time = Time(0,0,seconds) return time def main(): start = Time(9, 45, 00) start.print_time() end = start.increment(1337) #end = start.increment(1337, 460) end.print_time() print(\u0026#39;Is end after start?\u0026#39;) print(end.is_after(start)) print(\u0026#39;Using __str__\u0026#39;) print(start, end) start = Time(9, 45) duration = Time(1, 35) print(start + duration) print(start + 1337) print(1337 + start) print(\u0026#39;Example of polymorphism\u0026#39;) t1 = Time(7, 43) t2 = Time(7, 41) t3 = Time(7, 37) total = sum([t1, t2, t3]) print(total) if __name__ == \u0026#39;__main__\u0026#39;: main() ","date":"Jan 10","permalink":"https://o5o.me/post/think_python_exercise_17.1/","tags":null,"title":"Think Python Exercise 17.1"},{"categories":["Python"],"contents":"datetime 模块提供的 time 对象,和本章的 Time 对象类似, 但前者提供了更丰富的方法和操作符。\n使用 datetime 模块来编写一个程序,获取当前日期并打印当天是周几。 编写一个程序,接受一个生日作为输入,并打印用户的年龄以及距离下个生日所需要的天数、小时数、分钟数和秒数。 对于两个不在同一天出生的人来说,总有一天,一个人的出生天数是另一个人的两倍。我们把这一天称为 ‘‘双倍日’’。编写一个程序,接受两个不同的出生日期, 并计算他们的 ‘‘双倍日’’。 再增加点挑战,编写一个更通用的版本,用于计算一个人出生天数是另一个人 n 倍的日子。 题1\n1 2 3 4 5 6 7 from datetime import date today = date.today() #print(dir(today)) #打印属性、方法列表 print(\u0026#34;今天是星期%d.\u0026#34; % (today.weekday()+1)) # weekday返回的值是从0开始的,所以+1 题2\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from datetime import datetime def days_until_birthday(birthday): \u0026#34;\u0026#34;\u0026#34;距离下一次生日还有多长时间\u0026#34;\u0026#34;\u0026#34; today = datetime.today() next_birthday = datetime(today.year, birthday.month, birthday.day) if today \u0026gt; next_birthday: next_birthday = datetime(today.year+1, birthday.month, birthday.day) delta = next_birthday - today return delta.days today = datetime.today() print(today.strftime(\u0026#39;%A\u0026#39;)) #星期几 birthday = datetime(1999,5,10) print(days_until_birthday(birthday)) 题3和题4\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 from datetime import datetime def double_day(b1,b2): \u0026#34;\u0026#34;\u0026#34;计算,什么时候一个人的出生天数是另外一个人的两倍\u0026#34;\u0026#34;\u0026#34; if b1 \u0026gt; b2: delta = b1 - b2 dday = b1 + delta return dday elif b2 \u0026gt; b1: delta = b2 - b1 dday = b2 + delta return dday else: return False def ntimes_day(b1,b2,n): \u0026#34;\u0026#34;\u0026#34;计算,什么时候一个人的出生天数是另外一个人的n倍\u0026#34;\u0026#34;\u0026#34; if b1 \u0026gt; b2: delta = b1 - b2 dday = b1 + (n-1)*delta return dday elif b2 \u0026gt; b1: delta = b2 - b1 dday = b2 + (n-1)*delta return dday else: return False b1 = datetime(1999,5,10) b2 = datetime(2003,5,16) print(\u0026#39;两倍:\u0026#39;,double_day(b1,b2)) print(\u0026#39;6倍: \u0026#39;,ntimes_day(b1,b2,6)) ","date":"Jan 10","permalink":"https://o5o.me/post/think_python_exercise_16.2/","tags":null,"title":"Think Python Exercise 16.2"},{"categories":["Python"],"contents":"写一个叫做 mul_time 的函数,接收一个 Time 对象和一个数,并返回一个新的 Time 对象,包含原始时间和数的乘积。\n然后使用 mul_time 编写一个函数,接受一个表示比赛完赛时间的 Time 对象以及一个表示距离的数字,并返回一个用于表示平均配速 (每英里所需时间) 的 Time 对象。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Time: \u0026#34;\u0026#34;\u0026#34;用于记录时间 属性:hour, minute, second \u0026#34;\u0026#34;\u0026#34; def print_time(ti): print(\u0026#34;%.2d:%.2d:%.2d\u0026#34; % (ti.hour,ti.minute,ti.second)) #记得加括号 def time_to_int(time): minutes = time.hour * 60 + time.minute seconds = minutes * 60 + time.second return seconds def int_to_time(seconds): time = Time() minutes, time.second = divmod(seconds, 60) time.hour, time.minute = divmod(minutes, 60) return time def mul_time(t1, a): return int_to_time(time_to_int(t1) * a) distance = 10 #距离,英里 time1 = Time() time1.hour = 11 time1.minute = 59 time1.second = 30 speed = mul_time(time1, 1/distance) #单位:小时/英里 print_time(speed) ","date":"Jan 10","permalink":"https://o5o.me/post/think_python_exercise_16.1/","tags":null,"title":"Think Python Exercise 16.1"},{"categories":["Python"],"contents":"编写一个叫做 print_time 的函数,接收一个 Time 对象并用 时:分:秒 的格式打印它。提示: 格式化序列 %.2d 可以至少两位数的形式打印一个整数,如果不足则在前面补 0。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Time: \u0026#34;\u0026#34;\u0026#34;用于记录时间 属性:hour, minute, second \u0026#34;\u0026#34;\u0026#34; def print_time(ti): print(\u0026#34;%.2d:%.2d:%.2d\u0026#34; % (ti.hour,ti.minute,ti.second)) #记得加括号 time1 = Time() time1.hour = 11 time1.minute = 59 time1.second = 30 print_time(time1) 编写一个叫做 is_after 的布尔函数,接收两个 Time 对象,t1 和 t2 ,若 t1 的时间在 t2 之后,则返回 True ,否则返回 False。挑战:不要使用 if 语句。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Time: \u0026#34;\u0026#34;\u0026#34;用于记录时间 属性:hour, minute, second \u0026#34;\u0026#34;\u0026#34; def print_time(ti): print(\u0026#34;%.2d:%.2d:%.2d\u0026#34; % (ti.hour,ti.minute,ti.second)) #记得加括号 def is_after(t1,t2): return (t1.hour, t1.minute, t1.second) \u0026gt; (t2.hour, t2.minute, t2.second) time1 = Time() time1.hour = 11 time1.minute = 59 time1.second = 30 time2 = Time() time2.hour = 11 time2.minute = 58 time2.second = 30 is_after(time1,time2) 我们做个练习,编写一个纯函数版本的 increment ,创建并返回一个 Time 对象,而不是修改参数。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Time: \u0026#34;\u0026#34;\u0026#34;用于记录时间 属性:hour, minute, second \u0026#34;\u0026#34;\u0026#34; def print_time(ti): print(\u0026#34;%.2d:%.2d:%.2d\u0026#34; % (ti.hour,ti.minute,ti.second)) #记得加括号 def increment(t1, seconds): \u0026#34;\u0026#34;\u0026#34;给一个Time对象增加指定的秒数\u0026#34;\u0026#34;\u0026#34; addedtime = Time() minutes, addedtime.second = divmod(t1.hour * 60 * 60 + t1.minute * 60 + t1.second + seconds, 60) addedtime.hour, addedtime.minute = divmod(minutes, 60) return addedtime time1 = Time() time1.hour = 11 time1.minute = 59 time1.second = 30 print_time(increment(time1, 90)) 我们再做个练习, 使用 time_to_int 和 int_to_time 重写 increment 函数。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Time: \u0026#34;\u0026#34;\u0026#34;用于记录时间 属性:hour, minute, second \u0026#34;\u0026#34;\u0026#34; def print_time(ti): print(\u0026#34;%.2d:%.2d:%.2d\u0026#34; % (ti.hour,ti.minute,ti.second)) #记得加括号 def time_to_int(time): minutes = time.hour * 60 + time.minute seconds = minutes * 60 + time.second return seconds def int_to_time(seconds): time = Time() minutes, time.second = divmod(seconds, 60) time.hour, time.minute = divmod(minutes, 60) return time def add_time(t1, t2): seconds = time_to_int(t1) + time_to_int(t1) return int_to_time(seconds) def increment(t1,seconds): \u0026#34;\u0026#34;\u0026#34;给一个Time对象增加指定的秒数\u0026#34;\u0026#34;\u0026#34; return int_to_time(time_to_int(t1) + seconds) def add_time(t1, t2): assert valid_time(t1) and valid_time(t2) seconds = time_to_int(t1) + time_to_int(t2) return int_to_time(seconds) time1 = Time() time1.hour = 11 time1.minute = 59 time1.second = 30 print_time(increment(time1, 90)) ","date":"Jan 09","permalink":"https://o5o.me/post/think_python_exercise_16.0/","tags":null,"title":"Think Python Exercise 16.1"},{"categories":["Python"],"contents":"编写一个名为 draw_rect 的函数,该函数接受一个 Turtle 对象和一个 Rectangle 对象,使用Turtle 画出该矩形。参考第 四 章中使用 Turtle 的示例。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import turtle class Point: \u0026#34;\u0026#34;\u0026#34;表示一个二维的点 \u0026#34;\u0026#34;\u0026#34; class Rectangle: \u0026#34;\u0026#34;\u0026#34;Represents a rectangle. attributes: width, height, corner.\u0026#34;\u0026#34;\u0026#34; def draw_rect(t,rect): t.pu() #在移动到矩形左下角的过程中,不画出痕迹(提笔,put up) t.goto(rect.corner.x, rect.corner.y) t.pd() #落笔(put down) for i in range(2): t.fd(box.width) t.lt(90) t.fd(box.height) t.lt(90) bob = turtle.Turtle() box = Rectangle() box.width = 100.0 box.height = 200.0 box.corner = Point() box.corner.x = 100 box.corner.y = 90 draw_rect(bob,box) turtle.mainloop() 编写一个名为 draw_circle 的函数, 该函数接受一个 Turtle 对象和 Circle 对象, 并画出该圆。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 import turtle, math class Point: \u0026#34;\u0026#34;\u0026#34;表示一个二维的点 \u0026#34;\u0026#34;\u0026#34; class Circle: \u0026#34;\u0026#34;\u0026#34;属性:圆心center、半径radius 圆心是一个Point类 半径是一个数字 \u0026#34;\u0026#34;\u0026#34; #下面几个函数,用了4.1题画圆的代码 def polyline(t, n, length, angle): \u0026#34;\u0026#34;\u0026#34;Draws n line segments. t: Turtle object n: number of line segments length: length of each segment angle: degrees between segments \u0026#34;\u0026#34;\u0026#34; for i in range(n): t.fd(length) t.lt(angle) def polygon(t, n, length): \u0026#34;\u0026#34;\u0026#34;Draws a polygon with n sides. t: Turtle n: number of sides length: length of each side. \u0026#34;\u0026#34;\u0026#34; angle = 360.0/n polyline(t, n, length, angle) def arc(t, r, angle): \u0026#34;\u0026#34;\u0026#34;Draws an arc with the given radius and angle. t: Turtle r: radius angle: angle subtended by the arc, in degrees \u0026#34;\u0026#34;\u0026#34; arc_length = 2 * math.pi * r * abs(angle) / 360 #角度变为绝对值 n = int(arc_length / 4) + 3 #之前是int(arc_length / 3) + 1 step_length = arc_length / n step_angle = float(angle) / n # making a slight left turn before starting reduces # the error caused by the linear approximation of the arc # 在开始前稍微左转可以减少由直线逼近弧线所引起的误差 t.lt(step_angle/2) polyline(t, n, step_length, step_angle) t.rt(step_angle/2) def circle(t, r): \u0026#34;\u0026#34;\u0026#34;Draws a circle with the given radius. t: Turtle r: radius \u0026#34;\u0026#34;\u0026#34; arc(t, r, 360) def draw_circle(t,circ): t.pu() #在移动到圆心的过程中,不画出痕迹(提笔,put up) t.goto(circ.center.x, circ.center.y) t.pd() #落笔(put down) circle(t, circ.radius) bob = turtle.Turtle() tom = Circle() tom.center = Point() tom.center.x = 150 tom.center.y = 100 tom.radius = 75 draw_circle(bob,tom) turtle.mainloop() ","date":"Jan 09","permalink":"https://o5o.me/post/think_python_exercise_15.2/","tags":null,"title":"Think Python Exercise 15.2"},{"categories":["Python"],"contents":" 15.1.1 定义一个叫做Circle类, 类的属性是圆心 (center) 和半径 (radius), 其中, 圆心 (center) 是一个 Point 类,而半径 (radius) 是一个数字。\n实例化一个圆心 (center) 为 (150, 100) ,半径 (radius) 为 75 的Circle 对象。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Circle: \u0026#34;\u0026#34;\u0026#34;属性:圆心center、半径radius 圆心是一个Point类 半径是一个数字 \u0026#34;\u0026#34;\u0026#34; class Point: \u0026#34;\u0026#34;\u0026#34;表示一个二维的点 \u0026#34;\u0026#34;\u0026#34; bob = Circle() bob.center = Point() bob.center.x = 150 bob.center.y = 100 bob.radius = 75 15.1.2 编写一个名称为point_in_circle的函数,该函数可以接受一个圆类 (Circle) 对象和点类 (Point) 对象, 然后判断该点是否在圆内。在圆内则返回True 。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import math class Circle: \u0026#34;\u0026#34;\u0026#34;属性:圆心center、半径radius 圆心是一个Point类 半径是一个数字 \u0026#34;\u0026#34;\u0026#34; class Point: \u0026#34;\u0026#34;\u0026#34;表示一个二维的点 \u0026#34;\u0026#34;\u0026#34; def point_in_circle(cir,poi): distance = math.sqrt((poi.x-cir.center.x)**2+(poi.y-cir.center.y)**2)#求点到圆心的距离来判断点是否在圆内 if distance \u0026lt;= cir.radius: return True else: return False bob = Circle() bob.center = Point() bob.center.x = 150 bob.center.y = 100 bob.radius = 75 tom = Point() tom.x = 100 tom.y = 101 point_in_circle(bob,tom) 15.1.3 编写一个名称为rect_in_circle的函数,该函数接受一个圆类 (Circle) 对象和矩形(Rectangle) 对象,如果该矩形上的点完全在圆内或者在圆上则返回True。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import math class Point: \u0026#34;\u0026#34;\u0026#34;表示一个二维的点 \u0026#34;\u0026#34;\u0026#34; class Circle: \u0026#34;\u0026#34;\u0026#34;属性:圆心center、半径radius 圆心是一个Point类 半径是一个数字 \u0026#34;\u0026#34;\u0026#34; class Rectangle: \u0026#34;\u0026#34;\u0026#34;Represents a rectangle. attributes: width, height, corner.\u0026#34;\u0026#34;\u0026#34; def point_in_circle(cir,poi): distance = math.sqrt((poi.x-cir.center.x)**2+(poi.y-cir.center.y)**2)#求点到圆心的距离来判断点是否在圆内 if distance \u0026lt;= cir.radius: return True else: return False def rect_in_circle(cir,rect): #根据矩形左下角的坐标及长宽,得到其他三个点的坐标 topleft = Point() topleft.x = rect.corner.x topleft.y = rect.corner.y + rect.height topright = Point() topright.x = rect.corner.x + rect.width topright.y = rect.corner.y + rect.height bottomright = Point() bottomright.x = rect.corner.x + rect.width bottomright.y = rect.corner.y # 如果矩形的四个点都在圆内或圆上,就可以认为矩形在圆内或圆上 if point_in_circle(cir,topleft) and point_in_circle(cir,topright) and point_in_circle(cir, bottomright) and point_in_circle(cir, rect.corner): return True else: return False bob = Circle() bob.center = Point() bob.center.x = 150 bob.center.y = 100 bob.radius = 75 box = Rectangle() box.width = 100.0 box.height = 200.0 box.corner = Point() box.corner.x = 0.0 box.corner.y = 0.0 rect_in_circle(bob,box) 写到这里插句题外话,我在写代码的时候遇到两次这个类似报错:“AttributeError: \u0026lsquo;Rectangle\u0026rsquo; object has no attribute \u0026lsquo;x\u0026rsquo;”,但我咋看,我定义的函数咋没问题。最后发现,确实不是函数的问题,而是我调用错了函数。因为练习题与练习题之间是有关联的,我是直接在上一题的代码基础上来做这道题,在调用函数的时候,我错误地用了上一题的函数,调用了这一题这个函数对应的参数,所以就报错了。\n15.1.4 编写一个名为 rect_circle_overlap 函数,该函数接受一个圆类对象和一个矩形类对象,如果矩形有任意一个角落在圆内则返回True 。或者写一个更具有挑战性的版本, 如果该矩形有任何部分落在圆内返回True 。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import math class Point: \u0026#34;\u0026#34;\u0026#34;表示一个二维的点 \u0026#34;\u0026#34;\u0026#34; class Circle: \u0026#34;\u0026#34;\u0026#34;属性:圆心center、半径radius 圆心是一个Point类 半径是一个数字 \u0026#34;\u0026#34;\u0026#34; class Rectangle: \u0026#34;\u0026#34;\u0026#34;Represents a rectangle. attributes: width, height, corner.\u0026#34;\u0026#34;\u0026#34; def point_in_circle(cir,poi): distance = math.sqrt((poi.x-cir.center.x)**2+(poi.y-cir.center.y)**2)#求点到圆心的距离来判断点是否在圆内 if distance \u0026lt;= cir.radius: return True else: return False def rect_circle_overlap(cir, rect): #根据矩形左下角的坐标及长宽,得到其他三个点的坐标 topleft = Point() topleft.x = rect.corner.x topleft.y = rect.corner.y + rect.height topright = Point() topright.x = rect.corner.x + rect.width topright.y = rect.corner.y + rect.height bottomright = Point() bottomright.x = rect.corner.x + rect.width bottomright.y = rect.corner.y # 矩形是否有角落在圆内 if point_in_circle(cir,topleft) or point_in_circle(cir,topright) or point_in_circle(cir, bottomright) or point_in_circle(cir, rect.corner): return True else: return False bob = Circle() bob.center = Point() bob.center.x = 150 bob.center.y = 100 bob.radius = 75 box = Rectangle() box.width = 100.0 box.height = 200.0 box.corner = Point() box.corner.x = 100 box.corner.y = 90 rect_circle_overlap(bob,box) 这里也就是把上一段代码里的 and 换成了 or、改了个函数名。函数其他部分一点都没变。\n","date":"Jan 04","permalink":"https://o5o.me/post/think_python_exercise_15.1/","tags":null,"title":"Think Python Exercise 15.1"},{"categories":["Python"],"contents":"编写一个叫做distance_between_points的函数,它接受两个Point作为参数,然后返回这两个点之间的距离。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import math class Point: \u0026#34;\u0026#34;\u0026#34;表示一个二维的点 \u0026#34;\u0026#34;\u0026#34; def distance_between_points(p1,p2): return math.sqrt((p1.x - p2.x)**2+(p1.y - p2.y)**2) Po1 = Point() Po2 = Point() Po1.x = 5 Po1.y = 3 Po2.x = 6 Po2.y = 2 distance_between_points(Po1,Po2) 编写一个叫做move_rectangle的函数,接受一个Rectangle以及两个数字dx和dy。它把corner的x坐标加上dx,把corner的y坐标加上dy,从而改变矩形的位置。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 def print_point(p): print(\u0026#39;(%g, %g)\u0026#39; % (p.x, p.y)) class Point: \u0026#34;\u0026#34;\u0026#34;表示一个二维的点 \u0026#34;\u0026#34;\u0026#34; class Rectangle: \u0026#34;\u0026#34;\u0026#34;Represents a rectangle. attributes: width, height, corner.\u0026#34;\u0026#34;\u0026#34; def move_rectangle(rect,dx,dy): rect.corner.x += dx rect.corner.y += dy box = Rectangle() box.width = 100.0 box.height = 200.0 box.corner = Point() box.corner.x = 0.0 box.corner.y = 0.0 move_rectangle(box,20,30) print_point(box.corner) ","date":"Jan 04","permalink":"https://o5o.me/post/think_python_exercise_15.0/","tags":null,"title":"Think Python Exercise 15.0"},{"categories":["Python"],"contents":"将 习题6.2 中的 Ackermann 函数备忘录化(memoize), 看看备忘录化(memoization)是否可以支持解决更大的参数。没有提示!\n储存之前计算过的值以便今后使用,这个操作就称为备忘录化。\n做这道题首先要理解书上关于斐波那契数列的例子。\n计算斐波那契数列的代码:\n1 2 3 4 5 6 7 def fibonacci(n): if n == 0: return 0 elif n == 1: return 1 else: return fibonacci(n-1) + fibonacci(n-2) 备忘录化后的代码:\n1 2 3 4 5 6 7 8 9 known = {0:0, 1:1} def fibonacci(n): if n in known: return known[n] res = fibonacci(n-1) + fibonacci(n-2) known[n] = res return res 按照同样的思路,我们可以把计算的ack函数的值储存到字典里,后面再计算时先看之前是不是曾计算过,如果计算过,直接调用已有结果即可。\n原ack函数代码:\n1 2 3 4 5 6 7 8 def ackermann(m,n): #来自6.2 if m == 0: return n+1 if n == 0: return ackermann(m-1,1) return ackermann(m-1, ackermann(m,n-1)) print(ackermann(3,4)) 备忘录化后的ack代码:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 known = {} def ackermann(m,n): if (m,n) in known: return known[m,n] if m == 0: res = n+1 known[m,n] = res return res if n == 0: res = ackermann(m-1,1) known[m,n] = res return res res = ackermann(m-1, ackermann(m,n-1)) known[m,n] = res return res print(ackermann(3,4)) 可以注意到我用了元组作为字典的键,元组是下一章学的,这里用是因为我觉得没有比元组更合适的了。\n在 jupyter lab 里可以通过在代码第一行写%%time查看代码运行时间,经比较,可以发现备忘录化后的计算速度快了很多很多:\n1 2 3 4 5 6 7 8 9 %%time def ackermann(m,n): if m == 0: return n+1 if n == 0: return ackermann(m-1,1) return ackermann(m-1, ackermann(m,n-1)) print(ackermann(3,6)) 509 CPU times: user 55 ms, sys: 4.78 ms, total: 59.8 ms Wall time: 66.4 ms 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 %%time known = {} def ackermann(m,n): if (m,n) in known: return known[m,n] if m == 0: res = n+1 known[m,n] = res return res if n == 0: res = ackermann(m-1,1) known[m,n] = res return res res = ackermann(m-1, ackermann(m,n-1)) known[m,n] = res return res print(ackermann(3,6)) 509 CPU times: user 3.09 ms, sys: 1.51 ms, total: 4.59 ms Wall time: 5.48 ms 再大的数我就不试了。你感兴趣可以试试ackermann(4,3)/手动狗头。\n","date":"Jan 03","permalink":"https://o5o.me/post/think_python_exercise_11.3_ackermann/","tags":null,"title":"Think Python Exercise 11.3 阿克曼函数"},{"categories":["Python"],"contents":"Ackermann 函数 $A(m, n)$ 的定义如下:\n$$ A(m, n)=\\left{\\begin{array}{ll} n+1 \u0026amp; \\text { if } m=0 \\ A(m-1,1) \u0026amp; \\text { if } m\u0026gt;0 \\text { and } n=0 \\ A(m-1, A(m, n-1)) \u0026amp; \\text { if } m\u0026gt;0 \\text { and } n\u0026gt;0 \\end{array}\\right. $$\n查看维基百科的定义, 编写一个叫作ack的函数来计算 Ackermann 函数。使用你的函 数计算ack(3,4), 其结果应该为 125 。如果 $m$ 和 $n$ 的值较大时, 会发生什么?\n拙见:\n1 2 3 4 5 6 7 8 9 def ack(m,n): if m == 0: return n+1 elif m \u0026gt; 0 and n == 0: return ack(m-1,1) elif m \u0026gt; 0 and n \u0026gt; 0: return ack(m-1,ack(m,n-1)) ack(3,4) 似乎看上去平平无奇,把它的计算过程展示出来:\n1 2 3 4 5 6 7 8 9 10 11 12 def ack(m,n): if m == 0: print(n+1) return n+1 elif m \u0026gt; 0 and n == 0: print(\u0026#39;ack(\u0026#39;,m-1,\u0026#39;,\u0026#39;,1,\u0026#39;)\u0026#39;) return ack(m-1,1) elif m \u0026gt; 0 and n \u0026gt; 0: print(\u0026#39;ack(\u0026#39;,m-1,\u0026#39;,ack(\u0026#39;,m,\u0026#39;,\u0026#39;,n-1,\u0026#39;))\u0026#39;) return ack(m-1,ack(m,n-1)) ack(3,4) 你可以自己动手运行一下看看,实际上进行了上万次递归。\n作者的写法更简洁一些:\n1 2 3 4 5 6 7 8 def ackermann(m,n): if m == 0: return n+1 if n == 0: return ackermann(m-1,1) return ackermann(m-1, ackermann(m,n-1)) print(ackermann(3,4)) (补充说明一下,我的代码是在jupyter lab里运行的,所以不用使用print函数也能把结果显示出来。)\n","date":"Jan 03","permalink":"https://o5o.me/post/think_python_exercise_6.2_ackermann/","tags":null,"title":"Think Python Exercise 6.2 阿克曼函数"},{"categories":["Python"],"contents":"画出下面程序的堆栈图。这个程序的最终输出是什么?\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def b(z): prod = a(z, z) print(z, prod) return prod def a(x, y): x = x + 1 return x * y def c(x, y, z): total = x + y + z square = b(total)**2 return square x = 1 y = x + 1 print(c(x, y+3, x+y)) 程序的最终输出:8100\n应该没有特别的意义吧?\n","date":"Jan 03","permalink":"https://o5o.me/post/think_python_exercise_6.1/","tags":null,"title":"Think Python Exercise 6.1"},{"categories":["Python"],"contents":"查看字典方法setdefault的文档, 并使用该方法写一个更简洁的invert_dict。\n原来的函数的定义:\n1 2 3 4 5 6 7 8 9 10 11 def invert_dict(d): \u0026#34;\u0026#34;\u0026#34;倒转一个字典,将原字典的值作为键,原字典的键作为值。 \u0026#34;\u0026#34;\u0026#34; inverse = dict() for key in d: val = d[key] if val not in inverse: inverse[val] = [key] #反转后,一个键可能对应多个值,所以把值放到列表里 else: inverse[val].append(key) #相当于给列表追加一个值。键val的值是一个列表。 return inverse 文档链接:这里\nsetdefault(key[, default]), 如果字典存在键 key ,返回它的值。如果不存在,插入值为 default 的键 key ,并返回 default 。 default 默认为 None。\n1 2 3 4 5 6 7 8 9 10 def invert_dict(d): \u0026#34;\u0026#34;\u0026#34;倒转一个字典,将原字典的值作为键,原字典的键作为值。 \u0026#34;\u0026#34;\u0026#34; inverse = dict() for key in d: inverse[d[key]]=inverse.setdefault(d[key],[])+[key] return inverse A = {\u0026#39;a\u0026#39;:2,\u0026#39;b\u0026#39;:3,\u0026#39;c\u0026#39;:2} invert_dict(A) 做这种题有助于提高自信。不建议看答案,看完你会有:“为啥我就没想到”这种想法,进一步怀疑自己的智商。\n","date":"Jan 03","permalink":"https://o5o.me/post/think_python_exercise_11.2/","tags":null,"title":"Think Python Exercise 11.2 字典方法setdefault"},{"categories":["Python"],"contents":"编写一函数,读取words.txt中的单词并存储为字典中的键。值是什么无所谓。然后,你可以使用in操作符检查一个字符串是否在字典中。\n如果你做过练习 10.10 ,可以比较一下in操作符和二分查找的速度。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def make_word_list(): word_list = [] fin = open(\u0026#39;words.txt\u0026#39;) for line in fin: word = line.strip() word_list.append(word) return word_list def dictword(w_li): word_dict = dict() for word in w_li: word_dict[word] = \u0026#39;ok\u0026#39; #题目说值是什么无所谓 return word_dict word_list = make_word_list() word_dict = dictword(word_list) \u0026#39;aha\u0026#39; in word_dict 我就不比较in操作符和二分查找的速度了。\n","date":"Jan 03","permalink":"https://o5o.me/post/think_python_exercise_11.1/","tags":null,"title":"Think Python Exercise 11.1"},{"categories":["Python"],"contents":"两个单词中如果一个是另一个的反转, 则二者被称为是 ‘‘反转词对’’。 编写一个函数,找出单词表中所有的反转词对。\n解答:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 def read_word1(file_in): #来自10.9 word_list = [] for line in file_in: word = line.strip() word_list.append(word) return word_list def in_bisect(t,a): #来自10.10 \u0026#34;\u0026#34;\u0026#34;列表t,单词a,看单词是否在列表内,在的话返回单词的位置。 列表里的单词,必须是已经按字母顺序排好序的。 \u0026#34;\u0026#34;\u0026#34; s1 = int((len(t))/2) s = s1 #如果a在t内,s保存a的序数 while s1 \u0026gt; 0: if a == t[s1]: return s elif a \u0026gt; t[s1]: t = t[s1:] s1 = int(len(t)/2) #在循环过程中,确保s和s1所指的是同一个元素。 #print(a,\u0026#34;的位置大于\u0026#34;,s) s += s1 else: t = t[:s1] s1 = int(len(t)/2) #print(a,\u0026#34;的位置小于等于\u0026#34;,s) s -= s1 return None def findpairs(word_li): i = 0 for word in word_li: drow = word[::-1] #将单词里的字母按照相反的顺序排列 k = in_bisect(word_li[i:],drow) #写word_list[i:]目的是不让程序往前去找所谓的“反转词” if k: print(word,\u0026#39;和\u0026#39;,word_li[k+i]) i += 1 fin = open(\u0026#39;words.txt\u0026#39;) word_list = read_word1(fin) findpairs(word_list) 我在10.10题写的in_bisect函数有误差,导致上面的代码虽然能找到‘‘反转词对’,但在输出的时候有偏差。今天很晚了(0:44),后面有机会再改吧。\n下面我用了Python的bisect模块:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import bisect def read_word1(file_in): #来自10.9 word_list = [] for line in file_in: word = line.strip() word_list.append(word) return word_list def findpairs(word_li): for i in range(len(word_li)): drow = word_li[i][::-1] #将单词里的字母按照相反的顺序排列 k = bisect.bisect(word_li[i:],drow) #写word_list[i:]目的是不让程序往前去找所谓的“反转词” if drow == word_li[i+k-1]: print(i,word_li[i],\u0026#39;和\u0026#39;,i+k-1,word_li[i+k-1]) fin = open(\u0026#39;words.txt\u0026#39;) word_list = read_word1(fin) findpairs(word_list) 输出看起来很美好,但是我稍微变一下,它就不正常了:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import bisect def read_word1(file_in): #来自10.9 word_list = [] for line in file_in: word = line.strip() word_list.append(word) return word_list def findpairs(word_li): for i in range(len(word_li)): drow = word_li[i][::-1] #将单词里的字母按照相反的顺序排列 k = bisect.bisect(word_li[i:],drow) #写word_list[i:]目的是不让程序往前去找所谓的“反转词” if k: print(i,word_li[i],\u0026#39;和\u0026#39;,i+k-1,word_li[i+k-1]) fin = open(\u0026#39;words.txt\u0026#39;) word_list = read_word1(fin) findpairs(word_list) 我把if里的drow == word_li[i+k-1]改成k,输出就不正常了,打印出来的一对单词 就不是‘‘反转词对’’了。而且因为单词表里的单词很多(十几万个),电脑还会卡死。\n看看作者答案是怎么写的吧:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 from __future__ import print_function, division from inlist import in_bisect, make_word_list def reverse_pair(word_list, word): rev_word = word[::-1] return in_bisect(word_list, rev_word) if __name__ == \u0026#39;__main__\u0026#39;: word_list = make_word_list() for word in word_list: if reverse_pair(word_list,word): print(word, word[::-1]) 有点失望,作者的代码完全没有涉及到下标的加加减减。\n","date":"Jan 02","permalink":"https://o5o.me/post/think_python_exercise_10.11/","tags":null,"title":"Think Python Exercise 10.11"},{"categories":["Python"],"contents":"使用in运算符可以检查一个单词是否在单词表中, 但这很慢, 因为它是按顺序查找单词。\n由于单词是按照字母顺序排序的,我们可以使用两分法 (也称二叉树搜索) 来加快速度, 类似于在字典中查找单词的方法。你从中间开始,如果你要找的单词在中间的单词之前,你查找前半部分,否则你查找后半部分。\n不管怎样, 你都会将搜索范围减小一半。 如果单词表有 113,809 个单词, 你只需要 17 步就可以找到这个单词,或着得出单词不存在的结论。\n编写一个叫做in_bisect的函数,接受一个已排序的列表和一个目标值作为参数,返回该值在列表中的位置,如果不存在则返回None。\n或者你可以阅读bisect模块的文档并使用它!\n解答:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def in_bisect(t,a): \u0026#34;\u0026#34;\u0026#34;列表t,单词a,看单词是否在列表内,在的话返回单词的位置。 列表里的单词,必须是已经按字母顺序排好序的。 \u0026#34;\u0026#34;\u0026#34; s1 = int((len(t))/2) s = s1 #如果a在列表内,s保存a的下标 while s1 \u0026gt; 0: if a == t[s1]: return s elif a \u0026gt; t[s1]: t = t[s1:] s1 = int(len(t)/2) #在循环过程中,确保s和s1所指的是同一个元素。 #print(a,\u0026#34;的位置大于\u0026#34;,s) s += s1 else: t = t[:s1] s1 = int(len(t)/2) #print(a,\u0026#34;的位置小于等于\u0026#34;,s) s -= s1 return None t1 = [\u0026#39;a\u0026#39;,\u0026#39;b\u0026#39;,\u0026#39;c\u0026#39;,\u0026#39;d\u0026#39;,\u0026#39;e\u0026#39;,\u0026#39;f\u0026#39;,\u0026#39;g\u0026#39;,\u0026#39;h\u0026#39;,\u0026#39;i\u0026#39;,\u0026#39;j\u0026#39;,\u0026#39;k\u0026#39;,\u0026#39;l\u0026#39;,\u0026#39;m\u0026#39;,\u0026#39;n\u0026#39;,\u0026#39;o\u0026#39;,\u0026#39;p\u0026#39;,\u0026#39;q\u0026#39;] in_bisect(t1,\u0026#39;c\u0026#39;) s和s1这两个变量,保存的是列表元素的下标。在循环过程中,确保s和s1所指的是同一个元素,即:s保存的是元素在完整列表里的下标,s1保存的是元素在切片后的列表里的下标,在两个列表里,第s个元素和第s1个元素(都从0开始)是同一个元素。\n因为涉及到除法然后取整,写代码的时候老是担心误差,总想对下标加个1或减个1,所得到的结果越来越离谱,后来干脆不加不减,反倒顺顺利利的。真的就是做多错多。代码虽然是我写出来的,但实际上我对它的运行过程并不是了如指掌。\n我画了一个图来跟踪代码运行时的状态,不能叫堆栈图,就单纯是为了跟踪变量在一次循环后会如何变化。\n这是作者的代码:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import bisect def make_word_list(): word_list = [] fin = open(\u0026#39;words.txt\u0026#39;) for line in fin: word = line.strip() word_list.append(word) return word_list def in_bisect(word_list, word): \u0026#34;\u0026#34;\u0026#34;通过二分法,检查一个单词是否在一个列表里 前置条件:列表里的单词是已排序的 returns:如果单词在,返回True,否则返回False \u0026#34;\u0026#34;\u0026#34; if len(word_list) == 0: return False i = len(word_list)//2 if word_list[i] == word: return True if word_list[i] \u0026gt; word: return in_bisect(word_list[:i],word) else: return in_bisect(word_list[i+1:],word) def in_bisect_cheat(word_list,word): i = bisect.bisect_left(word_list,word) if i == len(word_list): return False return word_list[i] == word if __name__ == \u0026#39;__main__\u0026#39;: word_list = make_word_list() for word in [\u0026#39;aa\u0026#39;, \u0026#39;alien\u0026#39;, \u0026#39;allen\u0026#39;, \u0026#39;zymurgy\u0026#39;]: print(word, \u0026#39;in list\u0026#39;, in_bisect(word_list, word)) for word in [\u0026#39;aa\u0026#39;, \u0026#39;alien\u0026#39;, \u0026#39;allen\u0026#39;, \u0026#39;zymurgy\u0026#39;]: print(word, \u0026#39;in list\u0026#39;, in_bisect_cheat(word_list, word)) 用了递归,比我写的高明很多。我的代码一比较,就跟直肠子似的,没有一点弯弯绕。\n但是作者并没有返回单词在列表的下标,只是简单返回一个True或False。\n","date":"Jan 02","permalink":"https://o5o.me/post/think_python_exercise_10.10/","tags":null,"title":"Think Python Exercise 10.10 二分法"},{"categories":["Python"],"contents":"编写一个叫做middle的函数,接受一个列表作为参数,并返回一个除了第一个和最后一个元素的列表。例如:\n\u0026gt;\u0026gt;\u0026gt; t = [1, 2, 3, 4] \u0026gt;\u0026gt;\u0026gt; middle(t) [2, 3] 解答:\n1 2 3 4 5 def middle(t): return t[1:-1] t1 = [1,2,3,4] middle(t1) ","date":"Jan 02","permalink":"https://o5o.me/post/think_python_exercise_10.3/","tags":null,"title":"Think Python Exercise 10.3"},{"categories":["Python"],"contents":"编写一个叫做 has_duplicates 的函数,接受一个列表作为参数,如果一个元素在列表中出现了不止一次,则返回 True 。这个函数不能改变原列表。\n解答:\n1 2 3 4 5 6 7 8 9 10 11 12 def has_duplicates(t): p = [] for i in t: if i not in p: p.append(i) else: return True return False t1 = [1,2,3] has_duplicates(t1) ","date":"Jan 02","permalink":"https://o5o.me/post/think_python_exercise_10.7/","tags":null,"title":"Think Python Exercise 10.7"},{"categories":null,"contents":"Roam Research是一个笔记软件,Anki是一个间隔重复软件。我在RR里写的笔记有复习需求,在Anki里的卡片有编辑、整理需求,所以以前的我就用Keyboard Maestro这个工具写了一个动作(Macro)来帮我将二者联系起来。\nRR的笔记可以导出为Anki卡片(卡片仅用于复习、展示,所有的编辑都在笔记上完成),笔记更新,卡片能同步更新,笔记和卡片能相互跳转。\n可以按下面的顺序进行配置(所需文件在文章最后),手把手教学请看视频:\n【详细配置】Keyboard Maestro动作实现Roam Research笔记和Anki卡片的双向链接、跳转和同步更新,配置指南\nAnki模板导入、修改 要改的地方就一处:修改模板里的RR graph名称。不改的话就没办法从Anki跳转回RR了。\nKM动作导入、修改 要改的地方也就只有一处:把你的Anki牌组的名字填进去。\nAnki插件安装 anki connect,插件代码:2055492159\n禁用App Nap 从Mac OS X Mavericks开始,操作系统中引入了名为App Nap的功能。此功能导致某些打开(但不可见)的应用程序处于挂起状态。由于此行为会导致Anki Connect在前台有另一个窗口时停止工作,因此应禁用Anki的App Nap1。\n在终端运行下面的命令即可。\n1 2 3 defaults write net.ankiweb.dtop NSAppSleepDisabled -bool true defaults write net.ichi2.anki NSAppSleepDisabled -bool true defaults write org.qt-project.Qt.QtWebEngineCore NSAppSleepDisabled -bool true 最后 这就配置完成了,是不是很简单?quicker版的动作配置更简单,除了下载插件,啥都不用配置,自动配置。想想以前真的傻,我给quicker版的这个动作加了批量导入、多种挖空标记、多种方式排列的笔记导入、自动从Anki获取牌组列表、自动添加Anki模板等等功能,这些功能在我现在看来用途很小很小,但当时却花了大量时间去改动作、修bug,浪费了很多时间在完全没必要的事情上。\n模板、动作下载链接: 百度网盘,提取码: 3tvu\n希望能给看到这里的你带来一些帮助。\nNotes for MacOS Users\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"Jan 02","permalink":"https://o5o.me/post/rr_km_anki_configuration_tutorial/","tags":["Anki","RoamResearch","KeyboardMaestro","bilibili"],"title":"Roam Research和Anki联用:动作配置"},{"categories":["Python"],"contents":" 画一个执行 circle(bob,radius) 时的堆栈图(stack diagram),说明程序的各个状态。你可以手动进行计算,也可以在代码中加入打印语句。 “重构”一节中给出的 arc 函数版本并不太精确,因为圆形的线性近似(linear approximation)永远处在真正的圆形之外。因此,Turtle 总是和正确的终点相差几个像素。我的答案中展示了降低这个错误影响的一种方法。阅读其中的代码,看看你是否能够理解。如果你画一个堆栈图的话,你可能会更容易明白背后的原理。 先放上代码,得到代码过程可以看Think Python Exercise 4.0。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import turtle import math def polyline(t,length,n,angle): for i in range(n): t.fd(length) t.lt(angle) def arc(t,r,angle): \u0026#34;\u0026#34;\u0026#34;angle取值范围是1~360,度数。 画出圆的一部分。 \u0026#34;\u0026#34;\u0026#34; arc_length = 2 * math.pi * r * angle / 360 n = int(arc_length / 3) + 1 step_length = arc_length / n step_angle = angle / n polyline(t,step_length,n,step_angle) def circle(t,r): arc(t,r,360) #circle是arc函数在角度angle取360时的特例 bob = turtle.Turtle() circle(bob,100) turtle.mainloop() 我画的堆栈图如下:\n来看看作者重构的代码:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 \u0026#34;\u0026#34;\u0026#34;This module contains a code example related to Think Python, 2nd Edition by Allen Downey http://thinkpython2.com Copyright 2015 Allen Downey License: http://creativecommons.org/licenses/by/4.0/ \u0026#34;\u0026#34;\u0026#34; from __future__ import print_function, division import math import turtle def square(t, length): \u0026#34;\u0026#34;\u0026#34;Draws a square with sides of the given length. Returns the Turtle to the starting position and location. \u0026#34;\u0026#34;\u0026#34; for i in range(4): t.fd(length) t.lt(90) def polyline(t, n, length, angle): \u0026#34;\u0026#34;\u0026#34;Draws n line segments. t: Turtle object n: number of line segments length: length of each segment angle: degrees between segments \u0026#34;\u0026#34;\u0026#34; for i in range(n): t.fd(length) t.lt(angle) def polygon(t, n, length): \u0026#34;\u0026#34;\u0026#34;Draws a polygon with n sides. t: Turtle n: number of sides length: length of each side. \u0026#34;\u0026#34;\u0026#34; angle = 360.0/n polyline(t, n, length, angle) def arc(t, r, angle): \u0026#34;\u0026#34;\u0026#34;Draws an arc with the given radius and angle. t: Turtle r: radius angle: angle subtended by the arc, in degrees \u0026#34;\u0026#34;\u0026#34; arc_length = 2 * math.pi * r * abs(angle) / 360 #角度变为绝对值 n = int(arc_length / 4) + 3 #之前是int(arc_length / 3) + 1 step_length = arc_length / n step_angle = float(angle) / n # making a slight left turn before starting reduces # the error caused by the linear approximation of the arc # 在开始前稍微左转可以减少由直线逼近弧线所引起的误差 t.lt(step_angle/2) polyline(t, n, step_length, step_angle) t.rt(step_angle/2) def circle(t, r): \u0026#34;\u0026#34;\u0026#34;Draws a circle with the given radius. t: Turtle r: radius \u0026#34;\u0026#34;\u0026#34; arc(t, r, 360) # the following condition checks whether we are # running as a script, in which case run the test code, # or being imported, in which case don\u0026#39;t. if __name__ == \u0026#39;__main__\u0026#39;: bob = turtle.Turtle() # draw a circle centered on the origin radius = 100 bob.pu() #抬笔 bob.fd(radius) bob.lt(90) bob.pd() #落笔 circle(bob, radius) # wait for the user to close the window turtle.mainloop() 我画的堆栈图:\n代码主要的变化在于,作者换了种方式来计算用于近似圆的多边形的边数,所需边数更少了;在开始画画之前,让turtle稍微左转(多边形外角角度的一半),减少由直线逼近弧线所引起的误差。\n目前就分析出这么多。\n","date":"Jan 01","permalink":"https://o5o.me/post/think_python_exercise_4.1/","tags":null,"title":"Think Python Exercise 4.1"},{"categories":["Python"],"contents":" 写一个名为 square 的函数,接受一个名为 t 的形参,t 是一个海龟。这个函数应用这只海龟画一个正方形。 写一个函数调用,将 bob 作为实参传给 square ,然后再重新运行程序。\n1 2 3 4 5 6 7 8 9 10 11 12 import turtle def square(t): for i in range(4): t.fd(100) t.lt(90) bob = turtle.Turtle() square(bob) turtle.mainloop() 给 square 增加另一个名为 length 的形参。修改函数体,使得正方形边的长度是 length ,然后修改函数调用,提供第二个实参。 重新运行程序。用一系列 length 值测试你的程序。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import turtle def square(t,length): for i in range(4): t.fd(length) t.lt(90) bob = turtle.Turtle() square(bob,100) square(bob,120) square(bob,150) square(bob,200) turtle.mainloop() 复制 square ,并将函数改名为 polygon 。增加另外一个名为 n 的形参并修改函数体,让它画一个正n边形(n-sided regular polygon)。提示:正n边形的外角是$360/n$度。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import turtle def polygon(t,length,n): for i in range(n): t.fd(length) t.lt(360/n) bob = turtle.Turtle() polygon(bob,100,6) polygon(bob,100,8) polygon(bob,100,15) turtle.mainloop() 看了答案后,觉得把360/n放到循环外面比较好,这样的话只需要计算一次。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import turtle def polygon(t,length,n): angle = 360 / n for i in range(n): t.fd(length) t.lt(angle) bob = turtle.Turtle() polygon(bob,100,6) polygon(bob,100,8) polygon(bob,100,15) turtle.mainloop() 编写一个名为 circle 的函数,它接受一个海龟t和半径r作为形参, 然后以合适的边长和边数调用 polygon ,画一个近似圆形。 用一系列r值测试你的函数。 提示:算出圆的周长,并确保 length * n = circumference 。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import turtle import math def polygon(t,length,n): for i in range(n): t.fd(length) t.lt(360/n) def circle(t,r): \u0026#34;\u0026#34;\u0026#34;t是一只乌龟。r是所要画的圆的半径。 根据公式$2 \\pi r / n$确定用于近似圆的多边形的边长,n是多边形的边数。 \u0026#34;\u0026#34;\u0026#34; circumference = 2 * math.pi * r #周长 n = 200 #多边形边数,我想不到选多少边数合适,直接设200,画一个200边形 length = circumference / n polygon(t,length,n) bob = turtle.Turtle() circle(bob,150) #半径r设为150 turtle.mainloop() 答案是这样来确定n的值(多边形的边数)的:周长除以3,取整后加1,即n = int(circumference/3)+1,也就是近似为周长的三分之一,圆的半径的两倍。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import turtle import math def polygon(t,length,n): angle = 360 / n for i in range(n): t.fd(length) t.lt(angle) def circle(t,r): \u0026#34;\u0026#34;\u0026#34;t是一只乌龟。r是所要画的圆的半径。 根据公式$2 \\pi r / n$确定用于近似圆的多边形的边长,n是多边形的边数。 \u0026#34;\u0026#34;\u0026#34; circumference = 2 * math.pi * r #周长 n = int(circumference/3)+1 #改的是这句 length = circumference / n polygon(t,length,n) bob = turtle.Turtle() circle(bob,150) #半径r设为150 turtle.mainloop() 完成一个更泛化(general)的 circle 函数,称其为 arc ,接受一个额外的参数 angle ,确定画多完整的圆。angle 的单位是度,因此当 angle=360 时, arc 应该画一个完整的圆。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import turtle import math def polyline(t,length,n,m): \u0026#34;\u0026#34;\u0026#34;n是多边形的边数,m是指画出多边形的前多少条边 n和m必须为整数。 \u0026#34;\u0026#34;\u0026#34; for i in range(m): t.fd(length) t.lt(360/n) def arc(t,r,angle): \u0026#34;\u0026#34;\u0026#34;angle取值范围是1~360,度数。 画出圆的一部分。 \u0026#34;\u0026#34;\u0026#34; circumference = 2 * math.pi * r #周长 n = 200 #多边形边数,我想不到选多少边数合适,直接设200 length = circumference / n #多边形边长 m = int(n * angle / 360) # 画出多边形的前m条边 polyline(t,length,n,m) bob = turtle.Turtle() arc(bob,150,30) arc(bob,100,150) arc(bob,150,270) turtle.mainloop() 我是先算出来圆的周长,然后算出用于近似这个圆的多边形的边数,然后再算出近似圆的一部分需要的多边形的边数;\n答案是先算圆的一部分对应的长度,根据这个长度得到用边数为n的多边形去近似圆,然后得到一边的长度,以及多边形的外角的角度。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import turtle import math def polyline(t,length,n,angle): for i in range(n): t.fd(length) t.lt(angle) def arc(t,r,angle): \u0026#34;\u0026#34;\u0026#34;angle取值范围是1~360,度数。 画出圆的一部分。 \u0026#34;\u0026#34;\u0026#34; arc_length = 2 * math.pi * r * angle / 360 n = int(arc_length / 3) + 1 step_length = arc_length / n step_angle = angle / n polyline(t,step_length,n,step_angle) bob = turtle.Turtle() arc(bob,150,30) arc(bob,100,150) arc(bob,150,270) turtle.mainloop() 根据这一题的代码,可以重写第四题的circle函数。circle函数可以看作是arc函数的一个特例。\n1 2 def circle(t,r): arc(t,r,360) ","date":"Jan 01","permalink":"https://o5o.me/post/think_python_exercise_4.0/","tags":null,"title":"Think Python Exercise 4.0"},{"categories":["Python"],"contents":"下面程序的输出是什么?画出展示程序每次打印输出时的堆栈图。\n1 2 3 4 5 6 7 def recurse(n, s): if n == 0: print(s) else: recurse(n-1, n+s) recurse(3, 0) 如果你这样调用函数: recurse(-1,0) ,会有什么结果? 请写一个文档字符串,解释调用该函数时需要了解的全部信息(仅此而已)。 解答:\n我用obsidian的Excalidraw插件画了一个。\n如果这样调用函数: recurse(-1,0),Python会抛出一个递归错误:超过最大递归深度。\n\u0026gt;\u0026gt;\u0026gt; recurse(-1,0) Traceback (most recent call last): File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 5, in recurse File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 5, in recurse File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 5, in recurse [Previous line repeated 996 more times] RecursionError: maximum recursion depth exceeded 写一个文档字符串,解释调用该函数时需要了解的全部信息\n1 2 3 4 5 6 7 8 9 10 11 def recurse(n, s): \u0026#34;\u0026#34;\u0026#34;将s与前n个正整数(从1到n)相加, n是一个正整数, s可以是任意数字 \u0026#34;\u0026#34;\u0026#34; if n == 0: print(s) else: recurse(n-1, n+s) recurse(3, -5) ","date":"Jan 01","permalink":"https://o5o.me/post/think_python_exercise_5.4/","tags":null,"title":"Think Python Exercise 5.4"},{"categories":["RegEx"],"contents":"最近在用SuperMemo渐进阅读旋元佑的《英语魔法师之语法俱乐部》这本语法书。我的阅读前流程是这样的:(括号内的是所用工具)\n将epub电子书按章节分割(calibre-EpubSplit) 将需要阅读的章节导入SuperMemo(Quicker、pandoc、AutoHotkey) 在SuperMemo内将该章节分割成更小的卡片(鼠标右键-阅读-分割-分割文章) 统一将所得的所有卡片设为新材料(根卡片鼠标右键-处理选中分支-学习-忘记) 菜单栏-学习-阶段-新学材料(也可等当天复习完后根据提示进入) 问题在于,我所拥有的这本epub电子书,html标签很乱,导致我在导入SM后无法正确根据html标签分割文章,因此在导入SM前需要先整理epub文件的html标签。这也就是这篇文章的由来。\n截取这本书的一个片段:\n1 \u0026lt;p class=\u0026#34;calibre_16\u0026#34;\u0026gt;以上谈的是修饰动词专用的“方法、状态副词”,以及它在句中位置的变化原则。接下来看看其他种类的副词。\u0026lt;/p\u0026gt;\u0026lt;blockquote class=\u0026#34;calibre_5\u0026#34;\u0026gt;\u0026lt;blockquote class=\u0026#34;calibre2\u0026#34;\u0026gt;\u0026lt;blockquote class=\u0026#34;calibre2\u0026#34;\u0026gt;\u0026lt;blockquote class=\u0026#34;calibre2\u0026#34;\u0026gt;\u0026lt;blockquote class=\u0026#34;calibre_21\u0026#34;\u0026gt;\u0026lt;span class=\u0026#34;calibre1\u0026#34;\u0026gt;\u0026lt;span class=\u0026#34;bold\u0026#34;\u0026gt;\u0026lt;span class=\u0026#34;calibre_12\u0026#34;\u0026gt;强调语气的副词(Intensifiers)\u0026lt;/span\u0026gt;\u0026lt;/span\u0026gt;\u0026lt;/span\u0026gt;\u0026lt;/blockquote\u0026gt;\u0026lt;/blockquote\u0026gt;\u0026lt;/blockquote\u0026gt;\u0026lt;/blockquote\u0026gt;\u0026lt;a id=\u0026#34;filepos295095\u0026#34;\u0026gt;\u0026lt;/a\u0026gt;\u0026lt;/blockquote\u0026gt;\u0026lt;p class=\u0026#34;calibre_24\u0026#34;\u0026gt;这一类副词有一个特色:它在使用上很有弹性,\u0026lt;span class=\u0026#34;bold\u0026#34;\u0026gt;四种主要词类,包括名词、动词、形容词与副词都可以用它来修饰。\u0026lt;/span\u0026gt;认识这一点,才算真正弄清楚形容词与副词间的分工。这一类的副词又可以细分为以下三种:\u0026lt;/p\u0026gt; 所要做的是把下面这一段变为:\u0026lt;h2\u0026gt;强调语气的副词(Intensifiers)\u0026lt;/h2\u0026gt;,这样我就可以在SM里很方便地按h2标签分割内容。\n1 \u0026lt;blockquote class=\u0026#34;calibre2\u0026#34;\u0026gt;\u0026lt;blockquote class=\u0026#34;calibre2\u0026#34;\u0026gt;\u0026lt;blockquote class=\u0026#34;calibre2\u0026#34;\u0026gt;\u0026lt;blockquote class=\u0026#34;calibre_21\u0026#34;\u0026gt;\u0026lt;span class=\u0026#34;calibre1\u0026#34;\u0026gt;\u0026lt;span class=\u0026#34;bold\u0026#34;\u0026gt;\u0026lt;span class=\u0026#34;calibre_12\u0026#34;\u0026gt;强调语气的副词(Intensifiers)\u0026lt;/span\u0026gt;\u0026lt;/span\u0026gt;\u0026lt;/span\u0026gt;\u0026lt;/blockquote\u0026gt;\u0026lt;/blockquote\u0026gt;\u0026lt;/blockquote\u0026gt;\u0026lt;/blockquote\u0026gt; 可以看到,一共是4个blockquote标签,3个span标签,那么我们可以先设法用正则匹配出一个起始blockquote标签,然后重复4次,匹配出一个起始span标签,然后重复3词,就能匹配出所有的起始标签了。\n(\u0026lt;blockquote\\s[^\u0026gt;]*\u0026#34;\u0026gt;){4}(\u0026lt;span\\s[^\u0026gt;]*\u0026#34;\u0026gt;){3} 同样的道理,匹配所有的闭合标签。\n(\u0026lt;\\/span\u0026gt;){3}(\u0026lt;/blockquote\u0026gt;){4} 最后,标题可以用(.*?)匹配,把它们组合在一起,就成功匹配出了这段html代码。\n(\u0026lt;blockquote\\s[^\u0026gt;]*\u0026#34;\u0026gt;){4}(\u0026lt;span\\s[^\u0026gt;]*\u0026#34;\u0026gt;){3}(.*?)(\u0026lt;\\/span\u0026gt;){3}(\u0026lt;/blockquote\u0026gt;){4} 将其用下面的内容替换,内容就整理好了。\n\\n\u0026lt;h2\u0026gt;$3\u0026lt;/h2\u0026gt;\\n 上面的正则,经过我的实践,是比较经济实惠的。以后如果有更好的方案,本文会继续更新。\n","date":"Dec 31","permalink":"https://o5o.me/post/regexps_match_html_tags/","tags":["SuperMemo","Epub"],"title":"正则表达式匹配HTML标签"},{"categories":["Python"],"contents":"如果可以通过重排一个单词中字母的顺序,得到另外一个单词,那么称这两个单词是变位词。编写一个叫做 is_anagram 的函数,接受两个字符串作为参数, 如果它们是变位词则返回 True 。\n解答:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def is_anagram(s1,s2): t1 = list(s1) t2 = list(s2) #先把字符串转换成列表 if len(t1) == len(t2): #如果两个字符串长度都不一样,那么肯定不是变位词了 t1.sort() t2.sort() #按照同样的方式重新排序后,如果二者元素对应相等,那么它们就是变位词了 for i in range(len(t1)): if t1[i] == t2[i]: pass else: return False return True else: return False is_anagram(\u0026#39;abc\u0026#39;,\u0026#39;bac\u0026#39;) ","date":"Dec 31","permalink":"https://o5o.me/post/think_python_exercise_10.6/","tags":null,"title":"Think Python Exercise 10.6"},{"categories":["Python"],"contents":"编写一个叫做is_sorted的函数,接受一个列表作为参数,如果列表是递增排列的则返回 True ,否则返回False。 例如:\n\u0026gt;\u0026gt;\u0026gt; is_sorted([1, 2, 2]) True \u0026gt;\u0026gt;\u0026gt; is_sorted([\u0026#39;b\u0026#39;, \u0026#39;a\u0026#39;]) False 分析:\n可以先对列表用.sort()方法进行递增排列,如果递增排列后的列表和原列表的元素都对应相同,说明原列表本身就是递增排列的。\n一个错误的写法:\n1 2 3 4 5 6 7 8 9 def is_sorted(t): t_copy = t[:] print(t_copy) if t_copy == t.sort(): return True else: return False is_sorted([1, 2, 2]) 程序没有报错,但它仍是错的,因为,我们要比较的是两个列表里的元素对应相同,而不是看这两个变量是不是指向同一个列表。t_copy和t.sort()肯定不指向同一个列表,但它们的元素对应相同。因此我们不能用t_copy == t.sort()这样的方法来比较。可以这样做:\n1 2 3 4 5 6 7 8 9 10 11 def is_sorted(t): t_copy = t[:] for i in range(len(t)): if t_copy[i] == t[i]: #如果递增排列后的列表和原列表的元素都对应相同,说明原列表本身就是递增排列的 pass else: return False return True is_sorted([1, 2, 2]) 还可以用另一种方法实现:从第二个元素开始,依次和前面一个元素比较大小。\n字符串也可以进行大小比较。英文字母按字母顺序从小到大;所有的大写字母在小写字母前面,所以如果进行大小比较的话,'A'\u0026lt;'Z'\u0026lt;'a'\u0026lt;'z'。\n1 2 3 4 5 6 7 8 9 10 def is_sorted(t): for i in range(1,len(t)): #从1开始,而不是从0开始生成序列 if t[i] \u0026gt;= t[i-1]: #如果从第二个元素开始,每一个元素都比它前面一个元素大,说明这个列表就是递增的 pass else: return False return True is_sorted([1, 2, 2]) ","date":"Dec 31","permalink":"https://o5o.me/post/think_python_exercise_10.5/","tags":null,"title":"Think Python Exercise 10.5"},{"categories":["Python"],"contents":"编写一个叫做 chop 的函数,接受一个列表作为参数,移除第一个和最后一个元素,并返回None。例如:\n\u0026gt;\u0026gt;\u0026gt; t = [1, 2, 3, 4] \u0026gt;\u0026gt;\u0026gt; chop(t) \u0026gt;\u0026gt;\u0026gt; t [2, 3] 分析:\n一顿操作猛如虎:(错的)\n1 2 3 4 5 6 7 8 def chop(t): t = t[1:-1] t1 = [1, 2, 3, 4] chop(t1) t1 为什么t1没变化?是我列表切片用错了吗,再试试:(还是错的)\n1 2 3 4 5 6 7 8 def chop(t): t = t[1:len(t)-2] t1 = [1, 2, 3, 4] chop(t1) t1 可以看到,t1还是没变化,为什么?为什么这样是错的?\n不要被迷惑了,当一个列表t1传递给一个函数时,t1和形参t共同指向同一个列表,但当函数内部t重新赋值后,t指向了一个新的列表,而t1依然指向原来的列表,那么当函数运行结束后,t1的值当然还是原来的啦。\n由于题目要求函数不能有返回值,可以使用下面的做法:\n1 2 3 4 5 6 7 8 9 def chop(t): del t[0] del t[-1] t1 = [1, 2, 3, 4] chop(t1) t1 如果函数可以有返回值,可以这样写:\n1 2 3 4 5 6 7 8 def chop(t): return t[1:-1] t1 = [1, 2, 3, 4] t1 = chop(t1) t1 ","date":"Dec 31","permalink":"https://o5o.me/post/think_python_exercise_10.4/","tags":null,"title":"Think Python Exercise 10.4"},{"categories":["Python"],"contents":"编写一个函数,读取文件 words.txt ,建立一个列表,其中每个单词为一个元素。\n编写两个版本,一个使用 append 方法,另一个使用 t = t + [x] 。那个版本运行得慢?为什么?\n解答:\n在 jupyter 中的一个代码单元(cell)中,代码第一行写上%%time或%%timeit,即可计算代码的运行时间。\n版本1:\n1 2 3 4 5 6 7 8 9 10 11 12 %%time def read_word1(file_in): word_list = [] for line in file_in: word = line.strip() word_list.append(word) return word_list fin = open(\u0026#39;words.txt\u0026#39;) a = read_word1(fin) 运行结果:\nCPU times: user 85.3 ms, sys: 0 ns, total: 85.3 ms Wall time: 88.4 ms\n版本2:\n1 2 3 4 5 6 7 8 9 10 11 12 %%time def read_word2(file_in): word_list = [] for line in file_in: word = line.strip() word_list = word_list + [word] #相当于word_list这个列表和一个只有一个元素的列表合并 return word_list fin = open(\u0026#39;words.txt\u0026#39;) a = read_word2(fin) 运行结果:\nCPU times: user 1min 1s, sys: 13.5 ms, total: 1min 1s Wall time: 1min 1s\n结果很显然,即便不准确测量也能感知到,第二个版本的代码相比第一个慢了很多。\n第一版的代码,每次循环,仅仅是操作一个元素,把它追加到列表末端;而第二版的代码,每次循环都要进行一次两个列表的拼接。\n","date":"Dec 31","permalink":"https://o5o.me/post/think_python_exercise_10.9/","tags":null,"title":"Think Python Exercise 10.9"},{"categories":["Python"],"contents":"编写一个叫做 cumsum 的函数,接受一个由数值组成的列表,并返回累加和;即一个新列表,其中第i个元素是原列表中前i+1个元素的和(注意i是从0开始的)。 例如:\n\u0026gt;\u0026gt;\u0026gt; t = [1, 2, 3] \u0026gt;\u0026gt;\u0026gt; cumsum(t) [1, 3, 6] 解答:\n1 2 3 4 5 6 7 8 9 10 11 12 def cumsum(t): numsum = [] for i in range(len(t)): numsum.append(sum(t[:i+1])) #对t的前i+1个元素求和(注意i是从0开始,所以第i个元素是第i+1个元素),然后追加到新列表里 return numsum t = [1, 2, 3] print(cumsum(t)) t = [3,3,6,9,10] print(cumsum(t)) ","date":"Dec 31","permalink":"https://o5o.me/post/think_python_exercise_10.2/","tags":null,"title":"Think Python Exercise 10.2"},{"categories":["Python"],"contents":"编写一个叫做 nested_sum 的函数,接受一个由一些整数列表构成的列表作为参数,并将所有嵌套列表中的元素相加。例如:\n\u0026gt;\u0026gt;\u0026gt; t = [[1, 2], [3], [4, 5, 6]] \u0026gt;\u0026gt;\u0026gt; nested_sum(t) 21 解答:\n可以分别对每个子列表求和,再对得到的各个和求和。也可以先把各个子列表合并起来,然后对得到的列表求和。\n1 2 3 4 5 6 7 8 9 def nested_sum(t): c = [] for i in t: c = c + i #把t列表里的各个列表合并成一个列表 return sum(c) t = [[1, 2], [3], [4, 5, 6]] nested_sum(t) ","date":"Dec 31","permalink":"https://o5o.me/post/think_python_exercise_10.1/","tags":null,"title":"Think Python Exercise 10.1"},{"categories":["SRS"],"contents":"在用SuperMemo复习时,一边要移动鼠标,浏览卡片内容,一边要用键盘评分、修改卡片内容,容易手忙脚乱。把评分常用的按键和其他一些按键映射到游戏手柄上可以稍微方便一点。\n游戏手柄:八位堂Zero2小手柄。 软件:AntiMicroX\n手柄连接计算机 看说明书即可。\n在AntiMicroX中设定快捷键 打开软件后,点手柄上的按键,可以看到有的按键映射是错的,点软件窗口左下角的“游戏控制器映射”,先矫正一遍。\n然后在软件主窗口中找到你想设定快捷键的按键(可以通过按手柄上的对应按键确定它的位置),鼠标点击,设定快捷键即可。\n在我的SuperMemo中,目前是有很多的摘录卡片,以及很少的填空题卡片。我习惯于一边复习一边编辑卡片内容,比如把一段紧凑的话分成一行一行的句子,修改一些措辞。\n在SM中,当我的鼠标点击了卡片,可以看作我进入了“编辑模式”,如果这张卡片看完了,想进入下一张卡片,我需要先退出“编辑模式”(按ESC),再按回车键。可以把这两个键简化为一个键,在游戏手柄上按一次,无论你是不是在“编辑模式”,都可以跳转到下一张卡片。\n在AntiMicroX窗口中鼠标点击按键,在弹出的窗口中点击“高级选项”,又弹出一个窗口,在“分配”栏里点击“小方块”(里面出现\u0026quot;\u0026hellip;\u0026quot;),这时候在键盘上输入想设定的按键。设定两个按键,那么点击游戏手柄上的按键时,这两个按键会依次执行。\n我的按键设定 设定原则是,使用越多的按键放在越容易按的位置。\n使用手柄时我习惯竖着拿。除了十字方向键(我不用),最不方便按的是左肩键,其次是右肩键,然后是开始键和选择键。\n因此,我的设定是:\n左肩键,评分5。一是用的少,二来对一张卡片评分5时需要调整手的握姿,这样就会很慎重、很有仪式感。如果这种情况下还非要把一张卡片评分为5,说明真的掌握的很好了。\n右肩键,评分2。答错时按的。\nA,评分3/ENTER。最容易按的键。因为在评分时,按Enter键的效果和按3是一样的,所以设定为Enter,这样在复习时就不用频繁移动手指了。\nX,评分1。\nB,评分4。\nY,ESC+Enter,切换到下一张卡片。\n为什么A设定了Enter,这里还要再设定一个ESC+Enter呢?或者能不能都用ESC+Enter呢?你复习的时候亲手去操作一下就明白了。评分时按ESC+Enter没效果;阅读并编辑卡片时,按Enter不会跳转到下一张卡片,而是在卡片里添加一个空行。\n这是一件很主观的事情,怎么舒服怎么来。\n全文完。\n","date":"Dec 30","permalink":"https://o5o.me/post/supermemo-gamepad/","tags":["SuperMemo"],"title":"游戏手柄用于SuperMemo复习"},{"categories":["Python"],"contents":"编写一个名为is_abecedarian 的函数, 如果单词中的字符以字符表的顺序出现 (允许重复字符),则返回True。有多少个具备这种特征的单词?\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 def word_abcde(word): alphabets = \u0026#34;abcdefghijklmnopqrstuvwxyz\u0026#34; i = 0 front = 0 #前一个字母的序数 mine = 0 #当前字母的序数 while i \u0026lt; len(word): index = 0 while index \u0026lt; len(alphabets): # print(i,word[i],index,alphabets[index]) if word[i] == alphabets[index]: mine = index if mine \u0026lt; front: return False else: front = mine break index += 1 i += 1 return True def is_abecedarian(file_in): count = 0 for line in file_in: word = line.strip() if word_abcde(word): count += 1 #print(word) print(count) fin = open(\u0026#39;words.txt\u0026#39;) is_abecedarian(fin) 我所使用的这个单词表里,这样的单词有596个。\n","date":"Dec 29","permalink":"https://o5o.me/post/think_python_exercise_9.6/","tags":null,"title":"Think Python Exercise 9.6"},{"categories":["Python"],"contents":"编写一个名为uses_all的函数,接受一个单词和一个必须使用的字符组成的字符串。如果该单词包括此字符串中的全部字符至少一次,则返回True。你能统计出多少单词包含了所有的元音字符aeiou吗?如果换成aeiouy 呢?\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 def uses_all(word, muststr): for i in muststr: count = 0 index = 0 while index \u0026lt; len(word): if i == word[index]: break index += 1 if index == len(word): return False return True def count_word(file_in, als): count = 0 for line in file_in: word = line.strip() if uses_all(word, als): count += 1 print(count,\u0026#34;个单词包含了\u0026#34;,als,\u0026#34;所有的字符\u0026#34;) fin = open(\u0026#39;words.txt\u0026#39;) count_word(fin, \u0026#34;aeiouy\u0026#34;) 运行结果:\n598 个单词包含了 aeiou 所有的字符\n42 个单词包含了 aeiouy 所有的字符\n可以发现,uses_all函数和9.4的uses_only函数的实现一模一样,其实没必要再写一遍。\n","date":"Dec 29","permalink":"https://o5o.me/post/think_python_exercise_9.5/","tags":null,"title":"Think Python Exercise 9.5"},{"categories":["Python"],"contents":"编写一个名为 avoids 的函数,接受一个单词和一个指定禁止使用字符的字符串,如果单词中不包含任意被禁止的字符,则返回True 。\n修改你的程序,提示用户输入一个禁止使用的字符,然后打印不包含这些字符的单词的数量。你能找到一个5个禁止使用字符的组合,使得其排除的单词数目最少么?\n你能找到一个\u0026quot;5个禁止使用字符\u0026quot;的组合,使得其排除的单词数目最少么? 分析:从26个字母中挑选5个字母构成一个组合,打印不包含这些字母的单词的数量,看哪个组合下被排除出去的单词数目最少。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def avoids(word,ause): #ause,avoid use for i in ause: for letter in word: if letter == i: return False return True def has_no_string(file_in, nals): #nals指的是no alphabets,字母 count_nals = 0 #不含用户指定的各个字符的单词的个数 for line in file_in: word = line.strip() if avoids(word,nals): count_nals += 1 print(\u0026#34;不含\u0026#34;,nals,\u0026#34;的单词有\u0026#34;,count_nals,\u0026#34;个\u0026#34;) text = input(\u0026#34;请输入一个禁止使用的字符串,字符串里的字母都不会出现在单词中\\n\u0026#34;) fin = open(\u0026#39;words.txt\u0026#39;) has_no_string(fin,text) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 def avoids(word,ause): #ause,avoid use for i in ause: for letter in word: if letter == i: return False return True def has_no_string(file_in, nals): #nals指的是no alphabets,字母 count_nals = 0 #不含用户指定的各个字符的单词的个数 for line in file_in: word = line.strip() if avoids(word,nals): count_nals += 1 # print(\u0026#34;不含\u0026#34;,nals,\u0026#34;的单词有\u0026#34;,count_nals,\u0026#34;个\u0026#34;) return count_nals def alp5group(file_in): #5个字母的组合,起名困难 alphabets = \u0026#34;abcdefghijklmnopqrstuvwxyz\u0026#34; count_word = 0 # 不含指定的5个字母的单词的数量 namegroup = \u0026#39;\u0026#39; # 不含5个字符的组合,单词最多 i = 0 while i \u0026lt; len(alphabets): group = \u0026#39;\u0026#39; group += alphabets[i] j = 1 while j \u0026lt; len(alphabets[i:]): group += alphabets[i+j] k = 1 while k \u0026lt; len(alphabets[i+j:]): group += alphabets[i+j+k] l = 1 while l \u0026lt; len(alphabets[i+j+k:]): group += alphabets[i+j+k+l] m = 1 while m \u0026lt; len(alphabets[i+j+k+l:]): group += alphabets[i+j+k+l+m] count = has_no_string(file_in,group) if count \u0026gt; count_word: count_word = count namegroup = group m += 1 l += 1 k += 1 j += 1 i += 1 print(\u0026#34;组合\u0026#34;,namegroup,\u0026#34;被排除出去的单词数目最少,单词数量为\u0026#34;,count_word) fin = open(\u0026#39;words.txt\u0026#39;) alp5group(fin) 下面是使用 in 操作符优化后的方案,可以用 in 真的太幸福了😭。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def avoids(word,ause): #ause,avoid use for i in ause: if i in word: return False return True fin = open(\u0026#39;words.txt\u0026#39;) text = input(\u0026#34;请输入一个禁止使用的字符串,字符串里的字母都不会出现在单词中\\n\u0026#34;) count = 0 #单词总数 count_n = 0 #不含用户输入字符的单词的个数 for line in fin: word = line.strip() count += 1 if avoids(word,text): count_n += 1 print(\u0026#34;不含\u0026#34;,text,\u0026#34;的单词有\u0026#34;,count_n,\u0026#34;个,单词一共:\u0026#34;,count) ","date":"Dec 29","permalink":"https://o5o.me/post/think_python_exercise_9.4/","tags":null,"title":"Think Python Exercise 9.4"},{"categories":["Python"],"contents":"编写一个名为 avoids 的函数,接受一个单词和一个指定禁止使用字符的字符串,如果单词中不包含任意被禁止的字符,则返回True 。\n修改你的程序,提示用户输入一个禁止使用的字符,然后打印不包含这些字符的单词的数量。你能找到一个5个禁止使用字符的组合,使得其排除的单词数目最少么?\n你能找到一个\u0026quot;5个禁止使用字符\u0026quot;的组合,使得其排除的单词数目最少么? 分析:从26个字母中挑选5个字母构成一个组合,打印不包含这些字母的单词的数量,看哪个组合下被排除出去的单词数目最少。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def avoids(word,ause): #ause,avoid use for i in ause: for letter in word: if letter == i: return False return True def has_no_string(file_in, nals): #nals指的是no alphabets,字母 count_nals = 0 #不含用户指定的各个字符的单词的个数 for line in file_in: word = line.strip() if avoids(word,nals): count_nals += 1 print(\u0026#34;不含\u0026#34;,nals,\u0026#34;的单词有\u0026#34;,count_nals,\u0026#34;个\u0026#34;) text = input(\u0026#34;请输入一个禁止使用的字符串,字符串里的字母都不会出现在单词中\\n\u0026#34;) fin = open(\u0026#39;words.txt\u0026#39;) has_no_string(fin,text) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 def avoids(word,ause): #ause,avoid use for i in ause: for letter in word: if letter == i: return False return True def has_no_string(file_in, nals): #nals指的是no alphabets,字母 count_nals = 0 #不含用户指定的各个字符的单词的个数 for line in file_in: word = line.strip() if avoids(word,nals): count_nals += 1 # print(\u0026#34;不含\u0026#34;,nals,\u0026#34;的单词有\u0026#34;,count_nals,\u0026#34;个\u0026#34;) return count_nals def alp5group(file_in): #5个字母的组合,起名困难 alphabets = \u0026#34;abcdefghijklmnopqrstuvwxyz\u0026#34; count_word = 0 # 不含指定的5个字母的单词的数量 namegroup = \u0026#39;\u0026#39; # 不含5个字符的组合,单词最多 i = 0 while i \u0026lt; len(alphabets): group = \u0026#39;\u0026#39; group += alphabets[i] j = 1 while j \u0026lt; len(alphabets[i:]): group += alphabets[i+j] k = 1 while k \u0026lt; len(alphabets[i+j:]): group += alphabets[i+j+k] l = 1 while l \u0026lt; len(alphabets[i+j+k:]): group += alphabets[i+j+k+l] m = 1 while m \u0026lt; len(alphabets[i+j+k+l:]): group += alphabets[i+j+k+l+m] count = has_no_string(file_in,group) if count \u0026gt; count_word: count_word = count namegroup = group m += 1 l += 1 k += 1 j += 1 i += 1 print(\u0026#34;组合\u0026#34;,namegroup,\u0026#34;被排除出去的单词数目最少,单词数量为\u0026#34;,count_word) fin = open(\u0026#39;words.txt\u0026#39;) alp5group(fin) 下面是使用 in 操作符优化后的方案,可以用 in 真的太幸福了😭。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def avoids(word,ause): #ause,avoid use for i in ause: if i in word: return False return True fin = open(\u0026#39;words.txt\u0026#39;) text = input(\u0026#34;请输入一个禁止使用的字符串,字符串里的字母都不会出现在单词中\\n\u0026#34;) count = 0 #单词总数 count_n = 0 #不含用户输入字符的单词的个数 for line in fin: word = line.strip() count += 1 if avoids(word,text): count_n += 1 print(\u0026#34;不含\u0026#34;,text,\u0026#34;的单词有\u0026#34;,count_n,\u0026#34;个,单词一共:\u0026#34;,count) ","date":"Dec 29","permalink":"https://o5o.me/post/think_python_exercise_9.3/","tags":null,"title":"Think Python Exercise 9.3"},{"categories":["Python"],"contents":"1939年,Ernest Vincent Wright出版了一本名为 《Gadsby》 的小说,该小说里完全没有使用字符“e”。由于“e”是最常用的英文字符,因此这并不容易做到。\n事实上,不使用这个最常用的符号(字符e)来构建一个孤立的想法是很难的。开始进展缓慢,但是经过有意识的、长时间的训练,你可以逐渐地熟练。\n好啦,不再说题外话了(让我们开始编程练习)。\n写一个叫做has_no_e的函数,如果给定的单词中不包含字符“e”,其返回 True 。\n修改上一节中的程序,只打印不包含“e”的单词,并且计算列表中不含“e”单词的比例。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 fin = open(\u0026#39;words.txt\u0026#39;) def has_no_e(file_in): count_ne = 0 #不含e的单词的个数 count = 0 #总单词个数 for line in file_in: word = line.strip() index = 0 while index \u0026lt; len(word): if word[index] == \u0026#39;e\u0026#39;: break else: index = index + 1 if index == len(word): # print(word) #因为单词比较多,不一个个列出来了 count_ne = count_ne + 1 count = count + 1 print(\u0026#34;不含e的单词有\u0026#34;,count_ne,\u0026#34;个\u0026#34;,\u0026#34;单词一共有\u0026#34;,count,\u0026#34;个\u0026#34;,\u0026#34;不含“e”单词的比例为\u0026#34;,count_ne/count*100,\u0026#34;%\u0026#34;) has_no_e(fin) 上面是一个字母一个字母判断单词是不是包含‘e’的,下面是在 if 中使用 in 操作符简化后的方案:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def has_no_e(word): if \u0026#39;e\u0026#39; not in word: return True else: return False fin = open(\u0026#39;words.txt\u0026#39;) count = 0 #单词总数 count_ne = 0 #不含e的单词数 for line in fin: word = line.strip() count += 1 if has_no_e(word): count_ne += 1 #print(word) print(\u0026#34;不含e的单词有\u0026#34;,count_ne,\u0026#34;个\u0026#34;,\u0026#34;单词一共有\u0026#34;,count,\u0026#34;个\u0026#34;,\u0026#34;不含“e”单词的比例为\u0026#34;,count_ne/count*100,\u0026#34;%\u0026#34;) 所得结果都是一样的:\n不含e的单词有 37641 个 单词一共有 113809 个 不含“e”单词的比例为 33.07383423103621 %\n","date":"Dec 29","permalink":"https://o5o.me/post/think_python_exercise_9.2/","tags":null,"title":"Think Python Exercise 9.2"},{"categories":["Python"],"contents":"编程写一个程序,使得它可以读取 words.txt ,然后只打印出那些长度超过20个字符的单词(不包括空格)。\n1 2 3 4 5 6 fin = open(\u0026#39;words.txt\u0026#39;) for line in fin: word = line.strip() if len(word) \u0026gt; 20: print(word) ","date":"Dec 28","permalink":"https://o5o.me/post/think_python_exercise_9.1/","tags":null,"title":"Think Python Exercise 9.1"},{"categories":[null],"contents":"和女朋友聊天的时候,她发来的有趣的话我都会收藏起来,我想将这些内容永久保存下来。文字复制粘贴即可,那么语音怎样导出保存呢? 先声明:免费,真的不收费。 目的:将微信语音聊天记录导出为mp3格式。 思路:将语音转存为笔记,微信会将语音以.silk格式文件的形式插入笔记,解码这个文件为MP3格式即可。 在手机上,从聊天框中收藏想要导出的语音,在微信“我”-“收藏”页面中,点开语音,再点集右上角“…”,在屏幕底部弹窗中选择“转存为笔记”; 在电脑上登录微信,点开“收藏”页面,点开刚才的那条笔记,按快捷键“Ctrl+A”全选笔记的内容,再任意打开一个聊天窗口(我一般用“文件传输助手“那个窗口”),按“Ctrl+V”粘贴内容,点击发送,可以看到有一个名称很长、后缀为“.silk”的文件被发送了出去,在这个文件上点击鼠标右键,点击“另存为”,将文件保存到电脑上。 从Github下载项目silk2mp3,如果是win系统,运行目录“silk2mp3-master\\windows”内的silk2mp3.exe程序,“导入待转换文件”——“更改输出目录”——“开始转换”即可,不用更改其他设置。这样你就得到了一条mp3格式的聊天语音。 最后想说 非常感谢silk2mp3这个小工具的作者,虽然发布于七年前,但今天仍然可以使用。但是我在网上看到竟然有人拿着这样一个免费的小工具牟利,这是非常可耻的。 网上有很多很多的收费的软件用于导出微信语音聊天记录,别人自己写的软件,我不好说什么,但我总感觉这些软件有点“趁人之危”。 这篇文章,献给所有需要的人。能为大家节省一些时间,我已深感满足。\n","date":"Nov 02","permalink":"https://o5o.me/post/wp/wechat-voice-chat-record-export-to-mp3/","tags":[null],"title":"导出、保存微信语音聊天记录为MP3文件"},{"categories":[null],"contents":"目的:修改/System/Library/CoreServices/SystemVersion.plist这个文件。\n关闭SIP 我是用OC引导的,并且版本比较久了,我也不知道自己用的是啥版本。 首先,用Hackintool查看自己电脑的OC版本,我的是0.6.9。我现在是能用就行,不追求最新。 然后去官网下载对应版本的OpenCore Configurator。这个官网看起来不太正规的样子🤦♂️。 打开OpenCore Configurator,挂载EFI分区,修改config.plist,像图中这样修改这个值。这个值的由来,可以看文章最下方的参考资料链接。修改后保存,重启电脑就行了。 挂载分区 在磁盘工具里查看分区的名字,就是“设备”后跟的那一串,我这里是disk1s5,记住它。 在桌面上新建一个文件夹,命名为test。即这个文件夹的路径是:/Users/aoyu/Desktop/test/ ,其中aoyu是我的用户名。 在终端中运行这句命令:\nsudo mount -o nobrowse -t apfs /dev/disk1s5 /Users/aoyu/Desktop/test 那么要修改的路径“/System/Library/CoreServices”就被映射为了“/Users/aoyu/Desktop/test/System/Library/CoreServices”。\n动手修改 思路:先把要修改的SystemVersion.plist文件复制一份出来,修改后再放回去。 我先把这个文件拷贝到桌面上:\ncp /System/Library/CoreServices/SystemVersion.plist /Users/aoyu/Desktop/ 修改后再放回去:\nsudo rm -f /Users/aoyu/Desktop/test/System/Library/CoreServices/SystemVersion.plist sudo cp /Users/aoyu/Desktop/SystemVersion.plist /Users/aoyu/Desktop/test/System/Library/CoreServices/ 上面第一句是把原来的文件删掉,第二句是把修改后的文件拷贝过去。 这个时候,系统文件还没被真正修改,需要运行下面这两条命令,生成快照并重启系统。\nsudo bless --folder /Users/aoyu/Desktop/test/System/Library/CoreServices/ --bootefi --create-snapshot sudo reboot 然后就OK了。\n参考资料 我为解决这个问题所参考的所有有效的内容的链接都列在下面了,我所做的只是将它们的内容整合到了一起,希望给有同样困扰的你带去一些帮助。\nOpenCore如何查看自己使用的OC引导版本号是多少 Download OpenCore Configurator 如何正确关闭macOS 11和12的SIP以及authenticated-root macOS Big Sur 解决系统文件不可修改 Hackintool的下载地址 ","date":"Oct 25","permalink":"https://o5o.me/post/wp/hackintosh_modify_system_files/","tags":[null],"title":"修改黑苹果系统文件"},{"categories":[null],"contents":"普通话考试分数出来了,二甲,分数是90.8,记录一下考完才知道的一些东西。\n普通话查分网址:http://www.cltt.org/#/scoreQuery 我考试时是找机构报的名,报名费是120,官网报名费是50。我起初比较排斥机构,但为什么我还是通过机构报了名呢?我想报名的时候,官网统一报名一直没有开放,因此,如果想考普通话,就只能通过机构,机构直接联系测试站,报名后直接到测试站考试。通过机构报名也比较省事儿,如果是官方统一报名,虽说便宜了,但考试前需要线下去交费、确认(目前是这样),也就是需要多跑一趟,比较麻烦。 准考证上的考试报道时间是8:00,实际上我7点半就已经到考场了,已经在候考了。考试时不到半个小时就考完了。 考试时是有一个个单独的小隔间,还是蛮隔音的。 进小隔间前,候考时会发纸质试题让你看,纸上的题就是一会儿的考试题,遇到不会读的字词可以用手机查。 咨询机构的老师,说是成绩要3周才出来。我是7月3号考试,分数在7月15号出来的,也就是不到两周分数就出来了。 Block Ref ","date":"Jul 18","permalink":"https://o5o.me/post/wp/putonghua_shuiping_ceshi/","tags":[null],"title":"记一次普通话考试"},{"categories":[null],"contents":"我之前是用Git在不同设备之间同步Obsidian的数据的,很麻烦。昨天了解到新出了一个Obsidian插件叫Remotely Save,可以实现用WebDAV在不同设备间同步数据。 如果用 WebDAV同步数据,必须要先开启CORS。经测试,坚果云是不行的。好在我之前自己用Cloudreve搭建了一个网盘,支持WebDAV,开启CORS就可以了。 在Cloudreve的配置文件conf.ini中加入下面一段配置:\n; 跨域配置 [CORS] AllowOrigins = * AllowMethods = * AllowHeaders = * AllowCredentials = true 之后更新配置(systemctl daemon-reload)即可。\n参考资料: 配置文件 – Cloudreve\n","date":"Dec 26","permalink":"https://o5o.me/post/wp/cloudreve_webdav_obsidian_synchronization/","tags":["Obsidian"],"title":"Cloudreve开启CORS实现用WebDAV在不同设备间同步Obsidian数据"},{"categories":["SRS"],"contents":"使用Ankiweb同步卡片在我这里确实是有些慢,所以我自己尝试搭建了自己的Anki同步服务Ankisyncd,搭建好后又发现安卓版的Ankidroid只支持https同步,所以我又用Apache2对同步地址进行了反向代理。\n文章前半部分其实还是老生常谈,最精华的,我觉得是反向代理那部分内容。这篇文章全文无图片,只有文字,可以看作是整个实现过程的思路和要点整理,我省去了很多前置步骤,例如许多命令我都是直接拿来用的,而它们在你自己的服务器中可能需要提前安装。 在文章最后会附上我所参考的资料,那里一般有完整、详细的说明,如果想自己搭建类似服务,遇到问题后建议先去浏览这些内容。 文章分成三部分,先介绍在服务器端怎样搭建Ankisyncd,之后介绍电脑版Anki和安卓版Ankidroid怎样配置,在Ankidroid同步失败后又介绍了在服务器端怎样用Apache2实现反向代理。我的服务器是阿里云的学生轻量应用服务器,系统版本是Ubuntu18.04。\n服务器端 在服务器安装Ankisyncd[1] 大致过程如下[2]:\ngit clone https://github.com/tsudoko/anki-sync-server.git /usr/local/anki-sync-server git submodule update --init cd anki-bundled pip3 install -r requirements.txt # 可预先从requirements.txt中删去“pyaudio”,这个不是必装的。 pip3 install webob 修改/usr/local/anki-sync-server下的ankisyncctl.py文件,把开头的#!/usr/bin/env python改为#!/usr/bin/env python3 按教程,这样就算安装好了,但我第二步但那条命令git submodule update --init运行之后一直卡在Cloning into '/usr/local/anki-sync-server/anki-bundled'...这里,原因是github访问速度太慢。我一开始想的办法是把github的仓库文件克隆到gitee(码云),修改 /usr/local/anki-sync-server下的.gitmodules文件,把url改成码云的链接。我也在git里add和commit了,但还是卡在这里。后来我用了一个粗暴的办法,从github下载库文件后,直接上传到vps上,解压缩到anki-bundled文件夹里面。 安装完成。\n启动Ankisyncd[3] cd /usr/local/anki-sync-server screen -S anki python3 -m ankisyncd 之后在浏览器里访问“http://你的ip:27701”,浏览器页面上会显示一句“Anki Sync Server”。注意,如果访问不了,可能是需要在阿里云后台开放端口27701。 按Ctrl+A,D,退出会话,Ankisyncd会继续在后台运行。\n管理用户 创建用户:\ncd /usr/local/anki-sync-server ./ankisyncctl.py adduser 回车后会提示输入密码。 其他操作:\nCommands: adduser - add a new user deluser - delete a user lsuser - list users passwd - change password of a user 电脑端Anki配置 从Releases · tsudoko/anki-sync-server · GitHub了解到,Ankisyncd最新版本是2.1.0,支持的Anki版本是:Anki from 2.0.27 to 2.1.16;AnkiDroid 2.3 and up。也就是说,目前最新的Anki版本(2.1.38,2021年1月23日)是无法使用的。经测试,在2.1.38版本中用刚刚设定的账号和密码登录时会提示“用户名或密码错误”。我用的版本是2.1.15。从2.1.20开始就不正常、无法登录了(2.1.20还是2.1.21我给忘了,可以登录但同步有问题)。 Anki电脑版若要登录到自建服务器,需要安装一个插件。 打开“工具”——“附加组件”——“查看文件”,新建一个文件夹“AnkiServer”(随便命名),在新建的文件夹内新建文件__init__.py,填入如下内容:\nimport anki.sync, anki.hooks, aqt addr = \u0026#34;http://127.0.0.1:27701/\u0026#34; # put your server address here anki.sync.SYNC_BASE = \u0026#34;%s\u0026#34; + addr def resetHostNum(): aqt.mw.pm.profile[\u0026#39;hostNum\u0026#39;] = None anki.hooks.addHook(\u0026#34;profileLoaded\u0026#34;, resetHostNum) 把里面的ip地址换成你自己的服务器的地址。 重启Anki,点击同步按钮,输入用户名和密码即可正常同步。\n安卓端Ankidroid配置 把上面的同步地址填入较新版本的Ankidroid(版本2.14.2),点击同步按钮会提示同步出错,因为那个地址是http的,而这种传输方式已被禁止。使用较低版本的Ankidroid(2.8.3),正常。 我不想使用旧版本的App,因此就需要让同步数据加密传输,即让同步地址由http变成https的。由于我的vps已经安装了Apache2,因此我使用Apache反向代理转发同步地址。 由于我对这块内容并不是很熟,所以下面的步骤当中肯定有冗余。 我的计划是,先配置一个不使用ssl的反向代理(使用80端口),然后用Certbot获取Let’s Encrypt证书(会自动生产一个新的conf配置文件,使用443端口)。\n启用模块 a2enmod proxy a2enmod proxy_http a2enmod ssl 新建Apache配置文件 cd /etc/apache2/sites-available vim your_domain.conf 在your_domain.conf文件中填入以下内容[4]\nServerName anki.xiake.me ProxyPass http://127.0.0.1:27701/ ProxyPassReverse http://127.0.0.1:27701/ UseCanonicalName off ProxyRequests off ProxyPreserveHost on 保存后,启用配置\na2ensite your_domain-le-ssl.conf systemctl reload apache2 在浏览器地址中填入你设置的域名,应该就能得到刚才访问那个ip地址一样的结果。\n使用Certbot获得Let’s Encrypt的SSL证书[5] certbot --apache -d your_domain 按提示操作即可,之后会得到一个新的conf配置文件,类似下面这样\nServerName your\\_domain ProxyPass http://127.0.0.1:27701/ ProxyPassReverse http://127.0.0.1:27701/ UseCanonicalName off ProxyRequests off ProxyPreserveHost on SSLCertificateFile /etc/letsencrypt/live/your\\_domain/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/your\\_domain/privkey.pem Include /etc/letsencrypt/options-ssl-apache.conf 重新加载配置,之后在浏览器里就可以使用加密地址访问了。\na2ensite your_domain-le-ssl.conf systemctl reload apache2 在Ankidroid中的设置 打开“侧边栏”——“设置”——“高级设置”——“自定义同步服务器”,选中“使用自定义同步服务器”,把“同步地址”和“媒体文件同步地址”分别设为:\nhttps://your_domain/ https://your_domain/msync 而后返回主页,点击同步按钮,输入设定的用户名和密码,就可以同步了。\n参考资料 [1] github仓库, ankisyncd\n[2] 知乎专栏文章, (一 超详细自架 Ankisyncd 版)\n[3] 博客文章, Anki同步服务器搭建教程\n[4] Anki Community Wiki, HTTPS Encryption with Apache Proxy\n[5] How To Secure Apache with Let’s Encrypt on Ubuntu 18.04\n","date":"Jan 24","permalink":"https://o5o.me/post/wp/ankisyncd_apache_https_ssl/","tags":["Anki"],"title":"配置Anki同步服务Ankisyncd,并使用Apache2反代实现https加密同步"},{"categories":[null],"contents":"Apple Music(iTunes)是我一直在用的软件,充分满足了我对音乐管理的需求,深得我心。但我在黑苹果系统下,一直受本文标题所述问题的困扰,不能正常使用。 苦此问题久矣。一直没找到解决方案,直到昨天晚上。\n之前写过一篇在Surface Pro4安装黑苹果的文章,其实我对折腾这些东西并没有太多兴趣,也不追求最新的功能,只想好好用。现在我还在用10.15.2的系统,一直都没更新。 这个问题很具有迷惑性。因为如果我注册一个新的账号登录Apple Music,并选择免费试用或者单独付费,上述问题是不存在的,初次登录时会有“转移”按钮提示转移资料库(记忆中是这样的提示),可以正常使用。而一旦我加入家庭组,问题就出现了:无法转移此设备,此设备必须与您的Apple ID关联……,因此我一直以为出现这个问题的原因是我的黑苹果没有成功“洗白”。 昨天晚上看到一篇文章[1],说Apple Music通过MAC地址来区分不同设备,修改即可。虽然我已经很困了很想睡觉,但我隐隐感觉他说的是正确的,一番尝试后真的成功了。 思路:随机生成一个MAC地址;挂载EFI分区;编辑路径EFI/CLOVER/ACPI/patched/下的SSDT-rmne.aml文件;用随机生成的MAC地址替换原MAC地址(黑苹果一般都是11-22-33-44-55-66);保存后卸载EFI分区;重启。 下面详述。 1.随机生成MAC地址 可以用在线工具,我用的那个广告挺多,就不放链接了。直接搜“MAC地址生成”可以找到。 2.挂载EFI分区 使用软件Clover Configurator,这个软件是必备的,相信装了黑苹果的各位都有。不再多说。 3.编辑SSDT-rmne.aml文件 需要用到软件MaciASL,下载地址:https://github.com/acidanthera/MaciASL/releases 4.替换原MAC地址 用MaciASL打开SSDT-rmne.aml文件,像普通的文本编辑那样修改即可,如图。 5.卸载EFI分区、重启 到软件Clover Configurator里卸载EFI分区。我曾经是不卸载直接重启的,但我发现这样子重启后进入Clover界面会很困难,所以我养成了先卸载EFI分区再重启的习惯。玄学。 7.测试 打开Apple Music,应该会提示需要授权,登录账号后就能正常使用了。\n在最后 我开始折腾黑苹果是在去年11月份,而我参考的知乎的那篇解决问题的文章是去年7月份写的,也就是说在我遇到这个问题之前已经有人解决了,而我直到今年8月份才搜索到这个解决方法。虽然知乎那篇文章是中文的、发表在国内的平台上的,但我却是用Google找到的。最近我遇到的几个棘手问题都是用Google找到解决方案的。这不能不让我反思我的信息获取能力以及信息获取途径。\n参考资料 [1] 解决黑苹果Apple Music资料库不能转移的问题 https://zhuanlan.zhihu.com/p/74910556\n[2] “You cannot associate this computer with another Apple ID for 72 days” https://www.tonymacx86.com/threads/you-cannot-associate-this-computer-with-another-apple-id-for-72-days.200078/\n[3] Apple Music not downloading song on my disk https://github.com/daliansky/XiaoMi-Pro-Hackintosh/issues/193\n","date":"Aug 09","permalink":"https://o5o.me/post/wp/hackintosh_apple_music_errors/","tags":[null],"title":"黑苹果Apple Music提示“无法转移此设备 此设备必须与您的Apple ID关联”无法使用iTunes Match且无法离线下载播放"},{"categories":[null],"contents":"7月29日晚上画的。 图象: 怎么样,是不是很像。 Matlab代码:\nclear clc x=-pi:0.01:pi; f=0; n=7; fx=zeros(n,length(x)); for i=1:n for j=1:length(x) f=(1/(2*i-1))*(sin((2*i-1)*x(j))); fx(i,j)=f; end end j=1; for i=[1,2,7] %plot(x,sum(fx(1:i,:),1),\u0026#39;DisplayName\u0026#39;,[\u0026#39;n=\u0026#39;,num2str(i)]); p(j)=plot(x,sum(fx(1:i,:),1)); hold on; j=j+1; end %legend(\u0026#39;n=1\u0026#39;,\u0026#39;n=2\u0026#39;,\u0026#39;n=3\u0026#39;,\u0026#39;n=4\u0026#39;,\u0026#39;n=5\u0026#39;,\u0026#39;n=6\u0026#39;,\u0026#39;n=7\u0026#39;) %legend(\u0026#39;n=1\u0026#39;,\u0026#39;n=2\u0026#39;,\u0026#39;n=7\u0026#39;) axis equal axis([-pi*1.2 pi*1.2 -pi*0.8 pi*0.8]) plot([-pi,0],[-pi/4,-pi/4],\u0026#39;m-o\u0026#39;,[0,pi],[pi/4,pi/4],\u0026#39;m-o\u0026#39;); plot([-pi 0 pi],[0 0 0],\u0026#39;ro\u0026#39;,\u0026#39;MarkerFaceColor\u0026#39;,\u0026#39;m\u0026#39;);%绘制实心圆点 plot([-pi,-pi],[0,-pi/4],\u0026#39;b--\u0026#39;,[pi,pi],[0,pi/4],\u0026#39;b--\u0026#39;); set(gca,\u0026#39;xaxislocation\u0026#39;,\u0026#39;origin\u0026#39;,\u0026#39;yaxislocation\u0026#39;,\u0026#39;origin\u0026#39;) %xy坐标轴在原点处相交 set(gca,\u0026#39;XTick\u0026#39;,[-pi pi],\u0026#39;XTickLabel\u0026#39;,{\u0026#39;-PI \u0026#39;,\u0026#39;PI\u0026#39;})%添加坐标注释 %set(gca,\u0026#39;YTick\u0026#39;,[-pi/4 pi/4],\u0026#39;YTickLabel\u0026#39;,{\u0026#39;-PI/4\u0026#39;,\u0026#39;PI/4\u0026#39;})%添加坐标注释 set(gca,\u0026#39;YTick\u0026#39;,pi/4,\u0026#39;YTickLabel\u0026#39;,{\u0026#39;PI/4\u0026#39;}) text(0,-pi/4,\u0026#39; -PI/4\u0026#39;)%添加坐标注释 text(pi/30+0.15,pi/4+0.2,\u0026#39;\\leftarrow n=7\u0026#39;)%添加坐标注释 text(pi/30+0.75,pi/4+0.2,\u0026#39;\\leftarrow n=2\u0026#39;)%添加坐标注释 text(pi/30+0.3,pi/8,\u0026#39;\\leftarrow n=1\u0026#39;)%添加坐标注释 hold off; legend(p(:),{\u0026#39;n=1\u0026#39;,\u0026#39;n=2\u0026#39;,\u0026#39;n=7\u0026#39;}) ","date":"Aug 06","permalink":"https://o5o.me/post/wp/fourier_series_expansion_graphic/","tags":[null],"title":"一个傅里叶级数展开式的图象"},{"categories":[null],"contents":"今天看了看综合除法。\n现在有这样一个问题: f(x)=2x^5-5x^3-8x, g(x)=x+3 问g(x)除f(x)的商式q(x)和余式r(x)是多少。 要怎么来算呢?你可能会按下面的格式来做除法: 这样一步步算下来,于是你知道了,哦,商式q(x)=2x^4-6x^3+13x^2-39x+109,余式r(x)=-327. 那我接下来又有一个问题要问你,我要让你把f(x)表成g(x)=x+3的方幂和,即表成: c_0+c_1(x+3)+c_2(x+3)^2+\\cdots 的形式。 这时你要怎么办呢?还是按上面的方法一步步算吗,那可要算很久喽。 有没有什么简便的算法呢?我们先来观察f(x),g(x),q(x),r(x)这几个式子。 f(x),g(x),q(x),r(x)的次数分别是5、1、4、0,于是我们可以大胆猜测,对于所有这样的求一个1次多项式除一个n次多项式的商式和余式的问题,商式q(x)的次数总是比被除式f(x)的次数少1,而余式r(x)总是一个常数。(这里不再证明,到具体例子的时候验证。) 那这样一来,求“商式、余式”的问题就转化成了求“商式、余式的多项式系数”的问题,因为n次多项式是有标准形式的,我们知道了多项式的系数,就等于知道了这个多项式。比如,如果我们知道了上面q(x)的系数是2,-6,13,-39,109,那么就可以把q(x)写出来:q(x)=2x^4-6x^3+13x^2-39x+109。\n接下来,为了方便寻找规律,我们看看这个求商式、余式的问题的一般形式: 设 f(x)=a_nx^n+a_{n_1}x^{n-1}+\\cdots+a_1x+a_0, g(x)=x-c,c是一个常数。 g(x)除f(x)的商式设为 q(x)=b_{n-1}x^{n-1}+b_{n_2}x^{n-2}+\\cdots+b_1x+b_0 余式或者说余数记为r。 之后我们对这个问题做除法: 我没有写完,但写到这儿就可以发现一些规律浮现了出来。 q(x)=a_nx^{n-1}+\\left(a_{n-1}+ca_n\\right)x^{n-2}+\\left[a_{n-2}+c\\left(a_{n-1}+ca_n\\right)\\right]x^{n-3}+\\cdots, q(x)=b_{n-1}x^{n-1}+b_{n_2}x^{n-2}+\\cdots+b_1x+b_0 对比之后可以得到: b_{n-1}=a_n, b_{n-2}=a_{n-1}+ca_n=a_{n-1}+cb_{n-1}, b_{n-3}=a_{n-2}+cb_{n-2}. 我们不妨按照这个规律继续写下去: b_{n-4}=a_{n-3}+cb_{n-3}, \\cdots, b_1=a_2+cb_2, b_0=a_1+cb_1, r=a_0+cb_0. 即,商式的每一项的系数等于它的前一项的系数的c倍加上被除式中对应项的前一项的系数;余式等于商式最后一项的系数的c倍加上被除式最后一项的系数。 如果这种规律成立(确实成立,后面会验证),那么商式和余式的系数就可以经由被除式和除式的多项式系数计算得到,进而我们就可以得到商式和余式的表达式。\n如何简便地应用这个规律呢? 我们还是以最开始的那个问题为例, f(x)=2x^5-5x^3-8x, g(x)=x+3 问g(x)除f(x)的商式q(x)和余式r(x)是多少。 可以按照下面这种格式来做除法。 第一步 搭框架 因为我们把求商式和余式的问题转化为了多项式系数的加法和乘法问题,所以,我们不再需要写出完整的多项式,而只把各项系数写出来即可。 把f(x)写成标准形式:f(x)=2x^5+0x^4-5x^3+0x^2-8x+0 f(x)的首项系数a_n为2,其余各项系数为0, -5, 0, -8, 0. 另外,为了统一形式,我们把x+3写成x-(-3)。 第二步 计算商式首项系数b_{n-1} 由上面的规律,显然b_{n-1}=a_n,我们填上去。 第三步 计算商式第二项系数b_{n-2} 由b_{n-2}=a_{n-1}+ca_n=a_{n-1}+cb_{n-1}计算得到, cb_{n-1}=-3\\times 2=-6, b_{n-2}=a_{n-1}+ca_n=a_{n-1}+cb_{n-1}=0+(-6)=-6 我们也填上去。 第四步 计算所有剩余的系数 照例,先算cb_{i-1},填到横线上面的空白处,再上下两项相加得到b_i,填到横线下面的空白处,我们计算所有剩余的系数。 即商式各项系数分别为2, -6, 13, -39, 109. 余式或者说余数,为-327. 即商式q(x)=2x^4-6x^3+13x^2-39x+109,余式r(x)=-327. 得到的结果和之前计算的一样,但我们只是列了一个小小的算式,足足省下那么多的纸、省下那么多的时间,的确是够简便的。 这就是“综合除法”。一种简便、简洁的计算一个1次多项式除一个n次多项式的商式和余式的方法。\n接着再来看这个问题:把f(x)表成g(x)=x+3的方幂和,即表成: c_0+c_1(x+3)+c_2(x+3)^2+\\cdots 我们只需要多次使用综合除法即可,计算依然很简便: 怎么还原成表达式呢,我个人喜欢这样写: 先写出来一半表达式 (x+3)[(x+3)[(x+3)[(x+3)[(x+3) 再一层一层地补充完整 (x+3)[(x+3)[(x+3)[(x+3)[(x+3)2-30], (x+3)[(x+3)[(x+3)[(x+3)[(x+3)2-30]+175], (x+3)[(x+3)[(x+3)[(x+3)[(x+3)2-30]+175]-495], (x+3)[(x+3)[(x+3)[(x+3)[(x+3)2-30]+175]-495]+667], (x+3)[(x+3)[(x+3)[(x+3)[(x+3)2-30]+175]-495]+667]-327 然后,再整理一下 2\\left(x+3\\right)^5-30\\left(x+3\\right)^4+175\\left(x+3\\right)^3-495\\left(x+3\\right)^2+667\\left(x+3\\right)-327 这样我们就把f(x)改写成了x+3的方幂和的形式。在标次数的时候,我是直接数的中括号外层的(x+3)的个数。\n那么我们算的到底对不对呢?怎么检验呢?难道再手动一层层地乘回去? 我们用Matlab验证。用expand()函数展开上面得到的方幂和形式的f(x),看看和最初的f(x)是否一样,如果一样那就说明整个综合除法的过程是正确的。\nsyms x f=2*(x+3)^5-30*(x+3)^4+175*(x+3)^3-495*(x+3)^2+667*(x+3)-327; expand(f) 运行结果为:\nans = 2*x^5 - 5*x^3 - 8*x 太棒了,这就是最开始的那个f(x)。f(x)=2x^5-5x^3-8x,我们是正确的。\n上面我们说x-c中的c是一个常数,那么如果c是一个复常数,能否使用综合除法呢?当然可以。 我们再来看一道题: f(x)=x^3-x^2-x, g(x)=x-1+2i 求商式和余数。 将g(x)改写为g(x)=x-(1-2i),应用综合除法: 即商式q(x)=x^2-2ix-(5+2i),余数r=-9+8i。\n综合除法,你会了吗。\n参考资料 [1] 高等代数辅导与习题解答 北大·第四版, p8.\n","date":"Jul 31","permalink":"https://o5o.me/post/wp/principle_and_application_of_integrated_division/","tags":[null],"title":"综合除法原理及应用"},{"categories":[null],"contents":"买它的原因无非是想降低手机对学习的干扰。 我是昨天收到的。收到之后感觉还行,除了打电话、发信息之外几乎什么都做不了(暂时),甚至连一个游戏都没有。\n我学习时有开番茄钟的习惯,那肯定要在手机里装一个这样的APP。\n安装APP K1只能在自带的应用商店里安装app,所以我们手动把app拷贝进去安装是不行的。 其实也挺简单,如果有个视频之类的理解起来会快很多,用文字来描述的话我感觉挺苍白的。我试一下: 总的思路:用你想安装的app的apk文件,替换你在应用商店里下载的app的apk文件。注意:替换操作一定要在你进入app安装界面后再进行(这句话是不是一头雾水、似懂非懂)。详述: 在手机自带应用商店里下载一个app,下载完成、进入安装界面后,按“挂机键”返回主页;在手机自带文件管理器里找到刚才在应用商店里下载的app对应的apk文件存放路径(Android——data——com….ppstore——cache——apk),记住它;在电脑上打开这个路径,进去后把这个apk文件拷贝出来;之后,在电脑上下载好你想安装的app的apk文件,把这个apk文件的文件名改成刚才拷贝出来的那个apk文件的文件名(之所以拷贝出来就是为了方便复制文件名);把改好名的apk文件拷贝到手机上(比如放到Download文件夹里);打开手机文件管理器,进入Download文件夹(或者你刚才放到的那个文件夹),找到你拷贝进去的apk文件,点“选项”——“选择多个”——上下选择,之后点“确定”选中这个apk文件——点“选项”——“复制”,这时窗口刷新,之后一步步进入那个在应用商店里下载的apk文件的存放路径,在那个apk文件上点“选项”——“粘贴”——弹出窗口问是“替换”还是“均保留”,选“替换”(默认)——点“确定”。之后还是点“挂机键”返回主页,点击手机应用商店的图标,这时你会发现,app安装界面上显示,即将安装的app就变成了你想安装的那个app。正常安装即可。成功。 经过多番比较,我最终还是选择了“番茄ToDo”。\n丑陋的主界面 虽然app安装好了,但打开很麻烦,因为手机主界面是固定只显示那几个app,其他的app均被收到了一个叫“应用文件夹”的二级界面里,而且手机也没有后台(其实是有的,只是被隐藏了,但打开后也不能操作,因为没有适配按键),不能直接打开正在运行的app。 那能不能安装第三方桌面呢?我听网上说不可以,其实可以,但很不好用,比如那个“极简桌面”,没有适配按键,只能用“鼠标手”操作,连翻页都做不到。 那怎么办呢,总得想个办法吧。我反正忍不了。 这时我看中了“通知栏”,如果能把常用的应用放在这里,那就方便很多了。 由此引发了第一次危机(其实就这一次)。\n手机“变砖”自救 我安装了一个叫“快启动”的app。这个app其实并没有满足我上面的设想,而是可以在点击通知栏中的图标后打开一个悬浮窗。这个悬浮窗是可以扩展到整个窗口的,甚至,可以把这个app当成桌面来用。 这个app我摸索了好一阵,还把它设为了桌面。感觉如果把它当成桌面来用比原来的桌面要好太多了。但美中不足的是它还是要用“鼠标手”来操作。 我想,如果能直接用按键就好了,要不要试一下?我这样想,但也没多想,就当做实验了。然后我就到手机“鼠标手”设置里把这个app前的勾给去掉了?。 我返回桌面,突然意识到要坏事儿。按键用不了(肯定的)、手机屏幕不能触控,那不就是说我现在丧失了对手机的控制权?。手机变成了一块“板砖”?。 陷入了一个死循环。我想用按键操作手机,那么我就要先用按键操作手机去设置里把这个app的“鼠标手”加回来,或者把这个app卸载掉(即便能进到设置里我也卸载不掉,因为这时卸载按钮是灰的)。 只能请外援了,用adb工具吧。 其实也是经历了一些波折。我用adb工具要怎样救呢,一开始我想的也是直接把这个app卸载掉,发现卸载不掉;停用试试?应用确实被停止运行了,但因为我把它设为了桌面,停止运行后它又会重新运行;后来,我才想到要去设置里把鼠标手给它加回来。那我要怎样打开“设置”呢?“设置”对应的包名是什么呢?这又是一个故事了。不多讲,下面直接写正确答案。\nC:\\Users\\Aoyu\u0026gt;cd \u0026#34;C:\\Users\\Aoyu\\Desktop\\ADB\u0026#34; C:\\Users\\Aoyu\\Desktop\\ADB\u0026gt;adb devices List of devices attached * daemon not running; starting now at tcp:5037 * daemon started successfully DLF100K1XXX829O02222 device C:\\Users\\Aoyu\\Desktop\\ADB\u0026gt;adb shell am start com.android.settings/com.android.settings.Settings Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.android.settings/.Settings } 成功打开了“设置”页面,之后到“鼠标手控制”里面给这个app打上勾,返回“主页”,用“鼠标手”选中手机自带的桌面的app图标,打开原来的桌面。算是把这块“假板砖”给救回来了。\n通知栏启动app 继续我的设想:想办法把常用app固定到通知栏上。 我在尝试了一些app后,想到可以用Tasker来做。一番尝试,太难了,放弃。 我接着又尝试了很多app,但无一例外,虽然确实实现了把app的图标固定到通知栏上,但我用按键选中后点击没反应、打不开。原因我也有一些猜测,但我在这方面实在是小白,就不说了。 以前我对某些app霸占通知栏的行为感到深恶痛绝,但现在我反倒渴望这样的app再流氓一点?。 最后,我找到了一个至少可以追溯到2011年的app——BarControl,虽然有点丑,但成功完成了我的设想(怎么有点像《万万没想到》里的王大锤?)。 现在我的这部移动k1手机的通知栏是这样的: 真的是太丑了。但好在能用。 我现在如果想开启app,只需要长按MENU键,然后从通知栏里选就可以了,十分方便。至少比以前方便多了。\n让应用开机自启 我又一次颠覆了我的认知。没想到我也有巴不得应用开机自启的时候。 我上面最后找到的那个app虽然满足要求,但它不能开机自启,每次我重启手机,都要手动开启一次,这样在通知栏里才会显示那些我添加的app。 怎么让它开机自启呢?手机设置里没有修改应用权限的地方;我安装了一个管理开机启动项的app也是毫无卵用;我用adb工具给这个app添加开机自启的权限,也失败了。所以截至现在我没能让这个app开机自启。 好在影响不大,我不能把时间接着花在这上面了。\n在最后 希望这部手机能够让我更专注于书本。 考研加油。一研为定。\n参考资料 [1] error:more than one device/emulator https://www.jianshu.com/p/af20d55d53b5\n[2] adb打开系统设置的命令 https://www.cnblogs.com/candyzhmm/p/11427960.html\n[3] 百度知道 用adb shell am命令时候输入了adb shell am start -n JDRU.apk以后为啥提示Error:bad component name https://zhidao.baidu.com/question/1899810450203207220.html\n[4] 【adb】adb shell 查看 APK 信息(权限等)https://www.cnblogs.com/zhuwei0901-yanwu/p/9881324.html\n[5] 包名查看器 http://www.anzhi.com/pkg/8a20_com.mz.packageviewer.html\n[6] 通知栏快捷工具(BarControl) http://www.anzhi.com/pkg/1d3c_com.schwimmer.android.barcontrol.html\n","date":"Jul 18","permalink":"https://o5o.me/post/wp/android_phone_china_mobile_k1-4g_m550/","tags":[null],"title":"折腾安卓功能机中国移动K1 4G M550"},{"categories":[null],"contents":"昨天是我背《十天搞定考研词汇》这本书的第十天。过去的十天里我每天都花大量的时间在单词上,虽然并没有像书名所说的“搞定”考研词汇,但也确实收获颇丰。十天过去了,往后我要怎么复习单词呢?我想到了Anki。\n以前我也尝试过Anki许多次,但说真的Anki并没有给我带来任何帮助。我认为原因有以下几个:1.制作卡片太浪费时间;2.用别人的卡片不合心意;3.背卡片总感觉不踏实,看不到希望、没有成就感。 这一次应该会好很多。\n为什么用R语言 前几天我才开始学R语言[1],因为写双学位毕业论文可能要用它来处理数据。我其实更倾向于用Matlab,因为帮助文档很完善、例子也很多,用着也顺手,但用R可能会显得更“专业”一点?。我想正好这个问题用R也能处理,那就用R语言吧。但毕竟我对R语言的特性还不很了解,如果代码有可改进之处,欢迎友好指出。\n关于epub/azw3电子书 这两种格式的电子书文件,不专业地说,你可以把它们看成zip、rar之类的压缩包(把.epub后缀改成.zip可以直接打开),压缩包里面是一些html格式的网页,书的内容就在这些网页文件里面。既然是网页,那如果你想获取里面的内容的话,是不是可以用“网络爬虫”来抓取呢?\n大致思路 因此,通过epub/azw3格式的电子书制作卡片可以大致分成如下几个步骤:1.把电子书文件解压开,获取里面的html文件;2.用爬虫爬取里面需要的信息,并将信息整理汇总成一个“表格”(csv文件);3.将csv文件导入Anki;4.修理bug、调整细节、美化卡片。\n开始 下面以《十天搞定考研词汇》这本书为例。这是一个完整的例子,我写这篇文章的时候也已经制作完成了,因此我想这篇文章对R语言初学者、有相同需求的人来说都是很有借鉴意义的。 接下来将《十天搞定考研词汇》简称为《十天》。\n观察这本书 可以看到,这本书一共有20个单词列表,从list1到list20,背单词的任务分成了10天,每天背两个列表,因此,具体到这本书,我们的任务是:1.将电子书文件中的所有单词都导入到anki中;2.在anki中创建一个总记忆库、再创建20个子记忆库,每个子记忆库保存一个列表的单词;3.单词卡片正面是单词,背面是单词的音标、释义和扩展;4.需要把书中高亮的单词也给标注出来。\n步骤1:获取html网页文件、观察结构 凭空变出《十天》的epub或azw3格式的电子书文件。我这里用的是azw3格式的。导入Calibre中,在这本书上点鼠标右键选“Edit book”,在打开的页面中可观察到,每一个列表的单词被单独存放在一个html文件中,选择需要的html文件导出。 观察导出的html文件。这里选取一部分:\n### List 1 intellect [ˈɪntəlekt] n. 智力;理解力;[总称] 知识分子 派intellectual [ˌɪntəˈlektʃuəl] adj. 智力的;理智的;聪明的 考点搭配intellectual enquiry 知识探索,知识探求 intellectual achievement 知识成就,智力成果 intellectualize [ˌɪntəˈlektʃuəlaɪz] vt. 使…理智化;对…做理性探讨 contempt [kənˈtempt] n. 轻视,轻蔑 派 contemptible [kənˈtemptəbl] adj. 可鄙的;可轻视的 contemptuous [kənˈtemptʃuəs] adj. 轻视的,蔑视的 ultimate [ˈʌltɪmət] adj. 最后的,最终的 yield [jiːld] n. 产量,收获量;收益 v. 出产;屈服 contend [kənˈtend] vi. 竞争,争夺 vt. 坚决主张,声称 如上所示,每一个词条均被包括在一个p标签中,“主单词”的p标签的class属性值为bodytext,“单词扩展”的class属性为content-yinyong。至于p标签内部,可以看到需要修改css样式的部分都被包括在不同的span标签中,也都有不同的class属性,据此我们可以对它们应用css样式,这放到后面anki卡片样式美化那里再说。 因此我们需要做的有: 1.在R中创建一个数据框,一列是“单词”(字符串)、另一列是“单词释义”(对应的html代码,也是字符串) 2.用爬虫提取所需信息放到上述数据框中。 需要注意的是,一个“主单词”对应的不仅有后面的音标和解释,还可能有下面的“单词扩展”,虽然在书的html代码中二者没有包含关系(是同级并列的),但这两部分内容是要放在一起的,都要放到“主单词”对应的“单词释义”里面。\n步骤2:爬虫爬取内容 我们用到的是R的rvest包。\ninstall.packages(\u0026#39;rvest\u0026#39;)#安装,这句代码只需使用一次 library(\u0026#39;rvest\u0026#39;)#载入包 载入网页代码[2]:\nurl = \u0026#39;/Users/aoyu/Desktop/10dayshtml/list20.html\u0026#39; web = url%\u0026gt;%read_html(\u0026#39;UTF-8\u0026#39;) 提取需要的代码片段:\nmd = web%\u0026gt;%html_nodes(xpath=\u0026#39;//p\u0026#39;) 上面我们通过观察电子书的html代码已知道,我们所需的单词信息都是包括在p标签中的,p标签中都是我们需要的内容,而且我们在这一步不需要区分“主单词”和“单词扩展”,所以我们只使用p标签来筛选即可。 md的类型是xml_nodeset,它内部是一个个的节点,就像html的标签树一样,我们用md[i]可获取它内部第i个节点的代码的简略信息,如执行md[2]:\n\u0026gt; md[2] {xml_nodeset (1)} [1] effect [ɪˈfekt] n ... 新建一个新的空数据框:\nmylist1 \u0026lt;- data.frame(word=character(0), meaning=character(0)) word我们打算用来保存“主单词”字符串,meaning对应的是主单词的释义和单词扩展的html代码串(也是字符串)。 将主单词和单词扩展合并,保存到数据框中: 包裹主单词内容的p标签的class属性值是bodytext,包括单词扩展内容的p标签的class属性值是content-yinyong,据此我们可以把主单词和单词扩展区分开。 遍历变量md中保存的每一条代码段(下面称为节点),如果一个节点的class属性值为bodytext就说明它是一个“主单词”,这时我们从代码段中提取出这个单词的字符串保存到word变量中,并把代码段保存到meaning变量中(作为“单词释义”),二者作为一个“观测”保存到数据框mylist1中;如果一个节点的class属性值为content-yinyong,说明它是前面与它相临的“主单词”的“单词扩展”,就把它的内容合并到前面与它相临的“主单词”的内容中(meaning变量中)。 怎样把xml_nodeset类型的变量中的html代码输出到一个数据框中呢,这个问题着实困扰了我好久,用as.character()函数即可[3]。\n#将附加单词内容与主单词内容合并 for (i in 1:length(md)) { if (md[i]%\u0026gt;%html_attr(\u0026#39;class\u0026#39;)==\u0026#34;bodytext\u0026#34;) { word \u0026lt;- md[i]%\u0026gt;%html_nodes(\u0026#34;.jiacu\u0026#34;)%\u0026gt;%html_text() meaning \u0026lt;- md[i] %\u0026gt;% as.character mylist1 \u0026lt;- rbind(mylist1,c(word,meaning)) } else if (md[i]%\u0026gt;%html_attr(\u0026#39;class\u0026#39;)==\u0026#34;content-yinyong\u0026#34;) { mylist1[length(mylist1[,1]),2] \u0026lt;- paste(mylist1[length(mylist1[,1]),2],md[i] %\u0026gt;% as.character) } } 这样一个过程完成后,我们就得到了一个两列的数据框,数据框左边一列是单词,右边一列是html代码,这段代码不仅包含了音标、释义等内容,还包含了单词扩展的内容。如图: 输出到csv文件:\nwrite.table(mylist1, file = \u0026#34;/Users/aoyu/Desktop/10dayscsv/mylist1.csv\u0026#34;, row.names=FALSE,col.names=FALSE, sep=\u0026#34;,\u0026#34;) 上面代码的意思是输出数据框mylist1中的数据到mylist1.csv文件中,不包含row.names行名和col.names列名,内容以英文逗号分隔。\n步骤3:将csv文件导入anki 在得到csv文件后,我用excel打开发现中文乱码,其他软件正常、但密密麻麻的很难看,知道是excel的问题、csv文件没问题就好,索性就不细细检查了(为后面的bug埋下伏笔,之后我安装了LibreOffice)。 打开anki,在菜单中选择“工具”——“导入”,选择刚才得到的csv文件。接下来的设置,我是这样做的: 注意选中“允许在字段中使用HTML”。 导入完成后试一下,卡片没有美化,不过内容显示“好像”是正常的,似乎我们离成功已经很近了。但卡片内容后那一个小小的引号提醒我们,事情并没有我们想象的这么美好。\n步骤4:修理bug、调整细节、美化卡片 修理bug1 既然“看起来”一切正常,那剩下要做的就是美化卡片了。 但我在修改卡片css的时候发现,添加的css样式似乎不起作用。打开编辑框,选择“编辑HTML”看一下: 为什么会出现这么多的“\u0026quot;”???最后的那个引号是什么时候出现的??? 经过检查,发现是导入anki时出的问题,准确地说是anki的bug,anki不能正确识别csv文件内容中的双引号。又过了不知道多长时间,半个小时?我想到了解决方法,就是给输出csv文件的write.table()函数加个参数qmethod,帮助文档中对这个参数的解释如下:\na character string specifying how to deal with embedded double quote characters when quoting strings. 上面输出csv文件的代码应修改为:\nwrite.table(mylist1, file = \u0026#34;/Users/aoyu/Desktop/10dayscsv/mylist1.csv\u0026#34;, row.names=FALSE,col.names=FALSE, sep=\u0026#34;,\u0026#34;,qmethod = \u0026#34;double\u0026#34;) 这样在csv文件中,对于内容中的引号,不再是以\\”的方式转义,而是以””的形式转义。 再次将csv文件导入anki,一切正常。\n美化卡片 修理了bug后,这样看起来似乎一切又美好起来了。那接下来开始美化卡片吧。我是模仿《十天》纸质书来修改卡片样式的,这里不多讲,我对CSS已经很生疏了。效果如下: 看起来好像还不错。但因为源文件的限制,不能做的和纸质书一模一样。 CSS代码如下:\n.card { font-family: serif; font-size: 20px; text-align: center; background-color: white; } p { text-align: justify; } p.bodytext { border-top: 2px solid #87CEFA; } p.bodytext .jiacu { background-color: #87CEFA; font-weight: bold; font-size: 1.25em; } p .blue-title { color: #00A1E9; } p .juli { color: white; background-color: grey; margin-right: 16px; } p.content-yinyong { font-size: 0.85em; text-indent: 40px; } p .juli0 { margin-right: 16px; border: 1px solid grey; } 好像一切都很美好。\n修理bug2 昨天是我背《十天》的第10天,晚上还要复习6个列表的单词,索性我就用Anki来复习了。 背着背着我就发现,出bug了。如图: 怎么只显示了音标、释义和扩展,唯独没有“主单词”? 瞬间我就明白过来。上面我们看到,在电子书的html代码中,“主单词”那个词条被class属性值为bodytext的p标签包裹着,而“单词扩展”那个词条被class属性值为content-yinyong的p标签包裹着,而我忽略的一点是,很少一部分的单词有两种发音、对应两个含义,在html代码中,分别用不同的p标签包裹着,且class属性值都是bodytext,这样它们就被当成了两个单词。 怎么修改呢,我们需要修改“将主单词和单词扩展合并,保存到数据框中”这部分的代码。我的修改如下:\n#将附加单词内容与主单词内容合并 for (i in 1:length(md)) { if (md[i]%\u0026gt;%html_attr(\u0026#39;class\u0026#39;)==\u0026#34;bodytext\u0026#34; \u0026amp; length(md[i]%\u0026gt;%html_nodes(\u0026#34;.jiacu\u0026#34;)%\u0026gt;%html_text())) {#修bug,增加条件,判断span.jiacu里面有没有内容 word \u0026lt;- md[i]%\u0026gt;%html_nodes(\u0026#34;.jiacu\u0026#34;)%\u0026gt;%html_text() meaning \u0026lt;- md[i] %\u0026gt;% as.character mylist1 \u0026lt;- rbind(mylist1,c(word,meaning,paste(\u0026#34;list\u0026#34;,j,sep=\u0026#34;\u0026#34;)))#有补充,添加标签到每个anki卡片 } else { #去掉else if判断条件 mylist1[length(mylist1[,1]),2] \u0026lt;- paste(mylist1[length(mylist1[,1]),2],md[i] %\u0026gt;% as.character) } } 遍历到一个节点时,不仅看它的class属性,而且看它内部有没有一个class属性为jiacu的span标签,两者结合起来判断这个节点是否为“主单词”节点。不再判断一个节点是否为“单词扩展”节点,不满足条件的统统按“其他”处理。 好像世界又变得美好了。\n调整细节 在《十天》这本书里,我们要处理的一共有20个列表,也就是20个html文件,如果手动一个一个转换的话,未免不够“优雅”,也很浪费时间,这个过程不如也交给程序来做。 这里插入一个小细节,在RStudio中,“清屏”的快捷键是Ctrl+L;清除环境变量需要在控制台输入rm(list=ls())。在Matlab中,一个是clc,一个是clear,感觉还是Matlab更顺手。 如果导出20个csv文件,那么在anki中就要再导入20次,十分麻烦,不如在R程序中,只导出1个csv文件,而通过“加标签”的方式区分不同列表的单词,也就是给数据框再增加一列,这一列保存每个单词所处的列表。\n汇总 综上,R程序如下(总):\n#install.packages(\u0026#39;rvest\u0026#39;) #library(\u0026#39;rvest\u0026#39;) rm(list=ls()) #url = \u0026#39;https://xiake.me/usr/uploads/2020/07/3715819721.html\u0026#39; for (j in 1:20) { url = paste(\u0026#39;/Users/aoyu/Desktop/10dayshtml/list\u0026#39;,j,\u0026#39;.html\u0026#39;,sep=\u0026#34;\u0026#34;) web = url%\u0026gt;%read_html(\u0026#39;UTF-8\u0026#39;) #md = web%\u0026gt;%html_nodes(css = \u0026#39;p.list1\u0026#39;) #md = web%\u0026gt;%html_nodes(xpath=\u0026#39;//p[@class = \u0026#34;bodytext list1\u0026#34;] | //p[@class = \u0026#34;content-yinyong list1\u0026#34;]\u0026#39;) md = web%\u0026gt;%html_nodes(xpath=\u0026#39;//p\u0026#39;) #md1 = md %\u0026gt;% as.character #将xml_nodeset变成字符 #新建一个空数据框 mylist1 \u0026lt;- data.frame(word=character(0), meaning=character(0), taghao=character(0)) #mylist1 \u0026lt;- edit(mylist1) #将附加单词内容与主单词内容合并 for (i in 1:length(md)) { if (md[i]%\u0026gt;%html_attr(\u0026#39;class\u0026#39;)==\u0026#34;bodytext\u0026#34; \u0026amp; length(md[i]%\u0026gt;%html_nodes(\u0026#34;.jiacu\u0026#34;)%\u0026gt;%html_text())) {#修bug,增加条件,判断span.jiacu里面有没有内容 word \u0026lt;- md[i]%\u0026gt;%html_nodes(\u0026#34;.jiacu\u0026#34;)%\u0026gt;%html_text() meaning \u0026lt;- md[i] %\u0026gt;% as.character mylist1 \u0026lt;- rbind(mylist1,c(word,meaning,paste(\u0026#34;list\u0026#34;,j,sep=\u0026#34;\u0026#34;)))#有补充,添加标签到每个anki卡片 } else { #去掉else if判断条件 mylist1[length(mylist1[,1]),2] \u0026lt;- paste(mylist1[length(mylist1[,1]),2],md[i] %\u0026gt;% as.character) } } write.table(mylist1, file = paste(\u0026#34;/Users/aoyu/Desktop/10dayscsv/mylist\u0026#34;,j,\u0026#34;.csv\u0026#34;,sep=\u0026#34;\u0026#34;), row.names=FALSE,col.names=FALSE, sep=\u0026#34;,\u0026#34;,qmethod = \u0026#34;double\u0026#34;) write.table(mylist1, file = paste(\u0026#34;/Users/aoyu/Desktop/10dayscsv/mylist\u0026#34;,\u0026#34;.csv\u0026#34;,sep=\u0026#34;\u0026#34;), row.names=FALSE,col.names=FALSE, sep=\u0026#34;,\u0026#34;,qmethod = \u0026#34;double\u0026#34;,append=TRUE) } 代码中有一些被我注释掉的语句,我贴上来的时候没有去掉是因为觉得可以帮助理解。 一张截图: 在最后 虽然代码很短,但我写的时候遇到了很多困难,完成这个程序让我感觉我对R处理问题的逻辑有了进一步的了解。我受C语言的影响还是蛮大的,从上面的代码中还能看到C语言的影子。 我遇到的问题基本都是在外网搜索找到的解答。管中窥豹,感觉R社区的交流氛围应该是挺好的,不过这也不能掩盖帮助文档做的太不人性化的事实,我看过的软件、程序的帮助文档,还就属Matlab的最好。 我已经很尽力地去准确还原我的思考过程了。 在文章开头我说,以前Anki没有给我带来任何帮助但这一次应该会好很多。对应的,下面我也给出几点原因: 1.制作卡片不再需要很多时间。虽然《十天》这本书结构很简单,但我写的这个程序稍加修改就是可以应用到其他卡片制作当中的。 2.既然自己制作卡片这么省事,那也就不用考虑用别人的卡片不合心意的问题了,自己做就好。 3.因为我是先用纸质书背了10天的《十天》,书中的全篇内容实际上我都已经读了好几遍了,再看到书中一个单词的时候,可能我不知道它的含义,但一定知道它在书上出现过。用anki背卡片感觉不踏实无非是对自己的记忆效果没有一个准确的定位,但先用纸质书背过之后,就相当于给自己打了个底,知道自己用anki之后总不可能比之前更差,而且卡片内容是自己已经过了几遍的,也不会说看着几千张卡片手足无措、感觉黑暗一眼望不到头,至于说成就感,我过去10天里把纸质书过了几遍就已经很有成就感了。 至于做好的卡片,我不知道分享出来是否有侵权的嫌疑,就不分享了。认真思考一下,用我上面的代码自己也能做出来。\n参考资料 [1] R语言实战(第2版)\n[2] “简单粗暴”的R语言爬虫·其一 https://zhuanlan.zhihu.com/p/77777024\n[3] R – xmlnodeset output into dataframe or table https://stackoverflow.com/questions/37960580/r-xmlnodeset-output-into-dataframe-or-table\n","date":"Jul 10","permalink":"https://o5o.me/post/wp/r_lang_make_anki_cards_with_ebook/","tags":["Anki"],"title":"R语言实现将电子书转换为Anki卡片——以《十天搞定考研词汇》为例"},{"categories":[null],"contents":"我辅修的财务管理专业这学期开了“会计信息系统实务”课,需要用到“用友U8”软件。因为疫情,学校推迟开学,不能在学校机房上机练习,因此有必要在自己电脑上安装一个这个软件。\n用友U8要想成功安装/运行需要以很多其他支撑软件为前提,真的很多,而且我感觉用友U8这个软件的兼容性不太好。所以为了避免把我的电脑弄乱、出现未知错误,我是在虚拟机里安装的。同时,因为我只是为了学习软件的使用,而不是将其作为生产力工具,所以我的安装原则是:不追求稳定,能用就行,怎么方便怎么来。 老师提供了用友的安装包和其他各种软件、补丁的安装包,让我节省了很多时间。\n安装过程 1.安装虚拟机VMware Workstation。没什么好说的。 2.安装Win7。我安装的是Win7 SP1旗舰版,从msdn itell you下载的。没激活。 3.将安装包拷贝到虚拟机中。没什么好说的。 4.安装基础环境组件。打开用友U8安装程序,看缺少哪些软件,都安装上。 老师给的软件挺全,但是没有IIS和数据库软件安装包。一番搜索之后我才知道,IIS要到系统功能中启用。本着能用就行的原则,数据库软件我选择了适用于用友U8的“microsoft sql server 2008 r2”一键安装包。 5.进行完上面的操作后,不出意外软件就能安装成功了。放两张我安装时的图片。 安装之后 说实话,我是上个周六安装的,后面我做了什么差不多都忘了?。还好浏览器书签里有当时添加的链接。 我先写几条建议:\n1.一定要看安装包里的说明文档(如果有的话)。因为我没用过微软的数据库,加上我用的数据库软件可能是精简过的,当时很疑惑要到哪里新建数据库,后来才发现安装包给的说明里已经写好了默认的数据库名称和密码。\n2.出现问题,不妨重启电脑试试。我好像是遇到了数据库断开连接的问题(具体错误记不清了),我忘了重启之后好没好,反正过了一会儿等我理顺用友U8的使用逻辑之后这个问题就再没出现过了。 我遇到的问题中,印象最深的有两个: 1.用admin账号登录,提示我“系统管理员不能进行业务处理”。这时,新建一个“普通用户”,再用普通用户登录就好了。 2.提示“请选择数据源”。这时是要新建一个“数据源”,也就是把用友软件和数据库连接起来。具体操作看文章最后我给的参考资料链接吧。\n文章最后 就是这样了,我单纯地只是想把整个过程记录下来而已,毕竟这整个过程那天我花了3个小时的时间。 参考资料:\n[1] 登录用友U8时提示请选择数据源? https://www.ufidawhy.com/jcufida/773.html\n","date":"Mar 06","permalink":"https://o5o.me/post/wp/install_yongyou_u8_software/","tags":[null],"title":"安装用友U8软件"},{"categories":[null],"contents":"昨天晚上躺在床上,经过一天的学习疲惫不堪,朦胧间,感觉自己活得好累。这时突然有了一个大胆的想法——要不,期货从业资格考试先不考了?\n本打算去年暑假的时候报名的,但是因为数学建模竞赛的缘故,实在没有精力。这样一拖,就拖到了寒假,又要开始准备考研了。 我也没有什么现实的需求必须要去通过期货资格,更多的可能只是个人的一种执念。我之前已经通过了证券和基金从业资格,没怎么备考,就那样稀里糊涂地通过了,感觉我对于金融市场的整个的了解依然是处于一种很模糊的状态。去评价政府的一些经济政策的时候,总感觉不能很好地表达自己的想法,这让我怀疑我以前花掉的考试报名费是不是浪费。所以我也是想通过期货考试重拾信心。另外,也是想集齐证券、基金和期货这三个从业资格。 谁没做过“出任CEO、迎娶白富美,走向人生巅峰”的梦呢。真的很好奇,要怎样才能通过衍生品的组合,无论价格上涨还是下跌,都能“锁定利润”、实现“稳赚不赔”。 如果现在去考的话,我想我是能通过的。《期货及衍生品基础》那本书写的很好,我看了不止一遍。不过,确实没必要继续把精力花在这上面了。由于这次疫情,考试很可能被推迟到4月。还是要把更多的时间用在考研科目上。 应该说,我想要从期货从业考试中得到的东西差不多都得到了,除了最后的成绩合格证书。我想,对我来说,更大的意义在于,我看到了真正聪明的人应该是什么样子。第一个开展远期交易的人是怎样出现这样的天才想法的呢?江畔何人初见月?江月何年初照人? ","date":"Feb 29","permalink":"https://o5o.me/post/wp/march_cfa_china_examination_cancel/","tags":[null],"title":"取消报考3月期货从业资格考试"},{"categories":[null],"contents":"在备考期货从业资格考试。期货交易成交后的持仓量是怎样的,我不太熟悉,有必要捋一捋。\n持仓量是指期货交易者所持有的未平仓合约的累计数量。国内三家商品期货交易所(上期、郑商、大商)的成交量和持仓量数据按双边计算,中国金融期货交易所的成交量和持仓量数据按单边计算。 期货交易的每次成交,都必定有两方参与,一方是卖方、另一方是买方,两者成交的合约数量必定是相同的(比如卖方卖出10手白糖,买方必定是买入10手白糖)。 对于一次期货交易的买方来说,他参与此次交易的目的不外乎:1.他现在没有持有这个品种的合约,他要买入开仓(多头开仓);2.他是这个品种合约的空头(他之前卖出过这个品种的合约),他要平仓(空头平仓);3.他要增加持仓多头头寸;4.他要减少持仓空头头寸。 对于一次期货交易的卖方来说,他参与此次交易的目的不外乎:1.他现在没有持有这个品种的合约,他要卖出开仓(空头开仓);2.他是这个品种合约的多头(他之前买入过这个品种的合约),他要平仓(多头平仓);3.他要增加持仓空头头寸;4.他要减少持仓多头头寸。 只考虑买方和卖方开仓和平仓的情形,其他情形与这两种情形类似。也就是说,对于买方和卖方来说,他们参与期货交易的行为均有2种,组合起来,一次期货交易可能的情况有四种:1.买方多头开仓,卖方空头开仓;2.买方多头开仓,卖方多头平仓;3.买方空头平仓,卖方空头开仓;4.买方空头平仓,卖方多头平仓。\n假如现在白糖某一月份合约(设为A)的总持仓量为200手(双边计算),即多头头寸100手、空头头寸100手。\n情形1 有一个人觉得A要涨,于是买入1手A开仓(买方,多头开仓);他这1手A是从哪儿买的呢?是从另一个人手里买的,另外这一个人觉得A要跌,于是他卖出1手A开仓(卖方,空头开仓)。交易成交,A的多头头寸增加1手(101手)、空头头寸增加1手(101手),总的持仓量增加2手,变为202手。因此: 双开仓,持仓量增加。\n情形2 有一个人觉得A要涨,于是买入1手A开仓(买方,多头开仓);他这1手A是从哪儿买的呢?是从另一个人手里买的,另外这一个人觉得现在A的价格已经足够高了,所以他决定把手里持有的仅有的1手A的多头头寸平仓(卖方,多头平仓)。交易成交,A的多头头寸同时增加1手和减少1手(100+1-1=100),所以A的多头头寸不变(100手),总的持仓量也不变。因此: 买方多头开仓,卖方多头平仓,持仓量不变。 这种情形也被称为“多头换手”,即多头头寸从一个人的手里转移到了另一个人的手里,只是换了换主人,数量没有改变。\n情形3 有一个人觉得A已经跌到底了(此前A一直在跌),所以他决定把手里持有的仅有的1手A的空头头寸平仓(买方,空头平仓),要平仓的话,需要买入1手A合约,那么从哪儿买呢?是从另一个人手里买的,另外这个人觉得A未来肯定要跌(不管之前是跌还是涨),所以他决定卖出1手A开仓(卖方,卖出开仓)。交易成交,A的空头头寸同时减少1手和增加1手(100+1-1=100),所以A的空头头寸不变(100手),总的持仓量也不变。因此: 买方空头平仓,卖方空头开仓,持仓量不变。 这种情形也被称为“空头换手”,即空头头寸从一个人的手里转移到了另一个人的手里,只是换了换主人,数量没有改变。\n情形4 有一个人觉得A已经跌到底了(此前A一直在跌),所以他决定把手里持有的仅有的1手A的空头头寸平仓(买方,空头平仓),要平仓的话,需要买入1手A合约,那么从哪儿买呢?是从另一个人手里买的,另外这个人觉得A未来还要继续跌,所以他决定把手里持有的仅有的1手A的多头头寸平仓(卖方,多头平仓)。交易成交,A的空头头寸减少1手(100-1=99),多头头寸减少1手(100-1=99),总的持仓量减少2手,为198手。因此: 双平仓,持仓量减少。\n综上,引用教材中的话:\n交易行为与持仓量\n第一,只有当新的买入者和卖出者同时入市时,持仓量增加。\n第二,当买卖双方有一方做平仓交易时(即换手),持仓量不变。\n第三,当买卖双方均为原交易者,双方均为平仓时,持仓量减少。\n如果买卖(多空)双方均建立了新头寸,则持仓量增加。如果双方均是平仓了结原有头寸,则持仓量增加。如果一方开立新的交易头寸,而另一方平仓了结原有交易头寸,也就是换手,则持仓量不变,这包括多头换手和空头换手两种情况。\n买方 卖方 持仓量的变化 1 多头开仓 空头开仓 增加(双开仓) 2 多头开仓 多头平仓 不变(多头换手) 3 空头平仓 空头开仓 不变(空头换手) 4 空头平仓 多头平仓 减少(双平仓) 参考资料:期货及衍生品基础(第二版) 在最后必须要说的是,我没有参与过期货交易(包括模拟交易),一些术语用得可能不准确。到现在我感觉对于“多头、空头、头寸”这几个名词,我还是处于一种模糊的状态,知道背后的含义,但你要具体让我说它们是什么,很模糊。\n","date":"Feb 26","permalink":"https://o5o.me/post/wp/the_change_of_the_holdings_for_futures_contracts/","tags":[null],"title":"如何理解期货合约持仓量的变化"},{"categories":[null],"contents":"受不了“可见”这俩字。逻辑通顺的感觉,舒服。 高等代数 p13\n","date":"Jan 01","permalink":"https://o5o.me/post/wp/new-year-2020/","tags":[null],"title":"新年第一个证明,舒服。"},{"categories":[null],"contents":"买了一个ACM会员,可以访问O’Reilly的在线图书资源。我比较喜欢看head first系列的书。由于国内访问速度不佳,体验不是很好。再加上个人的占有欲,我打算把O’Reilly的书下载到本地看。\nGithub有一个库safaribooks,恰好满足我的需求。但是我用ACM账号登录时的登录方式为单点登录(SingleSignOn,SSO),不是直接用的oreilly的账号,所以为了让程序能正常运行,需要首先获取oreilly网站的cookie。下面我完整叙述整个流程。 安装safaribooks\n$ git clone https://github.com/lorenzodifuccia/safaribooks.git $ cd safaribooks/ $ pip3 install -r requirements.txt 获取cookie 在浏览器中正常登录oreilly learning,按F12,打开控制台(console),输入如下代码获取cookies:\njavascript:(function(){var output = {};document.cookie.split(/\\s*;\\s*/).forEach(function(pair) {pair = pair.split(/\\s*=\\s*/);output[pair[0]]=pair.splice(1).join(\u0026#39;=\u0026#39;);});console.log(JSON.stringify(output));})(); 把屏幕上输出的内容复制到文件中,文件名设为cookies.json,将文件放置到safaribooks.py所在文件夹中(即safaribooks/)。 按理说这时直接在终端输入以下命令就能把对应图书导出来了(后面那串数字是图书对应id,可以在浏览器中打开图书页面,从url链接中看到):\npython3 safaribooks.py 9781491919521 但是我却收到了一个错误。\n[#] Authentication issue: unable to access profile page. [!] Aborting... 我在该项目的Issues中寻找到的解决方案如下:\n# 修改safaribooks.py文件, # 修改这句代码:PROFILE_URL = SAFARI_BASE_URL + \u0026#34;/profile/\u0026#34; # 修改为: PROFILE_URL = SAFARI_BASE_URL + \u0026#34;/home/?next=%2Fprofile%2F\u0026#34; 再次运行命令,然后我就成功导出了格式为epub的图书文件。终端显示的内容如下:\naoyu@Guanghaos-MacBook-Pro safaribooks % python3 safaribooks.py 9781491919521 ____ ___ _ / __/__ _/ _/__ _____(_) _\\ \\/ _ `/ _/ _ `/ __/ / /___/\\_,_/_/ \\_,_/_/ /_/ / _ )___ ___ / /__ ___ / _ / _ \\/ _ \\/ \u0026#39;_/(_-\u0026lt; /____/\\___/\\___/_/\\_\\/___/ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [-] Successfully authenticated. [*] Retrieving book info... [-] Title: Head First Python, 2nd Edition [-] Authors: Paul Barry [-] Identifier: 9781491919521 [-] ISBN: 9781491919538 [-] Publishers: O\u0026#39;Reilly Media, Inc. [-] Rights: Copyright © 2016 Paul Barry [-] Description: Want to learn the Python language without slogging your way through how-to manuals? With Head First Python, you’ll quickly grasp Python’s fundamentals, working with the built-in data structures and functions. Then you’ll move on to building your very own webapp, exploring database management, exception handling, and data wrangling. If you’re intrigued by what you can do with context managers, decorators, comprehensions, and generators, it’s all here. This second edition is a complete learning ex... [-] Release Date: 2016-11-23 [-] URL: https://learning.oreilly.com/library/view/head-first-python/9781491919521/ [*] Retrieving book chapters... [*] Output directory: /Users/aoyu/Desktop/Oreilly/safaribooks/Books/Head First Python 2nd Edition (9781491919521) [-] Downloading book contents... (30 chapters) [#####################################################################] 100% [-] Downloading book CSSs... (4 files) [#####################################################################] 100% [-] Downloading book images... (216 files) [#####################################################################] 100% [-] Creating EPUB file... [*] Done: /Users/aoyu/Desktop/Oreilly/safaribooks/Books/Head First Python 2nd Edition (9781491919521)/9781491919521.epub If you like it, please * this project on GitHub to make it known: https://github.com/lorenzodifuccia/safaribooks e don\u0026#39;t forget to renew your Safari Books Online subscription: https://learning.oreilly.com [!] Bye!! end.\n当然这篇文章我是给自己看的,如果你不小心点了进来,但是还是没看懂该如何用这个程序,建议访问该项目在github的地址,地址我会放在文章最后。 ACM的会员在发展中国家(包括中国)的价格是8美元,换算成人民币是56块多(以前换算汇率都是按6,现在按7了?),如果能完整读完一本英文书,我认为是很划算的。\n2019年12月29日补充: 我在阅读的时候发现从第二章开始,每章的内容都只有一部分。我寻找解决方案的过程不再多言。出现这个问题的原因是,我用上面那段代码获取到的cookie是不完整的,缺了几条,导致在导出第一章后面的章节时,身份验证不通过,因此只导出了开头一小部分内容(预览内容)[4]。 我的解决方案是:把漏掉的cookies手动补上去?。根据我的对比,缺了下面三条cookie:\nkampyle_userid orm-rt groot_sessionid 添加到cookies.json文件末尾,重新运行程序就正常了。 至于说原理,或者说更优雅的解决方案,交给未来的我去考虑吧。现在的我不想关心。\naoyu@Guanghaos-MacBook-Pro safaribooks % python3 safaribooks.py 9781491919521 ██████╗ ██████╗ ██╗ ██╗ ██╗██████╗ ██╔═══██╗ ██╔══██╗██║ ╚██╗ ██╔╝╚════██╗ ██║ ██║ ██████╔╝██║ ╚████╔╝ ▄███╔╝ ██║ ██║ ██╔══██╗██║ ╚██╔╝ ▀▀══╝ ╚██████╔╝ ██║ ██║███████╗██║ ██╗ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [-] Successfully authenticated. [*] Retrieving book info... [-] Title: Head First Python, 2nd Edition [-] Authors: Paul Barry [-] Identifier: 9781491919521 [-] ISBN: 9781491919538 [-] Publishers: O\u0026#39;Reilly Media, Inc. [-] Rights: Copyright © 2016 Paul Barry [-] Description: Want to learn the Python language without slogging your way through how-to manuals? With Head First Python, you’ll quickly grasp Python’s fundamentals, working with the built-in data structures and functions. Then you’ll move on to building your very own webapp, exploring database management, exception handling, and data wrangling. If you’re intrigued by what you can do with context managers, decorators, comprehensions, and generators, it’s all here. This second edition is a complete learning ex... [-] Release Date: 2016-11-23 [-] URL: https://learning.oreilly.com/library/view/head-first-python/9781491919521/ [*] Retrieving book chapters... [*] Output directory: /Users/aoyu/Desktop/Oreilly/safaribooks/Books/Head First Python 2nd Edition (9781491919521) [-] Downloading book contents... (30 chapters) [#####################################################################] 100% [-] Downloading book CSSs... (4 files) [#####################################################################] 100% [-] Downloading book images... (1009 files) [#####################################################################] 100% [-] Creating EPUB file... [*] Done: /Users/aoyu/Desktop/Oreilly/safaribooks/Books/Head First Python 2nd Edition (9781491919521)/9781491919521.epub If you like it, please * this project on GitHub to make it known: https://github.com/lorenzodifuccia/safaribooks e don\u0026#39;t forget to renew your Safari Books Online subscription: https://learning.oreilly.com [!] Bye!! 参考资料 [1] safaribooks项目地址 https://github.com/lorenzodifuccia/safaribooks\n[2] Issue#1 https://github.com/lorenzodifuccia/safaribooks/issues/160\n[3] Issue#2 https://github.com/lorenzodifuccia/safaribooks/issues/2\n[4] Issue#3 https://github.com/lorenzodifuccia/safaribooks/issues/150\n","date":"Dec 26","permalink":"https://o5o.me/post/wp/down-epub-oreilly/","tags":[null],"title":"导出EPUB格式的O’Reilly图书及解决导出章节不完整问题"},{"categories":[null],"contents":"昨天注销了我的一个.cn域名的备案号。经常看到域名备案相关的文章,备案域名注销的文章倒是不常见。 域名备案要好多天,而注销备案号则只用了半个小时不到的时间。\n我的域名是在阿里云备案的,点一下备案系统页面上的“注销网站”按钮,输入备案密码和手机短信验证码,不一会儿就发来短信提醒我网站已被注销,备案号已被收回。\n【工信部备案系统】工业和信息化部网站备案系统-短信通知:尊敬的用户***,您备案信息中的网站数学本已被注销,该网站的备案号豫ICP备17049473号-2已被收回,特此通知!\n","date":"Nov 30","permalink":"https://o5o.me/post/wp/cancel-domain-name-beian/","tags":[null],"title":"工信部备案域名注销"},{"categories":[null],"contents":"因为各种原因吧,给我的Surface Pro4 装了一个黑苹果。蛮简单的,一次成功。我尤其喜欢这种“前人栽树,后人乘凉”的事情。\n我之前没装过macOS,连虚拟机里也没装过,这是第一次。从我打算安装macOS那天,我看了很多帖子和文章,心里有底之后才下手的。很多文章虽然写的很详细,但要点没写清楚,仅仅是过程的罗列,不具有普适性。 安装黑苹果,大致上就是以下几步(用别人做好的包):\n下载带Clover的镜像,校验MD5码 将镜像写入U盘,根据需要,替换U盘EFI分区clover 硬盘分区(确保BitLocker是关闭的) 关闭安全启动SecureBoot(Surface开机会出现红色锁) 让电脑从U盘启动(设置启动项顺序) 安装macOS 安装成功,将clover添加到硬盘EFI分区 添加clover到启动项 重建缓存 这个过程只要谨慎,就没什么难的。难点在于解决安装macOS过程中的错误。而众多网友已经帮我把这个难点解决了。 我刚开始迷迷糊糊的,下载了一个原版镜像。我去网上找将镜像写入U盘的教程,教程告诉我要在macOS系统下制作U盘镜像(问号脸,我有了mac还装黑苹果?)。当然这是有原因的,因为别人制作好的“懒人包”用起来毕竟没自己做的放心。我想,如果我真的这样来做,那么我肯定是要在虚拟机里安装macOS,我还是要另外下载一个别人做好的包,还不如直接就用别人做好的包来装。 CLOVER是什么呢?启动器,可以简单理解为,你装好系统后,选择进入win系统还是macos系统的那个界面,并且呢,可以通过它来加载系统驱动。 我下载的是黑果小兵制作的镜像。用etcher将镜像写入U盘,成功后,U盘被分为两个区,其中一个是U盘的EFI分区。因为我下载的镜像是带clover的,所以此时U盘EFI分区里是有一个clover文件夹的,但因为这个clover不适合我的电脑,所以要把它替换掉,换上适合我的电脑的clover(有别人做好的)。替换CLOVER文件夹我用的软件是DiskGenius。 我看到有一篇文章,讲的是Surface Pro6安装macos,文章中有一部分是直接用u盘的EFI分区直接替换硬盘的EFI分区,这就是我前面讲的“教程不具有普适性”,虽然跟他电脑型号相同、所用镜像相同的人这样做能成功安装,但其他人呢,很可能就进不去系统了。 我的Surface Pro4的硬盘是256G的,比较小,只有一个C盘。为了安装黑苹果,需要从c盘中分出一部分硬盘空间。我的硬盘快满了,所以在安装之前整理了一下自己的文件,把比较占空间的文件打包上传到了onedrive。整理的差不多之后,我用win自带的“磁盘管理”程序在c盘分区上“压缩卷”,但可压缩的空间很少。原因是:硬盘中储存的文件不连续,虽然我删掉了一部分文件把空间空了出来,但这些空闲空间不是连续的,不能“连成一片”,所以就压缩不了那么大了。 后来,我转移或者删除了好多文件,但还是不能压缩出来我想要的空间大小。此时,电脑里也没剩什么了,我干脆将电脑恢复了出厂状态。最终,我从c盘分出80G用来黑苹果。 而后便正式进入了安装过程。关掉安全启动、从u盘启动(将u盘放在启动顺序的第一位),这两个操作在surface上很简单明了,按着“音量+”再点一下电源键,电源键点一下之后就松开,继续按着“音量+”直到进入新界面。这两项很容易做。 上面两步完成后退出,电脑重启就会进入clover的界面了。在安装macos时,我所用的这个clover,需要用的配置文件是“config sp4-install.plist”,其他人可能会不同。 一步步安装macOS的过程我就不写了,这篇文章的目的不是这个。我在安装过程中一个错误也没遇到,非常非常顺利。 安装完成之后呢,macOS系统已经在硬盘上了,这时我们在硬盘的EFI分区添加clover,并把clover放在启动项的第一位,这样一来,开机后首先进入的就是clover的界面,这时我们便可选择进入win系统还是macos系统。这里我用到的软件是DiskGenius和EasyUEFI,我是在win下操作的。 之后呢,我依然选择加载“config sp4-install.plist”配置文件,进入macOS,用Kext Utility重建缓存。这个软件是我另外单独下载的。 重启电脑,进入macOS系统(默认加载“config.plist”配置文件),我遇到的问题是,进度条走到一多半电脑就重启了。原因是,我所用的clover是适配macos 10.15的,而我安装的是10.15.1,需要在mac下修改“config.plist”文件中“matchos”的值,将10.15改为10.15.1,这样我便成功进入系统了,且屏幕分辨率正常(用config sp4-install.plist进入系统,字体特别小)。作者可能是觉得这不算什么问题所以就没有修改。如果在win下修改文件,可能是因为编码原因,我重启电脑后进不去clover界面(解决方法是按音量+和电源键进入恢复界面,把win的启动项放在第一位,即可进入win)。 成功进入macos系统后,因为网卡问题没解决,不能上网。这个问题是已知的,我已经提前买好了有线网卡。但因为没装驱动的原因,不能上网。我想,先临时用手机共享网络给电脑上网体验一下吧,但发现也不能上网,不清楚原因。而后,我下载了网卡驱动,才成功连上了网。 之后呢,我登陆我的账号,发现不能用iTunes Match和iCloud这些服务。而后我才了解到还有一个过程是“洗白”,也就是让apple认为这台黑苹果是一个mac,我没看到好的教程,尝试着做了一下之后,跟前面一样也进不去clover界面了,所以就暂时没做。 如果是像之前的我一样的小白,单纯只看这篇文章是肯定不能装macos的,这篇文章的目的只是理顺我的一些思路、简单记录过程,以后可能会不断修改。 一开始我是打算在淘宝上找人装的,但价格超过了我的心理价位。用请人装macOS的钱,再另外加一部分,我得到的是: 一块32G的USB3.1的U盘 一根15米长的六类网线 一个千兆有线网卡+usb接口一分三分线器 一个pcbeta论坛共享账号 参考资料:\ngithub仓库 https://github.com/bigsadan/surface-pro-4-hackintosh 文章 Surface Pro 6超详细教程之安装windows10和黑苹果macOS 10.14双系统 http://www.macoshome.com/hackintosh/hcourse/1334.html 文章 黑苹果 Clover 四叶草 u盘安装后如何改为硬盘EFI引导 https://imac.hk/clover-usb-install-add-boot-menu.html 帖子 surface Pro 4 更新10.15.1 全网最完美SP4黑苹果 (surface book 1适用) http://bbs.pcbeta.com/forum.php?mod=viewthread\u0026amp;tid=1806282\u0026amp;highlight=surface 帖子 黑苹果系列教程之——CLOVER 配置教程 https://bbs.tpway.com/thread-5935-1-1.html 文章 macOS Catalina 10.15.1 19B8 正式版 with Clover 5098原版镜像[双EFI双平台版] https://blog.daliansky.net/macOS-Catalina-10.15.1-19B88-Release-version-with-Clover-5098-original-image-Double-EFI-Version.html 文章 clover使用教程 https://blog.daliansky.net/clover-user-manual.html ","date":"Nov 10","permalink":"https://o5o.me/post/wp/surface-pro-4-install-hackintosh-macos-catalina-10-15-1/","tags":[null],"title":"Surface Pro 4 安装黑苹果macOS Catalina 10.15.1"},{"categories":[null],"contents":"曾经英语老师讲过这样一个笑话:“一个学生写英语作文,作文内容是和外国朋友打电话。他在开头这样写:’I have a good friend. He likes to speak Chinese with me.’然后作文后面的内容全程用中文来写。”我最近也做了一件类似的事?。\n我曾经在Play商店买过一个音乐APP,名字是Stellio,当时也是一时冲动,买后才发现并不喜欢。那时是2016年,我这几年完全忘了这回事。不久前,想找一个合适的音乐播放器听歌,隐约想到我曾经买过一个漂亮的播放器,但我并没有在我的Play商店购买记录中找到。我很确信我买了这么一件东西,但我忘了我是通过何种方式买的。我翻了我的Paypal、支付宝,都没找到相关购买记录。 我去这个APP的网站上看帮助文档,发现开发者之前在谷歌商店的账号被封了。我购买的时候,这个APP是通过额外安装一个名为Stellio Unlocker的APP激活的,现在改成了输入激活码激活。开发者给出的解决方案是,安装他们网站上提供的Stellio Unlocker试一试,如果不行,和他们邮件联系。 我最终在邮箱中找到了曾经的购买记录邮件。我原来用的是网易企业邮箱,现在改成了阿里邮箱。物是人非。 我这样写: Hi, I met some problems, please help me. My English is poor. I ues Chinese to describe my problem. 你好,我在2016年7月29日在Google Play购买了Stellio Unloker,但是这几年我忘记了这件事情,直到今天,我发现我无法通过安装Unlocker的方式激活Stellio,请帮助我解决这个问题。 然后我附上了当时的订单号和邮件截图。 今天对方给我回信并送上了激活码。 我试了试,发现还是不喜欢。 ","date":"Nov 05","permalink":"https://o5o.me/post/wp/something-stellio-google-play/","tags":[null],"title":"趣事一则 stellio音乐播放器"},{"categories":[null],"contents":"侠客是放在阿里云的轻量应用服务器上的,我直接用的LAMP镜像。这几天感觉博客访问越来越慢,并且WP的后台提示我该升级PHP版本了。侠客的PHP版本还是5.x,确实要升级一下。升级php不知道又会遇到什么问题,还是换台服务器比较好。 这台服务器是在阿里云的北京机房,系统是ubuntu18.04。 首先更新系统。\napt-get update apt-get upgrade apt-get dist-upgrade 安装php\napt-get install php php -v(查看版本) 安装php自动把apache也安装了,很奇怪。 安装Mysql\napt-get install mysql-server (中间会让输入root密码) apt-get install mysql-client #查看是否安装成功 mysql -u root -p #回车,输入密码,也可能不需要输密码 select version(); exit; #退出mysql环境 打印phpinfo,查看php信息,如php.ini路径\n#新建php文件并写入下述语句,通过浏览器访问文件 php echo phpinfo(); ? 浏览器访问服务器ip,可看到apache默认页面内容,页面上会显示出网页文件的根目录、配置文件路径等内容。\n# It works! This is the default welcome page used to test the correct operation of the Apache2 server after installation on Ubuntu systems. It is based on the equivalent page on Debian, from which the Ubuntu Apache packaging is derived. If you can read this page, it means that the Apache HTTP server installed at this site is working properly. You should **replace this file** (located at `/var/www/html/index.html`) before continuing to operate your HTTP server. If you are a normal user of this web site and don\u0026#39;t know what this page is about, this probably means that the site is currently unavailable due to maintenance. If the problem persists, please contact the site\u0026#39;s administrator. # Configuration Overview Ubuntu\u0026#39;s Apache2 default configuration is different from the upstream default configuration, and split into several files optimized for interaction with Ubuntu tools. The configuration system is **fully documented in /usr/share/doc/apache2/README.Debian.gz**. Refer to this for the full documentation. Documentation for the web server itself can be found by accessing the **manual** if the **apache2-doc** package was installed on this server. The configuration layout for an Apache2 web server installation on Ubuntu systems is as follows: /etc/apache2/ |\u0026ndash; apache2.conf | -- ports.conf |-- mods-enabled | |-- *.load | \u0026ndash; *.conf |\u0026ndash; conf-enabled | -- *.conf |-- sites-enabled | \u0026ndash; *.conf\n* **apache2.conf** is the main configuration file. It puts the pieces together by including all remaining configuration files when starting up the web server. * **ports.conf** is always included from the main configuration file. It is used to determine the listening ports for incoming connections, and this file can be customized anytime. * Configuration files in the **mods-enabled/**, **conf-enabled/** and **sites-enabled/** directories contain particular configuration snippets which manage modules, global configuration fragments, or virtual host configurations, respectively. * They are activated by symlinking available configuration files from their respective \\*-available/ counterparts. These should be managed by using our helpers **a2enmod, a2dismod,** **a2ensite, a2dissite,** and **a2enconf, a2disconf** . See their respective man pages for detailed information. * The binary is called apache2. Due to the use of environment variables, in the default configuration, apache2 needs to be started/stopped with **/etc/init.d/apache2** or **apache2ctl**. Calling **/usr/bin/apache2** directly will not work with the default configuration. # Document Roots By default, Ubuntu does not allow access through the web browser to _any_ file apart of those located in /var/www, **public_html** directories (when enabled) and /usr/share (for web applications). If your site is using a web document root located elsewhere (such as in /srv) you may need to whitelist your document root directory in /etc/apache2/apache2.conf. The default Ubuntu document root is /var/www/html. You can make your own virtual hosts under /var/www. This is different to previous releases which provides better security out of the box. # Reporting Problems Please use the ubuntu-bug tool to report bugs in the Apache2 package with Ubuntu. However, check [existing bug reports](https://bugs.launchpad.net/ubuntu/+source/apache2) before reporting a new bug. Please report bugs specific to modules (such as PHP and others) to respective packages, not to the web server itself. 现在我要做的是修改配置文件,绑定侠客的域名。 为了不影响侠客在迁移过程中的访问,我先不修改xiake.me的解析ip,而先在本地的hosts文件中把xiake.me指向新的服务器的ip地址。\nwindows的hosts文件路径 C:\\Windows\\System32\\drivers\\etc 修改apache的配置文件,绑定xiake.me\ncd /var/www ls -l mkdir xiake ls -l cd xiake echo \u0026#34;hello world\u0026#34; \u0026gt; index.html cd /etc/apache2/sites-available vim xiake.conf 写入如下代码\nServerName www.xiake.me ServerAdmin webmaster@localhost DocumentRoot /var/www/xiake ErrorLog ${APACHE\\_LOG\\_DIR}/error.log CustomLog ${APACHE\\_LOG\\_DIR}/access.log combined ServerName xiake.me ServerAdmin webmaster@localhost DocumentRoot /var/www/xiake ErrorLog ${APACHE\\_LOG\\_DIR}/error.log CustomLog ${APACHE\\_LOG\\_DIR}/access.log combined # vim: syntax=apache ts=4 sw=4 sts=4 sr noet 更好的方式:\n# https://httpd.apache.org/docs/2.4/vhosts/name-based.html ServerName www.xiake.me ServerAlias xiake.me ServerAdmin webmaster@localhost DocumentRoot /var/www/xiake ErrorLog ${APACHE\\_LOG\\_DIR}/error.log CustomLog ${APACHE\\_LOG\\_DIR}/access.log combined # vim: syntax=apache ts=4 sw=4 sts=4 sr noet 保存后,启用配置\na2ensite xiake.conf systemctl reload apache2 这时通过浏览器访问xiake.me会看到一句“hello world”。 安装phpMyAdmin\napt install phpmyadmin 创建符号链接\nln -s /usr/share/phpmyadmin /var/www/html/phpmyadmin 访问“你的ip/phpmyadmin”测试。这里的用户名和密码是root和你的MySQL密码。 登录phpmyadmin出现错误\nmysqli_real_connect(): (HY000/1698): Access denied for user \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; 进入 /etc/php/7.2/apache2/php.ini 文件,去掉extension=mysqli前的分号“;” vim检索:/mysqli,回车,跳到下一个按小写n,上一个是大写N。 按照 https://askubuntu.com/questions/763336/cannot-enter-phpmyadmin-as-root-mysql-5-7 的解决方案\n#连接mysql sudo mysql --user=root mysql #创建新用户aoyu CREATE USER \u0026#39;aoyu\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;密码\u0026#39;; GRANT ALL PRIVILEGES ON *.* TO \u0026#39;aoyu\u0026#39;@\u0026#39;localhost\u0026#39; WITH GRANT OPTION; FLUSH PRIVILEGES; 使用新用户aoyu登录,成功,问题解决。 开启SSL 由于存在风险,我先用另一个域名shuxueben.cn测试。\n# 我是完全按照这篇文章来做的。https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-ubuntu-18-04 安装ftp server,VSFTP\napt-get install vsftpd systemctl start vsftpd systemctl enable vsftpd #创建用户,用户名:aoyuftp useradd -m aoyuftp #修改/设置密码 passwd aoyuftp #回车后输入密码,两次 #配置文件 cp /etc/vsftpd.conf /etc/vsftpd.conf.bake \u0026gt; /etc/vsftpd.conf vim /etc/vsftpd.conf #写入如下内容 listen=NO listen_ipv6=YES anonymous_enable=NO local_enable=YES write_enable=YES local_umask=022 dirmessage_enable=YES use_localtime=YES xferlog_enable=YES connect_from_port_20=YES chroot_local_user=YES secure_chroot_dir=/var/run/vsftpd/empty pam_service_name=vsftpd rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key ssl_enable=NO pasv_enable=Yes pasv_min_port=10000 pasv_max_port=10100 allow_writeable_chroot=YES #保存后就可以用ftp客户端登录了,端口22 #我用的是filezilla 我在shuxueben.cn安装wordpress时,由于文件权限,需要手动修改wp-config.php。我们需要修改文件的用户和用户组为www-data。apache的用户和用户组可以在/etc/apache2/envvars文件中看到。\nroot@iZ2zeajx0vuldrne51da6bZ:/var/www/shuxueben# chown www-data:www-data -R * 修改wordpress固定链接,发现.htaccess文件不可写。手动添加后也不能正常访问网页。怀疑apache相关模块未开启。 在phpinfo页面中发现 mod_rewrite 已开启。修改apache配置文件。\n# https://www.howtoing.com/how-to-rewrite-urls-with-mod_rewrite-for-apache-on-ubuntu-18-04 #写入 Options FollowSymLinks AllowOverride All Require all granted #加载配置 systemctl reload apache2 新服务器上的准备工作差不多已经做完了。在备份原数据前先对博客进行一些优化,避免迁移到新服务器后出现问题。 把主题、插件清理一下;垃圾评论清理一下;回收站文章、草稿清理一下;数据库下载一个插件清理一下。做完之后感觉侠客变快了很多,主要是我把jetpack插件停用了。 备份数据库。\n#使用阿里云的dms管理数据库 sudo /usr/local/mysql/bin/mysql -uroot -p77Bef3a468cc GRANT ALL PRIVILEGES ON *.* TO \u0026#39;root\u0026#39;@\u0026#39;101.37.74.67\u0026#39; IDENTIFIED BY \u0026#39;77Bef3a468cc\u0026#39; WITH GRANT OPTION;flush privileges; 将备份的数据库文件通过phpmyadmin导入新服务器数据库。 出现错误:\nWarning in ./libraries/plugin_interface.lib.php#551 count(): Parameter must be an array or an object that implements Countable Backtrace phpmyadmin版本是4.6.6,与php7.2不兼容。需要升级phpmyadmin。\n#参考http://bilibala.cc/archives/manually-upgrad-phpmyadmin-4-6-to-4-8/ mv /usr/share/phpmyadmin/ /usr/share/phpmyadmin.backup #通过ftp软件上传从phpmyadmin.net下载的压缩包 unzip phpMyAdmin-4.9.0.1-all-languages.zip mv phpMyAdmin-4.9.0.1-all-languages phpmyadmin chown root:root -R phpmyadmin/ mv phpmyadmin /usr/share/ 从浏览器访问phpmyadmin,已升级,但出现如下问题:\n配置文件现在需要一个短语密码。 变量 $cfg[\u0026#39;TempDir\u0026#39;] (./tmp/)无法访问。phpMyAdmin无法缓存模板文件,所以会运行缓慢。 #解决变量 $cfg[\u0026#39;TempDir\u0026#39;] (./tmp/)无法访问。phpMyAdmin无法缓存模板文件,所以会运行缓慢。 #注意先备份 vim /usr/share/phpmyadmin/libraries/vendor_config.php #修改如下:(可照着升级前的文件改) define(\u0026#39;CONFIG_DIR\u0026#39;, \u0026#39;/etc/phpmyadmin/\u0026#39;); #再修改一句: define(\u0026#39;TEMP_DIR\u0026#39;, \u0026#39;/var/lib/phpmyadmin/tmp/\u0026#39;); #解决配置文件现在需要一个短语密码。 vim /usr/share/phpmyadmin/libraries/config.default.php #其中某一句随便写一些字符 $cfg[\u0026#39;blowfish_secret\u0026#39;] = \u0026#39;dajiangdongqulangtaojinqiangufengliurenwu\u0026#39;; #大功告成,清理文件 rm -f /usr/share/phpmyadmin/libraries/vendor_config.php.backup rm -rf /usr/share/phpmyadmin.backup 我用阿里云的dms导出数据库,导入数据库的时候提示错误。我又用了一个wp插件,用插件备份数据库,虽然有警告但导入成功。 迁移文件。\nmv ./shuxueben/wordpress-5.2.2-zh_CN.tar.gz ./xiake cd xiake tar xzf wordpress-5.2.2-zh_CN.tar.gz rm -f wordpress-5.2.2-zh_CN.tar.gz chown www-data:www-data -R wordpress mv wordpress/ mine 好吧,太烦了。我直接重装了wordpress,然后导入xml文件。 为侠客开启https\ncertbot --apache -d xiake.me -d www.xiake.me SQL查询批量替换图片链接\nUPDATE xk_posts SET post_content = REPLACE( post_content, \u0026#39;https://www.xiake.me/mine/wp-content/uploads\u0026#39;, \u0026#39;https://www.xiake.me/wp-content/uploads\u0026#39; ) ","date":"Aug 31","permalink":"https://o5o.me/post/cotart/migrate_wordpress/","tags":[null],"title":"迁移WordPress"},{"categories":[null],"contents":"在当前主题的function.php文件中添加:\nfunction liveme_if_login() { if(!is_user_logged_in()){ auth_redirect(); } } 在主题的header.php文件顶部添加:\nphp liveme_if_login();? 这样,访问WordPress网站时,如果未登录会自动跳转到登录页,登录后可正常浏览。\n","date":"Aug 07","permalink":"https://o5o.me/post/cotart/wordpress_view_content_after_login/","tags":[null],"title":"WordPress内容登录后查看"},{"categories":[null],"contents":"几天前收到阿里云发来的短信和邮件,提醒我shuxueben.cn网站未指向阿里云服务器,如果3天内不修正就要删除我的网站备案接入信息。 我又去阿里云买了一个“轻量应用服务器”,环境选择了Ubuntu。\n所有的命令我都是以root身份执行的。 首先更新系统[1]\napt-get update apt-get upgrade apt-get dist-upgrade 安装Nginx\napt-get install nginx 查看是否安装成功\nnginx -v 这时候,访问服务器ip地址或指向该ip地址的域名,就可以看到一个Nginx的欢迎信息。 安装Mysql\napt-get install mysql-server (中间会让输入root密码) apt-get install mysql-client 查看是否安装成功\nmysql -u root -p 回车,输入密码 select version(); exit; #退出mysql环境 安装php\napt-get install php php -v(查看版本) 安装FastCgi\napt-get install spawn-fcgi 修改Nginx站点配置文件\ncd /etc/nginx/sites-available/ cp default default.backup vim default 添加index.php\n# Add index.php to the list if you are using PHP index index.html index.htm index.nginx-debian.html index.php; 去掉部分注释[2]:\n# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # location ~ \\.php$ { include snippets/fastcgi-php.conf; # #\t# With php7.0-cgi alone: #\tfastcgi_pass 127.0.0.1:9000; #\t# With php7.0-fpm: fastcgi_pass unix:/run/php/php7.0-fpm.sock; } 安装mysql扩展\napt-get install php7.0-mysql 安装必要扩展\napt-get install php7.0 php-pear apt-get install php7.0-curl apt-get install php7.0-json apt-get install php7.0-cgi 在根目录(/var/www/html 可以在default配置文件中看到)中新建test.php文件,测试php\nvim test.php 按i进入编辑模式,插入: php echo phpinfo(); ? 按Esc,输入“:wq”保存并退出 在浏览器中访问“你的ip/test.php”测试。 nginx相关命令\nservice nginx restart service nginx start service nginx stop Nginx配置文件夹中的site-available和sites-enabled的区别[3][4]\nIf you are coming from Apache, the \u0026#34;sites-available\u0026#34; and \u0026#34;sites-enabled\u0026#34; directories will be familiar. These directories are used to define configurations for your websites. Files are generally created in the \u0026#34;sites-available\u0026#34; directory, and then symbolically linked to the \u0026#34;sites-enabled\u0026#34; directory when they are ready to go live. ... In the \u0026#34;nginx.conf\u0026#34; file, we can see that the end of the \u0026#34;http\u0026#34; block has: include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; # How To Configure Nginx # https://www.digitalocean.com/community/tutorials/how-to-configure-the-nginx-web-server-on-a-virtual-private-server Nginx不能正常启动的常见原因[5] nginx端口被占用\nnetstat -tnlp ps -ef | grep nginx pkill -9 nginx service nginx restart nginx 日志文件出现”fastcgi_pass” directive is duplicate 错误[6] 这是在修改刚才那个配置文件default的时候,同时去掉了“fastcgi_pass 127.0.0.1:9000;”和“fastcgi_pass unix:/run/php/php7.0-fpm.sock;”这两行的注释。\n启动php-fpm进程[7]\nsystemctl start php7.0-fpm 或 service start php7.0-fpm 而非\nsystemctl start php-fpm service start php-fpm Nginx添加虚拟主机Virtual Host 把Nginx配置文件default最下面的一些代码取消注释,把关键信息修改成你自己的即可。 我在添加虚拟主机的同时,还将顶级域名301重定向到www二级域名[8][9],我的代码如下:\n# Virtual Host configuration for example.com # # You can move that to a different file under sites-available/ and symlink that # to sites-enabled/ to enable it. # server { listen 80; listen [::]:80; server_name www.shuxueben.cn; root /var/www/www.shuxueben.cn; index index.html index.php; location / { try_files $uri $uri/ =404; } } server { listen 80; listen [::]:80; server_name shuxueben.cn; return 301 http://www.shuxueben.cn$request_uri; } listen 80;和listen [::]:80;均表示监听80端口的链接,后者是前者的ipv6版本[10]。 在访问php页面时总是弹出下载页面[11] 这是因为在nginx的配置文件中没有添加解析php的代码,我们把default文件中虚拟主机那部分的代码修改为如下形式:\nserver { listen 80; listen [::]:80; server_name www.shuxueben.cn; root /var/www/www.shuxueben.cn; index index.html index.php; location / { try_files $uri $uri/ =404; } location ~ \\.php$ { include snippets/fastcgi-php.conf; # # # With php7.0-cgi alone: # fastcgi_pass 127.0.0.1:9000; # # With php7.0-fpm: fastcgi_pass unix:/run/php/php7.0-fpm.sock; } } 安装phpMyAdmin[12]\napt install phpmyadmin; 提示选择web服务器时,因为列表中没有Nginx,按Tab键跳过。再按回车。 Configure database for phpmyadmin with dbconfig-common? 选择Yes MySQL application password for phpmyadmin,这个密码仅仅在phpMyAdmin内部用于和MySQL通信,留空即可,会自动生成密码。登录phpMyAdmin时还是用你的MySQL密码。 创建符号链接\nln -s /usr/share/phpmyadmin /var/www/html/phpmyadmin 访问“你的ip/phpmyadmin”测试。这里的用户名和密码是root和你的MySQL密码。\n上传网站代码,以WordPress为例\ncd /var/www/www.shuxueben.cn wget https://wordpress.org/latest.tar.gz tar zxf latest.tar.gz #得到wordpress文件夹 修改文件所有者和用户组为www-data[13][14]\nchown www-data:www-data -R wordpress 文件权限不需要修改。修改权限可以使用chmod命令。 批量给文件设置644权限、给文件夹设置755权限[15]\nchmod 644 -R wordpress find wordpress -type d -print|xargs chmod 755 创建www用户组和用户(用不到)[16] 我的wordpress上传文件时提示输入ftp账户和密码,是文件所有者和所在用户组的问题。我以为应该把文件的所有者和用户组设为www,其实应该设为www-data。 系统中没有www用户和用户组。其实不需要手动添加的。\nid www groupadd www useradd -g www -s /sbin/nologin www id www 后来我发现没用,又把www用户和用户组删了。 删除用户和用户组[17]\nuserdel www groupdel www wordpress上传文件大小最大只有2MB,需要修改php.ini文件。 找到php.ini文件,可用浏览器访问我们刚才写的test.php文件,里面有php.ini的路径。[18]\n/etc/php/7.0/fpm/php.ini 修改wordpress固定链接[19] 需要修改Nginx的网站配置文件代码,要添加的代码为:\nif (-f $request_filename/index.html){ rewrite (.*) $1/index.html break; } if (-f $request_filename/index.php){ rewrite (.*) $1/index.php; } if (!-f $request_filename){ rewrite (.*) /index.php; } 完整的虚拟主机配置代码为:\n# Virtual Host configuration for example.com # # You can move that to a different file under sites-available/ and symlink that # to sites-enabled/ to enable it. # server { listen 80; listen [::]:80; server_name www.shuxueben.cn; root /var/www/www.shuxueben.cn; index index.html index.php; if (-f $request_filename/index.html){ rewrite (.*) $1/index.html break; } if (-f $request_filename/index.php){ rewrite (.*) $1/index.php; } if (!-f $request_filename){ rewrite (.*) /index.php; } location / { try_files $uri $uri/ =404; } location ~ \\.php$ { include snippets/fastcgi-php.conf; # # # With php7.0-cgi alone: # fastcgi_pass 127.0.0.1:9000; # # With php7.0-fpm: fastcgi_pass unix:/run/php/php7.0-fpm.sock; } } server { listen 80; listen [::]:80; server_name shuxueben.cn; return 301 http://www.shuxueben.cn$request_uri; } wordpress后台工具里有一个“站点健康”,提醒我php没有安装bcmath和imagick模块。[20][21]\napt-get install php-bcmath php -m #如果出现bcmath就说明安装成功了 apt-get install php-imagick php -m #如果出现imagick就说明安装成功了, 如果没有,可重启php-fpm服务 service php7.0-fpm restart #或者重启apache 2019年8月31日0:40更新 systemctl restart apache2 参考文献 我想说明的是,我不保障下面所列网址里的内容为作者原创,但它们都给予了我很大的帮助。\n[1] Ubuntu 16 安装Nginx+Php+Mysql - 西贝小小凤 - 博客园 https://www.cnblogs.com/xbxxf/p/9122920.html [2] 在树莓派上安装 Nginx + PHP - 简书 https://www.jianshu.com/p/81a75b4b51bd [3] nginx里面的sites-available和sites-enabled有什么区别 - SegmentFault 思否 https://segmentfault.com/q/1010000009726769 [4] How To Configure The Nginx Web Server On a Virtual Private Server | DigitalOcean https://www.digitalocean.com/community/tutorials/how-to-configure-the-nginx-web-server-on-a-virtual-private-server [5] 关于Nginx不能正常启动的问题 - 秋天未成熟的博客 - CSDN博客 https://blog.csdn.net/u012832088/article/details/80729002 [6] 解决安装nginx 日志文件出现\u0026#34;fastcgi_pass\u0026#34; directive is duplicate 错误 - qq_26184091的博客 - CSDN博客 https://blog.csdn.net/qq_26184091/article/details/45419201 [7] Ubuntu安装PHP7 - 程序小工 - 博客园 https://www.cnblogs.com/zqunor/p/8524646.html [8] nginx 301 将不带www域名,重定向到www域名 - beyond__devil的博客 - CSDN博客 https://blog.csdn.net/beyond__devil/article/details/79880947 [9] Converting rewrite rules Converting rewrite rules [10] Listen 0.0.0.0:80 Listen [::0]:80 - weixin_34037173的博客 - CSDN博客 https://blog.csdn.net/weixin_34037173/article/details/93885997 [11] centos系统,基于nginx服务器,用https访问php页面总弹出下载页面问题完美解决 - leeten的博客 - CSDN博客 https://blog.csdn.net/jinx_leeten/article/details/51769154 [12] 在Ubuntu 18.04上为Nginx安装phpMyAdmin - 斑驳的岁月 - CSDN博客 https://blog.csdn.net/weixin_39467231/article/details/83958552 [13] 要执行请求的操作,WordPress 需要访问您网页服务器的权限。 请输入您的 FTP 登录XXXX完美解决方法 - Michael Wing\u0026#39;s workspace - CSDN博客 https://blog.csdn.net/micwing/article/details/12004987 [14] LNMP添加、删除虚拟主机及伪静态使用教程 - LNMP一键安装包 https://lnmp.org/faq/lnmp-vhost-add-howto.html [15] linux 批量设置文件夹755 文件644权限 - 正风三才的博客 - CSDN博客 https://blog.csdn.net/tty521/article/details/73123857 [16] linux创建www用户组和用户 http://www.dreamwu.com/post-20.html [17] Ubuntu基础命令(六)--添加和删除用户和用户组 - Mikowoo007的博客 - CSDN博客 https://blog.csdn.net/Mikowoo007/article/details/84952068 [18] 查找php配置文件php.ini所在路径的二种方法 - 齐泽文的Blog - CSDN博客 https://blog.csdn.net/qq_17054989/article/details/79832507 [19] nginx 配置wordpress固定链接(自定义) - 微笑阳光哈*_* - 博客园 https://www.cnblogs.com/holdon521/p/5675729.html [20] Ubuntu PHP安装bcmath模块 - GoodByeZ - 博客园 https://www.cnblogs.com/jingjingdidunhe/p/6945355.html [21] how to install imagemagick for php7 on ubuntu? - Ask Ubuntu https://askubuntu.com/questions/769396/how-to-install-imagemagick-for-php7-on-ubuntu 2019年8月6日更新 shuxueben.cn 出现错误: 413 Request Entity Too Large 我在nginx.conf(/etc/nginx)中添加了一行\nclient_max_body_size 50m; ","date":"Jul 29","permalink":"https://o5o.me/post/cotart/ubuntu_nginx_php_fastcgi_mysql_phpmyadmin_struct_website/","tags":[null],"title":"LNMP网站环境搭建"},{"categories":[null],"contents":"迷宫寻路是一个很有趣的问题,这篇文章是对我思考过程的重述。完整流程图和代码请直接翻到文章最后。我的算法比较简单,在我的印象中,“算法”都是那种很“高大上”的东西,不敢相信我写出来的东西也能被叫做算法。所以我在标题里用了“问题”两个字?。有时间了真得系统地学学算法。\n问题:已知一迷宫图,B,E两点分别为起始点及终止点,图中红色线为不可穿越路径,蓝色线为可穿越路径。某人从B点出发,当到达某一位置时,其周围的路径通行情况才被探索,未到达的位置,其周围路径该人无法得到。 求:从B点到E点的一条路径 Matlab Ctrl+R 多行注释;Ctrl+T 取消多行注释。 axis on/off 坐标区线条和背景的可见性,指定为 on 或 off。 maze是最常见的一个表示“迷宫”的词。labyrinth是专指古希腊Crete(克里特)岛上的迷宫,由Crete国王,Zeus与Europe之子,Minos建造,里面关着Minos之妻与Poseidon的牛的后代——Minotaur,一个半牛半人的怪物,凶残而暴虐。\n分三步进行\n迷宫的数字化及迷宫的MATLAB绘制 为了绘制迷宫框线,我们可以这样考虑:我们把二维迷宫想象为铺在一个X-Y坐标系上;迷宫由一条条长度为1的短线拼接而成。 我们分别绘制横向的迷宫框线和纵向的迷宫框线。对于横向短线,它的颜色由左端点的值决定;对于纵向短线,它的颜色由下端点处的值决定。 我们也可以说,坐标系中的某迷宫结点,向上延伸出一条竖向的短线、向右延伸出一条横向的短线。例如,对于点(2,1)来讲,它向上延伸出一条蓝色的线,向右延伸出一条红色的线。 我们约定,如果某点延伸出的线的颜色为红色,则该点处的值为“0”,否则为“1”(蓝色)。 例如,我们想绘制横向的线,则点(1,1)处的值为0,点(1,2)处的值为1,……,(2,1)=0, (3,1)=0, (4,1)=0, (5,1)=0, (1,2)=1, (2,2)=1, …… 那么,我们可以得到一个0-1矩阵如下:\nhori=[ 0 1 1 1 0 0; 0 1 1 1 1 0; 0 1 1 1 1 0; 0 1 0 1 0 0; 0 0 0 1 1 0; ]; 同理,对于纵向的线,则有(1,1)=0, (2,1)=1, (3,1)=0, (4,1)=1, (5,1)=1, (6,1)=0, (1,2)=0, …… 那么我们也可以再得到一个0-1矩阵如下:\nvert=[ 0 0 0 0 0; 1 0 0 0 1; 0 1 0 0 0; 1 0 1 0 0; 1 1 1 0 1; 0 0 0 0 0; ]; 这样我们便把一个二维迷宫数字化为了两个0-1矩阵。 接下来我们使用Matlab的plot函数绘制迷宫框线。plot函数的实质其实就是“描点连线”,所以,如果我们想绘制横框线,我们只需要把两个点用带颜色的线连结起来就可以了。 如点(1,1)处横向框线是红色的,那么我们只需要用plot在点(1,1)和点(2,1)间绘制一条红色的线,用代码来表示就是:\nplot([1,2],[1,1],\u0026#39;r\u0026#39;) 接下来,我们使用两层嵌套的for循环,绘制出该二维迷宫中所有的横向框线,代码如下:\nhold on axis square axis([0,7,0,7]) for i=1:5 for j=1:6 if hori(i,j)==1 plot([i,i+1],[j,j],\u0026#39;b\u0026#39;) else plot([i,i+1],[j,j],\u0026#39;r\u0026#39;) end end end 效果如图: 接下来我们再在此基础上绘制出所有的纵向框线,迷宫绘制完成。完整代码如下:\nhori=[ 0 1 1 1 0 0; 0 1 1 1 1 0; 0 1 1 1 1 0; 0 1 0 1 0 0; 0 0 0 1 1 0; ]; vert=[ 0 0 0 0 0; 1 0 0 0 1; 0 1 0 0 0; 1 0 1 0 0; 1 1 1 0 1; 0 0 0 0 0; ]; hold on axis square axis([0,7,0,7]) for i=1:5 for j=1:6 if hori(i,j)==1 plot([i,i+1],[j,j],\u0026#39;b\u0026#39;) else plot([i,i+1],[j,j],\u0026#39;r\u0026#39;) end end end for i=1:6 for j=1:5 if vert(i,j)==1 plot([i,i],[j,j+1],\u0026#39;b\u0026#39;) else plot([i,i],[j,j+1],\u0026#39;r\u0026#39;) end end end hold off 效果如图: 为了分析问题的方便,我们把红色线条的宽度设置为2,让迷宫图形更加直观:\nplot([i,i+1],[j,j],\u0026#39;r\u0026#39;,\u0026#39;LineWidth\u0026#39;,2) plot([i,i],[j,j+1],\u0026#39;r\u0026#39;,\u0026#39;LineWidth\u0026#39;,2) 效果如图: 路径求解 在迷宫绘制完成后,我们从正常的人的角度看这个迷宫。迷宫是由一个个小方格构成的,“走迷宫”时“走一步,再走一步”之中的“一步”,指的是在这些小方格之间的一次移动。在求解时我们把这一个个小方格抽象为一个个的点,那么“走一步”就变成了点的坐标的变换。 在该迷宫问题中,有这样一个限制:当到达某一位置时,其周围的路径通行情况才被探索;未到达的位置,其周围路径该人无法得到。也就是说,我们走到某一个点(某一个方格)处时,才可以判断“上下左右”四个方向的哪条路可以走。 如果只有一个方向可以走,那么我们接着往下走就可以了;如果有两个以上方向可以走,就面临选择“走哪条路”的问题;如果除了来时的路,无路可走,那么我们就需要原路返回,在上一个点处重新选择路径。 我们把思考过程用流程图(不是标准流程图,但能说明问题)表示如下: Created with Raphaël @@VERSIONStart输入数据,定义各种变量查找所有可以走的路径查找所有未走过的路径未走过的路径 数量是否为0?原路回退随机选取一条到下一结点是否终点?Endyesnoyesno 接下来我们要做的就是用严谨的编程语言把流程图给叙述出来。\n定义变量 在这个迷宫中,起点的坐标为(1,5),终点的坐标为(5,5),要判断迷宫是否已到终点,我们只需要比较“位置变量中的值”和“终点坐标”是否相同,如果相同,说明我们到达了终点,程序结束;如果不相同,说明我们还要继续往前走。很显然,我们需要用到“循环”来实现“走一步,再走一步”的过程,条件为“坐标变量表示的点不是终点”,Matlab中的while循环很合适。 该怎样将坐标保存到变量中呢?对于一个点,我用一个1×2的矩阵保存横纵坐标;对于点的列表,我用n×2的矩阵保存坐标,随着循环的进行,不断把新的点的坐标追加到矩阵中。 我定义的变量有:\nBegP=[1,5];%初始点坐标 EndP=[5,5];%终点坐标 PosP=BegP;%当前位置点坐标,走一步变换一次,初值BegP PosHis=[];%已走过位置坐标列表,初值空 PosB=[];%所有可走路径 PosBB=[];%所有未走过的路径 PTime=0;%尝试次数 %前进次数 BTime=0;%后退次数 接下来的所有路径求解代码都是在循环中的,先把框架搭好:\nwhile ~isequal(PosP,EndP) end 循环条件是“PosP和EndP两个变量中的值是否相等”,如果不相等,循环就继续进行下去。函数isequal()用来判断两个数组是否相等;前面的“~”表示“取反”。 每一次循环时,我们都要把当前位置点追加到PosHis中,尝试次数也要增加1次,代码如下:\nwhile ~isequal(PosP,EndP) PosHis=[PosHis;PosP(1),PosP(2)]; PTime=PTime+1; end 查找所有可走的路径 判断路径是否可以通过,即判断横向点矩阵中”当前点“和”上方紧邻点“对应位置处的值是”1或0“、判断纵向点矩阵中”当前点“和”右方紧邻点“对应位置处的值是”1或0“,代码表示如下:\nwhile ~isequal(PosP,EndP) PosHis=[PosHis;PosP(1),PosP(2)]; PTime=PTime+1; % 查找所有可走的路径 if hori(PosP(1),PosP(2))==1 PosB=[PosB;PosP(1),PosP(2)-1]; end if vert(PosP(1),PosP(2))==1 PosB=[PosB;PosP(1)-1,PosP(2)]; end if hori(PosP(1),PosP(2)+1)==1 PosB=[PosB;PosP(1),PosP(2)+1]; end if vert(PosP(1)+1,PosP(2))==1 PosB=[PosB;PosP(1)+1,PosP(2)]; end end 查找所有未走过的路径 找到哪条路径没有走过,我们只需要判断PosB中的各点是否在PosHis中,在PosHis中的点就是已走过的点,我们可以使用ismember()函数实现。请查阅官方帮助文档获取详细信息。代码如下:\n%查找所有未走过的路径 LiaP=ismember(PosB,PosHis,\u0026#39;rows\u0026#39;);%判断PosB中的行能否在PosHis中找到 PosBB=PosB(LiaP==0,:);%逻辑索引 选择路径 如果PosBB非空,那么就说明在当前位置周围,还有我们没走过的路,我们可以从中选择一条来走;如果PosBB为空,说明当前位置周围所有的路我们都走过了,只好原路回退了。 如果走未走过的路,我本来想的是随机选择一条,但后来又想了想,直接走PosBB中第一行元素对应的点就好,我们走错了路还可以回来,而且,PosBB是每走一步都要重新计算的。 如果原路返回,直接从PosHis读取上一个点的坐标就行。 代码如下:\nif isempty(PosBB) %原路返回 PosP=PosHis(PTime-1,:); else %走到下一结点. 走PosBB中的第一行坐标, 没问题 PosP=PosBB(1,:); PosB=[]; PosBB=[]; end 现在代码初具规模,我们走一走看看能走到哪里,while中代码如下:\nwhile ~isequal(PosP,EndP) PosHis=[PosHis;PosP(1),PosP(2)]; PTime=PTime+1; % 查找所有可走的路径 if hori(PosP(1),PosP(2))==1 PosB=[PosB;PosP(1),PosP(2)-1]; end if vert(PosP(1),PosP(2))==1 PosB=[PosB;PosP(1)-1,PosP(2)]; end if hori(PosP(1),PosP(2)+1)==1 PosB=[PosB;PosP(1),PosP(2)+1]; end if vert(PosP(1)+1,PosP(2))==1 PosB=[PosB;PosP(1)+1,PosP(2)]; end %查找所有未走过的路径 LiaP=ismember(PosB,PosHis,\u0026#39;rows\u0026#39;);%判断PosB中的行能否在PosHis中找到 PosBB=PosB(LiaP==0,:);%逻辑索引 %路径选择 if isempty(PosBB) %原路返回 PosP=PosHis(PTime-1,:); else %走到下一结点. 走PosBB中的第一行坐标, 没问题 PosP=PosBB(1,:); PosB=[]; PosBB=[]; end end 可以发现,走到点(1,4)处就要出问题了,因为前面已无路可走,要原路返回,但原路返回的代码有问题,导致我们只能来回地在点(1,4)和点(1,3)之间移动。 能原路返回的关键是能根据PTime和BTime在PosHis中找到正确的坐标,经计算,我把相关代码修改为:\n%路径选择 if isempty(PosBB) %原路返回 BTime=BTime+1; PosP=PosHis(PTime-2*BTime+1,:); PosB=[]; PosBB=[]; else %走到下一结点. 走PosBB中的第一行坐标, 没问题 BTime=0;%把后退次数清零 PosP=PosBB(1,:); PosB=[]; PosBB=[]; end 我还想过引入一个NTime变量来记录前进次数(不包含后退次数),后来发现没必要。 我们再来测试程序,看能走到哪里。 我们可以发现,当第3次走到点(4,1)的时候出问题了,本应回退到点(3,1),但却回退到了(4,2)。 这个问题是由我们”原路返回部分“的代码造成的,因为我们为回退到之前的点,采用的坐标计算方式是线性的,不能跳过一部分点。 这个问题不能在现有的流程框架下解决,我们有必要对流程图进行修改。 我的做法是:引入”标记点“。当某点可以前进的方向大于1条时,就把这个点储存起来作为”标记点“,如果某个标记点无路可走时,则直接跳到前一个标记点。 如果在某点处未走过的路径为3条(更多条的情况在这种形式的迷宫下不会出现),则该点会被重复标记一次(是的,一次),程序有在该点处产生死循环的危险,当然,这个迷宫里没有这种情况出现,但我们要考虑到更一般的情况。有两种方式解决,一,在标记某点时先判断该点是否已经是标记点;二,在返回上一个标记点时,返回上一个紧邻的与当前点不同的标记点。~~我这里采用第二种方式,因为我觉得第二种方式可以把代码写得很巧妙。~~我这里不对这个问题进行修正。 新的流程图如下: Created with Raphaël @@VERSIONStart输入数据,定义各种变量查找所有可以走的路径查找所有未走过的路径未走过的路径 数量是否为0?当前点是否 为标记结点?回退到上一 非自身紧邻点原路回退未走过的路径 数量是否为1?走第一条到下一结点是否终点?End标记该节点yesnoyesnoyesnoyesno ”路径选择“部分,新代码如下:\n%路径选择 if isempty(PosBB) %判断该点是否为标记结点 [LiaS,LocbS]=ismember(PosP,PosPS,\u0026#39;rows\u0026#39;); if LiaS==1 PosP=PosPS(LocbS-1,:);%返回到上一标记结点 BTime=0;%把后退次数清零 PosB=[]; PosBB=[]; else %原路返回 BTime=BTime+1; PosP=PosHis(PTime-2*BTime+1,:); PosB=[]; PosBB=[]; end else if size(PosBB,1)==1 BTime=0;%把后退次数清零 PosP=PosBB(1,:); PosB=[]; PosBB=[]; else BTime=0;%把后退次数清零 PosPS=[PosPS;PosP];%标记该点为特殊点%注意此时PosP还没被重新赋值 PosP=PosBB(1,:);%PosP被重新赋值 PosB=[]; PosBB=[]; end end 我又引入了一个变量PosPS用来保存特别标记点。我还想过引入一个变量PosPSH,用来保存两个相邻标记点之间的点,一步步退回去,后来觉得没必要,又去掉了。 我还想过给变量PosPS一个初值,把BegP赋值给它。后来明白这是多此一举。因为即便是第一个点,后面的代码也会判断该点是否应该作为标记点。 再来测试代码,程序顺利走到了终点。此时我们成功完成了路径求解部分的代码,代码如下:\nBegP=[1,5];%初始点坐标 EndP=[5,5];%终点坐标 PosP=BegP;%当前位置点坐标,走一步变换一次,初值BegP PosHis=[];%已走过位置坐标列表,初值空 PosB=[];%所有可走路径 PosBB=[];%所有未走过的路径 PTime=0;%尝试次数 %前进次数 BTime=0;%后退次数 PosPS=[];%特别标记点,前进方向多于1个的点,%del但无论怎样,初始点是特别标记点 while ~isequal(PosP,EndP) PosHis=[PosHis;PosP(1),PosP(2)]; PTime=PTime+1; % 查找所有可走的路径 if hori(PosP(1),PosP(2))==1 PosB=[PosB;PosP(1),PosP(2)-1]; end if vert(PosP(1),PosP(2))==1 PosB=[PosB;PosP(1)-1,PosP(2)]; end if hori(PosP(1),PosP(2)+1)==1 PosB=[PosB;PosP(1),PosP(2)+1]; end if vert(PosP(1)+1,PosP(2))==1 PosB=[PosB;PosP(1)+1,PosP(2)]; end %查找所有未走过的路径 LiaP=ismember(PosB,PosHis,\u0026#39;rows\u0026#39;);%判断PosB中的行能否在PosHis中找到 PosBB=PosB(LiaP==0,:);%逻辑索引 %路径选择 if isempty(PosBB) %判断该点是否为标记结点 [LiaS,LocbS]=ismember(PosP,PosPS,\u0026#39;rows\u0026#39;); if LiaS==1 PosP=PosPS(LocbS-1,:);%返回到上一标记结点 BTime=0;%把后退次数清零 PosB=[]; PosBB=[]; else %原路返回 BTime=BTime+1; PosP=PosHis(PTime-2*BTime+1,:); PosB=[]; PosBB=[]; end else if size(PosBB,1)==1 BTime=0;%把后退次数清零 PosP=PosBB(1,:); PosB=[]; PosBB=[]; else BTime=0;%把后退次数清零 PosPS=[PosPS;PosP];%标记该点为特殊点%注意此时PosP还没被重新赋值 PosP=PosBB(1,:);%PosP被重新赋值 PosB=[]; PosBB=[]; end end end 路径绘制 接下来就是绘制指示正确路径的箭头的部分了。 我根据PosHis中保存的点来绘制正确路径。我们走了很多弯路,现在要把这些”弯路“去掉。我是这样考虑的:如果一个点在PosHis中重复出现,那么出现的两次之间所夹的点就是无用的,这些点连起来所构成的路径就是”弯路“。我们用代码实现这一过程。代码如下:\ni=1; while i\u0026lt;=size(PosHis,1) [LiaPP,LocbPP]=ismember(PosHis(i,:),PosHis((i+1):end,:),\u0026#39;rows\u0026#39;); if LiaPP==1 i=i+LocbPP; end PlotPP=[PlotPP;PosHis(i,:)]; i=i+1; end PlotPP=[PlotPP;EndP]; 可从代码看出,我引入了一个新变量PlotPP,用来保存从PosHis中得到的构成”正确路径“的各点坐标。因为我们放置的位置的原因,PlotHis中没有包含终点坐标,我也给额外添加到了PlotPP中。 我们用每个小方格左下角的点来代表这个小方格,但我们画箭头的时候肯定是要把箭头画在方格中心位置,所以需要对PlotPP中的坐标进行修正。 我使用quiver()函数来画箭头。我将所有箭头起点的坐标保存在一个变量中,将所有箭头终点坐标保存在另一个变量中。我还计算得到每个箭头向量的坐标。 代码如下:\n%quiver(x,y,u,v) x,y为起点坐标,u,v为起点在原点的向量的坐标,也就是在x,y轴的分量 %得到每个方格的中心点坐标 PlotPPF=PlotPP+0.5; %起点坐标和终点坐标 PlotPPA=PlotPPF(1:end-1,:); PlotPPZ=PlotPPF(2:end,:); %得到向量坐标u,v PlotPPV=PlotPPZ-PlotPPA; quiver(PlotPPA(:,1),PlotPPA(:,2),PlotPPV(:,1),PlotPPV(:,2),0.5,\u0026#39;LineWidth\u0026#39;,2) 我还把箭头宽度变为了原来的2倍,看起来更美观。\n这样,我们的程序就大功告成了,完整代码如下:\n% 我的Matlab版本:2018b,运行通过。 clear clc hori=[ 0 1 1 1 0 0; 0 1 1 1 1 0; 0 1 1 1 1 0; 0 1 0 1 0 0; 0 0 0 1 1 0; ]; vert=[ 0 0 0 0 0; 1 0 0 0 1; 0 1 0 0 0; 1 0 1 0 0; 1 1 1 0 1; 0 0 0 0 0; ]; hold on axis square axis([0,7,0,7]) for i=1:5 for j=1:6 if hori(i,j)==1 plot([i,i+1],[j,j],\u0026#39;b\u0026#39;) else plot([i,i+1],[j,j],\u0026#39;r\u0026#39;,\u0026#39;LineWidth\u0026#39;,2) end end end for i=1:6 for j=1:5 if vert(i,j)==1 plot([i,i],[j,j+1],\u0026#39;b\u0026#39;) else plot([i,i],[j,j+1],\u0026#39;r\u0026#39;,\u0026#39;LineWidth\u0026#39;,2) end end end %hold off %注意这里的hold off被我移动到了最下面 BegP=[1,5];%初始点坐标 EndP=[5,5];%终点坐标 PosP=BegP;%当前位置点坐标,走一步变换一次,初值BegP PosHis=[];%已走过位置坐标列表,初值空 PosB=[];%所有可走路径 PosBB=[];%所有未走过的路径 PTime=0;%尝试次数 %前进次数 BTime=0;%后退次数 PosPS=[];%特别标记点,前进方向多于1个的点,%del但无论怎样,初始点是特别标记点 PlotPP=[];%画图有效点 while ~isequal(PosP,EndP) PosHis=[PosHis;PosP(1),PosP(2)]; PTime=PTime+1; % 查找所有可走的路径 if hori(PosP(1),PosP(2))==1 PosB=[PosB;PosP(1),PosP(2)-1]; end if vert(PosP(1),PosP(2))==1 PosB=[PosB;PosP(1)-1,PosP(2)]; end if hori(PosP(1),PosP(2)+1)==1 PosB=[PosB;PosP(1),PosP(2)+1]; end if vert(PosP(1)+1,PosP(2))==1 PosB=[PosB;PosP(1)+1,PosP(2)]; end %查找所有未走过的路径 LiaP=ismember(PosB,PosHis,\u0026#39;rows\u0026#39;);%判断PosB中的行能否在PosHis中找到 PosBB=PosB(LiaP==0,:);%逻辑索引 %路径选择 if isempty(PosBB) %判断该点是否为标记结点 [LiaS,LocbS]=ismember(PosP,PosPS,\u0026#39;rows\u0026#39;); if LiaS==1 PosP=PosPS(LocbS-1,:);%返回到上一标记结点 BTime=0;%把后退次数清零 PosB=[]; PosBB=[]; else %原路返回 BTime=BTime+1; PosP=PosHis(PTime-2*BTime+1,:); PosB=[]; PosBB=[]; end else if size(PosBB,1)==1 BTime=0;%把后退次数清零 PosP=PosBB(1,:); PosB=[]; PosBB=[]; else BTime=0;%把后退次数清零 PosPS=[PosPS;PosP];%标记该点为特殊点%注意此时PosP还没被重新赋值 PosP=PosBB(1,:);%PosP被重新赋值 PosB=[]; PosBB=[]; end end end i=1; while i\u0026lt;=size(PosHis,1) [LiaPP,LocbPP]=ismember(PosHis(i,:),PosHis((i+1):end,:),\u0026#39;rows\u0026#39;); if LiaPP==1 i=i+LocbPP; end PlotPP=[PlotPP;PosHis(i,:)]; i=i+1; end PlotPP=[PlotPP;EndP]; %quiver(x,y,u,v) x,y为起点坐标,u,v为起点在原点的向量的坐标,也就是向量在x,y轴的分量 %得到每个方格的中心点坐标 PlotPPF=PlotPP+0.5; %起点坐标和终点坐标 PlotPPA=PlotPPF(1:end-1,:); PlotPPZ=PlotPPF(2:end,:); %得到向量坐标u,v PlotPPV=PlotPPZ-PlotPPA; quiver(PlotPPA(:,1),PlotPPA(:,2),PlotPPV(:,1),PlotPPV(:,2),0.5,\u0026#39;LineWidth\u0026#39;,2) hold off %注意,上面的hold off被我移动到了这里 效果如图: 写在最后 我觉得这个问题最难的部分就是程序开头,定义变量那部分。定义什么变量都随意,没人规定好,要自行判断,简直纠结死个人。虽说万事开头难,也正是魅力所在。 C语言对我影响很大,再加上我对面向对象的语言特性理解得也不深刻,我会不由自主地以面向过程的思考方式或者说编程方式去思考问题。也许以后我能给出更完美的解答。 程序可改进的部分我已写在文中,也许还有其他不妥之处我暂时没注意到,留待以后完善。\n","date":"Jul 28","permalink":"https://o5o.me/post/wp/maze-automatic-path-finding/","tags":[null],"title":"迷宫自动寻路问题求解"},{"categories":[null],"contents":"昨天左手手指擦在空调外机冷凝器上,受了伤。 为避免伤口沾水,我在手接触水的时候不由自主的把左手食指翘起来。 也许这就是兰花指的由来?古代女性做针线活的时候不小心把食指刺伤,为避免触碰到伤口,所以翘起手指。\n","date":"Jul 27","permalink":"https://o5o.me/post/cotart/the_origin_of_the_orchid_finger/","tags":[null],"title":"兰花指的由来"},{"categories":[null],"contents":"从哆嗒数学网微信公众号看到一个LaTeX教程, 感觉还行, 视频作者将LaTeX和emacs结合使用, 貌似效率很高. 期末考试完后我也试了试emacs编辑器. 没想到安装包那么大. 看完了软件自带教程, 感觉实在是用不来, 按键太反人类了. 这时我想到了和emacs齐名的vim. 许久都没用过vim了, 我甚至忘了我是怎么学的vim. 哦…我想想, 我之前看过一本linux教程, 里面有一部分是关于vim的. 对了, 还有, vim自带的有一本教程. emacs和vim自带的教程写的都很不错. 两个软件比较, 相对来讲我还是更喜欢vim一些. 我又把vim自带的教程读了一遍. 我之前肯定是读过的, 但不知为何文章后面那部分感觉很陌生.\n","date":"Jul 04","permalink":"https://o5o.me/post/justaoyu/book-vimtutor/","tags":[null],"title":"读Vim自带教程"},{"categories":[null],"contents":"很早以前开了一个微信公众号订阅号,打算用来记录自己的数学学习历程,没想到一直荒废到现在。昨天做数学建模作业,想到不如发到公众号里。但想要在公众号文章中插入数学公式的话,只能采用图片的形式,那我该怎么将LaTeX公式转为图片呢?这可犯了难。\n方案1(失败) 我首先想到的是Typora,这是一个Markdown编辑器,非常漂亮,所见即所得,支持插入数学公式。Typora可以把文档导出为多种格式,基础的支持PDF、html和图片,其他格式需要借助Pandoc来完成。我把文档导出为html格式,发现公式被转为了svg格式。但微信公众号不能使用svg格式的图片。\n方案2(失败) 我接着就想,想办法把svg格式图片转换成png格式吧,因为Typora的排版真的太好看了,不想放弃。找了一圈,最让我心动的是mathjax-node的拓展mathjax-node-svg2png和它所依赖的svg2png。不过我没时间去鼓捣它了,放弃。\n方案3(失败) WordPress官方的 JetPack 插件附带的 Beautiful Math with LaTeX 模块也是以图片显示公式的,公式也很清晰,但只支持行内公式。官方的说法是,\\begin{}\\end{}这样的环境不支持。只能放弃了。\n方案4(成功) 那么,只能用QuickLaTeX插件了,虽然它的公式很模糊(别急)。这是一个WP插件,也可以在WP站点上显示图片格式的LaTeX公式。我想,既然没有更好的办法,那就先凑合吧。 我用了一下,发现效果确实很糟糕。这是我这种稍微有一些DIY能力的人所不能容忍的。 我想,怎么提高图片清晰度呢。\n方案4-1(失败) 我首先想到的是,插件代码里有没有控制生成的图片清晰度的选项?我去看了看插件代码,发现公式是在远程服务器上生成图片后又缓存到本地的。失败。\n方案4-2(成功) 我又想,先用着吧。但就在这个时候,我注意到了一个惊人的事实:插件的后台选项里是可以调整字体大小的,数值越大,那么生成的图片越大。也就是说,虽然图片100%大小显示时很模糊 ,但我只要让它显示为50%的大小,那看起来不就更清晰了吗?!! 来试一下。我在浏览器里把用来测试的公式的图片的img标签的width属性调小,把height属性删除(删掉后,只要width调整,height会跟着等比例变化,所以这个属性是多余的),可行。\n修改插件 那开始着手修改插件代码吧。 先把插件停用,然后打开Plugin Editor,我发现,我们要修改的代码都在 wp-quicklatex/wp-quicklatex.php 里面。 width属性的属性值是由$image_width变量来控制的,height的属性值是由$image_height变量来控制的。不过后者就不用在意了,直接删掉就是了。这几句模板代码大概在1580行左右,有好几句,因为公式有好几种,图片有不同的img标签。修改后就像这样:\n$out_str .= \u0026#34;![](\\\u0026#34;$image_url\\\u0026#34;\u0026#34;.\u0026#34;)\u0026#34;; 我顺便把title属性和alt属性也给删了,因为会显示烦人的“公式由quicklatex生成”(原话是英语,我记不清了,因为没备份)。 接下来我们要修改的是 $image_width 变量的赋值。我发现,赋值是有两种方式。如果缓存的有图片属性值,那就用缓存里面的;如果图片还没缓存,那就从远程服务器获取图片和图片信息,得到图片的宽度。控制这两者的代码一个是在1440行左右,一个是在1500行左右。前者我们不需要修改,我们把后者修改为:\n$image_width = $regs[4]; 改为: $image_width = $regs[4]/2; 来看看是否有效果。保存代码,启用插件。成功。 但是生成的公式有一点错位,我看了看代码,发现图片的行高line-height属性是与$image_height值有关的;图片的垂直偏移量vertical-align是和$image_align值有关的。那也很简单,把这几个变量值都同比例缩小就好了。另外,我觉得在保证清晰度的同时(我的LaTeX字体大小为50px),1/2还是有些大,调成1/3更合适。代码如下:\n$status = $regs[1]; $image_url = $regs[2]; $image_align = $regs[3]/3; $image_width = $regs[4]/3; $image_height = $regs[5]/3; $error_msg = $regs[6]; 保存代码;启用插件。完美!!效果: 复制,然后粘贴到微信公众号编辑器。完美。\n我想说 很早以前,我就在想办法解决公众号里插入数学公式的问题。发现一个叫“数海拾贝”的公众号的公式很清晰(现在已经停止更新了)。还见到一个老哥,也有和我同样的需求,非常锲而不舍?,先是在一篇文章里提问,而后又到知乎提问。不知道现在解决没有。如果还没,我想,这篇文章应该能帮到他吧。?? 像我这样的代码水平,也只能做一些这样小修小补的工作了。不知道未来的什么时候,我会由“追求完美”完全转变为“能用就行”。我还是继续钻研数学更好一些。 我的微信公众号:我是小白那些年(greenhandme),想看公式效果可以去瞧一瞧。我觉得还是在WordPress写文章更舒服。\n","date":"Jun 08","permalink":"https://o5o.me/post/wp/quicklatex-latex-wechat-png/","tags":[null],"title":"使用QuickLaTeX生成适于在微信公众号中使用的高清LaTeX数学公式图片"},{"categories":[null],"contents":"这是我的数学模型作业。数学模型(第五版)7.2节的复习题第2题(p246)。端午节假期计划的是学《常微分方程》和《概率论》,但今天我的大脑特别排斥《常微分方程》,索性做数学建模作业吧,反正不能闲着,反正早晚都得做。\n题目 以选择旅游地为目标的层次结构图如右图,景色、费用等5个准则构成准则层,P_1,P_2,P_33个旅游地构成方案层[1]. 旅游地选择层次结构图 已知准则对目标的成对比较阵 A=\\begin{bmatrix} 1 \u0026amp; 1/2 \u0026amp; 4 \u0026amp; 3 \u0026amp; 3\\ 2 \u0026amp; 1 \u0026amp; 7 \u0026amp; 5 \u0026amp; 5\\ 1/4 \u0026amp; 1/7 \u0026amp; 1 \u0026amp; 1/2 \u0026amp; 1/3\\ 1/3 \u0026amp; 1/5 \u0026amp; 2 \u0026amp; 1 \u0026amp; 1\\ 1/3 \u0026amp; 1/5 \u0026amp; 3 \u0026amp; 1 \u0026amp; 1 \\end{bmatrix} 及方案对5个准则的成对比较阵 B_1=\\begin{bmatrix} 1 \u0026amp; 2 \u0026amp; 5\\ 1/2 \u0026amp; 1 \u0026amp; 2\\ 1/5 \u0026amp; 1/2 \u0026amp;1 \\end{bmatrix}, B_2=\\begin{bmatrix} 1 \u0026amp; 1/3 \u0026amp; 1/8\\ 3 \u0026amp; 1 \u0026amp; 1/3\\ 8 \u0026amp; 3 \u0026amp; 1 \\end{bmatrix}, B_3=\\begin{bmatrix} 1 \u0026amp; 1 \u0026amp; 3\\ 1 \u0026amp; 1 \u0026amp; 3\\ 1/3 \u0026amp; 1/3 \u0026amp;1 \\end{bmatrix}, B_4=\\begin{bmatrix} 1 \u0026amp; 3 \u0026amp; 4\\ 1/3 \u0026amp; 1 \u0026amp; 1\\ 1/4 \u0026amp; 1 \u0026amp; 1 \\end{bmatrix}, B_5=\\begin{bmatrix} 1 \u0026amp; 1 \u0026amp; 1/4\\ 1 \u0026amp; 1 \u0026amp; 1/4\\ 4 \u0026amp; 4 \u0026amp; 1 \\end{bmatrix} (1)计算各个成对比较阵的特征向量, 作一致性检验, 确定权向量. (2)计算方案对目标的综合权重, 确定用层次分析法选择的旅游地.\n特征向量 不妨先来算一算以上各个矩阵的秩, 看看各个成对比较矩阵是不是一致阵[2]. 我们使用 Matlab 的rank 函数计算得到以上各个矩阵的秩为:\nA B1 B2 B3 B4 B5 5 3 3 1 3 1 可以看到 B3, B5 的秩为1, 说明这两个矩阵为一致阵. 那么这两个矩阵的特征向量就可以取为 \\overrightarrow{\\omega}=\\left ( \\omega_1,\\omega_2,\\omega_3 \\right )^\\mathrm{T} 其中\\omega_i 为各个方案在准则”居住(B3)”和”旅途(B5)”两方面的重要性(权重)之比. 不妨假定 \\omega_1,\\omega_2,\\omega_3已经归一化,即满足 \\sum_{i=1}^3\\omega_i=1. 回到本题. 对于以上矩阵, 我们用 Matlab 计算得到它们各自的最大特征根及其对应的特征向量为 \\lambda_A=5.0721,\\overrightarrow{\\omega_A}=\\left ( 0.2636,0.4758, 0.0538,0.0981,0.1087 \\right )^\\mathrm{T}\\ \\lambda_{B1}=3.0055,\\overrightarrow{\\omega_{B1}}=\\left ( 0.5954,0.2764,0.1283\\right )^\\mathrm{T}\\ \\lambda_{B2}=3.0015,\\overrightarrow{\\omega_{B2}}=\\left ( 0.0819,0.2363,0.6817\\right )^\\mathrm{T}\\ \\lambda_{B3}=3.0000,\\overrightarrow{\\omega_{B3}}=\\left ( 0.4286,0.4286,0.1429\\right )^\\mathrm{T}\\ \\lambda_{B4}=3.0092,\\overrightarrow{\\omega_{B4}}=\\left ( 0.6337,0.1919,0.1744\\right )^\\mathrm{T}\\ \\lambda_{B5}=3.0000,\\overrightarrow{\\omega_{B5}}=\\left ( 0.1667,0.1667,0.6667\\right )^\\mathrm{T}\\ 一致性检验 Saaty将CI定义为一致性指标 CI=\\frac{\\lambda-n}{n-1} 其中, \\lambda为矩阵的最大特征根, n为矩阵的阶数. 当CI=0时, 矩阵为一致阵, CI越大, 矩阵越不一致. Saaty又引入随机一致性指标RI, 并给出了他计算得到的随机一致性指标RI的数值:\nn 3 4 5 6 7 8 9 10 RI 0.58 0.90 1.12 1.24 1.32 1.41 1.45 1.49 当CR(一致性比率)满足 CR=\\frac{CI}{RI}\u0026lt;0.1 时认为A的不一致程度在容许范围之内. 上式中的0.1是可以调整的, 对于重要决策问题应当适当减小. 上述过程称为一致性检验. 若检验通过, 则可以用矩阵的特征向量作为权向量, 若检验不通过, 需要对矩阵作修正, 或者重新做成对比较. 经计算, 以上各矩阵的CI值如下表 A B1 B2 B3 B4 B5 0.0180 0.0028 7.7081e-04 -1.1102e-15 0.0046 -4.4409e-16 以上各矩阵的CR值如下表 A B1 B2 B3 B4 B5 0.0161 0.0048 0.0013 -1.9142e-15 0.0079 -7.6567e-16 可以看到, 以上各矩阵的CR均小于0.1, 一致性检验通过. 故它们的特征向量(已归一化)\\overrightarrow{\\omega_{A}},\\overrightarrow{\\omega_{B1}},\\overrightarrow{\\omega_{B2}},\\overrightarrow{\\omega_{B3}},\\overrightarrow{\\omega_{B4}},\\overrightarrow{\\omega_{B5}}均可作为权向量. 其中最引人注目的是矩阵B3和B5, 因为它们是一致矩阵, 所以计算得到的值为0(Matlab中不为0是因为存在误差). 综合权重 令W=[\\overrightarrow{\\omega_{B1}},\\overrightarrow{\\omega_{B2}},\\overrightarrow{\\omega_{B3}},\\overrightarrow{\\omega_{B4}},\\overrightarrow{\\omega_{B5}}], 那么 W=\\begin{bmatrix} 0.5954\u0026amp;0.0819\u0026amp;0.4286\u0026amp;0.6337\u0026amp;0.1667\\ 0.2764\u0026amp;0.2363\u0026amp;0.4286\u0026amp;0.1919\u0026amp;0.1667\\ 0.1283\u0026amp;0.6817\u0026amp;0.1429\u0026amp;0.1744\u0026amp;0.6667 \\end{bmatrix} 其中, 第i行的5个数值分别是方案P_i对4项准则的权重, 将它们与准则层对目标层的权重\\overrightarrow{\\omega_A}对应地相乘再求和, 就得到方案P_i对目标的权重. 从而, 第3层(方案层)对第1层(目标层)的综合权重\\omega可表示为 \\omega=W\\overrightarrow{\\omega_A} 我们使用Matlab计算得到 \\omega=\\begin{bmatrix} 0.2993\\ 0.2453\\ 0.4554 \\end{bmatrix} 故, 使用层次分析法, 3种方案的优劣顺序为P_3,P_1,P_2. 故我们应该选择方案P_3.\n参考文献 [1]姜启源,谢金星,叶俊.数学模型.北京:高等教育出版社,2018.\n[2] 胡端平,唐超.一致矩阵的特征性质[J].武汉工程大学学报,2009,31(05):93-94.\n代码 A=[1 1/2 4 3 3; 2 1 7 5 5; 1/4 1/7 1 1/2 1/3; 1/3 1/5 2 1 1; 1/3 1/5 3 1 1]; B1=[1 2 5; 1/2 1 2; 1/5 1/2 1]; B2=[1 1/3 1/8; 3 1 1/3; 8 3 1]; B3=[1 1 3; 1 1 3; 1/3 1/3 1]; B4=[1 3 4; 1/3 1 1; 1/4 1 1]; B5=[1 1 1/4; 1 1 1/4; 4 4 1]; % 计算以上各矩阵的秩 rank(A);rank(B1);rank(B2);rank(B3);rank(B4);rank(B5); % 计算以上各矩阵的特征值和右特征向量,找出它们的最大特征值,以及与之对应的右特征向量 % [V,D] = eig(A) 返回特征值的对角矩阵 D 和矩阵 V,其列是对应的右特征向量,使得 A*V = V*D。 [VA,DA]=eig(A); [MDA,indexA]=max(diag(DA)); MVA=VA(:,indexA); MVA=MVA./sum(MVA);% 归一化 MDA,MVA\u0026#39; %打印最大特征值和对应的特征向量(已归一化) [VB1,DB1]=eig(B1); [MDB1,indexB1]=max(diag(DB1)); MVB1=VB1(:,indexB1); MVB1=MVB1./sum(MVB1);% 归一化 MDB1,MVB1\u0026#39; %打印最大特征值和对应的特征向量(已归一化) [VB2,DB2]=eig(B2); [MDB2,indexB2]=max(diag(DB2)); MVB2=VB2(:,indexB2); MVB2=MVB2./sum(MVB2);% 归一化 MDB2,MVB2\u0026#39; %打印最大特征值和对应的特征向量(已归一化) [VB3,DB3]=eig(B3); [MDB3,indexB3]=max(diag(DB3)); MVB3=VB3(:,indexB3); MVB3=MVB3./sum(MVB3);% 归一化 MDB3,MVB3\u0026#39; %打印最大特征值和对应的特征向量(已归一化) [VB4,DB4]=eig(B4); [MDB4,indexB4]=max(diag(DB4)); MVB4=VB4(:,indexB4); MVB4=MVB4./sum(MVB4);% 归一化 MDB4,MVB4\u0026#39; %打印最大特征值和对应的特征向量(已归一化) [VB5,DB5]=eig(B5); [MDB5,indexB5]=max(diag(DB5)); MVB5=VB5(:,indexB5); MVB5=MVB5./sum(MVB5);% 归一化 MDB5,MVB5\u0026#39; %打印最大特征值和对应的特征向量(已归一化) % 计算各矩阵的CI,计算CR并与0.1比较 CIA=(MDA-size(A,1))/(size(A,1)-1) CRA=CIA/1.12 %其中1.12为RI值 CIB1=(MDB1-size(B1,1))/(size(B1,1)-1) CRB1=CIB1/0.58 CIB2=(MDB2-size(B2,1))/(size(B2,1)-1) CRB2=CIB2/0.58 CIB3=(MDB3-size(B3,1))/(size(B3,1)-1) CRB3=CIB3/0.58 CIB4=(MDB4-size(B4,1))/(size(B4,1)-1) CRB4=CIB4/0.58 CIB5=(MDB5-size(B5,1))/(size(B5,1)-1) CRB5=CIB5/0.58 % 计算综合权重 SW=[MVB1,MVB2,MVB3,MVB4,MVB5]*MVA 用到的工具 [0]Matlab\n[1] Typora\n[2] LaTeX在线公式编辑\n[3] Visual Studio Code\n[4] KaTeX(及WP同名插件)\n[5] Word\n我想说 没有严格按我们平时用的模板的格式来写。 WordPress 插入公式好麻烦。 代码不够优雅。但我越来越喜欢Matlab了。\n","date":"Jun 07","permalink":"https://o5o.me/post/wp/mm-ahp-travel/","tags":[null],"title":"使用层次分析法选择旅游地"},{"categories":[null],"contents":"2018年6月7日,微信公众号:我是小白那些年。 第一次开公众号,不知道写什么,正巧今天解锁了买来收藏的美版Lumia830,运营商是 AT\u0026amp;T,就谈一谈我申请解锁码的过程吧。这个公众号的初心是记录自己由小白的成长历程,“竹心空,空以体道,君子见其心,则思应用虚受者”,期望我能永远对未知世界充满兴趣。 解锁的过程很简单,容我细细讲来。 打开网页 https://www.att.com/deviceunlock/ ,选择 Unlock a device; 在弹出的新页面中会出现一个选项”Are you an AT\u0026amp;T wireless customer?”,实事求是,选择”No”就行,接着输入你的手机的 IMEI,如果你不知道自己手机的 IMEI,在手机拨号盘中输入“*#06#”就可以看到了; 在你输完 IMEI 后,页面会显示出你的手机的品牌和型号,如果确认无误,在通过机器人验证后,再选中页面下方的单选框后就可以单击”Next”进行下一步了(注意,如果在国内,因为网络问题,机器人验证选项可能不会显示出来); 接下来会让你填写一些信息,如姓名、手机号、邮件地址,照常填写就可以了,填写完毕后单击”Submit”提交。注意,邮件地址一定要检查无误;美国手机号是10位,如果自己没有可以填写朋友的。 过不了多久,你的邮箱就会收到一封邮件,告诉你”We’re working on unlocking your device.”,你要在24小时内访问邮件中的一个链接以确认你的请求,否则你的申请会被取消。你收到邮件后单击”Confirm”访问一下那个链接就可以了。 确认后,客服会在48小时内处理你的请求,如果成功了,你就会再收到一封邮件,提醒你”We approved your unlock request”,这封邮件里附带有解锁码和解锁指导,照做就可以了。 解锁的过程如下: 1. 把手机关机,然后把 AT\u0026amp;T 的手机卡从手机里拔出来(如果有的话); 2. 将其他运营商的手机卡放进去; 3. 开机,将会弹出一个解锁页面,输入你收到的解锁码,就解锁成功了。 解锁成功后就可以自由使用其他运行商的手机卡套餐了,好棒。\n","date":"Jun 04","permalink":"https://o5o.me/post/justaoyu/lumia830-att/","tags":[null],"title":"Lumia830 美版 AT\u0026T 申请解锁码解网络锁"},{"categories":[null],"contents":"很好看。很有创意。 之前我很难去描述这样一种感受:在做一件事之前,虽然我还没有做,但我就已经知道我可以把它做好。现在我知道,并不是只有我有这种感觉了:a leap of faith.\n","date":"Jun 02","permalink":"https://o5o.me/post/justaoyu/spider-man-into-the-spider-verse/","tags":[null],"title":"蜘蛛侠·平行宇宙观后感"},{"categories":[null],"contents":"这学期都过去一半了,为什么现在才想起来去看Abook呢?还不是因为它太难用了。也许高等教育出版社开发这款产品的初衷是好的,但是,用“粗制滥造”来形容它不为过吧?!\n官方介绍是这样的:\n欢迎大家访问abook.hep.com.cn,访问相关教材配套的数字课程!\nABook是高等教育出版社为广大高校教师和学生开发的适应教育发展和出版发展要求的“新形态”的高校教材。\nABook新形态教材在教材的编写理念、内容形式、出版机制等各方面都突破了传统高校教材的模式,充分融合教师课内课外教学、学生线上线下学习的数字网络化时代教与学的需求,使学生在学习精品纸质教材的同时,方便的通过计算机网络、手机、iPad等电子终端设备,获取名师微视频、拓展阅读材料、重点难点解析、习题练习讲解等精心筛选和紧密配套的数字化资源。学生还可以在网上进行自测、答疑等互动互助式的学习。\n我发现注册账号的网页有bug,图片滑动拼图验证时图片加载不出来或只加载一次,滑块滑不动,不能正常注册。我最后是在app里注册的。 视频不提供下载情有可原,但PDF文档都不能下载我就不能理解了。 下载出来的文件的文件名乱码。其实这是我的浏览器问题,Edge\nChromium内核 dev版乱码,IE正常,Chrome 正常。\n我要下载的配套文件属于《数学模型(第五版)》这本书。 我所用的浏览器是 Edge Chromium内核版。我在网页上观看视频时,在视频框的右下角有一个 Download 按钮,能点开但不能下载,会提示“ Couldn’t download – No permissions\n”,没有权限。直接访问视频连接,显示403 Forbidden. 但是,代码文件和excel文件是可以下载的,能够下载的文件的图标上,会显示一个箭头,点击箭头就能下载了。 文件的下载链接是这样的:\nhttp://abook.hep.com.cn/downLoadResouce.action?resourceInfoId=5000252368\u0026amp;resourceUrl=5000002867/resourses/2018/3/16/544dee21-42e4-4fe7-9bfd-bfcca464eb06.xlsx 如果我把链接后半部分的文件路径替换为视频的文件路径,能不能下载视频呢(我们在上面已经获取了视频的文件路径,只不过403无法访问)?来试一试:\nhttp://abook.hep.com.cn/downLoadResouce.action?resourceInfoId=5000252368\u0026amp;resourceUrl=5000002867/resourses/2018/3/22/1521701992053829766210263416081.mp4 下载的依然是那个xlsx文件。说明下载链接后半部分的字符是没有用的,删除掉试一试:\nhttp://abook.hep.com.cn/downLoadResouce.action?resourceInfoId=5000252368 依然能够成功下载到那个xlsx格式文件。说明起作用的只是那个resourceInfoId。那么,如果我们把这串ID替换为视频对应的ID,是不是就能下载视频了呢?我们该如何获取呢? 我这时先想到的是这个问题:这串数字是不是连续的?我尝试把最后一位的8改为7,链接是能够正常访问的,下载得到一个PDF文件。说明这串数字是有规律的。 然后我发现,在我把鼠标指针放到文件上的时候,浏览器窗口的左下角会显示这串字符(其实就是一个链接):\njavascript:showResource(5000252368,0); 括号里的正是我们寻求的resourceInfoId。 我惊奇的发现,把鼠标指针放到视频图标上的时候,也会显示同样的字符串,正是视频的resourceInfoId,视频的下载地址就这样暴露了哈哈。来试试看:\nhttp://abook.hep.com.cn/downLoadResouce.action?resourceInfoId=5000261344 我成功地得到了想要的视频。\n怎样批量下载文件呢? 如果是我的话,因为我只需要下载这么一本书的配套文件,没必要花时间去分析 resourceInfoId 的规律/摊手。真香。我会一个个把链接复制下来(javascript开头的那个),然后批量替换成可下载的链接,必要的时候可以用一点正则表达式。因为得到的链接是必须要登录账号才能下载的,不能用迅雷等软件,我只好一个个点开链接把文件下载下来。 是不是觉得不够优雅?我也这样觉得,用爬虫做这件事是很自然、优雅的,如果我以后也有同样的下载文件的需求,一劳永逸。但写爬虫也得花时间啊,我的知识储备也暂时不允许我流畅地完成一个爬虫。\n我全程都没有用到什么高深的知识,让那些点进来的,想看高深算法、解密骚操作的朋友们失望了/笑哭/笑哭。???\n","date":"Apr 29","permalink":"https://o5o.me/post/wp/hep-abook-down/","tags":[null],"title":"怎样下载Abook上的视频等文件"},{"categories":[null],"contents":"在2018年暑假的时候,写了一个程序,使得在使用Anki卡片记忆英文短文的时候,卡片上每隔一个单词隐藏一个单词(显示一个空白方格),通过对被隐去的单词的联想,记忆文章。\n我把我制作好的 Anki 卡片导出上传到了知乎、在博客里写了一篇记录我的思考过程的文章。很多人在知乎或我的博客里评论或私信我说他们自己制作的卡片不能工作。因为我已经没在用 Anki 了,所以我并没有去验证,并且认为是他们不会用才导致的。 在清明节的前一天,收到知乎发来的短信,有人向我付费咨询问题。我把知乎App安装到手机上,打开来看,那位网友也遇到了上述问题。我依然觉得是他不会用的原因,不过既然他付费咨询了,那就明明白白地向他证明,是他不会用,而不是我的代码有问题。我决定给他截张图,并委婉地“嘲讽”他一下。 我从我在知乎提供的下载链接把卡片下载下来、把Anki安装到电脑上,并把卡片导入到Anki中。我点开卡片,我用来做例子的新概念英语课文 A Puma at Large 显示依然是正常的。然后我就开始编辑给那位网友的回复:\n我现在已经没在用Anki了,看到你所说的问题后,我测试了一下,\n写到这里我想起,要制作一张新的卡片才有说服力。我随便写了一些句子,然后发现,确实不能正常挖空/笑哭。我本来想写“一切正常”的,硬生生的改成了“确实不能正常挖空”:\n我现在已经没在用Anki了,看到你所说的问题后,我测试了一下, 确实不能正常挖空/笑哭。\n我开始思考原因。 我把卡片导出为文本,发现了端倪。 我原本以为 Anki 是这样组织内容的:\nNice to meet you. Nice to meet you. Nice to meet you. Nice to meet you. Nice to meet you. Nice to meet you. 或者是这样的:\nNice to meet you. Nice to meet you. Nice to meet you. Nice to meet you. Nice to meet you. Nice to meet you. 其实它是这样的:\nNice to meet you. Nice to meet you. Nice to meet you. Nice to meet you. Nice to meet you. Nice to meet you. 也许是我写那篇文章的时候还是我以为的那种方式,后来改成了后面这种。 我重写了代码。没错,是重写,不是修改。 我在平时用 JavaScript 其实不多,之前写的代码,也只是把 JS 当成 C 来写。前几天我系统地学了一遍 Python,对我这次写的 JS 代码有了一些影响,至少,不再像是小学生写的代码了(虽然我还是用了很多 if 语句)。 本质上,这两次的代码是没什么区别的。 我重写后的代码,\n中、英文都能实现“隔字/单词挖空” 用到了一些正则表达式。 手机和电脑加载同一段代码,不再需要根据不同的设备选择加载不同的代码 在卡片内容里无论插入什么html标签都不会影响挖空 我在写代码时用 git 来进行代码管理 没有对卡片进行美化 感觉自己总算是长大了。 我把代码上传到了 GitHub,感兴趣可以看看我的 commits。仓库地址:https://github.com/ZuoAoyu/Anki-Blank-Space 感觉自己也是起名困难症患者,写 README 和各种描述、名字的时候真的要纠结死个人。 代码我就不再在文章里分析了,制作好的卡片可以去 github 下载,直接导入 Anki 就能用了。 ","date":"Apr 07","permalink":"https://o5o.me/post/wp/javascript-anki-blank-space-sequel/","tags":[null],"title":"续·JavaScript实现文章隔字挖空及与Anki结合使用"},{"categories":[null],"contents":"这本书买了好久了。 想读些书,但小说完全读不下去,感觉是在浪费时间,没读一会儿就感觉很焦虑。 我是用 Acrobat 读的这本书,读的时候,我一边读,一边写Comment(应该翻译成“评论”?“笔记”?我用的 Acrobat 是英文版),读完也不至于空有感慨,什么重点都说不上来。 很早之前我就想,把 SafariBooksOnline 上面的书抓下来打包成 epub 文档。我可以手动完成,因为提供有现成的API,但怎么把这个过程自动化,我做不到啊。。。直到 SafariBooksOnline 改名字,我还是做不到。 读完这本书,我尝试了一下,还是没做到。理论和现实是有差距的。 我应该也成功登录了上去,但访问 api 的时候依然提示:\n{\u0026#34;detail\u0026#34;:\u0026#34;Authentication credentials were not provided.\u0026#34;} 不知道是怎么着被发现了。 读这本书的收获还是蛮大的,数据采集的方方面面都写到了,虽然不深入,但让我有了方向。 下面是书里一些很有趣的句子的摘录:\nBeautifulSoup 库的名字取自刘易斯·卡罗尔在《爱丽丝梦游仙境》里的同名诗歌。\n当米开朗基罗被问及如何完成《大卫》这样匠心独具的雕刻作品时,他有一段著名的回答:“很简单,你只要用锤子把石头上不像大卫的地方敲掉就行了。”\n计算机科学里曾经有个笑话:“如果你有一个问题打算用正则表达式(regular expression)来解决,那么就是两个问题了。”\n我们即将用来做数据归纳的文字样本源自美国第九任总统威廉·亨利·哈里森的就职演说。哈里森的总统生涯创下美国总统任职历史的两个记录:一个是最长的就职演说,另一个是最短的任职时间—32天。\n正如一句谚语所说:“如果你喜欢某个东西,就放开手。”\n在使用自动化技术采集互联网数据时,其实很少遇到完全无法解决的问题。记住一点就行:互联网其实就是一个用户界面不太友好的超API。\n","date":"Mar 20","permalink":"https://o5o.me/post/justaoyu/book-web-scraping-with-python/","tags":[null],"title":"读Python网络数据采集"},{"categories":[null],"contents":"今天下午在图书馆转悠,想读些书。 我先是找了找有关python爬虫的,图书馆没有,但我还是去三楼西转了转,没发现什么能让我提起兴趣的好看的书;我接着去了三楼东,想找本小说看看,突然想到阿加莎克里斯蒂的悬疑小说,太厚了,找到、拿起又放下了,又想起三体,发现书架上没有,不得不吐槽图书馆的微信公众号里的图书检索,做的很差劲;然后我想还是去六楼东吧,里面的书应该不会让我感觉不耐烦,一眼相中这本《疯狂科学》。 这是一本很有想象力的书,我在回家的时候一定得给我弟弟买一本。 读到《用饼干发射火箭》那一节,我想起了在小学四五年级时小伙伴们用烟盒里的锡纸、乒乓球碎屑、爆竹里的火药制作的火箭,我在现场观摩了整个发射过程。记忆最深的是火箭发射后的浓烟和气味,很难闻、记忆犹新。火箭还是很成功的,成功飞了出去,大概有几米吧。如果不是读到这里,这段记忆不知还要被埋藏多久。 我之前也很好奇铅笔是怎么制作的,这本书里有这个实验。 这本书的第4章标题为“玩火”,我想起了小时候大人警告小孩子的话:玩火光尿床。小时候的某段时间我是深信不疑的。大人到底欺骗过我们多少次。 这本书里有很多很大幅的插图,读起来很过瘾。 这本书的味道有些重,读到后面我已经适应、闻不到了。 我是翻完的,不是读完的。\n","date":"Mar 17","permalink":"https://o5o.me/post/justaoyu/book-mad-science/","tags":[null],"title":"读 疯狂科学"},{"categories":[null],"contents":"前天早上,我安装了这样一个游戏,“Words Story”,游戏规则是,从所给的字母中选出一些字母,组合成正确的单词,获得金币,帮助监狱里的火柴人逃出监狱。游戏背景好像就是电影《肖申克的救赎》。\n游戏做的很棒,但要将零散的字母组合成单词真的是难为胖虎我了。就在我下载这个游戏的前一天的晚上,我刚刚读完《The Linux Command Line(中英对照)》,恰巧里面“正则表达式”那一章有提到 Linux 系统都自带有一个词库,可以通过正则表达式匹配单词,便产生了写一个能让我更舒服的玩游戏的程序的想法。 我的思考过程是这样的: 我的设想(最初的)是,写一个 Shell 脚本,由给定字母生成所有可能的指定字数的字母组合,然后与词典对照,输出所有正确的单词。程序可以像这样运行:\n[root@hostname ~]# genwords 3 e t s q l g o b 其中,3 是最终要生成的单词包含的字母数,后面的是给定的字母。 那便开始吧。 要想直接使用脚本文件名运行程序,需要把脚本所在文件夹添加到 PATH 变量中。这里我把脚本放在 ~/bin 文件夹中,这个文件夹路径已经在 PATH 变量中了。如果家目录里没有 bin 文件夹,可以自己新建一个。可以这样将一个路径追加到 PATH 变量值末尾(例如~/bin):\nPATH=$PATH:~/bin 我接着新建一个空白文件,并给它赋予可执行权限:\n\u0026gt; genwords; chmod 755 genwords; ls -l nice 注意这串命令前的大于号,这里是重定向符,将标准输出重定向到文件 genwords 中,但标准输入为空,所以没有内容可写入到文件中,所以文件为空,这样我们便生成了一个空文件。 Shell 脚本的第一句都是:\n#!/bin/bash 可以在后面加上 -x 选项,来追踪脚本的运行,就像这样:\n#!/bin/bash -x 我要从命令行参数中获得“要生成的单词的字数”和“所有备选字母”,我使用了下面两句代码:\nnumletters=$1 #要生成的单词所包含的字母个数 numaltletter=$(($#-1)) #所有给定的字母的个数 $1 是在位置1处的变量的值,也就是命令中的那个数字;$# 是所有命令行参数的个数,减去1,就是备选字母的个数。 然后,我把备选字母储存到一个数组中,我使用了如下代码:\nfor ((i=0; i 运行一次 shift 命令,就会使所有命令行参数向左移动一位,这让我想到了高中生物里学过的蛋白质的合成。 我用 echo ${someletters[i]} 这句代码测试代码有没有正常工作。在程序还小的时候,多测试,很容易发现和定位问题,也能更快的解决问题。测试完成后我把这句代码注释掉了。 我是先在 Visual Studio Code 里把代码写好,再粘贴到远程服务器的 vim 编辑器里。在 vim 的正常模式里,按 gg 再按 dG,可以很快的清除代码。gg 是跳转到代码第一行,dG 是删除当前行到文件末尾的内容。 然后就是,由备选字母生成所有的指定字数的字母组合,再使用 grep 程序与词库匹配单词,输出所有拼写正确的单词。但是我发现,适用于所有字数单词的代码。。。我不会写/汗,绞尽脑汁也没想到。我真该学学算法了。然后我就想着先写一个特殊情况下的,说不定就有灵感了呢,于是我就写了一段只能生成含有3个字母的字母组合的代码,用到了三层嵌套的 for 循环。\n#将字母组合成指定位数,只能生成3位的字母组合 for ((i=0; i 我发现代码运行起来很慢,毕竟,生成所有字母可能的组合再从中挑出拼写正确的单词真的是一种很蠢的行为。这才是3个字母的组合了,要是更多位数的单词呢,只会更慢了。 那本字典(文件名words),我在我安装的 ubuntu 的 /usr/share/dict 目录里竟然没找到,我在腾讯云开发者平台的 Cloud Studio 的系统相应路径也没找到,难道是阉割了?我从网上找了一份,但并不好用。这个字典是用来进行拼写检查的,其中有很多不是单词。在筛选出来的单词里,有很多单词含有重复的字母,而我玩的这个游戏里,单词都是由互不相同的字母组成的。虽然存在这样的问题,但我想着,给单词排一下序会不会好一点?且看下面的代码。字典链接:https://svnweb.freebsd.org/base/head/share/dict/web2?view=log\n#将字母组合成指定位数,只能生成3位的字母组合 for ((i=0; i\u0026gt; ~/genwords.txt done done done cat genwords.txt | sort -u rm -f genwords.txt sort 只能接受文件,所以我就先把程序的输出结果保存到一个临时文件 genwords.txt 中,再用管道传递给 sort,最后删除临时文件。 排序之后效果也并不好,单词太多,而且其中有很多含有相同字母的单词,不能用来玩(you)儿(xi)游(zuo)戏(bi)。我又想着能不能把那些含有重复的字母的单词剔除出去,但是我写的条件判断总是出错/无奈。 在晚上睡觉的时候,灵光一闪,我真的是舍近求远,为什么不直接用正则表达式从字典里匹配单词呢/吐血 在 Visual Studio Code 中,注释掉多行代码,要先用鼠标选中多行,再按 ctrl+k ctrl+c 。 把上面那段循环注释掉,用正则表达式替代。然而。。。我写的正则表达式总是出错/无奈\necho ${someletters[*]} grep -Ei ^[\u0026#34;${someletters[*]}\u0026#34;]{$numletters}$ ~/words \u0026gt;\u0026gt; ~/genwords.txt cat genwords.txt | sort -u rm -f genwords.txt 我用 ${someletters[*]} 获取数组内的所有元素。程序曾经的错误提示之一是: grep: Unmatched [ or [^ ,给 grep 加上一个 E 选项就解决了,表示支持扩展的正则表达式。给脚本第一行后面加上 -x,追踪脚本的运行,运行结果:\n+ echo a n d a n d + grep -Ei \u0026#39;^[a n d]{3}$\u0026#39; /root/words + sort -u + cat genwords.txt cat: genwords.txt: No such file or directory + rm -f genwords.txt 我以为是因为 ${someletters[*]} 生成的字符之间有空格,所以导致正则表达式不能正确运行(其实正则表达式是正常的),所以我又加上了一段代码来去除空格:\n#去除${someletters[*]}元素之间的空格 wordsletter=${someletters[*]} # shell 将命令输出结果保存为变量的值 wordsletter=$(echo $wordsletter | sed \u0026#39;s/[[:space:]]//g\u0026#39;) echo $wordsletter # ${someletters[*]}生成的字符之间是有空格的 # [root@iZuf6fxweg30jhggd2hv4tZ bin]# grep -Ei ^[a n d]{3}$ ~/words # grep: Unmatched [ or [^ grep -Ei ^[\u0026#34;${someletters[*]}\u0026#34;]{$numletters}$ ~/words \u0026gt;\u0026gt; ~/genwords.txt cat genwords.txt | sort -u rm -f genwords.txt 重新运行程序,但依然是相同的错误结果。我把后面那段把结果输出到临时文件再排序再删除临时文件的代码删除后正常了。我也没深究出错的原因是什么,因为这段代码是多余的。 现在程序可以正常工作了,运行一下试一试:\n[root@iZuf6fxweg30jhggd2hv4tZ bin]# genwords 3 a n d Ada add Ana ana and Ann ann dad Dan dan naa Nan nan 结果中含有重复字母的单词太多了,还是不能实际帮助我玩游戏。我第一时间想到的解决方法现在已经不可知了,因为那个版本的代码我没有保存。然后我想到的方法是找一个词汇量小一点的字典。 我先是找到了一个包含2000个单词的表格,但很多常用的单词都没有,丢弃。然后我又找到了一个包含 103976个单词的字典,格式是 csv 和 sql,我用下面这个命令把单词那一列保存到 enwordsok 文件中。\nawk -F, \u0026#39;{print $1}\u0026#39; enwords.csv \u0026gt; enwordsok 但效果还是不好,这个词库还是太大,含有重复字母的单词还是占了很大一部分,我还是得想想把这些单词去掉的方法。我还是选择了正则表达式。 然而我还是遇到了和前面遇到的一样的问题,正则表达式出错,这次提示: grep: Invalid back reference。原因是我又忘了给 grep 命令加上 -E 选项。\n#去除${someletters[*]}元素之间的空格 wordsletter=${someletters[*]} # shell 将命令输出结果保存为变量的值 wordsletter=$(echo $wordsletter | sed \u0026#39;s/[[:space:]]//g\u0026#39;) # ${someletters[*]}生成的字符之间是有空格的 # [root@iZuf6fxweg30jhggd2hv4tZ bin]# grep -Ei ^[a n d]{3}$ ~/words # grep: Unmatched [ or [^ # grep 加E 扩展的正则表达式 # grep 加i 忽略大小写 #出现 grep: Invalid back reference 没有加E grep -E ^[$wordsletter]{$numletters}$ ~/words | grep -Ev \u0026#39;^.*(.).*\\1.*$\u0026#39; grep 的 -v 选项表示反选。运行结果:\n[root@iZuf6fxweg30jhggd2hv4tZ bin]# genwords 3 a n d and dan 太完美了。 最后的代码是:\n#!/bin/bash # 由给定字母生成指定字数单词 # 程序示例:genwords 3 e t s q l g o b numletters=$1 #要生成的单词所包含的字母个数 numaltletter=$(($#-1)) #所有给定的字母的个数 for ((i=0; i 只有短短的十几行,但还能再精简,比如,去掉那两句“去掉 ${someletters[*]} 列出的数组元素之间的空格”的代码:\n#!/bin/bash # 由给定字母生成指定字数单词 # 程序示例:genwords 3 e t s q l g o b numletters=$1 #要生成的单词所包含的字母个数 numaltletter=$(($#-1)) #所有给定的字母的个数 for ((i=0; i 代码还可以更简单,只要改一改命令形式:\n程序示例:genwords 3 etsqlgob 把备选字母之间的空格去掉,命令输入起来也更方便,我们也就不需要那段把备选字母保存到数组中的代码了,代码像这样:\n#!/bin/bash # 由给定字母生成指定字数单词 # 程序示例:genwords 3 etsqlgob numletters=$1 #要生成的单词所包含的字母个数 grep -E ^[\u0026#34;$2\u0026#34;]{$numletters}$ ~/words | grep -Ev \u0026#39;^.*(.).*\\1.*$\u0026#39; 那何不更简单一点呢:\n#!/bin/bash # 由给定字母生成指定字数单词 # 程序示例:genwords 3 etsqlgob grep -E ^[\u0026#34;$2\u0026#34;]{$1}$ ~/words | grep -Ev \u0026#39;^.*(.).*\\1.*$\u0026#39; 有效代码只需要一行。突然有点伤心,我用了三天的空闲时间写出来的代码原来。。。竟然用一句话就能概括/呜呜ㄒoㄒ\n这是我写的人生中第一个 Shell 脚本,收获挺多的。在写代码时的那些困惑还是因为我对 Shell、对正则表达式、对算法不了解。 我一个站在学科鄙视链顶端的数学专业的学生 其实还是很有成就感的。\n","date":"Feb 12","permalink":"https://o5o.me/post/wp/shell-and-regex-generate-special-words/","tags":[null],"title":"Shell脚本和正则表达式实现由给定字母生成指定字数单词"},{"categories":[null],"contents":"因为我的博客放在阿里云的轻量应用服务器上,而我自己也是不消停的主,偶尔需要远程连接服务器进行一些操作,但很多 Linux 命令我都已经开始生疏了,所以,再次学习是很有必要的。趁寒假,我把《The Linux Command Line》读了一遍。 我感觉,所有希望入门 Linux 的人,都应该首先阅读这本书。“深入浅出”用来形容这本书再恰当不过了。也许我的感觉会有一些偏差,因为我之前有一定的知识储备。 我读这本书,只是想系统的了解一下 Linux 命令行,知道自己可以用命令行完成什么样的操作、能做到怎样的程度,以后需要用某个命令时能快速找到详细选项、用法。 这应该是我第二次读这本书了,前一次没读完,也忘记读到了哪里,没读完的原因是当时自己对 Linux 的使用并没有什么现实的需求,越到后面感觉越枯燥,遂放弃。 我读的是《The Linux Command Line》的中英对照版,读起来很舒服,中文翻译很贴切,当然也有翻译得马(gou)马(pi)糊(bu)糊(tong)的地方,甚至还有错误,此时英语原文就派上了用场,把中文翻译对应的原文读一遍,作者想表达的意思呼之欲出,也大概可以知道译者为什么要这样翻译,如果还是不理解,先放一边也就是了。链接:https://billie66.github.io/TLCL/index.html 我是从前到后读的这本书,但很惭愧,我并没有把每一章都很认真的读完,第21、22章尤其粗略,而第23章“打印”,我是直接跳过了的。 在所有我尝试学习过的编程语言中,我了解的最多的,非 C语言 莫属,感谢曾经一次次放弃又一次次站起来学习 C语言 的自己,让我在学习其他知识时,不时会有一种心意相通的感觉。编程语言的基本要素是很相似的,有很多语言都借鉴了 C语言 的语法,我在学习 Shell 脚本时,可以与 C语言 的某些相对应的概念进行对比,分析二者的差异,更快的理解书中的内容。 作者在书中的一些观点,我是十分认同的,但原谅我不能列举一二。在读这本书的前期,为了防止自己看到那么多的命令和命令选项不耐烦,我用纸笔对书中涉及的命令做了笔记,到后面就没有做了,同时,因为我是用浏览器在网页上读的电子书,读到精彩处,也没有留下一些标记。 曾经,我也觉得那些 hacker 很酷,并梦想成为其中一员。然而,并没有。\n","date":"Feb 09","permalink":"https://o5o.me/post/justaoyu/the-linux-command-line/","tags":[null],"title":"读 The Linux Command Line"},{"categories":[null],"contents":"「Learn LaTeX in 30 minutes」文档翻译。\nIn this guide, we hope to give you your first introduction to LATEX. The guide does not require you to have any prior knowledge of LATEX, but by the time you are finished, you will have written your first LaTeX document, and hopefully will have a good knowledge of some of the basic functions provided by LATEX. 在这份指南中,我们期望带给你 LaTeX 的第一次介绍。这份指南不需要你之前拥有任何 LaTeX 相关的知识,但当你读完后,你会拥有你的第一份 LaTeX 文档,并且有很大可能获得许多有关 LaTeX 基本功能的知识。\nWhat is LATEX? 什么是 LATEX? LATEX (pronounced LAY-tek or LAH-tek) is a tool used to create professional-looking documents. It is based on the WYSIWYM (what you see is what you mean) idea, meaning you only have focus on the contents of your document and the computer will take care of the formatting. Instead of spacing out text on a page to control formatting, as with Microsoft Word or LibreOffice Writer, users can enter plain text and let LATEX take care of the rest. LaTeX 是一种用于创建拥有专业外观的文档的工具。它基于 WYSIWYM,意味着你只需要集中精力于内容,排版交给计算机。就像 Microsoft Word 或 LibreOffice Writer,用户能够输入纯文本并让 LaTeX 处理剩下的部分,而非通过给页面上的文本手动添加间距来进行排版。\nWhy learn LATEX? 为什么要学习 LaTeX? LATEX is used all over the world for scientific documents, books, as well as many other forms of publishing. Not only can it create beautifully typeset documents, but it allows users to very quickly tackle the more complicated parts of typesetting, such as inputting mathematics, creating tables of contents, referencing and creating bibliographies, and having a consistent layout across all sections. Due to the huge number of open source packages available (more on this later), the possibilities with LATEX are endless. These packages allow users to do even more with LATEX, such as add footnotes, draw schematics, create tables etc. LaTex 的使用遍及世界,被用于科学文档、书籍和许多其他形式的出版物。它不仅能够创建美观的印刷文档,而且允许用户非常便捷地处理排版中更加复杂地部分,例如输入数学公式、创建内容表格、引用和创建参考文献,并能使所有的部分都拥有一致的布局。由于有许多的开源包可以使用,LaTeX 的可能性是无止境的。这些包允许用户用 LaTeX 做得更多,例如添加脚注、画图表、创建表格等等。 One of the most important reasons people use LATEX is that it separates the content of the document from the style. This means that once you have written the content of your document, we can change its appearance with ease. Similarly, you can create one style of document which can be used to standardise the appearance of many different documents. This allows scientific journals to create templates for submissions. These templates have a pre-made layout meaning that only the content needs to be added. In fact there are hundreds of templates available for everything from CVs to slideshows. 人们使用 LaTeX 的一个重要原因是,它将文档的内容同文档的样式分离开了。这就意味着一旦你写好文档的内容,我们能轻易地改变它的外观。类似地,你可以创建一种文档样式用于将许多不同文档的外观规范化。这允许学术期刊为投稿创建拥有预先制作好的布局的模板,意味着,只需要作者再往里添加内容就可以了。事实上,从简历到幻灯片,有数百种模板可以使用。\nWriting your first piece of LATEX 写下你的 LaTeX 文档的第一部分 The first step is to create a new LATEX project. You can do this on your own computer by creating a new .tex file, or else you can start a new project in Overleaf. Let’s start with the simplest working example: 第一步是创建一个新 LaTeX 项目。你能够在你自己的电脑上通过创建一个 .tex 后缀的新文件做到这一步,也可以在 Overleaf 上开始一个新项目。让我们从最简单的作品示例开始:\n\\documentclass{article} \\begin{document} First document. This is a simple example, with no extra parameters or packages included. \\end{document} You can see that LATEX has already taken care of the first piece of formatting for you, by indenting the first line of the paragraph. Let’s have a close look at what each part of our code does. 你能够看到 LaTeX 已经帮你完成了初步排版,缩进了段落的第一行。让我们离近点看一看代码的每一部分都做了些什么。 The first line of code declares the type of document, known as the class. The class controls the overall appearance of the document. Different types of documents will require different classes i.e. a CV/resume will require a different class than a scientific paper. In this case, the class is article, the simplest and most common LATEX class. Other types of documents you may be working on may require different classes such as book or report. 代码的第一行表明了文档类型,也就是所谓的类。类控制着文档的全局外观。不同的文档类型需要不同的类,即一份简历与一份学术论文需要使用不同的类。在当前例子中,文档的类是 article,这个是最简单也是最常用的 LaTeX 类。你要是写其他各种类型文档,就需要各种不同的类,例如 book 或 report. After this, you write the content of our document, enclosed inside the \\begin{document} and \\end{document} tags. This is known as the body of the document. You can start writing here and make changes to the text if you wish. To see the result of these changes in the PDF you have to compile the document. To do this in Overleaf, simply hit Recompile. (You can also set your project to automatically recompile when you edit your files, by clicking on the small arrow next to the ‘Recompile button and set ‘Auto Compile to ‘On.) 在这之后,你写下了文档的内容,并将其放在 \\begin{document} 和 \\end{document} 标签里,这就是所谓的文档主体。只要你愿意,你可以在从这儿开始写作或改变文本以观察结果。为了从 PDF 文档中看到这些改变带来的结果,你需要编译这份文档。在\nOverleaf 中,你只需要点击 Recompile 就可以了。 If you are using a basic text editor such as gedit, emacs, vim, sublime, notepad etc., you will have to compile the document manually. To do this, simply run pdflatex in your computers terminal/command line. See here for more information on how to do this. 如果你正在使用一种基本的文本编辑器,例如 gedit, emacs, vim, sublime, notepad 等等,你需要去手动编译文档。为了完成这一过程,仅仅只需要在你的电脑的终端或命令行中使用 pdflatex 命令。 If you are using a dedicated LaTeX editor such as TeXmaker or TeXworks, simply hit the Recompile button. Consult the programs documentation if you are unsure of where this is. 如果你正在使用一种专业 LaTeX 编辑器,例如 TeXmaker 或 TeXworks,只需点击 Recompile 按钮。如果你不确定按钮在哪,查阅程序文档吧。 Now that you have learnt how to add content to our document, the next step is to give it a title. To do this, we must talk briefly about the preamble. 现在你已经学会了怎样去为文档添加内容,下一步就是给它添加一个标题。为了做到这一步,我们必须要简短地谈一谈 preamble 序文(翻译为“头文本”应该更合适)。\nThe preamble of a document 文档的 preamble In the previous example the text was entered after the \\begin{document} command. Everything in your .tex file before this point is called the preamble. In the preamble you define the type of document you are writing, the language you are writing in, the packages you would like to use (more on this later) and several other elements. For instance, a normal document preamble would look like this: 在先前的例子中,内容被放在 \\begin{document} 命令后。你的 .tex 文件中的所有在此之前的内容被称为 preamble。在 preamble 中,你定义了你正在写的文档的类型、你正在输入的语言、你想要使用的包(稍后会详细介绍)和其他几种元素。实际上,一个正常的文档序文通常看起来像这样:\n\\documentclass[12pt, letterpaper]{article} \\usepackage[utf8]{inputenc} Below a detailed description of each line: 下面是对每一行的详细描述:\n\\documentclass[12pt, letterpaper]{article}\nAs said before, this defines the type of document. Some additional parameters included in the square brackets brackets can be passed to the command. These parameters must be comma-separated. In the example, the extra parameters set the font size (12pt) and the paper size (letterpaper). Of course other font sizes (9pt, 11pt, 12pt) can be used, but if none is specified, the default size is 10pt. As for the paper size other possible values are a4paper and legalpaper; see the article about Page size and margins for more details about this. \\documentclass[12pt, letterpaper]{article} 正如之前所说,这一行定义文档类型。一些被放在方括号内的附加参数可以被传送给命令。这些参数必须以逗号分隔。在这个例子中,额外的参数设置了字体大小(12pt)和纸张大小 (letterpaper)。 当然,也可以设置为其他字体大小(9pt, 11pt, 12pt),但如果没有指定参数的话,默认的字体大小是 10pt。至于纸张大小,其他可能的值是 a4paper 和 legalpaper。 \\usepackage[utf8]{inputenc}\nThis is the encoding for the document. It can be omitted or changed to another encoding but utf-8 is recommended. Unless you specifically need another encoding, or if you are unsure about it, add this line to the preamble. \\usepackage[utf8]{inputenc} 这是文档的编码。它可以被省略或是改为其他的编码,但一般推荐使用 utf-8。除非你明确地需要其他编码,或你对此不确定,就把这行命令加到 preamble 里。\nAdding a title, author and date 添加标题、作者和日期 To add a title, author and date to our document, you must add three lines to the preamble (NOT the main body of the document). These lines are 为了给文档添加标题、作者和日期,你需要在序文里添加三行命令。这些代码分别是 \\title{First document}\nThis is the title. \\title{First document}\n这行代码用于添加标题 \\author{Hubert Farnsworth}\nHere you put the name of the Author(s) and, as a optional parameter, you can add the next command: \\author{Hubert Farnsworth}\n这行代码用于添加作者姓名,并且,作为可选参数,你可以添加这行代码: \\thanks{funded by the Overleaf team}\nThis can be added after the name of the autor, inside the braces of the title command. It will add a superscript and a footnote with the text inside the braces. Useful if you need to thank an institution in your article. \\thanks{funded by the Overleaf team}\n这行代码可以写在作者姓名之后,与之一起放在花括号里。这行代码会为花括号里的文本添加上标和脚注。如果你需要在文章中感谢某个机构,这条命令会十分有用。 \\date{February 2014}\nYou can enter the date manually or use the command \\today so the date will be updated automatically at the time you compile your document \\date{February 2014}\n你可以手动输入日期,也可使用 \\today 这一命令,这样一来,日期会被自动更新为你编译文档的时间。 With these lines added, your preamble should look something like this 添加上述命令后,你的 preamble 看起来应该像这样:\n\\documentclass[12pt, letterpaper, twoside]{article} \\usepackage[utf8]{inputenc} \\title{First document} \\author{Hubert Farnsworth \\thanks{funded by the Overleaf team}} \\date{February 2017} Now that you have given your document a title, author and date, you can print this information on the document with the \\maketitle command. This should be included in the body of the document at the place you want the title to be printed. 现在你已经为文档添加了标题、作者和日期,你可以使用 \\maketitle 命令,这一以来,这些信息就可以在生成的文档中显示出来了。你想让这些信息在哪里显示,就把它们放在 body(文档主体) 中对应的位置。\n\\begin{document} \\maketitle We have now added a title, author and date to our first \\LaTeX{} document! \\end{document} Adding comments 添加注释 As with any code you are writing, it can often be useful to include comments. Comments are pieces of text you can include in the document which will not be printed, and will not affect the document in any way. They are useful for organizing your work, taking notes, or commenting out lines/sections when debugging. To make a comment in LATEX, simply write a % symbol at the beginning of the line as shown below: 就像你正在写的任何代码一样,为代码添加注释通常是有用的。注释是你添加到文档中的文本的一部分,但不会被显示出来,也不会对文档产生任何影响。这些注释对你组织文章、做笔记或在调试时暂时注释掉某一行或某一部分很有用。在 LaTeX 中添加注释,只需在那一行的开头加一个 %,就像这样:\n\\begin{document} \\maketitle We have now added a title, author and date to our first \\LaTeX{} document! % This line here is a comment. It will not be printed in the document. \\end{document} Bold, italics and underlining 加粗、倾斜与添加下划线 We will now look at some simple text formatting commands. 现在我们来看一些简单的文本格式化命令。\nBold: Bold text in LaTeX is written with the \\textbf{…} command. Italics: Italicised text in LaTeX is written with the \\textit{…} command. Underline: Underlined text in LaTeX is written with the \\underline{…} command. 粗体:在 LaTeX 中用 \\textbf{…} 命令加粗文本。 斜体: 在 LaTeX 中用 \\textit{…} 命令倾斜文本。 下划线: 在 LaTeX 中用 \\underline{…} 命令为文本添加下划线。 An example of each of these in action is shown below: 下面是每一种命令的实际操作示例: Some of the \\textbf{greatest} discoveries in \\underline{science} were made by \\textbf{\\textit{accident}}. Another very useful command is the \\emph{…} command. What the \\emph command actually does with its argument depends on the context – inside normal text the emphasized text is italicized, but this behaviour is reversed if used inside an italicized text- see example below: 另一个非常有用的命令是 \\emph{…}. \\emph 命令会对它的参数产生何种效果取决于上下文——在正常文本里,强调文本被显示为斜体,但如果被放在倾斜文本中,强调文本的行为是相反的。看下面的例子:\nSome of the greatest \\emph{discoveries} in science were made by accident. \\textit{Some of the greatest \\emph{discoveries} in science were made by accident.} \\textbf{Some of the greatest \\emph{discoveries} in science were made by accident.} Moreover, some packages, e.g. Beamer, change the behaviour of \\emph command. 此外,一些包,例如 Beamer,改变了 \\emph 命令的行为。\nAdding images 添加图片 We will now look at how to add images to a LATEX document. On Overleaf, you will first have to upload the images. 我们现在来看如何给 LaTeX 文档添加图片。在 Overleaf 中,你首先需要上传图片。 Below is a example on how to include a picture. 下面是一个添加图片的例子:\n\\documentclass{article} \\usepackage{graphicx} \\graphicspath{ {images/} } \\begin{document} The universe is immense and it seems to be homogeneous, in a large scale, everywhere we look at. \\includegraphics{universe} There\u0026#39;s a picture of a galaxy above \\end{document} LATEX can not manage images by itself, so you will need to use a package. Packages can be used to change the default look of your LATEX document, or to allow more functionalities. In this case, you need to include an image in our document, so you should use the graphicx package. This package gives new commands, \\includegraphics{…} and \\graphicspath{…}. To use the graphicx package, include the following line in you preamble: \\usepackage{graphicx} LaTeX 自身无法管理图片,所以你需要使用包。包能够被用来更改 LaTeX 文档的默认外观,或添加更多功能。在这个例子里,你需要添加图片到文档中,所以你应该使用 graphicx 包。这个包包含一些新命令,\\includegraphics{…} 和 \\graphicspath{…}. 为了使用 graphicx 包,把如下命令添加到 preamble 中:\\usepackage{graphicx} The command \\graphicspath{ {images/} } tells LATEX that the images are kept in a folder named images under the current directory. 命令 \\graphicspath{ {images/} } 告诉 LaTeX,图片被保存在当前文件夹中的一个名为 images 的文件夹中。 The \\includegraphics{universe} command is the one that actually included the image in the document. Here universe is the name of the file containing the image without the extension, then universe.PNG becomes universe. The file name of the image should not contain white spaces nor multiple dots. 命令 \\includegraphics{universe} 真正用于将图片添加到文档中。这里,universe 是图片文件的名称,不带扩展名,那么,universe.PNG 就该写成 universe. 图片的名称不应该包含空格或多个句点. Note: The file extension is allowed to be included, but it’s a good idea to omit it. If the file extension is omitted it will prompt LaTeX to search for all the supported formats. It is also usually recommended to use lowercase letters for the file extension when uploading image files. For more details see the section about generating high resolution and low resolution images. 注意: 文件扩展名是允许被使用的,但更好的主意是忽略它。如果文件名被忽略,它会提示 LaTeX 去搜索所有支持的格式。在上传图片文件时,也推荐使用小写文件扩展名。\nCaptions, labels and references 题注、标签和引用 Images can be captioned, labelled and referenced by means of the figure environment as shown below: 可以通过 figure 环境的方式为图片添加题注、标签和引用,就像这样:\n\\begin{figure}[h] \\centering \\includegraphics[width=0.25\\textwidth]{mesh} \\caption{a nice plot} \\label{fig:mesh1} \\end{figure} As you can see in the figure \\ref{fig:mesh1}, the function grows near 0. Also, in the page \\pageref{fig:mesh1} is the same example. There are three important commands in the example: 在例子中有三条重要的命令:\n\\caption{a nice plot}: As you may expect this command sets the caption for the figure. If you create a list of figures this caption will be used there. You can place it above or below the figure. \\label{fig:mesh1}: If you need to refer the image within your document, set a label with this command. The label will number the image, and combined with the next command will allow you to reference it. \\ref{fig:mesh1}: This code will be substituted by the number corresponding to the referenced figure. \\caption{a nice plot}: 正如你所期待的,这条命令为图像添加题注。如果你创建了一个图像列表,这条题注会被用在那儿。把它放在图片上方和下方都可以。 \\label{fig:mesh1}: 如果你需要在文档内引用图片,用这条命令给图片设置标签。标签会为图片编号,结合下一条命令,你就可以引用图片了。 \\ref{fig:mesh1}: 这段代码会被图像对应的编号所替换。 When placing images in a LATEX document, we should always put them inside a figure environment or similar so that LATEX will position the image in a way that fits in with the rest of your text. 在 LaTeX 文档中插入图片时,我们应该总是把它们放在 figure 环境或其他相似环境中,这样,LaTeX 会以一种与其他文本相适应的方式定位图片。 Note: If you are using captions and references on your own computer, you will have to compile the document twice for the references to work. Overleaf will do this for you automatically.’ 注意: 如果你正在你自己的电脑上使用题注和引用,你需要编译文档两次,才能让引用生效。ShareLaTeX 会为你自动做这件事。 Creating lists in LATEX 用 LaTeX 创建列表 Lists are very simple to create in LATEX. You can create lists using different list environments. Environments are sections of our document that you want to present in a different way to the rest of the document. They start with a \\begin{…} command and end with an \\end{…} command. 用 LaTeX 创建列表是非常简单的。你可以使用不同的列表环境创建列表。环境是文档的一部分,使你可以以一种与其余文本不同的方式表现内容。环境以 \\begin{…} 命令开头,以 \\end{…} 命令结束。 There are two main different types of lists, ordered lists and unordered lists. Each will use a different environment. 有两种不同类型的列表,有序列表和无序列表。每一种都使用一种不同的环境。\nUnordered lists 无序列表 Unordered lists are produced by the itemize environment. Each entry must be preceded by the control sequence \\item as shown below. 无序列表由 itemize 环境生成。每一项都必须跟在控制序列 \\item 之后,就像下面这样:\n\\begin{itemize} \\item The individual entries are indicated with a black dot, a so-called bullet. \\item The text in the entries may be of any length. \\end{itemize} By default the individual entries are indicated with a black dot, so-called bullet. The text in the entries may be of any length. 默认情况下,单独一项用一个黑点表示,也可称作 bullet(着重号)。项中的文本可以是任意长度。\nOrdered lists 有序列表 Ordered list have the same syntax inside a different environment. We make ordered lists using the enumerate environment: 有序列表被放在一个不同的环境中,语法相同。我们使用 enumerate 环境创建有序列表:\n\\begin{enumerate} \\item This is the first entry in our list \\item The list numbers increase with each entry we add \\end{enumerate} As with unordered lists, each entry must be preceded by the control sequence \\item, which will automatically generate the number labelling the item. The enumerate labels consists of sequential numbers starting at one. 就像无序列表那样,每一个项都必须跟在控制序列 \\item 之后,这样就能自动生成数字标签。枚举标签由从1开始的连续数字组成。\nAdding math to LATEX 在 LaTeX 文档中添加数学公式 One of the main advantages of LATEX is the ease at which mathematical expressions can be written. LATEX allows two writing modes for mathematical expressions: the inline mode and the display mode. The first one is used to write formulas that are part of a text. The second one is used to write expressions that are not part of a text or paragraph, and are therefore put on separate lines. Let’s see an example of the inline mode: LaTeX 的主要优势之一就是可以很轻易地书写数学公式。LaTeX 有两种数学表达式书写模式:行内模式和单独显示模式。行内模式用于书写和其他文本分不开的公式。单独显示模式用于书写不是文本或段落的一部分,并因此被放在单独一行上的公式。来看一个行内模式的例子:\nIn physics, the mass-energy equivalence is stated by the equation $E=mc^2$, discovered in 1905 by Albert Einstein. To put your equations in inline mode use one of these delimiters: ( … ), $ … $ or \\begin{math} … \\end{math}. They all work and the choice is a matter of taste. 要以行内模式表示公式,需要用到这些定界符:( … ), $ … $ 或 \\begin{math} … \\end{math}. 它们都可以使用,选择使用哪种看个人喜好了。 The displayed mode has two versions: numbered and unnumbered. 单独显示模式有两种形式:未编号的和带编号的。\nThe mass-energy equivalence is described by the famous equation \\[E=mc^2\\] discovered in 1905 by Albert Einstein. In natural units ($c = 1$), the formula expresses the identity \\begin{equation} E=m \\end{equation} To print your equations in display mode use one of these delimiters: [ … ], \\begin{displaymath} … \\end{displaymath} or \\begin{equation} … \\end{equation}. $$ … $$ is discouraged as it can give inconsistent spacing, and may not work well with some math packages. 为了以单独显示模式显示你的等式,需要用到以下定界符:[ … ], $$ … $$ 或 \\begin{displaymath} … \\end{displaymath} 或 \\begin{equation} … \\end{equation} Important Note: equation* environment is provided by an external package, consult the amsmath article. 特别注意:equation* 环境由外部包提供,详情浏览 amsmath article. Many math mode commands require the amsmath package, so be sure to include it when writing math. An example is shown below of some basic math mode commands. 许多数学模式相关命令需要用到 amsmath 包,所以,在书写数学文章时一定要把它包含进来。下面是一些基本的数学模式命令的例子:\nSubscripts in math mode are written as $a_b$ and superscripts are written as $a^b$. These can be combined an nested to write expressions such as $$T^{i_1 i_2 \\dots i_p}_{j_1 j_2 \\dots j_q} = T(x^{i_1},\\dots,x^{i_p},e_{j_1},\\dots,e_{j_q})$$ We write integrals using $\\int$ and fractions using $\\frac{a}{b}$. Limits are placed on integrals using superscripts and subscripts: $$\\int_0^1 \\frac{1}{e^x} = \\frac{e-1}{e}$$ Lower case Greek letters are written as $\\omega$ $\\delta$ etc. while upper case Greek letters are written as $\\Omega$ $\\Delta$. Mathematical operators are prefixed with a backslash as $\\sin(\\beta)$, $\\cos(\\alpha)$, $\\log(x)$ etc. The possibilities with math in LATEX are endless and it is impossible to list them all here. Be sure to check out our other articles on 数学在 LaTeX 中拥有无限可能,不可能把它们全部列在这里。一定要去查看我们的其他文章。\nMathematical expressions Subscripts and superscripts Brackets and Parentheses Fractions and Binomials Aligning Equations Operators Spacing in math mode Integrals, sums and limits Display style in math mode List of Greek letters and math symbols Mathematical fonts Basic Formatting 基本排版 We will now look at how to write abstracts, as well as how to format a LATEX document into different chapters, sections and paragraphs. 我们现在来看如何去书写摘要,以及,怎样将一份 LaTeX 文档格式化为不同的章节、部分和段落。\nAbstracts 摘要 In scientific documents it’s a common practice to include a brief overview of the main subject of the paper. In LATEX there’s the abstract environment for this. The abstract environment will put the text in a special format at the top of your document. 在科学文献中,通常的做法是在文章中包含一个简短的综述以说明论文的主题。在 LaTeX 中有一个 abstract 环境可以做到。abstract 环境会将文本以一种特殊的格式放到文档前面。\n\\begin{document} \\begin{abstract} This is a simple paragraph at the beginning of the document. A brief introduction about the main subject. \\end{abstract} \\end{document} Paragraphs and newlines 段落和换行 \\begin{document} \\begin{abstract} This is a simple paragraph at the beginning of the document. A brief introduction about the main subject. \\end{abstract} Now that we have written our abstract, we can begin writing our first paragraph. This line will start a second Paragraph. \\end{document} When writing the contents of your document, if you need to start a new paragraph you must hit the “Enter” key twice (to insert a double blank line). Notice that LATEX automatically indents paragraphs. 书写文档内容时,如果你需要开始一个新的段落,你需要敲击“Enter”键两次(去插入一个双空行)。注意,LaTeX 会自动缩进段落。 To start a new line without actually starting a new paragraph insert a break line point, this can be done by \\ (a double backslash as in the example) or the \\newline command. 如果想要另起一行却不开始新段落,需要插入一个换行符,这一操作通过 \\ (双反斜杠,就像在例子中那样) 或 \\newline 命令完成。 Care should be taken that multiple \\ or \\newlines are not used to “simulate” paragraphs with larger spacing between them, as this can interfere with LATEX’s typesetting algorithms. The recommended method to do so is to keep using double blank lines to create new paragraphs without any , and then add \\usepackage{parskip} to the preamble. 应该小心,多个 反斜杠 \\ 或 \\newlines 不是用来“假装”扩大段落间距的。想要达到这样的目的,推荐的方法是,依然使用双空行去创建新段落,而不是使用 \\,然后添加命令 \\usepackage{parskip} 到 preamble 中。 You can find more information in the Paragraphs and new lines article. 你可以从 Paragraphs and new lines article 获取更多信息。\nChapters and Sections 章节和分节 Commands to organize a document vary depending on the document type, the simplest form of organization is the sectioning, available in all formats. 用来组织文档的命令根据文档类型的不同而有所不同,最简单的文章组织形式是分节,可以在所有格式中起作用。\n\\chapter{First Chapter} \\section{Introduction} This is the first section. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam lobortisfacilisis sem. Nullam nec mi et neque pharetra sollicitudin. Praesent imperdietmi nec ante. Donec ullamcorper, felis non sodales... \\section{Second Section} Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam lobortis facilisissem. Nullam nec mi et neque pharetra sollicitudin. Praesent imperdiet mi necante... \\subsection{First Subsection} Praesent imperdietmi nec ante. Donec ullamcorper, felis non sodales... \\section*{Unnumbered Section} Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam lobortis facilisissem The command \\section{} marks the beginning of a new section, inside the braces is set the title. Section numbering is automatic and can be disabled by including a * in the section command as \\section*{}. We can also have \\subsection{}s, and indeed \\subsubsection{}s. The basic levels of depth are listed below: 命令 \\section{} 标记了新节的开始,在花括号内设置标题名称。小节编号是自动生成的,并且可以通过在 section 命令中加 * 关闭,就像这样:\\section*{}. 我们还有 \\subsection{} 和 \\subsubsection{} 命令. 基本深度层次如下:\n-1 \\part{part} 0 \\chapter{chapter} 1 \\section{section} 2 \\subsection{subsection} 3 \\subsubsection{subsubsection} 4 \\paragraph{paragraph} 5 \\subparagraph{subparagraph} Note that \\part and \\chapter are only available in report and book document classes. 注意,\\part 和 \\chapter 只在 report 和 book 文档类型中生效。 For a more complete discussion about the document structure see the article about sections and chapters. 获得更完整的关于文档结构的讨论,请看 sections and chapters. Creating tables 创建表格 Creating a simple table in LATEX 用 LaTeX 创建简单表格 Below you can see the simplest working example of a table 下面你可以看到最简单的表格示例\n\\begin{center} \\begin{tabular}{ c c c } cell1 \u0026amp; cell2 \u0026amp; cell3 \\\\ cell4 \u0026amp; cell5 \u0026amp; cell6 \\\\ cell7 \u0026amp; cell8 \u0026amp; cell9 \\end{tabular} \\end{center} The tabular environment is the default LATEX method to create tables. You must specify a parameter to this environment, in this case {c c c}. This tells LATEX that there will be three columns and that the text inside each one of them must be centred. You can also use r to align the text to the right and l for left alignment. The alignment symbol \u0026amp; is used to specify the breaks in the table entries. There must always be one less alignment symbol in each line than the number of columns. To go to the next line of your table, we use the new line command \\. We wrap the entire table inside the center environment so that it will appear in the center of the page. tabular 环境是 LaTeX 创建表格的默认方法。你必须为该环境指定一个参数,就像这样:{c c c}. 该参数告诉 LaTeX 该表格有三列,而且,表格内的文本都是居中的。你也可以使用 r 让文本右对齐,用 l 让文本左对齐。对齐符号 \u0026amp; 被用来标记表格条目的中断。每一行的对齐标志的个数总是比列的个数少一个。为了换行,我们使用新行命令 \\。我们把整个放在 center 环境中,所以表格会被显示在页面中间。\nAdding borders 添加边框 The tabular environment is more flexible, you can put separator lines in between each column. tabular 环境非常灵活,你可以把分隔线放在两列之间。\n\\begin{center} \\begin{tabular}{ |c|c|c| } \\hline cell1 \u0026amp; cell2 \u0026amp; cell3 \\\\ cell4 \u0026amp; cell5 \u0026amp; cell6 \\\\ cell7 \u0026amp; cell8 \u0026amp; cell9 \\\\ \\hline \\end{tabular} \\end{center} You can add borders using the horizontal line command \\hline and the vertical line parameter |. 你可以使用水平线命令 \\hline 和垂直线参数 | 添加边框。\n{ |c|c|c| }: This declares that three columns, separated by a vertical line, are going to be used in the table. The | symbol specifies that these columns should be separated by a vertical line. \\hline: This will insert a horizontal line. We have included horizontal lines at the top and bottom of the table here. There is no restriction on the number of times you can use \\hline. { |c|c|c| }: 这表明表格有三列,被垂直线分开。| 符号表明这些列被垂直线分开。 \\hline: 这条命令会插入一个水平线。我们在表格的顶部和底部分别插入了一条水平线。\\hline 命令的使用次数没有限制。 Below you can see a second example. 下面你可以看到第二个示例。 \\begin{center} \\begin{tabular}{||c c c c||} \\hline Col1 \u0026amp; Col2 \u0026amp; Col2 \u0026amp; Col3 \\\\ [0.5ex] \\hline\\hline 1 \u0026amp; 6 \u0026amp; 87837 \u0026amp; 787 \\\\ \\hline 2 \u0026amp; 7 \u0026amp; 78 \u0026amp; 5415 \\\\ \\hline 3 \u0026amp; 545 \u0026amp; 778 \u0026amp; 7507 \\\\ \\hline 4 \u0026amp; 545 \u0026amp; 18744 \u0026amp; 7560 \\\\ \\hline 5 \u0026amp; 88 \u0026amp; 788 \u0026amp; 6344 \\\\ [1ex] \\hline \\end{tabular} \\end{center} Creating tables in LATEX can be a bit tricky sometimes, so you may want to use the TablesGenerator.com online tool to export LATEX code for tabulars. The File \u0026gt; Paste table data option lets you copy and paste data from spreadsheet applications. 使用 LaTeX 创建表格有时候是有一些麻烦的,所以你可能会去想到使用 TablesGenerator.com 在线工具导出可以直接在 tabulars 环境中使用的 LaTeX 代码。File \u0026gt; Paste table data 这一选项可以让你从电子表格应用中复制并粘贴数据。\nCaptions, labels and references 题注、标签和引用 You can caption and reference tables in much the same way as images. The only difference is that instead of the figure environment, you use the table environment. 你可以与为图片添加题注和引用很类似的操作为表格添加题注和引用。唯一的区别是,你需要使用 table 环境,而不是 figure 环境。\nTable \\ref{table:data} is an example of referenced \\LaTeX{} elements. \\begin{table}[h!] \\centering \\begin{tabular}{||c c c c||} \\hline Col1 \u0026amp; Col2 \u0026amp; Col2 \u0026amp; Col3 \\\\ [0.5ex] \\hline\\hline 1 \u0026amp; 6 \u0026amp; 87837 \u0026amp; 787 \\\\ 2 \u0026amp; 7 \u0026amp; 78 \u0026amp; 5415 \\\\ 3 \u0026amp; 545 \u0026amp; 778 \u0026amp; 7507 \\\\ 4 \u0026amp; 545 \u0026amp; 18744 \u0026amp; 7560 \\\\ 5 \u0026amp; 88 \u0026amp; 788 \u0026amp; 6344 \\\\ [1ex] \\hline \\end{tabular} \\caption{Table to test captions and labels} \\label{table:data} \\end{table} Note: If you are using captions and references on your own computer, you will have to compile the document twice for the references to work. Overleaf will do this for you automatically.’ Note: 如果你正在你自己的电脑上使用题注和引用,你需要编译文档两次以使引用生效。ShareLaTeX 会为你自动做这件事。\nAdding a Table of Contents 添加目录 To create the table of contents is straightforward, the command \\tableofcontents does all the work for you: 添加目录很简单,\\tableofcontents 命令为你做了所有的工作:\n\\documentclass{article} \\usepackage[utf8]{inputenc} \\title{Sections and Chapters} \\author{Gubert Farnsworth} \\date{ } \\begin{document} \\maketitle \\tableofcontents \\section{Introduction} This is the first section. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam lobortisfacilisis sem. Nullam nec mi et neque pharetra sollicitudin. Praesent imperdietmi nec ante. Donec ullamcorper, felis non sodales... \\addcontentsline{toc}{section}{Unnumbered Section} \\section*{Unnumbered Section} Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam lobortis facilisissem. Nullam nec mi et neque pharetra sollicitudin. Praesent imperdiet mi necante... \\section{Second Section} Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam lobortis facilisissem. Nullam nec mi et neque pharetra sollicitudin. Praesent imperdiet mi necante... \\end{document} Sections, subsections and chapters are automatically included in the table of contents. To manually add entries, for example when you want an unnumbered section, use the command \\addcontentsline as shown in the example. 节标题,小节标题和章节标题被自动包含到目录中。手动添加目录项,比如当你想添加一个未编号的节标题,使用命令 \\addcontentsline,就像例子中那样。\n这是截止目前我看过的最好的 LaTeX 入门文章。将其翻译为中文的想法是上个学期萌发的,很早以前就差不多翻译完了,一直没有发出来,今天得以成行。 ShareLaTeX 和 Overleaf 在中文翻译中我给混用了,因为我之前翻译的时候 Overleaf 还在测试,那时原文档里都写的是 ShareLaTeX,改不改看我的心情,也没什么影响。 原英文文档里附有很多扩展文章链接,建议大家最好还是去看原文档,单词都很简单。文档链接:https://www.overleaf.com/learn/latex/Learn_LaTeX_in_30_minutes 未来我会继续对翻译进行修正,以期更信、达、雅。\n","date":"Jan 18","permalink":"https://o5o.me/post/wp/learn-latex-in-30-minutes/","tags":[null],"title":"[翻译/中英对照] Learn LaTeX in 30 minutes 30分钟入门LaTeX"},{"categories":[null],"contents":"在今年的双十一,我买了人生中第一部函数型计算器——卡西欧 fx-991CN X,相见恨晚。然而我在得到它的一个月后就又失去了它。在图书馆不知道被谁给偷偷拿走了。\n刚遇见它的时候,即便是我换新手机也没那么高兴。进行矩阵运算(不超过4阶)不仅仅是方便,简直是好用到哭、好用到爆炸。我用很短的一段时间把说明书看了一遍,并把上面的例子挨个敲了一遍。计算器在手,让我有一种踏实的感觉,所有可能出现的情况都是我掌握了的,只要不超过它的能力范围,就不会抛出什么未知的错误。 失去以后才更能想起它的好。 这学期我们有一门课是物理实验,数据处理时要进行很多重复计算,很枯燥,但使用 CALC 就很方便了。使用CALC 可以保存含有变量的表达式,在进行重复计算时,只要把表达式中的变量所指的数据代入就能得到答案,不需要再去重复输入算式。 求矩阵的逆矩阵,是在输入矩阵变量后,再按 x-1(方形按钮3行3列)。我一开始不太熟悉,在功能列表里没找到”逆矩阵”,还以为不能算,去百度上搜也有人说没这个功能。后来我是无意中发现的。其实说明书里已经写了,还举了例子。 计算器说明书写的很清楚,把说明书看一遍就会用了,完全不用再看其他教程浪费时间。说明书可以从这里下载:说明书链接 我又买了一个一模一样的计算器,今天刚收到。\n","date":"Jan 03","permalink":"https://o5o.me/post/justaoyu/casiofx991cnx/","tags":[null],"title":"当时只道是寻常"},{"categories":[null],"contents":"我竟然从没玩儿过扫雷/笑哭。 在以前的某个时候,我看到了一份写的乱七八糟的扫雷游戏教程,导致我一直觉得扫雷是一款很难的游戏,今天在玩儿之前也是抱着这样的想法。 今天开始数学实习,机房还是xp系统,网速还异乎寻常地慢,在等待老师上课期间,能玩儿的似乎只有扫雷。 没想到扫雷的游戏规则却是异乎寻常地简单:点开一个方格,它里面的数字就是与它相邻的方格(横向、竖向、斜向)内的炸弹数,把炸弹给找出来。 在机房只玩儿了一小会儿就上课了,意犹未尽。晚上回寝室在自己的电脑上安装了扫雷。界面变的很精美,炸弹也变成了瓢虫,还有闯关游戏。在闯关游戏中,我遇到这样一种情况,两个方格其中之一是炸弹,但我却无法根据周围的数字判断出到底哪个里面有炸弹:\n1 3 炸 ? ? 炸 1 炸 炸 3 3 炸 1 2 2 1 1 1 找不出来也不影响通关。我认为是无解的。有一个标识出一个雷的位置的道具被我不小心用掉了,可能正确的使用位置是这里。\n","date":"Dec 10","permalink":"https://o5o.me/post/justaoyu/minesweeper/","tags":[null],"title":"扫雷"},{"categories":[null],"contents":"2018, 我准备好了.\n大学 在 2018 年的 3 月把计算机二级考试中的 “C语言” 和 “Office” 过了, 拿到优秀证书. 参加 2018 年 4 月和 5 月进行的全国大学生英语竞赛初赛和决赛, 至少获得一等奖(各参赛高校初赛人数的5%). 参加 6 月份的大学英语四级考试, 分数至少为 425 分(或 550 分). 参加 9 月份的大学生数学建模竞赛 (重在参与, 不求获奖) 参加 12 月份的大学英语六级考试, 分数至少为 425 分.\n口琴 每当学会一首新曲, 发布在博客里.\n读书 读完一本书后写一篇读后感\n其他 学习 会计 有关基础知识 学习 法律 相关基础知识 2018 年花出的每一笔钱都要记账\n","date":"Dec 31","permalink":"https://o5o.me/post/justaoyu/2018-new-year-resolutions/","tags":[null],"title":"Aoyu 的 2018 新年计划"},{"categories":[null],"contents":"我们学校昨天开放报名, 人很多. 九点开始, 我到 12 点多终于报上了. 我报了 C 语言和 Office. 由于我不知道报名开始的具体时间, 担心 0 点开始, 到了夜里12点多才睡. 睡前定了闹钟, 6点钟就起床了, 左等右等就是看不到考点选择里出现我们学校. 后来听学姐说是 9 点钟开始的. 需要上传证件照, 头像是我自己拍照片扣的, 加了蓝色背景, 还算可以吧. 只是皮肤没我自己认为的那么白, 眼睛也没我自己认为的那么大. 其实本不需要这么麻烦, 教务系统里有照片, 下载下来改个背景就能用, 我是报完名后才想起来的.\n","date":"Dec 19","permalink":"https://o5o.me/post/justaoyu/ncre-registration/","tags":[null],"title":"计算机二级报名"},{"categories":[null],"contents":"我的手机是去年买的 乐2 pro X625,昨天刷了安卓原生系统。 我之前使用的手机是在我刷机时报废的(芯片烧毁的那种报废),因此我对刷机一直避而远之。压垮骆驼的最后一根稻草可能在昨天飘到了骆驼背上。 被某兔一键刷机给坑了。一键刷机?呵呵,我竟然傻傻地相信了。刷完 RECOVERY,停在了安装驱动那一步(不能手动安装,我也不知道安装的什么驱动)。把数据线拔掉,再开机就停在了开机界面那里——变砖了。 最后,我用了 adb sideload 命令才不至于丢人丢到连刷机都能搞砸。 最靠谱的刷机步骤如下:刷入第三方 RECOVERY;下载卡刷包;刷机。 使用 adb 命令时(其他新安装的命令行工具也一样),如果遇到什么未知错误,先考虑是不是端口被占用了,重启电脑试试。 我刷机前,第一次付费加 QQ 群。手机变砖后我尝试去群里求助,一人愿意提供有偿服务,4元钱。感觉那人不靠谱,遂拒绝。 原生安卓系统没有重启按钮(安卓6.0),长按电源键可重启。原生系统也没有通话录音功能,使用第三方应用录的音噪声太大,遂放弃。 我刷入的包默认使用 Arrow 桌面,但让我奇怪的是桌面上只能排列两行图标。是 bug 还是“创意”?我一开始以为是“创意”,但我某次重启后桌面又可以排列多行图标。我以为是我不小心按到了什么选项,找了好几遍,没有。正好手边有一个 OPPO 小手机,安装 Arrow 桌面测试,发现图标排列正常,遂知我遇到了 bug。重启几次后桌面图标排列正常。期间到 QQ 群里提问,没人搭理。 乐2 pro 电池为 3000 毫安时,但我刷入的系统在电池界面却显示 4050 毫安时。该 bug 已向作者反馈。 我的手机处理器为 联发科X25,但我刷的是 X20 的包,所以,我 2100万像素的摄像头在系统自带相机里显示最多为 1600万像素。无所谓了,即便摄像头达到了所谓的 2100万像素也照样拍不出你以为的清晰度的照片。 系统自带 Root 权限,但有的应用依然无法卸载。遂下载 RE 文件管理器到 /system/app 找到相关文件删之。但出现在桌面上的图标不会自动消失,点击后会弹出“程序无法正确运行”的提示。重启手机,世界就清净了。 准备安装谷歌四件套,但某一键安装器提示“system分区未解锁”。如果手动下载,四件套也不容易凑齐,遂放弃。 我对这次刷的系统很满意,准备长期使用。\n","date":"Sep 07","permalink":"https://o5o.me/post/justaoyu/lemobile-android-aosp/","tags":[null],"title":"乐2Pro X625手机刷机"},{"categories":[null],"contents":"今天在图灵读者群问了一个蠢问题。\n#include void fortune\\_cookie(char msg[]) { printf(\u0026#34;Message reads: %s\\n\u0026#34;, msg); } int main() { char quote[] = \u0026#34;Cookies make you fat\u0026#34;; fortune\\_cookie(quote); return 0; } 我的问题是:为什么 msg 的前面不加 “*”? C语言中,字符串即是字符数组。在传递字符串时,只能传递数组中第一个元素的地址,然后程序循着这个地址依次向后读取字符,直到遇到”\\0″。msg 是一个指针,指向字符串”Cookies make you fat”中的第一个字母”C”。给指针变量加”*”,读取的是指针指向的存储器地址中保存的数据,而我们想要的原是根据字符数组第一个元素的地址读取整个字符串,显然这与我们的期望不符,况且,C语言也不允许这样做。\n","date":"Aug 15","permalink":"https://o5o.me/post/justaoyu/a-silly-question-c/","tags":[null],"title":"为什么通过指针传递字符串时不在变量名前加”*”?"},{"categories":[null],"contents":"我生活在楚门的世界里吗?我不确定。如果是真的,那么绝对是由一位比 Christof 更高明的导演执导的。 多希望我能像楚门一样触碰到世界的边界。 数学就很合适。\n","date":"Aug 14","permalink":"https://o5o.me/post/justaoyu/the-truman-show/","tags":[null],"title":"The Truman Show"},{"categories":[null],"contents":"这本书是我去年暑假买的,看到了“结构、联合、位字段”那一章,往后便读不下去了。这一结果与生物进化假说中的中性突变基因积累的结果类似,那些看似无关紧要的令人略微困惑的小知识点累积下来就最终成为了阅读途中的绊脚石,而且还是不容易跨过的那种。 数次决心读完这本书,可是最终都落得头皮发麻、不知所以然的下场,无功而返。今天停电,我又重新拿起了这本书。 我读完了第一章。记笔记与番茄钟的使用让我很有成就感。记笔记时我使用了网格笔记本与康奈尔笔记法。读完这一章我一共用了三个番茄钟。 这本书真的很适合有一定基础的初学者,可是,以前的我为了追求速度并没有认真对待这些财富,总是想着我先把这本书快速读一遍,以后再认真翻,然而,哪有什么以后…… The end.\n","date":"Aug 13","permalink":"https://o5o.me/post/justaoyu/read-headfirstc-again/","tags":[null],"title":"重新阅读“嗨翻C语言”"},{"categories":[null],"contents":"也许是我太过渴望安逸,我越发地怀念当初使用 WordPress 写博客的时光,越发地喜欢那种所有想要的一切都已被安排好的感觉,于是,我来到了这里。 我从 WordPress 到 Typecho,从 Typecho 到 Hexo,再从 Hexo 到 Jekyll,又从 Jekyll 到 Bitcron,最后又回到原点,这样一个过程,像极了我们的人生。 我是一名“数学与应用数学”专业的新生,很高兴认识你。\n","date":"Aug 12","permalink":"https://o5o.me/post/justaoyu/hello-world/","tags":[null],"title":"Hello, world"},{"categories":[null],"contents":"每年我可以过三个生日,这是今年的最后一个。从今天起,我便再也不能说自己是未成年了。\n","date":"May 10","permalink":"https://o5o.me/post/justaoyu/the-eve-of-cee/","tags":[null],"title":"高考前夕"},{"categories":[null],"contents":"唯愿六月八号合上笔盖的那一刻,有着侠客收刀入鞘的骄傲。\n","date":"Feb 03","permalink":"https://o5o.me/post/justaoyu/0203/","tags":[null],"title":"20170203"},{"categories":null,"contents":"","date":"Jan 01","permalink":"https://o5o.me/articles/","tags":null,"title":"Articles"}]