Motor Unreal no Macbook M2

Se você conseguiu executar o Unreal Engine 5 Editor em um Macbook com processador Apple, deve ter notado que isso é bastante lento.

Para aumentar o desempenho do editor e do mecanismo, defina Configurações de escalabilidade do mecanismo -> Médio. Depois disso, o mecanismo começará a desenhar tudo de maneira não tão bonita, mas você poderá trabalhar normalmente com o mecanismo do seu MacBook.

Corrigindo menu móvel no WordPress


document.addEventListener('DOMContentLoaded', function() {
    new navMenu('primary');
    new navMenu('woo');
});

Se você também não abre o menu do blog no iOS/Android há vários anos em seu blog WordPress ao usar o tema Seedlet, basta adicionar:
Fechando o arquivo wp-content/themes/seedlet/assets/js/primary-navigation.js na função, próximo à janela de assinatura padrão addEventListener ‘load’.

Radio-Maximum-Electron

Radio Maximum Electron é um aplicativo poderoso e conveniente projetado para ouvir o stream da estação de rádio Radio Maximum em seu computador com sistemas operacionais Windows, Linux e macOS. Este player combina facilidade de uso com alta funcionalidade, dando acesso à transmissão ao vivo com o mínimo de esforço.

Basta baixar o aplicativo no GitHub:

https://github.com/demensdeum/Radio-Maximum-Electron/releases

O autor não tem nada a ver com a Rádio Máxima, ele apenas gosta muito dessa rádio.
A principal funcionalidade é implementada pelo projeto Nativifier

https://github.com/nativefier/nativefier

Licença para scripts de construção do MIT, o tempo de execução tem sua própria licença!

Aventura Subaquática do Urso Polar

Um jogo simples com labirintos gerados infinitamente usando ThreeJS.

Criado como parte de um game jam de 3 dias “Start the Game” sobre o tema “Family Game”.

Um filhote de explorador polar estava caminhando no gelo com sua mãe quando de repente aconteceu um desastre – o gelo rachou e ele caiu nas águas geladas do oceano. Mamãe não teve tempo de salvá-lo e o urso acabou em uma misteriosa caverna subaquática. Para sua surpresa, ele descobriu que conseguia respirar debaixo d’água. Só há uma maneira de sair dessa armadilha – superando as profundezas do mar, resolvendo enigmas e lutando contra tubarões agressivos, que podem ser combatidos com arremessos certeiros de maçãs.

Agora seu objetivo é encontrar uma saída dessa armadilha subaquática e retornar para sua mãe, superando os perigos do fundo do mar e resolvendo enigmas.

https://demensdeum.com/demos/arctica/

Nixy Player

Nixy Player – Tempo de execução JavaScript pequeno, extensível e multiplataforma.

Multiplataforma: Disponível em Windows, macOS e Linux, bem como em qualquer outra plataforma com suporte para C++ e bibliotecas dinâmicas.
Leve: Consumo mínimo de recursos com desempenho eficiente.
Extensível: Projetado para ser facilmente estendido com plug-ins e bibliotecas adicionais.

Visite a página de lançamentos para se manter atualizado com os lançamentos e atualizações mais recentes:
https://github.com/demensdeum/NixyPlayer/releases/

Raiden Video Ripper

Raiden Video Ripper é um projeto de código aberto projetado para edição de vídeo e conversão de formato. Ele é construído usando Qt 6 (Qt Creator) e permite cortar e converter vídeos para os formatos MP4, GIF e WebM. Você também pode extrair áudio de vídeos e convertê-lo para o formato MP3.
Интерфейс RaidenVideoRipper

Foto da COSTA RICA EM 4K 60fps HDR (ULTRA HD)
https://www.youtube.com/watch?v=LXb3EKWsInQ
Visite a página de lançamentos para obter os lançamentos e atualizações mais recentes:
https://github.com/demensdeum/RaidenVideoRipper/releases

Donki Hills

Em um mês fiz uma piada engraçada, um jogo de paródia usando o Unreal Engine 5. O desenvolvimento foi realizado no stream do Twitch.

A história deste jogo fala sobre um russo comum, James, que encontrou uma garota, Maria, no Tinder, mas devido às sanções e ao encerramento do Tinder na Rússia, ele perdeu contato com ela. Agora a única coisa que lhe resta é uma captura de tela da foto dela, usando o Google Maps ele encontra o local onde a foto foi tirada – a vila de Tikhie Donki, perto de Novosibirsk. James vai até lá em busca de Maria…

