Raspberry PI 3 als WLAN-Router

Im Internet gibt es viele Artikel darüber, wie man aus einem Raspberry Pi (RPI) einen WLAN-Router baut. In diesem Beitrag werde ich kurz meine Methode zum Erstellen eines WLAN-Routers mit einer Sing-Box an Bord beschreiben. Die beschriebene Methode funktioniert zum jetzigen Zeitpunkt, und in Zukunft kann sich noch viel ändern. Nutzen Sie diese Notiz also als groben Überblick darüber, was auf Sie zukommt.

SSH

Für diejenigen, die nicht wissen, wie man mit OpenWrt arbeitet, empfehle ich die Installation von dietPI.
Verbinden Sie RPI über eth0 mit Ihrem aktuellen Router und verbinden Sie sich dort über SSH. Sie können die RPI-IP-Adresse im DHCP-Panel des Routers herausfinden. Stellen Sie eine direkte Verbindung zum Root her, zum Beispiel so:

ssh root@[IP_ADDRESS]

WLAN-Adapter

Das eingebaute RPI3 erwies sich ehrlich gesagt als schwach und unterstützt kein 5GHz. Deshalb habe ich den RITMIX RWA-150-Adapter über USB 2 an den Realtek RTL8811CU-Chipsatz angeschlossen. Die Treiber wurden dem Linux-Kernel hinzugefügt, der in meiner dietPi-Version enthalten war. Als nächstes habe ich mit dietpi-config das integrierte WLAN komplett ausgeschaltet. Infolgedessen war nur noch ein WLAN0-USB-Adapter übrig.

Zugangspunkt

Das standardmäßige dietPI-Passwort für Root lautet dietpi. Sobald die Verbindung hergestellt ist, werden Sie vom dietPI-Installationsprogramm/Konfigurator begrüßt. Wenn Sie fertig sind, müssen Sie aufgrund des Neustarts des Geräts erneut eine Verbindung herstellen.

Zuerst müssen Sie hostapd konfigurieren, damit Geräte Ihren Zugangspunkt sehen können. Wenn hostapd nicht installiert ist, installieren Sie es über APK.

Als nächstes müssen Sie eine Konfiguration für hostapd schreiben. Beispiel meiner Konfiguration:

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

Die Bedeutung der Hostapd-Konfiguration finden Sie im Handbuch. Wichtig ist jedoch, dass Sie es selbst konfigurieren – den Kanal (2,4 GHz oder 5 GHz), den Ländercode, sonst können Ihre lokalisierten Geräte ohne diesen korrekt mit dem Access Point funktionieren, ich habe dies bereits getan und weiß es, also stellen Sie Ihr Land sorgfältig ein.

DHCP

Als nächstes installieren und konfigurieren Sie dnsmasq, um DHCP zu implementieren. Dies ist notwendig, damit die angeschlossenen Computer die IP-Adresse und den DNS-Server ermitteln können.
Beispiel meiner Konfiguration:

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

Dies ist die Mindestkonfiguration, die es Ihnen ermöglicht, eine Verbindung zu einem Zugangspunkt herzustellen und eine IP-Adresse zu erhalten. Als nächstes müssen Sie Routing und NAT konfigurieren. Dies ist notwendig, damit angeschlossene Computer auf das Internet zugreifen können.

Hier fällt der Hinweis in die Kategorie der typischen Routing-Einrichtung auf einem regulären Debian-kompatiblen System, über die es im Internet viele Artikel gibt. Dann hängt alles davon ab, welche Ziele Sie verfolgen, zum Beispiel die Verbindung zu einem externen Server als neue Schnittstelle im System oder einfach wlan0 <-> eth0, hier enden die RPI-Besonderheiten, dann konfigurieren Sie es nach Ihrem Geschmack.

Ich möchte auch die Notwendigkeit erwähnen, benutzerdefinierte Systemdienste über systemctl zu konfigurieren; Möglicherweise besteht die Notwendigkeit, Dienste in einer Kette zu verbinden. All dies steht in den Systemctl-Handbüchern im Netzwerk. Wenn es Probleme auf der Serviceebene gibt, überprüfen Sie die Protokolle in Journalctl.

Schlussfolgerung

Aus den Geschwindigkeitsmessungen konnten wir etwa 50 Mbit/s aus RPI3 über WLAN herausholen (nach Anschluss eines 5-GHz-Adapters), was einen Geschwindigkeitsverlust von der Hälfte im Vergleich zur direkten Verbindung mit dem Router bedeutet. Ich gebe zu, dass Sie mit produktiveren RPI-Modellen bessere Ergebnisse erzielen können, aber auch spezielle OpenWrt-Geräte und vorgefertigte Lösungen sind möglicherweise besser für Ihre Anforderungen geeignet.

Quellen

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

Lokale Bilderzeugung: ComfyUI- und FLUX-Modell

Heutzutage sind Sie nicht mehr auf Cloud-Dienste angewiesen: Sie können hochwertige Bilder vollständig auf Ihrer eigenen Hardware erstellen. In diesem Beitrag beschreibe ich, wie Sie das moderne FLUX-Modell mithilfe von ComfyUI lokal auf Ihrem Computer ausführen.

ComfyUI verwendet eine knotenbasierte Architektur. Dies ermöglicht Ihnen:
– Volle Kontrolle über jede Generationsstufe.
– Einfaches Teilen vorgefertigter „Workflows“

FLUX ist ein großes Modell, daher sind die Hardwareanforderungen höher als bei SD 1.5 oder SDXL:
Grafikkarte (GPU): Nvidia RTX mit 12 GB VRAM oder höher (für komfortables Arbeiten). Wenn Sie über 8 GB oder weniger verfügen, müssen Sie die quantisierten Versionen (GGUF oder NF4) verwenden.
Arbeitsspeicher (RAM): mindestens 16 GB (vorzugsweise 32 GB und mehr).
Speicherplatz: Ungefähr 20–50 GB für Modelle und Komponenten.

Der einfachste Weg, FLUX zu starten, ist die Verwendung einer vorgefertigten Vorlage. Suchen Sie einfach im Workflow-Fenster nach „flux text to image“ und installieren Sie es.

Schreiben Sie im Knoten „Text to Image (Flux.1 Dev)“ eine Eingabeaufforderung auf Englisch, wählen Sie die Auflösung aus (FLUX funktioniert gut mit 1024 x 1024 und sogar höher) und drücken Sie RUN.

Die erste Generation kann einige Zeit dauern, da die Modelle in den Grafikkartenspeicher geladen werden.

https://github.com/comfyanonymous/ComfyUI

Ausführen von macOS in Docker

Es ist möglich, macOS in Docker auszuführen, trotz der Einwände von Leuten, die sagen, dass dies unmöglich sei, und angeblich verfügt macOS über Schutzsysteme, die dem widerstehen können.

Einige der klassischen Möglichkeiten, macOS auf PC-Rechnern auszuführen, waren in der Vergangenheit:
*Hackintosh
* Virtualisierung, zum Beispiel mit VMWare

Hackintosh setzt das Vorhandensein von Hardware voraus, die dem Original-Mac ähnelt oder diesem sehr nahe kommt. Die Virtualisierung stellt bestimmte Anforderungen an die Hardware, jedoch im Allgemeinen nicht so streng wie im Fall von Hackintosh. Allerdings kommt es bei der Virtualisierung zu Performance-Problemen, da macOS nicht für das Arbeiten in einer virtuellen Umgebung optimiert ist.

Seit kurzem ist es möglich, macOS in Docker auszuführen. Möglich wird dies durch das Docker-OSX-Projekt, das vorgefertigte macOS-Images zur Ausführung auf Docker bereitstellt. Es ist erwähnenswert, dass Docker-OSX kein offizielles Apple-Projekt ist und von diesem nicht unterstützt wird. Sie können jedoch macOS auf Docker ausführen und es zum Entwickeln und Testen von Anwendungen verwenden.

Eines der ersten Projekte, das macOS in Docker ausführt:
https://github.com/sickcodes/Docker-OSX

Allerdings konnte ich es nie vollständig starten; Nach dem Laden in Recovery OS fielen meine Tastatur und meine Maus einfach ab und ich konnte die Installation nicht fortsetzen. Gleichzeitig funktioniert im ersten Bootmenü die Tastatur. Vielleicht liegt es daran, dass dieses Projekt nicht mehr so ​​aktiv unterstützt wird und es bei der Ausführung unter Windows 11 + WSL2 + Ubuntu einige spezifische Probleme gibt.

Eines der derzeit aktivsten Projekte:
https://github.com/dockur/macos

Ermöglicht die Ausführung von macOS in Docker, die Schnittstelle funktioniert über den Browser per VNC(?)-Weiterleitung. Nach dem Start ist macOS unter http://localhost:5900 verfügbar

Ich habe es geschafft, dieses Projekt auszuführen und macOS Big Sur (Minute 2020) unter Windows 11 + WSL2 + Ubuntu zu installieren, aber nur durch Ändern der Compose-Datei, nämlich:

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

VERSION: „11“ ist die Version von macOS, in diesem Fall Big Sur
RAM_SIZE: „8G“ ist die für macOS zugewiesene RAM-Größe
CPU_CORES: „4“ ist die Anzahl der CPU-Kerne, die macOS zugewiesen sind

Derzeit ist auch die Ausführung von macOS Tahoe (16) möglich, allerdings gibt es eine Reihe von Problemen, die die Projektentwickler beherzt zu lösen versuchen.

Diese originelle Art, macOS zu starten, ermöglicht es Ihnen, es auf Ihrer Nicht-Mac-Hardware auszuprobieren und sich, nachdem Sie genug gelitten haben, einen Mac zu kaufen. Es kann jedoch zum Testen von Software auf älteren Systemen und für die allgemeine Entwicklung nützlich sein.

Swift in WSL2 (Linux) erstellen

Das Swift-Ökosystem entwickelt sich aktiv außerhalb der Apple-Plattformen, und heute ist es recht komfortabel, darin unter Windows mit dem Windows-Subsystem für Linux (WSL2) zu schreiben. Es ist zu bedenken, dass für Assemblys unter Linux/WSL eine schlanke Version von Swift verfügbar ist – ohne proprietäre Apple-Frameworks (wie SwiftUI, UIKit, AppKit, CoreData, CoreML, ARKit, SpriteKit und andere iOS/macOS-spezifische Bibliotheken), aber für Konsolen-Dienstprogramme und das Backend ist dies mehr als ausreichend. In diesem Beitrag werden wir den Prozess der Vorbereitung der Umgebung und der Erstellung des Swift-Compilers aus dem Quellcode in WSL2 Schritt für Schritt durchgehen (am Beispiel von Ubuntu/Debian).

Wir aktualisieren die Liste der Pakete und das System selbst:

sudo apt update && sudo apt upgrade -y

Installieren Sie die erforderlichen Abhängigkeiten für den Build:

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

Installieren Sie den Compiler und Linker (LLVM und LLD):

sudo apt install -y llvm lld

Klonen Sie das Swift-Repository mit allen Abhängigkeiten:

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

Installieren Sie „swiftly“ und fertiges Swift mit Swiftc

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

Beginnen wir mit dem Build (dies wird lange dauern):

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

Nachdem der Build abgeschlossen ist, fügen Sie den Pfad zum Compiler zu PATH hinzu (geben Sie Ihren Pfad zum Build-Ordner an):

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

Wir überprüfen, ob die installierte Version von Swift funktioniert:

swift --version

Erstellen Sie eine Testdatei und führen Sie sie aus:

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

Sie können die Binärdatei auch kompilieren und ausführen:

swiftc hello.swift
./hello

Quellen

Musterinterpreter in der Praxis

Im letzten Artikel haben wir uns mit der Theorie des Interpreter-Musters befasst, gelernt, was ein AST-Baum ist und wie man terminale und nicht-terminale Ausdrücke abstrahiert. Lassen Sie uns dieses Mal von der Theorie Abstand nehmen und sehen, wie dieses Muster in ernsthaften kommerziellen Projekten angewendet wird, die wir alle täglich verwenden!

Spoiler: Möglicherweise verwenden Sie gerade das Interpreter-Muster, indem Sie einfach diesen Text in Ihrem Browser lesen!

Eines der auffälligsten und vielleicht wichtigsten Beispiele für die Verwendung dieses Musters in der Branche ist JavaScript. Die ursprünglich „auf dem Knie“ entstandene Sprache funktioniert heute dank des Interpretationskonzepts auf Milliarden von Geräten.

10 Tage, die das Internet verändert haben

Die Geschichte von JavaScript ist voller Legenden. Im Jahr 1995 erhielt Brendan Eich während seiner Arbeit bei Netscape Communications die Aufgabe, eine einfache Skriptsprache zu entwickeln, die direkt in einem Browser (Netscape Navigator) ausgeführt werden konnte, um Webseiten interaktiv zu gestalten. Das Management wollte etwas mit einer Syntax, die dem damals sehr beliebten Java ähnelte, aber nicht für professionelle Ingenieure, sondern für Webdesigner gedacht war.

Eich hatte nur 10 Tage Zeit, um den ersten Prototyp der Sprache zu schreiben, die damals Mocha hieß (damals LiveScript und aus Marketinggründen erst JavaScript). Der Ansturm kam nicht von ungefähr: Microsoft war ihm dicht auf den Fersen und bereitete gleichzeitig aktiv seine eigene Skriptsprache VBScript für die Einbettung in den Internet Explorer-Browser vor. Netscape musste dringend seine Antwort veröffentlichen, um im drohenden Browserkrieg nicht zu verlieren.

Es war einfach keine Zeit, einen komplexen Compiler in Maschinencode zu schreiben. Die offensichtlichste und schnellste Lösung für Eich war die Architektur des klassischen Interpreter.

Der erste Interpreter (SpiderMonkey) funktionierte folgendermaßen:

  1. Der Textquellcode des Skripts wurde von der Seite gelesen.
  2. Der lexikalische Analysator hat den Text in Token zerlegt.
  3. Der Parser hat einen Abstract Syntax Tree (AST) erstellt. In Bezug auf das Interpreter-Muster bestand dieser Baum aus terminalen Ausdrücken (Zeichenfolgen, Zahlen wie 42) und nicht-terminalen Ausdrücken (Funktionsaufrufe, Anweisungen wie If, ​​​​While).
  4. Dann „durchlief“ die virtuelle Maschine diesen Baum Schritt für Schritt und führte die darin eingebetteten Anweisungen an jedem Knoten aus (wobei sie eine Methode ähnlich Interpret() aufrief).

