Demonstração em vídeo da instalação e configuração do OldUnreal para Unreal Tournament 99 no macOS com CPU M1/M2/M3/M4
Você precisa de arquivos da versão Steam ou GoG do Unreal Tournament 99 para Windows!
Por que SECO é importante
Existem muitos artigos sobre o tema DRY, recomendo a leitura da fonte original “The Pragmatist Programmer” de Andy Hunt e Dave Thomas. No entanto, ainda vejo quantos desenvolvedores têm dúvidas sobre esse princípio no desenvolvimento de software.
O princípio DRY afirma que não devemos nos repetir, isso se aplica tanto ao código quanto aos processos que realizamos como programadores. Código de exemplo que viola DRY:
class Client {
public let name: String
private var messages: [String] = []
init(name: String) {
self.name = name
}
func receive(_ message: String) {
messages.append(message)
}
}
class ClientController {
func greet(client: Client?) {
guard let client else {
debugPrint("No client!")
return
}
client.receive("Hello \(client.name)!")
}
func goodbye(client: Client?) {
guard let client else {
debugPrint("No client!!")
return
}
client.receive("Bye \(client.name)!")
}
}
Como você pode ver nos métodos greet e adeus, uma instância opcional da classe Client é passada, que então precisa ser verificada quanto a zero, e então você pode começar a trabalhar com ela. Para cumprir o método DRY, você precisa remover a verificação nula duplicada da instância da classe. Isto pode ser implementado de várias maneiras; uma opção é passar a instância para o construtor da classe, após o que não haverá necessidade de verificações.
Mantemos o DRY especializando o ClientController em uma única instância do Client:
class Client {
public let name: String
private var messages: [String] = []
init(name: String) {
self.name = name
}
func receive(_ message: String) {
messages.append(message)
}
}
class ClientController {
private let client: Client
init(client: Client) {
self.client = client
}
func greet() {
client.receive("Hello \(client.name)!")
}
func goodbye() {
client.receive("Bye \(client.name)!")
}
}
DRY também diz respeito a processos que ocorrem durante o desenvolvimento de software. Vamos imaginar uma situação em que uma equipe de desenvolvimento tenha que carregar uma versão no mercado por conta própria, distraindo-a do desenvolvimento de software; isso também é uma violação do DRY; Essa situação é resolvida conectando-se um pipeline de CI/CD, no qual o release é liberado automaticamente, sujeito a determinadas condições atendidas pelos desenvolvedores.
Em geral, DRY trata da ausência de repetições tanto nos processos quanto no código, isso também é importante pela presença do fator humano: código que contém código menos repetitivo e barulhento é mais fácil de verificar se há erros; Os processos automatizados impossibilitam que as pessoas cometam erros ao executá-los, porque não há nenhum ser humano envolvido.
Steve Jobs tinha um ditado: “Uma linha de código que você nunca precisou escrever é uma linha de código que você nunca precisou depurar”.
Fontes
https://pragprog.com/titles/tpp20/the-pragmatic-programmer-20th-anniversary-edition/
https://youtu.be/-msIEOGvTYM
Eu vou te ajudar com desenvolvimento iOS para Swift ou Objective-C
Tenho o prazer de anunciar que agora ofereço meus serviços como desenvolvedor iOS no Fiverr. Se precisar de ajuda para desenvolver aplicativos iOS de qualidade ou melhorar projetos existentes, confira meu perfil:
https://www.fiverr.com/s/Q7x4kb6
Eu ficaria feliz em ter a oportunidade de trabalhar em seu projeto.
E-mail: demensdeum@gmail.com
Telegrama: https://t.me/demensdeum
Vinculação dinâmica de aplicativos Qt no macOS
Hoje lancei uma versão do RaidenVideoRipper para dispositivos Apple com macOS e processadores M1/M2/M3/M4 (Apple Silicon). RaidenVideoRipper é um aplicativo rápido de edição de vídeo que permite cortar parte de um arquivo de vídeo em um novo arquivo. Você também pode criar gifs e exportar a trilha de áudio para mp3.
A seguir, descreverei brevemente quais comandos usei para fazer isso. A teoria do que está acontecendo aqui, a documentação das concessionárias, pode ser lida nos seguintes links:
https://www.unix.com/man-page/osx/1/otool/
https://www.unix.com/man-page/osx/1/install_name_tool/
https://llvm.org/docs/CommandGuide/llvm-nm.html
https://linux.die.net/man/1/file
https://www.unix.com/man-page/osx/8/SPCTL/
https://linux.die.net/man/1/chmod
https://linux.die.net/man/1/ls
https://man7.org/linux/man-pages/man7/xattr.7.html
https://doc.qt.io/qt-6/macos-deployment.html
Primeiro, instale o Qt no seu macOS e também instale o ambiente de desenvolvimento Qt Desktop. Depois disso, construa seu projeto, por exemplo, no Qt Creator. Em seguida, descreverei o que é necessário para que as dependências com bibliotecas dinâmicas externas funcionem corretamente ao distribuir o aplicativo para usuários finais.
Crie um diretório Frameworks na pasta YOUR_APP.app/Contents do seu aplicativo e coloque dependências externas nele. Por exemplo, esta é a aparência dos Frameworks para o aplicativo RaidenVideoRipper:
Frameworks
├── DullahanFFmpeg.framework
│ ├── dullahan_ffmpeg.a
│ ├── libavcodec.60.dylib
│ ├── libavdevice.60.dylib
│ ├── libavfilter.9.dylib
│ ├── libavformat.60.dylib
│ ├── libavutil.58.dylib
│ ├── libpostproc.57.dylib
│ ├── libswresample.4.dylib
│ └── libswscale.7.dylib
├── QtCore.framework
│ ├── Headers -> Versions/Current/Headers
│ ├── QtCore -> Versions/Current/QtCore
│ ├── Resources -> Versions/Current/Resources
│ └── Versions
├── QtGui.framework
│ ├── Headers -> Versions/Current/Headers
│ ├── QtGui -> Versions/Current/QtGui
│ ├── Resources -> Versions/Current/Resources
│ └── Versions
├── QtMultimedia.framework
│ ├── Headers -> Versions/Current/Headers
│ ├── QtMultimedia -> Versions/Current/QtMultimedia
│ ├── Resources -> Versions/Current/Resources
│ └── Versions
├── QtMultimediaWidgets.framework
│ ├── Headers -> Versions/Current/Headers
│ ├── QtMultimediaWidgets -> Versions/Current/QtMultimediaWidgets
│ ├── Resources -> Versions/Current/Resources
│ └── Versions
├── QtNetwork.framework
│ ├── Headers -> Versions/Current/Headers
│ ├── QtNetwork -> Versions/Current/QtNetwork
│ ├── Resources -> Versions/Current/Resources
│ └── Versions
└── QtWidgets.framework
├── Headers -> Versions/Current/Headers
├── QtWidgets -> Versions/Current/QtWidgets
├── Resources -> Versions/Current/Resources
└── Versions
Para simplificar, imprimi apenas o segundo nível de aninhamento.
A seguir, imprimimos as dependências dinâmicas atuais da sua aplicação:
otool -L RaidenVideoRipper
Saída para o binário RaidenVideoRipper, localizado em RaidenVideoRipper.app/Contents/MacOS:
RaidenVideoRipper:
@rpath/DullahanFFmpeg.framework/dullahan_ffmpeg.a (compatibility version 0.0.0, current version 0.0.0)
@rpath/QtMultimediaWidgets.framework/Versions/A/QtMultimediaWidgets (compatibility version 6.0.0, current version 6.8.1)
@rpath/QtWidgets.framework/Versions/A/QtWidgets (compatibility version 6.0.0, current version 6.8.1)
@rpath/QtMultimedia.framework/Versions/A/QtMultimedia (compatibility version 6.0.0, current version 6.8.1)
@rpath/QtGui.framework/Versions/A/QtGui (compatibility version 6.0.0, current version 6.8.1)
/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 2575.20.19)
/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/Metal.framework/Versions/A/Metal (compatibility version 1.0.0, current version 367.4.0)
@rpath/QtNetwork.framework/Versions/A/QtNetwork (compatibility version 6.0.0, current version 6.8.1)
@rpath/QtCore.framework/Versions/A/QtCore (compatibility version 6.0.0, current version 6.8.1)
/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit (compatibility version 1.0.0, current version 275.0.0)
/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/UniformTypeIdentifiers.framework/Versions/A/UniformTypeIdentifiers (compatibility version 1.0.0, current version 709.0.0)
/System/Library/Frameworks/AGL.framework/Versions/A/AGL (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1800.101.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1351.0.0)
Como pode ser visto no RaidenVideoRipper nas dependências Qt e Dullahan_ffmpeg. Dullahan FFmpeg é um fork do FFmpeg que encapsula sua funcionalidade em uma biblioteca dinâmica, com a capacidade de obter o progresso e cancelamento da execução atual usando rotinas C.
Em seguida, substitua os caminhos do aplicativo e todas as bibliotecas necessárias usando install_name_tool.
O comando para isso é:
install_name_tool -change old_path new_path target
Exemplo de uso:
install_name_tool -change /usr/local/lib/libavfilter.9.dylib @rpath/DullahanFFmpeg.framework/libavfilter.9.dylib dullahan_ffmpeg.a
Depois de inserir todos os caminhos corretos, o aplicativo deverá iniciar corretamente. Verifique se todos os caminhos para bibliotecas são relativos, transfira o binário e abra-o novamente.
Se você encontrar algum erro, verifique os caminhos via otool e altere novamente via install_name_tool.
Também ocorre um erro com confusão de dependências, quando a biblioteca que você substituiu não possui um símbolo na tabela você pode verificar a presença ou ausência de um símbolo assim:
nm -gU path
Uma vez executado, você verá toda a tabela de símbolos da biblioteca ou aplicativo.
Também é possível que você tenha copiado as dependências da arquitetura errada, você pode verificar isso usando o arquivo:
file path
O utilitário de arquivo mostrará a qual arquitetura a biblioteca ou aplicativo pertence.
O Qt também requer uma pasta Plugins na pasta Contents do seu diretório YOUR_APP.app, copie os plugins do Qt para Contents. A seguir, verifique a funcionalidade do aplicativo, após o qual você poderá começar a otimizar a pasta Plugins, excluindo elementos desta pasta e testando o aplicativo.
Segurança do macOS
Depois de copiar todas as dependências e corrigir os caminhos para vinculação dinâmica, você precisará assinar o aplicativo com a assinatura do desenvolvedor e, adicionalmente, enviar a versão do aplicativo à Apple para reconhecimento de firma.
Se você não tem US$ 100 para uma licença de desenvolvedor ou não deseja assinar nada, escreva instruções para seus usuários sobre como iniciar o aplicativo.
Esta instrução também funciona para RaidenVideoRipper:
- Desativar o Gatekeeper: spctl –master-disable
- Permitir inicialização de qualquer fonte em Privacidade e segurança: permitir que aplicativos mudem para qualquer lugar
- Remova o sinalizador de quarentena após fazer download de um aplicativo zip ou dmg: xattr -d com.apple.quarantine app.dmg
- Verifique se o sinalizador de quarentena (com.apple.quarantine) está faltando: ls -l@ app.dmg
- Adicione confirmação para iniciar o aplicativo, se necessário, em Privacidade e segurança
Um erro com o sinalizador de quarentena geralmente é reproduzido pelo erro “O aplicativo está corrompido” que aparece na tela do usuário. Nesse caso, você precisa remover o sinalizador de quarentena dos metadados.
Link para construir o RaidenVideoRipper para Apple Silicon:
https://github.com/demensdeum/RaidenVideoRipper/releases/download/1.0.1.0/RaidenVideoRipper-1.0.1.0.dmg
Estabilização de vídeo usando ffmpeg
Se você deseja estabilizar vídeos e remover o tremor da câmera, a ferramenta `ffmpeg` oferece uma solução poderosa. Graças aos filtros integrados `vidstabdetect` e `vidstabtransform`, você pode obter resultados profissionais sem usar editores de vídeo complexos.
Preparando-se para o trabalho
Antes de começar, certifique-se de que seu `ffmpeg` suporta a biblioteca `vidstab`. No Linux você pode verificar isso com o comando:
bash
ffmpeg -filters | grep vidstab
Se a biblioteca não estiver instalada, você poderá adicioná-la:
sudo apt install ffmpeg libvidstab-dev
Instalação para macOS via brew:
brew install libvidstab
brew install ffmpeg
Agora vamos passar para o processo.
Etapa 1: análise de movimento
Primeiro você precisa analisar o movimento do vídeo e criar um arquivo com parâmetros de estabilização.
ffmpeg -i input.mp4 -vf vidstabdetect=shakiness=10:accuracy=15 transfile=transforms.trf -f null -
Parâmetros:
tremor: Nível de vibração do vídeo (padrão 5, pode ser aumentado para 10 para casos mais complexos).
precisão: Precisão da análise (padrão 15).
transfile: Nome do arquivo para salvar os parâmetros de movimento.
Passo 2: Aplicar Estabilização
Agora você pode aplicar a estabilização usando o arquivo de transformação:
ffmpeg -i input.mp4 -vf vidstabtransform=input=transforms.trf:zoom=5 output.mp4
Parâmetros:
input: Aponta para o arquivo com parâmetros de transformação (criados na primeira etapa).
zoom: Fator de zoom para remover bordas pretas (por exemplo, 5 – zoom automático até que os artefatos sejam removidos).
Análise automática de código com Bistr
Se você precisa analisar o código-fonte de um projeto, mas deseja automatizar o processo e utilizar a energia local do seu computador, o utilitário Bistr pode ser uma ótima solução. Neste artigo, veremos como esse utilitário ajuda a analisar código usando o modelo de aprendizado de máquina Ollama.

