Raspberry PI 3 como roteador Wi-Fi

Existem muitos artigos na Internet sobre como fazer um roteador Wi-Fi a partir de um Raspberry Pi (RPI). Neste post descreverei brevemente meu método de criação de um roteador Wi-Fi com uma caixa única integrada. O método descrito funciona no momento e muita coisa pode mudar no futuro. Portanto, use esta nota como uma visão geral do que você enfrentará.

SSH

Para quem não sabe trabalhar com OpenWrt, recomendo instalar dietPI.
Conecte o RPI ao seu roteador atual via eth0 e, em seguida, conecte-o via SSH. Você pode descobrir o endereço IP RPI no painel DHCP do roteador. Conecte-se diretamente ao root, por exemplo assim:

raiz ssh@[ENDEREÇO_IP]

Adaptador Wi-Fi

O RPI3 integrado revelou-se francamente fraco e não suporta 5GHz. Portanto, conectei o adaptador RITMIX RWA-150 no chipset Realtek RTL8811CU via USB 2. Os drivers foram adicionados ao kernel Linux que estava na minha versão dietPi. Em seguida, usando dietpi-config, desliguei completamente o Wi-Fi integrado. Como resultado, sobrou apenas um adaptador USB wlan0.

Ponto de acesso

A senha dietPI padrão para root é dietpi. Uma vez conectado, você será recebido pelo instalador/configurador dietPI. Quando terminar, você precisará se conectar novamente devido à reinicialização do dispositivo.

Primeiro, você precisa configurar o hostapd para que os dispositivos possam ver seu ponto de acesso. Se o hostapd não estiver instalado, instale-o via apk.

Em seguida, você precisará escrever uma configuração para o hostapd. Exemplo da minha configuração:

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

O significado da configuração do hostapd pode ser encontrado no manual. Porém, o importante é configurar você mesmo – o canal (2,4 GHz ou 5 GHz), o código do país, caso contrário sem isso seus dispositivos localizados podem funcionar corretamente com o ponto de acesso, eu já fiz isso e sei, então defina seu país com cuidado.

DHCP

Em seguida, instale e configure o dnsmasq para implementar o DHCP. Isso é necessário para que os computadores conectados determinem o endereço IP e o servidor DNS.
Exemplo da minha configuração:

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

Esta é a configuração mínima que permitirá conectar-se a um ponto de acesso e obter um endereço IP. Em seguida, você precisará configurar o roteamento e o NAT. Isso é necessário para que os computadores conectados possam acessar a Internet.

Aqui a nota vai para a categoria de configuração típica de roteamento em um sistema regular compatível com Debian, sobre a qual existem muitos artigos na Internet. Então tudo depende de quais objetivos você está perseguindo, por exemplo, conectar-se a um servidor externo como uma nova interface no sistema, ou apenas fazer wlan0 <-> eth0, é aqui que terminam as especificações do RPI, depois configure-o ao seu gosto.

Gostaria também de mencionar a necessidade de configurar serviços personalizados do sistema via systemctl; pode haver necessidade de conectar serviços em uma cadeia; tudo isso está nos manuais do systemctl na rede. Se houver problemas no nível de serviço, verifique os logs no journalctl.

Conclusão

A partir das medições de velocidade, conseguimos extrair cerca de 50 Mbps do RPI3 via Wi-Fi (após conectar um adaptador de 5 GHz), o que significa uma perda de metade da velocidade em comparação com a conexão direta ao roteador. Admito que modelos RPI mais produtivos permitirão obter melhores resultados, também dispositivos OpenWrt especializados e soluções prontas podem ser melhores para suas necessidades.

Fontes

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

Geração de imagem local: modelo ComfyUI e FLUX

Hoje em dia, você não precisa depender de serviços em nuvem: você pode gerar imagens de alta qualidade inteiramente no seu próprio hardware. Neste post, descreverei como executar o modelo FLUX moderno localmente em seu computador usando o ComfyUI.

ComfyUI usa arquitetura baseada em nós. Isso permite que você:
– Controle totalmente todas as etapas da geração.
– Compartilhe facilmente “fluxos de trabalho” prontos

FLUX é um modelo grande, portanto os requisitos de hardware são superiores a SD 1.5 ou SDXL:
Placa de vídeo (GPU): Nvidia RTX com 12 GB VRAM ou superior (para um trabalho confortável). Se você tiver 8 GB ou menos, terá que usar as versões quantizadas (GGUF ou NF4).
Memória de acesso aleatório (RAM): mínimo 16 GB (de preferência 32 GB e superior).
Espaço em disco: Aproximadamente 20–50 GB para modelos e componentes.

A maneira mais fácil de iniciar o FLUX é usar um modelo pronto. Basta procurar por fluxo de texto para imagem na janela de fluxos de trabalho e instalar.

Escreva um prompt em inglês no nó `Text to Image (Flux.1 Dev)`, selecione a resolução (FLUX funciona bem com 1024×1024 e até superior) e pressione RUN.

A primeira geração pode demorar, pois os modelos serão carregados na memória da placa de vídeo.

https://github.com/comfyanonymous/ComfyUI

Executando macOS no Docker

É possível rodar macOS no Docker, apesar das objeções de quem diz que isso é impossível, e supostamente o macOS possui algum tipo de sistema de proteção que pode resistir a isso.

Algumas das formas clássicas de executar o macOS em máquinas PC têm sido historicamente:
*Hackintosh
* Virtualização, por exemplo usando VMWare

Hackintosh pressupõe a presença de hardware semelhante ou muito próximo do Mac original. A virtualização impõe certos requisitos de hardware, mas geralmente não tão rígidos como no caso do Hackintosh. Porém, no caso da virtualização, existem problemas de desempenho, pois o macOS não está otimizado para funcionar em ambiente virtual.

Recentemente, tornou-se possível executar o macOS no Docker. Isso é possível graças ao projeto Docker-OSX, que fornece imagens macOS prontas para serem executadas no Docker. É importante notar que o Docker-OSX não é um projeto oficial da Apple e não é suportado por ele. No entanto, permite executar o macOS no Docker e usá-lo para desenvolver e testar aplicativos.

Um dos primeiros projetos a rodar macOS no Docker:
https://github.com/sickcodes/Docker-OSX

No entanto, nunca consegui iniciá-lo totalmente; depois de carregar no Recovery OS, meu teclado e mouse simplesmente caíram e não consegui continuar a instalação. Ao mesmo tempo, no primeiro menu de boot, o teclado funciona. Talvez o fato seja que este projeto não tem mais suporte tão ativo e há alguns problemas específicos ao rodar no Windows 11 + WSL2 + Ubuntu.

Um dos projetos mais ativos no momento:
https://github.com/dockur/macos

Permite rodar macOS no Docker, a interface funciona através do navegador via encaminhamento VNC(?). Após a inicialização, o macOS está disponível em http://localhost:5900

Consegui rodar este projeto e instalar o macOS Big Sur (minuto 2020) no Windows 11 + WSL2 + Ubuntu, mas apenas alterando o arquivo compose, a saber:

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

VERSÃO: “11” é a versão do macOS, neste caso Big Sur
RAM_SIZE: “8G” é a quantidade de RAM alocada para macOS
CPU_CORES: “4” é o número de núcleos de CPU alocados para macOS

No momento, executar o macOS tahoe (16) também é possível, mas há uma série de problemas que os desenvolvedores do projeto estão tentando resolver corajosamente.

Esta forma original de iniciar o macOS permite que você experimente em seu hardware que não seja Mac e, depois de sofrer o suficiente, compre um Mac. No entanto, pode ser útil para testar software em sistemas mais antigos e no desenvolvimento geral.

Construindo Swift em WSL2 (Linux)

O ecossistema Swift está se desenvolvendo ativamente fora das plataformas Apple e hoje é bastante confortável escrever nele no Windows usando o Windows Subsystem for Linux (WSL2). Vale a pena considerar que para assemblies no Linux/WSL, uma versão leve do Swift está disponível – sem estruturas proprietárias da Apple (como SwiftUI, UIKit, AppKit, CoreData, CoreML, ARKit, SpriteKit e outras bibliotecas específicas do iOS/macOS), mas para utilitários de console e backend isso é mais que suficiente. Neste post, vamos percorrer passo a passo o processo de preparação do ambiente e construção do compilador Swift a partir do código-fonte dentro do WSL2 (usando Ubuntu/Debian como exemplo).

Atualizamos a lista de pacotes e o próprio sistema:

sudo apt update && sudo apt upgrade -y

Instale as dependências necessárias para a compilação:

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

Instale o compilador e vinculador (LLVM e LLD):

sudo apt install -y llvm lld

Clone o repositório Swift com todas as dependências:

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

Instale o `swiftly` e o swift pronto com 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

Vamos começar a construção (isso levará muito tempo):

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

Após a conclusão da compilação, adicione o caminho do compilador ao PATH (especifique o caminho para a pasta de compilação):

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

Verificamos se a versão instalada do Swift está funcionando:

swift --version

Crie um arquivo de teste e execute-o:

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

Você também pode compilar o binário e executá-lo:

swiftc hello.swift
./hello

Fontes

Interpretador de padrões na prática

No último artigo examinamos a teoria do padrão Interpreter, aprendemos o que é uma árvore AST e como abstrair expressões terminais e não terminais. Desta vez, vamos nos afastar da teoria e ver como esse padrão é aplicado em projetos comerciais sérios que todos usamos todos os dias!

Spoiler: Você pode estar usando o padrão Interpreter agora mesmo, apenas lendo este texto no seu navegador!

Um dos exemplos mais marcantes e, talvez, mais importantes do uso desse padrão na indústria é o JavaScript. A linguagem, que originalmente foi criada “no joelho”, hoje funciona em bilhões de dispositivos justamente graças ao conceito de interpretação.

10 dias que mudaram a Internet

A história do JavaScript está cheia de lendas. Em 1995, Brendan Eich, enquanto trabalhava na Netscape Communications, recebeu a tarefa de criar uma linguagem de script simples que pudesse ser executada diretamente em um navegador (Netscape Navigator) para tornar as páginas da web interativas. A administração queria algo com uma sintaxe semelhante ao então super popular Java, mas destinado não a engenheiros profissionais, mas a web designers.

Eich teve apenas 10 dias para escrever o primeiro protótipo da linguagem, que então se chamava Mocha (depois LiveScript, e só depois JavaScript por razões de marketing). A pressa não foi acidental: a Microsoft estava logo atrás, que ao mesmo tempo preparava ativamente sua própria linguagem de script VBScript para ser incorporada no navegador Internet Explorer. A Netscape precisava liberar urgentemente sua resposta para não perder na iminente guerra dos navegadores.

Simplesmente não havia tempo para escrever um compilador complexo em código de máquina. A solução óbvia e mais rápida para Eich foi a arquitetura do clássico Interpreter.