Kontext und Objekte

Erinnern Sie sich an das Context-Objekt, das wir in der klassischen Implementierung an die Methode Interpret(Context context) übergeben mussten? Der Interpreter benötigt es, um den aktuellen Speicherzustand zu speichern.

Im Fall von JavaScript wird die Rolle dieses Kontexts auf der obersten Ebene von einem globalen Objekt (z. B. einem Fenster in einem Browser) übernommen. Wenn Ihr AST-Knoten versucht, beispielsweise über document.write(“Hello”) Text auf den Bildschirm zu schreiben, greift der Interpreter auf seinen Kontext (das Dokumentobjekt) zu und ruft die gewünschte interne Browser-API auf.

Dank des Interpreters kann JavaScript so einfach mit dem DOM (Document Object Model) interagieren – das sind alles nur Objekte in einem Kontext, auf die über Baumknoten zugegriffen wird.

Entwicklung des Interpreters: JIT-Kompilierung

Historisch gesehen ist JS in Browsern lange Zeit ein „reiner“ Interpreter geblieben. Und das hatte einen großen Nachteil: langsame Geschwindigkeit. Das Parsen des Baums und das langsame Durchlaufen jedes Knotens bei jeder Ausführung des Skripts verlangsamte komplexe Webanwendungen.

Mit der Einführung der V8-Engine von Google (integriert in Chrome) im Jahr 2008 kam es zu einer Revolution. Ingenieure erkannten, dass ein Dolmetscher für das moderne Web nicht ausreicht. Die Engine ist komplexer geworden: Sie erstellt immer noch den AST-Baum, verwendet aber jetzt die JIT-Kompilierung (Just-In-Time).

Moderne JS-Engines (V8, SpiderMonkey) funktionieren wie eine komplexe Pipeline:

  1. Der schnelle und einfache Basisinterpreter beginnt sofort mit der Ausführung Ihres JS-Codes, ohne überhaupt auf die Kompilierung warten zu müssen (das klassische Muster funktioniert hier immer noch).
  2. Parallel dazu überwacht die Engine „heiße“ Codeabschnitte (Schleifen oder Funktionen, die tausende Male aufgerufen werden).
  3. Diese Abschnitte werden vom JIT-Compiler unter Umgehung des langsamen Interpreters direkt in optimierten Maschinencode kompiliert.

Es war diese Kombination aus dem sofortigen Start des Interpreters und der Rechenleistung der Kompilierung, die es JavaScript ermöglichte, die Welt zu erobern und zur Sprache von Servern (Node.js) und mobilen Anwendungen (React Native) zu werden.

Dolmetscher in der Spielebranche

Trotz der Dominanz von C++ im Heavy Computing ist das Interpreter-Muster ein Industriestandard in der Spieleentwicklung zum Erstellen von Spiellogik. Wofür? Damit Spieleentwickler Spiele erstellen können, ohne das Risiko einzugehen, die Engine „abzuwerfen“ oder sie ständig neu kompilieren zu müssen.

Ein hervorragendes historisches Beispiel ist UnrealScript – die Sprache, in der die Logik der Spiele Unreal Tournament und Gears of War in den Unreal Engines 1, 2 und 3 geschrieben wurde. Der Text wurde in einen kompakten abstrakten Maschinenbytecode kompiliert, der dann Schritt für Schritt von der virtuellen Maschine der Engine (interpretiert) wurde.

Visuelle Diagrammskripte (Blueprints)

Heute wurde Text durch visuelle Programmierung ersetzt – das Blueprints-System in Unreal Engine 4 und 5.

Wenn Sie jemals einen Blueprint in Unreal Engine geöffnet haben, haben Sie viele Knoten gesehen, die durch Kabel verbunden sind. Architektonisch gesehen ist das gesamte Blueprints-Diagramm ein riesiger abstrakter Syntaxbaum (AST), der auf dem Bildschirm gezeichnet wird:

  1. Terminalausdrücke: Konstante Knoten. Zum Beispiel ein Knoten, der einfach die Zahl 42 oder einen String speichert. Sie geben bei der Interpretation einen bestimmten Wert zurück.
  2. Nicht-terminale Ausdrücke: Rechenknoten (Hinzufügen) oder Flusskontrollknoten (Zweig). Sie verfügen über Argumenteingänge, die der Interpreter zunächst rekursiv auswertet, bevor er das Ergebnis als Ausgabepin erzeugt.

Und die Rolle des Kontexts spielt hier die Erinnerung an eine Instanz eines bestimmten Spielobjekts (Akteur). Die Interpretermaschine „geht“ sicher durch dieses Diagramm, fordert Daten an und führt Übergänge durch.

Wo wird der Interpreter sonst noch verwendet?

Das Interpretermuster kann in fast jedem komplexen System gefunden werden, in dem dynamische Anweisungen ausgeführt werden müssen. Hier sind nur einige Beispiele aus kommerzieller Software:

  • Interpretierte Programmiersprachen (Python, Ruby, PHP). Ihre gesamte Laufzeit basiert auf dem klassischen Muster. Beispielsweise analysiert die CPython-Referenzimplementierung Ihr .py-Skript zunächst in ein AST, kompiliert es in Bytecode und dann interpretiert eine riesige virtuelle Maschine (Rechenschleife) diesen Bytecode Schritt für Schritt.
  • Java Virtual Machine (JVM). Zunächst wird Java-Code nicht in Maschinenanweisungen, sondern in Bytecode kompiliert. Wenn Sie die Anwendung ausführen, fungiert die JVM als Interpreter (allerdings mit aggressiver JIT-Kompilierung, genau wie in V8).
  • Datenbanken und SQL Wenn Sie eine SQL-Abfrage (SELECT * FROM user) in PostgreSQL oder MySQL ausgeben, fungiert die Datenbank-Engine als Interpreter. Es führt eine lexikalische Analyse durch, erstellt einen AST-Abfragebaum, generiert einen Ausführungsplan und „interpretiert“ diesen Plan dann buchstäblich, indem es über die Zeilen der Tabellen iteriert.
  • Reguläre Ausdrücke (RegEx). Jede Engine für reguläre Ausdrücke analysiert intern ein Zeichenfolgenmuster (z. B. ^\d{3}-\d{2}$) in ein Zustandsdiagramm (NFA/DFA-Automaten), das der interne Interpreter dann durchläuft und jedes Eingabezeichen mit den Eckpunkten dieses Diagramms abgleicht.
  • Unity Shader Graph / Unreal Material Editor – visuelle Knoten in modularen Shader-Code (GLSL/HLSL) interpretieren.
  • Blender Geometry Nodes – interpretieren mathematische und geometrische Operationen, um prozedural 3D-Modelle in Echtzeit zu generieren.

Gesamt

Das Interpreter-Muster geht längst über den Rahmen des „Schreibens eines eigenen Taschenrechners“ hinaus. Dies ist der leistungsstärkste Industriestandard. Von JavaScript-Engines, die täglich Gigabytes an Code hinter den Kulissen von Browsern ausführen, bis hin zu Spieledesignern, die es Ihnen ermöglichen, komplexe Logik ohne Kenntnisse von C++ zu erstellen, bleiben Interpreter eines der wichtigsten Architekturkonzepte in der modernen IT-Entwicklung.

Ollama-Anruf

Wenn Sie Ollama verwenden und nicht jedes Mal Ihren eigenen API-Wrapper schreiben möchten,
Das Projekt ollama_call vereinfacht die Arbeit erheblich.

Dies ist eine kleine Python-Bibliothek, die es Ihnen ermöglicht, mit einer Funktion eine Anfrage an ein lokales LLM zu senden
und erhalten sofort eine Antwort, auch im JSON-Format.

Installation

pip install ollama-call

Warum wird es benötigt

  • Minimalcode für die Arbeit mit dem Modell;
  • Strukturierte JSON-Antwort zur weiteren Verarbeitung;
  • praktisch für schnelle Prototypen und MVPs;
  • unterstützt bei Bedarf die Streaming-Ausgabe.

Beispiel verwenden

from ollama_call import ollama_call

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

print(response)

Wenn es besonders nützlich ist

  • Sie schreiben Skripte oder Dienste auf Basis von Ollama;
  • benötigen ein vorhersehbares Antwortformat;
  • Es besteht kein Wunsch, schwere Frameworks zu verbinden.

Gesamt

ollama_call ist ein leichter und übersichtlicher Wrapper für die Arbeit mit Ollama aus Python.
Eine gute Wahl, wenn Einfachheit und schnelle Ergebnisse wichtig sind.

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

SFAP: ein modulares Framework für die moderne Datenerfassung und -verarbeitung

Im Rahmen der aktiven Entwicklung von Automatisierung und künstlicher Intelligenz besteht die Aufgabe, effektiv zu sammeln,
Das Bereinigen und Transformieren von Daten wird von entscheidender Bedeutung. Die meisten Lösungen schließen nur
Dabei handelt es sich um separate Phasen dieses Prozesses, die eine komplexe Integration und Unterstützung erfordern.

SFAP (Seek · Filter · Adapt · Publish) ist ein Open-Source-Projekt in Python,
das einen ganzheitlichen und erweiterbaren Ansatz für die Verarbeitung von Daten in allen Phasen ihres Lebenszyklus bietet:
von der Quellensuche bis zur Veröffentlichung des fertigen Ergebnisses.

Was ist SFAP?

SFAP ist ein asynchrones Framework, das auf einem klaren Konzept einer Datenverarbeitungspipeline basiert.
Jede Stufe ist logisch getrennt und kann unabhängig erweitert oder ersetzt werden.

Das Projekt basiert auf dem Architekturmuster Chain of Responsibility, das Folgendes bietet:

  • Flexibilität der Pipeline-Konfiguration;
  • einfaches Testen einzelner Stufen;
  • Skalierbarkeit für hohe Lasten;
  • saubere Aufgabentrennung zwischen den Komponenten.

Hauptphasen der Pipeline

Seek – Datensuche

In dieser Phase werden Datenquellen entdeckt: Webseiten, APIs, Dateispeicher
oder andere Informationsflüsse. SFAP erleichtert die Anbindung neuer Quellen ohne Änderungen
der Rest des Systems.

Filter – Filtern

Die Filterung soll Rauschen entfernen: irrelevante Inhalte, Duplikate, technische Elemente
und Daten von geringer Qualität. Dies ist entscheidend für nachfolgende Verarbeitungsschritte.

Anpassen – Anpassung und Verarbeitung

Die Anpassungsphase ist für die Datentransformation verantwortlich: Normalisierung, Strukturierung,
semantische Verarbeitung und Integration mit KI-Modellen (einschließlich generativer).

Veröffentlichen – Veröffentlichung

Im letzten Schritt werden die Daten im Zielformat veröffentlicht: Datenbanken, APIs, Dateien, externe Dienste
oder Content-Plattformen. SFAP schränkt die Art und Weise, wie das Ergebnis geliefert wird, nicht ein.

Hauptmerkmale des Projekts

  • Asynchrone Architektur basierend auf asyncio
  • Modularität und Erweiterbarkeit
  • Unterstützung für komplexe Verarbeitungspipelines
  • Bereit für die Integration mit AI/LLM-Lösungen
  • Geeignet für hochbelastete Systeme

Praktische Anwendungsfälle

  • Aggregation und Analyse von Nachrichtenquellen
  • Datensätze für maschinelles Lernen vorbereiten
  • Automatisierte Content-Pipeline
  • Bereinigung und Normalisierung großer Datenströme
  • Integration von Daten aus heterogenen Quellen

Erste Schritte mit SFAP

Alles, was Sie brauchen, um loszulegen, ist:

  1. Klonen Sie das Projekt-Repository;
  2. Python-Abhängigkeiten installieren;
  3. Definieren Sie Ihre eigenen Pipeline-Schritte;
  4. Starten Sie einen asynchronen Datenverarbeitungsprozess.

Das Projekt lässt sich leicht an spezifische Geschäftsaufgaben anpassen und kann mit dem System wachsen,
ohne sich in einen Monolithen zu verwandeln.

Schlussfolgerung

SFAP ist nicht nur ein Parser oder Datensammler, sondern ein vollwertiges Framework zum Erstellen
moderne Daten-Pipeline-Systeme. Es eignet sich für Entwickler und Teams, die sich darum kümmern
skalierbar, architektonisch sauber und datenbereit.
Der Quellcode des Projekts ist auf GitHub verfügbar:
https://github.com/demensdeum/SFAP

Warum kann ich den Fehler nicht beheben?

Sie verbringen Stunden damit, am Code zu arbeiten, Hypothesen durchzugehen und die Bedingungen anzupassen, aber der Fehler wird trotzdem reproduziert. Kommt Ihnen das bekannt vor? Dieser Zustand der Frustration wird oft als „Geisterjagd“ bezeichnet. Das Programm scheint sein eigenes Leben zu führen und Ihre Korrekturen zu ignorieren.

Einer der häufigsten – und ärgerlichsten – Gründe für diese Situation ist die Suche nach einem Fehler an der völlig falschen Stelle in der Anwendung.

Die Falle der „falschen Symptome“

Wenn wir einen Fehler sehen, wird unsere Aufmerksamkeit auf die Stelle gelenkt, an der er „geschossen“ hat. Aber in komplexen Systemen ist das Auftreten eines Fehlers (Absturz oder falscher Wert) nur das Ende einer langen Kette von Ereignissen. Wenn Sie versuchen, das Ende zu beheben, bekämpfen Sie die Symptome, nicht die Krankheit.

Hier kommt das Flussdiagramm-Konzept ins Spiel.

Wie es in der Realität funktioniert

Natürlich ist es nicht notwendig, jedes Mal direkt ein Flussdiagramm auf Papier zu zeichnen, aber es ist wichtig, es im Kopf zu haben oder als architektonischen Leitfaden zur Hand zu haben. Mit einem Flussdiagramm können Sie den Betrieb einer Anwendung als Ergebnisbaum visualisieren.