https://demensdeum.itch.io/donki-hills

Robôs Defensores

Muitas vezes, durante discussões sobre o funcionamento correto de algum recurso de software, me deparo com uma situação em que a funcionalidade, do ponto de vista do usuário, parecia estranha e ilógica. A discussão com o proprietário do produto foi mais ou menos assim:

– Há claramente um problema de comportamento aqui
– Bom, vamos lançar e quando os usuários começarem a reclamar, então vamos consertar
– ??? Bem, ok…

Parece um esquema funcional, certo? Um algoritmo bastante ideal para equipes com orçamento pequeno, prazos apertados, pesquisa insuficiente/falta de um especialista em UI/UX. Os usuários reclamarão se algo acontecer, tudo bem.
Uma pesquisa no Google revela que a fonte deste método vem de um artigo – “Desenvolvimento Orientado a Reclamações” por Codificação de Terror

Uma vez eu vendia comida, inclusive linguiça médica, por 300 rublos. através de um terminal de um supermercado, saí da loja com esta salsicha com plena confiança de que ela havia sido paga – o terminal se ofereceu para não imprimir o cheque e eu concordei para não desperdiçar papel precioso nesse cheque. Durante o processo de “perfuração” da mercadoria de cada produto, o terminal emitia um guincho, que sinaliza que tudo funcionou corretamente. Além disso, com um alerta sonoro, o terminal piscou com a luz de fundo do leitor de código de barras.

No dia seguinte fui ao supermercado comprar mantimentos novamente e coloquei os mantimentos no terminal. Na saída fui recebido por um homem de aparência sulista e de barba espessa, segurando um smartphone, ele disse – “É você na câmera?”, olhei para o celular dele e me vi com uma camiseta Melodic-Death-Metal do Arch Enemy com caveiras e tudo mais, não havia motivo para duvidar.
“Sim, sou eu, qual é o problema?”, o homem, semicerrando os olhos, disse: “Ontem você não deu um soco na salsicha.” uau

Depois de uma breve investigação sobre quem ele era e como tirou essas conclusões, ele me mostrou um vídeo que está pendurado no teto da loja, no vídeo eu soco a salsicha, o terminal pisca com a luz de fundo do scanner, Coloquei a salsicha no saco.

– O vídeo mostra como o scanner funcionou
– Não deu para nada, paga a linguiça!

Um pouco surpreso com esta atitude, pedi um livro de reclamações para escrever que o terminal precisava de melhorias de software, pois dava todos os sinais de correto funcionamento, mas na realidade estava simplesmente bugado, sem sinalizar isso na tela de forma alguma.

Após 10 minutos de brigas com ele e seu chefe, que imediatamente correu em defesa de seu funcionário e do terminal de merda, eles decidiram ligar para a garota do administrador para que ela trouxesse um livro de reclamações e desse um soco no médico salsicha.

Naquele dia, percebi como é realmente difícil para os usuários reclamarem de produtos de hardware e software, e que muito provavelmente o mantra “as pessoas vão reclamar 𔂿 Vamos consertar” funciona muito mal. A principal razão são as pessoas que defendem robôs quebrados, soluções de software quebradas. Para simplificar, proponho introduzir novos termos – “O que é isso?” Defensor do Robô Quebrado e Defensor dos Sistemas Quebrados.

Os usuários comuns não podem reclamar do mau funcionamento dos terminais porque são incomodados pelos ZasRoshniks, que por algum motivo se apegam e passam a amar as máquinas com as quais trabalham, talvez considerando-as uma espécie de entidade animada, esquecendo que não há nada morando lá.

Uma situação semelhante ocorre com ZaSSoshniki, essas pessoas podem espumar pela boca em defesa de algumas falhas estúpidas em frameworks, linguagens de programação ou qualquer outro produto de software, apesar das reclamações de usuários e outros desenvolvedores.
Uma conversa típica com ZaSSoshnik é a seguinte:

– Aqui algo não funciona, segundo a documentação tudo parece estar correto
– Ah, então você não leu aquele manual de 2005, onde na parte inferior diz em letras minúsculas que você precisa adicionar PROGRAM_START:6969
– ??? ah