O primeiro intérprete (SpiderMonkey) funcionou assim:

  1. Ele lê o código-fonte do texto do script na página.
  2. O analisador léxico dividiu o texto em tokens.
  3. O analisador construiu uma Árvore de Sintaxe Abstrata (AST). Em termos do padrão Interpreter, esta árvore consistia em expressões terminais (strings, números como 42) e não terminais (chamadas de função, instruções como If, ​​While).
  4. Então a máquina virtual “atravessou” esta árvore passo a passo, executando as instruções embutidas nela em cada nó (chamando um método semelhante a Interpret()).

Contexto e Objetos

Lembra do objeto Context que tivemos que passar para o método Interpret(Context context) na implementação clássica? O intérprete precisa dele para armazenar o estado atual da memória.

No caso do JavaScript, o papel deste contexto no nível superior é desempenhado por um objeto global (por exemplo, uma janela em um navegador). Quando seu nó AST tenta, digamos, escrever texto na tela via document.write(“Hello”), o interpretador acessa seu contexto (o objeto document) e chama a API interna do navegador desejada.

É graças ao interpretador que o JavaScript é capaz de interagir tão facilmente com o DOM (Document Object Model) – todos eles são apenas objetos em um contexto que são acessados ​​por nós de árvore.

Evolução do intérprete: Compilação JIT

Historicamente, JS em navegadores permaneceu por muito tempo um intérprete “puro”. E isso tinha uma grande desvantagem – velocidade lenta. Analisar a árvore e percorrer cada nó lentamente cada vez que o script era executado tornava aplicativos da Web complexos mais lentos.

Com o advento do mecanismo V8 do Google (integrado ao Chrome) em 2008, ocorreu uma revolução. Os engenheiros perceberam que um intérprete não é suficiente para a web moderna. O mecanismo se tornou mais complexo: ele ainda constrói a árvore AST, mas agora usa compilação JIT (Just-In-Time).

Os mecanismos JS modernos (V8, SpiderMonkey) funcionam como um pipeline complexo:

  1. O interpretador base rápido e burro começa a executar seu código JS instantaneamente, sem sequer esperar que ele seja compilado (o padrão clássico ainda funciona aqui).
  2. Paralelamente, o mecanismo monitora seções “quentes” do código (loops ou funções que são chamadas milhares de vezes).
  3. Essas seções são compiladas pelo compilador JIT diretamente no código de máquina otimizado, ignorando o interpretador lento.

Foi essa combinação do início instantâneo do interpretador e do poder computacional de compilação que permitiu que o JavaScript dominasse o mundo, tornando-se a linguagem dos servidores (Node.js) e dos aplicativos móveis (React Native).

Intérprete na indústria de jogos

Apesar do domínio do C++ na computação pesada, o padrão Interpreter é um padrão da indústria no desenvolvimento de jogos para a criação de lógica de jogos. Para que? Para que os designers de jogos possam fazer jogos sem o risco de “deixar cair” o motor ou a necessidade de recompilá-lo constantemente.

Um excelente exemplo histórico é o UnrealScript – a linguagem na qual a lógica dos jogos Unreal Tournament e Gears of War foi escrita no Unreal Engine 1, 2 e 3. O texto foi compilado em um bytecode de máquina abstrato compacto, que foi então passo a passo (interpretado) pela máquina virtual do motor.

Scripts gráficos visuais (Blueprints)

Hoje, o texto foi substituído pela programação visual – o sistema Blueprints no Unreal Engine 4 e 5.

Se você já abriu um Blueprint no Unreal Engine, viu muitos nós conectados por fios. Arquitetonicamente, todo o gráfico do Blueprints é uma enorme Árvore de Sintaxe Abstrata (AST) desenhada na tela:

  1. Expressões de Terminal: Nós constantes. Por exemplo, um nó que simplesmente armazena o número 42 ou uma string. Eles retornam um valor específico quando interpretados.
  2. Expressões não terminais: Nós de computação (Adicionar) ou nós de controle de fluxo (Filial). Eles têm entradas de argumentos, que o intérprete avalia primeiro recursivamente antes de produzir o resultado como um pino de saída.

E o papel do contexto aqui é desempenhado pela memória de uma instância de um objeto de jogo específico (Ator). A Máquina Interpretadora “caminha” com segurança por esse gráfico, solicitando dados e realizando transições.

Onde mais o Interpretador é usado?

O padrão de intérprete pode ser encontrado em quase todos os sistemas complexos onde instruções dinâmicas precisam ser executadas. Aqui estão apenas alguns exemplos de software comercial:

  • Linguagens de programação interpretadas (Python, Ruby, PHP). Todo o seu tempo de execução é baseado no padrão clássico. Por exemplo, a implementação de referência do CPython primeiro analisa seu script .py em um AST, compila-o em bytecode e, em seguida, uma enorme máquina virtual (loop de computação) interpreta esse bytecode passo a passo.
  • Java Virtual Machine (JVM). Inicialmente, o código Java é compilado não em instruções de máquina, mas em bytecode. Quando você executa o aplicativo, a JVM atua como um intérprete (embora com compilação JIT agressiva, assim como na V8).
  • Bancos de dados e SQL Quando você emite uma consulta SQL (SELECT * FROM users) no PostgreSQL ou MySQL, o mecanismo de banco de dados atua como um intérprete. Ele realiza análises lexicais, constrói uma árvore de consulta AST, gera um plano de execução e, em seguida, literalmente “interpreta” esse plano iterando nas linhas das tabelas.
  • Expressões regulares (RegEx). Qualquer mecanismo de expressão regular analisa internamente um padrão de string (por exemplo, ^\d{3}-\d{2}$) em um gráfico de estado (NFA/DFA Automata), pelo qual o interpretador interno passa, combinando cada caractere de entrada com os vértices deste gráfico.
  • Unity Shader Graph / Unreal Material Editor – interpreta nós visuais em código de shader modular (GLSL/HLSL).
  • Nós de geometria do Blender – interpreta operações matemáticas e geométricas para gerar modelos 3D de forma processual em tempo real.

Total

O padrão Interpreter já ultrapassou o escopo de “escrever sua própria calculadora”. Este é o padrão mais poderoso da indústria. Desde mecanismos JavaScript que executam gigabytes de código nos bastidores dos navegadores todos os dias até designers de jogos que permitem construir lógica complexa sem conhecimento de C++, os intérpretes continuam sendo um dos conceitos de arquitetura mais importantes no desenvolvimento de TI moderno.

chamada de ollama

Se você usa Ollama e não quer escrever seu próprio wrapper de API todas as vezes,
o projeto ollama_call simplifica significativamente o trabalho.

Esta é uma pequena biblioteca Python que permite enviar uma solicitação para um LLM local com uma função
e receba imediatamente uma resposta, inclusive no formato JSON.

Instalação

pip install ollama-call

Por que é necessário

  • código mínimo para trabalhar com o modelo;
  • resposta JSON estruturada para processamento posterior;
  • conveniente para protótipos rápidos e MVPs;
  • suporta saída de streaming, se necessário.

Usar exemplo

from ollama_call import ollama_call

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

print(response)

Quando é especialmente útil

  • você escreve scripts ou serviços em cima do Ollama;
  • precisa de um formato de resposta previsível;
  • não há desejo de conectar estruturas pesadas.

Total

ollama_call é um wrapper leve e claro para trabalhar com Ollama do Python.
Uma boa escolha se simplicidade e resultados rápidos forem importantes.

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

SFAP: uma estrutura modular para aquisição e processamento de dados modernos

No contexto do desenvolvimento ativo da automação e da inteligência artificial, a tarefa de coletar efetivamente,
Limpar e transformar dados torna-se fundamental. A maioria das soluções apenas fecha
etapas separadas deste processo, exigindo integração e suporte complexos.

SFAP (Seek · Filter · Adapt · Publish) é um projeto de código aberto em Python,
que oferece uma abordagem holística e extensível ao processamento de dados em todas as fases do seu ciclo de vida:
desde a busca de fontes até a publicação do resultado final.

O que é SFAP

SFAP é uma estrutura assíncrona construída em torno de um conceito claro de pipeline de processamento de dados.
Cada estágio é logicamente separado e pode ser expandido ou substituído de forma independente.

O projeto é baseado no padrão arquitetônico Chain of Responsibility, que fornece:

  • Flexibilidade de configuração de pipeline;
  • teste simples de estágios individuais;
  • escalabilidade para cargas altas;
  • separação clara de responsabilidades entre componentes.

Principais etapas do pipeline

Busca – pesquisa de dados

Nesta fase, as fontes de dados são descobertas: páginas web, APIs, armazenamentos de arquivos
ou outros fluxos de informação. O SFAP facilita a conexão de novas fontes sem alterar
o resto do sistema.

Filtro – filtragem

A filtragem foi projetada para remover ruídos: conteúdo irrelevante, duplicatas, elementos técnicos
e dados de baixa qualidade. Isto é crítico para as etapas de processamento subsequentes.

Adaptar – adaptação e processamento

A etapa de adaptação é responsável pela transformação dos dados: normalização, estruturação,
processamento semântico e integração com modelos de IA (inclusive generativos).

Publicar – publicação

Na fase final, os dados são publicados no formato alvo: bancos de dados, APIs, arquivos, serviços externos
ou plataformas de conteúdo. O SFAP não limita a forma como o resultado é entregue.

Principais características do projeto

  • Arquitetura assíncrona baseada em asyncio
  • Modularidade e extensibilidade
  • Suporte para pipelines de processamento complexos
  • Pronto para integração com soluções AI/LLM
  • Adequado para sistemas altamente carregados

Casos de uso práticos

  • Agregação e análise de fontes de notícias
  • Preparando conjuntos de dados para aprendizado de máquina
  • Pipeline de conteúdo automatizado
  • Limpar e normalizar grandes fluxos de dados
  • Integração de dados de fontes heterogêneas

Introdução ao SFAP

Tudo que você precisa para começar é:

  1. Clone o repositório do projeto;
  2. Instalar dependências do Python;
  3. Defina suas próprias etapas de pipeline;
  4. Iniciar um processo de processamento de dados assíncrono.

O projeto é facilmente adaptado a tarefas específicas do negócio e pode crescer com o sistema,
sem se transformar em um monólito.

Conclusão

SFAP não é apenas um analisador ou coletor de dados, mas uma estrutura completa para construir
sistemas modernos de pipeline de dados. É adequado para desenvolvedores e equipes que se preocupam com
escalável, arquitetonicamente limpo e pronto para dados.
O código-fonte do projeto está disponível no GitHub:
https://github.com/demensdeum/SFAP

Por que não consigo corrigir o bug?

Você passa horas trabalhando no código, analisando hipóteses, ajustando as condições, mas o bug ainda é reproduzido. Parece familiar? Esse estado de frustração costuma ser chamado de “caça aos fantasmas”. O programa parece viver sua própria vida, ignorando suas correções.

Um dos motivos mais comuns – e mais irritantes – para essa situação é procurar um erro no lugar completamente errado do aplicativo.

A armadilha dos “falsos sintomas”

Quando vemos um erro, nossa atenção é atraída para o local onde ele “disparou”. Mas em sistemas complexos, onde ocorre um bug (travamento ou valor incorreto) é apenas o fim de uma longa cadeia de eventos. Ao tentar consertar o final, você está lutando contra os sintomas, não contra a doença.