O que é Bistr?
Bistr é um utilitário de análise de código-fonte que permite integrar um modelo LLM (modelo de linguagem grande) local, como Ollama, para análise e processamento de código. Com o Bistr, você pode analisar arquivos em várias linguagens de programação, como Python, C, Java, JavaScript, HTML e muito mais.
Bistr usa o modelo para verificar arquivos em relação a determinadas consultas, por exemplo, para encontrar uma resposta a uma pergunta sobre a funcionalidade do código ou parte dele. Isso fornece uma análise estruturada que auxilia no desenvolvimento, teste e manutenção de projetos.
Como funciona o Bistr?
- Carregando Estado: Ao iniciar uma análise, o utilitário verifica se o estado da análise foi salvo anteriormente. Isso ajuda você a continuar de onde parou sem precisar analisar os mesmos arquivos novamente.
- Análise de código: cada arquivo é analisado usando o modelo Ollama. O utilitário envia uma solicitação ao modelo para analisar um trecho específico de código. O modelo retorna informações sobre a relevância do código em resposta à consulta e também fornece uma explicação textual de por que um determinado fragmento é relevante para a tarefa.
- Salvar estado: depois que cada arquivo é analisado, o estado é atualizado para que você possa continuar com as informações mais recentes na próxima vez.
- Saída dos resultados: Todos os resultados da análise podem ser exportados para um arquivo HTML, que contém uma tabela com uma classificação dos arquivos por relevância, o que ajuda a entender quais partes do código são mais importantes para análises posteriores.
Instalação e inicialização
Para usar o Bistr, você deve instalar e executar o Ollama, uma plataforma que fornece modelos LLM em sua máquina local. As instruções para instalar o Ollama para macOS, Windows e Linux estão descritas abaixo.
Baixe a versão mais recente do Bistr do git:
https://github.com/demensdeum/Bistr/
Depois de instalar o Ollama e o Bistr, você pode executar a análise de código. Para fazer isso, você precisa preparar o código-fonte e especificar o caminho para o diretório que contém os arquivos a serem analisados. O utilitário permite que você continue a análise de onde parou e também oferece a capacidade de exportar resultados em formato HTML para facilitar análises posteriores.
Exemplo de comando para executar análise:
python bistr.py /path/to/code --model llama3.1:latest --output-html result.html --research "What is the purpose of this function?"
Nesta equipe:
–model especifica o modelo a ser usado para análise.
–output-html especifica o caminho para salvar os resultados da análise em um arquivo HTML.
–research permite que você faça uma pergunta que deseja responder analisando o código.
Benefícios de usar o Bistr
- Execução local: a análise é realizada no seu computador sem a necessidade de conexão com serviços em nuvem, o que agiliza o processo.
- Flexibilidade: você pode analisar código em diferentes linguagens de programação.
- Automação: todo o trabalho de revisão de código é automatizado, o que economiza tempo e esforço, especialmente ao trabalhar com projetos grandes.
Redes neurais locais usando ollama
Se você deseja lançar algo como ChatGPT e tem um computador bastante poderoso, por exemplo com uma placa de vídeo Nvidia RTX, então você pode executar o projeto ollama, que permitirá que você use um dos modelos LLM prontos em sua máquina local, totalmente grátis. ollama oferece a capacidade de se comunicar com modelos LLM, na forma do ChatGPT também na versão mais recente, foi anunciada a capacidade de ler imagens e formatar os dados de saída no formato json;
Também executei o projeto em um MacBook com processador Apple M2 e sei que os modelos mais recentes de placas de vídeo da AMD são suportados.

