AI 资讯
Nine puzzle solvers, one browser tab, zero servers: a tour of classic search algorithms
I recently finished building a small suite of puzzle and game solvers that all run entirely in the browser — no backend, no API calls, no machine-learning models. You paste in a Sudoku, a chess position, or a crossword pattern, and the answer comes back instantly, computed on your own device. The fun part wasn't the UI. It was that each puzzle turned out to be a textbook excuse to reach for a different classic algorithm. Nine solvers, and I got to use constraint propagation, adversarial search, heuristic search, brute-force scanning, and plain old pattern matching — the stuff that shows up in an algorithms course and then, in most day jobs, never again. This is a tour of which algorithm fits which puzzle, and a few of the potholes I hit along the way. Everything here is vanilla JavaScript running in a Web Worker. The one design constraint: no server Before the algorithms, the rule that shaped all of them: it has to run client-side. That's a privacy choice (your puzzle never leaves the tab) and a cost choice (no compute bill), but it's also a fun forcing function. You can't lean on a beefy backend or a hosted model — you get one browser thread (well, a Worker thread) and whatever you can compute in a few hundred milliseconds. That budget is exactly why classic algorithms shine here. They're fast, deterministic, and small enough to ship as a script. Let's group the solvers by the technique each one leans on. Family 1: Constraint propagation Sudoku Sudoku is the poster child for constraint propagation. A cell that can only be one value forces that value; that in turn shrinks its neighbours' options, which forces more cells, and so on. Most "easy" and "medium" boards fall over from propagation alone (naked singles + hidden singles), and only the hard ones need a backtracking search on top. The nice property: the same engine that solves the board also powers the hint feature (find the next forced cell and explain why it's forced) and a uniqueness check — count solutions,
开发者
Alienware AW3926QW Review: 39 Inches of Gaming Glory
Alienware’s latest gaming monitor explores a new size and resolution for ultrawide monitors, and I have a feeling PC gamers are going to love it.
AI 资讯
MEU COMEÇO NA ÁREA DA TECNOLOGIA
Olá, comunidade dev.to! Meu nome é Neto, tenho 17 anos e sou estudante de Ciência da Computação no UNIPÊ, em João Pessoa. Atualmente, estou cursando o segundo semestre da graduação e também estudando design profissional, área que considero importante para a criação de soluções digitais mais úteis, intuitivas e visualmente agradáveis. Minha trajetória na tecnologia ainda está no começo, mas já tem sido marcada por descobertas, aprendizados e desafios. Escolhi Ciência da Computação porque sempre tive curiosidade sobre como aplicativos, sites e sistemas funcionam. Quero aprender não apenas a programar, mas também a compreender todo o processo de desenvolvimento de um produto, desde a identificação de um problema até a construção de uma solução. Durante o curso, tive a oportunidade de desenvolver, com alguns colegas, um projeto relacionado à criação de um aplicativo. Essa experiência foi importante porque me mostrou que desenvolver um produto vai muito além de escrever código. Foi necessário discutir ideias, organizar tarefas, pensar nas necessidades dos usuários e encontrar soluções para os problemas que surgiram durante o processo. Mesmo enfrentando desafios simples, percebi como cada obstáculo pode contribuir para o nosso crescimento. Em alguns momentos, precisamos revisar decisões, corrigir erros e adaptar o projeto. Também aprendemos que uma equipe precisa manter uma boa comunicação, pois cada integrante possui habilidades, responsabilidades e pontos de vista diferentes. O estudo de design profissional complementa minha formação em computação. Estou aprendendo que uma aplicação não deve apenas funcionar corretamente: ela também precisa oferecer uma boa experiência ao usuário. Elementos como cores, tipografia, organização das informações, acessibilidade e facilidade de navegação influenciam a maneira como as pessoas utilizam um produto. Ainda tenho muito a aprender sobre programação, design e desenvolvimento de projetos. Porém, entendo que a evolução acontece aos po
AI 资讯
My Nand2Tetris Journey #2 - Building Basic Chips And ALU
What I Built HalfAdder, FullAdder, Add16, Inc16, And ALU. How I Solved Like when I built logic gates, I started with analyzing truth table of HalfAdder , FullAdder . HalfAdder was really easy. After looking at the truth table, I could map the sum and carry outputs to logic gates pretty quickly. FullAdder was also not hard since it's really similar to HalfAdder except that it can add 3 bits. I realized that I could build it by combining some chips and logic gates I had already made instead of designing everything again from scratch. Once I finished building them, I was also able to build Add16 . At first, I had no idea how to sum all the 16 bits. But I soon realized that I could build a 16-bit adder by combining the smaller adders I had already built and passing carry information to the next bit. It looks not beautiful, but still works. And about Inc16 , it's basically add exactly 1(0000000000000001) . So I could easily build it using Add16 . (But I did something weird at first.. check the Reflection below) ALU was the core part of project 2. Once I realized that Mux can be used as if , I could make proper outputs using logic gates. ALU is also a combination of logic gates and chips, after all. What I Learned How to build basic chips using logic gates and already-built chips Why I should reuse the chips for another chip(check the Reflection section below) Mux can be used like if How to use bit slicing and fan-out in HDL and why it's important Reflection Before I started this part, I didn't know two things: I could use bit slicing and true , false for each bit. So when I first tried to build Inc16 , it looked really weird, since I calculated all the bits one by one. It's not logically wrong. But not beautiful either. I was not sure if it was right or not. Then I realized that I already built Add16 . But I had no idea how I could use it to add exactly 1(0000000000000001) . After googling, I realized that I could use bit slicing like Python's list slicing and construct
AI 资讯
Apple Mac Mini M6 and Mac Studio M5 Ultra: Specs, Price, Release Date
Apple’s Mac Mini and Mac Studio have been tough to buy for months, but updated versions have arrived. Both include new chips optimized for AI, with the M6 Mac Mini getting a $200 price bump.
AI 资讯
Leetcode 31: Next Permutation
Question : Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers. If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order). The replacement must be in-place and use only constant extra memory. Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column. Example : 1,2,3 → 1,3,2 3,2,1 → 1,2,3 1,1,5 → 1,5,1 Idea : Scan from right to left and find the first element that is less that its previous. eg: 1 6 3 5 -> here it is 3. Let's name it as index. Again scan from right to left and find the first element that is greater than 3 and that's 5. Let's mark it as idx. 3.In this step we swap 3 and 5. Reverse elements from index+1 till the array length. Code: public void nextPermutation(int[] nums) { int index = -1; for(int i=nums.length-1;i>0;i--){ if(nums[i]>nums[i-1]){ index = i-1; break; } } if(index==-1){ reverse(nums,0,nums.length-1); return; } int idx=0; for(int i=nums.length-1;i>=index+1;i--){ if(nums[i]>nums[index]){ idx=i; break; } } swap(nums,index,idx); reverse(nums,index+1,nums.length-1); } void swap(int[] nums,int i,int j){ int temp =nums[i]; nums[i] = nums[j]; nums[j] = temp; } void reverse(int[] nums,int i ,int j){ while(i<j){ swap(nums,i,j); i++; j--; } } Code Explanation : We first initialize index=-1 and traverse backward to find the first one with i that satisfy the condition nums[i]>nums[i-1] . We assign this to index and break out of the loop. for(int i=nums.length-1;i>0;i--){ if(nums[i]>nums[i-1]){ index = i-1; break; } } Next step we are discussing a corner case. For example if the given array is 3,2,1 then we cannot find the element that satisfies the previous condition. So when the array is given in decreasing order we just reverse it and return. if(index==-1){ reverse(nums,0,nums.length-1); return; } Next iteration we are considering another variable idx and traverse backw
科技前沿
Asus ROG Swift RGB Stripe OLED Review: Clarity King
The Asus PG27UCWM brings a new sub-pixel layout to the world of OLED gaming monitors, and in my testing, I appreciated the improvements it brings to the table.
科技前沿
The 6 Best Laptop Docking Stations to Unlock the Full Desktop Experience (2026)
Docking stations expand what your laptop can do, and I’ve been testing the best of the best to see which you should buy.
AI 资讯
LeetCode 3116 (Hard) — binary search + inclusion-exclusion makes it easy
Full walkthrough: https://www.youtube.com/watch?v=vFuFA3ByCs0 LeetCode 3116 — Kth Smallest Amount With Single Denomination Combination. Here’s the trick everyone misses: Brute force (generate all multiples, pick k-th) fails because k can reach 2×10⁹. The real approach: Binary search the answer X Count valid amounts ≤ X using inclusion-exclusion Odd subsets add, even subtract (bitmask over coins) LCM via GCD, break when LCM > X O(n · 2ⁿ · log(k·M)) — passes cleanly. The 26% acceptance rate makes this look harder than it is. Once you see the count(X) monotonic trick, it clicks.
AI 资讯
Part 1 — What Actually Happens When Code Runs
When we write: const result = add ( 10 , 20 ); it feels like the computer simply "runs the code." But the CPU doesn't understand JavaScript. There are several layers between the code we write and the hardware actually executing instructions. That's what I wanted to understand first. From JavaScript to the CPU In Node.js, JavaScript is handled by V8 , the JavaScript engine. A simplified view looks like this: JavaScript ↓ V8 ↓ Bytecode ↓ JIT compilation ↓ Machine instructions ↓ CPU V8 doesn't simply "interpret JavaScript" or "compile JavaScript" once and forget about it. It can start with bytecode and progressively compile frequently executed ("hot") code into more optimized machine code. Eventually, the CPU is executing instructions that operate at a much lower level than the JavaScript we originally wrote. What does the CPU actually do? At its core, a CPU repeatedly executes instructions. A simplified mental model is: Fetch → Decode → Execute → Repeat The CPU has several important pieces involved in this process. Registers are tiny, extremely fast storage locations inside the CPU. They're used to hold values the CPU is actively working with. The ALU (Arithmetic Logic Unit) performs many arithmetic and logical operations. The Program Counter (PC) keeps track of where the next instruction comes from. And the CPU runs according to a clock, measured in GHz. A 3 GHz CPU has roughly 3 billion clock cycles per second, but that does not mean it executes 3 billion instructions per second. Different instructions and architectures have different costs. Modern CPUs are far more sophisticated than this simplified model, using pipelining, multiple execution units, branch prediction, out-of-order execution, and more. But the basic model is enough to start reasoning about performance. The CPU doesn't get everything from RAM One of the most important things I learned here is that where data lives matters . A simplified hierarchy looks like: Registers ↓ L1 Cache ↓ L2 Cache ↓ L3 Cache
AI 资讯
Computer Fundamentals: No BS
I've been working with software for a few years now, and I've noticed something uncomfortable. I can build things. I can work with React, Node.js, databases, APIs, cloud services, and all the usual stuff that comes with being a software engineer. But if I stop and ask: "What is the computer actually doing underneath all of this?" My mental model gets surprisingly fuzzy. I know the concepts. I've used them. I've probably explained some of them before. But knowing how to use something and understanding what is actually happening underneath it are two very different things. And I want to fix that. Why I'm writing this This isn't a course, and I'm not writing this as an expert teaching computer science. These are essentially my notes while rebuilding my computer fundamentals from the ground up . I'm trying to connect the things I use every day as a software engineer with what is actually happening inside the machine. Instead of learning concepts because they're on a traditional CS syllabus, I'm starting with a question: What do I actually need to understand to reason about a production system? For me, that means being able to look at a system and understand what's happening underneath my code. Why is something slow? Where is the bottleneck? What happens when something fails? Why does adding more memory help in one situation but not another? What actually happens when two things execute concurrently? Why does a database query become slow? What happens to a simple HTTP request between two machines? I don't want to just know the answer. I want the mental model that lets me reason about the answer . The path I'm taking I'm roughly following the layers that a typical request passes through: CPU → Memory → OS → Network → Storage → Concurrency → Distributed Systems So the series will go through: 1. What Actually Happens When Code Runs Starting from the bottom: CPU mental model, memory hierarchy, and number representation . 2. Operating Systems Then moving up into processes, th
科技前沿
Dell XPS 13 vs. MacBook Neo: A Surprising Upset
The Dell XPS 13 and MacBook Neo are both premium-feeling laptops with only 8 GB of RAM. Which $700 laptop is the better buy?
开发者
7 Best Cheap Laptops to Buy in 2026 (and Some to Avoid)
From surprisingly good $300 Chromebooks to excellent $650 Windows notebooks and more, these are the best budget laptops I’ve tested.
AI 资讯
How Garbage Collection Works: Let's Build One From Scratch
Introduction Your program keeps creating objects. Every function call, every loop iteration, every parsed JSON response produces new ones. You don't manually delete most of them. You've never written a line of code that says "free this memory now." And yet your application doesn't immediately exhaust all available RAM and crash. So who cleans everything up? The answer is a garbage collector, a piece of the runtime that runs quietly in the background, deciding what your program no longer needs and reclaiming that memory for future use. Most developers interact with it only when something goes wrong: an unexpected pause, a memory leak, or an out-of-memory error that shouldn't be happening. Understanding how it actually works turns those confusing moments into solvable problems. And as a bonus, the core algorithm is simple enough to build yourself. We'll do that by the end of this article. -- 1. The Memory Problem Every time your program creates an object, the runtime allocates a chunk of memory to hold it. A string, a dictionary, a class instance: they all need memory, and that memory has to come from somewhere. The somewhere is a region called the heap , a pool of memory that the program draws from as it runs. When you create an object, the runtime finds a suitable slot in the heap and reserves it. When that object is no longer needed, that slot should be freed so it can be used for something else. In languages like C, you manage this manually. You allocate memory when you need it, and you free it when you're done. This gives you control, but it creates two classic failure modes. Free memory too early and you have a dangling pointer, a reference to memory that's now being used for something else. Forget to free it at all and you have a memory leak: the program slowly consumes more and more memory until it runs out. Automatic memory management exists to eliminate these failure modes. Instead of relying on the programmer to track every allocation and release, the runti
AI 资讯
Lean 创始人访谈全记录:当形式化验证遇上 AI,手写数学与软件验证将如何被重塑
https://www.youtube.com/watch?v=KzdYKeAqWhY 题目:《Lean 创始人访谈全记录:当形式化验证遇上 AI,手写数学与软件验证将如何被重塑》 第(一)部分 开场与核心命题:从“测试只能证明有 bug”到“证明可确保无 bug” (0% - 8%) Dijkstra 名言引出形式化验证的根本价值:主持人以 Dijkstra 的名言“程序测试可用于揭示 bug 的存在,但永远无法证明 bug 的不存在”开场,指出 Lean 与形式化证明的意义恰恰在于“证明 bug 不可能发生”。 Lean 的基础定位:Lean 既是一门编程语言(可以写代码),也是一个证明系统(可以对代码写性质并用机器可检查的证明来验证)。它提供绝对正确的保证,并拥有多个独立的检查器。 Lean 应被视为平台:用户可以在 Lean 上写代码、写关于代码的性质命题、并给出证明;本期节目将围绕它如何工作、以及它如何改变数学和软件验证的未来展开,并提出“手写数学是否会终结”这一核心疑问。 第(二)部分 Lean 是什么:编程语言与证明助手的一体两面 (8% - 18%) Lean 的双重身份:Lean 不仅可用于数学证明,也可用于软件验证。基于依赖类型论(Dependent Type Theory)的一族证明助手(如 Rocq/Coq 和 Lean)天然就是“编程语言 + 证明助手”。 软件验证的两种主流路径: • 浅嵌入(Shallow Embedding):通过工具(如把 Rust 翻译到 Lean 的工具)把其他语言映射到 Lean 中进行验证。 • 深嵌入/语义建模:在 Lean 中为 C 语言等编写语义,把 C 程序表示为 Lean 中的数据结构,从而对其陈述性质并进行推理。 具体例子——数组越界验证:以 C 语言访问数组为例,可在 Lean 中把“索引 i 满足 0 ≤ i < 10”写成数学命题;原来的 C 源文件可对应一份“元数据式”的 Lean 证明,由 Lean 逐行检查。 自动化与可维护性:人们会建立自动化框架(如基于前置条件-语句-后置条件的三元组),把证明过程变得更易管理;复杂度是软件验证的大敌,而 AI 的出现让“自动证明”成为可能,但前提是把证明写得模块化以便扩展。 第(三)部分 从“测试套件”到“形式化规格”:为什么规格优于测试 (18% - 28%) 测试 vs. 证明的本质差异:测试套件再全面,也只覆盖了有限场景,角落案例仍可能遗漏;而形式化证明覆盖所有可能情况,真正做到了“bug 的不存在”。 Zlib 压缩库的震撼案例:主持人的同事 Kim Morrison 发起项目,让 AI 把 C 写的 Zlib 压缩库翻译进 Lean,要求通过原测试套件,并证明“压缩后再解压得到原始数据”这一强性质。结果仅用一周就完成了整个形式化,目前只需再做性能优化,且优化不能破坏既有证明。 规格说明(Specification)的成本讨论:写出一份好的规格,工作量因程序而异。一个实用技巧是:先用“低效但正确”的实现作为规格(Spec),再让 AI 生成高效版本并证明其与规格等价。 Jane Street 与工业界实践:Jane Street 等公司已在投资形式化验证,例如对微内核 seL4 的完整验证。过去这类工作在没有 AI 时“手动证明 + 维护证明”的成本极高(往往是写程序本身的 10 倍),而 AI 正在消除这种痛苦——AI 非常擅长撰写和维护形式化证明,即使人已经忘了当初为何这么证。 第(四)部分 Lean 作为编程语言的工程实践与工具链 (28% - 36%) 不仅是证明助手,更是生产级编程语言:AWS 内部有一个约 50 万行 Lean 写的 AI 加速器编译器,主要把 Lean 当编程语言用,顺带获得一些性质证明作为“额外红利”。 工具链体验接近现代语言:构建系统 Lake 相当于 Rust 的 Cargo;编辑器用 VS Code,提供 IntelliSense 等熟悉体验。 Info View——Lean 独有的核心交互界面:屏幕通常一分为二,左侧是代码/证明文件,右侧 Info View 实时显示当前证明目标的状态变化,给用户持续反馈。 Tactic 模式:把证明当成“游戏”:用户通过 by 进入领域特定语言(DSL)来写证明,每一步可简化目标、应用已知引理等,看着目标逐步减少直到归零,过程极具“通关”快感,不少用户戏称自己“沉迷其中”。 第(五)部分 内核信任问题:Lean 自身是否被 Lean 验证? (36% - 42%) 只需信任极小的内核:Lean 整体庞大且规格频繁变动(如简化器的行为不断被用户定制),难以对全部进行形式化;但证明检查的核心——“内核”是可以被规格化的。 多内核策
科技前沿
Best Computer Monitors (2026): The Home Office Upgrade You Need
My expert advice on what computer monitor to buy for your home office, ranging from budget-tier to fully featured.
AI 资讯
Dell XPS 13 Review: Move Over, Neo
It’s amazing how few compromises Dell made to get the new XPS 13 down to $700, but I strongly recommend the $900 model, which comes with 16 GB of RAM.
AI 资讯
What Building a C++ Benchmarking Suite Taught Me About "Simple" Data Structures
We all know the Big-O complexity of basic data structures. Arrays are O(n) for search. Hash maps are O(1). Linked lists are... well, complicated. But when I set out to build hashbrowns — a C++17 benchmarking suite comparing arrays, linked lists, and hash maps — I discovered that theory and practice are very different beasts. Here's what I learned building this project from scratch, and why you should probably benchmark before you optimize. 🎯 The Goal Was Simple (Ha!) I wanted a clean, educational project that would: Implement dynamic arrays, linked lists, and hash maps from scratch Benchmark insert, search, and remove operations Find the "crossover points" where one structure beats another Export everything to CSV for analysis Sounds straightforward, right? Four months later, I had written a custom memory tracker, implemented multiple hash map strategies, added statistical bootstrapping for confidence intervals, and learned more about CPU caches than I ever wanted to know. 📚 Lesson 1: Polymorphism Has a Price (But It's Worth It) My first architectural decision was creating a common DataStructure interface: class DataStructure { public: virtual void insert ( int key , const std :: string & value ) = 0 ; virtual bool search ( int key , std :: string & value ) const = 0 ; virtual bool remove ( int key ) = 0 ; virtual size_t memory_usage () const = 0 ; virtual std :: string type_name () const = 0 ; // ... }; This made benchmarking elegant — I could write generic code that tested any data structure: for ( auto & structure : structures ) { timer . start (); structure -> insert ( key , value ); timer . stop (); } But virtual function calls have overhead. In tight loops, that vtable lookup adds up. I spent a whole weekend convinced my hash map was slower than expected... until I realized I was measuring the cost of polymorphism, not the data structure itself. The fix? I kept the clean interface for the benchmarking harness but used templates internally where performance-cri
开发者
Paradigma de Programação Orientada a Objetos (POO)
Introdução A Programação Orientada a Objetos nasceu com a linguagem Simula 67 , considerada a primeira linguagem orientada a objetos, e foi consolidada e popularizada por Smalltalk nos anos 1970. Ganhou adoção massiva na indústria com C++ e, posteriormente, Java e C#. A ideia central é organizar o código em torno de objetos : unidades que combinam dados (atributos/estado) e comportamento (métodos) em uma única estrutura. Os quatro pilares Encapsulamento — os dados internos de um objeto são protegidos e só podem ser acessados/alterados através de métodos expostos, escondendo detalhes de implementação do mundo externo. Abstração — o objeto expõe apenas o que é relevante para quem o utiliza, escondendo a complexidade interna (ex.: você chama CalcularSalario() sem precisar saber como o cálculo é feito por dentro). Herança — uma classe pode herdar atributos e métodos de outra, permitindo reaproveitamento e especialização (uma classe Desenvolvedor pode herdar de Funcionario ). Polimorfismo — objetos de classes diferentes podem responder de forma diferente ao mesmo "chamado" (o mesmo método CalcularSalario() se comporta de forma distinta para um Desenvolvedor e para um Gerente , por exemplo). Exemplo // Exemplo de Programação Orientada a Objetos em C# public abstract class Funcionario { public string Nome { get ; } protected decimal SalarioBase { get ; } protected Funcionario ( string nome , decimal salarioBase ) { Nome = nome ; SalarioBase = salarioBase ; } // Abstração: cada subclasse decide como calcular seu próprio salário public abstract decimal CalcularSalario (); } public class Desenvolvedor : Funcionario { private int BonusPorProjeto { get ; } public Desenvolvedor ( string nome , decimal salarioBase , int bonusPorProjeto ) : base ( nome , salarioBase ) // Herança { BonusPorProjeto = bonusPorProjeto ; } // Polimorfismo: implementação específica do método herdado public override decimal CalcularSalario () { return SalarioBase + BonusPorProjeto ; } } public class Gere
AI 资讯
Programação Funcional
Introdução A Programação Funcional tem raízes no cálculo lambda , formalizado pelo matemático Alonzo Church nos anos 1930, décadas antes da existência de computadores modernos. Sua primeira grande expressão em linguagem de programação foi o Lisp (1958), e o paradigma ganhou força prática com linguagens como Haskell, Erlang, F# e, mais recentemente, com a incorporação de recursos funcionais em linguagens multiparadigma como JavaScript, C# e Python. Princípios centrais Funções puras — dado o mesmo input, uma função pura sempre retorna o mesmo output, sem produzir efeitos colaterais (não altera variáveis externas, não grava em disco, não modifica o argumento recebido). Imutabilidade — os dados não são alterados após criados; em vez de modificar uma estrutura existente, cria-se uma nova versão com a alteração aplicada. Funções de primeira classe / funções de alta ordem — funções podem ser tratadas como qualquer outro valor: armazenadas em variáveis, passadas como argumento e retornadas por outras funções. Composição de funções — programas são construídos combinando funções pequenas em pipelines ( map , filter , reduce são os exemplos mais comuns no dia a dia). Recursão no lugar de laços mutáveis, já que, sem estado mutável, for / while tradicionais perdem o sentido em sua forma pura. Como não há estado compartilhado sendo alterado por múltiplas partes do código, o raciocínio sobre o comportamento do programa fica mais previsível — e a paralelização se torna muito mais segura, já que não existe o risco clássico de condições de corrida sobre uma mesma variável mutável. Exemplo // Exemplo de Programação Funcional em TypeScript type Produto = { nome : string ; preco : number }; const produtos : Produto [] = [ { nome : "Notebook" , preco : 3000 }, { nome : "Mouse" , preco : 50 }, { nome : "Teclado" , preco : 150 }, ]; // Função de alta ordem que retorna outra função (currying) const aplicarDesconto = ( percentual : number ) => ( preco : number ) => preco * ( 1 - percentual )