LaTeX 学习

LaTeX 中的常用数学符号

Archived to LaTeX 中的常用数学符号.

CTeX

在 ctexart 文档类中使 \today 输出英文格式的当前日期

\documentclass[UTF8]{ctexart} 之后\begin{document} 之前的任何地方插入 \CTEXoptions[today=old]

也可以统一地放在 \ctexset 命令的参数中

\ctexset{
    section = {
        format = \Large\bfseries
    },
    today = old     % 使日期显示为英文格式
}

Linux 学习

Git

只提交部分修改

使用 git add 将想要提交更改的文件添加到暂存区然后再用 git commit 提交即可在 vscode 中可以通过直接点击文件后面的暂存更改按钮来将文件的修改添加到暂存区这两个方式是等价的

如果只想提交某个文件的部分修改可以使用 git add -p可以在随后唤起的交互模式中选择要添加到暂存区的修改

修改 commit 记录的描述

使用 git commit --amend 即可修改上一次 commit 记录的描述

如果需要修改倒数第 n 个 commit 记录的描述则需要通过 git rebase -i 先变基到 HEAD~(n+1)然后在打开的文本编辑器内找到你想要修改的 commit将行首的 pick 改为 reword这将再次打开一个文本编辑器对选定的 commit 描述进行修改最后通过 git rebase --continue 回到 HEAD这可能会造成远程仓库的历史记录与本地不一致

APT

APT 换源后出现由于没有公钥无法验证下列签名

root@ub22:/# apt-get update
...
获取:4 http://mirrors.aliyun.com/kali kali-rolling InRelease [41.5 kB]
...
错误:4 http://mirrors.aliyun.com/kali kali-rolling InRelease
  由于没有公钥, 无法验证下列签名: NO_PUBKEY xxxxxxxx
...
正在读取软件包列表... 完成
W: GPG 错误: http://mirrors.aliyun.com/kali kali-rolling InRelease: 由于没有公钥, 无法验证下列签名:  NO_PUBKEY xxxxxxxx
E: 仓库 "http://mirrors.aliyun.com/kali kali-rolling InRelease" 没有数字签名.
N: 无法安全地用该源进行更新, 所以默认禁用该源.
N: 参见 apt-secure(8) 手册以了解仓库创建和用户配置方面的细节.

解决方案sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv xxxxxxxxxxxxxxxx 替换成报错中的实际公钥

解决上述报错后可能会在 apt-get update 的时候得到报错

W: http://mirrors.aliyun.com/kali/dists/kali-rolling/InRelease: 密钥存储在过时的 trusted.gpg 密钥环中(/etc/apt/trusted.gpg), 请参见 apt-key(8) 的 DEPRECATION 一节以了解详情.

需要将密钥从旧的 apt 密钥工具转换为新的 apt 可信密钥格式

可以先尝试使用 apt-key list gazebo在我遇到的情况下这将返回与上面的警告类似但更详细的报错信息

Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)).

使用 sudo cp /etc/apt/trusted.gpg /etc/apt/trusted.gpg.d 即可

NodeJS 与 JavaScript 学习

NPM

NPM 常用命令

  • npm install 安装 npm 包使用 --save 参数将安装的包添加到 package.json 文件的 dependencies 列表中在 npm 5.0.0 及更高版本中 --save 选项不再被需要因为它现在是默认行为使用 -g 参数全局安装

  • npm list 查看当前已安装的所有包这个命令会列出所有已安装的包及其依赖如果只想查看顶级也就是直接安装的使用 npm list --depth=0使用 -g 参数查看全局安装的包

  • npm uninstall 卸载 npm 包这个命令会从 node_modules 目录中删除 npm 包并且也会从 package.json 文件的 dependencies 列表中移除它如果想要同时从 package.json 文件的 devDependencies 列表中移除它你可以使用 -D--save-dev 选项

Python 学习

基础

数据类型

数组

索引数组

索引数组是一种特殊的数组它的元素是另一个数组的索引索引数组可以用来选择或修改另一个数组的元素

例如假设我们有一个数组 a = np.array([0, 11, 22, 33, 44, 55])我们可以创建一个索引数组 indices = np.array([1, 3, 5])然后使用这个索引数组来选择 a 的元素selected = a[indices]这样selected 就是一个新的数组它的元素是 a 的第 13 和第 5 个元素[11, 33, 55]

列表

元组

字典

集合

字符串

控制流

函数

异常处理

函数式编程

PPL 中的高阶函数

高阶函数 map()

高阶函数 filter()

高阶函数 reduce()

匿名函数 / lambda 表达式

面向对象编程

语法糖

装饰器

生成器

迭代器

列表推导式

实战

使用 Python 计算常见四搭型的牌效率

麻将中常见复合型的进张情况

import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
import numpy as np
import random
from scipy.spatial import ConvexHull