É aqui que entra o conceito de fluxograma.

Como funciona na realidade

Claro, não é necessário desenhar (desenhar) diretamente um fluxograma no papel todas as vezes, mas é importante tê-lo em mente ou em mãos como um guia arquitetônico. Um fluxograma permite visualizar a operação de um aplicativo como uma árvore de resultados.

Sem compreender essa estrutura, o desenvolvedor muitas vezes fica tateando no escuro. Imagine a situação: você edita a lógica em uma ramificação de condição, enquanto a aplicação (devido a um determinado conjunto de parâmetros) vai para uma ramificação completamente diferente na qual você nem pensou.

Resultado: você gasta horas em uma correção de código “perfeita” em uma parte do algoritmo, o que, é claro, não faz nada para corrigir o problema em outra parte do algoritmo onde ele realmente falha.


Algoritmo para derrotar um bug

Para parar de bater em uma porta fechada, você precisa mudar sua abordagem ao diagnóstico:

  • Encontre o estado na árvore de resultados:Antes de escrever o código, você precisa determinar exatamente o caminho que o aplicativo seguiu. Em que ponto a lógica tomou o rumo errado? Que estado específico (Estado) levou ao problema?
  • A reprodução tem 80% de sucesso: Isso geralmente é feito por testadores e testes automatizados. Se o bug estiver “flutuante”, o desenvolvimento estará envolvido no processo de busca conjunta de condições.
  • Use o máximo de informações possível: registros, versão do sistema operacional, parâmetros do dispositivo, tipo de conexão (Wi-Fi/5G) e até mesmo uma operadora de telecomunicações específica são importantes para a localização.

“Fotografia” do momento do erro

Idealmente, para corrigi-lo, você precisa obter o estado completo do aplicativo no momento em que o bug foi reproduzido. Os logs de interação também são extremamente importantes: eles mostram não apenas o ponto final, mas também todo o caminho do usuário (quais ações precederam a falha). Isso ajuda a entender como recriar um estado semelhante novamente.

Dica futura: se você encontrar um caso complexo, adicione informações estendidas de registro de depuração a esta seção do código, caso a situação aconteça novamente.


O problema dos estados “indescritíveis” na era da IA

Em sistemas modernos que usam LLM (Large Language Models), o determinismo clássico (“uma entrada, uma saída”) é frequentemente violado. Você pode passar exatamente os mesmos dados de entrada, mas obter um resultado diferente.

Isso acontece devido ao não determinismo dos sistemas de produção modernos:

  • Paralelismo de GPU: as operações de ponto flutuante da GPU nem sempre são associativas. Devido à execução paralela de threads, a ordem em que os números são adicionados pode mudar ligeiramente, o que pode afetar o resultado.
  • Temperatura e aceleração da GPU: a velocidade de execução e a distribuição de carga podem depender do estado físico do hardware. Em modelos enormes, essas diferenças microscópicas se acumulam e podem levar à seleção de um token diferente na saída.
  • Lote dinâmico: Na nuvem, sua solicitação é combinada com outras. Diferentes tamanhos de lote alteram a matemática dos cálculos nos kernels.

Sob tais condições, torna-se quase impossível reproduzir “esse mesmo estado”. Somente uma abordagem estatística aos testes pode salvá-lo aqui.


Quando a lógica falha: problemas de memória

Se você estiver trabalhando com linguagens “inseguras” (C ou C++), o bug pode ocorrer devido a corrupção de memória.

Esses são os casos mais graves: um erro em um módulo pode “sobrescrever” dados em outro. Isso leva a falhas completamente inexplicáveis ​​e isoladas que não podem ser rastreadas usando a lógica normal do aplicativo.

Como se proteger no nível arquitetônico?

Para evitar esses bugs “místicos”, você deve usar abordagens modernas:

  • Padrões de programação multithread:a sincronização clara elimina condições de corrida.
  • Linguagens thread-safe: Ferramentas que garantem a segurança da memória em tempo de compilação:
    • Rust: o sistema de propriedade elimina erros de memória.
    • Simultaneidade Swift 6:fortes verificações de isolamento de dados.
    • Erlang: isolamento completo do processo por meio do modelo de ator.

Resumo

Consertar um bug não é escrever um novo código, mas entender como o antigo funciona. Lembre-se: você pode estar perdendo tempo editando uma branch que a administração nem sequer toca. Registre o estado do sistema, leve em consideração o fator de não determinismo da IA ​​e escolha ferramentas seguras.

Diagramas de bloqueio na prática sem formalina

O diagrama de blocos é uma ferramenta visual que ajuda a transformar um algoritmo complexo em uma sequência de ações compreensíveis e estruturadas. Da programação ao gerenciamento de processos de negócios, eles servem como uma linguagem universal para visualização, análise e otimização dos sistemas mais complexos.

Imagine um mapa onde, em vez de estradas, é lógica e, em vez de cidades – ações. Esta é um diagrama de blocos-uma ferramenta indispensável para navegação nos processos mais confusos.

Exemplo 1: Esquema de lançamento de jogo simplificado
Para entender o princípio do trabalho, vamos apresentar um simples esquema de lançamento de jogo.

Esse esquema mostra o script perfeito quando tudo acontece sem falhas. Mas na vida real, tudo é muito mais complicado.

Exemplo 2: Esquema expandido para iniciar o jogo com carregamento de dados
Os jogos modernos geralmente exigem conexão à Internet para baixar dados, salvamento ou configurações do usuário. Vamos adicionar essas etapas ao nosso esquema.

Esse esquema já é mais realista, mas o que acontecerá se algo der errado?

Como foi: um jogo que “quebrou” com a perda da Internet

No início do projeto, os desenvolvedores não puderam levar em consideração todos os cenários possíveis. Por exemplo, eles se concentraram na lógica principal do jogo e não pensaram no que aconteceria se o jogador tivesse uma conexão com a Internet.

Em tal situação, o diagrama de blocos de seu código ficaria assim:

Nesse caso, em vez de emitir um erro ou fechar corretamente, o jogo congelou na fase de espera por dados que ela não recebeu devido à falta de uma conexão. Isso levou à “tela preta” e congelando o aplicativo.

Como se tornou: correção nas reclamações de usuários

Após inúmeras reclamações dos usuários sobre pairando, a equipe do desenvolvedor percebeu que precisávamos corrigir o erro. Eles fizeram alterações no código adicionando uma unidade de processamento de erros que permite que o aplicativo responda corretamente à falta de conexão.

É assim que o diagrama de blocos corrigido se parece, onde ambos os cenários são levados em consideração:

Graças a essa abordagem, o jogo agora informa corretamente o usuário sobre o problema e, em alguns casos, ele pode até ir para o modo offline, permitindo que você continue o jogo. Este é um bom exemplo de por que os diagramas de blocos são tão importantes : eles fazem o desenvolvedor pensar não apenas sobre a maneira ideal de execução, mas também sobre todas as falhas possíveis, tornando o produto final muito mais estável e confiável.

comportamento incerto

Pendurado e erros são apenas um exemplos de comportamento imprevisível do programa. Na programação, existe um conceito de comportamento incerto (comportamento indefinido) – Esta é uma situação em que o padrão do idioma não descreve como o programa deve se comportar em um determinado caso.

Isso pode levar a qualquer coisa: do “lixo” aleatório na retirada ao fracasso do programa ou mesmo à séria vulnerabilidade de segurança. O comportamento indefinido geralmente ocorre ao trabalhar com memória, por exemplo, com linhas na linguagem de C.

Um exemplo do idioma c:

Imagine que o desenvolvedor copiou a linha para o buffer, mas esqueceu de adicionar ao final o símbolo zero (`\ 0`) , que marca o final da linha.

É assim que o código se parece:

#include 

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

memcpy(buffer, my_string, 5);

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

Resultado esperado: “Olá”
O resultado real é imprevisível.

Por que isso está acontecendo? A função `printf` com o especificador`%s` espera que a linha termine com um símbolo zero. Se ele não estiver, ela continuará lendo a memória fora do buffer destacado.

Aqui está o diagrama de blocos deste processo com dois resultados possíveis:

Este é um exemplo claro de por que os diagramas de blocos são tão importantes: eles fazem o desenvolvedor pensar não apenas sobre a maneira ideal de execução, mas também sobre todas as falhas possíveis, incluindo problemas de baixo nível, tornando o produto final muito mais estável e confiável.

Pixel perfeito: mito ou realidade na era da declaratividade?

No mundo do desenvolvimento de interfaces, existe um conceito comum – “Pixel perfeito no alojamento” . Isso implica a reprodução mais precisa da máquina de design ao menor pixel. Durante muito tempo, era um padrão -ouro, especialmente na era de um design clássico da web. No entanto, com a chegada da milha declarativa e o rápido crescimento da variedade de dispositivos, o princípio de “Pixel Perfect” está se tornando mais efêmero. Vamos tentar descobrir o porquê.

Imperial Wysiwyg vs. Código declarativo: Qual é a diferença?

Tradicionalmente, muitas interfaces, especialmente o desktop, eram criadas usando abordagens imperativas ou wysiwyg (o que você vê é o que recebe) dos editores. Nessas ferramentas, o designer ou desenvolvedor manipula diretamente com elementos, colocando -os em tela com precisão no pixel. É semelhante ao trabalho com um editor gráfico – você vê como o seu elemento se parece e definitivamente pode posicioná -lo. Nesse caso, a conquista de “Pixel Perfect” era um objetivo muito real.

No entanto, o desenvolvimento moderno é cada vez mais baseado em milhas declarativas . Isso significa que você não diz ao computador para “colocar este botão aqui”, mas descreva o que deseja obter. Por exemplo, em vez de indicar as coordenadas específicas do elemento, você descreve suas propriedades: “Este botão deve ser vermelho, ter recua de 16px de todos os lados e estar no centro do contêiner”. Freimvorki como React, Vue, Swiftui ou Jetpack Compose apenas use esse princípio.

Por que “Pixel Perfect” não funciona com uma milha declarativa para muitos dispositivos

Imagine que você cria um aplicativo que deve parecer igualmente bom no iPhone 15 Pro Max, Samsung Galaxy Fold, iPad Pro e uma resolução 4K. Cada um desses dispositivos possui resolução de tela diferente, densidade de pixels, partes e tamanhos físicos.

Quando você usa a abordagem declarativa, o próprio sistema decide como exibir a interface descrita em um dispositivo específico, levando em consideração todos os seus parâmetros. Você define as regras e dependências, não coordenadas duras.

