Raspberry PI 3 comme routeur Wi-Fi

Il existe de nombreux articles sur Internet sur la façon de créer un routeur Wi-Fi à partir d’un Raspberry Pi (RPI), dans cet article je vais décrire brièvement ma méthode de création d’un routeur Wi-Fi avec une sing-box à bord. La méthode décrite fonctionne actuellement et beaucoup de choses pourraient changer à l’avenir. Utilisez donc cette note comme un aperçu approximatif de ce à quoi vous serez confronté.

SSH

Pour ceux qui ne savent pas travailler avec OpenWrt, je recommande d’installer dietPI.
Connectez RPI à votre routeur actuel via eth0, puis connectez-vous-y via SSH. Vous pouvez connaître l’adresse IP RPI dans le panneau DHCP du routeur. Connectez-vous directement à root, par exemple comme ceci :

ssh racine@[IP_ADDRESS]

Adaptateur Wi-Fi

Le RPI3 intégré s’est avéré franchement faible et ne prend pas en charge 5 GHz. J’ai donc connecté l’adaptateur RITMIX RWA-150 sur le chipset Realtek RTL8811CU via USB 2. Les pilotes ont été ajoutés au noyau Linux qui se trouvait dans ma version dietPi. Ensuite, en utilisant dietpi-config, j’ai complètement désactivé le Wi-Fi intégré. En conséquence, il ne restait qu’un seul adaptateur USB wlan0.

Point d’accès

Le mot de passe dietPI par défaut pour root est dietpi. Une fois connecté, vous serez accueilli par l’installateur/configurateur dietPI. Une fois terminé, vous devrez vous reconnecter en raison du redémarrage de l’appareil.

Tout d’abord, vous devez configurer hostapd afin que les appareils puissent voir votre point d’accès. Si hostapd n’est pas installé, installez-le via apk.

Ensuite, vous devrez écrire une configuration pour hostapd. Exemple de ma config :

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

La signification de la configuration hostapd peut être trouvée dans le manuel. Cependant, ce qui est important est de le configurer vous-même – le canal (2,4 GHz ou 5 GHz), le code du pays, sinon sans cela, vos appareils localisés peuvent fonctionner correctement avec le point d’accès, je l’ai déjà fait et je le sais, alors définissez soigneusement votre pays.

DHCP

Ensuite, installez et configurez dnsmasq pour implémenter DHCP. Ceci est nécessaire pour que les ordinateurs connectés déterminent l’adresse IP et le serveur DNS.
Exemple de ma config :

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

C’est la configuration minimale qui vous permettra de vous connecter à un point d’accès et d’obtenir une adresse IP. Ensuite, vous devrez configurer le routage et le NAT. Cela est nécessaire pour que les ordinateurs connectés puissent accéder à Internet.

Ici, la note entre dans la catégorie des configurations de routage typiques sur un système compatible Debian classique, sur lesquelles il existe de nombreux articles sur Internet. Ensuite, tout dépend des objectifs que vous poursuivez, par exemple, vous connecter à un serveur externe en tant que nouvelle interface dans le système, ou simplement faire wlan0 <-> eth0, c’est là que se terminent les spécificités du RPI, puis configurez-le à votre goût.

Je voudrais également mentionner la nécessité de configurer des services système personnalisés via systemctl ; il peut être nécessaire de connecter les services en chaîne ; tout cela est dans les manuels systemctl sur le réseau. S’il y a des problèmes au niveau du service, vérifiez les journaux dans journalctl.

Conclusion

À partir des mesures de vitesse, nous avons pu extraire environ 50 Mbps du RPI3 via Wi-Fi (après avoir connecté un adaptateur 5 GHz), ce qui signifie une perte de moitié de la vitesse par rapport à une connexion directe au routeur. J’admets que des modèles RPI plus productifs vous permettront d’obtenir de meilleurs résultats, et que les appareils OpenWrt spécialisés et les solutions prêtes à l’emploi peuvent également être mieux adaptés à vos besoins.

Sources

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

Génération d’images locales : modèle ComfyUI et FLUX

De nos jours, vous n’avez plus besoin de recourir aux services cloud : vous pouvez générer des images de haute qualité entièrement sur votre propre matériel. Dans cet article, je vais décrire comment exécuter le modèle FLUX moderne localement sur votre ordinateur à l’aide de ComfyUI.

ComfyUI utilise une architecture basée sur les nœuds. Cela vous permet de :
– Contrôlez totalement chaque étape de la génération.
– Partagez facilement des « workflows » prêts à l’emploi

FLUX est un grand modèle, la configuration matérielle requise est donc supérieure à celle de SD 1.5 ou SDXL :
Carte vidéo (GPU) : Nvidia RTX avec 12 Go de VRAM ou plus (pour un travail confortable). Si vous disposez de 8 Go ou moins, vous devrez utiliser les versions quantifiées (GGUF ou NF4).
Mémoire vive (RAM) : minimum 16 Go (de préférence 32 Go et plus).
Espace disque : environ 20 à 50 Go pour les modèles et les composants.

Le moyen le plus simple de démarrer FLUX est d’utiliser un modèle prêt à l’emploi. Recherchez simplement le texte de flux à image dans la fenêtre des flux de travail et installez-le.

Écrivez une invite en anglais dans le nœud « Text to Image (Flux.1 Dev) », sélectionnez la résolution (FLUX fonctionne bien avec 1024 x 1024 et même plus) et appuyez sur RUN.

La première génération peut prendre du temps car les modèles seront chargés dans la mémoire de la carte vidéo.

https://github.com/comfyanonymous/ComfyUI

Exécuter macOS dans Docker

Il est possible d’exécuter macOS dans Docker, malgré les objections de ceux qui disent que c’est impossible, et apparemment macOS dispose d’une sorte de système de protection qui peut résister à cela.

Certaines des méthodes classiques d’exécution de macOS sur des machines PC ont été historiquement :
*Hackintosh
* Virtualisation, par exemple à l’aide de VMWare

Hackintosh suppose la présence d’un matériel similaire ou très proche du Mac d’origine. La virtualisation impose certaines exigences en matière de matériel, mais généralement pas aussi strictes que dans le cas de Hackintosh. Cependant, dans le cas de la virtualisation, il existe des problèmes de performances, car macOS n’est pas optimisé pour travailler dans un environnement virtuel.

Récemment, il est devenu possible d’exécuter macOS dans Docker. Ceci est rendu possible par le projet Docker-OSX, qui fournit des images macOS prêtes à l’emploi à exécuter sur Docker. Il convient de noter que Docker-OSX n’est pas un projet Apple officiel et n’est pas pris en charge par celui-ci. Cependant, il vous permet d’exécuter macOS sur Docker et de l’utiliser pour développer et tester des applications.

L’un des premiers projets à exécuter macOS dans Docker :
https://github.com/sickcodes/Docker-OSX

Cependant, je n’ai jamais pu le lancer complètement ; après le chargement dans Recovery OS, mon clavier et ma souris sont tout simplement tombés et je n’ai pas pu continuer l’installation. En même temps, dans le premier menu de démarrage, le clavier fonctionne. Le fait est peut-être que ce projet n’est plus aussi activement pris en charge et qu’il existe des problèmes spécifiques lors de l’exécution sous Windows 11 + WSL2 + Ubuntu.

Un des projets les plus actifs en ce moment :
https://github.com/dockur/macos

Vous permet d’exécuter macOS dans Docker, l’interface fonctionne via le navigateur via le transfert VNC (?). Après le démarrage, macOS est disponible sur http://localhost:5900

J’ai réussi à exécuter ce projet et à installer macOS Big Sur (minute 2020) sur Windows 11 + WSL2 + Ubuntu, mais uniquement en modifiant le fichier de composition, à savoir :

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

VERSION : “11” est la version de macOS, dans ce cas Big Sur
RAM_SIZE : “8G” est la quantité de RAM allouée pour macOS
CPU_CORES : “4” est le nombre de cœurs de processeur alloués à macOS

Pour le moment, exécuter macOS tahoe (16) est également possible, mais il existe un certain nombre de problèmes que les développeurs du projet tentent de résoudre vaillamment.

Cette manière originale de lancer macOS vous permet de l’essayer sur votre matériel non Mac et, après avoir assez souffert, d’aller vous acheter un Mac. Cependant, cela peut être utile pour tester des logiciels sur des systèmes plus anciens et pour le développement général.

Construire Swift dans WSL2 (Linux)

L’écosystème Swift se développe activement en dehors des plates-formes Apple, et il est aujourd’hui assez confortable d’y écrire sous Windows à l’aide du sous-système Windows pour Linux (WSL2). Il convient de noter que pour les assemblys sous Linux/WSL, une version allégée de Swift est disponible – sans frameworks propriétaires Apple (tels que SwiftUI, UIKit, AppKit, CoreData, CoreML, ARKit, SpriteKit et autres bibliothèques spécifiques à iOS/macOS), mais pour les utilitaires de console et le backend, cela est plus que suffisant. Dans cet article, nous allons parcourir étape par étape le processus de préparation de l’environnement et de construction du compilateur Swift à partir du code source dans WSL2 (en utilisant Ubuntu/Debian comme exemple).

Nous mettons à jour la liste des packages et le système lui-même :

sudo apt update && sudo apt upgrade -y

Installez les dépendances nécessaires pour le 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

Installez le compilateur et l’éditeur de liens (LLVM et LLD) :

sudo apt install -y llvm lld

Clonez le dépôt Swift avec toutes les dépendances :

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

Installez Swiftly et prêt à l’emploi avec 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

Commençons la construction (cela prendra beaucoup de temps) :

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

Une fois la construction terminée, ajoutez le chemin d’accès au compilateur à PATH (spécifiez votre chemin d’accès au dossier de construction) :

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

Nous vérifions que la version installée de Swift fonctionne :

swift --version

Créez un fichier de test et exécutez-le :

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

Vous pouvez également compiler le binaire et l’exécuter :

swiftc hello.swift
./hello

Sources

Interprète de modèles en pratique

Dans le dernier article, nous avons examiné la théorie du modèle Interpreter, appris ce qu’est un arbre AST et comment faire abstraction des expressions terminales et non terminales. Cette fois, éloignons-nous de la théorie et voyons comment ce modèle est appliqué dans des projets commerciaux sérieux que nous utilisons tous au quotidien !

Spoiler : Vous utilisez peut-être le modèle Interpreter en ce moment, simplement en lisant ce texte dans votre navigateur !

L’un des exemples les plus frappants et, peut-être, les plus importants de l’utilisation de ce modèle dans l’industrie est JavaScript. Le langage, qui a été créé à l’origine « sur le genou », fonctionne aujourd’hui sur des milliards d’appareils précisément grâce au concept d’interprétation.

10 jours qui ont changé Internet

L’histoire de JavaScript est pleine de légendes. En 1995, Brendan Eich, alors qu’il travaillait chez Netscape Communications, s’est vu confier la tâche de créer un langage de script simple pouvant s’exécuter directement dans un navigateur (Netscape Navigator) pour rendre les pages Web interactives. La direction voulait quelque chose avec une syntaxe similaire à Java alors très populaire, mais destiné non pas aux ingénieurs professionnels, mais aux concepteurs Web.

Eich n’a eu que 10 jours pour écrire le premier prototype du langage, qui s’appelait alors Mocha (puis LiveScript, et seulement ensuite JavaScript pour des raisons marketing). Cette ruée n’était pas fortuite : Microsoft était sur ses talons, qui préparait en même temps activement son propre langage de script VBScript à intégrer dans le navigateur Internet Explorer. Netscape devait de toute urgence publier sa réponse afin de ne pas perdre dans la guerre imminente des navigateurs.

Nous n’avions tout simplement pas le temps d’écrire un compilateur complexe en code machine. La solution évidente et la plus rapide pour Eich était l’architecture du classique Interpreter.

