本地音乐生成:ComfyUI 和 ACE-Step-1.5 模型

如今,您不必依赖云服务来创建内容:您可以完全在自己的硬件上生成高质量的音乐。在这篇文章中,我将描述如何使用 ComfyUI 在计算机上本地运行现代 ACE-Step-1.5 模型。

ComfyUI 采用基于节点的架构。这使您能够:
– 完全控制音频生成的每个阶段。
– 轻松共享现成的“工作流程”。

ACE-Step-1.5 是一种高级音乐生成模型,需要大量计算资源。其硬件要求比许多简单的合成器要高:
显卡 (GPU): Nvidia RTX,具有 8 GB VRAM 或更高(建议 12 GB+),可实现舒适的高品质工作。
随机存取存储器 (RAM): 至少 16 GB(最好 32 GB 及以上)。
处理器(CPU):现代多核处理器,对 AVX/CUDA 计算有良好的支持。
磁盘空间: 模型和组件大约需要 20–50 GB。

运行 ACE-Step-1.5 最简单的方法是使用现成的音频生成模板。只需在工作流程窗口中搜索音乐文本到音频并安装即可。

在“提示输入”节点中编写描述流派和情绪的提示(例如,“带有重低音的令人振奋的合成波曲目”)。指定所需的持续时间并按运行
第一代可能需要一些时间,因为模型将被加载到显卡内存中并处理复杂的声学模式。

https://github.com/comfyanonymous/ComfyUI
https://www.youtube.com/watch?v=UAlLD5fS7-c

Raspberry PI 3 作为 Wi-Fi 路由器

互联网上有很多关于如何从 Raspberry Pi (RPI) 制作 Wi-Fi 路由器的文章,在这篇文章中我将简要描述我创建带有板上单盒的 Wi-Fi 路由器的方法。所描述的方法在当前有效,并且将来可能会发生很多变化。因此,请使用此注释作为您将遇到的情况的粗略概述。

SSH

对于那些不知道如何使用 OpenWrt 的人,我建议安装 DietPI。
通过 eth0 将 RPI 连接到当前路由器,然后通过 SSH 连接。您可以在路由器的 dhcp 面板中找到 RPI IP 地址。直接连接到 root,例如如下所示:

ssh root@[IP_ADDRESS]

接入点

root 的默认 DietPI 密码是 Dietpi。连接后,您将看到 DietPI 安装程序/配置程序。完成后,由于设备重新启动,您将需要重新连接。

首先,您需要配置 hostapd 以便设备可以看到您的接入点。如果没有安装hostapd,则通过apt安装。

接下来,您需要为 hostapd 编写配置。我的配置示例:

interface=wlan0
driver=nl80211
ssid=MyPiAP
hw_mode=a
channel=157
wmm_enabled=1

auth_algs=1
wpa=2
wpa_passphrase=your_password
wpa_key_mgmt=WPA-PSK
rsn_pairwise=CCMP
ieee80211n=1
ieee80211ac=0
ieee80211ax=0
country_code=RU

hostapd配置的含义可以在手册中找到。然而,重要的是要为自己配置它 – 频道(2.4GHz 或 5GHz)、国家/地区代码,否则没有这些,您的本地化设备就可以正确地与接入点配合使用,我已经完成了这一点并且知道,所以请务必设置您的国家/地区。

DHCP

接下来,安装并配置 dnsmasq 以实施 DHCP。这是连接计算机确定 IP 地址和 DNS 服务器所必需的。
我的配置示例:

interface=wlan0
dhcp-range=172.19.0.10,172.19.0.200,255.255.255.0,12h

dhcp-option=3,172.19.0.1

dhcp-option=6,1.1.1.1,8.8.8.8

no-resolv

server=1.1.1.1
server=8.8.8.8

这是允许您连接到接入点并获取 IP 地址的最低配置。接下来,您需要配置路由和 NAT。这是连接计算机可以访问互联网所必需的。

这里的说明涉及常规 Debian 兼容系统上的典型路由设置类别,互联网上有很多关于该系统的文章。然后这一切都取决于您追求的目标,例如,作为系统中的新接口连接到外部服务器,或者只是执行 wlan0 <-> eth0,这是 RPI 细节结束的地方,然后根据您的喜好进行配置。

我还想提一下需要通过systemctl配置自定义系统服务;可能需要将服务连接成一条链;所有这些都在网络上的 systemctl 手册中。如果服务层面有问题,则检查journalctl中的日志。

Wi-Fi 适配器

坦白说,内置的 RPI3 性能很弱,而且不支持 5GHz。因此,我通过 USB 2 将 RITMIX RWA-150 适配器连接到 Realtek RTL8811CU 芯片组上。驱动程序已添加到我的 DietPi 版本中的 Linux 内核中。接下来,使用dietpi-config,我完全关闭了内置Wi-Fi。结果,只剩下一个wlan0 USB适配器了。

结论

从速度测量来看,我们能够通过 Wi-Fi 从 RPI3 中挤出约 50Mbps(连接 5GHz 适配器后),这意味着与直接连接到路由器相比,速度损失了一半。我承认更高效的 RPI 模型将使您获得更好的结果,而且专门的 OpenWrt 设备和现成的解决方案可能更适合您的需求。

来源

https://forums.raspberrypi.com/viewtopic.php?t=394710
https://superuser.com/questions/1408586/raspberry-pi-wifi-hotspot-slow-internet-speed
https://www.youtube.com/watch?v=jlHWnKVpygw

本地图像生成:ComfyUI 和 FLUX 模型

如今,您不必依赖云服务:您可以完全在自己的硬件上生成高质量的图像。在这篇文章中,我将描述如何使用 ComfyUI 在计算机上本地运行现代 FLUX 模型。

ComfyUI 采用基于节点的架构。这使您能够:
– 完全控制生成的每个阶段。
– 轻松共享现成的“工作流程”

FLUX是大型模型,因此硬件要求比SD 1.5或SDXL更高:
显卡 (GPU): Nvidia RTX,具有 12 GB VRAM 或更高(为了舒适工作)。如果您有 8 GB 或更少,则必须使用量化版本(GGUF 或 NF4)。
随机存取存储器 (RAM): 至少 16 GB(最好 32 GB 及以上)。
磁盘空间: 模型和组件大约需要 20–50 GB。

启动 FLUX 最简单的方法是使用现成的模板。只需在工作流程窗口中搜索 Flux Text to Image 并安装即可。

在“Text to Image (Flux.1 Dev)”节点中用英文编写提示,选择分辨率(FLUX 适用于 1024×1024 甚至更高),然后按 RUN

第一代可能需要一些时间,因为模型将加载到显卡内存中。

https://github.com/comfyanonymous/ComfyUI

在 Docker 中运行 macOS

在 Docker 中运行 macOS 是可能的,尽管有人反对说这是不可能的,而且据说 macOS 有某种保护系统可以抵抗这种情况。

历史上,在 PC 机上运行 macOS 的一些经典方法是:
*黑客塔
* 虚拟化,例如使用VMWare

Hackintosh 假设存在与原始 Mac 相似或非常接近的硬件。虚拟化对硬件有一定的要求,但通常不像 Hackintosh 那样严格。然而,在虚拟化的情况下,存在性能问题,因为 macOS 并未针对在虚拟环境中工作进行优化。

最近,在 Docker 中运行 macOS 已经成为可能。这是通过 Docker-OSX 项目实现的,该项目提供现成的 macOS 映像以在 Docker 上运行。值得注意的是,Docker-OSX 不是苹果官方项目,不受其支持。但是,它允许您在 Docker 上运行 macOS 并使用它来开发和测试应用程序。

首批在 Docker 中运行 macOS 的项目之一:
https://github.com/sickcodes/Docker-OSX

然而,我始终无法完全启动它;加载到 Recovery OS 后,我的键盘和鼠标直接掉落,无法继续安装。同时,在第一个启动菜单中,键盘起作用。也许事实是这个项目不再受到如此积极的支持,并且在 Windows 11 + WSL2 + Ubuntu 上运行时存在一些特定问题。

目前最活跃的项目之一:
https://github.com/dockur/macos

允许您在 Docker 中运行 macOS,界面通过 VNC(?) 转发通过浏览器工作。启动后,macOS 可访问 http://localhost:5900

我设法运行这个项目并在 Windows 11 + WSL2 + Ubuntu 上安装 macOS Big Sur(2020 分钟),但只能通过更改 compose 文件来实现,即:

environment:
    VERSION: "11"
    RAM_SIZE: "8G"
    CPU_CORES: "4"

版本:“11”是 macOS 的版本,在本例中为 Big Sur
RAM_SIZE:“8G”是为 macOS 分配的 RAM 量
CPU_CORES:“4”是分配给macOS的CPU核心数

目前,运行 macOS tahoe (16) 也是可能的,但项目开发人员正在努力解决许多问题。

这种启动 macOS 的原始方式允许您在非 Mac 硬件上尝试它,并在受够了痛苦后,再给自己买一台 Mac。但是,它对于在旧系统上测试软件和一般开发很有用。

在 WSL2 中构建 Swift (Linux)

Swift 生态系统正在 Apple 平台之外积极开发,如今在 Windows 下使用 Windows Subsystem for Linux (WSL2) 进行编写已经相当方便。值得考虑的是,对于 Linux/WSL 下的程序集,可以使用 Swift 的轻量级版本 – 无需专有的 Apple 框架(例如 SwiftUI、UIKit、AppKit、CoreData、CoreML、ARKit、SpriteKit 和其他 iOS/macOS 特定的库),但对于控制台实用程序和后端来说,这已经足够了。在这篇文章中,我们将逐步介绍准备环境并从 WSL2 中的源代码构建 Swift 编译器的过程(以 Ubuntu/Debian 为例)。

我们更新软件包列表和系统本身:

sudo apt update && sudo apt upgrade -y

安装构建所需的依赖项:

sudo apt install -y \
  git cmake ninja-build clang python3 python3-pip \
  libicu-dev libxml2-dev libcurl4-openssl-dev \
  libedit-dev libsqlite3-dev swig libncurses5-dev \
  pkg-config tzdata rsync

安装编译器和链接器(LLVM 和 LLD):

sudo apt install -y llvm lld

克隆包含所有依赖项的 Swift 存储库:

git clone https://github.com/apple/swift.git
cd swift
utils/update-checkout --clone

用 swiftc 安装 `swiftly` 和现成的 swift

curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz && \
tar zxf swiftly-$(uname -m).tar.gz && \
./swiftly init --quiet-shell-followup && \
. "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh" && \
hash -r

让我们开始构建(这将需要很长时间):

utils/build-script \
  --release-debuginfo \
  --swift-darwin-supported-archs="x86_64" \
  --llvm-targets-to-build="X86" \
  --skip-build-benchmarks \
  --skip-test-cmark \
  --skip-test-swift \
  --skip-ios \
  --skip-tvos \
  --skip-watchos \
  --skip-build-libdispatch=false \
  --skip-build-cmark=false \
  --skip-build-foundation \
  --skip-build-lldb \
  --skip-build-xctest \
  --skip-test-swift

构建完成后,将编译器的路径添加到PATH(指定构建文件夹的路径):

export PATH=/root/Sources/3rdparty/build/Ninja-RelWithDebInfoAssert/swift-linux-x86_64/bin/swiftc:$PATH

我们检查已安装的 Swift 版本是否正常工作:

swift --version

创建一个测试文件并运行它:

echo "print(\"Hello, World!\")" > hello.swift
swift hello.swift

您还可以编译二进制文件并运行它:

swiftc hello.swift
./hello

来源

模式解释器实践

上一篇文章中,我们研究了解释器模式的理论,了解了 AST 树是什么以及如何抽象终结符和非终结符表达式。这次,让我们抛开理论,看看如何将这种模式应用到我们每天都在使用的严肃商业项目中!

剧透:您现在可能正在使用解释器模式,只需在浏览器中阅读此文本即可!

业界使用此模式的最引人注目、也许也是最重要的示例之一是 JavaScript。这种语言最初是“在膝盖上”创建的,如今,由于解释的概念,它可以在数十亿台设备上运行。

改变互联网的 10 天

JavaScript 的历史充满了传奇。 1995 年,Brendan Eich 在 Netscape Communications 工作时,接到的任务是创建一种可以直接在浏览器 (Netscape Navigator) 中运行的简单脚本语言,以使网页具有交互性。管理层想要一种类似于当时超级流行的 Java 语法的东西,但不是针对专业工程师,而是针对网页设计师。

Eich 仅用了10 天来编写该语言的第一个原型,该语言当时被称为 Mocha(然后是 LiveScript,出于营销原因才称为 JavaScript)。这种热潮并非偶然:微软紧随其后,同时也在积极准备自己的脚本语言 VBScript,以便嵌入 Internet Explorer 浏览器中。 Netscape 迫切需要发布回应,以免在迫在眉睫的浏览器战争中落败。

根本没有时间将复杂的编译器编写成机器代码。对于 Eich 来说,显而易见且最快的解决方案是经典解释器的架构。

第一个解释器(SpiderMonkey)的工作方式如下:

  1. 它从页面读取脚本的文本源代码。
  2. 词法分析器将文本分解为标记。
  3. 解析器构建了一个抽象语法树(AST)。就解释器模式而言,这棵树由终端表达式(字符串,数字如42)和非终端(函数调用,语句如If、While)组成。
  4. 然后虚拟机一步步“遍历”这棵树,在每个节点执行嵌入其中的指令(调用类似于 Interpret() 的方法)。

上下文和对象

还记得在经典实现中我们必须传递给 Interpret(Context context) 方法的 Context 对象吗?解释器需要它来存储当前的内存状态。

就 JavaScript 而言,顶层上下文的角色由全局对象(例如浏览器中的窗口)扮演。例如,当您的 AST 节点尝试通过 document.write(“Hello”) 将文本写入屏幕时,解释器会访问其上下文(文档对象)并调用所需的内部浏览器 API。

多亏了解释器,JavaScript 才能够如此轻松地与 DOM(文档对象模型)进行交互 – 这些都只是上下文中可由树节点访问的对象。

解释器的演变:JIT 编译

从历史上看,浏览器中的 JS 长期以来一直是“纯粹”的解释器。而这有一个很大的缺点——速度慢。每次执行脚本时解析树并缓慢遍历每个节点会减慢复杂的 Web 应用程序的速度。

随着 2008 年 Google V8 引擎(内置于 Chrome 中)的出现,一场革命发生了。工程师们意识到,对于现代网络来说,一个解释器是不够的。引擎变得更加复杂:它仍然构建 AST 树,但现在使用 JIT(即时)编译

现代 JS 引擎(V8、SpiderMonkey)的工作方式就像一个复杂的管道:

  1. 快速而愚蠢的基本解释器立即开始执行您的 JS 代码,甚至无需等待它编译(经典模式在这里仍然有效)。
  2. 同时,引擎会监控代码的“热门”部分(被调用数千次的循环或函数)。
  3. 这些部分由 JIT 编译器直接编译为优化的机器代码,绕过缓慢的解释器。