* Adaptabilidade e capacidade de resposta: O principal objetivo das milhas declarativas é criar interfaces adaptativas e responsivas . Isso significa que sua interface deve se adaptar automaticamente ao tamanho e orientação da tela sem quebrar e manter a legibilidade. Se procurássemos “pixel perfeito” em cada dispositivo, teríamos que criar inúmeras opções para a mesma interface, o que nivelará completamente as vantagens da abordagem declarativa.
* densidade de pixel (DPI/PPI): Os dispositivos têm densidade de pixels diferentes. O mesmo elemento com o tamanho de 100 pixels “virtuais” em um dispositivo com alta densidade parecerá muito menor do que em um dispositivo de baixa densidade, se você não levar em consideração a escala. As estruturas declarativas são abstraídas por pixels físicos, trabalhando com unidades lógicas.
* Conteúdo dinâmico: em aplicativos modernos geralmente é dinâmico – seu volume e estrutura podem variar. Se embutirmos com força para os pixels, qualquer alteração no texto ou imagem levaria ao “colapso” do layout.
* Várias plataformas: Além da variedade de dispositivos, existem diferentes sistemas operacionais (iOS, Android, Web, Desktop). Cada plataforma possui seu próprio design, controles padrão e fontes. Uma tentativa de fazer uma interface perfeita de pixel absolutamente idêntica em todas as plataformas levaria a um tipo não natural e uma experiência de usuário ruim.

As abordagens antigas não foram embora, mas evoluíram

É importante entender que a abordagem das interfaces não é uma escolha binária entre “imperativo” e “declarativo”. Historicamente, para cada plataforma, havia suas próprias ferramentas e abordagens para a criação de interfaces.

* Arquivos de interface nativos: Para iOS, eram xib/storyboards, para arquivos de marcação Android-xml. Esses arquivos são um layout wysiwyg perfeito para pixels, que é exibido no rádio como no editor. Essa abordagem não desapareceu em nenhum lugar, continua a se desenvolver, integrando -se com quadros declarativos modernos. Por exemplo, SwiftUi na Apple e Jetpack compor no Android partiu no caminho de um código puramente declarativo, mas, ao mesmo tempo, manteve a oportunidade de usar um layout clássico.
* Soluções híbridas: Em projetos reais, é usada uma combinação de abordagens. Por exemplo, a estrutura básica do aplicativo pode ser implementada declarativamente e, para específicos, exigindo posicionamento preciso de elementos, métodos imperativos de nível inferior, podem ser usados ou componentes nativos desenvolvidos levando em consideração as especificidades da plataforma.

Do monólito à adaptabilidade: como a evolução dos dispositivos formou uma milha declarativa

O mundo das interfaces digitais passou por tremendas mudanças nas últimas décadas. De computadores estacionários com licenças fixas, chegamos à era do crescimento exponencial da variedade de dispositivos de usuário . Hoje, nossos aplicativos devem funcionar igualmente bem em:

* smartphones de todos os fatores de forma e tamanhos de tela.
* comprimidos com seus modos de orientação exclusivos e uma tela separada.
* Laptops e desktops com várias licenças de monitores.
* TVs e centros de mídia , controlados remotamente. Vale ressaltar que, mesmo para as TVs, cujas observações podem ser simples como Apple TV Remote com um mínimo de botões, ou vice -versa, sobrecarregados com muitas funções, os requisitos modernos para interfaces são tais que o código não exija adaptação específica para esses recursos de entrada. A interface deve funcionar “como se por si só”, sem uma descrição adicional do que “como” interagir com um controle remoto específico.
* relógios inteligentes e dispositivos vestíveis com telas minimalistas.
* Capacetes de realidade virtual (VR) , exigindo uma abordagem completamente nova para uma interface espacial.
* Dispositivos de realidade aumentada (AR) , aplicando informações sobre o mundo real.
* Informações de automóveis e sistemas de entretenimento .
* E até eletrodomésticos : de geladeiras com telas sensoriais e máquinas de lavar com displays interativos para fornos e sistemas inteligentes da casa inteligente.

Cada um desses dispositivos possui seus próprios recursos exclusivos: dimensões físicas, proporção de partes, densidade de pixels, métodos de entrada (tela de toque, mouse, controladores, gestos, comandos vocais) e, principalmente, as sutilezas do ambiente do usuário . Por exemplo, um shlesh de VR requer uma imersão profunda e um trabalho intuitivo e rápido do smartphone em movimento, enquanto a interface da geladeira deve ser tão simples e grande para navegação rápida.

Abordagem clássica: o ônus de apoiar interfaces individuais

Na era do domínio dos desktops e dos primeiros dispositivos móveis, o negócio usual era a criação e o suporte de arquivos de interface individuais ou mesmo um código de interface completamente separado para cada plataforma .

* O desenvolvimento em iOS geralmente exigia o uso de storyboards ou arquivos XIB no Xcode, escrevendo código no Objective-C ou Swift.
* Para Android Os arquivos de marcação XML e o código em Java ou Kotlin foram criados.
* Interfaces da Web ativadas em HTML/CSS/JavaScript.
* Para aplicativos C ++ Em várias plataformas de desktop, foram usadas suas estruturas e ferramentas específicas:
* Em Windows Estes foram MFC (Microsoft Foundation Classes), API Win32 com elementos de desenho manual ou usando arquivos de recursos para janelas de diálogo e elementos de controle.
* Cacau (Objective-C/Swift) ou A API de carbono antigo para controle direto da interface gráfica foram usados no macOS .
* Nos sistemas Linux/UNIX , bibliotecas como GTK+ ou QT foram frequentemente usadas, o que forneceu seu conjunto de widgets e mecanismos para criar interfaces, geralmente por meio de arquivos de marcação do tipo XML (por exemplo, arquivos .ui no designer QT) ou criação de software direto de elementos.

Essa abordagem garantiu o controle máximo sobre cada plataforma, permitindo que você levasse em consideração todos os seus recursos específicos e elementos nativos. No entanto, ele teve uma enorme desvantagem: duplicação de esforços e enormes custos de apoio . A menor mudança no design ou funcionalidade exigia a introdução de um direito a vários, de fato, bases de código independentes. Isso se transformou em um pesadelo real para as equipes de desenvolvedores, diminuindo a desaceleração da produção de novas funções e aumentando a probabilidade de erros.

Miles declarativos: um único idioma para a diversidade

Foi uma resposta a essa rápida complicação que as milhas declarativas apareceram como o paradigma dominante. Framws como React, Vue, Swiftui, Jetpack compõem e outros não são apenas uma nova maneira de escrever código, mas uma mudança fundamental no pensamento.

A idéia principal da abordagem declarativa : em vez de dizer o sistema “como” desenhar todos os elementos (imperativos), descrevemos “o que” queremos ver (declarativo). Definimos as propriedades e a condição da interface, e a estrutura decide como exibi -la melhor em um dispositivo específico.

Isso se tornou possível graças às seguintes vantagens importantes:

1. Abstração dos detalhes da plataforma: O Fraimvorki declarativo é especialmente projetado para esquecer os detalhes de baixo nível de cada plataforma. O desenvolvedor descreve os componentes e seus relacionamentos em um nível mais alto de abstração, usando um único código transferido.
2. Adaptação e capacidade de resposta automáticas: Freimvorki assume a responsabilidade pela escala automática, alterando o layout e a adaptação dos elementos para diferentes tamanhos de telas, densidade de pixels e métodos de entrada. Isso é conseguido através do uso de sistemas de layout flexíveis, como Flexbox ou grade, e conceitos semelhantes a “pixels lógicos” ou “dp”.
3. Consistência da experiência do usuário: Apesar das diferenças externas, a abordagem declarativa permite manter uma única lógica de comportamento e interação em toda a família de dispositivos. Isso simplifica o processo de teste e fornece uma experiência mais previsível do usuário.
4. A aceleração do desenvolvimento e redução de custos: Com o mesmo código capaz de trabalhar em muitas plataformas, significativamente é reduzido pelo tempo e custo de desenvolvimento e suporte . As equipes podem se concentrar na funcionalidade e no design, e não na reescrita repetida na mesma interface.
5. prontidão para o futuro: A capacidade de abstrair das especificidades dos dispositivos atuais torna o código declarativo mais mais resistente ao surgimento de novos tipos de dispositivos e fatores de forma . O Freimvorki pode ser atualizado para oferecer suporte a novas tecnologias, e seu código já escrito receberá esse suporte é relativamente perfeito.

Conclusão

A milha declarativa não é apenas uma tendência de moda, mas a etapa evolutiva necessária causada pelo rápido desenvolvimento de dispositivos de usuário, incluindo a esfera da a Internet das Coisas (IoT) e eletrodomésticos inteligentes. Ele permite que desenvolvedores e designers criem interfaces complexas, adaptativas e uniformes, sem se afogar em inúmeras implementações específicas para cada plataforma. A transição do controle imperativo sobre cada pixel para a descrição declarativa do estado desejado é um reconhecimento de que no mundo das interfaces futuras deve ser flexível, transferido e intuitivo independentemente de qual tela são exibidas.

Programadores, designers e usuários precisam aprender a viver neste novo mundo. Os detalhes extras do pixel perfeito, projetados para um dispositivo ou resolução específica, levam a custos de tempo desnecessários para desenvolvimento e suporte. Além disso, esses layouts severos podem simplesmente não funcionar em dispositivos com interfaces não padrão, como TVs de entrada limitadas, mudanças de VR e AR, bem como outros dispositivos do futuro, que ainda nem conhecemos hoje. Flexibilidade e adaptabilidade – essas são as chaves para a criação de interfaces bem -sucedidas no mundo moderno.

Por que os programadores não fazem nada, mesmo com redes neurais

Hoje, as redes neurais são usadas em todos os lugares. Os programadores os usam para gerar código, explicar outras soluções, automatizar tarefas de rotina e até criar aplicativos inteiros do zero. Parece que isso deve levar a um aumento de eficiência, reduzindo erros e aceleração do desenvolvimento. Mas a realidade é muito mais prosaica: muitos ainda não conseguem. As redes neurais não resolvem problemas importantes – elas apenas iluminam a profundidade da ignorância.

dependência total do LLM em vez de entender

A principal razão é que muitos desenvolvedores confiam completamente no LLM, ignorando a necessidade de uma compreensão profunda das ferramentas com as quais trabalham. Em vez de estudar documentação – uma solicitação de bate -papo. Em vez de analisar os motivos do erro – copiar a decisão. Em vez de soluções arquitetônicas – a geração de componentes de acordo com a descrição. Tudo isso pode funcionar em um nível superficial, mas assim que surgir uma tarefa não padrão, integração com um projeto real ou a necessidade de ajuste fino é necessário, tudo está desmoronando.

Falta de contexto e práticas desatualizadas

As redes neurais geram o código generalizado. Eles não levam em consideração os detalhes da sua plataforma, versão das bibliotecas, restrições ambientais ou soluções arquitetônicas do projeto. O que é gerado geralmente parece plausível, mas não tem nada a ver com o código real e suportado. Mesmo recomendações simples podem não funcionar se pertencem à versão desatualizada da estrutura ou abordagens de uso que há muito são reconhecidas como ineficazes ou inseguras. Os modelos não entendem o contexto – eles dependem de estatísticas. Isso significa que erros e antipattterns, populares em código aberto, serão reproduzidos repetidamente.

redundância, ineficiência e falta de perfil