Ohne diese Struktur zu verstehen, tappt der Entwickler oft im Dunkeln. Stellen Sie sich die Situation vor: Sie bearbeiten die Logik in einem Bedingungszweig, während die Anwendung (aufgrund eines bestimmten Parametersatzes) zu einem völlig anderen Zweig wechselt, an den Sie noch nicht einmal gedacht haben.


Ergebnis: Sie verbringen Stunden mit der „perfekten“ Codekorrektur in einem Teil des Algorithmus, was natürlich nicht dazu beiträgt, das Problem in einem anderen Teil des Algorithmus zu beheben, wo es tatsächlich fehlschlägt.


Algorithmus zur Beseitigung eines Fehlers

Um nicht mehr an einer verschlossenen Tür herumzurütteln, müssen Sie Ihre Herangehensweise an die Diagnose ändern:

  • Finden Sie den Status im Ergebnisbaum:Bevor Sie Code schreiben, müssen Sie den Pfad genau bestimmen, den die Anwendung genommen hat. An welchem ​​Punkt hat die Logik eine falsche Wendung genommen? Welcher konkrete Staat (Staat) hat zu dem Problem geführt?
  • Reproduktion ist 80 % des Erfolgs: Dies wird normalerweise durch Tester und automatisierte Tests durchgeführt. Wenn der Fehler „schwebt“, wird die Entwicklung in den Prozess einbezogen, um gemeinsam nach Bedingungen zu suchen.
  • Verwenden Sie so viele Informationen wie möglich: Protokolle, Betriebssystemversion, Geräteparameter, Verbindungstyp (WLAN/5G) und sogar ein bestimmter Telekommunikationsbetreiber sind für die Lokalisierung wichtig.

„Foto“ vom Moment des Fehlers

Um das Problem zu beheben, müssen Sie im Idealfall über den vollständigen Status der Anwendung zum Zeitpunkt der Reproduktion des Fehlers verfügen. Auch Interaktionsprotokolle sind von entscheidender Bedeutung: Sie zeigen nicht nur den Endpunkt, sondern auch den gesamten Benutzerpfad (welche Aktionen dem Fehler vorausgingen). Dies hilft zu verstehen, wie ein ähnlicher Zustand wieder hergestellt werden kann.

Zukunftstipp: Wenn Sie auf einen komplexen Fall stoßen, fügen Sie diesem Codeabschnitt erweiterte Debug-Protokollierungsinformationen hinzu, für den Fall, dass die Situation erneut auftritt.


Das Problem „schwer fassbarer“ Zustände im Zeitalter der KI

In modernen Systemen, die LLM (Large Language Models) verwenden, wird der klassische Determinismus („ein Eingang, ein Ausgang“) häufig verletzt. Sie können genau die gleichen Eingabedaten übergeben, erhalten aber ein anderes Ergebnis.

Dies geschieht aufgrund des Nichtdeterminismus moderner Produktionssysteme:

  • GPU-Parallelität: GPU-Gleitkommaoperationen sind nicht immer assoziativ. Aufgrund der parallelen Ausführung von Threads kann sich die Reihenfolge, in der Zahlen hinzugefügt werden, geringfügig ändern, was sich auf das Ergebnis auswirken kann.
  • GPU-Temperatur und -Drosselung: Ausführungsgeschwindigkeit und Lastverteilung können vom physischen Zustand der Hardware abhängen. In großen Modellen häufen sich diese mikroskopischen Unterschiede und können dazu führen, dass am Ausgang ein anderer Token ausgewählt wird.
  • Dynamisches Batching: In der Cloud wird Ihre Anfrage mit anderen kombiniert. Unterschiedliche Batch-Größen verändern die Mathematik der Berechnungen in den Kerneln.

Unter solchen Bedingungen wird es fast unmöglich, „denselben Zustand“ zu reproduzieren. Nur ein statistischer Testansatz kann Sie hier retten.


Wenn die Logik versagt: Speicherprobleme

Wenn Sie mit „unsicheren“ Sprachen (C oder C++) arbeiten, kann der Fehler aufgrund einer Speicherbeschädigung auftreten.

Dies sind die schwerwiegendsten Fälle: Ein Fehler in einem Modul kann Daten in einem anderen „überschreiben“. Dies führt zu völlig unerklärlichen und isolierten Fehlern, die mit der normalen Anwendungslogik nicht nachvollzogen werden können.

Wie schützt man sich auf architektonischer Ebene?

Um solche „mystischen“ Fehler zu vermeiden, sollten Sie moderne Ansätze verwenden:

  • Multithread-Programmiermuster:Eine klare Synchronisierung eliminiert Race Conditions.
  • Thread-sichere Sprachen: Tools, die Speichersicherheit zur Kompilierungszeit garantieren:
    • Rust: Das Eigentümersystem eliminiert Speicherfehler.
    • Swift 6-Parallelität:Starke Datenisolationsprüfungen.
    • Erlang: Vollständige Prozessisolation durch das Akteurmodell.

Zusammenfassung

Bei der Behebung eines Fehlers geht es nicht darum, neuen Code zu schreiben, sondern darum, zu verstehen, wie der alte funktioniert. Denken Sie daran: Sie könnten Zeit damit verschwenden, einen Zweig zu bearbeiten, den das Management nicht einmal berührt. Erfassen Sie den Zustand des Systems, berücksichtigen Sie den Faktor des KI-Nichtdeterminismus und wählen Sie sichere Tools aus.

Blockierungen in der Praxis ohne Formalin blockieren

Das Blockdiagramm ist ein visuelles Werkzeug, das dazu beiträgt, einen komplexen Algorithmus in eine verständliche und strukturierte Folge von Aktionen zu verwandeln. Von der Programmierung bis zum Geschäftsprozessmanagement dienen sie als universelle Sprache für die Visualisierung, Analyse und Optimierung der komplexesten Systeme.

Stellen Sie sich eine Karte vor, auf der anstelle von Straßen Logik und anstelle von Städten – Aktionen ist. Dies ist ein Blockdiagramm-ein unverzichtbares Werkzeug für die Navigation in den verwirrendsten Prozessen.

Beispiel 1: vereinfachtes Spielstartschema
Um das Arbeitsprinzip zu verstehen, präsentieren wir ein einfaches Spielstartschema.

Dieses Schema zeigt das perfekte Skript, wenn alles ohne Fehler passiert. Aber im wirklichen Leben ist alles viel komplizierter.

Beispiel 2: Erweitertes Schema zum Starten des Spiels mit Datenladen
Moderne Spiele erfordern häufig eine Internetverbindung, um Benutzerdaten, Speichern oder Einstellungen herunterzuladen. Fügen wir diese Schritte zu unserem Schema hinzu.

Dieses Schema ist bereits realistischer, aber was wird passieren, wenn etwas schief geht?

Wie war es: Ein Spiel, das mit dem Verlust des Internets “brach”

“brach”

Zu Beginn des Projekts konnten Entwickler nicht alle möglichen Szenarien berücksichtigen. Zum Beispiel konzentrierten sie sich auf die Hauptlogik des Spiels und überlegten nicht, was passieren würde, wenn der Spieler eine Internetverbindung hat.

In einer solchen Situation würde das Blockdiagramm ihres Codes so aussehen:

In diesem Fall hat das Spiel in der Phase des Wartens auf Daten, die sie aufgrund des Fehlens einer Verbindung nicht erhielt, anstatt einen Fehler oder korrekt zu schließen. Dies führte zum “schwarzen Bildschirm” und zum Einfrieren der Anwendung.

Wie es wurde: Korrektur bei Benutzerbeschwerden

Nach zahlreichen Beschwerden der Benutzer über das Schwebewesen erkannte das Entwicklerteam, dass wir den Fehler korrigieren mussten. Sie nahmen Änderungen am Code vor, indem sie eine Fehlerverarbeitungseinheit hinzufügen, mit der die Anwendung auf den mangelnden Verbindungsmangel korrekt reagiert.

So sieht das korrigierte Blockdiagramm aus, in dem beide Szenarien berücksichtigt werden:

Dank dieses Ansatzes informiert das Spiel jetzt den Benutzer über das Problem und kann in einigen Fällen sogar in den Offline -Modus gehen, sodass Sie das Spiel fortsetzen können. Dies ist ein gutes Beispiel dafür, warum Blockdiagramme so wichtig sind.

Unsicheres Verhalten

Das Hängen und Fehler sind nur ein Beispiel für unvorhersehbare Verhalten des Programms. Bei der Programmierung gibt es ein Konzept von unsicherem Verhalten (undefiniertes Verhalten) – Dies ist eine Situation, in der der Standard der Sprache nicht beschreibt, wie sich das Programm in einem bestimmten Fall verhalten sollte.

Dies kann zu allem führen: vom zufälligen „Müll“ beim Rückzug zum Versagen des Programms oder sogar der schwerwiegenden Sicherheitsanfälligkeit. Unbestimmte Verhalten tritt häufig bei der Arbeit mit dem Gedächtnis auf, zum Beispiel mit Zeilen in der Sprache von C.

Ein Beispiel aus der Sprache c:

Stellen Sie sich vor, der Entwickler hat die Linie in den Puffer kopiert, aber vergessen, das Ende des Null -Symbols (\ 0`) zu addieren, das das Ende der Linie markiert.

So sieht der Code aus:

#include 

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

memcpy(buffer, my_string, 5);

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

Erwartete Ergebnis: “Hallo”
Das wirkliche Ergebnis ist unvorhersehbar.

Warum passiert das? Die Funktion “printf`) mit dem Spezifizierer%S` erwartet, dass die Linie mit einem Nullsymbol endet. Wenn er nicht ist, wird sie die Erinnerung außerhalb des hervorgehobenen Puffers weiter lesen.

Hier ist das Blockdiagramm dieses Prozesses mit zwei möglichen Ergebnissen:

Dies ist ein klares Beispiel dafür, warum die Blockdiagramme so wichtig sind: Sie lassen den Entwickler nicht nur über die ideale Ausführungsweise nachdenken, sondern auch über alle möglichen Fehler, einschließlich solcher Probleme mit niedrigem Niveau, was das Endprodukt viel stabiler und zuverlässiger macht.

Pixel perfekt: Mythos oder Realität in der Ära der Deklarativität?

In der Welt der Schnittstellenentwicklung gibt es ein gemeinsames Konzept – “Pixel perfekt in der Lodge” . Es impliziert die genaueste Reproduktion der Konstruktionsmaschine auf das kleinste Pixel. Lange Zeit war es ein Goldstandard, insbesondere in der Ära eines klassischen Webdesigns. Mit der Ankunft der deklarativen Meile und dem schnellen Wachstum der Vielfalt der Geräte wird das Prinzip von “Pixel Perfect” jedoch zunehmend kurzlebiger. Versuchen wir herauszufinden, warum.

Imperial Wysiwyg vs. Deklarativer Code: Was ist der Unterschied?

Traditionell wurden viele Schnittstellen, insbesondere der Desktop, mit imperativen Ansätzen oder wysiwyg (was Sie sehen, was Sie erhalten) von Herausgebern erstellt. In solchen Tools manipuliert der Designer oder Entwickler direkt mit Elementen und platziert sie auf Leinwand mit Genauigkeit zum Pixel. Es ähnelt der Arbeit mit einem Grafikeditor – Sie sehen, wie Ihr Element aussieht, und Sie können es definitiv positionieren. In diesem Fall war die Leistung von “Pixel Perfect” ein sehr reales Ziel.

Die moderne Entwicklung basiert jedoch zunehmend auf deklarativen Meilen . Dies bedeutet, dass Sie dem Computer nicht sagen, dass er “diese Schaltfläche hier einstellen”, sondern beschreiben, was Sie bekommen möchten. Anstatt die spezifischen Koordinaten des Elements anzuzeigen, beschreiben Sie beispielsweise seine Eigenschaften: “Diese Schaltfläche sollte rot sein, 16px -Eindringlinge von allen Seiten haben und in der Mitte des Behälters stehen.” Freiimvorki wie React, Vue, Swiftui oder Jetpack komponieren nur dieses Prinzip.

Warum “Pixel Perfect” für viele Geräte nicht mit einer deklarativen Meile funktioniert

Stellen Sie sich vor, Sie erstellen eine Anwendung, die auf dem iPhone 15 Pro Max, Samsung Galaxy Fold, iPad Pro und einer 4K -Auflösung gleich gut aussehen soll. Jedes dieser Geräte hat eine unterschiedliche Bildschirmauflösung, Pixeldichte, Parteien und physische Größen.

Wenn Sie den deklarativen Ansatz verwenden, entscheidet das System selbst unter Berücksichtigung aller Parameter, wie Sie Ihre beschriebene Schnittstelle auf einem bestimmten Gerät anzeigen. Sie setzen die Regeln und Abhängigkeiten, nicht die harten Koordinaten.

* Anpassungsfähigkeit und Reaktionsfähigkeit: Das Hauptziel der deklarativen Meilen ist es, adaptive und reaktionsschnelle Schnittstellen zu erstellen. Dies bedeutet, dass sich Ihre Schnittstelle automatisch an die Größe und Ausrichtung des Bildschirms anpassen sollte, ohne die Lesbarkeit zu brechen und aufrechtzuerhalten. Wenn wir auf jedem Gerät „Pixel Perfect“ beantragen möchten, müssten wir unzählige Optionen für dieselbe Schnittstelle erstellen, was die Vorteile des deklarativen Ansatzes vollständig ausgleichen wird.
* Pixeldichte (DPI/PPI): Die Geräte haben eine unterschiedliche Pixeldichte. Das gleiche Element mit der Größe von 100 “virtuellen” Pixeln auf einem Gerät mit hoher Dichte sieht viel kleiner aus als bei einem Gerät mit niedrigem Dichte, wenn Sie die Skalierung nicht berücksichtigen. Deklarative Rahmenbedingungen werden von physischen Pixeln abstrahiert und arbeiten mit logischen Einheiten.
* Dynamischer Inhalt: Inhalt in modernen Anwendungen ist häufig dynamisch – sein Volumen und die Struktur können variieren. Wenn wir hart auf die Pixel gerissen würden, würde jede Änderung des Textes oder des Bildes zum “Zusammenbruch” des Layouts führen.
* Verschiedene Plattformen: Zusätzlich zur Vielfalt der Geräte gibt es verschiedene Betriebssysteme (iOS, Android, Web, Desktop). Jede Plattform verfügt über eigene Design, Standardsteuerungen und Schriftarten. Ein Versuch, auf allen Plattformen eine absolut identische Pixel -Perfect -Schnittstelle zu erstellen, würde zu einem unnatürlichen Typ und einer schlechten Benutzererfahrung führen.

Die alten Ansätze gingen nicht verschwunden, sondern entwickelten

Es ist wichtig zu verstehen, dass der Ansatz zu Schnittstellen keine binäre Wahl zwischen “imperativ” und “deklarativ” ist. Historisch gesehen gab es für jede Plattform ihre eigenen Werkzeuge und Ansätze zur Erstellung von Schnittstellen.

* Native Schnittstellendateien: Für iOS waren es XIB/Storyboards für Android-XML-Markierungsdateien. Diese Dateien sind ein Pixel-Perfekt-Wysiwyg-Layout, das dann wie im Editor im Radio angezeigt wird. Dieser Ansatz ist nirgendwo verschwunden und entwickelt sich weiter und integriert sich in moderne deklarative Rahmen. Zum Beispiel haben Swiftui in Apple und Jetpack in Android auf dem Pfad eines rein deklarativen Code eingeschaltet, behielten gleichzeitig die Möglichkeit, ein klassisches Layout zu verwenden.
* Hybridlösungen: häufig in realen Projekten wird eine Kombination von Ansätzen verwendet. Beispielsweise kann die Grundstruktur der Anwendung deklarativ implementiert werden und für spezifische, die eine genaue Positionierung von Elementen, niedrigere Level, imperative Methoden erfordern, oder native Komponenten, die unter Berücksichtigung der Einzelheiten der Plattform entwickelt wurden.

vom Monolith zur Anpassungsfähigkeit: Wie die Entwicklung von Geräten eine deklarative Meile bildete

Die Welt der digitalen Schnittstellen hat sich in den letzten Jahrzehnten enorme Veränderungen verändert. Von stationären Computern mit festen Genehmigungen kamen wir in die Ära des exponentiellen Wachstums der Vielfalt der Benutzergeräte . Heute sollten unsere Bewerbungen gleich gut funktionieren:

* Smartphones aller Formfaktoren und Bildschirmgrößen.
* Tablets mit ihren einzigartigen Ausrichtungsmodi und einem getrennten Bildschirm.
* Laptops und Desktops mit verschiedenen Monitorengenehmigungen.
* TVS- und Medienzentren , kontrolliert remote. Es ist bemerkenswert, dass selbst für Fernseher, deren Bemerkungen mit einem Minimum an Schaltflächen, oder umgekehrt, mit vielen Funktionen überladen sind, selbst für die Schnittstellen so einfach sind, dass der Code keine spezifische Anpassung für diese Eingabefunktionen benötigt. Die Schnittstelle sollte “als ob für sich selbst” funktionieren, ohne eine zusätzliche Beschreibung des “Wie” mit einer bestimmten Fernbedienung zu interagieren.
* Smart Watches und Wearable -Geräte mit minimalistischen Bildschirmen.
* Virtual Reality Helme (VR) , die einen völlig neuen Ansatz für eine räumliche Schnittstelle erfordern.
* Augmented Reality Device Devices (AR) , die Informationen über die reale Welt anwenden.
* Automobilinformationen und Unterhaltungssysteme .
* Und sogar Haushaltsgeräte : Von Kühlschränken mit sensorischen Bildschirmen und Waschmaschinen mit interaktiven Displays bis hin zu intelligenten Öfen und Systemen des Smart House.

Jedes dieser Geräte verfügt über eigene Merkmale: physikalische Dimensionen, Parteienverhältnisse, Pixeldichte, Eingabemethoden (Touchscreen, Maus, Controller, Gesten, Stimmbefehle) und vor allem die Feinheiten der Benutzerumgebung . Beispielsweise erfordert ein VR Shlesh ein tiefes Eintauchen und eine Smartphone-schnelle und intuitive Arbeit unterwegs, während die Kühlschrankschnittstelle für schnelle Navigation genauso einfach und groß sein sollte.

Klassischer Ansatz: Die Belastung für die Unterstützung einzelner Schnittstellen

In der Zeit der Dominanz von Desktops und der ersten mobilen Geräte war das übliche Geschäft die Erstellung und Unterstützung von von einzelnen Schnittstellendateien oder sogar eines vollständig separaten Schnittstellencodes für jede Plattform .

* Die Entwicklung unter iOS erforderte häufig die Verwendung von Storyboards oder XIB-Dateien in Xcode, das Code auf Objective-C oder Swift schreibt.
* Für Android wurden die XML -Markierungsdateien und der Code auf Java oder Kotlin erstellt.
* Web -Schnittstellen haben HTML/CSS/JavaScript eingeschaltet.
* Für Anwendungen c ++ auf verschiedenen Desktop -Plattformen wurden ihre spezifischen Frameworks und Tools verwendet:
* In Windows waren dies MFC (Microsoft Foundation Classes), Win32 -API mit manuellen Zeichnungselementen oder Verwendung von Ressourcendateien für Dialogfenster und Steuerelemente.
* Kakao (Objektiv-C/Swift) oder die alte Kohlenstoff-API zur direkten Kontrolle der grafischen Grenzfläche wurden in macOS verwendet.
* In Linux/UNIX-ähnlichen Systemen wurden häufig Bibliotheken wie GTK+ oder QT verwendet, die ihre Widgets und Mechanismen zum Erstellen von Schnittstellen bereitstellten, häufig über XML-ähnliche Markierungsdateien (z.

Dieser Ansatz sorgte für eine maximale Kontrolle über jede Plattform, sodass Sie alle spezifischen Funktionen und nativen Elemente berücksichtigen können. Er hatte jedoch einen großen Nachteil: Duplizierung von Bemühungen und enorme Kosten für Unterstützung . Die geringste Änderung des Designs oder der Funktionalität erforderte die Einführung eines Rechts auf mehrere unabhängige Codebasen. Dies wurde zu einem echten Albtraum für Entwicklerteams, verlangsamte die Ausgabe neuer Funktionen und erhöhte die Wahrscheinlichkeit von Fehlern.

deklarative Meilen: Eine einzelne Sprache für Vielfalt

Als Reaktion auf diese schnelle Komplikation erschien die deklarativen Meilen als dominantes Paradigma. Framws wie React, Vue, Swiftui, Jetpack Compose und andere sind nicht nur eine neue Art, Code zu schreiben, sondern eine grundlegende Verschiebung des Denkens.

Die Hauptidee des deklarativen Ansatzes : Anstatt das System zu sagen, wie man jedes Element zeichnet (imperativ), beschreiben wir „was“ wir sehen wollen (deklarativ). Wir setzen die Eigenschaften und den Zustand der Schnittstelle, und das Framework entscheidet, wie Sie sie am besten auf einem bestimmten Gerät anzeigen können.

Dies wurde dank der folgenden wichtigen Vorteile möglich:

1. Abstraktion aus den Details der Plattform: deklarative Fraimvorki sind speziell so konzipiert, dass sie die Details mit niedrigem Level für jede Plattform vergessen. Der Entwickler beschreibt die Komponenten und ihre Beziehungen auf einer höheren Abstraktionsebene unter Verwendung eines einzelnen, übertragenen Codes.
2. Automatische Anpassung und Reaktionsfähigkeit: Freiimvorki Übernehmen Sie die Verantwortung für die automatische Skalierung , ändern Sie das Layout und die Anpassung von Elementen in verschiedene Größen von Bildschirmen, Pixeldichte und Eingabemethoden. Dies wird durch die Verwendung flexibler Layoutsysteme wie Flexbox oder Grid und Konzepte erreicht, die “logischen Pixeln” oder “DP” ähneln.
3.. Dies vereinfacht den Testprozess und bietet vorhersehbarere Benutzererfahrung.
V. Die Teams können sich auf Funktionalität und Design konzentrieren und nicht auf wiederholte Umschreiben derselben Schnittstelle.
5. Freiimvorki kann aktualisiert werden, um neue Technologien zu unterstützen, und Ihr bereits geschriebener Code erhält. Diese Unterstützung ist relativ nahtlos.

Schlussfolgerung

Die deklarative Meile ist nicht nur ein Modetrend, sondern auch die notwendige evolutionäre Stufe , die durch die schnelle Entwicklung von Benutzergeräten verursacht wird, einschließlich der Sphäre des Internet der Dinge (IoT) und intelligente Haushaltsgeräte. Es ermöglicht Entwicklern und Designern, komplexe, adaptive und gleichmäßige Schnittstellen zu erstellen, ohne in endlosen spezifischen Implementierungen für jede Plattform zu ertrinken. Der Übergang von der imperativen Kontrolle über jedes Pixel zur deklarativen Beschreibung des gewünschten Zustands ist eine Erkenntnis, dass in der Welt der zukünftigen Schnittstellen flexibel, übertragen und intuitiv unabhängig davon, welcher Bildschirm angezeigt wird.

Programmierer, Designer und Benutzer müssen lernen, wie man in dieser neuen Welt lebt. Die zusätzlichen Details des Pixel Perfect, das auf ein bestimmtes Gerät oder eine bestimmte Auflösung ausgelegt ist, führen zu unnötigen Zeitkosten für die Entwicklung und Unterstützung. Darüber hinaus funktionieren solche harten Layouts möglicherweise nicht auf Geräten mit nicht standardmäßigen Schnittstellen wie begrenzten Eingangsfernsehern, VR- und AR-Verschiebungen sowie anderen Geräten der Zukunft, von denen wir heute noch nicht einmal wissen. Flexibilität und Anpassungsfähigkeit – Dies sind die Schlüssel zur Schaffung erfolgreicher Schnittstellen in der modernen Welt.

Warum tun Programmierer auch mit neuronalen Netzwerken nichts?

Heute werden überall neuronale Netze verwendet. Programmierer verwenden sie, um Code zu generieren, andere Lösungen zu erläutern, Routineaufgaben zu automatisieren und sogar ganze Anwendungen von Grund auf neu zu erstellen. Es scheint, dass dies zu einer Erhöhung der Effizienz, der Verringerung der Fehler und der Beschleunigung der Entwicklung führen sollte. Aber die Realität ist viel prosaischer: Viele gelingt es immer noch nicht. Die neuronalen Netze lösen keine wichtigen Probleme – sie beleuchten nur die Tiefe der Unwissenheit.

Vollständige Abhängigkeit von LLM anstatt zu verstehen

Der Hauptgrund ist, dass sich viele Entwickler vollständig auf LLM verlassen und die Notwendigkeit eines tiefen Verständnisses der Werkzeuge ignorieren, mit denen sie arbeiten. Anstatt Dokumentation zu studieren – eine Chat -Anfrage. Anstatt die Gründe für den Fehler zu analysieren – kopieren Sie die Entscheidung. Anstelle von architektonischen Lösungen – die Erzeugung von Komponenten gemäß der Beschreibung. All dies kann auf oberflächlicher Ebene funktionieren, aber sobald eine nicht standardmäßige Aufgabe entsteht, ist die Integration in ein echtes Projekt oder die Notwendigkeit einer Feinabstimmung erforderlich, alles bröckelt.

Mangel an Kontext und veralteten Praktiken

Die neuronalen Netzwerke generieren den Code verallgemeinert. Sie berücksichtigen die Einzelheiten Ihrer Plattform, der Version von Bibliotheken, Umgebungsbeschränkungen oder architektonischen Lösungen des Projekts nicht. Was generiert wird, sieht oft plausibel aus, hat aber nichts mit dem realen, unterstützten Code zu tun. Selbst einfache Empfehlungen funktionieren möglicherweise nicht, wenn sie zur veralteten Version des Frameworks oder zur Verwendung von Ansätzen angehören, die seit langem als unwirksam oder unsicher anerkannt werden. Modelle verstehen den Kontext nicht – sie stützen sich auf Statistiken. Dies bedeutet, dass Fehler und Antipatttern, die im offenen Code beliebt sind, immer wieder reproduziert werden.

Redundanz, Ineffizienz und Mangel an Profilerstellung

Der generierte KI ist oft überflüssig. Es enthält unnötige Abhängigkeiten, doppelte Logik, fügt unnötig Abstraktionen hinzu. Es stellt sich eine ineffektive, schwere Struktur heraus, die schwer zu unterstützen ist. Dies ist besonders akut in der mobilen Entwicklung, wo die Größe der Bande, die Reaktionszeit und der Energieverbrauch von entscheidender Bedeutung sind.

Das neuronale Netzwerk führt keine Profilerstellung durch, berücksichtigt nicht die Einschränkungen der CPU und der GPU und kümmert sich nicht um die Speicherlecks. Es wird nicht analysiert, wie effektiv der Code in der Praxis ist. Die Optimierung ist immer noch handgefertigt und erfordert eine Analyse und Untersuchung. Ohne sie wird die Anwendung langsam, instabil und ressourcenintensiv, auch wenn sie aus Sicht der Struktur „rechts“ aussehen.

Verwundbarkeit und eine Bedrohung für die Sicherheit

Vergessen Sie die Sicherheit nicht. Es sind bereits Fälle bekannt, in denen Projekte, die teilweise oder vollständig mit LLM erstellt wurden, erfolgreich gehackt wurden. Die Gründe sind typisch: Die Verwendung unsicherer Funktionen, mangelnde Überprüfung der Eingabedaten, Fehler in der Logik der Autorisierung, Lecks durch externe Abhängigkeiten. Das neuronale Netzwerk kann einen gefährdeten Code generieren, nur weil es in offenen Repositorys gefunden wurde. Ohne die Teilnahme von Sicherheitsspezialisten und eine vollständige Überarbeitung werden solche Fehler leicht zu Eingabepunkten für Angriffe.

Das Gesetz ist Pareto und die Essenz der Fehler

Das Pareto -Gesetz arbeitet eindeutig mit neuronalen Netzwerken zusammen: 80% des Ergebnisses werden aufgrund von 20% der Anstrengungen erzielt. Das Modell kann eine große Menge Code generieren, die Grundlage des Projekts erstellen, die Struktur verbreiten, Typen anordnen, Module verbinden. All dies kann jedoch veraltet sein, mit aktuellen Versionen von Bibliotheken oder Frameworks nicht kompatibel sein und eine erhebliche manuelle Überarbeitung erfordern. Die Automatisierung funktioniert hier eher als Entwurf, der überprüft, verarbeitet und an bestimmte Realitäten des Projekts angepasst werden muss.

Vorsichtsoptimismus

Trotzdem sieht die Zukunft ermutigend aus. Konstante Aktualisierung von Trainingsdatensätzen, Integration in die aktuelle Dokumentation, automatisierte Architekturprüfungen, Einhaltung von Design- und Sicherheitsmustern – all dies kann die Spielregeln radikal ändern. Vielleicht können wir in ein paar Jahren den Code wirklich schneller, sicherer und effizienter schreiben und sich als echter technischer Co -Autor auf LLM verlassen. Aber vorerst – leider – muss viel manuell überprüft, umgeschrieben und modifiziert werden.

Neuronale Netze sind ein leistungsstarkes Werkzeug. Aber damit er für Sie und nicht gegen Sie arbeitet, benötigen Sie eine Basis, ein kritisches Denken und eine Bereitschaft, die Kontrolle jederzeit zu übernehmen.

Vibe-Core-Tricks: Warum LLM immer noch nicht mit festem, trockenem und sauberem funktioniert

Mit der Entwicklung von großsprachigen Modellen (LLM) wie Chatgpt verwenden immer mehr Entwickler sie, um Code, Designarchitektur und Beschleunigung der Integration zu generieren. Bei der praktischen Anwendung wird jedoch spürbar: Die klassischen Prinzipien der Architektur – solide, trocken, sauber – verstehen Sie die Besonderheiten der LLM -Codgendation schlecht.

Dies bedeutet nicht, dass die Prinzipien veraltet sind – im Gegenteil, sie arbeiten perfekt zur manuellen Entwicklung. Aber mit LLM muss der Ansatz angepasst werden.

Warum LLM nicht mit architektonischen Prinzipien fertig werden kann

Kapselung

Die Zusammenfassung erfordert das Verständnis der Grenzen zwischen Teilen des Systems, Kenntnissen über die Absichten des Entwicklers sowie den strengen Zugriffsbeschränkungen. LLM vereinfacht häufig die Struktur, macht Felder ohne Grund öffentlich oder dupliziert die Implementierung. Dies macht den Code anfälliger für Fehler und verstößt gegen die architektonischen Grenzen.

Abstracts und Schnittstellen

Entwurfsmuster wie eine abstrakte Fabrik oder Strategie erfordern eine ganzheitliche Sicht des Systems und das Verständnis seiner Dynamik. Modelle können eine Schnittstelle ohne klaren Zweck erstellen, ohne die Implementierung zu gewährleisten, oder gegen die Verbindung zwischen Schichten verstoßen. Das Ergebnis ist eine überschüssige oder nicht funktionsfähige Architektur.

trocken (Donolt wiederholt sich)

LLM bemüht sich nicht, den sich wiederholenden Code zu minimieren – im Gegenteil, es ist für sie einfacher, Blöcke zu duplizieren, als allgemeine Logik zu erstellen. Obwohl sie auf Anfrage auf Anfrage eingerichtet werden können, neigen Modelle neigen dazu, „selbstfassende“ Fragmente zu erzeugen, auch wenn dies zu Redundanz führt.

saubere Architektur

Clean impliziert eine strenge Hierarchie, Unabhängigkeit von Frameworks, gerichtete Abhängigkeit und minimale Verbindung zwischen Schichten. Die Erzeugung einer solchen Struktur erfordert ein globales Verständnis des Systems – und LLM arbeiten auf der Ebene der Wahrscheinlichkeit von Wörtern, nicht auf architektonischer Integrität. Daher ist der Code unter Verstoß gegen die Anweisungen der Abhängigkeit und eine vereinfachte Aufteilung in Ebenen gemischt.

Was funktioniert besser bei der Arbeit mit LLM

Nass statt trocken
Der nasses (zweimal alles schreiben) ist praktischer bei der Arbeit mit LLM. Die Duplikation von Code erfordert keinen Kontext aus dem Modell der Aufbewahrung, was bedeutet, dass das Ergebnis vorhersehbar ist und einfacher zu korrekt ist. Es reduziert auch die Wahrscheinlichkeit von nicht offenen Verbindungen und Fehler.

Darüber hinaus hilft die Duplikation, den kurzen Speicher des Modells zu kompensieren: Wenn ein bestimmtes Logikfragment an mehreren Stellen gefunden wird, berücksichtigt LLM es mit größerer Wahrscheinlichkeit mit weiterer Generation. Dies vereinfacht die Begleitung und erhöht den Widerstand gegen “Vergessen”.

einfache Strukturen anstelle von Kapselung

Vermeiden Sie eine komplexe Kapselung und stützen sich auf die direkte Übertragung von Daten zwischen den Teilen des Codes und können sowohl die Generation als auch das Debuggen erheblich vereinfachen. Dies gilt insbesondere für eine schnelle iterative Entwicklung oder Schaffung von MVP.

vereinfachte Architektur

Eine einfache, flache Struktur des Projekts mit einer minimalen Menge an Abhängigkeiten und Abstraktionen liefert während der Erzeugung ein stabileres Ergebnis. Das Modell passt einen solchen Code leichter an und verletzt die erwarteten Verbindungen zwischen den Komponenten.

SDK -Integration – Manuell zuverlässig


Die meisten Sprachmodelle werden auf veralteten Dokumentationsversionen geschult. Bei der Erstellung von Anweisungen zur Installation von SDK werden daher häufig Fehler angezeigt: veraltete Befehle, irrelevante Parameter oder Links zu unzugänglichen Ressourcen. Praxis zeigt: Es ist am besten, offizielle Dokumentation und manuelle Abstimmung zu verwenden und LLM eine Hilfsrolle zu hinterlassen – beispielsweise eine Vorlagencode oder eine Anpassung von Konfigurationen zu generieren.

Warum funktionieren die Prinzipien noch – aber mit manueller Entwicklung

Es ist wichtig zu verstehen, dass die Schwierigkeiten von festem, trockenem und sauberem Zusammenhang die Codhegeneration durch LLM betreffen. Wenn der Entwickler den Code manuell schreibt, demonstrieren diese Prinzipien weiterhin seinen Wert: Sie reduzieren die Verbundenheit, vereinfachen die Unterstützung und erhöhen die Lesbarkeit und Flexibilität des Projekts.

Dies liegt an der Tatsache, dass menschliches Denken anfällig für die Verallgemeinerung ist. Wir suchen nach Mustern, wir bringen wiederholte Logik in einzelne Entitäten und erstellen Muster. Wahrscheinlich hat dieses Verhalten evolutionäre Wurzeln: Reduzierung der Anzahl der Informationen spart kognitive Ressourcen.

LLM handelt anders: Sie erleben keine Lasten aus dem Datenvolumen und streben nicht nach Einsparungen. Im Gegenteil, es ist für sie einfacher, mit doppelten, fragmentierten Informationen zu arbeiten, als komplexe Abstraktionen aufzubauen und aufrechtzuerhalten. Aus diesem Grund ist es für sie einfacher, mit dem Code ohne Kapselung fertig zu werden, mit wiederholenden Strukturen und minimaler architektonischer Schwere.

Schlussfolgerung

Großsprachenmodelle sind ein nützliches Instrument in der Entwicklung, insbesondere in den frühen Stadien oder beim Erstellen eines Hilfscode. Es ist jedoch wichtig, den Ansatz an sie anzupassen: die Architektur zu vereinfachen, die Abstraktion zu begrenzen, komplexe Abhängigkeiten zu vermeiden und bei der Konfiguration von SDK nicht auf sie zu verlassen.

Die Prinzipien von fester, trocken und sauber sind immer noch relevant, aber sie haben den besten Effekt in den Händen einer Person. Bei der Arbeit mit LLM ist es vernünftig, einen vereinfachten, praktischen Stil zu verwenden, der es Ihnen ermöglicht, einen zuverlässigen und verständlichen Code zu erhalten, der leicht manuell abschließen kann. Und wo LLM vergisst – die Duplizierung von Code hilft ihm, sich zu erinnern.

Portierung der Surreal Engine C++ auf WebAssembly

In diesem Beitrag beschreibe ich, wie ich die Surreal Engine-Spiel-Engine auf WebAssembly portiert habe.

Surreal Engine – eine Spiel-Engine, die die meisten Funktionen der Unreal Engine 1 implementiert, berühmte Spiele auf dieser Engine – Unreal Tournament 99, Unreal, Deus Ex, Undying. Es bezieht sich auf klassische Engines, die hauptsächlich in einer Single-Threaded-Ausführungsumgebung arbeiteten.

Anfangs hatte ich die Idee, ein Projekt zu übernehmen, das ich nicht in angemessener Zeit abschließen konnte, und so meinen Twitch-Followern zu zeigen, dass es Projekte gibt, die selbst ich nicht schaffen kann. Während meines ersten Streams wurde mir plötzlich klar, dass die Aufgabe, Surreal Engine C++ mit Emscripten auf WebAssembly zu portieren, machbar ist.

Surreal Engine Emscripten Demo

Nach einem Monat kann ich meine Gabel- und Motorbaugruppe auf WebAssembly demonstrieren:
https://demensdeum.com/demos/SurrealEngine/

Die Steuerung erfolgt wie im Original über die Tastaturpfeile. Als nächstes plane ich, es für die mobile Steuerung (Tachi) anzupassen und dabei die richtige Beleuchtung und andere grafische Funktionen des Unreal Tournament 99-Renderings hinzuzufügen.

Wo soll ich anfangen?

Das erste, was ich sagen möchte, ist, dass jedes Projekt mit Emscripten von C++ nach WebAssembly portiert werden kann. Die Frage ist nur, wie vollständig die Funktionalität sein wird. Wählen Sie ein Projekt, dessen Bibliotheksports bereits für Emscripten verfügbar sind; im Fall von Surreal Engine haben Sie großes Glück, denn Die Engine verwendet die Bibliotheken SDL 2, OpenAL – Sie sind beide auf Emscripten portiert. Allerdings kommt als Grafik-API Vulkan zum Einsatz, was aktuell nicht für HTML5 verfügbar ist, an der Implementierung von WebGPU wird gearbeitet, befindet sich aber ebenfalls im Entwurfsstadium und es ist auch unbekannt, wie einfach die weitere Portierung von Vulkan auf WebGPU sein wird , nachdem es vollständig standardisiert ist. Daher musste ich mein eigenes grundlegendes OpenGL-ES/WebGL-Rendering für die Surreal Engine schreiben.

Erstellung des Projekts

System in Surreal Engine erstellen – CMake, was auch die Portierung vereinfacht, weil Emscripten stellt seinen nativen Buildern – emcmake, emmake.
Die Surreal Engine-Portierung basierte auf dem Code meines neuesten Spiels in WebGL/OpenGL ES und C++ namens Death-Mask. Dadurch war die Entwicklung viel einfacher, ich hatte alle notwendigen Build-Flags und Codebeispiele dabei.

Einer der wichtigsten Punkte in CMakeLists.txt sind die Build-Flags für Emscripten, unten ist ein Beispiel aus der Projektdatei:


-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")

Das Build-Skript selbst:


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

Als nächstes bereiten wir den Index vor .html , das den Projektdateisystem-Preloader enthält. Zum Hochladen ins Web habe ich die Unreal Tournament Demo-Version 338 verwendet. Wie Sie der CMake-Datei entnehmen können, wurde der entpackte Spielordner zum Build-Verzeichnis hinzugefügt und als Preload-Datei für Emscripten verlinkt.

Hauptcodeänderungen

Dann war es notwendig, die Spielschleife des Spiels zu ändern, man kann keine Endlosschleife ausführen, das führt zum Einfrieren des Browsers, stattdessen muss man emscripten_set_main_loop verwenden, über diese Funktion habe ich in meiner Notiz von 2017 geschrieben „< a href="https://demensdeum.com /blog/ru/2017/03/29/porting-sdl-c-game-to-html5-emscripten/" rel="noopener" target="_blank">SDL C++-Spiel auf HTML5 (Emscripten) portieren“
Wir ändern den Code zum Beenden der while-Schleife in if, zeigen dann die Hauptklasse der Spiel-Engine, die die Spielschleife enthält, im globalen Bereich an und schreiben eine globale Funktion, die den Spielschleifenschritt vom globalen Objekt aus aufruft :


#include <emscripten.h>

Engine *EMSCRIPTEN_GLOBAL_GAME_ENGINE = nullptr;

void emscripten_game_loop_step() {

	EMSCRIPTEN_GLOBAL_GAME_ENGINE->Run();

}

#endif

Danach müssen Sie sicherstellen, dass in der Anwendung keine Hintergrundthreads vorhanden sind. Wenn ja, dann bereiten Sie sich darauf vor, diese für die Single-Thread-Ausführung neu zu schreiben, oder verwenden Sie die phtread-Bibliothek in Emscripten.
Der Hintergrund-Thread in Surreal Engine wird zum Abspielen von Musik verwendet. Vom Haupt-Engine-Thread kommen Daten über den aktuellen Titel, die Notwendigkeit, Musik abzuspielen, oder dessen Fehlen. Anschließend erhält der Hintergrund-Thread über einen Mutex einen neuen Status und beginnt mit der Wiedergabe neuer Musik , oder pausiert es. Der Hintergrundstream wird auch zum Puffern von Musik während der Wiedergabe verwendet.
Meine Versuche, die Surreal Engine für Emscripten mit pthread zu erstellen, waren erfolglos, da die SDL2- und OpenAL-Ports ohne pthread-Unterstützung erstellt wurden und ich sie nicht aus Gründen der Musik neu erstellen wollte. Daher habe ich die Funktionalität des Hintergrundmusik-Streams mithilfe einer Schleife auf die Single-Threaded-Ausführung übertragen. Durch das Entfernen von pthread-Aufrufen aus dem C++-Code habe ich die Pufferung und Musikwiedergabe in den Hauptthread verschoben, damit es keine Verzögerungen gibt, ich habe den Puffer um einige Sekunden erhöht.

Als nächstes werde ich spezifische Implementierungen von Grafik und Sound beschreiben.

Vulkan wird nicht unterstützt!

Ja, Vulkan wird in HTML5 nicht unterstützt, obwohl alle Marketingbroschüren die plattformübergreifende und breite Plattformunterstützung als Hauptvorteil von Vulkan darstellen. Aus diesem Grund musste ich meinen eigenen grundlegenden Grafikrenderer für einen vereinfachten OpenGL-Typ schreiben – ES, es wird auf mobilen Geräten verwendet, enthält manchmal nicht die modischen Funktionen des modernen OpenGL, lässt sich aber sehr gut auf WebGL portieren, was genau das ist, was Emscripten implementiert. Das Schreiben des grundlegenden Kachel-Renderings, des BSP-Renderings für die einfachste GUI-Anzeige und des Renderns von Modellen und Karten wurde in zwei Wochen abgeschlossen. Dies war vielleicht der schwierigste Teil des Projekts. Es liegt noch viel Arbeit vor uns, die volle Funktionalität des Surreal Engine-Renderings zu implementieren, daher ist jede Hilfe von Lesern in Form von Code und Pull-Requests willkommen.

OpenAL unterstützt!

Das große Glück ist, dass Surreal Engine OpenAL für die Audioausgabe verwendet. Nachdem ich eine einfache Hallo-Welt in OpenAL geschrieben und sie mit Emscripten in WebAssembly zusammengestellt hatte, wurde mir klar, wie einfach alles war, und ich machte mich an die Portierung des Sounds.
Nach mehreren Stunden des Debuggens stellte sich heraus, dass die OpenAL-Implementierung von Emscripten mehrere Fehler aufweist. Beispielsweise gab die Methode beim Initialisieren des Lesens der Anzahl der Monokanäle eine unendliche Zahl zurück, und nachdem versucht wurde, einen Vektor unendlicher Größe zu initialisieren, C++ stürzt mit der Ausnahme vector::length_error ab.

Wir haben es geschafft, dies zu umgehen, indem wir die Anzahl der Monokanäle auf 2048 fest codiert haben:


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



#if __EMSCRIPTEN__

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

#endif



Gibt es ein Netzwerk?

Surreal Engine unterstützt derzeit kein Online-Spielen, das Spielen mit Bots wird unterstützt, aber wir brauchen jemanden, der KI für diese Bots schreibt. Theoretisch können Sie mithilfe von Websockets ein Netzwerkspiel auf WebAssembly/Emscripten implementieren.

Schlussfolgerung

Abschließend möchte ich sagen, dass die Portierung der Surreal Engine aufgrund der Verwendung von Bibliotheken, für die es Emscripten-Portierungen gibt, sowie meiner bisherigen Erfahrung bei der Implementierung eines Spiels in C++ für WebAssembly recht reibungslos verlief auf Emscripten. Nachfolgend finden Sie Links zu Wissensquellen und Repositories zum Thema.
M-M-M-MONSTER TÖTEN!

Außerdem, wenn Sie dem Projekt helfen möchten, vorzugsweise mit WebGL/OpenGL ES-Rendering-Code, dann schreiben Sie mir per Telegram:
https://t.me/demenscave

Links

https://demensdeum.com/demos/SurrealEngine/
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-Repository

In dieser Notiz beschreibe ich die Grundprinzipien des bekannten klassischen CRUD-Musters und die Implementierung in der Swift-Sprache. Swift ist eine offene, plattformübergreifende Sprache, die für Windows, Linux, macOS, iOS und Android verfügbar ist.

Es gibt viele Lösungen zur Abstraktion von Datenspeicherung und Anwendungslogik. Eine solche Lösung ist der CRUD-Ansatz, ein Akronym für C– Erstellen, R -Lesen, U – Update, D– Löschen.
Typischerweise wird dieses Prinzip durch die Implementierung einer Schnittstelle zur Datenbank implementiert, in der Elemente mithilfe einer eindeutigen Kennung, beispielsweise einer ID, manipuliert werden. Für jeden Buchstaben CRUD – wird eine Schnittstelle erstellt. Erstellen (Objekt, ID), Lesen (ID), Aktualisieren (Objekt, ID), Löschen (Objekt, ID).
Wenn ein Objekt eine ID in sich selbst enthält, kann das ID-Argument in den Methoden (Create, Update, Delete) weggelassen werden, da das gesamte Objekt zusammen mit seinem Feld – Ausweis. Aber für – Für das Lesen ist eine ID erforderlich, da wir anhand der ID ein Objekt aus der Datenbank abrufen möchten.

Alle Namen sind fiktiv

Stellen wir uns vor, dass eine hypothetische AssistantAI-Anwendung mit dem kostenlosen EtherRelm-Datenbank-SDK erstellt wurde. Die Integration war einfach, die API war sehr praktisch und als Ergebnis wurde die Anwendung auf den Markt gebracht.
Plötzlich beschließt der SDK-Entwickler EtherRelm, es kostenpflichtig zu machen und legt den Preis auf 100 US-Dollar pro Jahr und Anwendungsbenutzer fest.
Was? Ja! Was sollen die Entwickler von AssistantAI jetzt tun, da sie bereits 1 Million aktive Benutzer haben! 100 Millionen US-Dollar zahlen?
Stattdessen wird beschlossen, die Übertragung der Anwendung in die plattformeigene RootData-Datenbank zu evaluieren. Nach Angaben der Programmierer wird eine solche Übertragung etwa sechs Monate dauern, wobei die Implementierung neuer Funktionen in der Anwendung nicht berücksichtigt ist. Nach einigem Überlegen wurde beschlossen, die Anwendung vom Markt zu nehmen und sie auf einem anderen kostenlosen plattformübergreifenden Framework mit integrierter BueMS-Datenbank neu zu schreiben. Dadurch wird das Problem mit der kostenpflichtigen Datenbank gelöst und die Entwicklung auf anderen Plattformen vereinfacht.
Ein Jahr später wurde die Anwendung in BueMS umgeschrieben, doch dann beschließt der Framework-Entwickler plötzlich, sie kostenpflichtig zu machen. Es stellt sich heraus, dass das Team zweimal in die gleiche Falle getappt ist; ob es beim zweiten Mal wieder herauskommt, ist eine ganz andere Geschichte.

Abstraktion zur Rettung

Diese Probleme hätten vermieden werden können, wenn Entwickler eine Abstraktion von Schnittstellen innerhalb der Anwendung verwendet hätten. Zu den drei Säulen von OOP – Polymorphismus, Kapselung, Vererbung, vor nicht allzu langer Zeit haben sie ein weiteres – Abstraktion.
Mit der Datenabstraktion können Sie Ideen und Modelle allgemein und mit einem Minimum an Details beschreiben und gleichzeitig genau genug sein, um spezifische Implementierungen zu implementieren, die zur Lösung von Geschäftsproblemen verwendet werden.
Wie können wir den Datenbankbetrieb abstrahieren, sodass die Anwendungslogik nicht davon abhängt? Wir verwenden den CRUD-Ansatz!

Ein vereinfachtes UML CRUD-Diagramm sieht so aus:

Beispiel mit einer fiktiven EtherRelm-Datenbank:

Beispiel mit einer echten SQLite-Datenbank:

Wie Sie bereits bemerkt haben, ändert sich beim Wechseln der Datenbank nur die CRUD-Schnittstelle, mit der die Anwendung interagiert. CRUD ist eine Implementierung des GoF-Musters – Adapter, weil Damit passen wir Anwendungsschnittstellen an beliebige Datenbanken an und kombinieren inkompatible Schnittstellen.
Worte sind leer, zeig mir den Code
Um Abstraktionen in Programmiersprachen zu implementieren, werden Schnittstellen/Protokolle/abstrakte Klassen verwendet. All dies sind Phänomene der gleichen Art. In Interviews werden Sie jedoch möglicherweise gebeten, den Unterschied zwischen ihnen zu benennen. Ich persönlich denke, dass dies nicht viel Sinn macht, weil Der einzige Verwendungszweck besteht darin, eine Datenabstraktion zu implementieren, andernfalls dient es dazu, das Gedächtnis des Befragten zu testen.
CRUD wird oft im Rahmen des Repository-Musters implementiert. Das Repository kann jedoch die CRUD-Schnittstelle implementieren oder auch nicht, alles hängt vom Einfallsreichtum des Entwicklers ab.

Stellen Sie sich einen ziemlich typischen Swift-Code für das Book-Struktur-Repository vor, der direkt mit der UserDefaults-Datenbank arbeitet:


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)
}

Der obige Code scheint einfach zu sein, aber zählen wir die Anzahl der Verstöße gegen das DRY-Prinzip (Do not Repeat Yourself) und die Kohärenz des Codes:
Konnektivität zur UserDefaults-Datenbank
Konnektivität mit JSON-Encodern und -Decodern – JSONEncoder, JSONDecoder
Verbunden mit der Book-Struktur, aber wir benötigen ein abstraktes Repository, um nicht für jede Struktur, die wir in der Datenbank speichern, eine Repository-Klasse zu erstellen (DRY-Verletzung)

Ich sehe diese Art von CRUD-Repository-Code ziemlich oft, er kann verwendet werden, aber eine hohe Kopplung und Duplizierung des Codes führt dazu, dass seine Unterstützung mit der Zeit sehr kompliziert wird. Dies macht sich besonders bemerkbar, wenn Sie versuchen, zu einer anderen Datenbank zu wechseln, oder wenn Sie die interne Logik der Arbeit mit der Datenbank in allen in der Anwendung erstellten Repositorys ändern.
Anstatt Code zu duplizieren, halten Sie die Kopplung hoch – Schreiben wir ein Protokoll für das CRUD-Repository und abstrahieren so die Datenbankschnittstelle und die Anwendungsgeschäftslogik unter Berücksichtigung von DRY und implementieren eine geringe Kopplung:

    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
}

Das CRUDRepository-Protokoll beschreibt Schnittstellen und zugehörige Datentypen für die weitere Implementierung eines bestimmten CRUD-Repositorys.

Als nächstes schreiben wir eine spezifische Implementierung für die UserDefaults-Datenbank:

    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()
    }
}

Der Code sieht lang aus, enthält aber eine vollständige konkrete Implementierung eines CRUD-Repositorys mit loser Kopplung, Details unten.
Typalias wurden zur Selbstdokumentation des Codes hinzugefügt.
Schwache Kopplung und starke Kopplung
Die Loslösung von einer bestimmten Struktur (Struktur) wird mithilfe des generischen T implementiert, das wiederum die Codable-Protokolle implementieren muss. Mit Codable können Sie Strukturen mithilfe von Klassen konvertieren, die die Protokolle TopLevelEncoder und TopLevelDecoder implementieren, beispielsweise JSONEncoder und JSONDecoder. Bei Verwendung von Basistypen (Int, String, Float usw.) ist es nicht erforderlich, zusätzlichen Code zum Konvertieren von Strukturen zu schreiben.

Die Entkopplung von einem bestimmten Encoder und Decoder erfolgt mithilfe der Abstraktion im DataTransformer-Protokoll:

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

Durch die Implementierung eines Datentransformators haben wir eine Abstraktion der Encoder- und Decoder-Schnittstellen implementiert und eine lose Kopplung implementiert, um die Arbeit mit verschiedenen Arten von Datenformaten sicherzustellen.

Das Folgende ist der Code für einen bestimmten DataTransformer, nämlich für 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
	}
}

War es möglich?

Was hat sich geändert? Jetzt reicht es aus, ein bestimmtes Repository zu initialisieren, um mit jeder Struktur zu arbeiten, die das Codable-Protokoll implementiert, wodurch die Notwendigkeit entfällt, Code zu duplizieren und eine lose Kopplung der Anwendung zu implementieren.

Ein Beispiel für ein Client-CRUD mit einem bestimmten Repository, UserDefaults ist die Datenbank, JSON-Datenformat, Client-Struktur, außerdem ein Beispiel für das Schreiben und Lesen eines Arrays:


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!)

Während der ersten CRUD-Prüfung wurde eine Ausnahmebehandlung implementiert, bei der das Lesen des Remote-Elements nicht mehr möglich ist.

Datenbank wechseln

Jetzt zeige ich Ihnen, wie Sie Ihren aktuellen Code in eine andere Datenbank übertragen. Als Beispiel nehme ich den SQLite-Repository-Code, den ChatGPT generiert hat:


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)
    }
}

Oder der CRUD-Code des Repositorys für das Dateisystem, der ebenfalls von ChatGPT generiert wurde:


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)
	}
}

Ersetzen Sie das Repository im Client-Code:


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!)

Die Initialisierung von UserDefaultsRepository wurde durch FileSystemRepository mit den entsprechenden Argumenten ersetzt.
Nachdem Sie die zweite Version des Client-Codes ausgeführt haben, finden Sie im Dokumentenordner ein Verzeichnis „Clients Database“, das eine Datei eines in JSON serialisierten Arrays mit einer Client-Struktur enthält.

Datenspeicherformat wechseln

Jetzt bitten wir ChatGPT, einen Encoder und Decoder für XML zu generieren:

	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)
	}
}

Dank der in Swift integrierten Typen wird die Aufgabe für ein neuronales Netzwerk elementar.

Ersetzen Sie JSON durch XML im Client-Code:


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!)

Der Client-Code wurde in nur einen Ausdruck JSONDataTransformer geändert -> XMLDataTransformer

Gesamt

CRUD-Repositorys sind eines der Entwurfsmuster, die zur Implementierung einer losen Kopplung von Anwendungsarchitekturkomponenten verwendet werden können. Eine weitere Lösung – unter Verwendung von ORM (Object-Relational Mapping), kurz gesagt, ORM verwendet einen Ansatz, bei dem Strukturen vollständig auf die Datenbank abgebildet werden und dann Änderungen an Modellen in der Datenbank angezeigt (zugeordnet (!)) werden sollen.
Aber das ist eine ganz andere Geschichte.

Eine vollständige Implementierung von CRUD-Repositorys für Swift ist verfügbar unter:
https://gitlab.com/demensdeum/crud-example

Swift wird übrigens schon lange außerhalb von macOS unterstützt; der Code aus dem Artikel wurde vollständig unter Arch Linux geschrieben und getestet.

Quellen

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 Ein-/Ausgabefehler

Was sollten Sie tun, wenn Sie beim Kopieren einer normalen Festplatte mit dd unter Linux einen Ein-/Ausgabefehler erhalten?

Situevina ist sehr traurig, aber lösbar. Höchstwahrscheinlich haben Sie es mit einer ausgefallenen Festplatte zu tun, die fehlerhafte Blöcke enthält, die nicht mehr verwendet, beschrieben oder gelesen werden können.

Überprüfen Sie eine solche Festplatte unbedingt mit S.M.A.R.T., da Ihnen höchstwahrscheinlich Festplattenfehler angezeigt werden. Dies war in meinem Fall der Fall; die Anzahl der fehlerhaften Blöcke war so groß, dass ich mich von der alten Festplatte verabschieden und sie durch eine neue SSD ersetzen musste.

Das Problem bestand darin, dass diese Diskette ein voll funktionsfähiges System mit lizenzierter Software enthielt, die für die Arbeit erforderlich war. Ich habe versucht, partimage zum schnellen Kopieren von Daten zu verwenden, aber plötzlich habe ich festgestellt, dass das Dienstprogramm nur ein Drittel der Festplatte kopiert. Dann endet es entweder mit einem Segfault oder mit einem anderen lustigen Sishny/Sipplusplus-Witz.

Als nächstes habe ich versucht, die Daten mit dd zu kopieren, und es stellte sich heraus, dass dd ungefähr an die gleiche Stelle wie partimage gelangt und dann ein Eingabe-/Ausgabefehler auftritt. Gleichzeitig haben alle möglichen lustigen Flags wie conv=noerr, skip oder etwas anderes überhaupt nicht geholfen.

Aber die Daten konnten mit einem GNU-Dienstprogramm namens ddrescue problemlos auf eine andere Festplatte kopiert werden.

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

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

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

ChatGPT

Hallo zusammen! In diesem Artikel möchte ich über ChatGPT sprechen – leistungsstarke Sprachmodellierung von OpenAI, die bei der Lösung einer Vielzahl von Textverarbeitungsproblemen helfen kann. Ich zeige Ihnen, wie dieses Tool funktioniert und wie es in der Praxis eingesetzt werden kann. Fangen wir an!

ChatGPT ist derzeit eines der weltweit besten Sprachmodelle auf Basis neuronaler Netze. Es wurde mit dem Ziel entwickelt, Entwicklern dabei zu helfen, intelligente Systeme zu erstellen, die in der Lage sind, natürliche Sprache zu erzeugen und mit den darin enthaltenen Menschen zu kommunizieren.

Einer der Hauptvorteile von ChatGPT ist seine Fähigkeit, Text kontextbezogen zu modellieren. Das bedeutet, dass das Modell frühere Dialoge berücksichtigt und diese nutzt, um die Situation genauer zu verstehen und eine natürlichere Reaktion zu generieren.

Sie können ChatGPT für eine Vielzahl von Aufgaben verwenden, wie zum Beispiel die Automatisierung des Kundensupports, die Erstellung von Chatbots, die Textgenerierung und vieles mehr.

Die neuronalen Netze hinter ChatGPT wurden anhand großer Textmengen trainiert, um äußerst genaue Vorhersagen zu gewährleisten. Dadurch kann das Modell natürlichen Text generieren, der den Dialog unterstützen und Fragen beantworten kann.

Mit ChatGPT können Sie Ihre eigenen Chatbots und andere intelligente Systeme erstellen, die mit Menschen in natürlicher Sprache interagieren können. Dies kann besonders in Branchen wie Tourismus, Einzelhandel und Kundenbetreuung nützlich sein.

Zusammenfassend lässt sich sagen, dass ChatGPT – Es ist ein leistungsstarkes Werkzeug zur Lösung verschiedener Sprachmodellierungsprobleme. Seine Fähigkeit zur kontextbezogenen Modellierung macht es besonders nützlich für die Erstellung von Chatbots und intelligenten Systemen.


Tatsächlich hat ChatGPT alles oben Genannte selbst geschrieben. Was? Ja? Ich bin selbst schockiert!

Sie können das Raster selbst hier ausprobieren:
https://chat.openai.com/chat

Schalten Sie die Hintergrundbeleuchtung der USB-Tastatur unter macOS ein

Ich habe kürzlich eine sehr preiswerte Getorix GK-45X USB-Tastatur mit RGB-Hintergrundbeleuchtung gekauft. Nachdem ich es an ein MacBook Pro mit M1-Prozessor angeschlossen hatte, stellte sich heraus, dass die RGB-Hintergrundbeleuchtung nicht funktionierte. Selbst durch Drücken der magischen Kombination Fn + Scroll Lock konnte die Hintergrundbeleuchtung nicht eingeschaltet werden; nur die Hintergrundbeleuchtung des MacBook-Bildschirms änderte sich.
Es gibt mehrere Lösungen für dieses Problem, nämlich OpenRGB (funktioniert nicht), HID-LED-Test (funktioniert nicht). Nur das Dienstprogramm kvmswitch hat funktioniert:
https://github.com/stoutput/OSX-KVM

Sie müssen es von GitHub herunterladen und die Ausführung über das Terminal im Bereich „Sicherheit“ der Systemeinstellungen zulassen.
Wie ich der Beschreibung entnehme, sendet das Dienstprogramm nach dem Start einen Tastendruck auf Fn + Scroll Lock und schaltet so die Hintergrundbeleuchtung der Tastatur ein/aus.

Baumsortierung

Baumsortierung – Sortierung mithilfe eines binären Suchbaums. Zeitkomplexität – O(n²). In einem solchen Baum hat jeder Knoten links Zahlen, die kleiner sind als der Knoten, rechts sind es mehr als der Knoten. Wenn wir von der Wurzel kommen und die Werte von links nach rechts drucken, erhalten wir eine sortierte Liste von Zahlen . Überraschend, oder?

Betrachten Sie das binäre Suchbaumdiagramm:

Derrick Coetzee (gemeinfrei)

Versuchen Sie, die Zahlen manuell abzulesen, beginnend beim vorletzten linken Knoten der unteren linken Ecke, für jeden Knoten links – einen Knoten – rechts.

Es wird so aussehen:

  1. Vorletzter Knoten unten links – 3.
  2. Es hat einen linken Zweig – 1.
  3. Nehmen Sie diese Nummer (1)
  4. Als nächstes nehmen wir Scheitelpunkt 3 (1, 3)
  5. Auf der rechten Seite befindet sich Zweig 6, der jedoch Zweige enthält. Deshalb lesen wir es genauso.
  6. Linker Zweig von Knoten 6 Nummer 4 (1, 3, 4)
  7. Der Knoten selbst ist 6 (1, 3, 4, 6)
  8. Rechts 7 (1, 3, 4, 6, 7)
  9. Gehen Sie zum Wurzelknoten hoch – 8 (1,3, 4,6, 7, 8)
  10. Alles auf der rechten Seite drucken wir analog aus
  11. Wir erhalten die endgültige Liste – 1, 3, 4, 6, 7, 8, 10, 13, 14

Um den Algorithmus im Code zu implementieren, benötigen Sie zwei Funktionen:

  1. Zusammenstellen eines binären Suchbaums
  2. Den binären Suchbaum in der richtigen Reihenfolge ausdrucken

Der binäre Suchbaum wird auf die gleiche Weise zusammengestellt, wie er gelesen wird. An jeden Knoten wird links oder rechts eine Zahl angehängt, je nachdem, ob er kleiner oder größer ist.

Beispiel in 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

Eimersortierung

Bucket Sort – Sortierung nach Buckets. Der Algorithmus ähnelt der Zählsortierung, mit dem Unterschied, dass die Zahlen in „Buckets“-Bereichen gesammelt werden, dann die Buckets mit einem anderen, ausreichend produktiven Sortieralgorithmus sortiert werden und der letzte Schritt darin besteht, den „Buckets“-Bereich zu entfalten um eins, was zu einer sortierten Liste führt

Die zeitliche Komplexität des Algorithmus beträgt O(nk). Der Algorithmus arbeitet in linearer Zeit für Daten, die einem Gleichverteilungsgesetz gehorchen. Vereinfacht ausgedrückt müssen die Elemente in einem bestimmten Bereich liegen, ohne „Spitzen“, zum Beispiel Zahlen von 0,0 bis 1,0. Wenn unter solchen Zahlen 4 oder 999 sind, dann gilt eine solche Reihe nach den Hofgesetzen nicht mehr als „gerade“.

Implementierungsbeispiel in 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

Radixsort

Radix-Sortierung – Basissortierung. Der Algorithmus ähnelt der Zählsortierung darin, dass es keinen Vergleich von Elementen gibt; stattdessen werden Elemente *Zeichen für Zeichen* in *Buckets* (Buckets) gruppiert, der Bucket wird anhand des Index des aktuellen Zahlenzeichens ausgewählt. Zeitkomplexität – O(nd).

Es funktioniert ungefähr so:

  • Die Eingabe erfolgt aus den Zahlen 6, 12, 44, 9
  • Wir werden 10 Buckets mit Listen (0-9) erstellen, in die wir Zahlen Stück für Stück hinzufügen/sortieren.

Weiter:

  1. Starten Sie eine Schleife mit Zähler i bis zur maximalen Anzahl von Zeichen in der Zahl
  2. Durch den Index i von rechts nach links erhalten wir ein Symbol für jede Zahl; wenn es kein Symbol gibt, dann gehen wir davon aus, dass es Null ist
  3. Konvertieren Sie das Symbol in eine Zahl
  4. Wählen Sie einen Bucket nach Indexnummer aus und geben Sie dort die ganze Zahl ein
  5. Nachdem Sie mit der Suche durch die Zahlen fertig sind, wandeln Sie alle Buckets wieder in eine Zahlenliste um
  6. Erhalten Sie Zahlen nach Rang sortiert
  7. Wiederholen Sie den Vorgang, bis alle Ziffern verschwunden sind

Beispiel für Radix-Sortierung in 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}")

    }

}

Der Algorithmus verfügt auch über eine Version zur parallelen Ausführung, beispielsweise auf einer GPU; Es gibt auch eine Möglichkeit zum Sortieren von Bits, die sehr interessant und wirklich atemberaubend sein muss!

Links

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

Quellen

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

Haufensort

Heapsort – Pyramidensortierung. Zeitliche Komplexität des Algorithmus – O(n log n), schnell, oder? Ich würde das Sortieren das Sortieren fallender Kieselsteine ​​nennen. Es scheint mir, dass man es am einfachsten visuell erklären kann.

Die Eingabe ist eine Liste von Zahlen, zum Beispiel:
5, 0, 7, 2, 3, 9, 4

Von links nach rechts wird eine Datenstruktur erstellt – ein Binärbaum, oder wie ich es nenne – Pyramide. Pyramidenelemente können maximal zwei untergeordnete Elemente, aber nur ein oberstes Element haben.

Lassen Sie uns einen Binärbaum erstellen:
⠀⠀5
⠀0⠀7
2 3 9 4

Wenn Sie die Pyramide längere Zeit betrachten, können Sie erkennen, dass es sich nur um Zahlen aus einer Reihe handelt, die nacheinander auftauchen. Die Anzahl der Elemente in jeder Etage wird mit zwei multipliziert.

Als nächstes beginnt der Spaß: Sortieren wir die Pyramide von unten nach oben mit der Methode der fallenden Kieselsteine ​​(aufhäufen). Mit dem Sortieren könnte man ab der letzten Etage beginnen (2 3 9 4), aber es hat keinen Sinn, weil Es gibt keine Etage darunter, in die man fallen könnte.

Daher beginnen wir, Elemente aus der vorletzten Etage (0 7) fallen zu lassen
⠀⠀5
⠀0⠀7
2 3 9 4

Das erste fallende Element wird von rechts ausgewählt, in unserem Fall ist es 7, dann schauen wir uns an, was darunter liegt, und darunter sind 9 und 4, neun ist größer als vier, und auch neun ist größer als Sieben! Wir lassen 7 auf 9 fallen und heben 9 auf Platz 7.
⠀⠀5
⠀0⠀9
2 3 7 4

Als nächstes verstehen wir, dass die Sieben nirgendwo tiefer fallen kann, und gehen weiter zur Zahl 0, die sich im vorletzten Stockwerk auf der linken Seite befindet:
⠀⠀5
0⠀9
2 3 7 4

Mal sehen, was sich darunter verbirgt – 2 und 3, zwei ist kleiner als drei, drei ist mehr als null, also vertauschen wir null und drei:
⠀⠀5
⠀3⠀9
2 0 7 4

Wenn Sie das Ende der Etage erreichen, gehen Sie in die darüber liegende Etage und lassen Sie dort, wenn möglich, alles ab.
Das Ergebnis ist eine Datenstruktur – ein Heap, nämlich Max Heap, weil Oben ist das größte Element:
⠀⠀9
⠀3⠀7
2 0 5 4

Wenn Sie es in eine Array-Darstellung zurückführen, erhalten Sie eine Liste:
[9, 3, 7, 2, 0, 5, 4]

Daraus können wir schließen, dass wir durch Vertauschen des ersten und letzten Elements die erste Zahl an der endgültigen sortierten Position erhalten, nämlich 9 sollte am Ende der sortierten Liste stehen, Plätze vertauschen:
[4, 3, 7, 2, 0, 5, 9]

Sehen wir uns einen Binärbaum an:
⠀⠀4
⠀3⠀7
2 0 5 9

Das Ergebnis ist eine Situation, in der der untere Teil des Baums sortiert ist. Sie müssen lediglich 4 an der richtigen Position ablegen, den Algorithmus wiederholen, aber die bereits sortierten Zahlen, nämlich 9, nicht berücksichtigen:
⠀⠀4
⠀3⠀7
2 0 5 9

⠀⠀7
⠀3⠀4
2 0 5 9

⠀⠀7
⠀3⠀5
2 0 4 9

Es stellte sich heraus, dass wir, nachdem wir 4 verloren hatten, die nächstgrößte Zahl nach 9 erhöht hatten – 7. Vertauschen Sie die letzte unsortierte Zahl (4) und die größte Zahl (7)
⠀⠀4
⠀3⠀5
2 0 7 9

Es stellt sich heraus, dass wir jetzt zwei Zahlen an der richtigen Endposition haben:
4, 3, 5, 2, 0, 7, 9

Als nächstes wiederholen wir den Sortieralgorithmus und ignorieren die bereits sortierten. Am Ende erhalten wir ein Heap Typ:
⠀⠀0
⠀2⠀3
4 5 7 9

Oder als Liste:
0, 2, 3, 4, 5, 7, 9

Implementierung

Der Algorithmus ist normalerweise in drei Funktionen unterteilt:

  1. Einen Heap erstellen
  2. Sifting-Algorithmus (Heapify)
  3. Ersetzen des letzten unsortierten Elements durch das erste

Der Heap wird erstellt, indem die vorletzte Zeile des Binärbaums mithilfe der Heapify-Funktion von rechts nach links bis zum Ende des Arrays durchlaufen wird. Als nächstes im Zyklus erfolgt die erste Ersetzung der Zahlen, danach fällt/bleibt das erste Element an Ort und Stelle, wodurch das größte Element an die erste Stelle fällt, der Zyklus wird mit einer Verringerung der Teilnehmerzahl um eins wiederholt, weil Nach jedem Durchlauf bleiben sortierte Zahlen am Ende der Liste.

Heapsort-Beispiel in 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



Dieser Algorithmus ist ohne Visualisierung nicht leicht zu verstehen, daher empfehle ich als Erstes, eine Funktion zu schreiben, die die aktuelle Ansicht des Binärbaums druckt.

Links

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

Quellen

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

https://habr.com/ru/company/ otus/blog/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

https://ru.wikipedia.org/wiki/Tree (Datenstruktur)

https://ru.wikipedia.org/wiki/Heap (Datenstruktur)

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/ array-representation-of-binary-heap/

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

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

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

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

Quicksort

Quicksort ist ein Sortieralgorithmus nach dem Prinzip „Teile und herrsche“. Rekursiv, Stück für Stück, analysieren wir das Zahlenarray, ordnen die Zahlen ausgehend vom ausgewählten Referenzelement in kleinerer und größerer Reihenfolge an und fügen das Referenzelement selbst in den Cutoff zwischen ihnen ein. Nach mehreren rekursiven Iterationen erhalten Sie eine sortierte Liste. Zeitkomplexität O(n2).

Schema:

  1. Wir beginnen damit, eine Liste von Elementen von außen zu erhalten, die Sortiergrenzen. Im ersten Schritt werden die Sortiergrenzen von Anfang bis Ende festgelegt.
  2. Überprüfen Sie, dass sich die Start- und Endgrenzen nicht überschneiden. Wenn dies passiert, ist es Zeit, den Vorgang abzuschließen
  3. Wählen Sie ein Element aus der Liste aus und nennen Sie es Pivot
  4. Bewegen Sie es am letzten Index nach rechts bis zum Ende, damit es nicht im Weg ist
  5. Erstellen Sie einen Zähler mit *kleineren Zahlen*, die immer noch Null sind
  6. Durchlaufen Sie die Liste von links nach rechts, bis einschließlich zum letzten Index, an dem sich das Referenzelement befindet
  7. Wir vergleichen jedes Element mit dem Referenzelement
  8. Wenn er kleiner als der Referenzwert ist, tauschen wir ihn entsprechend dem Index des Zählers kleinerer Zahlen aus. Erhöhen Sie den Zähler kleinerer Zahlen.
  9. Wenn die Schleife das Stützelement erreicht, halten wir an und tauschen das Stützelement mit dem Element entsprechend dem Zähler der kleineren Zahlen aus.
  10. Wir führen den Algorithmus separat für den kleineren linken Teil der Liste und separat für den größeren rechten Teil der Liste aus.
  11. Infolgedessen werden alle rekursiven Iterationen aufgrund des Eincheckpunkts 2 angehalten
  12. Erhalten Sie eine sortierte Liste

Quicksort wurde vom Wissenschaftler Charles Anthony Richard Hoare an der Moskauer Staatsuniversität erfunden. Nachdem er Russisch gelernt hatte, studierte er Computerübersetzung sowie Wahrscheinlichkeitstheorie an der Kolmogorov-Schule. 1960 verließ er aufgrund der politischen Krise die Sowjetunion.

Beispielimplementierung in 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);
  }
}

Wenn nichts klar ist, empfehle ich Ihnen, sich das Video von Rob Edwards von der University of San Diego anzusehen https://www.youtube.com/watch?v=ZHVk2blR45Q Es zeigt ganz einfach Schritt für Schritt das Wesen und die Implementierung des Algorithmus.

Links

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

Quellen

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

Binäre Einfügungssortierung

Binary Insertion Sort ist eine Variante der Insertion Sort, bei der die Einfügeposition mithilfe der binären Suche ermittelt wird. Die zeitliche Komplexität des Algorithmus beträgt O(n2)

Der Algorithmus funktioniert folgendermaßen:

  1. Eine Schleife beginnt bei Null bis zum Ende der Liste
  2. In der Schleife wird eine Zahl zum Sortieren ausgewählt, die Zahl wird in einer separaten Variablen gespeichert
  3. Die binäre Suche sucht nach dem Index, um diese Zahl gegen die Zahlen auf der linken Seite einzufügen
  4. Sobald der Index gefunden wurde, werden die Zahlen auf der linken Seite beginnend beim Einfügeindex um eine Position nach rechts verschoben. Dabei wird die zu sortierende Nummer gelöscht.
  5. Die zuvor gespeicherte Nummer wird am Einfügeindex eingefügt
  6. Am Ende der Schleife wird die gesamte Liste sortiert

Bei einer binären Suche kann es sein, dass die Nummer nicht gefunden wird und der Index nicht zurückgegeben wird. Aufgrund der Besonderheit der binären Suche wird die Zahl gefunden, die der gesuchten Zahl am nächsten kommt. Um den Index zurückzugeben, müssen Sie ihn mit der gesuchten Zahl vergleichen. Wenn die gesuchte Zahl kleiner ist, sollte die gesuchte Zahl bei sein der Index links, und wenn größer oder gleich, dann rechts.

Go-Code:


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)
}

Links

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

Quellen

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

Muschelsortierung

Shell Sort – eine Variante der Einfügungssortierung mit vorläufiger Kämmung eines Zahlenarrays.

Wir müssen uns daran erinnern, wie die Einfügungssortierung funktioniert:

1. Eine Schleife wird von Null bis zum Ende der Schleife gestartet, wodurch das Array in zwei Teile geteilt wird
2. Für den linken Teil wird eine zweite Schleife gestartet, die Elemente von rechts nach links vergleicht. Das kleinere Element auf der rechten Seite wird gelöscht, bis ein kleineres Element auf der linken Seite gefunden wird
3. Am Ende beider Schleifen erhalten wir eine sortierte Liste

Es war einmal der Informatiker Donald Schell, der sich fragte, wie man den Einfügungssortierungsalgorithmus verbessern könnte. Er kam auch auf die Idee, das Array zunächst in zwei Zyklen zu durchlaufen, jedoch in einem bestimmten Abstand, und den „Kamm“ schrittweise zu verkleinern, bis daraus ein regulärer Einfügungssortierungsalgorithmus wird. Alles ist wirklich so einfach, keine Fallstricke. Zu den beiden oben genannten Zyklen fügen wir einen weiteren hinzu, in dem wir die Größe des „Kamms“ schrittweise reduzieren. Das Einzige, was Sie tun müssen, ist, beim Vergleich den Abstand zu überprüfen, damit er nicht über das Array hinausgeht.

Ein wirklich interessantes Thema ist die Auswahl der Reihenfolge zum Ändern der Vergleichslänge bei jeder Iteration der ersten Schleife. Dies ist deshalb interessant, weil die Leistung des Algorithmus davon abhängt.

Die Tabelle bekannter Optionen und Zeitkomplexität finden Sie hier: https: //en .wikipedia.org/wiki/Shellsort#Gap_sequences

An der Berechnung des idealen Abstands waren verschiedene Personen beteiligt; für sie war dieses Thema offenbar so interessant. Könnten sie nicht einfach Ruby ausführen und den schnellsten sort()-Algorithmus aufrufen?

Im Allgemeinen haben diese seltsamen Leute Dissertationen zum Thema der Berechnung des Abstands/der Lücke des „Kamms“ für den Shell-Algorithmus geschrieben. Ich habe einfach die Ergebnisse ihrer Arbeit verwendet und fünf Arten von Sequenzen überprüft: 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)

In meiner Implementierung sind für einen zufälligen Satz von Zahlen die schnellsten Lücken Sedgwick und Hibbard.

mypy

Ich möchte auch den statischen Typisierungsanalysator für Python 3 erwähnen – mypy. Hilft bei der Bewältigung der Probleme, die Sprachen mit dynamischer Eingabe innewohnen, indem es die Möglichkeit ausschließt, etwas dort festzuhalten, wo es nicht benötigt wird.

Wie erfahrene Programmierer sagen: „Statisches Tippen ist nicht erforderlich, wenn Sie ein Team von Profis haben.“ Eines Tages werden wir alle Profis, wir werden Code in völliger Einheit und Verständnis mit Maschinen schreiben, aber im Moment können Sie ähnliche Dienstprogramme verwenden und statisch typisierte Sprachen.

Links

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

Quellen

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

Doppelte Auswahlsortierung

Doppelte Auswahlsortierung – eine Unterart der Auswahlsortierung, die anscheinend doppelt so schnell sein sollte. Der Vanilla-Algorithmus durchläuft die Liste der Zahlen doppelt, findet die Mindestzahl und tauscht die Plätze mit der aktuellen Zahl, auf die die Schleife auf der Ebene darüber zeigt. Die Sortierung mit doppelter Auswahl sucht nach den minimalen und maximalen Zahlen und ersetzt dann die beiden Ziffern, auf die die Schleife auf der Ebene darüber zeigt – zwei Zahlen links und rechts. Diese ganze Orgie endet, wenn die Cursor der zu ersetzenden Zahlen in der Mitte der Liste gefunden werden und als Ergebnis sortierte Zahlen links und rechts von der visuellen Mitte erhalten werden.
Die zeitliche Komplexität des Algorithmus ähnelt der von Selection Sort – O(n2), aber angeblich gibt es eine Beschleunigung von 30 %.

Grenzzustand

Bereits in diesem Stadium können Sie sich den Moment einer Kollision vorstellen, zum Beispiel, wenn die Zahl des linken Cursors (die Mindestzahl) auf die Höchstzahl in der Liste zeigt, dann wird die Mindestzahl neu angeordnet, die Neuordnung der Höchstzahl bricht sofort zusammen. Daher beinhalten alle Implementierungen des Algorithmus die Prüfung auf solche Fälle und das Ersetzen von Indizes durch korrekte. In meiner Implementierung hat eine Prüfung gereicht:

  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;
    }
} 

Links

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

Quellen

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/

Cocktail-Shaker-Sorte

Cocktail-Shaker-Sortierung – Schüttlersortierung, eine Variante der bidirektionalen Blasensortierung.
Der Algorithmus funktioniert wie folgt:

  1. Die anfängliche Suchrichtung in der Schleife wird ausgewählt (normalerweise von links nach rechts)
  2. Als nächstes werden in der Schleife die Zahlen paarweise überprüft
  3. Wenn das nächste Element größer ist, werden sie vertauscht
  4. Wenn der Suchvorgang abgeschlossen ist, beginnt er erneut mit umgekehrter Richtung
  5. Die Suche wird wiederholt, bis keine Permutationen mehr vorhanden sind

Die zeitliche Komplexität des Algorithmus ist ähnlich wie bei einer Blase – O(n2).

Beispiel für die Implementierung in 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

…And Primus for All

In this note I will describe the launch of Steam games on the Linux distribution Arch Linux in the configuration of an Intel + Nvidia laptop

Counter-Strike: Global Offensive

The only configuration that worked for me is Primus-vk + Vulkan.

Install the required packages:
pacman -S vulkan-intel lib32-vulkan-intel nvidia-utils lib32-nvidia-utils vulkan-icd-loader lib32-vulkan-icd-loader primus_vk

Next, add launch options for Counter-Strike: Global Offensive:
pvkrun %command% -vulkan -console -fullscreen

Should work!

Sid Meier’s Civilization VI

Works in conjunction – Primus + OpenGL + LD_PRELOAD.

Install the Primus package:
pacman -S primus

Next, add launch options for Sid Meier’s Civilization VI:
LD_PRELOAD='/usr/lib/libfreetype.so.6:/usr/lib/libbrotlicommon.so.1:/usr/lib/libbrotlidec.so.1' primusrun %command%

LD_PRELOAD pushes the Freetype compression and font libraries.

Dota 2

Works in conjunction – Primus + OpenGL + removal of locks at startup.

Install the Primus package:
pacman -S primus

Next, add launch options for Dota 2:
primusrun %command% -gl -console

If the game doesn’t start with fcntl(5) for /tmp/source_engine_2808995433.lock failed, then try deleting the /tmp/source_engine_2808995433.lock file
rm /tmp/source_engine_2808995433.lock
Usually the lock file is left over from the last game session unless the game was closed naturally.

How to check?

The easiest way to check the launch of applications on a discrete Nvidia graphics card is through the nvidia-smi utility:

For games on the Source engine, you can check through the game console using the mat_info command:

References

https://wiki.archlinux.org/title/Steam/Game-specific_troubleshooting
https://help.steampowered.com/en/faqs/view/145A-FE54-F37B-278A
https://bbs.archlinux.org/viewtopic.php?id=277708