正是解释器的即时启动和编译的计算能力的结合让 JavaScript 占领了世界,成为服务器 (Node.js) 和移动应用程序 (React Native) 的语言。

游戏行业的翻译

尽管 C++ 在重型计算领域占据主导地位,但解释器模式仍然是游戏开发中用于创建游戏逻辑的行业标准。为了什么?这样游戏设计师就可以制作游戏,而无需“放弃”引擎或需要不断重新编译引擎的风险。

一个很好的历史例子是UnrealScript – 虚幻竞技场和战争机器游戏的逻辑是在虚幻引擎1、2和3中编写的语言。文本被编译成紧凑的抽象机器字节码,然后由引擎的虚拟机逐步(解释)。

可视化图形脚本(蓝图)

如今,文本已被可视化编程所取代 – 虚幻引擎 4 和 5 中的蓝图系统。

如果您曾经在虚幻引擎中打开过蓝图,您就会看到很多通过电线连接的节点。从架构上来说,整个蓝图图是绘制在屏幕上的巨大抽象语法树 (AST)

  1. 终端表达式:常量节点。例如,仅存储数字 42 或字符串的节点。它们在解释时返回特定值。
  2. 非终止表达式:计算节点(添加)或流控制节点(分支)。它们具有参数输入,解释器首先对其进行递归计算,然后将结果作为输出引脚生成。

而上下文在这里的作用是由特定游戏对象(Actor)实例的记忆来扮演的。解释器机器安全地“行走”通过该图,请求数据并执行转换。

解释器还用在什么地方?

解释器模式几乎可以在任何需要执行动态指令的复杂系统中找到。以下只是商业软件中的一些示例:

  • 解释型编程语言(Python、Ruby、PHP)。它们的整个运行时基于经典模式。例如,CPython 参考实现首先将您的 .py 脚本解析为 AST,将其编译为字节码,然后一个巨大的虚拟机(计算循环)逐步解释该字节码。
  • Java 虚拟机 (JVM)。 最初,Java 代码不是编译成机器指令,而是编译成字节码。当您运行应用程序时,JVM 充当解释器(尽管使用积极的 JIT 编译,就像在 V8 中一样)。
  • 数据库和 SQL 当您在 PostgreSQL 或 MySQL 中发出 SQL 查询(SELECT * FROM users)时,数据库引擎充当解释器。它执行词法分析,构建 AST 查询树,生成执行计划,然后通过迭代表的行来逐字“解释”该计划。
  • 正则表达式 (RegEx)。任何正则表达式引擎都会在内部将字符串模式(例如 ^\d{3}-\d{2}$)解析为状态图 (NFA/DFA 自动机),然后内部解释器会通过该状态图,将每个输入字符与该图的顶点进行匹配。
  • Unity Shader Graph / 虚幻材质编辑器 – 将视觉节点解释为模块化着色器代码 (GLSL/HLSL)。
  • Blender 几何节点 – 解释数学和几何运算以按程序实时生成 3D 模型。

总计

解释器模式早已超出了“编写自己的计算器”的范围。这是最有力的行业标准。从每天在浏览器幕后执行数十亿字节代码的 JavaScript 引擎,到无需 C++ 知识即可构建复杂逻辑的游戏设计者,解释器仍然是现代 IT 开发中最重要的架构概念之一。

乌拉马卡尔

如果您使用 Ollama 并且不想每次都编写自己的 API 包装器,
ollama_call 项目显着简化了工作。

这是一个小型 Python 库,允许您使用一个函数向本地 LLM 发送请求
并立即收到响应,包括 JSON 格式。

安装

pip install ollama-call

为什么需要它

  • 使用模型的最少代码;
  • 结构化 JSON 响应以供进一步处理;
  • 方便快速制作原型和 MVP;
  • 如有需要,支持流式输出。

使用示例

from ollama_call import ollama_call

response = ollama_call(
    user_prompt="Hello, how are you?",
    format="json",
    model="gemma3:12b"
)

print(response)

当它特别有用时

  • 您在 Ollama 之上编写脚本或服务;
  • 需要可预测的响应格式;
  • 不想连接重型框架。

总计

ollama_call 是一个轻量级且清晰的包装器,用于使用 Python 中的 Ollama。
如果简单性和快速结果很重要,那么这是一个不错的选择。

GitHub
https://github.com/demensdeum/ollama_call

SFAP:现代数据采集和处理的模块化框架

在自动化和人工智能积极发展的背景下,有效收集、
清理和转换数据变得至关重要。大多数解决方案只是关闭
这个过程的各个阶段,需要复杂的集成和支持。

SFAP(Seek·Filter·Adapt·Publish)是Python的一个开源项目,
它提供了一种整体且可扩展的方法来处理数据生命周期各个阶段的数据:
从寻找来源到发布最终结果。

什么是 SFAP

SFAP 是一个围绕数据处理管道的清晰概念构建的异步框架。
每个阶段在逻辑上都是独立的,并且可以独立扩展或替换。

该项目基于责任链架构模式,它提供:

  • 管道配置灵活性;
  • 各个阶段的简单测试;
  • 高负载的可扩展性;
  • 组件之间的职责清晰分离。

管道的主要阶段

Seek – 数据搜索

在此阶段,发现数据源:网页、API、文件存储
或其他信息流。 SFAP 可以轻松连接新来源而无需更改
系统的其余部分。

Filter – 过滤

过滤旨在消除噪音:不相关的内容、重复内容、技术元素
和低质量的数据。这对于后续处理步骤至关重要。

Adapt – 适配和处理

适应阶段负责数据转换:标准化、结构化、
语义处理以及与人工智能模型(包括生成模型)的集成。

发布 – 发布

最后阶段,数据以目标格式发布:数据库、API、文件、外部服务
或内容平台。 SFAP 不限制结果的交付方式。

该项目的主要特点

  • 基于asyncio的异步架构
  • 模块化和可扩展性
  • 支持复杂的处理管道
  • 准备好与 AI/LLM 解决方案集成
  • 适合高负载系统

实际用例

  • 新闻来源的汇总和分析
  • 准备机器学习数据集
  • 自动化内容管道
  • 清理和标准化大型数据流
  • 整合来自异构源的数据

SFAP 入门

您需要开始的是:

  1. 克隆项目存储库;
  2. 安装 Python 依赖项;
  3. 定义您自己的管道步骤;
  4. 启动异步数据处理进程。

该项目很容易适应特定的业务任务,并且可以随着系统的增长而增长,
而不变成一个整体。

结论

SFAP 不仅仅是一个解析器或数据收集器,而且是一个成熟的构建框架
现代数据管道系统。适合关注的开发者和团队
可扩展、架构简洁且数据就绪。
项目源代码可在 GitHub 上获取:
https://github.com/demensdeum/SFAP

为什么我无法修复该错误?

您花费数小时编写代码,进行假设,调整条件,但错误仍然重现。听起来很熟悉吗?这种沮丧的状态通常被称为“幽灵狩猎”。该程序似乎过着自己的生活,忽略您的更正。

造成这种情况的最常见且最烦人的原因之一是在应用程序中完全错误的位置寻找错误。

“虚假症状”的陷阱

当我们看到错误时,我们的注意力就会被吸引到它“出问题”的地方。但在复杂的系统中,发生错误(崩溃或不正确的值)只是一长串事件的结束。当你试图修复结局时,你是在对抗症状,而不是疾病。

这就是流程图概念的用武之地。

它在现实中是如何运作的

当然,没必要每次都直接在纸上画(画)出流程图,但将其放在脑子里或手边作为架构指南很重要。流程图允许您将应用程序的操作可视化为结果树。

在不理解这个结构的情况下,开发者常常是在黑暗中摸索。想象一下这种情况:您在一个条件分支中编辑逻辑,而应用程序(由于一组特定参数)转到您甚至没有想到的完全不同的分支。

<块引用>
结果:您花费数小时对算法的一个部分进行“完美”代码修复,当然,这对解决算法实际失败的另一部分的问题没有任何作用。

<小时/>

击败错误的算法

要停止敲打紧闭的门,您需要改变诊断方法:

  • 在结果树中查找状态:在编写代码之前,您需要准确确定应用程序所采取的路径。逻辑在什么时候出现了错误?哪个具体状态(状态)导致了该问题?
  • 复制成功率为 80%:这通常由测试人员和自动化测试完成。如果bug“浮动”,则开发参与过程中共同寻找条件。
  • 使用尽可能多的信息:日志、操作系统版本、设备参数、连接类型(Wi-Fi/5G)甚至特定的电信运营商对于本地化都很重要。

错误时刻的“照片”

理想情况下,要修复它,您需要在重现错误时获取应用程序的完整状态。交互日志也非常重要:它们不仅显示最终点,还显示整个用户路径(失败之前执行了哪些操作)。这有助于理解如何再次重新创建类似的状态。

未来提示:如果您遇到复杂的情况,请在这部分代码中添加扩展的调试日志记录信息,以防这种情况再次发生。

<小时/>

人工智能时代“难以捉摸”的状态问题

在使用LLM(大型语言模型)的现代系统中,经典决定论(“一个输入,一个输出”)经常被违反。您可以传递完全相同的输入数据,但会得到不同的结果。

发生这种情况是由于现代生产系统的非决定论

  • GPU 并行性:GPU 浮点运算并不总是关联的。由于线程并行执行,数字相加的顺序可能会略有变化,这可能会影响结果。
  • GPU 温度和限制:执行速度和负载分布可能取决于硬件的物理状态。在巨大的模型中,这些微观差异会累积,并可能导致在输出时选择不同的标记。
  • 动态批处理:在云端,您的请求会与其他请求合并。不同的批量大小会改变内核中的计算数学。

在这样的条件下,再现“同样的状态”几乎是不可能的。只有统计测试方法才能拯救您。

<小时/>

当逻辑失败时:内存问题

如果您使用“不安全”语言(CC++),该错误可能会因内存损坏而发生。

这些是最严重的情况:一个模块中的错误可能会“覆盖”另一个模块中的数据。这会导致完全无法解释的孤立故障,无法使用正常的应用程序逻辑进行追踪。

如何在架构层面保护自己?

为了避免这种“神秘”的错误,您应该使用现代方法:

  • 多线程编程模式:清晰的同步消除了竞争条件。
  • 线程安全语言:编译时保证内存安全的工具:
    • Rust:所有权系统消除了内存错误。
    • Swift 6 并发:强大的数据隔离检查。
    • Erlang:通过参与者模型完成进程隔离。

摘要

修复错误并不是要编写新代码,而是要了解旧代码是如何工作的。请记住:您可能会浪费时间编辑管理层根本不接触的分支。记录系统状态,考虑AI非确定性因素,选择安全工具。

实际上没有福尔马林的框图

框图是一种视觉工具,有助于将复杂的算法转变为可理解且结构化的动作序列。从编程到业务流程管理,它们是一种可视化,分析和优化最复杂系统的通用语言。

想象一下一张地图,而不是道路是逻辑,而不是城市 – 行动。这是一个框图 – 在最令人困惑的过程中导航的必不可少的工具。

示例1:简化的游戏启动方案
要了解工作原理,让我们提出一个简单的游戏发布计划。

当一切都没有失败时,该方案显示了完美的脚本。但是在现实生活中,一切都更加复杂。

示例2:扩展的方案用于使用数据加载开始游戏
现代游戏通常需要互联网连接才能下载用户数据,保存或设置。让我们将这些步骤添加到我们的计划中。

该方案已经更现实,但是如果出现问题,会发生什么?

怎么样:一款“破产”的游戏,失去了互联网

在项目开始时,开发人员无法考虑所有可能的情况。例如,他们专注于游戏的主要逻辑,并且不认为如果玩家具有互联网连接会发生什么。

在这种情况下,其代码的框图看起来像这样:

在这种情况下,该游戏在等待数据的阶段冻结了,而不是由于缺乏连接而无法收到的数据,而不是发出错误或关闭。这导致了“黑屏”并冻结了应用程序。

如何变成:用户投诉纠正

在许多用户对徘徊的投诉之后,开发人员团队意识到我们需要纠正错误。他们通过添加错误处理单元来更改代码,该单元允许应用程序正确响应缺乏连接。

这就是校正的框图的外观,其中两个方案都被考虑:

借助这种方法,游戏现在正确地通知了用户有关问题的信息,在某些情况下,它甚至可以进入离线模式,从而使您可以继续游戏。这是一个很好的例子,说明了为什么框图如此重要:他们使开发人员不仅考虑了理想的执行方式,而且还考虑了所有可能的失败,从而使最终产品更加稳定和可靠。

不确定的行为

悬挂和错误只是该程序不可预测行为的一个例子。在编程中,存在一个不确定行为(不确定的行为)的概念 – 这是语言标准无法描述程序在某种情况下应如何行为的情况。

这可能会导致任何事情:从撤回的随机“垃圾”到程序的失败甚至严重的安全漏洞。例如,使用内存时,通常会发生不确定的行为。

语言C的一个示例:

想象一下,开发人员将线路复制到缓冲区中,但忘了添加到零符号(`\ 0`)的端,该符号标记了线路的末端。

这就是代码的样子:

#include 

int main() {
char buffer[5];
char* my_string = "hello";

memcpy(buffer, my_string, 5);

printf("%s\n", buffer);
return 0;
}

预期结果:“你好”
真正的结果是不可预测的。

为什么会发生这种情况?使用指定符的“ printf”函数期望该行以零符号结束。如果他不是,她将继续阅读突出显示的缓冲区之外的记忆。

这是此过程的框图,并具有两个可能的结果:

这是一个明显的例子,说明了框图如此重要的原因:它们使开发人员不仅考虑了理想的执行方式,而且还考虑了所有可能的失败,包括如此低级的问题,使最终产品更加稳定和可靠。

Pixel Perfect:在声明性时代的神话还是现实?

在界面开发的世界中,有一个共同的概念 – “小屋中的像素完美” 。它意味着设计机对最小像素的最准确再现。很长一段时间以来,它一直是黄金标准,尤其是在经典的网页设计时代。但是,随着声明性英里的到来和各种设备的快速增长,“ Pixel Perfect”的原理变得越来越短暂。让我们尝试找出原因。

帝国wysiwyg vs.声明守则:有什么区别?

传统上,许多接口,尤其是桌面,是使用命令式方法或编辑器的Wysiwyg(您看到的)创建的。在这样的工具中,设计师或开发人员直接用元素来操纵,将它们放在画布上,以准确的像素。它类似于使用图形编辑器 – 您会看到元素的外观,并且肯定可以将其定位。在这种情况下,“ Pixel Perfect”的成就是一个非常真实的目标。

但是,现代发展越来越基于声明性里程。这意味着您不告诉计算机“在此处放这个按钮”,而是要描述您想要获得的内容。例如,您没有指示元素的特定坐标,而是描述其属性:“此按钮应为红色,从各个方面有16px的凹痕,并且位于容器的中心。”像React,Vue,Swiftui或JetPack一样,只需使用此原理即可。