O código gerado pela IA geralmente é redundante. Inclui dependências desnecessárias, duplica a lógica, adiciona abstrações desnecessariamente. Acontece que uma estrutura pesada e ineficaz que é difícil de apoiar. Isso é especialmente agudo no desenvolvimento móvel, onde o tamanho da gangue, o tempo de resposta e o consumo de energia são críticos.

A rede neural não conduz o perfil, não leva em consideração as restrições da CPU e da GPU, não se importa com os vazamentos da memória. Ele não analisa a eficácia do código na prática. A otimização ainda é feita à mão, exigindo análise e exame. Sem ele, o aplicativo se torna lento, instável e intensivo em recursos, mesmo que pareça “certo” do ponto de vista da estrutura.

vulnerabilidade e uma ameaça à segurança

Não se esqueça da segurança. Já existem casos conhecidos em que projetos parcial ou totalmente criados usando LLM foram invadidos com sucesso. Os motivos são típicos: o uso de funções inseguras, falta de verificação dos dados de entrada, erros na lógica da autorização, vazamento por meio de dependências externas. A rede neural pode gerar um código vulnerável simplesmente porque foi encontrado em repositórios abertos. Sem a participação de especialistas em segurança e uma revisão completa, esses erros se tornam facilmente pontos de entrada para ataques.

A lei é pareto e a essência das falhas

A lei de Pareto funciona claramente com as redes neurais: 80% do resultado é alcançado devido a 20% do esforço. O modelo pode gerar uma grande quantidade de código, criar a base do projeto, espalhar a estrutura, organizar tipos, conectar módulos. No entanto, tudo isso pode estar desatualizado, incompatível com versões atuais de bibliotecas ou estruturas, e requer revisão manual significativa. A automação aqui funciona como um rascunho que precisa ser verificado, processado e adaptado a realidades específicas do projeto.

CUIDADO Otimismo

No entanto, o futuro parece encorajador. Atualização constante dos conjuntos de dados de treinamento, integração com documentação atual, verificações automatizadas de arquitetura, conformidade com padrões de design e segurança – tudo isso pode alterar radicalmente as regras do jogo. Talvez em alguns anos possamos realmente escrever o código mais rápido, mais seguro e eficiente, confiando no LLM como um verdadeiro co -autor técnico. Mas por enquanto – infelizmente – muito precisa ser verificado, reescrito e modificado manualmente.

As redes neurais são uma ferramenta poderosa. Mas, para que ele trabalhe para você, e não contra você, você precisa de uma base, pensamento crítico e vontade de assumir o controle a qualquer momento.

Truques de vibe-core: por que o LLM ainda não funciona com sólido, seco e limpo

Com o desenvolvimento de grandes modelos de idiomas (LLM), como ChatGPT, mais e mais desenvolvedores os usam para gerar código, projetar arquitetura e acelerar a integração. No entanto, com a aplicação prática, torna -se perceptível: os princípios clássicos da arquitetura – sólidos, secos, limpos – se dão mal com as peculiaridades da gordura do LLM.

Isso não significa que os princípios estejam desatualizados – pelo contrário, eles funcionam perfeitamente com o desenvolvimento manual. Mas com o LLM, a abordagem deve ser adaptada.

Por que o LLM não pode lidar com os princípios arquitetônicos

Encapsulamento

O incapsolamento requer a compreensão dos limites entre partes do sistema, conhecimento sobre as intenções do desenvolvedor, bem como seguem restrições estritas de acesso. O LLM geralmente simplifica a estrutura, torna o Fields público sem motivo ou duplica a implementação. Isso torna o código mais vulnerável a erros e viola os limites arquitetônicos.

Resumos e interfaces

Os padrões de design, como uma fábrica ou estratégia abstrata, requerem uma visão holística do sistema e entender sua dinâmica. Os modelos podem criar uma interface sem um objetivo claro sem garantir sua implementação ou violar a conexão entre as camadas. O resultado é uma arquitetura excessiva ou não funcional.

seco (não se repete)

O LLM não procura minimizar o código de repetição – pelo contrário, é mais fácil para eles duplicarem blocos do que fazer lógica geral. Embora eles possam oferecer refatoração mediante solicitação, os modelos padrão tendem a gerar fragmentos “auto -suficientes”, mesmo que isso leve à redundância.

Arquitetura limpa

A limpeza implica uma hierarquia estrita, independência das estruturas, dependência direcionada e conexão mínima entre as camadas. A geração de tal estrutura requer uma compreensão global do sistema – e o trabalho de LLM no nível de probabilidade de palavras, não a integridade arquitetônica. Portanto, o código é misturado, com a violação das direções da dependência e uma divisão simplificada em níveis.

O que funciona melhor ao trabalhar com LLM

Molhado em vez de seco
A abordagem molhada (escreva tudo duas vezes) é mais prática para trabalhar com o LLM. A duplicação do código não requer contexto do modelo de retenção, o que significa que o resultado é previsível e é mais fácil de corretamente correto. Também reduz a probabilidade de conexões e bugs não óbvios.

Além disso, a duplicação ajuda a compensar a lembrança curta do modelo: se um certo fragmento de lógica for encontrado em vários lugares, é mais provável que o LLM leve em consideração com mais geração. Isso simplifica o acompanhamento e aumenta a resistência ao “esquecimento”.

estruturas simples em vez de encapsulamento

Evitando encapsulamento complexo e confiar na transmissão direta de dados entre as partes do código, você pode simplificar bastante a geração e a depuração. Isto é especialmente verdade com um rápido desenvolvimento iterativo ou criação de MVP.

Arquitetura simplificada

Uma estrutura simples e plana do projeto com uma quantidade mínima de dependências e abstrações fornece um resultado mais estável durante a geração. O modelo adapta esse código mais fácil e menos frequentemente viola as conexões esperadas entre os componentes.

Integração SDK – manualmente confiável

A maioria dos modelos de idiomas é treinada em versões desatualizadas de documentação. Portanto, ao gerar instruções para a instalação do SDK, os erros geralmente aparecem: comandos desatualizados, parâmetros irrelevantes ou links para recursos inacessíveis. Mostra de prática: é melhor usar documentação oficial e ajuste manual, deixando o LLM uma função auxiliar – por exemplo, gerando um código de modelo ou adaptação de configurações.

Por que os princípios ainda funcionam – mas com desenvolvimento manual

É importante entender que as dificuldades de sólido, seco e limpo dizem respeito à código de código através do LLM. Quando o desenvolvedor escreve o código manualmente, esses princípios continuam demonstrando seu valor: reduzem a conexão, simplificam o suporte, aumentam a legibilidade e a flexibilidade do projeto.

Isso se deve ao fato de que o pensamento humano é propenso à generalização. Estamos procurando padrões, trazemos a lógica repetida para entidades individuais, criamos padrões. Provavelmente, esse comportamento tem raízes evolutivas: reduzir a quantidade de informações salva recursos cognitivos.

O LLM age de maneira diferente: eles não experimentam cargas do volume de dados e não se esforçam para economizar. Pelo contrário, é mais fácil para eles trabalhar com informações fragmentadas e duplicadas do que construir e manter abstrações complexas. É por isso que é mais fácil lidar com o código sem encapsulamento, com estruturas repetidas e severidade arquitetônica mínima.

Conclusão

Modelos de idiomas grandes são uma ferramenta útil no desenvolvimento, especialmente nos estágios iniciais ou ao criar um código auxiliar. Mas é importante adaptar a abordagem a eles: simplificar a arquitetura, limitar a abstração, evitar dependências complexas e não confiar nelas ao configurar o SDK.

Os princípios de sólidos, secos e limpos ainda são relevantes, mas dão o melhor efeito nas mãos de uma pessoa. Ao trabalhar com a LLM, é razoável usar um estilo prático e simplificado que permite obter um código confiável e compreensível que seja fácil de finalizar manualmente. E onde LLM esquece – a duplicação do código o ajuda a se lembrar.

Portando Surreal Engine C++ para WebAssembly

Neste post vou descrever como portei o motor de jogo Surreal Engine para WebAssembly.

Motor Surreal – um motor de jogo que implementa a maior parte das funcionalidades do Unreal Engine 1, jogos famosos neste motor – Torneio Unreal 99, Unreal, Deus Ex, Imortal. Refere-se a mecanismos clássicos que funcionavam principalmente em um ambiente de execução de thread único.

Inicialmente tive a ideia de assumir um projeto que não conseguiria concluir em um prazo razoável, mostrando assim aos meus seguidores do Twitch que existem projetos que nem eu consigo realizar. Durante minha primeira transmissão, de repente percebi que a tarefa de portar o Surreal Engine C++ para WebAssembly usando Emscripten é viável.

Surreal Engine Emscripten Demo

Depois de um mês posso demonstrar minha montagem de garfo e motor no WebAssembly:
https://demensdeum.com/demos/SurrealEngine/

O controle, como no original, é feito através das setas do teclado. Em seguida, pretendo adaptá-lo para controle móvel (tachi), adicionando iluminação correta e outros recursos gráficos da renderização do Unreal Tournament 99.

Por onde começar?

A primeira coisa que quero dizer é que qualquer projeto pode ser portado de C++ para WebAssembly usando Emscripten, a única dúvida é quão completa será a funcionalidade. Escolha um projeto cujas portas de biblioteca já estejam disponíveis para Emscripten, no caso do Surreal Engine, você tem muita sorte, pois o mecanismo usa as bibliotecas SDL 2, OpenAL – ambos foram portados para o Emscripten. No entanto, Vulkan é usado como uma API gráfica, que atualmente não está disponível para HTML5, o trabalho está em andamento para implementar WebGPU, mas também está em fase de rascunho, e também não se sabe quão simples será a porta adicional de Vulkan para WebGPU , depois de totalmente padronizado. Portanto, tive que escrever minha própria renderização básica OpenGL-ES/WebGL para Surreal Engine.

Construindo o projeto

Construir sistema no Surreal Engine – CMake, que também simplifica a portabilidade, porque Emscripten fornece aos seus construtores nativos – emcmake, emmake.
O porte do Surreal Engine foi baseado no código do meu último jogo em WebGL/OpenGL ES e C++ chamado Death-Mask, por isso o desenvolvimento foi muito mais simples, eu tinha todos os build flags necessários comigo e exemplos de código.

Um dos pontos mais importantes em CMakeLists.txt são os sinalizadores de construção do Emscripten. Abaixo está um exemplo do arquivo do projeto:


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

O próprio script de construção:


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

Em seguida, prepararemos o índice .html , que inclui o pré-carregador do sistema de arquivos do projeto. Para fazer upload para a web, usei o Unreal Tournament Demo versão 338. Como você pode ver no arquivo CMake, a pasta do jogo descompactada foi adicionada ao diretório de construção e vinculada como um arquivo de pré-carregamento para Emscripten.

Alterações no código principal

Então foi necessário alterar o loop do jogo, você não pode executar um loop infinito, isso faz com que o navegador congele, em vez disso você precisa usar emscripten_set_main_loop, escrevi sobre esse recurso em minha nota de 2017 “< a href="https://demensdeum.com /blog/ru/2017/03/29/porting-sdl-c-game-to-html5-emscripten/" rel="noopener" target="_blank">Portar jogo SDL C++ para HTML5 (Emscripten)”
Alteramos o código para sair do loop while para if, então exibimos a classe principal do mecanismo de jogo, que contém o loop do jogo, no escopo global, e escrevemos uma função global que chamará a etapa do loop do jogo do objeto global :