Le premier interprète (SpiderMonkey) fonctionnait comme ceci :

  1. Il lit le code source du texte du script à partir de la page.
  2. L’analyseur lexical a divisé le texte en jetons.
  3. L’analyseur a construit un arbre de syntaxe abstraite (AST). En termes de modèle Interpreter, cet arbre se composait d’expressions terminales (chaînes, nombres comme 42) et de non-terminaux (appels de fonction, instructions comme If, ​​​​While).
  4. Ensuite, la machine virtuelle a « parcouru » cet arbre étape par étape, exécutant les instructions qui y sont intégrées à chaque nœud (en appelant une méthode similaire à Interpret()).

Contexte et objets

Vous vous souvenez de l’objet Context que nous avons dû passer à la méthode Interpret(Context context) dans l’implémentation classique ? L’interpréteur en a besoin pour stocker l’état actuel de la mémoire.

Dans le cas de JavaScript, le rôle de ce contexte au niveau supérieur est joué par un Objet global (par exemple, une fenêtre dans un navigateur). Lorsque votre nœud AST essaie, par exemple, d’écrire du texte à l’écran via document.write(“Hello”), l’interpréteur accède à son contexte (l’objet document) et appelle l’API interne du navigateur souhaitée.

C’est grâce à l’interpréteur que JavaScript est capable d’interagir si facilement avec le DOM (Document Object Model) – ce ne sont que des objets dans un contexte accessibles par les nœuds de l’arborescence.

Évolution de l’interpréteur : Compilation JIT

Historiquement, JS dans les navigateurs est longtemps resté un « pur » interprète. Et cela avait un gros inconvénient : une vitesse lente. L’analyse de l’arborescence et la traversée lente de chaque nœud à chaque fois que le script était exécuté ralentissaient les applications Web complexes.

Avec l’avènement du moteur V8 de Google (intégré à Chrome) en 2008, une révolution s’est produite. Les ingénieurs ont réalisé qu’un seul interprète ne suffisait pas pour le Web moderne. Le moteur est devenu plus complexe : il construit toujours l’arborescence AST, mais utilise désormais la compilation JIT (Just-In-Time).

Les moteurs JS modernes (V8, SpiderMonkey) fonctionnent comme un pipeline complexe :

  1. L’interpréteur de base rapide et stupide commence à exécuter votre code JS instantanément, sans même attendre sa compilation (le modèle classique fonctionne toujours ici).
  2. En parallèle, le moteur surveille les sections de code « chaudes » (boucles ou fonctions appelées des milliers de fois).
  3. Ces sections sont compilées par le compilateur JIT directement dans un code machine optimisé, en contournant l’interpréteur lent.

C’est cette combinaison du démarrage instantané de l’interpréteur et de la puissance de calcul de la compilation qui a permis à JavaScript de conquérir le monde, devenant le langage des serveurs (Node.js) et des applications mobiles (React Native).

Interprète dans l’industrie du jeu vidéo

Malgré la domination du C++ dans l’informatique lourde, le modèle Interpreter est une norme industrielle en matière de développement de jeux pour la création de logique de jeu. Pour quoi? Pour que les concepteurs de jeux puissent créer des jeux sans risquer de « laisser tomber » le moteur ou sans avoir besoin de le recompiler constamment.

Un excellent exemple historique est UnrealScript – le langage dans lequel la logique des jeux Unreal Tournament et Gears of War a été écrite dans Unreal Engine 1, 2 et 3. Le texte a été compilé en bytecode compact et abstrait, qui a ensuite été (interprété) étape par étape par la machine virtuelle du moteur.

Scripts de graphiques visuels (Blueprints)

Aujourd’hui, le texte a été remplacé par une programmation visuelle – le système Blueprints dans Unreal Engine 4 et 5.

Si vous avez déjà ouvert un Blueprint dans Unreal Engine, vous avez vu de nombreux nœuds connectés par des fils. Sur le plan architectural, l’ensemble du graphique Blueprints est un immense arbre de syntaxe abstraite (AST) dessiné à l’écran :

  1. Expressions terminales : Nœuds constants. Par exemple, un nœud qui stocke simplement le nombre 42 ou une chaîne. Ils renvoient une valeur spécifique lorsqu’ils sont interprétés.
  2. Expressions non-terminales : nœuds de calcul (Ajouter) ou nœuds de contrôle de flux (Branche). Ils ont des entrées d’arguments, que l’interpréteur évalue d’abord de manière récursive avant de produire le résultat sous forme de broche de sortie.

Et le rôle de contexte ici est joué par la mémoire d’une instance d’un objet de jeu spécifique (acteur). La machine d’interprétation « parcourt » ce graphique en toute sécurité, demandant des données et effectuant des transitions.

Où d’autre l’interprète est-il utilisé ?

Le modèle d’interpréteur peut être trouvé dans presque tous les systèmes complexes où des instructions dynamiques doivent être exécutées. Voici quelques exemples de logiciels commerciaux :

  • Langages de programmation interprétés (Python, Ruby, PHP). L’ensemble de leur exécution est basé sur le modèle classique. Par exemple, l’implémentation de référence CPython analyse d’abord votre script .py dans un AST, le compile en bytecode, puis une énorme machine virtuelle (boucle de calcul) interprète ce bytecode étape par étape.
  • Machine virtuelle Java (JVM). Initialement, le code Java n’est pas compilé en instructions machine, mais en bytecode. Lorsque vous exécutez l’application, la JVM agit comme un interprète (mais avec une compilation JIT agressive, tout comme dans la V8).
  • Bases de données et SQL Lorsque vous émettez une requête SQL (utilisateurs SELECT * FROM) dans PostgreSQL ou MySQL, le moteur de base de données agit comme un interpréteur. Il effectue une analyse lexicale, construit un arbre de requêtes AST, génère un plan d’exécution, puis « interprète » littéralement ce plan en itérant sur les lignes des tables.
  • Expressions régulières (RegEx). Tout moteur d’expression régulière analyse en interne un modèle de chaîne (par exemple, ^\d{3}-\d{2}$) dans un graphe d’état (NFA/DFA Automata), que l’interpréteur interne traverse ensuite, en faisant correspondre chaque caractère d’entrée avec les sommets de ce graphe.
  • Unity Shader Graph / Unreal Material Editor – interprète les nœuds visuels dans un code de shader modulaire (GLSL/HLSL).
  • Nœuds géométriques Blender : interprètez les opérations mathématiques et géométriques pour générer de manière procédurale des modèles 3D en temps réel.

Total

Le modèle Interpreter a depuis longtemps dépassé le cadre de « l’écriture de votre propre calculatrice ». Il s’agit de la norme industrielle la plus puissante. Des moteurs JavaScript qui exécutent chaque jour des gigaoctets de code dans les coulisses des navigateurs, aux concepteurs de jeux qui vous permettent de créer une logique complexe sans connaissance du C++, les interpréteurs restent l’un des concepts architecturaux les plus importants du développement informatique moderne.

appel ollama

Si vous utilisez Ollama et que vous ne souhaitez pas écrire votre propre wrapper API à chaque fois,
le projet ollama_call simplifie considérablement le travail.

Il s’agit d’une petite bibliothèque Python qui vous permet d’envoyer une requête à un LLM local avec une seule fonction
et recevez immédiatement une réponse, y compris au format JSON.

Installation

pip install ollama-call

Pourquoi est-ce nécessaire

  • code minimal pour travailler avec le modèle ;
  • Réponse JSON structurée pour un traitement ultérieur ;
  • pratique pour les prototypes rapides et les MVP ;
  • prend en charge la sortie en streaming si nécessaire.

Utiliser un exemple

from ollama_call import ollama_call

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

print(response)

Quand c’est particulièrement utile

  • vous écrivez des scripts ou des services sur Ollama ;
  • besoin d’un format de réponse prévisible ;
  • il n’y a aucune volonté de connecter des frameworks lourds.

Total

ollama_call est un wrapper léger et clair pour travailler avec Ollama de Python.
Un bon choix si la simplicité et la rapidité des résultats sont importantes.

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

SFAP : un cadre modulaire pour l’acquisition et le traitement modernes des données

Dans le contexte du développement actif de l’automatisation et de l’intelligence artificielle, la tâche de collecter efficacement,
Le nettoyage et la transformation des données deviennent essentiels. La plupart des solutions se ferment uniquement
étapes distinctes de ce processus, nécessitant une intégration et un support complexes.

SFAP (Seek · Filter · Adapt · Publish) est un projet open source en Python,
qui propose une approche holistique et extensible du traitement des données à toutes les étapes de leur cycle de vie :
de la recherche des sources à la publication du résultat final.

Qu’est-ce que SFAP

SFAP est un framework asynchrone construit autour d’un concept clair de pipeline de traitement de données.
Chaque étape est logiquement séparée et peut être étendue ou remplacée indépendamment.

Le projet est basé sur le modèle architectural de la Chaîne de responsabilité, qui fournit :

  • flexibilité de configuration du pipeline ;
  • tests simples des étapes individuelles ;
  • évolutivité pour des charges élevées ;
  • séparation nette des responsabilités entre les composants.

Principales étapes du pipeline

Recherche – recherche de données

A cette étape, les sources de données sont découvertes : pages web, API, stockages de fichiers
ou d’autres flux d’informations. SFAP facilite la connexion de nouvelles sources sans modification
le reste du système.

Filtre – filtrage

Le filtrage est conçu pour supprimer le bruit : contenus non pertinents, doublons, éléments techniques
et des données de mauvaise qualité. Ceci est essentiel pour les étapes de traitement ultérieures.

Adapter – adaptation et traitement

L’étape d’adaptation est responsable de la transformation des données : normalisation, structuration,
traitement sémantique et intégration avec des modèles d’IA (y compris génératifs).

Publier – publication

Au stade final, les données sont publiées au format cible : bases de données, API, fichiers, services externes
ou des plateformes de contenu. SFAP ne limite pas la manière dont le résultat est fourni.

Principales caractéristiques du projet

  • Architecture asynchrone basée sur asyncio
  • Modularité et extensibilité
  • Prise en charge des pipelines de traitement complexes
  • Prêt pour l’intégration avec les solutions IA/LLM
  • Convient aux systèmes très chargés

Cas d’utilisation pratiques

  • Agrégation et analyse de sources d’actualités
  • Préparer des ensembles de données pour le machine learning
  • Pipeline de contenu automatisé
  • Nettoyage et normalisation des flux de données volumineux
  • Intégration de données provenant de sources hétérogènes

Démarrer avec SFAP

Tout ce dont vous avez besoin pour commencer est :

  1. Cloner le dépôt du projet ;
  2. Installer les dépendances Python ;
  3. Définissez vos propres étapes de pipeline ;
  4. Démarrez un processus de traitement de données asynchrone.

Le projet s’adapte facilement à des tâches métier spécifiques et peut évoluer avec le système,
sans se transformer en monolithe.

Conclusion

SFAP n’est pas seulement un analyseur ou un collecteur de données, mais un cadre à part entière pour créer
systèmes de pipeline de données modernes. Il convient aux développeurs et aux équipes soucieux de
évolutif, architecturalement propre et prêt pour les données.
Le code source du projet est disponible sur GitHub :
https://github.com/demensdeum/SFAP

Pourquoi ne puis-je pas corriger le bug ?

Vous passez des heures à travailler sur le code, à examiner des hypothèses, à ajuster les conditions, mais le bug est toujours reproduit. Cela vous semble familier ? Cet état de frustration est souvent appelé « chasse aux fantômes ». Le programme semble vivre sa propre vie, ignorant vos corrections.

L’une des raisons les plus courantes – et la plus ennuyeuse – de cette situation est la recherche d’une erreur au mauvais endroit dans l’application.

Le piège des « faux symptômes »

Lorsque nous constatons une erreur, notre attention est attirée sur l’endroit où elle a « tiré ». Mais dans les systèmes complexes, l’apparition d’un bug (plantage ou valeur incorrecte) n’est que la fin d’une longue chaîne d’événements. Lorsque vous essayez de réparer la fin, vous combattez les symptômes, pas la maladie.