# 回溯法计算是否是有效的听牌形式或胡牌形式(不含国士)
def is_jinzhang_OK(arr):
    arr = arr.copy()
    arr.sort()
    if not arr:
        return True
    # 刻子
    if arr.count(arr[0]) >= 3:
        new_arr = arr.copy()
        new_arr.remove(arr[0])
        new_arr.remove(arr[0])
        new_arr.remove(arr[0])
        if is_jinzhang_OK(new_arr):
            return True
    # 顺子
    if arr[0] + 1 in arr and arr[0] + 2 in arr:
        new_arr = arr.copy()
        new_arr.remove(arr[0])
        new_arr.remove(arr[0] + 1)
        new_arr.remove(arr[0] + 2)
        if is_jinzhang_OK(new_arr):
            return True
    # 对子, 两面, 坎张的情况的处理是类似的, 此处略去
    return False

# 列表中对应位置处的元素代表对应数牌是否是有效进张
def jinzhang(arr):
    return [0] + [is_jinzhang_OK(arr + [i]) for i in range(1, 10)]

# 进张种数
def jinzhang_zhongshu(arr):
    return jinzhang(arr).count(True)

# 计算除了这个搭子外, 所有数牌的残余数量
def shengyu_meishu(arr):
    return [0] + [(4 - arr.count(i)) for i in range(1, 10)]

# 进张枚数
def jinzhang_meishu(arr):
    remaining = shengyu_meishu(arr)
    jinzhang_result = jinzhang(arr)
    return sum([(remaining[i] * jinzhang_result[i]) for i in range(1, 10)])

# 生成所有的单张数牌
def generate_all_A():
    return [[i] for i in range(1, 10)]

# 生成所有的 ABCD 型搭子
def generate_all_ABCD():
    arrays = []
    for i in range(6):
        arrays.append([1 + i, 2 + i, 3 + i, 4 + i])
    return arrays

# ...

# 单张数牌 A 的情况
arrays0 = generate_all_A()
x0 = [jinzhang_zhongshu(arr) for arr in arrays0]
y0 = [jinzhang_meishu(arr) for arr in arrays0]
points0 = [(x + random.uniform(0, 0.5), y + random.uniform(0, 0.5)) for x, y in zip(x0, y0)]
points0 += [(x - random.uniform(0, 0.5), y + random.uniform(0, 0.5)) for x, y in zip(x0, y0)]
points0 += [(x - random.uniform(0, 0.5), y - random.uniform(0, 0.5)) for x, y in zip(x0, y0)]
points0 += [(x + random.uniform(0, 0.5), y - random.uniform(0, 0.5)) for x, y in zip(x0, y0)]

# 生成凸包并绘制
hull0 = ConvexHull(points0)
points0_np = np.array(points0)
plt.scatter(x0, y0, color='#FF4B00', alpha=0.2)
poly0 = Polygon(points0_np[hull0.vertices], fill=True, color='#FF4B00', alpha=0.2, linestyle='dashed', label='A')
plt.gca().add_patch(poly0)

plt.xlabel('zhongshu')
plt.ylabel('meishu')
plt.legend()
plt.show()

使用 PIL 库将多张图片合并成一个网格

在文件夹中按文件名的字典序选择对应格式 .xxx 的图片然后按指定的格式 .xxx 拼接为一张图输出 output-{timestamp}.xxx 到当前工作目录下

生成的图片的长宽将取决于每行每列被拼接图片数量以及所有图片中长宽的最大值

import os
import time
import argparse
from PIL import Image, ImageOps

# 创建命令行参数解析器, 并解析命令行参数
parser = argparse.ArgumentParser(description='Combine images into a grid.')
parser.add_argument('image_folder', help='The folder containing the images.')
parser.add_argument('image_format', help='The format of the images (e.g., .png, .jpg).')
parser.add_argument('num_rows', type=int, help='The number of rows in the grid.')
parser.add_argument('num_cols', type=int, help='The number of columns in the grid.')
parser.add_argument('--rotate', type=int, choices=[0, 90, 180, 270], help='The angle to rotate each image.')
parser.add_argument('--log', action='store_true', help='Log the process.')
args = parser.parse_args()

timestamp = int(time.time())

# 获取图片文件夹中的所有图片文件, 按文件名排序, 只保留前 num_rows * num_cols 张图片
image_files = sorted([os.path.join(args.image_folder, file) for file in os.listdir(args.image_folder) if file.endswith(args.image_format)])
image_files = image_files[:args.num_rows * args.num_cols]
images = [Image.open(image_file) for image_file in image_files]

# 如果指定了旋转角度, 就旋转每一张图片, 并获取旋转后的宽度和高度
if args.rotate:
    images = [image.rotate(args.rotate, expand = True) for image in images]
    image_widths_and_heights = [(image.getbbox()[2] - image.getbbox()[0], image.getbbox()[3] - image.getbbox()[1]) for image in images]
    image_width = max(width for (width, height) in image_widths_and_heights)
    image_height = max(height for (width, height) in image_widths_and_heights)
else:
    image_width = max(image.size[0] for image in images)
    image_height = max(image.size[1] for image in images)