#include <emscripten.h>

Engine *EMSCRIPTEN_GLOBAL_GAME_ENGINE = nullptr;

void emscripten_game_loop_step() {

	EMSCRIPTEN_GLOBAL_GAME_ENGINE->Run();

}

#endif

Depois disso, você precisa ter certeza de que não há threads em segundo plano no aplicativo, se houver, então prepare-se para reescrevê-los para execução de thread único ou use a biblioteca phtread no Emscripten.
O thread de segundo plano no Surreal Engine é usado para reproduzir música, os dados vêm do thread do mecanismo principal sobre a faixa atual, a necessidade de tocar música ou sua ausência, então o thread de segundo plano recebe um novo estado por meio de um mutex e começa a tocar nova música ou pausa-o. O fluxo de fundo também é usado para armazenar música em buffer durante a reprodução.
Minhas tentativas de construir o Surreal Engine para Emscripten com pthread não tiveram sucesso, porque as portas SDL2 e OpenAL foram construídas sem suporte a pthread e eu não queria reconstruí-las por causa da música. Portanto, transferi a funcionalidade do fluxo de música de fundo para execução de thread único usando um loop. Ao remover as chamadas pthread do código C++, movi o buffer e a reprodução da música para o thread principal, para que não houvesse atrasos, aumentei o buffer em alguns segundos.

A seguir, descreverei implementações específicas de gráficos e som.

Vulkan não é compatível!

Sim, Vulkan não é compatível com HTML5, embora todos os folhetos de marketing apresentem suporte multiplataforma e ampla plataforma como a principal vantagem do Vulkan. Por esse motivo, tive que escrever meu próprio renderizador gráfico básico para um tipo OpenGL simplificado – – ES, é usado em dispositivos móveis, às vezes não contém os recursos modernos do OpenGL moderno, mas porta muito bem para WebGL, que é exatamente o que o Emscripten implementa. A escrita da renderização básica de blocos, renderização bsp, para a exibição da GUI mais simples e renderização de modelos + mapas foi concluída em duas semanas. Esta foi talvez a parte mais difícil do projeto. Ainda há muito trabalho pela frente para implementar todas as funcionalidades da renderização do Surreal Engine, portanto, qualquer ajuda dos leitores é bem-vinda na forma de código e solicitações pull.

OpenAL compatível!

Grande sorte é que o Surreal Engine usa OpenAL para saída de áudio. Depois de escrever um hello world simples em OpenAL e montá-lo em WebAssembly usando Emscripten, ficou claro para mim como tudo era simples e comecei a portar o som.
Após várias horas de depuração, ficou óbvio que a implementação OpenAL do Emscripten possui vários bugs, por exemplo, ao inicializar a leitura do número de canais mono, o método retornou um número infinito, e após tentar inicializar um vetor de tamanho infinito, C++ trava com a exceção vector::length_error.

Conseguimos contornar isso codificando o número de canais mono para 2048:


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



#if __EMSCRIPTEN__

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

#endif



Existe uma rede?

O Surreal Engine atualmente não suporta jogos online, jogar com bots é compatível, mas precisamos de alguém para escrever IA para esses bots. Teoricamente, você pode implementar um jogo em rede no WebAssembly/Emscripten usando Websockets.

Conclusão

Concluindo, gostaria de dizer que a portabilidade do Surreal Engine acabou sendo bastante tranquila devido ao uso de bibliotecas para as quais existem portas Emscripten, bem como à minha experiência anterior na implementação de um jogo em C++ para WebAssembly em Emscripten. Abaixo estão links para fontes de conhecimento e repositórios sobre o tema.
M-M-M-MATANÇA DE MONSTRO!

Além disso, se você quiser ajudar o projeto, de preferência com código de renderização WebGL/OpenGL ES, escreva para mim no Telegram:
https://t.me/demenscave

Links

https://demensdeum.com/demos/SurrealEngine/
https://github.com/demensdeum/SurrealEngine-Emscripten

https://github.com/dpjudas/SurrealEngine

Flash Forever – Interceptor 2021

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

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

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

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

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

Repositório CRUD

Nesta nota descreverei os princípios básicos do conhecido padrão clássico CRUD, implementado na linguagem Swift. Swift é uma linguagem aberta e multiplataforma disponível para Windows, Linux, macOS, iOS, Android.

Existem muitas soluções para abstrair o armazenamento de dados e a lógica do aplicativo. Uma dessas soluções é a abordagem CRUD, que é um acrônimo para C– Criar, R -Leia, U– Atualização, D– Excluir.
Normalmente, este princípio é implementado através da implementação de uma interface para o banco de dados, na qual os elementos são manipulados usando um identificador único, como o id. Uma interface é criada para cada letra CRUD – Criar(objeto, id), Ler(id), Atualizar(objeto, id), Excluir(objeto, id).
Se um objeto contém um id dentro de si, então o argumento id pode ser omitido dos métodos (Create, Update, Delete), já que todo o objeto é passado para lá junto com seu campo – eu ia. Mas para – A leitura requer id porque queremos obter um objeto do banco de dados por id.

Todos os nomes são fictícios

Vamos imaginar que um hipotético aplicativo AssistantAI foi criado usando o SDK gratuito do banco de dados EtherRelm, a integração era simples, a API era muito conveniente e, como resultado, o aplicativo foi lançado nos mercados.
De repente, o desenvolvedor do SDK EtherRelm decide torná-lo pago, fixando o preço em US$ 100 por ano por usuário do aplicativo.
O que? Sim! O que os desenvolvedores da AssistantAI devem fazer agora, pois já possuem 1 milhão de usuários ativos! Pagar US$ 100 milhões?
Em vez disso, é tomada a decisão de avaliar a transferência da aplicação para o banco de dados RootData nativo da plataforma, segundo os programadores, tal transferência levará cerca de seis meses, isso não leva em consideração a implementação de novos recursos na aplicação; Depois de pensar um pouco, foi tomada a decisão de remover o aplicativo dos mercados, reescrevê-lo em outro framework multiplataforma gratuito com um banco de dados BueMS integrado, isso resolverá o problema com o banco de dados pago + simplificará o desenvolvimento em outras plataformas.
Um ano depois, o aplicativo foi reescrito em BueMS, mas de repente o desenvolvedor do framework decide torná-lo pago. Acontece que a equipe caiu na mesma armadilha duas vezes. Se conseguirão sair na segunda vez é uma história completamente diferente.

Abstração para o resgate

Esses problemas poderiam ter sido evitados se os desenvolvedores tivessem usado uma abstração de interfaces dentro da aplicação. Aos três pilares da OOP – polimorfismo, encapsulamento, herança, não faz muito tempo eles adicionaram outro – abstração.
A abstração de dados permite descrever ideias e modelos em termos gerais, com um mínimo de detalhes, ao mesmo tempo em que é preciso o suficiente para implementar implementações específicas usadas para resolver problemas de negócios.
Como podemos abstrair a operação do banco de dados para que a lógica da aplicação não dependa dela? Usamos a abordagem CRUD!

Um diagrama UML CRUD simplificado tem esta aparência:

Exemplo com um banco de dados EtherRelm fictício:

Exemplo com um banco de dados SQLite real:

Como você já percebeu, ao trocar o banco de dados, apenas ele muda a interface CRUD com a qual a aplicação interage permanece inalterada. CRUD é uma implementação do padrão GoF – Adaptador, porque com ele adaptamos interfaces de aplicações a qualquer banco de dados e combinamos interfaces incompatíveis.
As palavras estão vazias, mostre-me o código
Para implementar abstrações em linguagens de programação, são usadas interfaces/protocolos/classes abstratas. Todos esses são fenômenos da mesma ordem, porém, durante as entrevistas você pode ser solicitado a nomear a diferença entre eles, eu pessoalmente acho que isso não faz muito sentido porque o único propósito de uso é implementar a abstração de dados, caso contrário é para testar a memória do entrevistado.
O CRUD é frequentemente implementado dentro da estrutura do padrão Repository, porém, o repositório pode ou não implementar a interface CRUD, tudo depende da engenhosidade do desenvolvedor.

Considere um código Swift bastante típico para o repositório da estrutura Book, trabalhando diretamente com o banco de dados 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)
}

O código acima parece simples, mas vamos contar o número de violações do princípio DRY (Do not Repeat Yourself) e a coerência do código:
Conectividade com o banco de dados UserDefaults
Conectividade com codificadores e decodificadores JSON – JSONEncoder, JSONDecoder
Conectado com a estrutura Book, mas precisamos de um repositório abstrato para não criar uma classe de repositório para cada estrutura que iremos armazenar no banco de dados (violação DRY)

Vejo esse tipo de código de repositório CRUD com bastante frequência, ele pode ser usado, mas o alto acoplamento e a duplicação de código levam ao fato de que, com o tempo, seu suporte se tornará muito complicado. Isso será especialmente perceptível ao tentar mudar para outro banco de dados, ou ao alterar a lógica interna de trabalho com o banco de dados em todos os repositórios criados na aplicação.
Em vez de duplicar o código, mantenha o acoplamento alto – Vamos escrever um protocolo para o repositório CRUD, abstraindo assim a interface do banco de dados e a lógica de negócio da aplicação, respeitando o DRY, implementando baixo acoplamento:

    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
}

O protocolo CRUDRepository descreve interfaces e tipos de dados associados para implementação adicional de um repositório CRUD específico.

A seguir escreveremos uma implementação específica para o banco de dados 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()
    }
}

O código parece longo, mas contém uma implementação concreta completa de um repositório CRUD contendo acoplamento fraco, detalhes abaixo.
typealias foram adicionadas para autodocumentação do código.
Acoplamento fraco e acoplamento forte
O descolamento de uma estrutura específica (struct) é implementado usando o T genérico, que por sua vez deve implementar os protocolos Codable. Codable permite converter estruturas usando classes que implementam os protocolos TopLevelEncoder e TopLevelDecoder, por exemplo JSONEncoder e JSONDecoder, ao usar tipos básicos (Int, String, Float, etc.) não há necessidade de escrever código adicional para converter estruturas.

A dissociação de um codificador e decodificador específico ocorre usando abstração no protocolo DataTransformer:

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

Usando a implementação de um transformador de dados, implementamos uma abstração das interfaces do codificador e do decodificador, implementando acoplamento fraco para garantir o trabalho com diferentes tipos de formatos de dados.

A seguir está o código para um DataTransformer específico, ou seja, para 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
	}
}

Foi possível?

O que mudou? Agora basta inicializar um repositório específico para funcionar com qualquer estrutura que implemente o protocolo Codable, eliminando assim a necessidade de duplicação de código e implementação de acoplamento fraco da aplicação.