C’est là qu’intervient le concept d’organigramme.

Comment ça marche en réalité

Bien sûr, il n’est pas nécessaire de dessiner (dessiner) directement un organigramme sur papier à chaque fois, mais il est important de l’avoir en tête ou à portée de main comme guide architectural. Un organigramme vous permet de visualiser le fonctionnement d’une application sous forme d’arbre de résultats.

Sans comprendre cette structure, le développeur tâtonne souvent dans le noir. Imaginez la situation : vous modifiez la logique dans une branche de condition, tandis que l’application (en raison d’un certain ensemble de paramètres) va dans une branche complètement différente à laquelle vous n’avez même pas pensé.


Résultat : vous passez des heures sur une correction de code « parfaite » dans une partie de l’algorithme, ce qui, bien sûr, ne fait rien pour résoudre le problème dans une autre partie de l’algorithme où il échoue réellement.

Algorithme pour vaincre un bug

Pour arrêter de frapper à porte fermée, vous devez changer votre approche du diagnostic :

  • Recherchez l’état dans l’arborescence des résultats :Avant d’écrire du code, vous devez déterminer exactement le chemin emprunté par l’application. À quel moment la logique a-t-elle pris une mauvaise tournure ? Quel État spécifique (État) est à l’origine du problème ?
  • La reproduction représente 80 % de réussite : elle est généralement effectuée par des testeurs et des tests automatisés. Si le bug est « flottant », le développement est impliqué dans le processus de recherche conjointe des conditions.
  • Utilisez autant d’informations que possible : les journaux, la version du système d’exploitation, les paramètres de l’appareil, le type de connexion (Wi-Fi/5G) et même un opérateur de télécommunications spécifique sont importants pour la localisation.

« Photographie » du moment de l’erreur

Idéalement, pour le corriger, vous devez obtenir l’état complet de l’application au moment où le bug a été reproduit. Les journaux d’interaction sont également d’une importance cruciale : ils montrent non seulement le point final, mais également l’intégralité du parcours utilisateur (quelles actions ont précédé l’échec). Cela aide à comprendre comment recréer à nouveau un état similaire.

Conseil futur : si vous rencontrez un cas complexe, ajoutez des informations de journalisation de débogage étendues à cette section de code au cas où la situation se reproduirait.

Le problème des États « insaisissables » à l’ère de l’IA

Dans les systèmes modernes utilisant le LLM (Large Language Models), le déterminisme classique (« une entrée, une sortie ») est souvent violé. Vous pouvez transmettre exactement les mêmes données d’entrée, mais obtenir un résultat différent.

Cela se produit en raison du non-déterminisme des systèmes de production modernes :

  • Parallélisme GPU : les opérations en virgule flottante GPU ne sont pas toujours associatives. En raison de l’exécution parallèle des threads, l’ordre dans lequel les nombres sont ajoutés peut légèrement changer, ce qui peut affecter le résultat.
  • Température et limitation du GPU : la vitesse d’exécution et la répartition de la charge peuvent dépendre de l’état physique du matériel. Dans les modèles volumineux, ces différences microscopiques s’accumulent et peuvent conduire à la sélection d’un jeton différent en sortie.
  • Lot dynamique : dans le cloud, votre demande est combinée avec d’autres. Différentes tailles de lots modifient les mathématiques des calculs dans les noyaux.

Dans de telles conditions, il devient presque impossible de reproduire « ce même état ». Seule une approche statistique des tests peut vous sauver la vie.

En cas d’échec de la logique : problèmes de mémoire

Si vous travaillez avec des langages « non sécurisés » (C ou C++), le bug peut survenir en raison d’une corruption de la mémoire.

Ce sont les cas les plus graves : une erreur dans un module peut « écraser » les données dans un autre. Cela conduit à des pannes totalement inexplicables et isolées qui ne peuvent pas être retracées à l’aide de la logique d’application normale.

Comment se protéger au niveau architectural ?

Pour éviter de tels bugs « mystiques », vous devez utiliser des approches modernes :

  • Modèles de programmation multithread : une synchronisation claire élimine les conditions de concurrence.
  • Langages thread-safe : outils garantissant la sécurité de la mémoire au moment de la compilation :
    • Rust : le système de propriété élimine les erreurs de mémoire.
    • Concurrency Swift 6 :Contrôles rigoureux de l’isolation des données.
    • Erlang : Isolation complète des processus grâce au modèle d’acteur.

Résumé

Corriger un bug ne consiste pas à écrire du nouveau code, mais à comprendre comment fonctionne l’ancien. N’oubliez pas : vous pourriez perdre du temps à éditer une branche à laquelle la direction ne touche même pas. Enregistrez l’état du système, tenez compte du facteur de non-déterminisme de l’IA et choisissez des outils sûrs.

Bloquer les diagrammes en pratique sans formol

Le diagramme de blocs est un outil visuel qui aide à transformer un algorithme complexe en une séquence d’actions compréhensible et structurée. De la programmation à la gestion des processus métier, ils servent de langage universel pour la visualisation, l’analyse et l’optimisation des systèmes les plus complexes.

Imaginez une carte où au lieu des routes est logique et au lieu des villes – des actions. Il s’agit d’un schéma de bloc – un outil indispensable pour la navigation dans les processus les plus déroutants.

Exemple 1: Schéma de lancement de jeu simplifié
Pour comprendre le principe du travail, présentons un schéma de lancement de jeu simple.

Ce schéma montre le script parfait lorsque tout se produit sans échecs. Mais dans la vraie vie, tout est beaucoup plus compliqué.

Exemple 2: Schéma élargi pour démarrer le jeu avec le chargement des données
Les jeux modernes nécessitent souvent une connexion Internet pour télécharger les données des utilisateurs, l’enregistrement ou les paramètres. Ajoutons ces étapes à notre schéma.

Ce schéma est déjà plus réaliste, mais que se passera-t-il en cas de problème?

comment était-ce: un jeu qui “s’est cassé” avec la perte d’Internet

Au début du projet, les développeurs n’ont pas pu prendre en compte tous les scénarios possibles. Par exemple, ils se sont concentrés sur la logique principale du jeu et ne pensaient pas ce qui se passerait si le joueur avait une connexion Internet.

Dans une telle situation, le schéma de bloc de leur code ressemblerait à ceci:

Dans ce cas, au lieu d’émettre une erreur ou de fermer correctement, le jeu s’est figé au stade de l’attente des données qu’elle n’a pas reçues en raison de l’absence de connexion. Cela a conduit à “l’écran noir” et au gel de l’application.

comment il est devenu: correction sur les plaintes des utilisateurs

Après les plaintes de nombreux utilisateurs concernant le plan de volant, l’équipe du développeur s’est rendu compte que nous devions corriger l’erreur. Ils ont apporté des modifications au code en ajoutant une unité de traitement d’erreur qui permet à l’application de répondre correctement au manque de connexion.

C’est à quoi ressemble le diagramme de bloc corrigé, où les deux scénarios sont pris en compte:

Grâce à cette approche, le jeu informe désormais correctement l’utilisateur du problème, et dans certains cas, il peut même passer en mode hors ligne, vous permettant de continuer le jeu. Ceci est un bon exemple de la raison pour laquelle les diagrammes de blocs sont si importants : ils font que le développeur réfléchit non seulement à la façon idéale d’exécution, mais aussi à toutes les échecs possibles, ce qui rend le produit final beaucoup plus stable et fiable.

comportement incertain

La suspension et les erreurs ne sont qu’un exemple de comportement imprévisible du programme. En programmation, il existe un concept de comportement incertain (comportement non défini) – Il s’agit d’une situation où la norme du langage ne décrit pas comment le programme devrait se comporter dans un certain cas.

Cela peut conduire à n’importe quoi: des «ordures» aléatoires dans le retrait de l’échec du programme ou même de la vulnérabilité de sécurité sérieuse. Le comportement indéfini se produit souvent lorsque vous travaillez avec la mémoire, par exemple, avec des lignes dans la langue de C.

Un exemple de la langue C:

Imaginez que le développeur a copié la ligne dans le tampon, mais a oublié d’ajouter à la fin le symbole zéro (`\ 0`) , qui marque la fin de la ligne.

Voici à quoi ressemble le code:

#include 

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

memcpy(buffer, my_string, 5);

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

Résultat attendu: “Bonjour”
Le résultat réel est imprévisible.