为什么“ Pixel Perfect”不适合许多设备的声明英里

想象一下,您创建了一个在iPhone 15 Pro Max,Samsung Galaxy Fold,iPad Pro和4K分辨率上看起来同样不错的应用程序。这些设备中的每一个都有不同的屏幕分辨率,像素密度,派对和物理大小。

当您使用声明性方法时,系统本身会考虑其所有参数在特定设备上显示您所描述的接口。您设置规则和依赖性,而不是苛刻的坐标。

* 适应性和响应能力:声明英里的主要目标是创建自适应和响应式接口。这意味着您的接口应自动适应屏幕的大小和方向,而不会破坏和保持可读性。如果我们试图在每个设备上“像素完美”,则必须为同一界面创建无数的选项,这将完全阐明声明方法的优势。
* 像素密度(DPI/PPI):设备具有不同的像素密度。如果您不考虑缩放率,则具有高密度的设备上的100个“虚拟”像素的尺寸将比低密度设备上的元素小得多。声明的框架是通过物理像素抽象的,与逻辑单元一起工作。
* 动态内容:现代应用程序中的内容通常是动态的 – 其体积和结构可能会有所不同。如果我们用力地将像素破烂,文本或图像的任何更改都会导致布局的“崩溃”。
* 各种平台:除了各种设备之外,还有不同的操作系统(iOS,Android,Web,桌面)。每个平台都有自己的设计,标准控件和字体。尝试在所有平台上制作绝对相同的Pixel完美界面的尝试将导致不自然的类型和差的用户体验。

旧方法没有消失,而是进化了

重要的是要了解,接口的方法不是“命令”和“声明性”之间的二元选择。从历史上看,对于每个平台,都有自己的工具和方法来创建接口。

* 本机接口文件: iOS是xib/故事板,用于android-xml标记文件。这些文件是一个完美的Wysiwyg布局,然后像编辑器一样在广播中显示。这种方法并没有在任何地方消失,它继续发展,并与现代宣言框架集成。例如,苹果和Jetpack的Swiftui在Android中构成了纯粹的声明代码的道路,但同时保留了使用经典布局的机会。
* 混合解决方案:经常在实际项目中,使用多种方法。例如,可以声明地实现应用程序的基本结构,并且对于需要准确的元素定位,可以使用较低的,命令式方法或开发的本机组件,以考虑到平台的细节。

从整体到适应性:设备的演变如何形成声明英里

在过去的几十年中,数字界面的世界发生了巨大变化。从具有固定许可证的固定计算机来看,我们来到了各种用户设备的指数增长的时代。今天,我们的应用程序应该同样擅长:

* 智能手机的所有形式和屏幕尺寸的大小。
* 平板电脑具有独特的方向模式和一个分开的屏幕。
* 笔记本电脑和台式机具有各种监视器的许可。
* 电视和媒体中心,远程控制。值得注意的是,即使对于电视,它的备注也很简单,因为 Apple TV Remote 具有最少的按钮,反之亦然,但具有许多功能,对接口的现代要求也不应需要对这些输入功能进行特定的适应。该接口应“好像自身”工作,而不必对与特定遥控器进行交互的“如何”相互作用。
* 智能手表和可穿戴设备带有简约屏幕。
* 虚拟现实头盔(VR),需要一种全新的空间接口方法。
* 增强现实设备(AR),应用有关现实世界的信息。
* 汽车信息和娱乐系统
*甚至是家用电器:从带有感官屏幕和带有交互式显示器的洗衣机到智能烤箱和智能房屋系统的冰箱。

这些设备中的每一个都有其独特的功能:物理维度,派对比率,像素密度,输入方法(触摸屏,鼠标,控制器,手势,声乐命令),以及用户环境的微妙之处。例如,VR Shlesh需要深层沉浸,并且在旅途中进行了智能手机的快速和直观的工作,而冰箱界面应像快速导航一样简单而大。

经典方法:支撑各个接口的负担

在台式机和第一个移动设备的主导时代,通常的业务是对单个接口文件的创建和支持,甚至是每个平台的完全独立的接口代码。

* ios 下的开发通常需要在Xcode中使用故事板或XIB文件,在Objective-C或Swift上编写代码。
*对于 android ,创建了XML标记文件和Java或Kotlin上的代码。
* Web接口打开了HTML/CSS/JavaScript。
*对于 c ++应用程序在各种桌面平台上,使用了它们的特定框架和工具:
*在 Windows 中,这些是MFC(Microsoft Foundation类),带有手动绘制元素的Win32 API或使用资源文件用于对话框Windows和Control Elements。
*可可(Objective-C/Swift)或用于直接控制图形界面的旧碳API。
*在 linux/unix样系统中,经常使用诸如GTK+或QT之类的库,这些库通常通过类似XML的标记文件(例如,QT Designer中的.UI文件)或直接的软件创建元素来提供其用于创建接口的小部件和机制。

这种方法可确保对每个平台的最大控制,从而使您可以考虑其所有特定功能和本机元素。但是,他有一个巨大的缺点:重复的努力和支持的巨大成本。设计或功能的最小变化需要引入几个独立代码库的权利。对于开发人员团队来说,这变成了真正的噩梦,减慢了新功能的输出并增加了错误的可能性。

声明英里:多样性的单一语言

正是为了回应这种快速的并发症,声明性的里程似乎是主要的范式。像 React,Vue,Swiftui,JetPack构成之类的Framws不仅是编写代码的新方法,而且是思维的根本转变。

声明性方法的主要思想:我们没有说“如何”绘制每个元素(势在必行),而是描述了“我们想看到的(声明性)。我们设置了接口的属性和条件,框架决定如何最好地在特定设备上显示它。

由于以下主要优势,这可能是可能的:

1。从平台的详细信息中抽象:声明的fraimvorki专门设计为忘记每个平台的低级别详细信息。开发人员使用单个传输的代码以更高的抽象级别描述了组件及其关系。
2。自动适应性和响应能力: freimvorki负责自动缩放,将元素的布局和适应性更改为不同尺寸的屏幕,像素密度和输入方法。这是通过使用灵活布局系统(例如Flexbox或Grid)以及类似于“逻辑像素”或“ DP”的概念来实现的。
3。用户体验的一致性:尽管存在外部差异,但声明的方法使您可以在整个设备家族中维护行为和交互的单一逻辑。这简化了测试过程,并提供了更可预测的用户体验。
4。开发和降低成本的加速:具有能够在许多平台上工作的相同代码,大大减少了发展和支持的时间和成本。团队可以专注于功能和设计,而不是重复重写相同的接口。
5。未来的准备就绪:从当前设备的细节中抽象的能力使声明代码更具对新型设备和形式因素的出现的抵抗力。可以更新Freimvorki以支持新技术,并且您已经编写的代码将获得此支持相对无缝。

结论

声明的英里不仅是一种时尚趋势,而且是由用户设备快速开发(包括物联网(IoT)和智能家用电器)引起的必要进化步骤。它允许开发人员和设计师创建复杂,自适应和统一的接口,而不会淹没每个平台的无尽特定实现。从对每个像素的命令控制到所需状态的声明性描述的过渡是一种认识,即在未来的世界中,无论显示哪种屏幕,都应灵活,传递和直观

程序员,设计师和用户需要学习如何生活在这个新世界。 Pixter Perfect的额外细节,设计为特定设备或分辨率,导致不必要的时间成本进行开发和支持。此外,这种刺激性的布局可能根本无法在具有非标准界面的设备上使用,例如有限的输入电视,VR和AR Shifts以及未来的其他设备,我们仍然不知道今天。灵活性和适应性 – 这些是创建现代世界中成功接口的关键。

为什么程序员即使在神经网络上也无能为力

如今,神经网络无处不在。程序员使用它们来生成代码,解释其他解决方案,自动化日常任务,甚至从头开始创建整个应用程序。看来这应该导致效率提高,降低错误和发展的加速。但是现实要平淡得多:许多人仍然没有成功。神经网络无法解决关键问题 – 它们仅阐明了无知的深度。

完全依赖LLM而不是理解

主要原因是,许多开发人员完全依靠LLM,而无视对他们使用的工具的深入了解。而不是研究文档 – 聊天请求。而不是分析错误的原因 – 复制决定。而不是建筑解决方案 – 根据描述产生组件。所有这些都可以在表面上起作用,但是一旦出现了非标准任务,就可以与真实的项目集成或需要进行微调,一切都在崩溃。

缺乏上下文和过时的实践

神经网络生成代码概括。他们没有考虑到您的平台,库的版本,环境限制或项目的架构解决方案的细节。生成的东西通常看起来合理,但与真实的,受支持的代码无关。即使简单的建议属于框架的过时版本或长期以来一直被认为是无效或不安全的使用方法,它们也可能行不通。模型不了解上下文 – 他们依赖统计信息。这意味着在开放代码中流行的错误和反植物将一次又一次地复制。

冗余,效率低下和缺乏分析

生成的AI代码通常是多余的。它包括不必要的依赖性,重复的逻辑,不必要地添加了抽象。事实证明,一个难以支撑的无效结构。这在移动开发中尤其重要,在移动开发中,帮派的大小,响应时间和能耗至关重要。

神经网络不进行分析,不考虑CPU和GPU的限制,也不关心内存的泄漏。它没有分析代码在实践中的有效性。优化仍然是手工制作的,需要分析和检查。没有它,即使从结构的角度来看,应用程序就会变得缓慢,不稳定和资源密集型。

脆弱性和对安全性的威胁

不要忘记安全。当成功使用LLM成功地破解项目的项目部分或完全创建时,已经有已知的情况。原因是典型的:使用不安全功能,缺乏输入数据的验证,授权逻辑中的错误,通过外部依赖性泄漏。神经网络仅仅是因为它是在开放存储库中找到的,才能生成脆弱的代码。没有安全专家的参与和完整的修订,此类错误很容易成为攻击的投入点。

法律是帕累托,缺陷的本质

Pareto Law与神经网络明确合作:由于20%的努力,结果的80%是实现的。该模型可以生成大量代码,创建项目的基础,传播结构,安排类型,连接模块。但是,所有这些都可以过时,与当前版本的库或框架不兼容,并且需要大量的手动修订。自动化在这里而不是作为草稿,需要检查,处理和适应项目的特定现实。

谨慎乐观

然而,未来看起来令人鼓舞。不断更新培训数据集,与当前文档的集成,自动化体系结构检查,符合设计和安全模式 – 所有这些都可以从根本上改变游戏规则。也许几年后,我们可以真正依靠LLM作为真正的技术公司作者来更快,更安全,更有效地编写代码。但是现在 – a-必须手动检查,重写和修改很多。

神经网络是一个强大的工具。但是,为了使他为您工作,而不是反对您,您需要一个基础,批判性思维和愿意随时控制。

Vibe核心技巧:为什么LLM仍然无法与固体,干燥和干净一起使用

随着大型语言模型(LLM)的开发,例如ChatGpt,越来越多的开发人员使用它们来生成代码,设计体系结构和加速集成。但是,通过实用的应用,它变得明显:建筑的经典原理 – 固体,干燥,清洁 – 与LLM Codgendation的特殊性相同。

这并不意味着原理已经过时了 – 相反,它们与手动开发完美合作。但是使用LLM,必须适应该方法。

为什么LLM无法应对建筑原理

封装

不合时间需要了解系统部分之间的界限,对开发人员意图的知识以及严格的访问限制。 LLM通常会简化结构,无缘无故地公开字段或重复实现。这使得代码更容易受到错误的影响,并违反了架构边界。

摘要和接口

设计模式,例如抽象工厂或策略,需要对系统的整体视图并了解其动态。模型可以在没有明确目的的情况下创建一个界面,而无需确保其实现或违反层之间的连接。结果是过量或非功能架构。

干(托管重复自己)

LLM不要寻求最大程度地减少重复代码 – 相反,与制作一般逻辑相比,他们更容易复制块。尽管他们可以根据要求提供重构,但默认模型倾向于生成“自给自足的”片段,即使它导致冗余。

干净的体系结构

清洁意味着严格的层次结构,远离框架,定向依赖性以及层之间的最小连接性。这样的结构的产生需要对系统的全球理解,而llm则以单词概率而不是建筑完整性的含义来起作用。因此,该代码混合在一起,违反了依赖的方向和简化的划分。

使用LLM 时效果更好

湿而不是干
在与LLM合作时,湿(写两次)方法更为实用。代码的重复不需要保留模型中的上下文,这意味着结果是可以预测的,并且更易于正确纠正。它还减少了非明显连接和错误的可能性。

此外,重复有助于补偿模型的短记忆:如果在多个地方发现了某些逻辑片段,LLM更有可能将其考虑到进一步的生成中。这简化了伴奏,并增加了对“忘记”的抵制。

简单结构而不是封装

避免复杂的封装并依靠代码部分之间的数据直接传输,您可以大大简化生成和调试。通过快速的迭代开发或MVP的创建,尤其如此。

简化体系结构

该项目的简单,平坦的结构具有最少的依赖性和抽象,从而在生成过程中产生了更稳定的结果。该模型可以更轻松地调整这样的代码,并且较少违反了组件之间的预期连接。

SDK集成 – 手动可靠

大多数语言模型接受过过时的文档版本培训。因此,当生成用于安装SDK的指令时,通常会出现错误:过时的命令,无关参数或链接到无法访问的资源。实践显示:最好使用官方文档和手动调整,而LLM则是辅助角色 – 例如,生成模板代码或配置的改编。

为什么原理仍然有效 – 但是手动开发

重要的是要了解,固体,干燥和清洁的困难与LLM有关的CODHENELISERALION。当开发人员手动编写代码时,这些原则继续证明其价值:它们降低了连接性,简化支持,提高项目的可读性和灵活性。

这是由于人类思维容易概括的事实。我们正在寻找模式,将重复的逻辑带入单个实体,创建模式。可能,这种行为具有进化的根源:减少信息量可节省认知资源。

LLM的行为不同:他们没有从数据量中体验到负载,也不会努力储蓄。相反,与构建和维护复杂的抽象相比,他们更容易使用重复的,分散的信息。这就是为什么他们更容易在没有封装的情况下应对代码,重复结构和最小的架构严重性。

结论

大型语言模型是开发中的有用工具,尤其是在早期或创建辅助代码时。但是,重要的是要适应它们的方法:为了简化体系结构,限制抽象,避免复杂的依赖关系,并且在配置SDK时不依赖它们。

固体,干燥和清洁的原则仍然是相关的,但它们在人的手中赋予了最佳效果。使用LLM时,使用简化的实用风格是合理的,该样式使您可以获取可靠且易于理解的代码,该代码易于手动完成。 LLM忘记的地方 – 复制代码可以帮助他记住。

将 Surreal Engine C++ 移植到 WebAssembly

在这篇文章中,我将描述如何将 Surreal Engine 游戏引擎移植到 WebAssembly。