Um exemplo de cliente CRUD com um repositório específico, UserDefaults é o banco de dados, formato de dados JSON, estrutura do cliente, também um exemplo de escrita e leitura de um array:


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

Durante a primeira verificação CRUD, foi implementado o tratamento de exceções, em que a leitura do item remoto não estará mais disponível.

Trocar bancos de dados

Agora mostrarei como transferir seu código atual para outro banco de dados. Por exemplo, pegarei o código do repositório SQLite que o ChatGPT gerou:


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 o código CRUD do repositório do sistema de arquivos, que também foi gerado pelo 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)
	}
}

Substitua o repositório no código do cliente:


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

A inicialização de UserDefaultsRepository foi substituída por FileSystemRepository, com os argumentos apropriados.
Após executar a segunda versão do código do cliente, você encontrará um diretório “Banco de Dados de Clientes” na pasta de documentos, que conterá um arquivo de um array serializado em JSON com uma estrutura de Cliente.

Alterar formato de armazenamento de dados

Agora vamos pedir ao ChatGPT para gerar um codificador e decodificador para 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)
	}
}

Graças aos tipos integrados no Swift, a tarefa de uma rede neural torna-se elementar.

Substitua JSON por XML no código do cliente:


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

O código do cliente mudou para apenas uma expressão JSONDataTransformer -> XMLDataTransformer

Total

Repositórios CRUD são um dos padrões de design que podem ser usados ​​para implementar acoplamento fraco de componentes de arquitetura de aplicativos. Mais uma das soluções – usando ORM (Mapeamento Objeto-Relacional), resumindo, ORM usa uma abordagem em que as estruturas são completamente mapeadas para o banco de dados, e então as alterações com modelos devem ser exibidas (mapeadas(!)) no banco de dados.
Mas essa é uma história completamente diferente.

Uma implementação completa dos repositórios CRUD para Swift está disponível em:
https://gitlab.com/demensdeum/crud-example

A propósito, o Swift tem suporte fora do macOS há muito tempo; o código do artigo foi totalmente escrito e testado no Arch Linux.

Fontes

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

dd erro de entrada/saída

O que você deve fazer se receber um erro de entrada/saída ao copiar um disco normal usando dd no Linux?

Situevina é muito triste, mas tem solução. Provavelmente você está lidando com um disco com falha contendo blocos defeituosos que não podem mais ser usados, gravados ou lidos.

Certifique-se de verificar esse disco usando S.M.A.R.T., provavelmente ele mostrará erros no disco. Este foi o caso no meu caso; o número de blocos defeituosos foi tão grande que tive que dizer adeus ao disco rígido antigo e substituí-lo por um novo SSD.

O problema era que este disco continha um sistema totalmente funcional com software licenciado necessário para o trabalho. Tentei usar o partimage para copiar dados rapidamente, mas de repente descobri que o utilitário copia apenas um terço do disco e termina com um segfault ou com alguma outra piada engraçada do Sishny/Sipplusplus.

Em seguida, tentei copiar os dados usando dd, e descobri que dd chega aproximadamente ao mesmo lugar que partimage e então ocorre um erro de entrada/saída. Ao mesmo tempo, todos os tipos de sinalizadores engraçados como conv=noerr, skip ou qualquer outra coisa não ajudaram em nada.

Mas os dados foram copiados para outro disco sem problemas usando um utilitário GNU chamado ddrescue.

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

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

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

Bate-papoGPT

Olá a todos! Neste artigo quero falar sobre ChatGPT – modelagem de linguagem poderosa da OpenAI que pode ajudar a resolver uma variedade de problemas de processamento de texto. Vou mostrar como essa ferramenta funciona e como ela pode ser utilizada em situações práticas. Vamos começar!

No momento, ChatGPT é um dos melhores modelos de linguagem do mundo baseados em redes neurais. Ele foi criado com o objetivo de ajudar os desenvolvedores a criar sistemas inteligentes que sejam capazes de gerar linguagem natural e se comunicar com as pessoas.

Uma das principais vantagens do ChatGPT é sua capacidade de modelar texto contextualmente. Isso significa que o modelo leva em consideração o diálogo anterior e o utiliza para compreender a situação com mais precisão e gerar uma resposta mais natural.

Você pode usar o ChatGPT para diversas tarefas, como automatizar o suporte ao cliente, criar chatbots, gerar texto e muito mais.

As redes neurais por trás do ChatGPT foram treinadas em grandes quantidades de texto para garantir previsões altamente precisas. Isso permite que o modelo gere texto natural que pode apoiar o diálogo e responder a perguntas.

Com o ChatGPT, você pode criar seus próprios chatbots e outros sistemas inteligentes que podem interagir com pessoas em linguagem natural. Isto pode ser especialmente útil em setores como turismo, varejo e suporte ao cliente.

Concluindo, ChatGPT – é uma ferramenta poderosa para resolver vários problemas de modelagem de linguagem. Sua capacidade de modelagem contextual o torna especialmente útil para a criação de chatbots e sistemas inteligentes.


Na verdade, o ChatGPT escreveu tudo acima sozinho. O que? Sim? Eu também estou chocado!

Você pode experimentar a grade aqui:
https://chat.openai.com/chat

Ligue a luz de fundo do teclado USB no macOS

Comprei recentemente um teclado USB Getorix GK-45X muito barato com retroiluminação RGB. Ao conectá-lo a um MacBook Pro com processador M1, ficou claro que a luz de fundo RGB não funcionava. Mesmo pressionando a combinação mágica Fn + Scroll Lock não conseguiu ligar a luz de fundo; apenas o nível de luz de fundo da tela do MacBook mudou.
Existem várias soluções para este problema, nomeadamente OpenRGB (não funciona), HID LED Test (não funciona). Apenas o utilitário kvmswitch funcionou:
https://github.com/stoutput/OSX-KVM

Você precisa baixá-lo do GitHub e permitir que ele seja executado no terminal no painel Segurança das Configurações do Sistema.
Pelo que entendi pela descrição, após iniciar o utilitário, ele pressiona Fn + Scroll Lock, ligando/desligando assim a luz de fundo do teclado.

Tipo de árvore

Classificação em árvore – classificação usando uma árvore de pesquisa binária. Complexidade de tempo – O(n²). Nessa árvore, cada nó da esquerda tem números menores que o nó, à direita há mais que o nó, ao vir da raiz e imprimir os valores da esquerda para a direita, obtemos uma lista ordenada de números . Surpreendente, certo?

Considere o diagrama de árvore de pesquisa binária:

Derrick Coetzee (domínio público)

Tente ler manualmente os números começando pelo penúltimo nó esquerdo do canto inferior esquerdo, para cada nó à esquerda – um nó – à direita.

Ficará assim:

  1. Penúltimo nó no canto inferior esquerdo – 3.
  2. Tem um ramo esquerdo – 1.
  3. Pegue este número (1)
  4. Em seguida, pegamos o vértice 3 (1, 3)
  5. À direita está o ramo 6, mas contém ramos. Portanto, lemos da mesma maneira.
  6. Ramo esquerdo do nó 6 número 4 (1, 3, 4)
  7. O próprio nó é 6 (1, 3, 4, 6)
  8. Direita 7 (1, 3, 4, 6, 7)
  9. Vá até o nó raiz – 8 (1,3, 4,6, 7, 8)
  10. Imprimimos tudo à direita por analogia
  11. Obtemos a lista final – 1, 3, 4, 6, 7, 8, 10, 13, 14

Para implementar o algoritmo em código, você precisará de duas funções:

  1. Montando uma árvore de pesquisa binária
  2. Imprimindo a árvore de pesquisa binária na ordem correta

A árvore binária de busca é montada da mesma forma que é lida, um número é anexado a cada nó à esquerda ou à direita, dependendo se é menor ou maior.

Exemplo em 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

Classificação de intervalo

Classificação por bucket – classificação por buckets. O algoritmo é semelhante à classificação por contagem, com a diferença de que os números são coletados em intervalos de “baldes”, então os baldes são classificados usando qualquer outro algoritmo de classificação suficientemente produtivo, e a etapa final é desdobrar os “baldes” um por um, resultando em uma lista ordenada

.

A complexidade de tempo do algoritmo é O(nk). O algoritmo funciona em tempo linear para dados que obedecem a uma lei de distribuição uniforme. Simplificando, os elementos devem estar em um determinado intervalo, sem “picos”, por exemplo, números de 0,0 a 1,0. Se entre esses números houver 4 ou 999, então, de acordo com as leis do pátio, essa linha não será mais considerada “par”.

Exemplo de implementação em Julia:

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

    maxNumber = maximum(numbers)

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

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

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

end

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

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

Ссылки

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

Источники

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

Radixsort

Classificação de raiz – classificação de raiz. O algoritmo é semelhante à classificação por contagem, pois não há comparação de elementos; em vez disso, os elementos são agrupados *caractere por caractere* em *baldes* (baldes), o balde é selecionado pelo índice do caractere numérico atual. Complexidade de tempo – O(nd).

Funciona mais ou menos assim:

  • A entrada serão os números 6, 12, 44, 9
  • Criaremos 10 grupos de listas (0-9), nos quais adicionaremos/classificaremos números pouco a pouco.

Próximo:

  1. Inicie um loop com o contador i até o número máximo de caracteres no número
  2. Pelo índice i da direita para a esquerda obtemos um símbolo para cada número; se não houver símbolo, então assumimos que é zero
  3. ;

  4. Converta o símbolo em um número
  5. Selecione um intervalo por número de índice e coloque o número inteiro lá
  6. Depois de terminar de pesquisar os números, converta todos os grupos novamente em uma lista de números
  7. Obter números classificados por classificação
  8. Repita até que todos os dígitos desapareçam

Exemplo de classificação Radix em 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}")

    }

}

O algoritmo também possui uma versão para execução paralela, por exemplo em uma GPU; Há também uma opção de classificação, que deve sermuito interessante e realmente de tirar o fôlego!

Links

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

Fontes

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

Heapsort

Heapsort – classificação em pirâmide. Complexidade de tempo do algoritmo – O (n log n), rápido, certo? Eu chamaria isso de classificação de classificação de pedras que caem. Parece-me que a maneira mais fácil de explicar é visualmente.

A entrada é uma lista de números, por exemplo:
5, 0, 7, 2, 3, 9, 4

Da esquerda para a direita, uma estrutura de dados é criada – uma árvore binária, ou como eu chamo – pirâmide. Os elementos da pirâmide podem ter no máximo dois elementos filhos, mas apenas um elemento superior.

Vamos fazer uma árvore binária:
⠀⠀5
⠀0⠀7
2 3 9 4

Se você olhar a pirâmide por muito tempo, verá que são apenas números de uma matriz, vindo um após o outro, o número de elementos em cada andar é multiplicado por dois.

A seguir, a diversão começa; vamos classificar a pirâmide de baixo para cima usando o método das pedras caindo (heapify). A classificação poderia ser iniciada a partir do último andar (2 3 9 4), mas não adianta porque não há piso abaixo onde você possa cair.

Portanto, começamos a descartar elementos do penúltimo andar (0 7)
⠀⠀5
⠀0⠀7
2 3 9 4