total_width = image_width * args.num_cols
total_height = image_height * args.num_rows

if args.log:
    print(f'Max image width: {image_width}, max image height: {image_height}\n')
    print(f'Total width: {total_width}, total height: {total_height}\n')

# 遍历图片列表, 将每一张图片粘贴到新的图片上的正确位置
new_image = Image.new('RGB', (total_width, total_height), (255, 255, 255))
for index, image in enumerate(images):
    row = index // args.num_cols
    col = index % args.num_cols

    # 创建一个新的空白图片 background , 大小为最大宽度和高度, 将图片居中粘贴到背景图片上
    background = Image.new('RGB', (image_width, image_height))
    offset = ((image_width - image.size[0]) // 2, (image_height - image.size[1]) // 2)
    background.paste(image, offset)

    # 将 background 粘贴到新的图片上的正确位置
    new_image.paste(background, (col * image_width, row * image_height))

    if args.log:
        print(f'Pasted image {index} at offset: {offset}, at position: {(col * image_width, row * image_height)}\n')

new_image.save(f'output-{timestamp}{args.image_format}')

比较坑的地方是使用 image.rotate() 方法并不会自动调整图像的宽度和高度需要手动用 image.getbbox() 方法来获取图像的边界框左上和右下的 x, y 坐标然而这个边界框是包含图像所有像素的最小矩形所以在调用 image.rotate() 方法时需要指定 expand = True以确保旋转后的图像尺寸会扩展到包含旋转后的整个图像

使用 PIL 库将多张图片合并成 pdf 文件并使用图像的平均哈希值去重

import os
import time
import argparse
from PIL import Image
import imagehash

# 创建命令行参数解析器, 并解析命令行参数
parser = argparse.ArgumentParser(description='Output images as a PDF file, each image on a separate page, removing duplicates.')
parser.add_argument('image_folder', help='The folder containing the images.')
parser.add_argument('image_format', help='The format of the images (e.g., .png, .jpg).')
parser.add_argument('--rotate', type=int, choices=[0, 90, 180, 270], help='The angle to rotate each image.')
parser.add_argument('--log', action='store_true', help='Log the process.')
args = parser.parse_args()

timestamp = int(time.time())

# 获取图片文件夹中的所有图片文件, 按文件名排序
image_files = sorted([os.path.join(args.image_folder, file) for file in os.listdir(args.image_folder) if file.endswith(args.image_format)])
unique_images = []
hashes = []

for image_file in image_files:
    current_image = Image.open(image_file)
    current_hash = imagehash.average_hash(current_image)
    
    # 检查当前图片与已有图片的相似度
    if not any(current_hash - h < 5 for h in hashes):
        unique_images.append(current_image)
        hashes.append(current_hash)
    elif args.log:
        print(f'Skipped duplicate image: {image_file}')

# 如果指定了旋转角度, 就旋转每一张图片
if args.rotate:
    unique_images = [image.rotate(args.rotate, expand=True) for image in unique_images]

# 使用第一张图片作为封面, 其余图片追加到 PDF 中
if unique_images:
    last_folder_name = os.path.basename(os.path.normpath(args.image_folder))
    pdf_path = f'{last_folder_name}-{timestamp}.pdf'
    unique_images[0].save(pdf_path, "PDF", resolution=100.0, save_all=True, append_images=unique_images[1:])
    
    if args.log:
        print(f'Saved {len(unique_images)} images to {pdf_path}')

为当前文件夹内每一个 .md 文件添加更新时间

import os
import datetime

directory = '.'

for file in os.listdir(directory):
    if file.endswith('.md'):
        full_path = os.path.join(directory, file)
        mod_time = os.path.getmtime(full_path)
        readable_time = datetime.datetime.fromtimestamp(mod_time).strftime('%Y-%m-%d %H:%M:%S')
        update_string = f"updated: {readable_time}\n"

        with open(full_path, 'r', encoding='utf-8') as file:
            lines = file.readlines()

        if len(lines) >= 3:
            lines.insert(3, update_string)
        else:
            lines.append(update_string)

        with open(full_path, 'w', encoding='utf-8') as file:
            file.writelines(lines)

WSL 学习

WSL 与 宿主机之间的文件访问

宿主机访问 WSL 文件通过网络访问 \\wsl$\Ubuntu-22.04\...

WSL 访问宿主机文件/mnt/盘符/...

为 WSL 配置代理

WSL 提示

wsl: 检测到 localhost 代理配置, 但未镜像到 WSL. NAT 模式下的 WSL 不支持 localhost 代理.

在宿主机的 %USERPROFILE%\.wslconfig 文件中添加

[experimental]
autoMemoryReclaim=gradual  # gradual  | dropcache | disabled
networkingMode=mirrored
dnsTunneling=true
firewall=true
autoProxy=true

See:
https://learn.microsoft.com/en-us/windows/wsl/networking

https://github.com/microsoft/WSL/issues/10753