超现实引擎–一个游戏引擎,实现了Unreal Engine 1的大部分功能,著名的游戏都使用这个引擎–虚幻竞技场 99、虚幻、杀出重围、不朽。它指的是主要在单线程执行环境中工作的经典引擎。

最初,我有一个想法,那就是承担一个我无法在任何合理时间内完成的项目,从而向我的 Twitch 粉丝表明,有些项目甚至是我也无法完成。在我的第一次直播中,我突然意识到使用 Emscripten 将 Surreal Engine C++ 移植到 WebAssembly 的任务是可行的。

Surreal Engine Emscripten Demo

一个月后,我可以在 WebAssembly 上演示我的前叉和引擎组装:
https://demensdeum.com/demos/SurrealEngine/

与原始版本一样,控制是使用键盘箭头进行的。接下来,我计划将其调整为移动控制 (tachi),添加正确的光照和 Unreal Tournament 99 渲染的其他图形功能。

从哪里开始?

我想说的第一件事是,任何项目都可以使用 Emscripten 从 C++ 移植到 WebAssembly,唯一的问题是功能有多完整。选择一个库端口已经可用于 Emscripten 的项目;对于 Surreal Engine,你非常幸运,因为该引擎使用 SDL 2、OpenAL 库。它们都被移植到 Emscripten。不过,Vulkan 用作图形 API,目前无法用于 HTML5,实现 WebGPU 的工作正在进行中,但也处于草案阶段,也未知从 Vulkan 到 WebGPU 的进一步移植有多简单,完全标准化后。因此,我必须为 Surreal Engine 编写自己的基本 OpenGL-ES / WebGL 渲染器。

构建项目

在 Surreal Engine 中构建系统 – CMake,这也简化了移植,因为Emscripten 提供其原生构建器“ emcmake,emmake。
Surreal Engine 移植基于我最新的 WebGL/OpenGL ES 和 C++ 游戏代码,称为 Death-Mask,因此开发更加简单,我随身携带了所有必要的构建标志和代码示例。

CMakeLists.txt 中最重要的一点是 Emscripten 的构建标志,下面是项目文件中的示例:


-s MAX_WEBGL_VERSION=2 \

-s EXCEPTION_DEBUG \

-fexceptions \

--preload-file UnrealTournament/ \

--preload-file SurrealEngine.pk3 \

--bind \

--use-preload-plugins \

-Wall \

-Wextra \

-Werror=return-type \

-s USE_SDL=2 \

-s ASSERTIONS=1 \

-w \

-g4 \

-s DISABLE_EXCEPTION_CATCHING=0 \

-O3 \

--no-heap-copy \

-s ALLOW_MEMORY_GROWTH=1 \

-s EXIT_RUNTIME=1")

构建脚本本身:


emmake make -j 16

cp SurrealEngine.data /srv/http/SurrealEngine/SurrealEngine.data

cp SurrealEngine.js /srv/http/SurrealEngine/SurrealEngine.js

cp SurrealEngine.wasm /srv/http/SurrealEngine/SurrealEngine.wasm

cp ../buildScripts/Emscripten/index.html /srv/http/SurrealEngine/index.html

cp ../buildScripts/Emscripten/background.png /srv/http/SurrealEngine/background.png

接下来我们将准备索引.html ,其中包括项目文件系统预加载器。为了上传到网络,我使用了 Unreal Tournament Demo 版本 338。正如您从 CMake 文件中看到的,解压后的游戏文件夹已添加到构建目录中,并作为 Emscripten 的预加载文件进行链接。

主要代码更改

那就需要改变游戏的游戏循环,不能无限循环,这样会导致浏览器卡住,而是需要使用emscripten_set_main_loop,我在2017年的笔记中写过这个功能< a href="https://demensdeum.com/blog/ru/2017/03/29/porting-sdl-c-game-to-html5-emscripten/" rel="noopener" target="_blank">将 SDL C++ 游戏移植到 HTML5 (Emscripten)”
我们将退出 while 循环的代码更改为 if,然后在全局范围内显示包含游戏循环的游戏引擎的主类,并编写一个全局函数,该函数将从全局对象中调用游戏循环步骤:


#include <emscripten.h>

Engine *EMSCRIPTEN_GLOBAL_GAME_ENGINE = nullptr;

void emscripten_game_loop_step() {

	EMSCRIPTEN_GLOBAL_GAME_ENGINE->Run();

}

#endif

之后,您需要确保应用程序中没有后台线程;如果有,则准备将其重写为单线程执行,或者使用 Emscripten 中的 phtread 库。
Surreal Engine中的后台线程用于播放音乐,数据来自主引擎线程,有关当前曲目、是否需要播放音乐或是否缺少音乐,然后后台线程通过互斥体接收新状态并开始播放新音乐,或暂停。背景流还用于在播放期间缓冲音乐。
我尝试使用 pthread 为 Emscripten 构建 Surreal Engine,但没有成功,因为 SDL2 和 OpenAL 端口是在没有 pthread 支持的情况下构建的,而且我不想为了音乐而重建它们。因此,我使用循环将背景音乐流的功能转移到单线程执行。通过从 C++ 代码中删除 pthread 调用,我将缓冲和音乐播放移至主线程,这样就不会出现延迟,我将缓冲区增加了几秒钟。

接下来我会描述图形和声音的具体实现。

不支持 Vulkan!

是的,HTML5 不支持 Vulkan,尽管所有营销手册都将跨平台和广泛的平台支持作为 Vulkan 的主要优势。因此,我必须为简化的 OpenGL 类型编写自己的基本图形渲染器 – ES,它用在移动设备上,有时它不包含现代OpenGL的时尚功能,但它很好地移植到WebGL,这正是Emscripten所实现的。写了基本的tile渲染,bsp渲染,最简单的GUI显示,渲染模型+贴图,两周就完成了。这也许是该项目中最困难的部分。实现 Surreal Engine 渲染的全部功能还有很多工作要做,因此欢迎读者以代码和拉取请求的形式提供任何帮助。

支持 OpenAL!

幸运的是,Surreal Engine 使用 OpenAL 进行音频输出。在 OpenAL 中编写了一个简单的 hello world 并使用 Emscripten 在 WebAssembly 中将其组装后,我清楚地意识到一切是多么简单,然后我开始移植声音。
经过几个小时的调试,很明显 Emscripten 的 OpenAL 实现存在几个错误,例如,在初始化读取单通道数时,该方法返回无限数,并且在尝试初始化无限大小的向量后,C++崩溃并出现异常向量::length_error。
我们通过将单声道数量硬编码为 2048 来解决这个问题:


		alcGetIntegerv(alDevice, ALC_STEREO_SOURCES, 1, &stereoSources);



#if __EMSCRIPTEN__

		monoSources = 2048; // for some reason Emscripten's OpenAL gives infinite monoSources count, bug?

#endif



有网络吗?

Surreal Engine目前不支持在线游戏,支持与机器人一起玩,但我们需要有人为这些机器人编写AI。理论上,您可以使用Websockets在WebAssembly/Emscripten上实现网络游戏。

结论

总之,我想说,由于使用了 Emscripten 移植的库,以及我过去在 WebAssembly 中使用 C++ 实现游戏的经验,Surreal Engine 的移植非常顺利在 Emscripten 上。以下是有关该主题的知识来源和存储库的链接。
M-M-M-杀死怪物!

此外,如果您想帮助该项目,最好使用 WebGL/OpenGL ES 渲染代码,请在 Telegram 中给我写信:
https://t.me/demenscave

链接

https://demensdeum.com/demos/Sur​​realEngine/
https://github.com/demensdeum/SurrealEngine-Emscripten

https://github.com/dpjudas/SurrealEngine

Flash Forever – Interceptor 2021

Recently, it turned out that Adobe Flash works quite stably under Wine. During a 4-hour stream, I made the game Interceptor 2021, which is a sequel to the game Interceptor 2020, written for the ZX Spectrum.

For those who are not in the know – the Flash technology provided interactivity on the web from 2000 to around 2015. Its shutdown was prompted by an open letter from Steve Jobs, in which he wrote that Flash should be consigned to history because it lagged on the iPhone. Since then, JS has become even more sluggish than Flash, and Flash itself has been wrapped in JS, making it possible to run it on anything thanks to the Ruffle player.

You can play it here:
https://demensdeum.com/demos/Interceptor2021

Video:
https://www.youtube.com/watch?v=-3b5PkBvHQk

Source code:
https://github.com/demensdeum/Interceptor-2021

增删改查存储库

在这篇笔记中,我将描述著名的经典 CRUD 模式的基本原理,以及 Swift 语言的实现。 Swift 是一种开放的跨平台语言,适用于 Windows、Linux、macOS、iOS、Android。

有许多解决方案可以抽象数据存储和应用程序逻辑。其中一种解决方案是 CRUD 方法,它是 C# 的缩写。创建,R-读取,U –更新,D–删除。
通常,此原则是通过实现数据库接口来实现的,其中使用唯一标识符(例如 id)来操作元素。为每个字母创建一个界面 CRUD –创建(对象,id),读取(id),更新(对象,id),删除(对象,id)。
如果一个对象在其内部包含一个 id,那么 id 参数可以从方法(Create、Update、Delete)中省略,因为整个对象及其字段 – 都被传递到那里。 ID。但对于– Read需要id,因为我们想通过id从数据库中获取一个对象。

所有名字均为虚构

让我们假设一个假设的 AssistantAI 应用程序是使用免费的 EtherRelm 数据库 SDK 创建的,集成很简单,API 非常方便,因此该应用程序被发布到市场。
突然,SDK 开发者 EtherRelm 决定付费,将价格定为每个应用程序用户每年 100 美元。
什么?是的! AssistantAI的开发者现在应该做什么,因为他们已经有100万活跃用户了!支付一亿美元?
相反,会做出评估将应用程序转移到平台本机 RootData 数据库的决定;根据程序员的说法,这样的转移将需要大约六个月的时间,这还不考虑应用程序中新功能的实现。经过深思熟虑,决定从市场上删除该应用程序,并在另一个带有内置BueMS数据库的免费跨平台框架上重写它,这将解决付费数据库的问题+将简化在其他平台上的开发。
一年后,该应用程序在 BueMS 中被重写,但突然框架开发人员决定对其进行付费。事实证明,团队已经两次陷入同一个陷阱,第二次是否能摆脱困境就完全是另外一回事了。

抽象来救援

如果开发人员在应用程序中使用接口的抽象,这些问题本来可以避免。 OOP 的三大支柱多态、封装、继承,不久前他们还添加了另一个“多态”、“封装”、“继承”。抽象。
数据抽象允许您用最少的细节以一般术语描述想法和模型,同时足够准确以实现用于解决业务问题的特定实现。
如何抽象数据库操作,使得应用逻辑不依赖于它?我们使用 CRUD 方法!

简化的 UML CRUD 图如下所示:

虚构的 EtherRelm 数据库示例:

真实 SQLite 数据库的示例:

正如您已经注意到的,当您切换数据库时,只有与应用程序交互的 CRUD 接口发生变化。 CRUD 是 GoF 模式的实现适配器,因为使用它,我们可以使应用程序接口适应任何数据库并组合不兼容的接口。
文字都是空的,给我看看代码
为了用编程语言实现抽象,使用接口/协议/抽象类。所有这些都是同一个顺序的现象,但是,在面试中你可能会被要求说出它们之间的区别,我个人认为这没有多大意义,因为使用的唯一目的是实现数据抽象,否则就是测试受访者的记忆力。
CRUD通常是在Repository模式的框架内实现的,但是,存储库可能会也可能不会实现CRUD接口,这完全取决于开发人员的聪明才智。

考虑一下 Book 结构存储库的相当典型的 Swift 代码,直接使用 UserDefaults 数据库:


struct Book: Codable {
	let title: String
	let author: String
}

class BookRepository {
	func save(book: Book) {
    		let record = try! JSONEncoder().encode(book)
    		UserDefaults.standard.set(record, forKey: book.title)
	}
    
	func get(bookWithTitle title: String) -> Book? {
    		guard let data = UserDefaults.standard.data(forKey: title) else { return nil }
    		let book = try! JSONDecoder().decode(Book.self, from: data)
    		return book
	}
    
	func delete(book: Book) {
    		UserDefaults.standard.removeObject(forKey: book.title)
	}
}

let book = Book(title: "Fear and Loathing in COBOL", author: "Sir Edsger ZX Spectrum")
let repository = BookRepository()
repository.save(book: book)
print(repository.get(bookWithTitle: book.title)!)
repository.delete(book: book)
guard repository.get(bookWithTitle: book.title) == nil else {
	print("Error: can't delete Book from repository!")
	exit(1)
}

上面的代码看似简单,但我们来统计一下违反 DRY(Do not Repeat Yourself)原则的次数以及代码的连贯性:
与 UserDefaults 数据库的连接
与 JSON 编码器和解码器的连接 – JSONEncoder、JSONDecoder
与 Book 结构连接,但我们需要一个抽象存储库,以免为我们将存储在数据库中的每个结构创建一个存储库类(DRY 违规)

我经常看到这种CRUD存储库代码,它可以使用,但是高耦合和重复的代码导致随着时间的推移它的支持会变得非常复杂。当尝试切换到另一个数据库时,或者当更改在应用程序中创建的所有存储库中使用数据库的内部逻辑时,这一点尤其明显。
不要重复代码,而是保持高耦合度——让我们为CRUD存储库编写一个协议,从而抽象数据库接口和应用程序业务逻辑,尊重DRY,实现低耦合:

    typealias Item = Codable
    typealias ItemIdentifier = String
    
    func create<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws
    func read<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier) async throws -> T
    func update<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws
    func delete(id: CRUDRepository.ItemIdentifier) async throws
}

CRUDRepository 协议描述了用于进一步实现特定 CRUD 存储库的接口和关联数据类型。

接下来我们将为UserDefaults数据库编写一个具体的实现:

    private typealias RecordIdentifier = String
    
    let tableName: String
    let dataTransformer: DataTransformer
    
    init(
   	 tableName: String = "",
   	 dataTransformer: DataTransformer = JSONDataTransformer()
    ) {
   	 self.tableName = tableName
   	 self.dataTransformer = dataTransformer
    }
    
    private func key(id: CRUDRepository.ItemIdentifier) -> RecordIdentifier {
   	 "database_\(tableName)_item_\(id)"
    }
   	 
    private func isExists(id: CRUDRepository.ItemIdentifier) async throws -> Bool {
   	 UserDefaults.standard.data(forKey: key(id: id)) != nil
    }
    
    func create<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws {
   	 let data = try await dataTransformer.encode(item)
   	 UserDefaults.standard.set(data, forKey: key(id: id))
   	 UserDefaults.standard.synchronize()
    }
    
    func read<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier) async throws -> T {
   	 guard let data = UserDefaults.standard.data(forKey: key(id: id)) else {
   		 throw CRUDRepositoryError.recordNotFound(id: id)
   	 }
   	 let item: T = try await dataTransformer.decode(data: data)
   	 return item
    }
    
    func update<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws {
   	 guard try await isExists(id: id) else {
   		 throw CRUDRepositoryError.recordNotFound(id: id)
   	 }
   	 let data = try await dataTransformer.encode(item)
   	 UserDefaults.standard.set(data, forKey: key(id: id))
   	 UserDefaults.standard.synchronize()
    }
    
    func delete(id: CRUDRepository.ItemIdentifier) async throws {
   	 guard try await isExists(id: id) else {
   		 throw CRUDRepositoryError.recordNotFound(id: id)
   	 }
   	 UserDefaults.standard.removeObject(forKey: key(id: id))
   	 UserDefaults.standard.synchronize()
    }
}