Para instalar no macOS, acesse o site da ollama:
https://ollama.com/download/mac
Clique em “Baixar para macOS”, você baixará um arquivo no formato ollama-darwin.zip, dentro do arquivo estará Ollama.app que precisa ser copiado para “Aplicativos”. Depois disso, inicie o Ollama.app, provavelmente o processo de instalação ocorrerá na primeira vez que você iniciá-lo. Depois disso, na bandeja você viu o ícone ollama, a bandeja fica no canto superior direito ao lado do relógio.
Depois disso, inicie um terminal macOS normal e digite o comando para baixar, instalar e executar qualquer modelo ollama. Uma lista de modelos disponíveis, descrições e suas características podem ser conferidas no site da ollama:
https://ollama.com/search
Escolha o modelo com menos parâmetros caso ele não caiba na sua placa de vídeo no lançamento.
Por exemplo, comandos para executar o modelo llama3.1:latest:
ollama run llama3.1:latest
A instalação para Windows e Linux é geralmente semelhante, em um caso haverá um instalador ollama e trabalharemos posteriormente com ele através do Powershell.
Para Linux, a instalação é feita por meio de um script, mas recomendo usar a versão do seu gerenciador de pacotes específico. No Linux, ollama também pode ser iniciado através de um terminal bash normal.
Fontes
https://www.youtube.com/watch?v=Wjrdr0NU4Sk
https://ollama.com
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.

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.

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. p>
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
How to run Unreal Tournament 99 on MacBook M1
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:
- Penúltimo nó no canto inferior esquerdo – 3.
- Tem um ramo esquerdo – 1.
- Pegue este número (1)
- Em seguida, pegamos o vértice 3 (1, 3)
- À direita está o ramo 6, mas contém ramos. Portanto, lemos da mesma maneira.
- Ramo esquerdo do nó 6 número 4 (1, 3, 4)
- O próprio nó é 6 (1, 3, 4, 6)
- Direita 7 (1, 3, 4, 6, 7)
- Vá até o nó raiz – 8 (1,3, 4,6, 7, 8)
- Imprimimos tudo à direita por analogia
- 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:
- Montando uma árvore de pesquisa binária
- 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
Источники
Convert Sorted Array to Binary Search Tree (LeetCode 108. Algorithm Explained) – YouTube
Sorting algorithms/Tree sort on a linked list – Rosetta Code
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:
- Inicie um loop com o contador i até o número máximo de caracteres no número
- 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
- Converta o símbolo em um número
- Selecione um intervalo por número de índice e coloque o número inteiro lá
- Depois de terminar de pesquisar os números, converta todos os grupos novamente em uma lista de números
- Obter números classificados por classificação
- 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 a> 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:
- Criando uma pilha
- Algoritmo de peneiração (heapify)
- 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 p>
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/ a>
https://www.cs.usfca. edu/~galles/visualization/BST.html
https://www.youtube.com/watch?v=EQzqHWtsKq4
https://ru.wikibrief.org/wiki/Heapsort
https://www.youtube.com/watch?v=GUUpmrTnNbw