O primeiro elemento a cair é selecionado da direita, no nosso caso é 7, então olhamos o que está abaixo dele, e abaixo dele estão 9 e 4, nove é maior que quatro, e também nove é maior que Sete! Colocamos 7 em 9 e colocamos 9 no lugar 7.
⠀⠀5
⠀0⠀9
2 3 7 4

A seguir, entendemos que o sete não tem onde cair, passamos para o número 0, que está localizado no penúltimo andar à esquerda:
⠀⠀5
0⠀9
2 3 7 4

Vamos ver o que há por baixo – 2 e 3, dois é menor que três, três é maior que zero, então trocamos zero por três:
⠀⠀5
⠀3⠀9
2 0 7 4

Quando chegar ao final do andar, vá para o andar de cima e largue tudo lá, se puder.
O resultado é uma estrutura de dados – um heap, ou seja, max heap, porque no topo está o maior elemento:
⠀⠀9
⠀3⠀7
2 0 5 4

Se você retornar para uma representação de array, você obterá uma lista:
[9, 3, 7, 2, 0, 5, 4]

A partir disso podemos concluir que ao trocar o primeiro e o último elemento, obtemos o primeiro número na posição final ordenada, ou seja, 9 deve estar no final da lista ordenada, troque de lugar:
[4, 3, 7, 2, 0, 5, 9]

Vejamos uma árvore binária:
⠀⠀4
⠀3⠀7
2 0 5 9

O resultado é uma situação em que a parte inferior da árvore está ordenada, basta colocar 4 na posição correta, repetir o algoritmo, mas não levar em consideração os números já ordenados, nomeadamente 9:
⠀⠀4
⠀3⠀7
2 0 5 9

⠀⠀7
⠀3⠀4
2 0 5 9

⠀⠀7
⠀3⠀5
2 0 4 9

Acontece que nós, tendo perdido 4, aumentamos o próximo maior número depois de 9 – 7. Troque o último número não classificado (4) e o maior número (7)
⠀⠀4
⠀3⠀5
2 0 7 9

Acontece que agora temos dois números na posição final correta:
4, 3, 5, 2, 0, 7, 9

Em seguida repetimos o algoritmo de classificação, ignorando os já classificados, no final obtemos um heap tipo:
⠀⠀0
⠀2⠀3
4 5 7 9

Ou como uma lista:
0, 2, 3, 4, 5, 7, 9

Implementação

O algoritmo geralmente é dividido em três funções:

  1. Criando uma pilha
  2. Algoritmo de peneiração (heapify)
  3. Substituindo o último elemento não classificado e o primeiro

O heap é criado percorrendo a penúltima linha da árvore binária usando a função heapify, da direita para a esquerda, até o final do array. A seguir no ciclo, é feita a primeira substituição de números, após a qual o primeiro elemento cai/permanece no lugar, como resultado o elemento maior cai em primeiro lugar, o ciclo é repetido com uma diminuição de participantes em um, porque após cada passagem, os números classificados permanecem no final da lista.

Exemplo de Heapsort em 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



Esse algoritmo não é fácil de entender sem visualização, então a primeira coisa que recomendo é escrever uma função que imprima a visualização atual da árvore binária.

Links

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

Fontes

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 (estrutura de dados)

https://ru.wikipedia.org/wiki/Heap (estrutura de dados)

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/ representação de array de heap binário/

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

Classificação rápida

Quicksort é um algoritmo de classificação de divisão e conquista. Recursivamente, peça por peça, analisamos a matriz de números, colocando os números em ordem menor e maior a partir do elemento de referência selecionado, e inserimos o próprio elemento de referência no corte entre eles. Após várias iterações recursivas, você terá uma lista ordenada. Complexidade de tempo O(n2).

Esquema:

  1. Começamos obtendo uma lista de elementos externos, os limites de classificação. Na primeira etapa, os limites de classificação serão do início ao fim.
  2. Verifique se os limites inicial e final não se cruzam; se isso acontecer, é hora de terminar.
  3. Selecione algum elemento da lista e chame-o de pivô
  4. Mova-o para a direita até o final do último índice para que não atrapalhe
  5. Crie um contador de *números menores* ainda iguais a zero
  6. Percorrer a lista da esquerda para a direita, até e incluindo o último índice onde o elemento de referência está localizado
  7. Comparamos cada elemento com o de referência
  8. Se for menor que o de referência, trocamos de acordo com o índice do contador de números menores. Aumente o contador de números menores.
  9. Quando o loop atinge o elemento de suporte, paramos e trocamos o elemento de suporte pelo elemento de acordo com o contador de números menores.
  10. Executamos o algoritmo separadamente para a parte menor à esquerda da lista e separadamente para a parte maior à direita da lista.
  11. Como resultado, todas as iterações recursivas começarão a parar devido à verificação no ponto 2
  12. Obter uma lista ordenada

Quicksort foi inventado pelo cientista Charles Anthony Richard Hoare na Universidade Estadual de Moscou. Depois de aprender russo, ele estudou tradução computacional, bem como teoria das probabilidades na escola Kolmogorov. Em 1960, devido à crise política, deixou a União Soviética.

Exemplo de implementação em 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);
  }
}

Se nada estiver claro, sugiro assistir ao vídeo de Rob Edwards, da Universidade de San Diego https://www.youtube.com/watch?v=ZHVk2blR45Q mostra de maneira mais simples, passo a passo, a essência e a implementação do algoritmo.

Links

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

Fontes

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

Classificação de inserção binária

A classificação por inserção binária é uma variante da classificação por inserção na qual a posição de inserção é determinada usando pesquisa binária. A complexidade de tempo do algoritmo é O(n2)

O algoritmo funciona assim:

  1. Um loop começa de zero até o final da lista
  2. No loop, um número é selecionado para classificação, o número é armazenado em uma variável separada
  3. A pesquisa binária procura o índice para inserir esse número nos números à esquerda
  4. Uma vez encontrado o índice, os números à esquerda são deslocados uma posição para a direita, começando no índice de inserção. No processo, o número que precisa ser classificado será apagado.
  5. O número salvo anteriormente é inserido no índice de inserção
  6. No final do loop, a lista inteira será classificada

Durante uma pesquisa binária, é possível que o número não seja encontrado e o índice não seja retornado. Devido à peculiaridade da busca binária será encontrado o número mais próximo do buscado, então para retornar o índice será necessário compará-lo com o procurado, se o procurado for menor então o procurado deverá estar em o índice à esquerda e, se for maior ou igual, à direita.

Código Go:


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

const numbersCount = 20
const maximalNumber = 100

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

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

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

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

Links

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

Fontes

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

Classificação de casca

Shell Sort – uma variante da classificação por inserção com combinação preliminar de uma matriz de números.

Precisamos lembrar como funciona a classificação por inserção:

1. Um loop é iniciado do zero até o final do loop, assim o array é dividido em duas partes
2. Para a parte esquerda, um segundo loop é iniciado, comparando os elementos da direita para a esquerda, o elemento menor à direita é descartado até que um elemento menor à esquerda seja encontrado
3. No final de ambos os loops, obtemos uma lista ordenada

Era uma vez, o cientista da computação Donald Schell se perguntou como melhorar o algoritmo de classificação por inserção. Ele também teve a ideia de primeiro percorrer o array em dois ciclos, mas a uma certa distância, reduzindo gradativamente o “pente” até que ele se transforme em um algoritmo regular de ordenação por inserção. Tudo é realmente tão simples, sem armadilhas, aos dois ciclos acima acrescentamos outro, no qual vamos reduzindo gradativamente o tamanho do “pente”. A única coisa que você precisa fazer é verificar a distância ao comparar para que ela não ultrapasse o array.

Um tópico realmente interessante é escolher a sequência para alterar o comprimento da comparação a cada iteração do primeiro loop. É interessante porque o desempenho do algoritmo depende disso.

A tabela de opções conhecidas e complexidade de tempo pode ser encontrada aqui: https: //en.wikipedia.org/wiki/Shellsort#Gap_sequences

Pessoas diferentes estiveram envolvidas no cálculo da distância ideal, aparentemente, esse assunto era muito interessante para elas. Eles não poderiam simplesmente executar Ruby e chamar o algoritmo sort() mais rápido?

Em geral, essas pessoas estranhas escreveram dissertações sobre o tema do cálculo da distância/gap do “pente” para o algoritmo Shell. Simplesmente usei os resultados do trabalho deles e verifiquei 5 tipos de sequências, 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)

Na minha implementação, para um conjunto aleatório de números, as lacunas mais rápidas são Sedgwick e Hibbard.

meupy

Gostaria também de mencionar o analisador de tipagem estática para Python 3 – meu Deus. Ajuda a resolver os problemas inerentes às linguagens com digitação dinâmica, nomeadamente, elimina a possibilidade de colar algo onde não é necessário.

Como dizem programadores experientes, “a digitação estática não é necessária quando você tem uma equipe de profissionais”, um dia todos nos tornaremos profissionais, escreveremos código em total unidade e compreensão com as máquinas, mas por enquanto você pode usar utilitários semelhantes e linguagens de tipo estaticamente.

Links

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

Fontes

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

Classificação de seleção dupla

Classificação por seleção dupla – um subtipo de classificação por seleção, parece que deveria ser duas vezes mais rápido. O algoritmo vanilla faz um loop duplo pela lista de números, encontra o número mínimo e troca de lugar com o número atual apontado pelo loop no nível acima. A classificação por seleção dupla procura os números mínimo e máximo e, em seguida, substitui os dois dígitos apontados pelo loop no nível acima de – dois números à esquerda e à direita. Toda essa orgia termina quando os cursores dos números a serem substituídos são encontrados no meio da lista e, como resultado, os números ordenados são obtidos à esquerda e à direita do centro visual.
A complexidade de tempo do algoritmo é semelhante à classificação por seleção – O(n2), mas supostamente há uma aceleração de 30 %.

Estado limítrofe

Já nesta fase, você pode imaginar o momento de uma colisão, por exemplo, quando o número do cursor esquerdo (o número mínimo) aponta para o número máximo da lista, então o número mínimo é reorganizado, o rearranjo do número máximo quebra imediatamente. Portanto, todas as implementações do algoritmo contêm a verificação de tais casos e a substituição dos índices pelos corretos. Na minha implementação, uma verificação foi suficiente:

  maximalNumberIndex = minimalNumberIndex;
}

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

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

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

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

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

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

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

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

Links

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

Fontes

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/

Tipo de coqueteleira

Classificação de coqueteleira – classificação por shaker, uma variante da classificação por bolha bidirecional.
O algoritmo funciona da seguinte maneira:

  1. A direção inicial da pesquisa no loop é selecionada (geralmente da esquerda para a direita)
  2. A seguir no loop, os números são verificados em pares
  3. Se o próximo elemento for maior, eles serão trocados
  4. Ao terminar, o processo de busca recomeça com a direção invertida
  5. A busca é repetida até que não haja mais permutações

A complexidade de tempo do algoritmo é semelhante à bolha – O(n2).

Exemplo de implementação em 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