代码看起来很长,但包含了包含松散耦合的 CRUD 存储库的完整具体实现,详细信息如下。
添加了类型别名以用于代码的自文档化。
弱耦合和强耦合
与特定结构 (struct) 的分离是使用通用 T 实现的,而通用 T 又必须实现可编码协议。 Codable 允许您使用实现 TopLevelEncoder 和 TopLevelDecoder 协议的类(例如 JSONEncoder 和 JSONDecoder)来转换结构,当使用基本类型(Int、String、Float 等)时,无需编写额外的代码来转换结构。

使用 DataTransformer 协议中的抽象来实现与特定编码器和解码器的解耦:

	func encode<T: Encodable>(_ object: T) async throws -> Data
	func decode<T: Decodable>(data: Data) async throws -> T
}

使用数据转换器的实现,我们实现了编码器和解码器接口的抽象,实现松散耦合以确保处理不同类型的数据格式。

以下是特定DataTransformer的代码,即JSON:

	func encode<T>(_ object: T) async throws -> Data where T : Encodable {
    		let data = try JSONEncoder().encode(object)
    		return data
	}
    
	func decode<T>(data: Data) async throws -> T where T : Decodable {
    		let item: T = try JSONDecoder().decode(T.self, from: data)
    		return item
	}
}

这可能吗?

发生了什么变化?现在,初始化一个特定的存储库就足以与任何实现 Codable 协议的结构一起使用,从而消除了重复代码和实现应用程序松散耦合的需要。

具有特定存储库的客户端 CRUD 示例,UserDefaults 是数据库、JSON 数据格式、客户端结构,也是写入和读取数组的示例:


print("One item access example")

do {
	let clientRecordIdentifier = "client"
	let clientOne = Client(name: "Chill Client")
	let repository = UserDefaultsRepository(
    	tableName: "Clients Database",
    	dataTransformer: JSONDataTransformer()
	)
	try await repository.create(id: clientRecordIdentifier, item: clientOne)
	var clientRecord: Client = try await repository.read(id: clientRecordIdentifier)
	print("Client Name: \(clientRecord.name)")
	clientRecord.name = "Busy Client"
	try await repository.update(id: clientRecordIdentifier, item: clientRecord)
	let updatedClient: Client = try await repository.read(id: clientRecordIdentifier)
	print("Updated Client Name: \(updatedClient.name)")
	try await repository.delete(id: clientRecordIdentifier)
	let removedClientRecord: Client = try await repository.read(id: clientRecordIdentifier)
	print(removedClientRecord)
}
catch {
	print(error.localizedDescription)
}

print("Array access example")

let clientArrayRecordIdentifier = "clientArray"
let clientOne = Client(name: "Chill Client")
let repository = UserDefaultsRepository(
	tableName: "Clients Database",
	dataTransformer: JSONDataTransformer()
)
let array = [clientOne]
try await repository.create(id: clientArrayRecordIdentifier, item: array)
let savedArray: [Client] = try await repository.read(id: clientArrayRecordIdentifier)
print(savedArray.first!)

在第一次 CRUD 检查期间,已实现异常处理,其中远程项目的读取将不再可用。

切换数据库

现在我将向您展示如何将当前代码传输到另一个数据库。例如,我将采用 ChatGPT 生成的 SQLite 存储库代码:


class SQLiteRepository: CRUDRepository {
    private typealias RecordIdentifier = String
    
    let tableName: String
    let dataTransformer: DataTransformer
    private var db: OpaquePointer?

    init(
   	 tableName: String,
   	 dataTransformer: DataTransformer = JSONDataTransformer()
    ) {
   	 self.tableName = tableName
   	 self.dataTransformer = dataTransformer
   	 self.db = openDatabase()
   	 createTableIfNeeded()
    }
    
    private func openDatabase() -> OpaquePointer? {
   	 var db: OpaquePointer? = nil
   	 let fileURL = try! FileManager.default
   		 .url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
   		 .appendingPathComponent("\(tableName).sqlite")
   	 if sqlite3_open(fileURL.path, &db) != SQLITE_OK {
   		 print("error opening database")
   		 return nil
   	 }
   	 return db
    }
    
    private func createTableIfNeeded() {
   	 let createTableString = """
   	 CREATE TABLE IF NOT EXISTS \(tableName) (
   	 id TEXT PRIMARY KEY NOT NULL,
   	 data BLOB NOT NULL
   	 );
   	 """
   	 var createTableStatement: OpaquePointer? = nil
   	 if sqlite3_prepare_v2(db, createTableString, -1, &createTableStatement, nil) == SQLITE_OK {
   		 if sqlite3_step(createTableStatement) == SQLITE_DONE {
       		 print("\(tableName) table created.")
   		 } else {
       		 print("\(tableName) table could not be created.")
   		 }
   	 } else {
   		 print("CREATE TABLE statement could not be prepared.")
   	 }
   	 sqlite3_finalize(createTableStatement)
    }
    
    private func isExists(id: CRUDRepository.ItemIdentifier) async throws -> Bool {
   	 let queryStatementString = "SELECT data FROM \(tableName) WHERE id = ?;"
   	 var queryStatement: OpaquePointer? = nil
   	 if sqlite3_prepare_v2(db, queryStatementString, -1, &queryStatement, nil) == SQLITE_OK {
   		 sqlite3_bind_text(queryStatement, 1, id, -1, nil)
   		 if sqlite3_step(queryStatement) == SQLITE_ROW {
       		 sqlite3_finalize(queryStatement)
       		 return true
   		 } else {
       		 sqlite3_finalize(queryStatement)
       		 return false
   		 }
   	 } else {
   		 print("SELECT statement could not be prepared.")
   		 throw CRUDRepositoryError.databaseError
   	 }
    }
    
    func create<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws {
   	 let insertStatementString = "INSERT INTO \(tableName) (id, data) VALUES (?, ?);"
   	 var insertStatement: OpaquePointer? = nil
   	 if sqlite3_prepare_v2(db, insertStatementString, -1, &insertStatement, nil) == SQLITE_OK {
   		 let data = try await dataTransformer.encode(item)
   		 sqlite3_bind_text(insertStatement, 1, id, -1, nil)
   		 sqlite3_bind_blob(insertStatement, 2, (data as NSData).bytes, Int32(data.count), nil)
   		 if sqlite3_step(insertStatement) == SQLITE_DONE {
       		 print("Successfully inserted row.")
   		 } else {
       		 print("Could not insert row.")
       		 throw CRUDRepositoryError.databaseError
   		 }
   	 } else {
   		 print("INSERT statement could not be prepared.")
   		 throw CRUDRepositoryError.databaseError
   	 }
   	 sqlite3_finalize(insertStatement)
    }
    
    func read<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier) async throws -> T {
   	 let queryStatementString = "SELECT data FROM \(tableName) WHERE id = ?;"
   	 var queryStatement: OpaquePointer? = nil
   	 var item: T?
   	 if sqlite3_prepare_v2(db, queryStatementString, -1, &queryStatement, nil) == SQLITE_OK {
   		 sqlite3_bind_text(queryStatement, 1, id, -1, nil)
   		 if sqlite3_step(queryStatement) == SQLITE_ROW {
       		 let queryResultCol1 = sqlite3_column_blob(queryStatement, 0)
       		 let queryResultCol1Length = sqlite3_column_bytes(queryStatement, 0)
       		 let data = Data(bytes: queryResultCol1, count: Int(queryResultCol1Length))
       		 item = try await dataTransformer.decode(data: data)
   		 } else {
       		 throw CRUDRepositoryError.recordNotFound(id: id)
   		 }
   	 } else {
   		 print("SELECT statement could not be prepared")
   		 throw CRUDRepositoryError.databaseError
   	 }
   	 sqlite3_finalize(queryStatement)
   	 return item!
    }
    
    func update<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws {
   	 guard try await isExists(id: id) else {
   		 throw CRUDRepositoryError.recordNotFound(id: id)
   	 }
   	 let updateStatementString = "UPDATE \(tableName) SET data = ? WHERE id = ?;"
   	 var updateStatement: OpaquePointer? = nil
   	 if sqlite3_prepare_v2(db, updateStatementString, -1, &updateStatement, nil) == SQLITE_OK {
   		 let data = try await dataTransformer.encode(item)
   		 sqlite3_bind_blob(updateStatement, 1, (data as NSData).bytes, Int32(data.count), nil)
   		 sqlite3_bind_text(updateStatement, 2, id, -1, nil)
   		 if sqlite3_step(updateStatement) == SQLITE_DONE {
       		 print("Successfully updated row.")
   		 } else {
       		 print("Could not update row.")
       		 throw CRUDRepositoryError.databaseError
   		 }
   	 } else {
   		 print("UPDATE statement could not be prepared.")
   		 throw CRUDRepositoryError.databaseError
   	 }
   	 sqlite3_finalize(updateStatement)
    }
    
    func delete(id: CRUDRepository.ItemIdentifier) async throws {
   	 guard try await isExists(id: id) else {
   		 throw CRUDRepositoryError.recordNotFound(id: id)
   	 }
   	 let deleteStatementString = "DELETE FROM \(tableName) WHERE id = ?;"
   	 var deleteStatement: OpaquePointer? = nil
   	 if sqlite3_prepare_v2(db, deleteStatementString, -1, &deleteStatement, nil) == SQLITE_OK {
   		 sqlite3_bind_text(deleteStatement, 1, id, -1, nil)
   		 if sqlite3_step(deleteStatement) == SQLITE_DONE {
       		 print("Successfully deleted row.")
   		 } else {
       		 print("Could not delete row.")
       		 throw CRUDRepositoryError.databaseError
   		 }
   	 } else {
   		 print("DELETE statement could not be prepared.")
   		 throw CRUDRepositoryError.databaseError
   	 }
   	 sqlite3_finalize(deleteStatement)
    }
}

或者文件系统存储库的 CRUD 代码,它也是由 ChatGPT 生成的:


class FileSystemRepository: CRUDRepository {
	private typealias RecordIdentifier = String
    
	let directoryName: String
	let dataTransformer: DataTransformer
	private let fileManager = FileManager.default
	private var directoryURL: URL
    
	init(
    	directoryName: String = "Database",
    	dataTransformer: DataTransformer = JSONDataTransformer()
	) {
    	self.directoryName = directoryName
    	self.dataTransformer = dataTransformer
   	 
    	let paths = fileManager.urls(for: .documentDirectory, in: .userDomainMask)
    	directoryURL = paths.first!.appendingPathComponent(directoryName)
   	 
    	if !fileManager.fileExists(atPath: directoryURL.path) {
        	try? fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
    	}
	}
    
	private func fileURL(id: CRUDRepository.ItemIdentifier) -> URL {
    	return directoryURL.appendingPathComponent("item_\(id).json")
	}
    
	private func isExists(id: CRUDRepository.ItemIdentifier) async throws -> Bool {
    	return fileManager.fileExists(atPath: fileURL(id: id).path)
	}
    
	func create<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws {
    	let data = try await dataTransformer.encode(item)
    	let url = fileURL(id: id)
    	try data.write(to: url)
	}
    
	func read<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier) async throws -> T {
    	let url = fileURL(id: id)
    	guard let data = fileManager.contents(atPath: url.path) else {
        	throw CRUDRepositoryError.recordNotFound(id: id)
    	}
    	let item: T = try await dataTransformer.decode(data: data)
    	return item
	}
    
	func update<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws {
    	guard try await isExists(id: id) else {
        	throw CRUDRepositoryError.recordNotFound(id: id)
    	}
    	let data = try await dataTransformer.encode(item)
    	let url = fileURL(id: id)
    	try data.write(to: url)
	}
    
	func delete(id: CRUDRepository.ItemIdentifier) async throws {
    	guard try await isExists(id: id) else {
        	throw CRUDRepositoryError.recordNotFound(id: id)
    	}
    	let url = fileURL(id: id)
    	try fileManager.removeItem(at: url)
	}
}

替换客户端代码中的存储库:


print("One item access example")

do {
	let clientRecordIdentifier = "client"
	let clientOne = Client(name: "Chill Client")
	let repository = FileSystemRepository(
    	directoryName: "Clients Database",
    	dataTransformer: JSONDataTransformer()
	)
	try await repository.create(id: clientRecordIdentifier, item: clientOne)
	var clientRecord: Client = try await repository.read(id: clientRecordIdentifier)
	print("Client Name: \(clientRecord.name)")
	clientRecord.name = "Busy Client"
	try await repository.update(id: clientRecordIdentifier, item: clientRecord)
	let updatedClient: Client = try await repository.read(id: clientRecordIdentifier)
	print("Updated Client Name: \(updatedClient.name)")
	try await repository.delete(id: clientRecordIdentifier)
	let removedClientRecord: Client = try await repository.read(id: clientRecordIdentifier)
	print(removedClientRecord)
}
catch {
	print(error.localizedDescription)
}

print("Array access example")

let clientArrayRecordIdentifier = "clientArray"
let clientOne = Client(name: "Chill Client")
let repository = FileSystemRepository(
	directoryName: "Clients Database",
	dataTransformer: JSONDataTransformer()
)
let array = [clientOne]
try await repository.create(id: clientArrayRecordIdentifier, item: array)
let savedArray: [Client] = try await repository.read(id: clientArrayRecordIdentifier)
print(savedArray.first!)

UserDefaultsRepository 的初始化已替换为 FileSystemRepository,并带有适当的参数。
运行第二版客户端代码后,您将在文档文件夹中找到“Clients Database”目录,其中包含一个以 JSON 序列化的数组文件,具有一个客户端结构。

切换数据存储格式

现在让 ChatGPT 生成 XML 的编码器和解码器:

	let formatExtension = "xml"
    
	func encode<T: Encodable>(_ item: T) async throws -> Data {
    	let encoder = PropertyListEncoder()
    	encoder.outputFormat = .xml
    	return try encoder.encode(item)
	}
    
	func decode<T: Decodable>(data: Data) async throws -> T {
    	let decoder = PropertyListDecoder()
    	return try decoder.decode(T.self, from: data)
	}
}

得益于 Swift 中的内置类型,神经网络的任务变得非常简单。

在客户端代码中将 JSON 替换为 XML:


print("One item access example")