Pourquoi cela se produit-il? La fonction `printf` avec le spécificateur ‘% S` s’attend à ce que la ligne se termine par un symbole zéro. S’il ne l’est pas, elle continuera à lire la mémoire en dehors du tampon mis en évidence.

Voici le schéma de bloc de ce processus avec deux résultats possibles:

Ceci est un exemple clair de la raison pour laquelle les diagrammes de bloc sont si importants: ils font réfléchir le développeur non seulement à la manière idéale d’exécution, mais aussi à tous les échecs possibles, y compris des problèmes de niveau de bas niveau, ce qui rend le produit final beaucoup plus stable et fiable.

Pixel Perfect: Mythe ou réalité à l’ère de la déclarativité?

Dans le monde du développement des interfaces, il existe un concept commun – “Pixel parfait dans le lodge” . Cela implique la reproduction la plus précise de la machine de conception au plus petit pixel. Pendant longtemps, c’était une étalon-or, en particulier à l’ère d’une conception Web classique. Cependant, avec l’arrivée du mile déclarant et la croissance rapide de la variété des appareils, le principe de “Pixel parfait” devient plus éphémère. Essayons de comprendre pourquoi.

Imperial Wysiwyg vs code déclaratif: Quelle est la différence?

Traditionnellement, de nombreuses interfaces, en particulier de bureau, ont été créées en utilisant des approches impératives ou Wysiwyg (ce que vous voyez est ce que vous obtenez) des éditeurs. Dans de tels outils, le concepteur ou le développeur manipule directement avec des éléments, en les plaçant sur toile avec une précision au pixel. Il est similaire à travailler avec un éditeur graphique – vous voyez à quoi ressemble votre élément, et vous pouvez certainement le positionner. Dans ce cas, la réalisation de “Pixel Perfect” était un objectif très réel.

Cependant, le développement moderne est de plus en plus basé sur miles déclaratifs . Cela signifie que vous ne dites pas à l’ordinateur de “mettre ce bouton ici”, mais décrivez ce que vous voulez obtenir. Par exemple, au lieu d’indiquer les coordonnées spécifiques de l’élément, vous décrivez ses propriétés: “Ce bouton doit être rouge, avoir des indentations 16px de tous les côtés et être au centre du conteneur.” Freimvorki comme React, Vue, Swiftui ou Jetpack composent simplement ce principe.

Pourquoi “Pixel Perfect” ne fonctionne pas avec un mile déclaratif pour de nombreux appareils

Imaginez que vous créez une application qui devrait être tout aussi bonne sur l’iPhone 15 Pro Max, Samsung Galaxy Fold, iPad Pro et une résolution 4K. Chacun de ces appareils a une résolution d’écran différente, une densité de pixels, des parties et des tailles physiques.

Lorsque vous utilisez l’approche déclarative, le système lui-même décide comment afficher votre interface décrite sur un appareil particulier, en tenant compte de tous ses paramètres. Vous définissez les règles et les dépendances, pas les coordonnées sévères.

* Adaptabilité et réactivité: L’objectif principal des miles déclaratifs est de créer des interfaces adaptatives et réactives . Cela signifie que votre interface doit s’adapter automatiquement à la taille et à l’orientation de l’écran sans se casser et maintenir la lisibilité. Si nous cherchions à «Pixel parfait» sur chaque appareil, nous devions créer d’innombrables options pour la même interface, ce qui nivellera complètement les avantages de l’approche déclarative.
* densité de pixels (dpi / ppi): Les appareils ont une densité de pixels différente. Le même élément avec la taille de 100 pixels “virtuels” sur un appareil à haute densité sera beaucoup plus petit que sur un dispositif à faible densité, si vous ne prenez pas en compte la mise à l’échelle. Les cadres déclaratifs sont résumés par des pixels physiques, en travaillant avec des unités logiques.
* Contenu dynamique: Le contenu dans les applications modernes est souvent dynamique – son volume et sa structure peuvent varier. Si nous détendions durement les pixels, tout changement de texte ou d’image entraînerait “l’effondrement” de la mise en page.
* diverses plates-formes: En plus de la variété des appareils, il existe différents systèmes d’exploitation (iOS, Android, Web, Desktop). Chaque plate-forme a sa propre conception, ses commandes standard et ses polices. Une tentative de faire une interface parfaite absolument identique et parfaite sur toutes les plates-formes conduirait à un type non naturel et à une mauvaise expérience utilisateur.

Les anciennes approches n’ont pas disparu, mais ont évolué

Il est important de comprendre que l’approche des interfaces n’est pas un choix binaire entre “impératif” et “déclaratif”. Historiquement, pour chaque plate-forme, il y avait ses propres outils et approches de la création d’interfaces.

* Fichiers d’interface natif: Pour iOS, c’était XIB / storyboards, pour les fichiers de marquage Android-XML. Ces fichiers sont une disposition WYSIWYG parfaite avec pixel, qui est ensuite affichée à la radio comme dans l’éditeur. Cette approche n’a disparu nulle part, elle continue de se développer, s’intégrant à des cadres déclaratifs modernes. Par exemple, Swiftui dans Apple et Jetpack composent dans Android se déroulent sur le chemin d’un code purement déclaratif, mais en même temps, a conservé l’opportunité d’utiliser une disposition classique.
* Solutions hybrides: Souvent dans les projets réels, une combinaison d’approches est utilisée. Par exemple, la structure de base de l’application peut être mise en œuvre de manière déclarative et, pour un positionnement précis des éléments, des méthodes impératives de niveau inférieur peuvent être utilisées ou des composants natifs développés en tenant compte des spécificités de la plate-forme.

du monolithe à l’adaptabilité: comment l’évolution des appareils a formé un mile déclaratif

Le monde des interfaces numériques a subi d’énormes changements au cours des dernières décennies. Des ordinateurs stationnaires avec des permis fixes, nous sommes arrivés à l’ère de la croissance exponentielle de la variété des appareils utilisateur . Aujourd’hui, nos applications devraient fonctionner aussi bien sur:

* Smartphones de tous les facteurs de forme et tailles d’écran.
* tablettes avec leurs modes d’orientation uniques et un écran séparé.
* ordinateurs portables et ordinateurs de bureau avec divers permis de moniteurs.
* TVS et centres de médias , contrôlés à distance. Il est à noter que même pour les téléviseurs, dont les remarques peuvent être simples en tant que Apple TV Remote avec un minimum de boutons, ou vice versa, surchargée de nombreuses fonctions, les exigences modernes pour les interfaces sont telles que le code ne doit pas nécessiter une adaptation spécifique pour ces caractéristiques d’entrée. L’interface doit fonctionner “comme si elle-même”, sans une description supplémentaire de ce que “comment” interagir avec une télécommande spécifique.
* montres intelligentes et appareils portables avec des écrans minimalistes.
* Casques de réalité virtuelle (VR) , nécessitant une approche complètement nouvelle d’une interface spatiale.
* Dispositifs de réalité augmentés (AR) , appliquant des informations sur le monde réel.
* Informations automobiles et systèmes de divertissement .
* Et même appareils ménagers : des réfrigérateurs avec des écrans sensoriels et des machines à laver avec des écrans interactifs aux fours et systèmes intelligents de la maison intelligente.

Chacun de ces appareils a ses propres fonctionnalités uniques: dimensions physiques, rapport des parties, densité de pixels, méthodes d’entrée (écran tactile, souris, contrôleurs, gestes, commandes vocales) et, surtout, les subtilités de l’environnement utilisateur . Par exemple, un VR Shlesh nécessite une immersion profonde, et un travail rapide et intuitif pour les smartphones en déplacement, tandis que l’interface du réfrigérateur doit être aussi simple et grande pour une navigation rapide.

Approche classique: le fardeau de soutenir les interfaces individuelles

À l’ère de la domination des ordinateurs de bureau et des premiers appareils mobiles, l’activité habituelle était la création et la prise en charge de de fichiers d’interface individuels ou même un code d’interface complètement séparé pour chaque plate-forme .

* Développement sous iOS nécessite souvent l’utilisation de storyboards ou de fichiers XIB dans Xcode, en écrivant du code sur objectif-c ou Swift.
* Pour Android Les fichiers de marquage XML et le code sur Java ou Kotlin ont été créés.
* Les interfaces Web activées sur HTML / CSS / JavaScript.
* Pour les applications C ++ sur diverses plates-formes de bureau, leurs cadres et outils spécifiques ont été utilisés:
* Dans Windows il s’agissait de MFC (classes Microsoft Foundation), de l’API WIN32 avec des éléments de dessin manuel ou à l’aide de fichiers de ressources pour les fenêtres de dialogue et les éléments de contrôle.
* Le cacao (objectif-c / swift) ou l’ancienne API carbone pour le contrôle direct de l’interface graphique a été utilisé dans macOS .
* Dans Systems de type Linux / Unix , des bibliothèques comme GTK + ou QT ont souvent été utilisées, ce qui a fourni leur ensemble de widgets et de mécanismes pour créer des interfaces, souvent via des fichiers de marquage de type XML (par exemple, des fichiers .UI dans le concepteur QT) ou une création de logiciels directs d’éléments.

Cette approche a assuré un contrôle maximal sur chaque plate-forme, vous permettant de prendre en compte toutes ses caractéristiques spécifiques et ses éléments indigènes. Cependant, il a eu un énorme inconvénient: duplication des efforts et des coûts énormes de soutien . Le moindre changement de conception ou de fonctionnalité a nécessité l’introduction d’un droit à plusieurs bases de code indépendantes, en fait. Cela s’est transformé en un vrai cauchemar pour les équipes de développeurs, ralentissant la sortie des nouvelles fonctions et augmentant la probabilité d’erreurs.

Miles déclaratifs: une seule langue pour la diversité

C’est en réponse à cette complication rapide que les miles déclaratifs sont apparus comme le paradigme dominant. Les framws comme react, vue, swiftui, jetpack compose et d’autres ne sont pas seulement une nouvelle façon d’écrire du code, mais un changement fondamental dans la pensée.

L’idée principale de l’approche déclarative : Au lieu de dire que le système «comment» de dessiner chaque élément (impératif), nous décrivons «ce que« nous voulons voir (déclaratif). Nous définissons les propriétés et l’état de l’interface, et le cadre décide comment l’afficher au mieux sur un appareil particulier.

Cela est devenu possible grâce aux principaux avantages suivants:

1. L’abstraction des détails de la plate-forme: Les Fraimvorki déclaratifs sont spécialement conçus pour oublier les détails de bas niveau de chaque plate-forme. Le développeur décrit les composants et leurs relations à un niveau d’abstraction plus élevé, en utilisant un seul code transféré.
2. Adaptation et réactivité automatique: Freimvorki assumez la responsabilité de la mise à l’échelle automatique, de la mise en page et de l’adaptation des éléments en différentes tailles d’écrans, de densité de pixels et de méthodes d’entrée. Ceci est réalisé grâce à l’utilisation de systèmes de disposition flexibles, tels que Flexbox ou Grid, et des concepts similaires aux “pixels logiques” ou “DP”.
3. Cohérence de l’expérience utilisateur: Malgré les différences externes, l’approche déclarative vous permet de maintenir une seule logique de comportement et d’interaction dans toute la famille des appareils. Cela simplifie le processus de test et offre une expérience utilisateur plus prévisible.
4. Accélération du développement et réduction des coûts: Avec le même code capable de travailler sur de nombreuses plateformes, est considérablement réduit par le temps et le coût de développement et de soutien . Les équipes peuvent se concentrer sur la fonctionnalité et la conception, et non sur la réécriture répétée de la même interface.
5. Prépité pour l’avenir: La capacité de résumer des spécificités des appareils actuels rend le code déclaratif plus plus résistant à l’émergence de nouveaux types de dispositifs et de facteurs de forme . Freimvorki peut être mis à jour pour prendre en charge les nouvelles technologies, et votre code déjà écrit recevra ce support relativement transparent.

Conclusion

Le mile déclaratif n’est pas seulement une tendance de la mode, mais l’étape évolutive nécessaire causée par le développement rapide des appareils utilisateur, y compris la sphère l’Internet des objets (IoT) et les appareils de ménage intelligents. Il permet aux développeurs et aux concepteurs de créer des interfaces complexes, adaptatives et uniformes, sans se noyer dans des implémentations spécifiques sans fin pour chaque plate-forme. La transition du contrôle impératif de chaque pixel à la description déclarative de l’état souhaité est une reconnaissance que dans le monde des futures interfaces devrait être flexible, transféré et intuitif quel que soit l’écran qu’ils sont affichés.

Les programmeurs, les concepteurs et les utilisateurs doivent apprendre à vivre dans ce nouveau monde. Les détails supplémentaires du Pixel Perfect, conçu pour un appareil ou une résolution particulière, conduisent à des coûts de temps inutiles pour le développement et le support. De plus, de telles dispositions sévères peuvent tout simplement ne pas fonctionner sur des appareils avec des interfaces non standard, telles que les téléviseurs d’entrée limités, les changements VR et AR, ainsi que d’autres appareils du futur, que nous ne connaissons même pas aujourd’hui. Flexibilité et adaptabilité – Ce sont les clés de la création d’interfaces réussies dans le monde moderne.

Pourquoi les programmeurs ne font-ils rien même avec les réseaux de neurones

Aujourd’hui, les réseaux de neurones sont utilisés partout. Les programmeurs les utilisent pour générer du code, expliquer d’autres solutions, automatiser les tâches de routine et même créer des applications entières à partir de zéro. Il semblerait que cela entraîne une augmentation de l’efficacité, réduisant les erreurs et l’accélération du développement. Mais la réalité est beaucoup plus prosaïque: beaucoup ne réussissent toujours pas. Les réseaux de neurones ne résolvent pas les problèmes clés – ils n’éclairent que la profondeur de l’ignorance.

Dépendance complète sur LLM au lieu de comprendre

La raison principale est que de nombreux développeurs comptent complètement sur LLM, ignorant la nécessité d’une compréhension approfondie des outils avec lesquels ils travaillent. Au lieu d’étudier la documentation – une demande de chat. Au lieu d’analyser les raisons de l’erreur – copier la décision. Au lieu de solutions architecturales – la génération de composants selon la description. Tout cela peut fonctionner à un niveau superficiel, mais dès qu’une tâche non standard survient, l’intégration avec un vrai projet ou le besoin d’un réglage fin est nécessaire, tout s’effondre.

manque de contexte et pratiques obsolètes

Les réseaux de neurones génèrent le code généralisé. Ils ne prennent pas en compte les spécificités de votre plate-forme, de votre version des bibliothèques, des restrictions environnementales ou des solutions architecturales du projet. Ce qui est généré semble souvent plausible, mais n’a rien à voir avec le code réel et pris en charge. Même des recommandations simples peuvent ne pas fonctionner si elles appartiennent à la version obsolète du cadre ou utilisent des approches qui ont longtemps été reconnues comme inefficaces ou dangereuses. Les modèles ne comprennent pas le contexte – ils dépendent des statistiques. Cela signifie que les erreurs et les antipattères, populaires en code ouvert, seront reproduits encore et encore.

Redondance, inefficacité et manque de profilage

L’IA générée par le code est souvent redondante. Il comprend des dépendances inutiles, des doublons la logique, ajoute des abstractions inutilement. Il s’avère une structure lourde inefficace qui est difficile à soutenir. Ceci est particulièrement aigu dans le développement mobile, où la taille du gang, le temps de réponse et la consommation d’énergie sont essentielles.

Le réseau neuronal ne mène pas le profilage, ne prend pas en compte les restrictions du CPU et du GPU, ne se soucie pas des fuites de mémoire. Il n’analyse pas l’efficacité du code en pratique. L’optimisation est toujours faite à la main, nécessitant une analyse et un examen. Sans cela, l’application devient lente, instable et à forte intensité de ressources, même si elle semble «correcte» du point de vue de la structure.

Vulnérabilité et menace pour la sécurité

N’oubliez pas la sécurité. Il existe déjà des cas connus lorsque les projets sont partiellement ou entièrement créés à l’aide de LLM ont été piratés avec succès. Les raisons sont typiques: l’utilisation de fonctions dangereuses, le manque de vérification des données d’entrée, les erreurs dans la logique de l’autorisation, la fuite à travers des dépendances externes. Le réseau neuronal peut générer un code vulnérable simplement parce qu’il a été trouvé dans les référentiels ouverts. Sans la participation de spécialistes de la sécurité et une révision complète, de telles erreurs deviennent facilement des points d’entrée pour les attaques.

La loi est Pareto et l’essence des défauts

La loi de Pareto travaille clairement avec les réseaux de neurones: 80% du résultat est obtenu en raison de 20% des efforts. Le modèle peut générer une grande quantité de code, créer la base du projet, répartir la structure, organiser des types, connecter les modules. Cependant, tout cela peut être dépassé, incompatible avec les versions actuelles des bibliothèques ou des cadres, et nécessite une révision manuelle importante. L’automatisation ici fonctionne plutôt comme un projet qui doit être vérifié, traité et adapté à des réalités spécifiques du projet.

Optimisme de mise en garde

Néanmoins, l’avenir semble encourageant. Mise à jour constante des ensembles de données de formation, intégration avec la documentation actuelle, vérification de l’architecture automatisée, conformité à la conception et aux modèles de sécurité – tout cela peut radicalement modifier les règles du jeu. Peut-être que dans quelques années, nous pouvons vraiment écrire le code plus rapidement, plus sûr et plus efficacement, en nous appuyant sur LLM en tant que co-auteur technique. Mais pour l’instant – hélas – il faut vérifier, réécrit et modifié manuellement.

Les réseaux de neurones sont un outil puissant. Mais pour qu’il travaille pour vous, et non contre vous, vous avez besoin d’une base, de la pensée critique et de la volonté de prendre le contrôle à tout moment.

Astuces de core de vibration: pourquoi LLM ne fonctionne toujours pas avec un solide, sec et propre

Avec le développement de modèles de grandes langues (LLM), tels que Chatgpt, de plus en plus de développeurs les utilisent pour générer du code, une architecture de conception et une intégration accélérée. Cependant, avec une application pratique, elle devient perceptible: les principes classiques de l’architecture – solide, sec, propre – s’entendent mal avec les particularités de la codgendation LLM.

Cela ne signifie pas que les principes sont obsolètes – au contraire, ils fonctionnent parfaitement avec le développement manuel. Mais avec LLM, l’approche doit être adaptée.

Pourquoi LLM ne peut pas faire face aux principes architecturaux

Encapsulation

L’incapsulation nécessite de comprendre les frontières entre parties du système, des connaissances sur les intentions du développeur, ainsi que des restrictions d’accès strictes. LLM simplifie souvent la structure, rend les champs publics sans raison ou dupliquent la mise en œuvre. Cela rend le code plus vulnérable aux erreurs et viole les limites architecturales.

Résumé et interfaces

Les modèles de conception, tels qu’une usine abstraite ou une stratégie, nécessitent une vision holistique du système et la compréhension de sa dynamique. Les modèles peuvent créer une interface sans objectif clair sans assurer sa mise en œuvre ou violer la connexion entre les couches. Le résultat est une architecture excessive ou non fonctionnelle.

sec (donolt répétez-vous)

LLM ne cherchez pas à minimiser le code de répétition – au contraire, il leur est plus facile de dupliquer des blocs que de faire une logique générale. Bien qu’ils puissent offrir une refactorisation sur demande, par défaut, les modèles ont tendance à générer des fragments «auto-suffisants», même si cela conduit à une redondance.

Architecture propre

Clean implique une hiérarchie stricte, une indépendance des cadres, une dépendance dirigée et une connectivité minimale entre les couches. La génération d’une telle structure nécessite une compréhension globale du système – et du travail LLM au niveau de la probabilité de mots, et non de l’intégrité architecturale. Par conséquent, le code est mitigé, avec violation des directions de dépendance et une division simplifiée en niveaux.

ce qui fonctionne mieux lorsque vous travaillez avec LLM


Mouillé au lieu de sec
L’approche humide (écrivez tout deux fois) est plus pratique pour travailler avec LLM. La duplication du code ne nécessite pas de contexte du modèle de rétention, ce qui signifie que le résultat est prévisible et est plus facile à corriger correctement. Il réduit également la probabilité de connexions et de bogues non évidents.

De plus, la duplication aide à compenser la mémoire courte du modèle: si un certain fragment de logique se trouve à plusieurs endroits, LLM est plus susceptible de le prendre en compte avec une génération plus approfondie. Cela simplifie l’accompagnement et augmente la résistance à “l’oubli”.

Structures simples au lieu de l’encapsulation

Éviter l’encapsulation complexe et s’appuyer sur la transmission directe des données entre les parties du code, vous pouvez considérablement simplifier à la fois la génération et le débogage. Cela est particulièrement vrai avec un développement itératif rapide ou une création de MVP.

Architecture simplifiée

Une structure simple et plate du projet avec une quantité minimale de dépendances et d’abstractions donne un résultat plus stable pendant la génération. Le modèle adapte un tel code plus facile et moins souvent viole les connexions attendues entre les composants.

Intégration du SDK – fiable manuellement

La plupart des modèles linguistiques sont formés sur des versions obsolètes de la documentation. Par conséquent, lors de la génération d’instructions pour l’installation du SDK, les erreurs apparaissent souvent: commandes obsolètes, paramètres non pertinents ou liens vers des ressources inaccessibles. La pratique montre: il est préférable d’utiliser la documentation officielle et le réglage manuel, laissant LLM un rôle auxiliaire – par exemple, générer un code de modèle ou une adaptation des configurations.

Pourquoi les principes fonctionnent-ils toujours – mais avec le développement manuel

Il est important de comprendre que les difficultés de solides, sèches et propres concernent la codhégénération par LLM. Lorsque le développeur écrit le code manuellement, ces principes continuent de démontrer leur valeur: ils réduisent la connectivité, simplifient le soutien, augmentent la lisibilité et la flexibilité du projet.

Cela est dû au fait que la pensée humaine est sujette à la généralisation. Nous recherchons des modèles, nous apportons une logique répétitive dans des entités individuelles, créons des modèles. Ce comportement a probablement des racines évolutives: la réduction de la quantité d’informations permet d’économiser des ressources cognitives.

LLM agit différemment: ils ne ressentent pas de charges du volume de données et ne visent pas d’économies. Au contraire, il leur est plus facile de travailler avec des informations fragmentées en double que de construire et de maintenir des abstractions complexes. C’est pourquoi il leur est plus facile de faire face au code sans encapsulation, avec des structures répétitives et une gravité architecturale minimale.

Conclusion

Les modèles de grandes langues sont un outil utile en développement, en particulier dans les premiers stades ou lors de la création d’un code auxiliaire. Mais il est important de s’adapter à l’approche: pour simplifier l’architecture, limiter l’abstraction, éviter les dépendances complexes et ne pas compter sur eux lors de la configuration du SDK.

Les principes du solide, du sec et de la propreté sont toujours pertinents, mais ils donnent le meilleur effet entre les mains d’une personne. Lorsque vous travaillez avec LLM, il est raisonnable d’utiliser un style simplifié et pratique qui vous permet d’obtenir un code fiable et compréhensible qui est facile à finaliser manuellement. Et où LLM oublie – la duplication du code l’aide à se souvenir.

Portage du Surreal Engine C++ vers WebAssembly

Dans cet article, je décrirai comment j’ai porté le moteur de jeu Surreal Engine vers WebAssembly.

Moteur surréaliste &#8211 ; un moteur de jeu qui implémente la plupart des fonctionnalités de l’Unreal Engine 1, des jeux célèbres sur ce moteur – Unreal Tournament 99, Unreal, Deus Ex, Undying. Il fait référence aux moteurs classiques qui fonctionnaient principalement dans un environnement d’exécution monothread.

Au départ, j’ai eu l’idée de me lancer dans un projet que je ne pouvais pas réaliser dans un délai raisonnable, montrant ainsi à mes abonnés Twitch qu’il y a des projets que même moi je ne peux pas réaliser. Lors de mon premier stream, j’ai soudainement réalisé que la tâche de porter Surreal Engine C++ vers WebAssembly à l’aide d’Emscripten était réalisable.

Surreal Engine Emscripten Demo

Après un mois, je peux faire une démonstration de mon ensemble fourche et moteur sur WebAssembly :
https://demensdeum.com/demos/SurrealEngine/

Le contrôle, comme dans l’original, s’effectue à l’aide des flèches du clavier. Ensuite, je prévois de l’adapter au contrôle mobile (tachi), en ajoutant un éclairage correct et d’autres fonctionnalités graphiques du rendu Unreal Tournament 99.

Par où commencer ?

La première chose que je veux dire est que n’importe quel projet peut être porté de C++ vers WebAssembly en utilisant Emscripten, la seule question est de savoir dans quelle mesure la fonctionnalité sera complète. Choisissez un projet dont les ports de bibliothèque sont déjà disponibles pour Emscripten ; dans le cas de Surreal Engine, vous avez beaucoup de chance, car le moteur utilise les bibliothèques SDL 2, OpenAL – ils sont tous deux portés sur Emscripten. Cependant, Vulkan est utilisé comme API graphique, qui n’est actuellement pas disponible pour HTML5, des travaux sont en cours pour implémenter WebGPU, mais il est également au stade de projet, et on ne sait pas non plus à quel point le port ultérieur de Vulkan vers WebGPU sera simple. , une fois qu’il est entièrement standardisé. Par conséquent, j’ai dû écrire mon propre rendu de base OpenGL-ES/WebGL pour Surreal Engine.

Construire le projet

Construire un système dans Surreal Engine – CMake, qui simplifie également le portage, car Emscripten fournit à ses constructeurs natifs – emcmake, emmake.
Le portage de Surreal Engine était basé sur le code de mon dernier jeu en WebGL/OpenGL ES et C++ appelé Death-Mask, de ce fait le développement était beaucoup plus simple, j’avais tous les indicateurs de build nécessaires avec moi et des exemples de code.

L’un des points les plus importants de CMakeLists.txt concerne les indicateurs de build pour Emscripten. Vous trouverez ci-dessous un exemple tiré du fichier de projet :


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

Le script de build lui-même :


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

Ensuite, nous préparerons l’index .html , qui inclut le préchargeur du système de fichiers du projet. Pour télécharger sur le Web, j’ai utilisé Unreal Tournament Demo version 338. Comme vous pouvez le voir sur le fichier CMake, le dossier du jeu décompressé a été ajouté au répertoire de construction et lié en tant que fichier de préchargement pour Emscripten.

Modifications du code principal

Ensuite, il a fallu changer la boucle de jeu du jeu, vous ne pouvez pas exécuter une boucle sans fin, cela conduit au gel du navigateur, vous devez plutôt utiliser emscripten_set_main_loop, j’ai écrit à propos de cette fonctionnalité dans ma note de 2017 « < a href="https://demensdeum.com /blog/ru/2017/03/29/porting-sdl-c-game-to-html5-emscripten/" rel="noopener" target="_blank">Porter le jeu SDL C++ vers HTML5 (Emscripten)”
Nous modifions le code pour quitter la boucle while en if, puis nous affichons la classe principale du moteur de jeu, qui contient la boucle de jeu, dans la portée globale, et écrivons une fonction globale qui appellera l’étape de boucle de jeu à partir de l’objet global :


#include <emscripten.h>

Engine *EMSCRIPTEN_GLOBAL_GAME_ENGINE = nullptr;

void emscripten_game_loop_step() {

	EMSCRIPTEN_GLOBAL_GAME_ENGINE->Run();

}

#endif

Après cela, vous devez vous assurer qu’il n’y a pas de threads d’arrière-plan dans l’application ; s’il y en a, préparez-vous à les réécrire pour une exécution monothread, ou utilisez la bibliothèque phtread dans Emscripten.
Le fil d’arrière-plan dans Surreal Engine est utilisé pour lire de la musique, les données proviennent du fil du moteur principal concernant la piste en cours, la nécessité de jouer de la musique ou son absence, puis le fil d’arrière-plan reçoit un nouvel état via un mutex et commence à jouer de la nouvelle musique. , ou le met en pause. Le flux d’arrière-plan est également utilisé pour mettre la musique en mémoire tampon pendant la lecture.
Mes tentatives de création de Surreal Engine pour Emscripten avec pthread ont échoué, car les ports SDL2 et OpenAL ont été construits sans le support de pthread, et je ne voulais pas les reconstruire pour le plaisir de la musique. Par conséquent, j’ai transféré la fonctionnalité du flux de musique de fond vers une exécution monothread à l’aide d’une boucle. En supprimant les appels pthread du code C++, j’ai déplacé la mise en mémoire tampon et la lecture de musique vers le thread principal, afin qu’il n’y ait pas de retard, j’ai augmenté la mémoire tampon de quelques secondes.

Ensuite, je décrirai des implémentations spécifiques des graphiques et du son.

Vulkan n’est pas pris en charge !

Oui, Vulkan n’est pas pris en charge en HTML5, bien que toutes les brochures marketing présentent le support multiplateforme et large plate-forme comme le principal avantage de Vulkan. Pour cette raison, j’ai dû écrire mon propre moteur de rendu graphique de base pour un type OpenGL simplifié – – ES, il est utilisé sur les appareils mobiles, parfois il ne contient pas les fonctionnalités à la mode de l’OpenGL moderne, mais il se porte très bien sur WebGL, ce qui est exactement ce qu’Emscripten implémente. L’écriture du rendu de tuiles de base, du rendu bsp, pour l’affichage GUI le plus simple et du rendu des modèles + cartes a été achevée en deux semaines. C’était peut-être la partie la plus difficile du projet. Il reste encore beaucoup de travail à faire pour implémenter toutes les fonctionnalités du rendu Surreal Engine, donc toute aide des lecteurs est la bienvenue sous forme de code et de demandes d’extraction.

OpenAL pris en charge !

La grande chance est que Surreal Engine utilise OpenAL pour la sortie audio. Après avoir écrit un simple hello world dans OpenAL et l’avoir assemblé dans WebAssembly à l’aide d’Emscripten, il m’est devenu clair à quel point tout était simple et j’ai décidé de porter le son.
Après plusieurs heures de débogage, il est devenu évident que l’implémentation OpenAL d’Emscripten avait plusieurs bugs, par exemple, lors de l’initialisation de la lecture du nombre de canaux mono, la méthode a renvoyé un nombre infini, et après avoir essayé d’initialiser un vecteur de taille infinie, C++ plante avec l’exception vector::length_error.
Nous avons réussi à contourner ce problème en codant en dur le nombre de canaux mono à 2 048 :


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



#if __EMSCRIPTEN__

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

#endif



Y a-t-il un réseau ?

Surreal Engine ne prend actuellement pas en charge le jeu en ligne, jouer avec des robots est pris en charge, mais nous avons besoin de quelqu’un pour écrire l’IA pour ces robots. Théoriquement, vous pouvez implémenter un jeu en réseau sur WebAssembly/Emscripten à l’aide de Websockets.

Conclusion

En conclusion, je voudrais dire que le portage de Surreal Engine s’est avéré assez fluide grâce à l’utilisation de bibliothèques pour lesquelles il existe des ports Emscripten, ainsi que mon expérience passée dans l’implémentation d’un jeu en C++ pour WebAssembly. sur Emscripten. Vous trouverez ci-dessous des liens vers des sources de connaissances et des référentiels sur le sujet.
M-M-M-MONSTER KILL !

De plus, si vous souhaitez aider le projet, de préférence avec le code de rendu WebGL/OpenGL ES, alors écrivez-moi dans Telegram :
https://t.me/demenscave

Liens

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

Dépôt CRUD

Dans cette note, je décrirai les principes de base du modèle CRUD classique bien connu, implémenté dans le langage Swift. Swift est un langage ouvert et multiplateforme disponible pour Windows, Linux, macOS, iOS et Android.

Il existe de nombreuses solutions pour extraire le stockage des données et la logique des applications. L’une de ces solutions est l’approche CRUD, qui est l’acronyme de C– Créer, R-Read, U – Mise à jour, D– Supprimer.
Généralement, ce principe est mis en œuvre via la mise en œuvre d’une interface vers la base de données, dans laquelle les éléments sont manipulés à l’aide d’un identifiant unique, tel que id. Une interface est créée pour chaque lettre CRUD – Créer (objet, identifiant), Lire (identifiant), Mettre à jour (objet, identifiant), Supprimer (objet, identifiant).
Si un objet contient un identifiant en lui-même, alors l’argument id peut être omis des méthodes (Create, Update, Delete), puisque l’objet entier y est transmis avec son champ – identifiant. Mais pour – La lecture nécessite un identifiant car nous voulons obtenir un objet de la base de données par identifiant.

Tous les noms sont fictifs

Imaginons qu’une hypothétique application AssistantAI ait été créée à l’aide du SDK de base de données EtherRelm gratuit, l’intégration était simple, l’API était très pratique et, par conséquent, l’application a été lancée sur les marchés.
Du coup, le développeur du SDK EtherRelm décide de le rendre payant, fixant le prix à 100 $ par an et par utilisateur de l’application.
Quoi? Oui! Que doivent faire les développeurs d’AssistantAI maintenant, car ils comptent déjà 1 million d’utilisateurs actifs ! Payer 100 millions de dollars ?
Au lieu de cela, il est décidé d’évaluer le transfert de l’application vers la base de données RootData native de la plateforme ; selon les programmeurs, un tel transfert prendra environ six mois, cela ne prend pas en compte l’implémentation de nouvelles fonctionnalités dans l’application. Après réflexion, il a été décidé de retirer l’application des marchés, de la réécrire sur un autre framework multiplateforme gratuit avec une base de données BueMS intégrée, cela résoudra le problème avec la base de données payante + simplifiera le développement sur d’autres plateformes.
Un an plus tard, l’application a été réécrite dans BueMS, mais soudain, le développeur du framework décide de la rendre payante. Il s’avère que l’équipe est tombée deux fois dans le même piège ; qu’elle parvienne à s’en sortir une deuxième fois est une toute autre histoire.

L’abstraction à la rescousse

Ces problèmes auraient pu être évités si les développeurs avaient utilisé une abstraction des interfaces au sein de l’application. Aux trois piliers de la POO – polymorphisme, encapsulation, héritage, il n’y a pas si longtemps, ils ont ajouté un autre – abstraction.
L’abstraction des données vous permet de décrire des idées et des modèles en termes généraux, avec un minimum de détails, tout en étant suffisamment précis pour mettre en œuvre des implémentations spécifiques utilisées pour résoudre des problèmes commerciaux.
Comment pouvons-nous abstraire le fonctionnement de la base de données afin que la logique de l’application n’en dépende pas ? Nous utilisons l’approche CRUD !

Un diagramme UML CRUD simplifié ressemble à ceci :

Exemple avec une base de données EtherRelm fictive :

Exemple avec une vraie base de données SQLite :

Comme vous l’avez déjà remarqué, lorsque vous changez de base de données, seule celle-ci change ; l’interface CRUD avec laquelle l’application interagit reste inchangée. CRUD est une implémentation du modèle GoF – Adaptateur, parce que en l’utilisant, nous adaptons les interfaces d’application à n’importe quelle base de données et combinons des interfaces incompatibles.
Les mots sont vides, montre-moi le code
Pour implémenter des abstractions dans les langages de programmation, des interfaces/protocoles/classes abstraites sont utilisées. Ce sont tous des phénomènes du même ordre, cependant, lors d’entretiens, on peut vous demander de nommer la différence entre eux, je pense personnellement que cela n’a pas beaucoup de sens car le seul but d’utilisation est de mettre en œuvre l’abstraction des données, sinon il s’agit de tester la mémoire de la personne interrogée.
CRUD est souvent implémenté dans le cadre du modèle Repository, cependant, le référentiel peut ou non implémenter l’interface CRUD, tout dépend de l’ingéniosité du développeur.

Considérons un code Swift assez typique pour le référentiel de structure Book, travaillant directement avec la base de données UserDefaults :


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

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

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

Le code ci-dessus semble simple, mais comptons le nombre de violations du principe DRY (Do not Repeat Yourself) et la cohérence du code :
Connectivité à la base de données UserDefaults
Connectivité avec les encodeurs et décodeurs JSON – JSONEncoder, JSONDecoder
Connecté à la structure Book, mais nous avons besoin d’un référentiel abstrait afin de ne pas créer de classe référentiel pour chaque structure que nous allons stocker dans la base de données (violation DRY)

Je vois assez souvent ce type de code de référentiel CRUD, il peut être utilisé, mais un couplage élevé et une duplication de code conduisent au fait qu’avec le temps, sa prise en charge deviendra très compliquée. Cela sera particulièrement visible lorsque vous tenterez de passer à une autre base de données ou lors de la modification de la logique interne de travail avec la base de données dans tous les référentiels créés dans l’application.
Au lieu de dupliquer le code, maintenez un couplage élevé : Écrivons un protocole pour le référentiel CRUD, faisant ainsi abstraction de l’interface de base de données et de la logique métier de l’application, respectant DRY, implémentant un faible couplage :

    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
}

Le protocole CRUDRepository décrit les interfaces et les types de données associés pour une mise en œuvre ultérieure d’un référentiel CRUD spécifique.

Ensuite, nous écrirons une implémentation spécifique pour la base de données UserDefaults :

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

Le code semble long, mais contient une implémentation concrète complète d’un référentiel CRUD contenant un couplage lâche, détails ci-dessous.
des typealias ont été ajoutés pour l’auto-documentation du code.
Couplage faible et couplage fort
Le détachement d’une structure spécifique (struct) est implémenté à l’aide du T générique, qui à son tour doit implémenter les protocoles Codable. Codable vous permet de convertir des structures à l’aide de classes qui implémentent les protocoles TopLevelEncoder et TopLevelDecoder, par exemple JSONEncoder et JSONDecoder, lors de l’utilisation de types de base (Int, String, Float, etc.), il n’est pas nécessaire d’écrire du code supplémentaire pour convertir les structures.

Le découplage d’un encodeur et d’un décodeur spécifiques s’effectue à l’aide de l’abstraction dans le protocole DataTransformer :

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

En utilisant l’implémentation d’un transformateur de données, nous avons implémenté une abstraction des interfaces de l’encodeur et du décodeur, en implémentant un couplage lâche pour garantir le travail avec différents types de formats de données.

Ce qui suit est le code d’un DataTransformer spécifique, à savoir pour 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
	}
}

Était-ce possible ?

Qu’est-ce qui a changé ? Il suffit désormais d’initialiser un référentiel spécifique pour fonctionner avec n’importe quelle structure implémentant le protocole Codable, éliminant ainsi le besoin de dupliquer le code et de mettre en œuvre un couplage lâche de l’application.

Un exemple de client CRUD avec un référentiel spécifique, UserDefaults est la base de données, le format de données JSON, la structure client, également un exemple d’écriture et de lecture d’un tableau :


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

Lors de la première vérification CRUD, une gestion des exceptions a été implémentée, dans laquelle la lecture de l’élément distant ne sera plus disponible.

Changer de base de données

Je vais maintenant vous montrer comment transférer votre code actuel vers une autre base de données. Par exemple, je prendrai le code du référentiel SQLite généré par ChatGPT :


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

Ou le code CRUD du référentiel du système de fichiers, qui a également été généré par ChatGPT :


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

Remplacer le dépôt dans le code client :


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

L’initialisation de UserDefaultsRepository a été remplacée par FileSystemRepository, avec les arguments appropriés.
Après avoir exécuté la deuxième version du code client, vous trouverez un répertoire « Clients Database » dans le dossier documents, qui contiendra un fichier d’un tableau sérialisé en JSON avec une structure Client.

Changement de format de stockage des données

Demandons maintenant à ChatGPT de générer un encodeur et un décodeur pour XML :

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

Grâce aux types intégrés dans Swift, la tâche d’un réseau de neurones devient élémentaire.

Remplacez JSON par XML dans le code client :


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

Le code client a été remplacé par une seule expression JSONDataTransformer -> XMLDataTransformer

Total

Les référentiels CRUD sont l’un des modèles de conception qui peuvent être utilisés pour implémenter un couplage lâche des composants de l’architecture d’application. Une autre des solutions – en utilisant ORM (Object-Relational Mapping), en bref, ORM utilise une approche dans laquelle les structures sont entièrement mappées à la base de données, puis les modifications avec les modèles doivent être affichées (mappées (!)) sur la base de données.
Mais c’est une toute autre histoire.

Une implémentation complète des référentiels CRUD pour Swift est disponible sur :
https://gitlab.com/demensdeum/crud-example

À propos, Swift est pris en charge en dehors de macOS depuis longtemps ; le code de l’article a été entièrement écrit et testé sur Arch Linux.

Sources

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

jj erreur d’entrée/sortie

Que devez-vous faire si vous recevez une erreur d’entrée/sortie lors de la copie d’un disque normal à l’aide de dd sous Linux ?

Situevina est très triste, mais résoluble. Il s’agit très probablement d’un disque défaillant contenant des blocs défectueux qui ne peuvent plus être utilisés, écrits ou lus.

Assurez-vous de vérifier un tel disque à l’aide de S.M.A.R.T., il est fort probable qu’il vous montrera des erreurs de disque. Ce fut le cas dans mon cas ; le nombre de blocs défectueux était si énorme que j’ai dû dire au revoir à l’ancien disque dur et le remplacer par un nouveau SSD.

Le problème était que ce disque contenait un système entièrement fonctionnel avec un logiciel sous licence nécessaire au travail. J’ai essayé d’utiliser partimage pour copier rapidement des données, mais tout à coup j’ai découvert que l’utilitaire ne copie qu’un tiers du disque, puis cela se termine soit par une erreur de segmentation, soit par une autre blague amusante de Sishny/Sipplusplus.

Ensuite, j’ai essayé de copier les données en utilisant dd, et il s’est avéré que dd arrive à peu près au même endroit que partimage, puis une erreur d’entrée/sortie se produit. En même temps, toutes sortes de drapeaux amusants comme conv=noerr, skip ou autre n’aidaient pas du tout.

Mais les données ont été copiées sur un autre disque sans problème à l’aide d’un utilitaire GNU appelé ddrescue.

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

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

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

ChatGPT

Bonjour à tous ! Dans cet article, je veux parler de ChatGPT – une modélisation de langage puissante d’OpenAI qui peut aider à résoudre une variété de problèmes de traitement de texte. Je vais vous montrer comment fonctionne cet outil et comment il peut être utilisé dans des situations pratiques. Commençons !

À l’heure actuelle, ChatGPT est l’un des meilleurs modèles linguistiques au monde basés sur les réseaux de neurones. Il a été créé dans le but d’aider les développeurs à créer des systèmes intelligents capables de générer un langage naturel et de communiquer avec les personnes qui l’utilisent.

L’un des principaux avantages de ChatGPT est sa capacité à modéliser le texte de manière contextuelle. Cela signifie que le modèle prend en compte le dialogue précédent et l’utilise pour comprendre plus précisément la situation et générer une réponse plus naturelle.

Vous pouvez utiliser ChatGPT pour diverses tâches, telles que l’automatisation du support client, la création de chatbots, la génération de texte et bien plus encore.

Les réseaux neuronaux derrière ChatGPT ont été entraînés sur d’énormes quantités de texte pour garantir des prédictions très précises. Cela permet au modèle de générer un texte naturel pouvant prendre en charge le dialogue et répondre aux questions.

Avec ChatGPT, vous pouvez créer vos propres chatbots et autres systèmes intelligents capables d’interagir avec les gens en langage naturel. Cela peut être particulièrement utile dans des secteurs tels que le tourisme, la vente au détail et le service client.

En conclusion, ChatGPT – c’est un outil puissant pour résoudre divers problèmes de modélisation du langage. Sa capacité de modélisation contextuelle le rend particulièrement utile pour créer des chatbots et des systèmes intelligents.

En fait, ChatGPT a entièrement écrit tout ce qui précède. Quoi? Oui? Je suis moi-même choqué !

Vous pouvez essayer la grille elle-même ici :
https://chat.openai.com/chat

Activer le rétroéclairage du clavier USB sur macOS

J’ai récemment acheté un clavier USB Getorix GK-45X très bon marché avec rétroéclairage RVB. Après l’avoir connecté à un MacBook Pro équipé d’un processeur M1, il est devenu évident que le rétroéclairage RVB ne fonctionnait pas. Même en appuyant sur la combinaison magique Fn + Scroll Lock, seul le niveau de rétroéclairage de l’écran du MacBook a changé.
Il existe plusieurs solutions à ce problème, à savoir OpenRGB (ne fonctionne pas), HID LED Test (ne fonctionne pas). Seul l’utilitaire kvmswitch a fonctionné :
https://github.com/stoutput/OSX-KVM

Vous devez le télécharger depuis GitHub et l’autoriser à s’exécuter à partir du terminal dans le panneau de sécurité des paramètres système.
Si je comprends bien de la description, après avoir lancé l’utilitaire, il envoie une pression Fn + Scroll Lock, allumant/éteignant ainsi le rétroéclairage du clavier.

Tri des arbres

Tri par arbre : tri à l’aide d’un arbre de recherche binaire. Complexité temporelle – O(n²). Dans un tel arbre, chaque nœud à gauche a des nombres inférieurs au nœud, à droite il y en a plus que le nœud, en venant de la racine et en imprimant les valeurs de gauche à droite, on obtient une liste triée de nombres . Surprenant, non ?

Considérez l’arbre de recherche binaire :

Derrick Coetzee (domaine public)

Essayez de lire manuellement les nombres en partant de l’avant-dernier nœud gauche du coin inférieur gauche, pour chaque nœud à gauche – un nœud – à droite.

Cela ressemblera à ceci :

  1. Avant-dernier nœud en bas à gauche – 3.
  2. Il a une branche gauche – 1.
  3. Prenez ce numéro (1)
  4. Ensuite, nous prenons le sommet 3 (1, 3)
  5. À droite se trouve la branche 6, mais elle contient des branches. Par conséquent, nous le lisons de la même manière.
  6. Branche gauche du nœud 6 numéro 4 (1, 3, 4)
  7. Le nœud lui-même est 6 (1, 3, 4, 6)
  8. Droite 7 (1, 3, 4, 6, 7)
  9. Montez jusqu’au nœud racine – 8 (1,3, 4,6, 7, 8)
  10. Nous imprimons tout à droite par analogie
  11. Nous obtenons la liste finale – 1, 3, 4, 6, 7, 8, 10, 13, 14

Pour implémenter l’algorithme dans le code, vous aurez besoin de deux fonctions :

  1. Assembler un arbre de recherche binaire
  2. Imprimer l’arbre de recherche binaire dans le bon ordre

L’arbre binaire de recherche est assemblé de la même manière qu’il est lu, un numéro est attaché à chaque nœud à gauche ou à droite, selon qu’il est inférieur ou supérieur.

Exemple en 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

Tri par seau

Tri par seaux : tri par seaux. L’algorithme est similaire au tri par comptage, à la différence que les nombres sont collectés dans des plages de « seaux », puis les seaux sont triés à l’aide de tout autre algorithme de tri suffisamment productif, et l’étape finale consiste à déplier les « seaux » par un, ce qui donne une liste triée

La complexité temporelle de l’algorithme est O(nk). L’algorithme fonctionne en temps linéaire pour des données obéissant à une loi de distribution uniforme. Pour faire simple, les éléments doivent être dans une certaine plage, sans « pointes », par exemple des nombres de 0,0 à 1,0. Si parmi ces nombres il y en a 4 ou 999, alors selon les lois de la cour, une telle rangée n’est plus considérée comme « paire ».

Exemple d’implémentation dans 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

Tri par base

Tri Radix – tri par base. L’algorithme est similaire au tri par comptage dans la mesure où il n’y a pas de comparaison des éléments ; à la place, les éléments sont regroupés *caractère par caractère* dans des *buckets* (buckets), le bucket est sélectionné par l’index du numéro-caractère actuel. Complexité temporelle – O(nd).

Cela fonctionne comme ceci :

  • L’entrée sera les nombres 6, 12, 44, 9
  • Nous allons créer 10 groupes de listes (0 à 9), dans lesquels nous ajouterons/trierons des nombres petit à petit.

Suivant :

  1. Démarrer une boucle avec le compteur i jusqu’au nombre maximum de caractères du nombre
  2. Par l’index i de droite à gauche, nous obtenons un symbole pour chaque nombre ; s’il n’y a pas de symbole, alors nous supposons qu’il est nul
  3. Convertir le symbole en nombre
  4. Sélectionnez un bucket par numéro d’index et insérez-y le numéro entier
  5. Après avoir terminé la recherche parmi les nombres, reconvertissez tous les compartiments en une liste de nombres
  6. Obtenir des chiffres triés par rang
  7. Répétez jusqu’à ce que tous les chiffres aient disparu

Exemple de tri Radix dans 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}")

    }

}

L’algorithme dispose également d’une version pour une exécution parallèle, par exemple sur un GPU ; Il y a aussi une petite option de tri, qui doit êtretrès intéressante et vraiment époustouflante !

Liens

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

Sources

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

Tri en tas

Heapsort – tri pyramidal. Complexité temporelle de l’algorithme – O(n log n), vite, non ? J’appellerais ce tri le tri des cailloux qui tombent. Il me semble que la façon la plus simple de l’expliquer est visuellement.

L’entrée est une liste de nombres, par exemple :
5, 0, 7, 2, 3, 9, 4

De gauche à droite, une structure de données est créée : un arbre binaire, ou comme je l’appelle : un arbre binaire. pyramide. Les éléments pyramidaux peuvent avoir un maximum de deux éléments enfants, mais un seul élément supérieur.

Créons un arbre binaire :
⠀⠀5
⠀0⠀7
2 3 9 4

Si vous regardez la pyramide pendant longtemps, vous pouvez voir que ce ne sont que des nombres provenant d’un tableau, se succédant les uns après les autres, le nombre d’éléments dans chaque étage est multiplié par deux.

Ensuite, le plaisir commence : trions la pyramide de bas en haut en utilisant la méthode des cailloux tombants (tassifier). Le tri pourrait être lancé depuis le dernier étage (2 3 9 4), mais cela ne sert à rien car il n’y a pas d’étage en dessous où vous pourriez tomber.

Par conséquent, nous commençons à supprimer les éléments de l’avant-dernier étage (0 7)
⠀⠀5
⠀0⠀7
2 3 9 4

Le premier élément à tomber est sélectionné à droite, dans notre cas c’est 7, puis nous regardons ce qu’il y a en dessous, et en dessous se trouvent 9 et 4, neuf est supérieur à quatre, et neuf est également supérieur à Sept! Nous déposons 7 sur 9 et soulevons 9 à la place 7.
⠀⠀5
⠀0⠀9
2 3 7 4

Ensuite, on comprend que sept n’a nulle part où descendre plus bas, on passe au chiffre 0, qui se situe à l’avant-dernier étage à gauche :
⠀⠀5
0⠀9
2 3 7 4

Voyons ce qu’il y a en dessous – 2 et 3, deux est inférieur à trois, trois est supérieur à zéro, donc on échange zéro et trois :
⠀⠀5
⠀3⠀9
2 0 7 4

Lorsque vous atteignez le bout de l’étage, allez à l’étage supérieur et déposez tout là-bas si vous le pouvez.
Le résultat est une structure de données – un tas, à savoir max tas, car en haut se trouve le plus grand élément :
⠀⠀9
⠀3⠀7
2 0 5 4

Si vous le renvoyez à une représentation matricielle, vous obtenez une liste :
[9, 3, 7, 2, 0, 5, 4]

De là, nous pouvons conclure qu’en échangeant le premier et le dernier élément, nous obtenons le premier nombre dans la position triée finale, à savoir que 9 doit être à la fin de la liste triée, échangez les places :
[4, 3, 7, 2, 0, 5, 9]

Regardons un arbre binaire :
⠀⠀4
⠀3⠀7
2 0 5 9

Le résultat est une situation dans laquelle la partie inférieure de l’arbre est triée, il suffit d’en déposer 4 à la bonne position, de répéter l’algorithme, mais de ne pas prendre en compte les nombres déjà triés, à savoir 9 :
⠀⠀4
⠀3⠀7
2 0 5 9

⠀⠀7
⠀3⠀4
2 0 5 9

⠀⠀7
⠀3⠀5
2 0 4 9

Il s’est avéré que nous, après avoir perdu 4, avons soulevé le deuxième plus grand nombre après 9 – 7. Échangez le dernier numéro non trié (4) et le plus grand nombre (7)
⠀⠀4
⠀3⠀5
2 0 7 9

Il s’avère que nous avons maintenant deux nombres dans la bonne position finale :
4, 3, 5, 2, 0, 7, 9

Ensuite, nous répétons l’algorithme de tri, en ignorant ceux déjà triés, à la fin nous obtenons un tas :
⠀⠀0
⠀2⠀3
4 5 7 9

Ou sous forme de liste :
0, 2, 3, 4, 5, 7, 9

Mise en œuvre

L’algorithme est généralement divisé en trois fonctions :

  1. Créer un tas
  2. Algorithme de tamisage (heapify)
  3. Remplacement du dernier élément non trié et du premier

Le tas est créé en parcourant l’avant-dernière ligne de l’arbre binaire à l’aide de la fonction heapify, de droite à gauche, jusqu’à la fin du tableau. Ensuite dans le cycle, le premier remplacement des nombres est effectué, après quoi le premier élément tombe/reste en place, à la suite de quoi le plus grand élément tombe à la première place, le cycle se répète avec une diminution du nombre de participants d’un, car après chaque passage, les nombres triés restent à la fin de la liste.

Exemple de tri par tas dans 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



Cet algorithme n’est pas facile à comprendre sans visualisation, donc la première chose que je recommande est d’écrire une fonction qui imprimera la vue actuelle de l’arbre binaire.

Liens

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

Sources

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 (structure des données)

https://ru.wikipedia.org/wiki/Heap (structure des données)

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/ représentation-tableau-de-tas-binaire/

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

Tri rapide

Quicksort est un algorithme de tri diviser pour régner. De manière récursive, pièce par pièce, nous analysons le tableau de nombres, en plaçant les nombres dans un ordre de plus en plus grand à partir de l’élément de référence sélectionné, et insérons l’élément de référence lui-même dans la coupure entre eux. Après plusieurs itérations récursives, vous vous retrouverez avec une liste triée. Complexité temporelle O(n2).

Schéma :

  1. Nous commençons par obtenir une liste d’éléments de l’extérieur, les limites de tri. Dans un premier temps, les limites de tri s’étendront du début à la fin.
  2. Vérifiez que les limites de début et de fin ne se croisent pas ; si cela se produit, il est temps de terminer
  3. Sélectionnez un élément dans la liste et appelez-le pivot
  4. Déplacez-le vers la droite jusqu’à la fin du dernier index afin qu’il ne gêne pas
  5. Créez un compteur de *nombres plus petits* toujours égaux à zéro
  6. Parcourez la liste de gauche à droite, jusqu’au dernier index inclus où se trouve l’élément de référence
  7. Nous comparons chaque élément avec celui de référence
  8. S’il est inférieur à celui de référence, alors nous l’échangeons en fonction de l’index du compteur des nombres plus petits. Incrémentez le compteur des nombres plus petits.
  9. Lorsque la boucle atteint l’élément de support, nous nous arrêtons et échangeons l’élément de support avec l’élément en fonction du compteur de nombres plus petits.
  10. Nous exécutons l’algorithme séparément pour la plus petite partie gauche de la liste et séparément pour la plus grande partie droite de la liste.
  11. Par conséquent, toutes les itérations récursives commenceront à s’arrêter en raison de l’enregistrement au point 2
  12. Obtenir une liste triée

Quicksort a été inventé par le scientifique Charles Anthony Richard Hoare de l’Université d’État de Moscou. Ayant appris le russe, il a étudié la traduction informatique ainsi que la théorie des probabilités à l’école Kolmogorov. En 1960, en raison de la crise politique, il quitte l’Union soviétique.

Exemple d’implémentation dans 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);
  }
}

Si rien n’est clair, je vous suggère de regarder la vidéo de Rob Edwards de l’Université de San Diego https://www.youtube.com/watch?v=ZHVk2blR45Q il montre très simplement, étape par étape, l’essence et la mise en œuvre de l’algorithme.

Liens

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

Sources

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

Tri par insertion binaire

Le tri par insertion binaire est une variante du tri par insertion dans laquelle la position d’insertion est déterminée à l’aide d’une recherche binaire. La complexité temporelle de l’algorithme est O(n2)

L’algorithme fonctionne comme ceci :

  1. Une boucle commence de zéro jusqu’à la fin de la liste
  2. Dans la boucle, un nombre est sélectionné pour le tri, le nombre est stocké dans une variable distincte
  3. La recherche binaire recherche l’index pour insérer ce numéro par rapport aux chiffres de gauche
  4. Une fois l’index trouvé, les nombres de gauche sont décalés d’une position vers la droite, en commençant à l’index d’insertion. Au cours du processus, le numéro à trier sera effacé.
  5. Le numéro précédemment enregistré est inséré à l’index d’insertion
  6. A la fin de la boucle, toute la liste sera triée

Lors d’une recherche binaire, il est possible que le numéro ne soit pas trouvé et que l’index ne soit pas renvoyé. En raison de la particularité de la recherche binaire, le nombre le plus proche de celui recherché sera trouvé, puis pour renvoyer l’index il faudra le comparer avec celui recherché, si celui recherché est inférieur, alors celui recherché doit être à l’index à gauche, et s’il est supérieur ou égal, alors à droite.

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

Liens

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

Sources

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

Tri des coquilles

Shell Sort – une variante du tri par insertion avec peignage préliminaire d’un tableau de nombres.

Nous devons nous rappeler comment fonctionne le tri par insertion :

1. Une boucle commence de zéro jusqu’à la fin de la boucle, le tableau est donc divisé en deux parties
2. Pour la partie gauche, une deuxième boucle est lancée, comparant les éléments de droite à gauche, le plus petit élément de droite est déposé jusqu’à ce qu’un plus petit élément de gauche soit trouvé
3. A la fin des deux boucles, on obtient une liste triée

Il était une fois l’informaticien Donald Schell qui se demandait comment améliorer l’algorithme de tri par insertion. Il a également eu l’idée de parcourir d’abord le tableau en deux cycles, mais à une certaine distance, en réduisant progressivement le « peigne » jusqu’à ce qu’il se transforme en un algorithme de tri par insertion régulier. Tout est vraiment si simple, pas d’embûches, aux deux cycles ci-dessus on en ajoute un autre, dans lequel on réduit progressivement la taille du « peigne ». La seule chose que vous devez faire est de vérifier la distance lors de la comparaison afin qu’elle ne dépasse pas le tableau.

Un sujet vraiment intéressant est le choix de la séquence permettant de modifier la longueur de comparaison à chaque itération de la première boucle. C’est intéressant car les performances de l’algorithme en dépendent.

Le tableau des options connues et de la complexité temporelle peut être trouvé ici : https : //fr .wikipedia.org/wiki/Shellsort#Gap_sequences

Différentes personnes ont participé au calcul de la distance idéale ; apparemment, ce sujet les intéressait beaucoup. Ne pourraient-ils pas simplement exécuter Ruby et appeler l’algorithme sort() le plus rapide ?

En général, ces personnes étranges ont écrit des dissertations sur le thème du calcul de la distance/écart du « peigne » pour l’algorithme Shell. J’ai simplement utilisé les résultats de leurs travaux et vérifié 5 types de séquences, 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)

Dans mon implémentation, pour un ensemble aléatoire de nombres, les écarts les plus rapides sont Sedgwick et Hibbard.

monpy

Je voudrais également mentionner l’analyseur de typage statique pour Python 3 – mypy. Aide à faire face aux problèmes inhérents aux langages à typage dynamique, à savoir qu’il élimine la possibilité de coller quelque chose là où ce n’est pas nécessaire.

Comme le disent les programmeurs expérimentés, “la saisie statique n’est pas nécessaire lorsque vous avez une équipe de professionnels”, un jour nous deviendrons tous des professionnels, nous écrirons du code en pleine unité et compréhension avec les machines, mais pour l’instant vous pouvez utiliser des utilitaires similaires. et les langages typés statiquement.

Liens

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

Sources

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

Tri à double sélection

Tri par sélection double : un sous-type de tri par sélection, il semble qu’il devrait être deux fois plus rapide. L’algorithme Vanilla effectue une double boucle dans la liste des nombres, trouve le nombre minimum et échange les places avec le numéro actuel pointé par la boucle au niveau supérieur. Le tri par double sélection recherche les nombres minimum et maximum, puis remplace les deux chiffres pointés par la boucle au niveau supérieur à – deux chiffres à gauche et à droite. Toute cette orgie se termine lorsque les curseurs des nombres à remplacer se trouvent au milieu de la liste, et par conséquent, des nombres triés sont obtenus à gauche et à droite du centre visuel.
La complexité temporelle de l’algorithme est similaire à celle du tri par sélection – O(n2), mais soi-disant il y a une accélération de 30 %.

État limite

Déjà à ce stade, vous pouvez imaginer le moment d’une collision, par exemple, lorsque le numéro du curseur gauche (le nombre minimum) pointe vers le nombre maximum dans la liste, alors le nombre minimum est réorganisé, le réarrangement du nombre maximum tombe immédiatement en panne. Par conséquent, toutes les implémentations de l’algorithme contiennent la vérification de tels cas et le remplacement des index par des index corrects. Dans mon implémentation, une seule vérification suffisait :

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

Liens

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

Sources

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/

Trier par shaker à cocktail

Tri au shaker à cocktail &#8211 ; le tri shaker, une variante du tri bidirectionnel à bulles.
L’algorithme fonctionne comme suit :

  1. La direction initiale de la recherche dans la boucle est sélectionnée (généralement de gauche à droite)
  2. Ensuite dans la boucle, les nombres sont vérifiés par paires
  3. Si l’élément suivant est plus grand, alors ils sont échangés
  4. Une fois terminé, le processus de recherche recommence avec le sens inversé
  5. La recherche est répétée jusqu’à ce qu’il n’y ait plus de permutations

La complexité temporelle de l’algorithme est similaire à celle d’une bulle ; O(n2).

Exemple d’implémentation en 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