Essas pessoas podem não entender como elas próprias contribuem para a propagação de problemas, erros, perda de tempo e dinheiro, próprios e de outras pessoas. Por causa deles, todos sofrem, porque a transformação digital é impossível se coisas não óbvias e problemas com soluções de software e hardware forem abafados.
Estou ciente da recente história de um bug no software Horizon dos Correios Britânicos que levou as pessoas ao endividamento, arruinou casamentos e arruinou a vida das pessoas durante décadas. Tudo isso continuou devido à conivência de pessoas que se calaram sobre os problemas do software, “protegendo” ele.

Amigos, não sejam ZaSRoshniks e ZaSSoshniks, tratem as ferramentas com as quais vocês trabalham com cautela, caso contrário vocês enfrentarão a escravização total de sistemas quebrados e de merda, como reféns no novo mundo digital do futuro. Para quem não pode – pelo menos não incomode outras pessoas tentando prestar atenção a software/hardware que não funciona e interfere, porque os desenvolvedores desses produtos concordaram – com isso. “Quando os usuários começarem a reclamar, nós resolveremos o problema.”

Fontes
https://blog.codinghorror.com/complaint-driven-development/< /a>
https://habr.com/ru/articles/554404/< br/>
https://en.wikipedia.org/wiki/British_Post_Office_scandal

Construindo aplicativo bgfx Emscripten

Neste post descreverei uma maneira de construir aplicações bgfx para a web (WebAssembly) usando Emscripten.

A plataforma de instalação é Linux x86-64, por exemplo Arch Linux.

Primeiro, vamos instalar o Emscripten versão 3.1.51, caso contrário você não terá sucesso, tudo por causa da mudança no tipo de bibliotecas dinâmicas na versão mais recente do Emscripten. Você pode ler mais aqui:
https://github.com/bkaradzic/bgfx/discussions/3266

Isso é feito assim:


git clone https://github.com/emscripten-core/emsdk.git



cd emsdk



./emsdk install 3.1.51



./emsdk activate 3.1.51



source ./emsdk_env.sh



Vamos montar o bgfx para WebAssembly – Escrito:


mkdir bgfx-build-test



cd bgfx-build-test



git clone https://github.com/bkaradzic/bx.git



git clone https://github.com/bkaradzic/bimg.git



git clone https://github.com/bkaradzic/bgfx.git



cd bgfx



emmake make wasm-debug



Como resultado, na pasta .build você terá arquivos bitcode com extensão .bc, que precisarão ser vinculados ao seu aplicativo bgfx.
Deve ser bgfx.bc, bx.bc, bimg.bc; diferentes assemblies têm nomes diferentes para esses arquivos, dependendo do tipo de montagem (release/debug)

Adicione um link ao arquivo CMakeLists.txt com arquivos .bc, por exemplo, caminhos absolutos para arquivos do projeto bgfx-experiments:


target_link_libraries(${PROJECT_NAME} SDL2 GL /home/demensdeum_stream/Sources/bgfx-build/bgfx/.build/wasm/bin/bgfxDebug.bc /home/demensdeum_stream/Sources/bgfx-build/bgfx/.build/wasm/bin/bxDebug.bc /home/demensdeum_stream/Sources/bgfx-build/bgfx/.build/wasm/bin/bimgDebug.bc)



Agora altere o identificador da janela nativa nos dados da plataforma para inicialização do bgfx:


bgfx::PlatformData platformData{};



platformData.context = NULL;



platformData.backBuffer = NULL;



platformData.backBufferDS = NULL;



platformData.nwh = (void*)"#canvas";



Você também precisa alterar o tipo de renderização para OpenGL:


bgfx::Init init;



init.type = bgfx::RendererType::OpenGL;







init.resolution.width = screenWidth;



init.resolution.height = screenHeight;



init.resolution.reset = BGFX_RESET_VSYNC;



init.platformData = platformData;







if (!bgfx::init(init))



{



    throw std::runtime_error("Failed to initialize bgfx");



}



Recompilar shaders GLSL para 120:


shaderc -f "VertexShader.vs" -o "VertexShader.glsl" --type "v" -p "120"



shaderc -f "FragmentShader.fs" -o "FragmentShader.glsl" --type "f" -p "120"



Sim, mas os arquivos .glsl devem ser adicionados ao CMakeLists.txt como –preload-file:


set(CMAKE_CXX_FLAGS ... <Остальная часть>



--preload-file VertexShader.glsl \



--preload-file FragmentShader.glsl \



Resta substituir o loop de renderização principal em seu aplicativo por uma chamada de função while via emscripten_set_main_loop.

Você pode ler sobre isso aqui:
https ://demensdeum.com/blog/ru/2017/03/29/porting-sdl-c-game-to-html5-emscripten/

Em seguida, monte seu projeto Emscripten normalmente, tudo deve funcionar.
Interessante – a compilação do Emscripten 3.1.51 parece estar faltando OpenAL (ou apenas eu).

Código fonte do projeto que compila corretamente com bgfx e Emscripten:
https://github.com/demensdeum/ bgfx-experiments/tree/main/2-emscripten-build

Fontes

https://github.com/bkaradzic/bgfx/discussions/3266
https://bkaradzic.github.io/bgfx/build.html
https://emscripten.org/docs/getting_started/downloads.html
https ://demensdeum.com/blog/ru/2017/03/29/porting-sdl-c-game-to-html5-emscripten/
https://llvm.org/docs/BitCodeFormat.html

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

Jogos de demonstração Masons-DR

Masonry-AR é um jogo de realidade aumentada onde você precisa navegar pela cidade no mundo real e coletar conhecimento maçônico de livros, obtendo moeda e capturando território para sua ordem maçônica. O jogo não tem relação com nenhuma organização real, todas as partidas são aleatórias.

Demonstração do jogo:
https://demensdeum.com/demos/masonry-ar/client

Vicky:
https://demensdeum.com/masonry-ar-wiki-ru/

Código fonte:
https://github.com/demensdeum/Masonry-AR

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.

Mitsume ga Toru (NES) – Terceiro olho em Dandy

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

Mitsume ga Tooru (三つ目がとおる Mitsume ga tōru?, lit. “três olhos”) é um videogame de plataforma desenvolvido por Natsume em 1992 exclusivamente para o Nintendo Entertainment System. O jogo é baseado no mangá e anime Mitsume ga Tooru. Este é o segundo jogo baseado em anime desenvolvido pela Natsume; o anterior foi Mitsume ga Tooru: 3Lie-Mon, lançado para plataforma MSX dois anos antes. Na Rússia, o jogo é mais conhecido como “3 Eyes”, ou ” Terceiro olho”.

Number 2

Comrades, I take pride in projects that were created on the basis of Flame Steel Framework 1 and specifically on Flame Steel Engine 1, namely Death-Mask, Cube Art Project, since all this was conceived as a big experiment, creating a multimedia framework alone that can work on the most platforms. I think the experiment ended successfully immediately after the release of the Cube Art Project.

Now about the decisions that I came to during the development of new projects on FSFramework 1

During the development of Space Jaguar and the Space Jaguar Galaxy Bastards shooter, it became clear that the Flame Steel Framework tools were already outdated, not even having time to become at least somewhat convenient.

Therefore, I decided to develop a completely new Flame Steel Framework 2. The main decision will be to switch to my Rise 2 transpiler language, and the Component System (ECS) will no longer be used architecturally, because. it turned out to be needed only within the framework of game logic with great dynamics. For this reason, in Flame Steel Framework 2, the component system will only be possible while using the scripting languages ​​that are planned to be implemented (at least Lua and JavaScript), an interesting feature is that these languages ​​​​are dynamic in nature, so additional creation of the component system is redundant.

You can follow the development of new projects on the blog and on Gitlab:

https://gitlab.com/demensdeum/rise2

https://gitlab.com/demensdeum/flamesteelengine2

https://gitlab.com/demensdeum/flame-steel-engine-2-demo-projects

https://gitlab.com/demensdeum/space-jaguar-action-rpg

https://gitlab.com/demensdeum/space-jaguar-galaxy-bastards

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

FatBoySize – utilitário para exibir o tamanho de pastas e arquivos

FatBoySize – utilitário para exibir o tamanho de pastas e arquivos no terminal.
Funciona em qualquer sistema que suporte Python 3.

Execute: python3 fbs.py
Modo de saída 1: python3 fbs.py -v
Modo de saída 2: python3 fbs.py --version

Funciona apenas para o caminho atualmente aberto no terminal.

Exemplo de resultado:
python3 ~/Sources/my/fatboysize/fbs.py
.local -> 145 GB
Downloads -> 103.GB
.cache -> 37,0 GB
.android -> 11,6 GB
Fontes -> 8,63GB

Como você pode ver, a pasta Downloads é bemshmas grande

Links

https://gitlab.com/demensdeum/fatboysize/p>