do {
	let clientRecordIdentifier = "client"
	let clientOne = Client(name: "Chill Client")
	let repository = FileSystemRepository(
    	directoryName: "Clients Database",
    	dataTransformer: XMLDataTransformer()
	)
	try await repository.create(id: clientRecordIdentifier, item: clientOne)
	var clientRecord: Client = try await repository.read(id: clientRecordIdentifier)
	print("Client Name: \(clientRecord.name)")
	clientRecord.name = "Busy Client"
	try await repository.update(id: clientRecordIdentifier, item: clientRecord)
	let updatedClient: Client = try await repository.read(id: clientRecordIdentifier)
	print("Updated Client Name: \(updatedClient.name)")
	try await repository.delete(id: clientRecordIdentifier)
	let removedClientRecord: Client = try await repository.read(id: clientRecordIdentifier)
	print(removedClientRecord)
}
catch {
	print(error.localizedDescription)
}

print("Array access example")

let clientArrayRecordIdentifier = "clientArray"
let clientOne = Client(name: "Chill Client")
let repository = FileSystemRepository(
	directoryName: "Clients Database",
	dataTransformer: XMLDataTransformer()
)
let array = [clientOne]
try await repository.create(id: clientArrayRecordIdentifier, item: array)
let savedArray: [Client] = try await repository.read(id: clientArrayRecordIdentifier)
print(savedArray.first!)

客户端代码已更改为只有一个表达式 JSONDataTransformer -> XML数据转换器

总计

CRUD 存储库是可用于实现应用程序架构组件松散耦合的设计模式之一。另一种解决方案是“使用ORM(对象关系映射),简单来说,ORM使用一种将结构完全映射到数据库的方法,然后将模型的更改显示在数据库上(映射(!))。
但这是一个完全不同的故事。

Swift 的 CRUD 存储库的完整实现位于:
https://gitlab.com/demensdeum/crud-example

顺便说一句,Swift 长期以来一直在 macOS 之外得到支持;本文中的代码完全是在 Arch Linux 上编写和测试的。

来源

https://developer.apple.com/documentation/combine/topleveldecoder
https://developer.apple.com/documentation/combine/toplevelencoder
https://en.wikipedia.org/wiki/Create,_read,_update_and_delete

dd 输入/输出错误

如果在 Linux 中使用 dd 复制普通磁盘时出现输入/输出错误,该怎么办?

西图维娜很伤心,但也是可以解决的。您很可能正在处理包含无法再使用、写入或读取的坏块的故障磁盘。

请务必使用 S.M.A.R.T. 检查此类磁盘,它很可能会显示磁盘错误。我的情况就是这样;坏块数量如此之大,以至于我不得不告别旧硬盘并更换为新的 SSD。

问题在于该磁盘包含一个完整工作的系统,其中包含工作所需的许可软件。我尝试使用partimage来快速复制数据,但突然我发现该实用程序只复制了磁盘的三分之一,然后它以段错误或其他一些有趣的Sishny/Sipplusplus笑话结束。

接下来,我尝试使用dd复制数据,结果发现dd到达的位置与partimage大致相同,然后出现输入/输出错误。与此同时,各种有趣的标志,如 conv=noerr、skip 或其他东西根本没有帮助。

但是使用名为 ddrescue 的 GNU 实用程序将数据复制到另一个磁盘时没有出现任何问题。

После этого мои волосы стали шелковистыми, вернулась жена, дети и собака перестала кусать диван.

Большим плюсом ddrescue является наличие встроенного прогрессбара, поэтому не приходится костылять какие-то ухищрения навроде pv и всяких не особо красивых флажков dd. Также ddrescure показывает количество попыток прочитать данные; еще на вики написано что утилита обладает каким-то сверх алгоритмом для считывания поврежденных данных, оставим это на проверку людям которые любят ковыряться в исходниках, мы же не из этих да?

https://ru.wikipedia.org/wiki/Ddrescue
https://www.gnu.org/software/ddrescue/ddrescue_ru.html

聊天GPT

大家好!在这篇文章中我想谈谈 ChatGPT – OpenAI 强大的语言建模可以帮助解决各种文本处理问题。我将向您展示该工具的工作原理以及如何在实际情况中使用它。让我们开始吧!

目前,ChatGPT 是世界上最好的基于神经网络的语言模型之一。创建它的目的是帮助开发人员创建能够生成自然语言并与其中的人进行交流的智能系统。

ChatGPT 的主要优势之一是它能够对文本进行上下文建模。这意味着该模型会考虑之前的对话,并利用它来更准确地了解情况并生成更自然的响应。

您可以使用 ChatGPT 执行各种任务,例如自动化客户支持、创建聊天机器人、文本生成等等。

ChatGPT 背后的神经网络接受了大量文本的训练,以确保高度准确的预测。这使得模型能够生成可以支持对话和回答问题的自然文本。

借助 ChatGPT,您可以创建自己的聊天机器人和其他可以用自然语言与人们交互的智能系统。这在旅游、零售和客户支持等行业尤其有用。

总之,ChatGPT –它是解决各种语言建模问题的强大工具。它的上下文建模能力使其对于创建聊天机器人和智能系统特别有用。

<小时/>

事实上,上面的所有内容都是 ChatGPT 自己编写的。什么?是的?我自己都震惊了!

您可以在这里尝试网格本身:
https://chat.openai.com/chat

在 macOS 上打开 USB 键盘背光

我最近买了一个非常便宜的 Getorix GK-45X USB 键盘,带有 RGB 背光。将其连接到配备 M1 处理器的 MacBook Pro 后,很明显 RGB 背光不起作用。即使按下神奇组合 Fn + Scroll Lock 也无法打开背光;只是 MacBook 屏幕的背光亮度发生了变化。
这个问题有几种解决方案,分别是OpenRGB(不起作用)、HID LED Test(不起作用)。只有 kvmswitch 实用程序有效:
https://github.com/stoutput/OSX-KVM

您需要从 GitHub 下载它并允许它从系统设置的安全面板中的终端运行。
据我从描述中了解到,启动该实用程序后,它会发送 Fn + Scroll Lock 按键,从而打开/关闭键盘上的背光。

树排序

树排序——使用二叉搜索树进行排序。时间复杂度– O(n²)。在这样的树中,左边的每个节点都有小于该节点的数字,右边的每个节点都有大于该节点的数字,当从根开始并从左到右打印值时,我们得到一个排序的数字列表。令人惊讶吧?

考虑二叉搜索树图:

Derrick Coetzee(公共领域)

尝试手动读取从左下角倒数第二个左侧节点开始的数字,对于左侧的每个节点 – 右侧的一个节点。

它看起来像这样:

  1. 左下角倒数第二个节点 – 3.
  2. 它有一个左分支 – 1.
  3. 取这个数字(1)
  4. 接下来我们采用顶点 3 (1, 3)
  5. 右侧是分支 6,但它包含分支。因此,我们以同样的方式阅读它。
  6. 节点 6 编号 4 的左分支 (1, 3, 4)
  7. 节点本身为 6 (1, 3, 4, 6)
  8. 右 7(1、3、4、6、7)
  9. 向上到根节点– 8(1,3,4,6,7,8)
  10. 我们以此类推将所有内容打印在右侧
  11. 我们得到了最终名单– 1、3、4、6、7、8、10、13、14

要在代码中实现该算法,您需要两个函数:

  1. 组装二叉搜索树
  2. 以正确的顺序打印二叉搜索树

二叉搜索树的组装方式与读取时相同,左侧或右侧的每个节点都会附加一个数字,具体取决于它是较少还是较多。

Lua 中的示例:


function Node:new(value, lhs, rhs)
    output = {}
    setmetatable(output, self)
    self.__index = self  
    output.value = value
    output.lhs = lhs
    output.rhs = rhs
    output.counter = 1
    return output  
end

function Node:Increment()
    self.counter = self.counter + 1
end

function Node:Insert(value)
    if self.lhs ~= nil and self.lhs.value > value then
        self.lhs:Insert(value)
        return
    end

    if self.rhs ~= nil and self.rhs.value < value then
        self.rhs:Insert(value)
        return
    end

    if self.value == value then
        self:Increment()
        return
    elseif self.value > value then
        if self.lhs == nil then
            self.lhs = Node:new(value, nil, nil)
        else
            self.lhs:Insert(value)
        end
        return
    else
        if self.rhs == nil then
            self.rhs = Node:new(value, nil, nil)
        else
            self.rhs:Insert(value)
        end
        return
    end
end

function Node:InOrder(output)
    if self.lhs ~= nil then
       output = self.lhs:InOrder(output)
    end
    output = self:printSelf(output)
    if self.rhs ~= nil then
        output = self.rhs:InOrder(output)
    end
    return output
end

function Node:printSelf(output)
    for i=0,self.counter-1 do
        output = output .. tostring(self.value) .. " "
    end
    return output
end

function PrintArray(numbers)
    output = ""
    for i=0,#numbers do
        output = output .. tostring(numbers[i]) .. " "
    end    
    print(output)
end

function Treesort(numbers)
    rootNode = Node:new(numbers[0], nil, nil)
    for i=1,#numbers do
        rootNode:Insert(numbers[i])
    end
    print(rootNode:InOrder(""))
end


numbersCount = 10
maxNumber = 9

numbers = {}

for i=0,numbersCount-1 do
    numbers[i] = math.random(0, maxNumber)
end

PrintArray(numbers)
Treesort(numbers)

Важный нюанс что для чисел которые равны вершине придумано множество интересных механизмов подцепления к ноде, я же просто добавил счетчик к классу вершины, при распечатке числа возвращаются по счетчику.

Ссылки

https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/treesort

Источники

TreeSort Algorithm Explained and Implemented with Examples in Java | Sorting Algorithms | Geekific – YouTube

Tree sort – YouTube

Convert Sorted Array to Binary Search Tree (LeetCode 108. Algorithm Explained) – YouTube

Sorting algorithms/Tree sort on a linked list – Rosetta Code

Tree Sort – GeeksforGeeks

Tree sort – Wikipedia

How to handle duplicates in Binary Search Tree? – GeeksforGeeks

Tree Sort | GeeksforGeeks – YouTube

桶排序

桶排序——按桶排序。该算法类似于计数排序,不同之处在于将数字收集到“桶”范围中,然后使用任何其他足够高效的排序算法对桶进行排序,最后一步是将“桶”展开减一,得到一个排序列表。

算法的时间复杂度为O(nk)。该算法在线性时间内适用于服从均匀分布规律的数据。简单来说,元素必须在一定范围内,不能有“尖峰”,例如0.0到1.0之间的数字。如果这些数字中有 4 或 999,那么根据庭院法则,这样的行不再被视为“偶数”。

Julia 中的实现示例:

    buckets = Vector{Vector{Int}}()
    
    for i in 0:bucketsCount - 1
        bucket = Vector{Int}()
        push!(buckets, bucket)
    end

    maxNumber = maximum(numbers)

    for i in 0:length(numbers) - 1
        bucketIndex = 1 + Int(floor(bucketsCount * numbers[1 + i] / (maxNumber + 1)))
        push!(buckets[bucketIndex], numbers[1 + i])
    end

    for i in 0:length(buckets) - 1
        bucketIndex = 1 + i
        buckets[bucketIndex] = sort(buckets[bucketIndex])
    end

    flat = [(buckets...)...]
    print(flat, "\n")

end

numbersCount = 10
maxNumber = 10
numbers = rand(1:maxNumber, numbersCount)
print(numbers,"\n")
bucketsCount = 10
bucketSort(numbers, bucketsCount)

На производительность алгоритма также влияет число ведер, для большего количества чисел лучше взять большее число ведер (Algorithms in a nutshell by George T. Heineman)

Ссылки

https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/bucketSort

Источники

https://www.youtube.com/watch?v=VuXbEb5ywrU
https://www.youtube.com/watch?v=ELrhrrCjDOA
https://medium.com/karuna-sehgal/an-introduction-to-bucket-sort-62aa5325d124
https://www.geeksforgeeks.org/bucket-sort-2/
https://ru.wikipedia.org/wiki/%D0%91%D0%BB%D0%BE%D1%87%D0%BD%D0%B0%D1%8F_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0
https://www.youtube.com/watch?v=LPrF9yEKTks
https://en.wikipedia.org/wiki/Bucket_sort
https://julialang.org/
https://www.oreilly.com/library/view/algorithms-in-a/9780596516246/ch04s08.html

基数排序

Radix Sort – 基数排序。该算法与计数排序类似,不进行元素比较;而是将元素逐个字符分组到“桶”(桶)中,桶是通过当前数字字符的索引来选择的。时间复杂度– O(nd)。

它的工作原理是这样的:

  • 输入为数字 6、12、44、9
  • 我们将创建 10 个列表桶 (0-9),我们将在其中逐位添加/排序数字。

下一个:

  1. 使用计数器 i 开始循环,直到达到数字中的最大字符数
  2. 通过索引 i 从右到左,我们为每个数字获取一个符号;如果没有符号,则假设它为零
  3. 将符号转换为数字
  4. 按索引 – 数字选择一个存储桶,并将整个数字放在那里
  5. 搜索完数字后,将所有桶转换回数字列表
  6. 获取按排名排序的数字
  7. 重复直到所有数字都消失

Scala 中的基数排序示例:


import scala.util.Random.nextInt



object RadixSort {

    def main(args: Array[String]) = {

        var maxNumber = 200

        var numbersCount = 30

        var maxLength = maxNumber.toString.length() - 1



        var referenceNumbers = LazyList.continually(nextInt(maxNumber + 1)).take(numbersCount).toList

        var numbers = referenceNumbers

        

        var buckets = List.fill(10)(ListBuffer[Int]())



        for( i <- 0 to maxLength) { numbers.foreach( number => {

                    var numberString = number.toString

                    if (numberString.length() > i) {

                        var index = numberString.length() - i - 1

                        var character = numberString.charAt(index).toString

                        var characterInteger = character.toInt  

                        buckets.apply(characterInteger) += number

                    }

                    else {

                        buckets.apply(0) += number

                    }

                }

            )

            numbers = buckets.flatten

            buckets.foreach(x => x.clear())

        }

        println(referenceNumbers)

        println(numbers)

        println(s"Validation result: ${numbers == referenceNumbers.sorted}")

    }

}

该算法还有一个并行执行的版本,例如在 GPU 上;还有一个排序选项,这一定非常有趣并且真正令人惊叹!

链接

https://gitlab .com/demensdeum/algorithms/-/blob/master/sortAlgorithms/radixSort/radixSort.scala

来源

<一href="https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D1%80%D0%B0%D0%B7%D1%80%D1%8F%D0% B4%D0%BD%D0%B0%D1%8F_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA% D0%B0"目标=“_空白” rel="noopener">https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D1%80%D0%B0%D0%B7%D1%80%D1%8F%D 0%B4%D0%BD%D0%B0%D1%8F_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0% BA%D0%B0
https://www.geeksforgeeks.org/radix-sort/

https://www.youtube.com/watch?v=toAlAJKojos

https://github.com/gyatskov/radix-sort

堆排序

堆排序——金字塔排序。算法的时间复杂度– O(n log n),快吧?我将这种排序称为掉落卵石的排序。在我看来,最简单的解释方法就是视觉化。

输入是数字列表,例如:
5、0、7、2、3、9、4

从左到右,创建一个数据结构 – 二叉树,或者我称之为“二叉树”。金字塔。金字塔元素最多可以有两个子元素,但只能有一个顶部元素。

让我们制作一棵二叉树:
⠀⠀5
⠀0⠀7
2 3 9 4

如果你长时间观察金字塔,你会发现这些只是一个数组中的数字,一个接一个地出现,每一层的元素数量都乘以二。

接下来,有趣的事情开始了;让我们使用落下的卵石方法(heapify)对金字塔进行排序。排序可以从最后一层(2 3 9 4)开始,但是没有意义,因为下面没有任何地板可以让您跌倒。

因此,我们开始从倒数第二层(0 7)删除元素
⠀⠀5
⠀0⠀7
2 3 9 4

从右边选择第一个落下的元素,在我们的例子中是7,然后我们看看它下面的是什么,在它下面是9和4,九大于四,九也大于七!我们将 7 放到 9 上,然后将 9 提升到 7 的位置。
⠀⠀5
⠀0⠀9
2 3 7 4

接下来,我们知道七已经无处可去,我们继续看数字 0,它位于左边倒数第二层:
⠀⠀5
0⠀9
2 3 7 4

让我们看看下面是什么– 2和3,二小于三,三大于零,所以我们交换零和三:
⠀⠀5
⠀3⠀9
2 0 7 4

当你到达地板的尽头时,走到上面的地板上,如果可以的话,把所有东西都扔在那里。
结果是一个数据结构——堆,即最大堆,因为顶部是最大的元素:
⠀⠀9
⠀3⠀7
2 0 5 4

如果将其返回为数组表示形式,您将得到一个列表:
[9、3、7、2、0、5、4]

由此我们可以得出结论,通过交换第一个和最后一个元素,我们得到最终排序位置的第一个数字,即 9 应该位于排序列表的末尾,交换位置:
[4、3、7、2、0、5、9]

让我们看一下二叉树:
⠀⠀4
⠀3⠀7
2 0 5 9

结果是树的下部已排序的情况,只需将 4 放到正确的位置,重复算法,但不考虑已经排序的数字,即 9:
⠀⠀4
⠀3⠀7
2 0 5 9

⠀⠀7
⠀3⠀4
2 0 5 9

⠀⠀7
⠀3⠀5
2 0 4 9

事实证明,我们在下降了 4 个数字后,又筹集了继 9 个数字之后的下一个最大数字—— 7. 交换最后一个未排序的数字(4)和最大的数字(7)
⠀⠀4
⠀3⠀5
2 0 7 9

事实证明,现在我们有两个数字处于正确的最终位置:
4、3、5、2、0、79

接下来我们重复排序算法,忽略已经排序的,最后我们得到一个 类型:
⠀⠀0
⠀2⠀3
4 5 7 9

或者作为列表:
0、2、3、4、5、7、9

实施

算法通常分为三个函数:

  1. 创建堆
  2. 筛选算法(heapify)
  3. 替换最后一个未排序元素和第一个元素

堆是通过使用heapify函数从右到左遍历二叉树的倒数第二行,直到数组的末尾来创建的。接下来在循环中,进行第一次数字替换,之后第一个元素落入/保持在原位,因此最大的元素落入第一位,重复该循环,参与者减少一个,因为每次传递后,排序后的数字保留在列表的末尾。

Ruby 中的堆排序示例:






module Colors



    BLUE = "\033[94m"



    RED = "\033[31m"



    STOP = "\033[0m"



end







def heapsort(rawNumbers)



    numbers = rawNumbers.dup







    def swap(numbers, from, to)



        temp = numbers[from]



        numbers[from] = numbers[to]



        numbers[to] = temp



    end







    def heapify(numbers)



        count = numbers.length()



        lastParentNode = (count - 2) / 2







        for start in lastParentNode.downto(0)



            siftDown(numbers, start, count - 1)



            start -= 1 



        end







        if DEMO



            puts "--- heapify ends ---"



        end



    end







    def siftDown(numbers, start, rightBound)      



        cursor = start



        printBinaryHeap(numbers, cursor, rightBound)







        def calculateLhsChildIndex(cursor)



            return cursor * 2 + 1



        end







        def calculateRhsChildIndex(cursor)



            return cursor * 2 + 2



        end            







        while calculateLhsChildIndex(cursor) <= rightBound



            lhsChildIndex = calculateLhsChildIndex(cursor)



            rhsChildIndex = calculateRhsChildIndex(cursor)







            lhsNumber = numbers[lhsChildIndex]



            biggerChildIndex = lhsChildIndex







            if rhsChildIndex <= rightBound



                rhsNumber = numbers[rhsChildIndex]



                if lhsNumber < rhsNumber



                    biggerChildIndex = rhsChildIndex



                end



            end







            if numbers[cursor] < numbers[biggerChildIndex]



                swap(numbers, cursor, biggerChildIndex)



                cursor = biggerChildIndex



            else



                break



            end



            printBinaryHeap(numbers, cursor, rightBound)



        end



        printBinaryHeap(numbers, cursor, rightBound)



    end







    def printBinaryHeap(numbers, nodeIndex = -1, rightBound = -1)



        if DEMO == false



            return



        end



        perLineWidth = (numbers.length() * 4).to_i



        linesCount = Math.log2(numbers.length()).ceil()



        xPrinterCount = 1



        cursor = 0



        spacing = 3



        for y in (0..linesCount)



            line = perLineWidth.times.map { " " }



            spacing = spacing == 3 ? 4 : 3



            printIndex = (perLineWidth / 2) - (spacing * xPrinterCount) / 2



            for x in (0..xPrinterCount - 1)



                if cursor >= numbers.length



                    break



                end



                if nodeIndex != -1 && cursor == nodeIndex



                    line[printIndex] = "%s%s%s" % [Colors::RED, numbers[cursor].to_s, Colors::STOP]



                elsif rightBound != -1 && cursor > rightBound



                    line[printIndex] = "%s%s%s" % [Colors::BLUE, numbers[cursor].to_s, Colors::STOP]



                else



                    line[printIndex] = numbers[cursor].to_s



                end



                cursor += 1



                printIndex += spacing



            end



            print line.join()



            xPrinterCount *= 2           



            print "\n"            



        end



    end







    heapify(numbers)



    rightBound = numbers.length() - 1







    while rightBound > 0



        swap(numbers, 0, rightBound)   



        rightBound -= 1



        siftDown(numbers, 0, rightBound)     



    end







    return numbers



end







numbersCount = 14



maximalNumber = 10



numbers = numbersCount.times.map { Random.rand(maximalNumber) }



print numbers



print "\n---\n"







start = Time.now



sortedNumbers = heapsort(numbers)



finish = Time.now



heapSortTime = start - finish







start = Time.now



referenceSortedNumbers = numbers.sort()



finish = Time.now



referenceSortTime = start - finish







print "Reference sort: "



print referenceSortedNumbers



print "\n"



print "Reference sort time: %f\n" % referenceSortTime



print "Heap sort:      "



print sortedNumbers



print "\n"



if DEMO == false



    print "Heap sort time:      %f\n" % heapSortTime



else



    print "Disable DEMO for performance measure\n"



end







if sortedNumbers != referenceSortedNumbers



    puts "Validation failed"



    exit 1



else



    puts "Validation success"



    exit 0



end



如果没有可视化,这个算法不容易理解,所以我建议的第一件事是编写一个函数来打印二叉树的当前视图。

链接

https://gitlab.com/demensdeum/algorithms/-/blob/master/sortAlgorithms/heapsort/heapsort.rb

来源

http://rosettacode.org/wiki/Sorting_algorithms/Heapsort
https://www.youtube.com/watch?v=LbB357_RwlY

https://habr.com/ru/company/ otus/博客/460087/

https://ru.wikipedia.org/wiki/Pyramid_sort

https://neerc.ifmo.ru/wiki /index.php?title=Heap_sort

https://wiki5.ru/wiki/Heapsort

https://wiki.c2.com/?HeapSort

<一href="https://ru.wikipedia.org/wiki/%D0%94%D0%B5%D1%80%D0%B5%D0%B2%D0%BE_(%D1%81%D1 %82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B %D1%85)" target="_blank" rel="noopener">https://ru.wikipedia.org/wiki/Tree(数据结构)

<一href="https://ru.wikipedia.org/wiki/%D0%9A%D1%83%D1%87%D0%B0_(%D1%81%D1%82%D1 %80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85 )” target="_blank" rel="noopener">https://ru.wikipedia.org/wiki/Heap(数据结构)

https://www.youtube.com/watch?v=2DmK_H7IdTo

https://www.youtube.com/watch?v=kU4KBD4NFtw

https://www.youtube.com/watch?v=DU1uG5310x0

https://www.youtube.com/watch?v =BzQGPA_v-vc

https://www.geeksforgeeks.org/二进制堆的数组表示/

https://habr.com/ru/post/112222/

https://www.cs.usfca。 edu/~galles/visualization/BST.html

https://www.youtube.com/watch?v=EQzqHWtsKq4

<一href="https://medium.com/@dimko1/%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D1% 8B-%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8-heapsort-796ba965018b"目标=“_空白” rel="noopener">https://medium.com/@dimko1/%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC% D1 %8B-%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8-heapsort-796ba965018b

https://ru.wikibrief.org/wiki/Heapsort

https://www.youtube.com/watch?v=GUUpmrTnNbw

Bumblebee All Troubles

Recently, it turned out that users of modern Nvidia GPUs under Arch Linux do not need to use the bumblebee package at all, for example, for me it did not detect an external monitor when connected. I recommend removing the bumblebee package and all related packages, and installing prime using the instructions on the Arch Wiki.
Next, to launch all games on Steam and 3D applications, add prime-run, for Steam this is done like this prime-run %command% in additional launch options.
To check the correctness, you can use glxgears, prime-run glxgears.
https://bbs.archlinux.org/viewtopic.php? pid=2048195#p2048195

快速排序

快速排序是一种分而治之的排序算法。递归地,我们逐个解析数字数组,将所选参考元素中的数字按照较小和较大的顺序放置,并将参考元素本身插入到它们之间的截止位置。经过几次递归迭代后,您将得到一个排序列表。时间复杂度 O(n2)。

方案:

  1. 我们首先从外部获取元素列表,即排序边界。第一步,排序边界将从头到尾。
  2. 检查起始边界和结束边界是否相交;如果发生这种情况,则该结束了
  3. 从列表中选择某个元素并将其命名为数据透视
  4. 将其向右移动到最后一个索引处的末尾,这样就不会妨碍
  5. 创建一个*较小数字*仍等于零的计数器
  6. 从左到右循环遍历列表,直到引用元素所在的最后一个索引(包括该索引)
  7. 我们将每个元素与参考元素进行比较
  8. 如果小于参考值,则根据较小数字的计数器的索引进行交换。增加较小数字的计数器。
  9. 当循环到达支持元素时,我们停止并根据较小数字的计数器将支持元素与元素交换。
  10. 我们分别针对列表左侧较小的部分和列表右侧较大的部分分别运行该算法。
  11. 因此,由于检查点 2,所有递归迭代都将开始停止
  12. 获取排序列表

快速排序是由莫斯科国立大学的科学家 Charles Anthony Richard Hoare 发明的,他在学习俄语后,在 Kolmogorov 学院学习了计算机翻译以及概率论。 1960年,因政治危机离开苏联。

Rust 中的示例实现:


use rand::Rng;

fn swap(numbers: &mut [i64], from: usize, to: usize) {
    let temp = numbers[from];
    numbers[from] = numbers[to];
    numbers[to] = temp;
}

fn quicksort(numbers: &mut [i64], left: usize, right: usize) {
    if left >= right {
        return
    }
    let length = right - left;
    if length <= 1 {
        return
    }
    let pivot_index = left + (length / 2);
    let pivot = numbers[pivot_index];

    let last_index = right - 1;
    swap(numbers, pivot_index, last_index);

    let mut less_insert_index = left;

    for i in left..last_index {
        if numbers[i] < pivot {
            swap(numbers, i, less_insert_index);
            less_insert_index += 1;
        }
    }
    swap(numbers, last_index, less_insert_index);
    quicksort(numbers, left, less_insert_index);
    quicksort(numbers, less_insert_index + 1, right);
}

fn main() {
    let mut numbers = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    let mut reference_numbers = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

    let mut rng = rand::thread_rng();
    for i in 0..numbers.len() {
        numbers[i] = rng.gen_range(-10..10);
        reference_numbers[i] = numbers[i];
    }

    reference_numbers.sort();

  println!("Numbers           {:?}", numbers);
  let length = numbers.len();
  quicksort(&mut numbers, 0, length);
  println!("Numbers           {:?}", numbers);
  println!("Reference numbers {:?}", reference_numbers);

  if numbers != reference_numbers {
    println!("Validation failed");
    std::process::exit(1);
  }
  else {
    println!("Validation success!");
    std::process::exit(0);
  }
}

如果不清楚,我建议观看圣地亚哥大学 Rob Edwards 的视频 https://www.youtube.com/watch?v=ZHVk2blR45Q 最简单、一步步展示了算法的本质和实现。

链接

https://gitlab.com/demensdeum /algorithms/-/tree/master/sortAlgorithms/quickSort

来源

https://www.youtube.com/watch?v =4s-aG6yGGLU
https://www.youtube.com/watch?v=ywWBy6J5gz8
https://www.youtube.com/watch?v=Hoixgm4-P4M
https://ru.wikipedia.org/wiki/Быстрая_сортировка
https://www.youtube.com/watch?v=Hoixgm4-P4M
https://www.youtube.com/watch?v=XE4VP_8Y0BU
https://www.youtube.com/watch?v=MZaf_9IZCrc
https://www.youtube.com/watch?v=ZHVk2blR45Q
http://rosettacode.org/wiki/Sorting_algorithms/Quicksort
https://www.youtube.com/watch?v=4s-aG6yGGLU
https://www.youtube.com/watch?v=dQw4w9WgXcQ
https://www.youtube.com/watch?v=maibrCbZWKw
https://www.geeksforgeeks.org/quick-sort/
https://www.youtube.com/watch?v=uXBnyYuwPe8

二元插入排序

二分插入排序是插入排序的一种变体,其中使用二分查找来确定插入位置。该算法的时间复杂度为O(n2)

该算法的工作原理如下:

  1. 循环从零开始到列表末尾
  2. 在循环中选择一个数字进行排序,该数字存储在单独的变量中
  3. 二分查找查找索引以将此数字插入左侧的数字
  4. 一旦找到索引,左侧的数字就会从插入索引开始向右移动一位。在此过程中,需要排序的数字将被删除。
  5. 之前保存的号码将插入到插入索引处
  6. 循环结束时,整个列表将被排序

在二分查找过程中,有可能找不到该数字并且不会返回索引。由于二分查找的特殊性,会找到与要查找的数字最接近的数字,然后要返回索引,需要将其与要查找的数字进行比较,如果要查找的数字较小,则要返回的索引应为左侧的索引,如果大于或等于右侧的索引。

执行代码:


import (
	"fmt"
	"math/rand"
	"time"
)

const numbersCount = 20
const maximalNumber = 100

func binarySearch(numbers []int, item int, low int, high int) int {
	for high > low {
		center := (low + high) / 2
		if numbers[center] < item { low = center + 1 } else if numbers[center] > item {
			high = center - 1
		} else {
			return center
		}
	}

	if numbers[low] < item {
		return low + 1
	} else {
		return low
	}
}

func main() {
	rand.Seed(time.Now().Unix())
	var numbers [numbersCount]int
	for i := 0; i < numbersCount; i++ {
		numbers[i] = rand.Intn(maximalNumber)
	}
	fmt.Println(numbers)

	for i := 1; i < len(numbers); i++ { searchAreaLastIndex := i - 1 insertNumber := numbers[i] insertIndex := binarySearch(numbers[:], insertNumber, 0, searchAreaLastIndex) for x := searchAreaLastIndex; x >= insertIndex; x-- {
			numbers[x+1] = numbers[x]
		}
		numbers[insertIndex] = insertNumber
	}
	fmt.Println(numbers)
}

链接

https://gitlab.com/demensdeum/algorithms/-/blob/master/sortAlgorithms/binaryInsertionSort/binaryInsertionSort.go

来源

https://www.geeksforgeeks.org/binary-insertion-排序/
https://www.youtube.com/watch?v=-OVB5pOZJug

希尔排序

希尔排序 – 插入排序的一种变体,对数字数组进行初步组合。

我们需要记住插入排序是如何工作的:

1.一个循环从零开始到循环结束,因此数组被分成两部分
2. 对于左侧部分,启动第二个循环,从右到左比较元素,删除右侧较小的元素,直到找到左侧较小的元素
3. 在两个循环结束时,我们得到一个排序列表

很久以前,计算机科学家 Donald Schell 想知道如何改进插入排序算法。他还想出了这样的想法:首先分两个周期遍历数组,但在一定距离内,逐渐减少“梳子”,直到变成常规的插入排序算法。一切真的就是这么简单,没有陷阱,在上面的两个循环中我们添加了另一个循环,在这个循环中我们逐渐减小了“梳子”的尺寸。您唯一需要做的就是在比较时检查距离,使其不会超出数组。

一个真正有趣的主题是选择用于更改第一个循环每次迭代的比较长度的序列。有趣的是,算法的性能取决于它。

已知选项和时间复杂度表可以在这里找到:https: //en .wikipedia.org/wiki/Shellsort#Gap_sequences

不同的人参与计算理想距离;显然,这个话题对他们来说很有趣。他们不能只运行 Ruby 并调用最快的 sort() 算法吗?

总的来说,这些奇怪的人写的论文主题是计算 Shell 算法的“梳子”的距离/间隙。我简单地使用了他们的工作结果并检查了 5 种类型的序列:Hibbard、Knuth-Pratt、Chiura、Sedgwick。

import time
import random
from functools import reduce
import math

DEMO_MODE = False

if input("Demo Mode Y/N? ").upper() == "Y":
    DEMO_MODE = True

class Colors:
    BLUE = '\033[94m'
    RED = '\033[31m'
    END = '\033[0m'

def swap(list, lhs, rhs):
    list[lhs], list[rhs] = list[rhs], list[lhs]
    return list

def colorPrintoutStep(numbers: List[int], lhs: int, rhs: int):
    for index, number in enumerate(numbers):
        if index == lhs:
            print(f"{Colors.BLUE}", end = "")
        elif index == rhs:
            print(f"{Colors.RED}", end = "")
        print(f"{number},", end = "")
        if index == lhs or index == rhs:
            print(f"{Colors.END}", end = "")
        if index == lhs or index == rhs:
            print(f"{Colors.END}", end = "")
    print("\n")
    input(">")

def ShellSortLoop(numbers: List[int], distanceSequence: List[int]):
    distanceSequenceIterator = reversed(distanceSequence)
    while distance:= next(distanceSequenceIterator, None):
        for sortArea in range(0, len(numbers)):
            for rhs in reversed(range(distance, sortArea + 1)):
                lhs = rhs - distance
                if DEMO_MODE:
                    print(f"Distance: {distance}")
                    colorPrintoutStep(numbers, lhs, rhs)
                if numbers[lhs] > numbers[rhs]:
                    swap(numbers, lhs, rhs)
                else:
                    break

def ShellSort(numbers: List[int]):
    global ShellSequence
    ShellSortLoop(numbers, ShellSequence)

def HibbardSort(numbers: List[int]):
    global HibbardSequence
    ShellSortLoop(numbers, HibbardSequence)

def ShellPlusKnuttPrattSort(numbers: List[int]):
    global KnuttPrattSequence
    ShellSortLoop(numbers, KnuttPrattSequence)

def ShellPlusCiuraSort(numbers: List[int]):
    global CiuraSequence
    ShellSortLoop(numbers, CiuraSequence)

def ShellPlusSedgewickSort(numbers: List[int]):
    global SedgewickSequence
    ShellSortLoop(numbers, SedgewickSequence)

def insertionSort(numbers: List[int]):
    global insertionSortDistanceSequence
    ShellSortLoop(numbers, insertionSortDistanceSequence)

def defaultSort(numbers: List[int]):
    numbers.sort()

def measureExecution(inputNumbers: List[int], algorithmName: str, algorithm):
    if DEMO_MODE:
        print(f"{algorithmName} started")
    numbers = inputNumbers.copy()
    startTime = time.perf_counter()
    algorithm(numbers)
    endTime = time.perf_counter()
    print(f"{algorithmName} performance: {endTime - startTime}")

def sortedNumbersAsString(inputNumbers: List[int], algorithm) -> str:
    numbers = inputNumbers.copy()
    algorithm(numbers)
    return str(numbers)

if DEMO_MODE:
    maximalNumber = 10
    numbersCount = 10
else:
    maximalNumber = 10
    numbersCount = random.randint(10000, 20000)

randomNumbers = [random.randrange(1, maximalNumber) for i in range(numbersCount)]

ShellSequenceGenerator = lambda n: reduce(lambda x, _: x + [int(x[-1]/2)], range(int(math.log(numbersCount, 2))), [int(numbersCount / 2)])
ShellSequence = ShellSequenceGenerator(randomNumbers)
ShellSequence.reverse()
ShellSequence.pop()

HibbardSequence = [
    0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095,
    8191, 16383, 32767, 65535, 131071, 262143, 524287, 1048575,
    2097151, 4194303, 8388607, 16777215, 33554431, 67108863, 134217727,
    268435455, 536870911, 1073741823, 2147483647, 4294967295, 8589934591
]

KnuttPrattSequence = [
    1, 4, 13, 40, 121, 364, 1093, 3280, 9841, 29524, 88573, 265720, 
    797161, 2391484, 7174453, 21523360, 64570081, 193710244, 581130733, 
    1743392200, 5230176601, 15690529804, 47071589413
]

CiuraSequence = [
            1, 4, 10, 23, 57, 132, 301, 701, 1750, 4376, 
            10941, 27353, 68383, 170958, 427396, 1068491, 
            2671228, 6678071, 16695178, 41737946, 104344866, 
            260862166, 652155416, 1630388541
]

SedgewickSequence = [
            1, 5, 19, 41, 109, 209, 505, 929, 2161, 3905,
            8929, 16001, 36289, 64769, 146305, 260609, 587521, 
            1045505, 2354689, 4188161, 9427969, 16764929, 37730305, 
            67084289, 150958081, 268386305, 603906049, 1073643521, 
            2415771649, 4294770689, 9663381505, 17179475969
]

insertionSortDistanceSequence = [1]

algorithms = {
    "Default Python Sort": defaultSort,
    "Shell Sort": ShellSort,
    "Shell + Hibbard" : HibbardSort,
    "Shell + Prat, Knutt": ShellPlusKnuttPrattSort,
    "Shell + Ciura Sort": ShellPlusCiuraSort,
    "Shell + Sedgewick Sort": ShellPlusSedgewickSort,
    "Insertion Sort": insertionSort
}

for name, algorithm in algorithms.items():
    measureExecution(randomNumbers, name, algorithm)

reference = sortedNumbersAsString(randomNumbers, defaultSort)

for name, algorithm in algorithms.items():
    if sortedNumbersAsString(randomNumbers, algorithm) != reference:
        print("Sorting validation failed")
        exit(1)

print("Sorting validation success")
exit(0)

在我的实现中,对于一组随机数字,最快的差距是 Sedgwick 和 Hibbard。

mypy

我还想提一下 Python 3 的静态类型分析器——我的。有助于解决动态类型语言固有的问题,即它消除了将某些内容粘贴在不需要的地方的可能性。

正如经验丰富的程序员所说,“当你拥有一支专业团队时,就不需要静态类型”,有一天我们都会成为专业人士,我们将编写与机器完全统一和理解的代码,但现在您可以使用类似的实用程序和静态类型语言。

链接

https://gitlab.com/demensdeum /algorithms/-/tree/master/sortAlgorithms/shellSort
http://mypy-lang.org/

来源

https://dl.acm.org/doi/10.1145/368370.368387
https://en.wikipedia.org/wiki/Shellsort
http://rosettacode.org/wiki/Sorting_algorithms/Shell_sort
https://ru.wikipedia.org/wiki/Сортировка_Шелла
https://neerc.ifmo.ru/wiki/index.php?title=Сортировка_Шелла
https://twitter.com/gvanrossum/status/700741601966985216

双选排序

双选择排序 – 选择排序的子类型,看起来它的速度应该是它的两倍。普通算法对数字列表进行双重循环,找到最小数字并与上一层循环指向的当前数字交换位置。双选择排序查找最小和最大数字,然后替换上一层循环所指向的两位数字。左边和右边两个数字。当在列表中间找到需要替换的数字的光标时,整个狂欢就结束了,这样,视觉中心左右就得到了排序后的数字。
该算法的时间复杂度类似于选择排序– O(n2),但据说加速度为 30 %。

边缘状态

已经到了这个阶段,你可以想象一下碰撞的瞬间,比如当左光标的数字(最小的数字)指向列表中的最大的数字,那么最小的数字重新排列,重新排列最大数量的立即崩溃。因此,该算法的所有实现都包含检查此类情况并用正确的索引替换索引。在我的实现中,一次检查就足够了:

  maximalNumberIndex = minimalNumberIndex;
}

Реализация на Cito

Cito – язык либ, язык транслятор. На нем можно писать для C, C++, C#, Java, JavaScript, Python, Swift, TypeScript, OpenCL C, при этом совершенно ничего не зная про эти языки. Исходный код на языке Cito транслируется в исходный код на поддерживаемых языках, далее можно использовать как библиотеку, либо напрямую, исправив сгенеренный код руками. Эдакий Write once – translate to anything.
Double Selection Sort на cito:

{
    public static int[] sort(int[]# numbers, int length)
    {
        int[]# sortedNumbers = new int[length];
        for (int i = 0; i < length; i++) {
            sortedNumbers[i] = numbers[i];
        }
        for (int leftCursor = 0; leftCursor < length / 2; leftCursor++) {
            int minimalNumberIndex = leftCursor;
            int minimalNumber = sortedNumbers[leftCursor];

            int rightCursor = length - (leftCursor + 1);
            int maximalNumberIndex = rightCursor;
            int maximalNumber = sortedNumbers[maximalNumberIndex];

            for (int cursor = leftCursor; cursor <= rightCursor; cursor++) { int cursorNumber = sortedNumbers[cursor]; if (minimalNumber > cursorNumber) {
                    minimalNumber = cursorNumber;
                    minimalNumberIndex = cursor;
                }
                if (maximalNumber < cursorNumber) {
                    maximalNumber = cursorNumber;
                    maximalNumberIndex = cursor;
                }
            }

            if (leftCursor == maximalNumberIndex) {
                maximalNumberIndex = minimalNumberIndex;
            }

            int fromNumber = sortedNumbers[leftCursor];
            int toNumber = sortedNumbers[minimalNumberIndex];
            sortedNumbers[minimalNumberIndex] = fromNumber;
            sortedNumbers[leftCursor] = toNumber;

            fromNumber = sortedNumbers[rightCursor];
            toNumber = sortedNumbers[maximalNumberIndex];
            sortedNumbers[maximalNumberIndex] = fromNumber;
            sortedNumbers[rightCursor] = toNumber;
        }
        return sortedNumbers;
    }
} 

链接

https://gitlab.com/demensdeum /algorithms/-/tree/master/sortAlgorithms/doubleSelectionSort
https://github.com/pfusik/cito

来源

https://www.researchgate.net/publication/330084245_Improved_Double_Selection_Sort_using_Algorithm
http://algolab.valemak.com/selection-double
https://www.geeksforgeeks.org/sorting-algorithm-slightly-improves-selection-sort/

鸡尾酒调酒器分类

鸡尾酒调酒器分类–摇床排序,双向冒泡排序的一种变体。
该算法的工作原理如下:

  1. 选择循环中搜索的初始方向(通常是从左到右)
  2. 接下来在循环中成对检查数字
  3. 如果下一个元素更大,则交换它们
  4. 完成后,搜索过程会以相反的方向重新开始
  5. 重复搜索,直到没有更多的排列

该算法的时间复杂度与bubble类似– O(n2)

PHP 实现示例:

<?php

function cocktailShakeSort($numbers)
{
    echo implode(",", $numbers),"\n";
    $direction = false;
    $sorted = false;
    do {
        $direction = !$direction;        
        $firstIndex = $direction == true ? 0 : count($numbers) - 1;
        $lastIndex = $direction == true ? count($numbers) - 1 : 0;
        
        $sorted = true;
        for (
            $i = $firstIndex;
            $direction == true ? $i < $lastIndex : $i > $lastIndex;
            $direction == true ? $i++ : $i--
        ) {
            $lhsIndex = $direction ? $i : $i - 1;
            $rhsIndex = $direction ? $i + 1 : $i;

            $lhs = $numbers[$lhsIndex];
            $rhs = $numbers[$rhsIndex];

            if ($lhs > $rhs) {
                $numbers[$lhsIndex] = $rhs;
                $numbers[$rhsIndex] = $lhs;
                $sorted = false;
            }
        }
    } while ($sorted == false);

    echo implode(",", $numbers);
}

$numbers = [2, 1, 4, 3, 69, 35, 55, 7, 7, 2, 6, 203, 9];
cocktailShakeSort($numbers);

?>

Ссылки

https://gitlab.com/demensdeum/algorithms/-/blob/master/sortAlgorithms/cocktailShakerSort/cocktailShakerSort.php

Источники

https://www.youtube.com/watch?v=njClLBoEbfI
https://www.geeksforgeeks.org/cocktail-sort/
https://rosettacode.org/wiki/Sorting_algorithms/Cocktail_sort