<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ro">
  <generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator>
  <link href="https://nuculabs.dev/ro/feed.xml" rel="self" type="application/atom+xml" />
  <link href="https://nuculabs.dev/ro/" rel="alternate" type="text/html" hreflang="ro" />
  <updated>2026-11-10T21:30:17+02:00</updated>
  <id>https://nuculabs.dev/ro/feed.xml</id>
  <title type="html">NucuLabs Blog</title>
  <subtitle>Un blog despre programare, cloud și inginerie.</subtitle>
  <author>
    <name>Denis</name>
  </author>
  <entry xml:lang="ro">
    <title type="html">Construirea unui pipeline RAG</title>
    <link href="https://nuculabs.dev/ro/python/projects/artificial%20intelligence/2026/11/08/building-a-rag-pipeline.html" rel="alternate" type="text/html" title="Construirea unui pipeline RAG" />
    <published>2026-11-08T00:00:00+02:00</published>
    <updated>2026-11-08T00:00:00+02:00</updated>
    <id>https://nuculabs.dev/ro/python/projects/artificial%20intelligence/2026/11/08/building-a-rag-pipeline</id>
    <content type="html" xml:base="https://nuculabs.dev/ro/python/projects/artificial%20intelligence/2026/11/08/building-a-rag-pipeline.html"><![CDATA[<h1 id="introducere">Introducere</h1>

<p>Salut tuturor! 👋</p>

<p>Am vrut să scriu acest articol ca să arăt cum se construiește un pipeline RAG (Retrieval Augmented Generation, adică generare augmentată prin regăsire de informații). Un motiv pentru care ai dori să îl construiești este să activezi căutarea după intenție pentru afacerea sau proiectul tău.</p>

<!--more-->

<p>Uneori RAG funcționează cel mai bine împreună cu căutarea lexicală, metoda tradițională de căutare în care se construiește un index inversat, iar rezultatele sunt ordonate cu un algoritm precum BM25+.</p>

<p>Principalul avantaj al RAG este că agenții pot căuta în cantități mari de date după intenție, dar și oamenii pot. Îl poți folosi și fără agenți.</p>

<p class="post-figure"><img src="/assets/images/2026/rag-pipeline/intent_rag_vs_lexical_search.png" alt="Diagrama RAG" /></p>

<h1 id="proiectul">Proiectul</h1>

<p>Ca să mă joc cu asta, am vrut să indexez o carte, și am ales <a href="https://www.gutenberg.org/ebooks/28054">Frații Karamazov de Fiodor Dostoievski</a>, iar spre rușinea mea nu am citit-o.</p>

<p>Din fericire, am găsit-o pe Project Gutenberg și putem folosi <a href="https://www.gutenberg.org/cache/epub/28054/pg28054.txt">textul</a> gratuit.</p>

<p>Poți descărca textul de pe site și îl poți salva ca fișier. Am mai făcut și o curățare suplimentară: am șters din date introducerea, încheierea și cuprinsul.</p>

<p>În funcție de modelul folosit pentru generarea embedding-urilor, dacă este un model multilingv, utilizatorii pot pune întrebări despre date în practic orice limbă.</p>

<h2 id="pașii">Pașii</h2>

<p>Ca să implementăm pipeline-ul RAG vom parcurge următorii pași:</p>

<p class="post-figure"><img src="/assets/images/2026/rag-pipeline/dostoyevsky_rag_pipeline_horizontal.png" alt="Pipeline RAG" /></p>

<p>Pe scurt, pașii sunt:</p>
<ol>
  <li>Vom genera embedding-urile folosind o bibliotecă Python open source, FlagEmbedding, și un model numit “BAAI/bge-m3”.</li>
  <li>Vom normaliza vectorii folosind norma L2.</li>
  <li>Vom reduce dimensiunea de la 1024 la 256. Acesta este un compromis între acuratețe și viteză.</li>
  <li>Normalizăm din nou.</li>
  <li>Stocăm embedding-urile finale într-o bază de date vectorială locală: chromadb.</li>
</ol>

<p>Când interogăm indexul RAG, avem nevoie de același model pentru a genera embedding-ul interogării utilizatorului și de fișierul PCA pentru a reduce dimensiunea embedding-ului generat la 256. Apoi cerem chromadb cei mai apropiați vecini pentru interogarea utilizatorului.</p>

<h2 id="generarea-embedding-urilor">Generarea embedding-urilor</h2>

<p>Am folosit Python 3.14 și următoarele dependențe:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line">uv add FlagEmbedding</span><span class="code-line">uv add scikit-learn</span><span class="code-line">uv add chromadb</span></code></pre></div></div>

<p>Următorul fragment de cod Python construiește pipeline-ul descris mai sus.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span></span><span class="code-line">    <span class="n">paragraphs</span> <span class="o">=</span> <span class="nf">load_paragraphs</span><span class="p">(</span><span class="n">SOURCE_TEXT</span><span class="p">)</span></span><span class="code-line">    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Loaded </span><span class="si">{</span><span class="nf">len</span><span class="p">(</span><span class="n">paragraphs</span><span class="p">)</span><span class="si">}</span><span class="s"> paragraphs from </span><span class="si">{</span><span class="n">SOURCE_TEXT</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line">    <span class="n">model</span> <span class="o">=</span> <span class="nc">BGEM3FlagModel</span><span class="p">(</span><span class="n">EMBEDDING_MODEL</span><span class="p">,</span> <span class="n">normalize_embeddings</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">use_fp16</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span></span><span class="code-line">    <span class="n">output</span> <span class="o">=</span> <span class="n">model</span><span class="p">.</span><span class="nf">encode_corpus</span><span class="p">(</span><span class="n">paragraphs</span><span class="p">,</span> <span class="n">batch_size</span><span class="o">=</span><span class="mi">32</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="n">MAX_LENGTH</span><span class="p">)</span></span><span class="code-line">    <span class="n">embeddings</span> <span class="o">=</span> <span class="nf">l2_normalize</span><span class="p">(</span><span class="n">output</span><span class="p">[</span><span class="sh">"</span><span class="s">dense_vecs</span><span class="sh">"</span><span class="p">])</span></span><span class="code-line">    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Embeddings shape: </span><span class="si">{</span><span class="n">embeddings</span><span class="p">.</span><span class="n">shape</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line">    <span class="n">pca</span> <span class="o">=</span> <span class="nc">PCA</span><span class="p">(</span><span class="n">n_components</span><span class="o">=</span><span class="n">PCA_COMPONENTS</span><span class="p">)</span></span><span class="code-line">    <span class="n">embeddings</span> <span class="o">=</span> <span class="nf">l2_normalize</span><span class="p">(</span><span class="n">pca</span><span class="p">.</span><span class="nf">fit_transform</span><span class="p">(</span><span class="n">embeddings</span><span class="p">))</span></span><span class="code-line"></span><span class="code-line">    <span class="n">norms</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">linalg</span><span class="p">.</span><span class="nf">norm</span><span class="p">(</span><span class="n">embeddings</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span></span><span class="code-line">    <span class="nf">print</span><span class="p">(</span></span><span class="code-line">        <span class="sa">f</span><span class="sh">"</span><span class="s">After PCA: shape </span><span class="si">{</span><span class="n">embeddings</span><span class="p">.</span><span class="n">shape</span><span class="si">}</span><span class="s">, </span><span class="sh">"</span></span><span class="code-line">        <span class="sa">f</span><span class="sh">"</span><span class="s">explained variance </span><span class="si">{</span><span class="n">pca</span><span class="p">.</span><span class="n">explained_variance_ratio_</span><span class="p">.</span><span class="nf">sum</span><span class="p">()</span><span class="si">:</span><span class="p">.</span><span class="mi">1</span><span class="o">%</span><span class="si">}</span><span class="s">, </span><span class="sh">"</span></span><span class="code-line">        <span class="sa">f</span><span class="sh">"</span><span class="s">L2 norms in [</span><span class="si">{</span><span class="n">norms</span><span class="p">.</span><span class="nf">min</span><span class="p">()</span><span class="si">:</span><span class="p">.</span><span class="mi">6</span><span class="n">f</span><span class="si">}</span><span class="s">, </span><span class="si">{</span><span class="n">norms</span><span class="p">.</span><span class="nf">max</span><span class="p">()</span><span class="si">:</span><span class="p">.</span><span class="mi">6</span><span class="n">f</span><span class="si">}</span><span class="s">]</span><span class="sh">"</span></span><span class="code-line">    <span class="p">)</span></span><span class="code-line"></span><span class="code-line">    <span class="n">np</span><span class="p">.</span><span class="nf">savez</span><span class="p">(</span><span class="n">PCA_FILE</span><span class="p">,</span> <span class="n">mean</span><span class="o">=</span><span class="n">pca</span><span class="p">.</span><span class="n">mean_</span><span class="p">,</span> <span class="n">components</span><span class="o">=</span><span class="n">pca</span><span class="p">.</span><span class="n">components_</span><span class="p">)</span></span><span class="code-line">    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Saved PCA projection to </span><span class="si">{</span><span class="n">PCA_FILE</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line">    <span class="c1"># Reconstruim colecția la fiecare rulare, ca să corespundă mereu cu PCA salvat mai sus.</span></span><span class="code-line">    <span class="n">client</span> <span class="o">=</span> <span class="n">chromadb</span><span class="p">.</span><span class="nc">PersistentClient</span><span class="p">(</span><span class="n">path</span><span class="o">=</span><span class="nf">str</span><span class="p">(</span><span class="n">CHROMA_PATH</span><span class="p">))</span></span><span class="code-line">    <span class="k">if</span> <span class="n">COLLECTION_NAME</span> <span class="ow">in</span> <span class="p">[</span><span class="n">collection</span><span class="p">.</span><span class="n">name</span> <span class="k">for</span> <span class="n">collection</span> <span class="ow">in</span> <span class="n">client</span><span class="p">.</span><span class="nf">list_collections</span><span class="p">()]:</span></span><span class="code-line">        <span class="n">client</span><span class="p">.</span><span class="nf">delete_collection</span><span class="p">(</span><span class="n">COLLECTION_NAME</span><span class="p">)</span></span><span class="code-line">    <span class="c1"># Vectorii provin de la bge-m3 + PCA, deci Chroma nu trebuie să genereze nimic singură.</span></span><span class="code-line">    <span class="n">collection</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="nf">create_collection</span><span class="p">(</span></span><span class="code-line">        <span class="n">COLLECTION_NAME</span><span class="p">,</span> <span class="n">configuration</span><span class="o">=</span><span class="p">{</span><span class="sh">"</span><span class="s">hnsw</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span><span class="sh">"</span><span class="s">space</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">cosine</span><span class="sh">"</span><span class="p">}},</span> <span class="n">embedding_function</span><span class="o">=</span><span class="bp">None</span></span><span class="code-line">    <span class="p">)</span></span><span class="code-line"></span><span class="code-line">    <span class="n">batch_size</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="nf">get_max_batch_size</span><span class="p">()</span></span><span class="code-line">    <span class="k">for</span> <span class="n">start</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">paragraphs</span><span class="p">),</span> <span class="n">batch_size</span><span class="p">):</span></span><span class="code-line">        <span class="n">indices</span> <span class="o">=</span> <span class="nf">range</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="nf">min</span><span class="p">(</span><span class="n">start</span> <span class="o">+</span> <span class="n">batch_size</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">paragraphs</span><span class="p">)))</span></span><span class="code-line">        <span class="n">collection</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span></span><span class="code-line">            <span class="n">ids</span><span class="o">=</span><span class="p">[</span><span class="sa">f</span><span class="sh">"</span><span class="s">paragraph-</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="sh">"</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">indices</span><span class="p">],</span></span><span class="code-line">            <span class="n">embeddings</span><span class="o">=</span><span class="n">embeddings</span><span class="p">[</span><span class="n">indices</span><span class="p">.</span><span class="n">start</span> <span class="p">:</span> <span class="n">indices</span><span class="p">.</span><span class="n">stop</span><span class="p">],</span></span><span class="code-line">            <span class="n">documents</span><span class="o">=</span><span class="n">paragraphs</span><span class="p">[</span><span class="n">indices</span><span class="p">.</span><span class="n">start</span> <span class="p">:</span> <span class="n">indices</span><span class="p">.</span><span class="n">stop</span><span class="p">],</span></span><span class="code-line">            <span class="n">metadatas</span><span class="o">=</span><span class="p">[{</span><span class="sh">"</span><span class="s">paragraph</span><span class="sh">"</span><span class="p">:</span> <span class="n">i</span><span class="p">}</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">indices</span><span class="p">],</span></span><span class="code-line">        <span class="p">)</span></span><span class="code-line">    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Stored </span><span class="si">{</span><span class="n">collection</span><span class="p">.</span><span class="nf">count</span><span class="p">()</span><span class="si">}</span><span class="s"> paragraphs in collection </span><span class="si">{</span><span class="n">COLLECTION_NAME</span><span class="si">!r}</span><span class="s"> at </span><span class="si">{</span><span class="n">CHROMA_PATH</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span></span></code></pre></div></div>

<p>Rularea codului va scrie toate embedding-urile într-o bază de date vectorială locală, încorporată, chromadb, și va salva fișierul PCA pe disc. Am aplicat PCA asupra embedding-urilor ca să facem un compromis între acuratețe și viteză. Procesarea unui vector de 1024 de elemente necesită mai mult calcul, așa că am aplicat PCA ca să reducem dimensiunea la 256 de elemente.</p>

<h2 id="interogarea-embedding-urilor">Interogarea embedding-urilor</h2>

<p>Utilizatorul poate interoga embedding-urile printr-un MCP, un API web sau o interfață CLI simplă. Deocamdată folosim varianta CLI, pentru că avem baza de date locală și fișierul PCA, de care avem nevoie ca să construim vectorul de interogare.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line"><span class="k">def</span> <span class="nf">main</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span></span><span class="code-line">    <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="p">.</span><span class="nc">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="sh">"</span><span class="s">Search The Brothers Karamazov by meaning.</span><span class="sh">"</span><span class="p">)</span></span><span class="code-line">    <span class="n">parser</span><span class="p">.</span><span class="nf">add_argument</span><span class="p">(</span><span class="sh">"</span><span class="s">query</span><span class="sh">"</span><span class="p">,</span> <span class="nb">help</span><span class="o">=</span><span class="sh">"</span><span class="s">the text to search for</span><span class="sh">"</span><span class="p">)</span></span><span class="code-line">    <span class="n">parser</span><span class="p">.</span><span class="nf">add_argument</span><span class="p">(</span><span class="sh">"</span><span class="s">-k</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">--top-k</span><span class="sh">"</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span> <span class="nb">help</span><span class="o">=</span><span class="sh">"</span><span class="s">number of paragraphs to return</span><span class="sh">"</span><span class="p">)</span></span><span class="code-line">    <span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="p">.</span><span class="nf">parse_args</span><span class="p">()</span></span><span class="code-line"></span><span class="code-line">    <span class="n">client</span> <span class="o">=</span> <span class="n">chromadb</span><span class="p">.</span><span class="nc">PersistentClient</span><span class="p">(</span><span class="n">path</span><span class="o">=</span><span class="nf">str</span><span class="p">(</span><span class="n">CHROMA_PATH</span><span class="p">))</span></span><span class="code-line">    <span class="k">try</span><span class="p">:</span></span><span class="code-line">        <span class="n">collection</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="nf">get_collection</span><span class="p">(</span><span class="n">COLLECTION_NAME</span><span class="p">,</span> <span class="n">embedding_function</span><span class="o">=</span><span class="bp">None</span><span class="p">)</span></span><span class="code-line">    <span class="k">except</span> <span class="n">NotFoundError</span><span class="p">:</span></span><span class="code-line">        <span class="k">raise</span> <span class="nc">SystemExit</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">No collection </span><span class="si">{</span><span class="n">COLLECTION_NAME</span><span class="si">!r}</span><span class="s"> in </span><span class="si">{</span><span class="n">CHROMA_PATH</span><span class="si">}</span><span class="s">; run main.py first</span><span class="sh">"</span><span class="p">)</span></span><span class="code-line">    <span class="n">pca</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="n">PCA_FILE</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line">    <span class="n">model</span> <span class="o">=</span> <span class="nc">BGEM3FlagModel</span><span class="p">(</span><span class="n">EMBEDDING_MODEL</span><span class="p">,</span> <span class="n">normalize_embeddings</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">use_fp16</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span></span><span class="code-line">    <span class="n">query_vector</span> <span class="o">=</span> <span class="nf">embed_query</span><span class="p">(</span><span class="n">args</span><span class="p">.</span><span class="n">query</span><span class="p">,</span> <span class="n">model</span><span class="p">,</span> <span class="n">pca</span><span class="p">[</span><span class="sh">"</span><span class="s">mean</span><span class="sh">"</span><span class="p">],</span> <span class="n">pca</span><span class="p">[</span><span class="sh">"</span><span class="s">components</span><span class="sh">"</span><span class="p">])</span></span><span class="code-line"></span><span class="code-line">    <span class="n">results</span> <span class="o">=</span> <span class="n">collection</span><span class="p">.</span><span class="nf">query</span><span class="p">(</span></span><span class="code-line">        <span class="n">query_embeddings</span><span class="o">=</span><span class="p">[</span><span class="n">query_vector</span><span class="p">],</span></span><span class="code-line">        <span class="n">n_results</span><span class="o">=</span><span class="n">args</span><span class="p">.</span><span class="n">top_k</span><span class="p">,</span></span><span class="code-line">        <span class="n">include</span><span class="o">=</span><span class="p">[</span><span class="sh">"</span><span class="s">documents</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">metadatas</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">distances</span><span class="sh">"</span><span class="p">],</span></span><span class="code-line">    <span class="p">)</span></span><span class="code-line">    <span class="n">hits</span> <span class="o">=</span> <span class="nf">zip</span><span class="p">(</span><span class="n">results</span><span class="p">[</span><span class="sh">"</span><span class="s">documents</span><span class="sh">"</span><span class="p">][</span><span class="mi">0</span><span class="p">],</span> <span class="n">results</span><span class="p">[</span><span class="sh">"</span><span class="s">metadatas</span><span class="sh">"</span><span class="p">][</span><span class="mi">0</span><span class="p">],</span> <span class="n">results</span><span class="p">[</span><span class="sh">"</span><span class="s">distances</span><span class="sh">"</span><span class="p">][</span><span class="mi">0</span><span class="p">])</span></span><span class="code-line">    <span class="k">for</span> <span class="n">rank</span><span class="p">,</span> <span class="p">(</span><span class="n">document</span><span class="p">,</span> <span class="n">metadata</span><span class="p">,</span> <span class="n">distance</span><span class="p">)</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">hits</span><span class="p">,</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span></span><span class="code-line">        <span class="c1"># Colecția folosește distanța cosinus, deci 1 - distanța este similaritatea cosinus.</span></span><span class="code-line">        <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="se">\n</span><span class="s">#</span><span class="si">{</span><span class="n">rank</span><span class="si">}</span><span class="s">  paragraph </span><span class="si">{</span><span class="n">metadata</span><span class="p">[</span><span class="sh">'</span><span class="s">paragraph</span><span class="sh">'</span><span class="p">]</span><span class="si">}</span><span class="s">  score </span><span class="si">{</span><span class="mi">1</span> <span class="o">-</span> <span class="n">distance</span><span class="si">:</span><span class="p">.</span><span class="mi">3</span><span class="n">f</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span></span><span class="code-line">        <span class="nf">print</span><span class="p">(</span><span class="n">textwrap</span><span class="p">.</span><span class="nf">fill</span><span class="p">(</span><span class="n">document</span><span class="p">,</span> <span class="n">width</span><span class="o">=</span><span class="mi">100</span><span class="p">))</span></span></code></pre></div></div>

<p>Putem interoga baza de date cu:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line">uv run src/ragpipeline/query.py <span class="s2">"Ivan's poem about the Grand Inquisitor"</span></span></code></pre></div></div>

<p>Scriptul va crea un embedding pentru interogare, va aplica norma L2, PCA, apoi din nou norma L2, și apoi va interoga chroma db.</p>

<p class="post-figure"><img src="/assets/images/2026/rag-pipeline/indexing_vs_query_paths.png" alt="Căile de indexare și de interogare folosesc aceeași transformare PCA" /></p>

<p>Interogarea va returna:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line">#2  paragraph 1705  score 0.555</span><span class="code-line">“My poem is called ‘The Grand Inquisitor’; it’s a ridiculous thing, but I want to tell it to you.”</span><span class="code-line"></span><span class="code-line">#5  paragraph 5220  score 0.478</span><span class="code-line">“My dear fellow, I know a most charming and attractive young Russian gentleman, a young thinker and</span><span class="code-line">a great lover of literature and art, the author of a promising poem entitled _The Grand Inquisitor_.</span><span class="code-line">I was only thinking of him!”</span></code></pre></div></div>

<h2 id="exemple-de-interogări">Exemple de interogări</h2>

<p>Iată o altă interogare, de data aceasta în română: „L-au plătit”, adică a fost plătit sau cumpărat.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line">uv run src/ragpipeline/query.py "L-au platit"</span><span class="code-line"></span><span class="code-line">#1  paragraph 630  score 0.492</span><span class="code-line">“Pay back the three thousand.”</span><span class="code-line"></span><span class="code-line">#2  paragraph 645  score 0.454</span><span class="code-line">“It was he told you about the money, then?”</span></code></pre></div></div>

<p>Am primit câteva rezultate legate de bani, ceea ce este destul de tare. Și ultima interogare, tot în română: „Care sunt partenerele lui Fyodor?”</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line">uv run src/ragpipeline/query.py "Care sunt partenerele lui Fyodor?"</span><span class="code-line"></span><span class="code-line">#3  paragraph 1  score 0.393</span><span class="code-line">He was married twice, and had three sons, the eldest, Dmitri, by his first wife, and two, Ivan and</span><span class="code-line">Alexey, by his second. Fyodor Pavlovitch’s first wife, Adelaïda Ivanovna, belonged to a fairly rich</span><span class="code-line">and distinguished noble family, also landowners in our district, the Miüsovs. How it came to pass</span><span class="code-line">that an heiress, who was also a beauty, and moreover one of those vigorous, intelligent girls, so</span><span class="code-line">common in this generation, but sometimes also to be found in the last, could have married such a</span><span class="code-line">worthless, puny weakling, as we all called him, I won’t attempt to explain. I knew a young lady of</span><span class="code-line">the last “romantic” generation who after some years of an enigmatic passion for a gentleman, whom</span><span class="code-line">she might quite easily have married at any moment, invented insuperable obstacles to their union,</span><span class="code-line">and ended by throwing herself one stormy night into a rather deep and rapid river from a high bank,</span><span class="code-line">almost a precipice, and so perished, entirely to satisfy her own caprice, and to be like</span><span class="code-line">Shakespeare’s Ophelia. Indeed, if this precipice, a chosen and favorite spot of hers, had been less</span><span class="code-line">picturesque, if there had been a prosaic flat bank in its place, most likely the suicide would never</span><span class="code-line">have taken place. This is a fact, and probably there have been not a few similar instances in the</span><span class="code-line">last two or three generations. Adelaïda Ivanovna Miüsov’s action was similarly, no doubt, an echo of</span><span class="code-line">other people’s ideas, and was due to the irritation caused by lack of mental freedom. She wanted,</span><span class="code-line">perhaps, to show her feminine independence, to override class distinctions and the despotism of her</span><span class="code-line">family. And a pliable imagination persuaded her, we must suppose, for a brief moment, that Fyodor</span><span class="code-line">Pavlovitch, in spite of his parasitic position, was one of the bold and ironical spirits of that</span><span class="code-line">progressive epoch, though he was, in fact, an ill-natured buffoon and nothing more. What gave the</span><span class="code-line">marriage piquancy was that it was preceded by an elopement, and this greatly captivated Adelaïda</span><span class="code-line">Ivanovna’s fancy. Fyodor Pavlovitch’s position at the time made him specially eager for any such</span><span class="code-line">enterprise, for he was passionately anxious to make a career in one way or another. To attach</span><span class="code-line">himself to a good family and obtain a dowry was an alluring prospect. As for mutual love it did not</span><span class="code-line">exist apparently, either in the bride or in him, in spite of Adelaïda Ivanovna’s beauty. This was,</span><span class="code-line">perhaps, a unique case of the kind in the life of Fyodor Pavlovitch, who was always of a voluptuous</span><span class="code-line">temper, and ready to run after any petticoat on the slightest encouragement. She seems to have been</span><span class="code-line">the only woman who made no particular appeal to his senses.</span><span class="code-line"></span><span class="code-line">#4  paragraph 676  score 0.389</span><span class="code-line">Alyosha rose, but Fyodor Pavlovitch had already changed his mind.</span></code></pre></div></div>

<p class="post-figure"><img src="/assets/images/2026/rag-pipeline/embedding_space.png" alt="Interogările în limbi diferite ajung lângă paragrafe cu înțeles similar" /></p>

<h2 id="interogări-cu-agenți">Interogări cu agenți</h2>

<p>Și acum ceva diferit. Putem cere agentului să interogheze RAG-ul, iar asta este foarte eficient. Agentul regăsește doar informațiile importante, prin căutare semantică, în loc să citească întreaga carte.</p>

<p class="post-figure"><img src="/assets/images/2026/rag-pipeline/rag_with_agent.png" alt="Regăsire, augmentare și generare cu un agent" /></p>

<p class="post-figure"><img src="/assets/images/2026/rag-pipeline/claude-answer.png" alt="Răspunsul Claude la interogare" /></p>

<p>Claude reușește să identifice corect partenerele lui Fyodor.</p>

<h1 id="concluzie">Concluzie</h1>

<p>Construirea unui pipeline RAG este simplă. În era AI poți cere agentului tău să o facă în locul tău, dar poți să îl ghidezi. Biblioteca Python FlagEmbedding conține valori implicite sensibile și tot ce ai nevoie, în spatele unei interfețe simple și ușor de folosit.</p>

<p>Am folosit o bază de date vectorială locală, încorporată, <code class="language-plaintext highlighter-rouge">chromadb</code>, pentru acest proiect, dar poți folosi și alte soluții, precum ElasticSearch, care oferă mai multe funcționalități și îți permite să combini RAG cu căutarea tradițională.</p>

<p>Mulțumesc că ai citit!</p>

<p>Codul complet este disponibil pe GitHub și GitLab:</p>
<ul>
  <li><a href="https://github.com/dnutiu/RagPipeline/blob/main/src/ragpipeline/main.py">https://github.com/dnutiu/RagPipeline/blob/main/src/ragpipeline/main.py</a></li>
  <li><a href="https://gitlab.nuculabs.dev/dnutiu/RagPipeline">https://gitlab.nuculabs.dev/dnutiu/RagPipeline</a></li>
</ul>]]></content>
    <category term="Python" />
    <category term="Projects" />
    <category term="Artificial Intelligence" />
    <category term="python" />
    <category term="artificial intelligence" />
    <category term="ai" />
    <category term="rag" />
    <summary type="html"><![CDATA[Introducere Salut tuturor! 👋 Am vrut să scriu acest articol ca să arăt cum se construiește un pipeline RAG (Retrieval Augmented Generation, adică generare augmentată prin regăsire de informații). Un motiv pentru care ai dori să îl construiești este să activezi căutarea după intenție pentru afacerea sau proiectul tău.]]></summary>
  </entry>
  <entry xml:lang="ro">
    <title type="html">Te prezentăm Fluxa: o imagine mai clară a banilor tăi</title>
    <link href="https://nuculabs.dev/ro/projects/2026/09/28/meet-fluxa.html" rel="alternate" type="text/html" title="Te prezentăm Fluxa: o imagine mai clară a banilor tăi" />
    <published>2026-09-28T00:00:00+03:00</published>
    <updated>2026-09-28T00:00:00+03:00</updated>
    <id>https://nuculabs.dev/ro/projects/2026/09/28/meet-fluxa</id>
    <content type="html" xml:base="https://nuculabs.dev/ro/projects/2026/09/28/meet-fluxa.html"><![CDATA[<p>Salut tuturor! 👋</p>

<p>Ai deschis vreodată o tabelă numită <code class="language-plaintext highlighter-rouge">budget-final-v7-actually-final.xlsx</code>, te-ai uitat la trei aplicații bancare și tot te-ai întrebat unde s-au dus banii? Asta încearcă <a href="https://fluxa.nuculabs.dev/">Fluxa</a> să facă mai puțin frustrant. Este o aplicație de finanțe personale pe care o construiesc pentru lucrurile de zi cu zi: tranzacții, conturi, bugete, obiective și imaginea de ansamblu, dincolo de factura la magazin de luna aceasta.</p>

<!--more-->

<p>Iată un tur al părților la care ajung cel mai des. <strong>Toate capturile de ecran de mai jos folosesc date fictive</strong>, deci cifrele sunt exemple, nu finanțele cuiva.</p>

<h2 id="începe-cu-imaginea-de-ansamblu">Începe cu imaginea de ansamblu</h2>

<p>Tabloul de bord al Fluxa răspunde mai întâi la întrebările utile: cât a intrat, cât a ieșit, ce a rămas și dacă luna aceasta se comportă cum trebuie. Poți comuta între entitatea curentă, toate entitățile și vederea pe cont, apoi schimba perioada de timp fără să reconstruiești un tabel pivot într-o tabelă.</p>

<p class="post-figure"><img src="/assets/images/2026/fluxa/dashboard.png" alt="Tabloul de bord Fluxa cu șase luni de venituri, cheltuieli, rată de economisire și starea conturilor, cu date fictive" /></p>

<p><em>O prezentare pe șase luni pentru o gospodărie fictivă.</em></p>

<p>Graficul fluxului de numerar pune împreună veniturile, cheltuielile și mișcarea netă. Defalcările pe categorii și activitatea recentă ajută la explicarea cifrelor. Secțiunea de stare a conturilor adaugă soldurile și o estimare a autonomiei financiare, ceea ce este mult mai util decât un sentiment misterios de „pare în regulă”.</p>

<p class="post-figure"><img src="/assets/images/2026/fluxa/cashflow.png" alt="Graficul fluxului de numerar Fluxa cu date și defalcări pe categorii pentru șase luni" /></p>

<p><em>Șase luni de activitate fictivă, cu suficientă variație cât să arate a viață reală și nu a unei tabele copiate.</em></p>

<h2 id="dă-fiecărui-euro-sau-leu-o-adresă">Dă fiecărui euro (sau leu) o adresă</h2>

<p>Poți înregistra venituri și cheltuieli în conturi de numerar, bancare, de card, de economii și de investiții, apoi muta bani între conturi. Fluxa acceptă și mai multe entități, astfel încât finanțele personale și un PFA sau o afacere secundară pot avea propriile tranzacții, apărând totuși într-o vedere combinată.</p>

<p>Tranzacțiile pot fi în <strong>RON, EUR, USD, GBP sau CHF</strong>. Fluxa folosește cursuri de schimb bazate pe BCE pentru conversie și păstrează moneda și suma originale. Poți alege RON, EUR sau USD ca monedă pentru totalurile generale. Intrările recurente se ocupă de plățile previzibile, iar urmăritorul de abonamente arată cât adună serviciile lunare și anuale și când urmează să fie facturate.</p>

<h2 id="bugete-care-îți-spun-ceva-util">Bugete care îți spun ceva util</h2>

<p>Un buget este mai mult decât o listă de cifre optimiste. Fluxa compară alocarea lunară pe fiecare categorie cu cheltuielile reale, arată ce a rămas și estimează încotro se îndreaptă luna. Poți de asemenea să privești de la distanță, în vederea anuală. Aici o mică colecție de abonamente de „doar 49,99” ajunge la momentul adevărului.</p>

<p class="post-figure"><img src="/assets/images/2026/fluxa/budget.png" alt="Bugetul lunar Fluxa cu alocări fictive, cheltuieli reale, suma rămasă și progresul pe categorii" /></p>

<p><em>Un buget din septembrie populat: alimente, călătorii, cumpărături și restul au cheltuieli reale fictive în dreptul lor.</em></p>

<p>Obiectivele fac partea cealaltă a planificării mai distractivă. În loc de o promisiune vagă de a economisi pentru o călătorie, îi poți da o țintă, poți rezerva bani dintr-un cont existent sau poți folosi un cont de economii dedicat și poți vedea progresul și data estimată de finalizare. Gospodăria fictivă are în lucru o călătorie în Japonia, un laptop și o renovare a bucătăriei. Se pare că are planuri.</p>

<p class="post-figure"><img src="/assets/images/2026/fluxa/goals.png" alt="Trei obiective de economisire finanțate în Fluxa, cu bare de progres și date estimate de finalizare" /></p>

<p><em>Trei obiective cu bani deja puși deoparte, nu trei bare de progres goale.</em></p>

<h2 id="banii-sunt-mai-mult-decât-soldul-contului-curent">Banii sunt mai mult decât soldul contului curent</h2>

<p>Averea aduce conturile, investițiile, economiile pentru obiective și bunurile fizice urmărite într-o singură vedere pe entități. Arată soldurile în moneda originală alături de totalurile convertite, plus un istoric și un grafic al alocării. Dacă urmărești o locuință, o bicicletă sau aur și argint fizic, acestea pot intra și ele în tablou. Exemplul de mai jos include un apartament, o bicicletă, un fond indexat și economii.</p>

<p class="post-figure"><img src="/assets/images/2026/fluxa/wealth.png" alt="Tabloul de bord al averii Fluxa cu totaluri populate pentru conturi, investiții, obiective și bunuri fizice" /></p>

<p><em>Un portofoliu fictiv cu fonduri lichide, investiții, bani pentru obiective și bunuri fizice.</em></p>

<h2 id="mai-puțină-tastare-mai-multă-verificare">Mai puțină tastare, mai multă verificare</h2>

<p><strong>Importatorul de extrase bancare Pro</strong> poate citi extrase de la BRD, ING, Revolut și UniCredit, inclusiv exporturile CSV de la Revolut. Partea importantă este pasul de verificare: confirmi datele și sumele, ajustezi categoriile sau descrierile, elimini rândurile pe care nu le vrei și abia apoi confirmi importul. Dacă ai confirmat un lot greșit, îl poți anula.</p>

<p class="post-figure"><img src="/assets/images/2026/fluxa/import-review.png" alt="Verificarea extrasului ING în Fluxa cu șapte tranzacții fictive categorisite, gata de confirmare" /></p>

<p><em>Date ilustrative de verificare a extrasului, inclusiv alimente, transport, servicii și o intrare de venit.</em></p>

<p>Mai sunt câteva detalii pe care le apreciez: un export <strong>Registru Jurnal</strong> în PDF și Excel, teme luminoase și întunecate, engleza, româna și germana și o aplicație web instalabilă, care poate pune în coadă venituri sau cheltuieli noi când ești offline și le sincronizează ulterior. Fluxa poate, de asemenea, să împartă o factură comună și să urmărească tranzacțiile recurente, astfel încât treburile mărunte au și ele un loc.</p>

<p>Fluxa este pentru momentul în care vrei un răspuns, nu un alt tab. Dacă ți se pare util, <a href="https://fluxa.nuculabs.dev/">încearcă Fluxa</a> și spune-mi ce parte din tabela ta de bani ai dori cel mai mult să o pensionezi.</p>

<hr />

<p><em>Mulțumiri OpenAI Codex pentru ajutorul la redactarea acestui articol, pregătirea datelor demo fictive și realizarea capturilor de ecran.</em></p>]]></content>
    <category term="Projects" />
    <category term="fluxa" />
    <category term="personal finance" />
    <category term="ruby on rails" />
    <category term="vue" />
    <summary type="html"><![CDATA[Salut tuturor! 👋 Ai deschis vreodată o tabelă numită budget-final-v7-actually-final.xlsx, te-ai uitat la trei aplicații bancare și tot te-ai întrebat unde s-au dus banii? Asta încearcă Fluxa să facă mai puțin frustrant. Este o aplicație de finanțe personale pe care o construiesc pentru lucrurile de zi cu zi: tranzacții, conturi, bugete, obiective și imaginea de ansamblu, dincolo de factura la magazin de luna aceasta.]]></summary>
  </entry>
  <entry xml:lang="ro">
    <title type="html">Un tur al concurenței în Python</title>
    <link href="https://nuculabs.dev/ro/python/2026/09/26/a-tour-of-concurrency-in-python.html" rel="alternate" type="text/html" title="Un tur al concurenței în Python" />
    <published>2026-09-26T00:00:00+03:00</published>
    <updated>2026-09-26T00:00:00+03:00</updated>
    <id>https://nuculabs.dev/ro/python/2026/09/26/a-tour-of-concurrency-in-python</id>
    <content type="html" xml:base="https://nuculabs.dev/ro/python/2026/09/26/a-tour-of-concurrency-in-python.html"><![CDATA[<h1 id="introducere">Introducere</h1>

<p>Salut tuturor! 👋</p>

<p>Acest articol va fi un tur simplu și de bază al modelelor de concurență din Python. Vom începe cu thread-urile, apoi
vom trece la procese și, în final, vom acoperi asyncio. Îți voi arăta cum să interacționezi cu aceste
mecanisme folosind câteva API-uri de nivel jos, apoi vom trece la API-uri de nivel mai înalt, mai moderne. Îți voi arăta
și câteva primitive utilitare pentru sincronizare.</p>

<p>Nu voi acoperi totul, pentru că toate informațiile se găsesc pe site-ul oficial al documentației Python, dar după
ce vei citi acest articol vei înțelege bine modelul de concurență din Python.</p>

<h1 id="thread-uri">Thread-uri</h1>

<p>Unul dintre modelele principale de concurență sunt thread-urile. Programul tău rulează într-un Process, iar în
interiorul procesului există mai multe thread-uri. Deoarece thread-urile rulează în interiorul containerului procesului, ele <strong>partajează toate resursele</strong> acelui proces.</p>

<p>Thread-urile pot părea că rulează în paralel, în același timp, dar de fapt nu o fac. Python are <strong>GIL</strong>
(Global Interpreter Lock) și, din cauza lui, doar un thread rulează la un moment dat. Totuși, pentru că trec de la unul la altul
atât de repede, pare că rulează în paralel.</p>

<p class="post-figure"><img src="/assets/images/2026/py-concurrency/threads-diagram.png" alt="" /></p>

<p>Pe vremuri, calculatoarele aveau un singur nucleu de CPU, iar sistemul de operare și aplicațiile îl partajau. Codul putea
rula doar pe un nucleu, iar pentru ca sistemul de operare să permită multitasking-ul trebuia să aloce fiecărei aplicații
o fereastră de timp pe nucleu. Același lucru se întâmplă și astăzi, dar acum avem acces la mai multe nuclee și putem
rula lucrurile în paralel.</p>

<blockquote>
  <p>În alte limbaje, precum Java, thread-urile puteau fi programate pe nuclee diferite, ceea ce le făcea să ruleze în paralel. Dar în Python, din cauza GIL-ului, se permite doar un thread să ruleze
în același timp pe fiecare proces.</p>
</blockquote>

<p>Thread-urile sunt ușoare în comparație cu procesele, dar sunt costisitoare în comparație cu corutinele.</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th style="text-align: right">Coroutine</th>
      <th style="text-align: right">Thread</th>
      <th style="text-align: right">Process</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Creare</td>
      <td style="text-align: right">Foarte ieftină</td>
      <td style="text-align: right">Moderată</td>
      <td style="text-align: right">Costisitoare</td>
    </tr>
    <tr>
      <td>Memorie</td>
      <td style="text-align: right">Foarte mică</td>
      <td style="text-align: right">Mai mare</td>
      <td style="text-align: right">Mult mai mare</td>
    </tr>
    <tr>
      <td>Schimbare de context</td>
      <td style="text-align: right">Foarte ieftină</td>
      <td style="text-align: right">La nivel de OS, mai costisitoare</td>
      <td style="text-align: right">La nivel de OS, cea mai costisitoare</td>
    </tr>
    <tr>
      <td>Spațiu de memorie</td>
      <td style="text-align: right">Partajat</td>
      <td style="text-align: right">Partajat</td>
      <td style="text-align: right">Separat</td>
    </tr>
    <tr>
      <td>Programare</td>
      <td style="text-align: right">Event loop</td>
      <td style="text-align: right">OS</td>
      <td style="text-align: right">OS</td>
    </tr>
    <tr>
      <td>Scară tipică</td>
      <td style="text-align: right">Mii–milioane*</td>
      <td style="text-align: right">Sute–mii</td>
      <td style="text-align: right">De obicei relativ puține</td>
    </tr>
  </tbody>
</table>

<p>Deoarece la crearea unui thread trebuie să creezi și resursele partajate, fiecare thread are propria stivă,
iar acest lucru nu scalează bine dacă încerci să creezi un milion de thread-uri. Codul sistemului de operare
care gestionează thread-urile trebuie, de asemenea, să le programeze, iar dacă există multe thread-uri de programat,
se ajunge la un blocaj și performanța scade. Același lucru se întâmplă și cu procesele.</p>

<blockquote>
  <p>Un fork bomb este un fel de „virus” care se tot bifurcă (fork) la nesfârșit. Sistemul de operare va crea procese
până când toate resursele se epuizează, iar acestea nu mai pot fi programate în mod fiabil, ceea ce forțează sistemul să se blocheze.</p>
</blockquote>

<h2 id="thread">Thread</h2>

<p>Acesta este un exemplu de cod care pornește două thread-uri; ținta este funcția <code class="language-plaintext highlighter-rouge">thread_target</code>, care va fi rulată pe cele
două thread-uri.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line"><span class="kn">import</span> <span class="n">random</span></span><span class="code-line"><span class="kn">from</span> <span class="n">threading</span> <span class="kn">import</span> <span class="n">Thread</span></span><span class="code-line"></span><span class="code-line"></span><span class="code-line"><span class="k">class</span> <span class="nc">Program</span><span class="p">:</span></span><span class="code-line">    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">):</span></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="n">_shared_x</span> <span class="o">=</span> <span class="mi">2</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">thread_target</span><span class="p">(</span><span class="n">self</span><span class="p">):</span></span><span class="code-line">        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Hello from thread</span><span class="sh">"</span><span class="p">)</span></span><span class="code-line">        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">1_000_000</span><span class="p">):</span></span><span class="code-line">            <span class="n">self</span><span class="p">.</span><span class="n">_shared_x</span> <span class="o">+=</span> <span class="n">random</span><span class="p">.</span><span class="nf">randint</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">self</span><span class="p">):</span></span><span class="code-line">        <span class="n">th_one</span> <span class="o">=</span> <span class="nc">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">thread_target</span><span class="p">)</span></span><span class="code-line">        <span class="n">th_two</span> <span class="o">=</span> <span class="nc">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">thread_target</span><span class="p">)</span></span><span class="code-line">        <span class="n">th_one</span><span class="p">.</span><span class="nf">start</span><span class="p">()</span></span><span class="code-line">        <span class="n">th_two</span><span class="p">.</span><span class="nf">start</span><span class="p">()</span></span><span class="code-line"></span><span class="code-line">        <span class="n">th_one</span><span class="p">.</span><span class="nf">join</span><span class="p">()</span></span><span class="code-line">        <span class="n">th_two</span><span class="p">.</span><span class="nf">join</span><span class="p">()</span></span><span class="code-line"></span><span class="code-line">        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Shared X:</span><span class="sh">"</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">_shared_x</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line"></span><span class="code-line"><span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="sh">'</span><span class="s">__main__</span><span class="sh">'</span><span class="p">:</span></span><span class="code-line">    <span class="n">program</span> <span class="o">=</span> <span class="nc">Program</span><span class="p">()</span></span><span class="code-line">    <span class="n">program</span><span class="p">.</span><span class="nf">main</span><span class="p">()</span></span></code></pre></div></div>

<p>Trebuie să facem join la thread-uri, pentru că dacă nu le facem, thread-ul principal se termină, procesul se oprește,
iar celelalte două thread-uri rămân orfane.</p>

<h2 id="timer">Timer</h2>

<p>Timer este o clasă utilitară utilă atunci când vrei să programezi o acțiune. Pornește propriul thread și
execută acea acțiune. Este util pentru implementarea heartbeat-urilor sau pentru verificarea unor lucruri, cum ar fi reînnoirea tokenurilor de autorizare.</p>

<p>Iată un exemplu simplu de cod pentru afișarea unui heartbeat:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line"><span class="kn">from</span> <span class="n">threading</span> <span class="kn">import</span> <span class="n">Timer</span></span><span class="code-line"><span class="kn">from</span> <span class="n">time</span> <span class="kn">import</span> <span class="n">sleep</span></span><span class="code-line"></span><span class="code-line"></span><span class="code-line"><span class="k">class</span> <span class="nc">Program</span><span class="p">:</span></span><span class="code-line">    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">):</span></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="n">_timer</span><span class="p">:</span> <span class="n">Timer</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="nc">Timer</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">heartbeat</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">heartbeat</span><span class="p">(</span><span class="n">self</span><span class="p">):</span></span><span class="code-line">        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Heartbeat</span><span class="sh">"</span><span class="p">)</span></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="n">_timer</span> <span class="o">=</span> <span class="nc">Timer</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">heartbeat</span><span class="p">)</span></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="n">_timer</span><span class="p">.</span><span class="nf">start</span><span class="p">()</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="n">self</span><span class="p">):</span></span><span class="code-line">        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Running the program.</span><span class="sh">"</span><span class="p">)</span></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="n">_timer</span><span class="p">.</span><span class="nf">start</span><span class="p">()</span></span><span class="code-line">        <span class="k">while</span> <span class="bp">True</span><span class="p">:</span></span><span class="code-line">            <span class="nf">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">stop</span><span class="p">(</span><span class="n">self</span><span class="p">):</span></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="n">_timer</span><span class="p">.</span><span class="nf">cancel</span><span class="p">()</span></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="n">_timer</span> <span class="o">=</span> <span class="bp">None</span></span><span class="code-line"></span><span class="code-line"><span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="sh">"</span><span class="s">__main__</span><span class="sh">"</span><span class="p">:</span></span><span class="code-line">    <span class="n">program</span> <span class="o">=</span> <span class="nc">Program</span><span class="p">()</span></span><span class="code-line">    <span class="k">try</span><span class="p">:</span></span><span class="code-line">        <span class="n">program</span><span class="p">.</span><span class="nf">run</span><span class="p">()</span></span><span class="code-line">    <span class="k">except</span> <span class="nb">KeyboardInterrupt</span><span class="p">:</span></span><span class="code-line">        <span class="n">program</span><span class="p">.</span><span class="nf">stop</span><span class="p">()</span></span></code></pre></div></div>

<h2 id="concurrent-futures-thread-pool">Concurrent Futures: Thread Pool</h2>

<p>Acest exemplu este similar cu primul, dar folosește un API de nivel mai înalt pentru programarea thread-urilor.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line"><span class="kn">import</span> <span class="n">random</span></span><span class="code-line"><span class="kn">from</span> <span class="n">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span></span><span class="code-line"></span><span class="code-line"></span><span class="code-line"><span class="k">class</span> <span class="nc">Program</span><span class="p">:</span></span><span class="code-line">    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">):</span></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="n">_shared_x</span> <span class="o">=</span> <span class="mi">2</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">thread_target</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">low</span><span class="p">):</span></span><span class="code-line">        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Hello from thread</span><span class="sh">"</span><span class="p">)</span></span><span class="code-line">        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">1_000_000</span><span class="p">):</span></span><span class="code-line">            <span class="n">self</span><span class="p">.</span><span class="n">_shared_x</span> <span class="o">+=</span> <span class="n">random</span><span class="p">.</span><span class="nf">randint</span><span class="p">(</span><span class="nf">min</span><span class="p">(</span><span class="n">low</span><span class="p">,</span> <span class="n">i</span><span class="p">),</span> <span class="n">i</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line">        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">_shared_x</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">self</span><span class="p">):</span></span><span class="code-line">        <span class="n">th_pool</span> <span class="o">=</span> <span class="nc">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span></span><span class="code-line">        <span class="n">th_one</span> <span class="o">=</span> <span class="n">th_pool</span><span class="p">.</span><span class="nf">submit</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">thread_target</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span></span><span class="code-line">        <span class="n">th_two</span> <span class="o">=</span> <span class="n">th_pool</span><span class="p">.</span><span class="nf">submit</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">thread_target</span><span class="p">,</span><span class="mi">2</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line">        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Shared X after first thread</span><span class="sh">"</span><span class="p">,</span> <span class="n">th_one</span><span class="p">.</span><span class="nf">result</span><span class="p">())</span></span><span class="code-line">        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Shared X after second thread</span><span class="sh">"</span><span class="p">,</span> <span class="n">th_two</span><span class="p">.</span><span class="nf">result</span><span class="p">())</span></span><span class="code-line"></span><span class="code-line">        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Shared X:</span><span class="sh">"</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">_shared_x</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line"></span><span class="code-line"><span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="sh">'</span><span class="s">__main__</span><span class="sh">'</span><span class="p">:</span></span><span class="code-line">    <span class="n">program</span> <span class="o">=</span> <span class="nc">Program</span><span class="p">()</span></span><span class="code-line">    <span class="n">program</span><span class="p">.</span><span class="nf">main</span><span class="p">()</span></span></code></pre></div></div>

<p>Se inițializează și un thread pool de dimensiune fixă. Ce returnează <code class="language-plaintext highlighter-rouge">pool.submit</code> este un Future. Poți verifica rezultatul
future-ului folosind <code class="language-plaintext highlighter-rouge">.result()</code>, sau excepția cu <code class="language-plaintext highlighter-rouge">.exception()</code>.</p>

<p>Documentația conține toate funcțiile detaliate pe care le poți apela pe un obiect <a href="https://docs.python.org/3/library/asyncio-future.html#future-object">Future</a>.</p>

<h1 id="procese">Procese</h1>

<p>Procesele sunt gestionate de sistemul de operare și servesc drept container pentru thread-uri. Ele sunt folosite pentru a rula lucruri
în paralel. Poți ocoli limitările GIL-ului Python împărțind munca pe mai multe procese, iar dacă munca ta implică
multă activitate de I/O sau de rețea, poți porni un loop asincron în fiecare proces.</p>

<p>Crearea mai multor procese este o modalitate excelentă de a aborda o sarcină legată de CPU, iar pentru că sunt gestionate de OS, fiecare
proces rulează pe propriul nucleu. Dacă ai mai multe procese decât nuclee disponibile, atunci fiecare proces va primi o
fereastră de timp pe nucleul CPU.</p>

<p>Procesele au propria memorie privată, dar API-ul îți permite să partajezi o parte din memorie cu alte procese.
Pentru comunicarea între procese poți folosi Queues și Pipes.</p>

<p class="post-figure"><img src="/assets/images/2026/py-concurrency/processes-diagram.png" alt="" /></p>

<h2 id="multiprocessing">Multiprocessing</h2>

<p>API-ul multiprocessing este similar cu API-ul threading. Poți porni procese individuale sau poți folosi un pool de procese.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line"><span class="kn">from</span> <span class="n">multiprocessing</span> <span class="kn">import</span> <span class="n">Process</span></span><span class="code-line"></span><span class="code-line"><span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">name</span><span class="p">):</span></span><span class="code-line">    <span class="nf">print</span><span class="p">(</span><span class="sh">'</span><span class="s">hello</span><span class="sh">'</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line"><span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="sh">'</span><span class="s">__main__</span><span class="sh">'</span><span class="p">:</span></span><span class="code-line">    <span class="n">p</span> <span class="o">=</span> <span class="nc">Process</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">f</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="sh">'</span><span class="s">bob</span><span class="sh">'</span><span class="p">,))</span></span><span class="code-line">    <span class="n">p</span><span class="p">.</span><span class="nf">start</span><span class="p">()</span></span><span class="code-line">    <span class="n">p</span><span class="p">.</span><span class="nf">join</span><span class="p">()</span></span></code></pre></div></div>

<p>Și un exemplu de pool de procese:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line"><span class="kn">from</span> <span class="n">multiprocessing</span> <span class="kn">import</span> <span class="n">Pool</span></span><span class="code-line"></span><span class="code-line"><span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">):</span></span><span class="code-line">    <span class="k">return</span> <span class="n">x</span><span class="o">*</span><span class="n">x</span></span><span class="code-line"></span><span class="code-line"><span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="sh">'</span><span class="s">__main__</span><span class="sh">'</span><span class="p">:</span></span><span class="code-line">    <span class="k">with</span> <span class="nc">Pool</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="k">as</span> <span class="n">p</span><span class="p">:</span></span><span class="code-line">        <span class="nf">print</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]))</span></span></code></pre></div></div>

<p>Exemplele sunt copiate din documentația oficială Python despre <a href="https://docs.python.org/3/library/multiprocessing.html">multiprocessing</a>,
care este o excelentă analiză detaliată a subiectului.</p>

<h2 id="concurrent-futures-process-pool">Concurrent Futures: Process Pool</h2>

<p>Poți folosi același API concurrent.futures pentru a crea un pool de procese, așa cum ai crea un thread pool. În acest
exemplu merită observat că variabila _shared_x nu este de fapt partajată: fiecare proces primește propria copie, iar
procesul principal nu o modifică deloc.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line"><span class="kn">import</span> <span class="n">random</span></span><span class="code-line"><span class="kn">from</span> <span class="n">concurrent.futures</span> <span class="kn">import</span> <span class="n">ProcessPoolExecutor</span></span><span class="code-line"></span><span class="code-line"></span><span class="code-line"><span class="k">class</span> <span class="nc">Program</span><span class="p">:</span></span><span class="code-line">    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">):</span></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="n">_shared_x</span> <span class="o">=</span> <span class="mi">2</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">target</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">low</span><span class="p">):</span></span><span class="code-line">        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Hello from process</span><span class="sh">"</span><span class="p">)</span></span><span class="code-line">        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">1_000_000</span><span class="p">):</span></span><span class="code-line">            <span class="n">self</span><span class="p">.</span><span class="n">_shared_x</span> <span class="o">+=</span> <span class="n">random</span><span class="p">.</span><span class="nf">randint</span><span class="p">(</span><span class="nf">min</span><span class="p">(</span><span class="n">low</span><span class="p">,</span> <span class="n">i</span><span class="p">),</span> <span class="n">i</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line">        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">_shared_x</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">self</span><span class="p">):</span></span><span class="code-line">        <span class="n">process_pool</span> <span class="o">=</span> <span class="nc">ProcessPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span></span><span class="code-line">        <span class="n">th_one</span> <span class="o">=</span> <span class="n">process_pool</span><span class="p">.</span><span class="nf">submit</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">target</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span></span><span class="code-line">        <span class="n">th_two</span> <span class="o">=</span> <span class="n">process_pool</span><span class="p">.</span><span class="nf">submit</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">target</span><span class="p">,</span><span class="mi">2</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line">        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Shared X after first process</span><span class="sh">"</span><span class="p">,</span> <span class="n">th_one</span><span class="p">.</span><span class="nf">result</span><span class="p">())</span></span><span class="code-line">        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Shared X after second process</span><span class="sh">"</span><span class="p">,</span> <span class="n">th_two</span><span class="p">.</span><span class="nf">result</span><span class="p">())</span></span><span class="code-line"></span><span class="code-line">        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Shared X:</span><span class="sh">"</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">_shared_x</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line"></span><span class="code-line"><span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="sh">'</span><span class="s">__main__</span><span class="sh">'</span><span class="p">:</span></span><span class="code-line">    <span class="n">program</span> <span class="o">=</span> <span class="nc">Program</span><span class="p">()</span></span><span class="code-line">    <span class="n">program</span><span class="p">.</span><span class="nf">main</span><span class="p">()</span></span></code></pre></div></div>

<p>Dacă rulezi exemplul, ar trebui să afișeze ceva de genul:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line">Hello from process</span><span class="code-line">Hello from process</span><span class="code-line">Shared X after first process 250199267754</span><span class="code-line">Shared X after second process 249818236816</span><span class="code-line">Shared X: 2</span></code></pre></div></div>

<h1 id="asyncio">Asyncio</h1>

<p>Asyncio este unul dintre modelele mele preferate de concurență în Python. Când dezvolți servicii web, de obicei vrei să servești
mai mulți utilizatori în același timp. Înainte ca acest model să fie folosit, acest lucru se realiza creând un thread sau un proces
pentru fiecare cerere. Funcționa bine, dar nu scala bine: există o limită a numărului de procese pe care le putem crea
și programa, și același lucru este valabil și pentru thread-uri.</p>

<p>Și hai să fim sinceri: cu API-urile web de astăzi, majoritatea cererilor înseamnă doar preluarea unor date dintr-o bază de date sau cache,
un apel de rețea sau calcule numerice. Crearea unui proces nou pentru fiecare cerere ar fi exagerat.</p>

<p>O aplicație construită cu asyncio în minte folosește de obicei un singur proces, iar pe acel proces rulează event loop-ul; în loc de
thread-uri, folosim corutine, care sunt similare.</p>

<p>Thread-urile sunt de obicei gestionate de sistemul de operare, ceea ce este mai puțin eficient, iar corutinele sunt gestionate de
interpretorul Python. Thread-urile sunt întrerupte de OS pentru a permite rularea altor thread-uri/procese, iar corutinele îți spun
exact când pot fi întrerupte, folosind un cuvânt cheie precum <code class="language-plaintext highlighter-rouge">await</code>.</p>

<p class="post-figure"><img src="/assets/images/2026/py-concurrency/asyncio-diagram.png" alt="" /></p>

<p>Când o corutină cedează voluntar execuția înapoi event loop-ului, event loop-ul alege o altă corutină să ruleze.
Este foarte important ca, atunci când folosești acest model, corutina să nu blocheze event loop-ul, nefiind niciodată <code class="language-plaintext highlighter-rouge">await</code>-ată
pe alte sarcini sau petrecând mult timp cu calcule numerice, pentru că event loop-ul are un singur thread, iar asta înseamnă că celelalte
lucruri nu mai vor rula.</p>

<p>Acest model funcționează excelent pentru aplicațiile web, pentru că servesc cererile în paralel, dar dacă scrii un script simplu
care face cereri în mod sincron, nu vei câștiga prea mult, de exemplu:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line"><span class="kn">import</span> <span class="n">asyncio</span></span><span class="code-line"></span><span class="code-line"><span class="k">async</span> <span class="k">def</span> <span class="nf">task</span><span class="p">():</span></span><span class="code-line">    <span class="k">await</span> <span class="nf">fetch_data</span><span class="p">()</span>  <span class="c1"># wait until finished</span></span><span class="code-line">    <span class="k">await</span> <span class="nf">fetch_data</span><span class="p">()</span>  <span class="c1"># then start this</span></span><span class="code-line">    <span class="k">await</span> <span class="nf">fetch_data</span><span class="p">()</span></span><span class="code-line">    <span class="k">await</span> <span class="nf">fetch_data</span><span class="p">()</span></span><span class="code-line">    </span><span class="code-line"><span class="n">asyncio</span><span class="p">.</span><span class="nf">run</span><span class="p">(</span><span class="nf">task</span><span class="p">())</span></span></code></pre></div></div>

<p>Dacă poți, rulează toate cererile în paralel cu <code class="language-plaintext highlighter-rouge">asyncio.gather</code> sau cu alternativa mai modernă <code class="language-plaintext highlighter-rouge">TaskGroup</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line"><span class="k">async</span> <span class="k">def</span> <span class="nf">fetch_data</span><span class="p">(</span><span class="n">n</span><span class="p">):</span></span><span class="code-line">    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Fetching </span><span class="si">{</span><span class="n">n</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span></span><span class="code-line">    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="nf">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span></span><span class="code-line">    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Finished </span><span class="si">{</span><span class="n">n</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span></span><span class="code-line">    <span class="k">return</span> <span class="n">n</span></span><span class="code-line"></span><span class="code-line"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span></span><span class="code-line">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="nf">gather</span><span class="p">(</span></span><span class="code-line">        <span class="nf">fetch_data</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span></span><span class="code-line">        <span class="nf">fetch_data</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span></span><span class="code-line">        <span class="nf">fetch_data</span><span class="p">(</span><span class="mi">3</span><span class="p">),</span></span><span class="code-line">        <span class="nf">fetch_data</span><span class="p">(</span><span class="mi">4</span><span class="p">),</span></span><span class="code-line">    <span class="p">)</span></span><span class="code-line">    <span class="nf">print</span><span class="p">(</span><span class="n">results</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line"><span class="n">asyncio</span><span class="p">.</span><span class="nf">run</span><span class="p">(</span><span class="nf">main</span><span class="p">())</span></span></code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line"><span class="kn">import</span> <span class="n">asyncio</span></span><span class="code-line"></span><span class="code-line"></span><span class="code-line"><span class="k">async</span> <span class="k">def</span> <span class="nf">fetch_data</span><span class="p">(</span><span class="n">n</span><span class="p">):</span></span><span class="code-line">    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Fetching </span><span class="si">{</span><span class="n">n</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span></span><span class="code-line">    <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="nf">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span></span><span class="code-line">    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Finished </span><span class="si">{</span><span class="n">n</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span></span><span class="code-line">    <span class="k">return</span> <span class="n">n</span></span><span class="code-line"></span><span class="code-line"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span></span><span class="code-line">    <span class="k">async</span> <span class="k">with</span> <span class="n">asyncio</span><span class="p">.</span><span class="nc">TaskGroup</span><span class="p">()</span> <span class="k">as</span> <span class="n">tg</span><span class="p">:</span></span><span class="code-line">        <span class="n">task1</span> <span class="o">=</span> <span class="n">tg</span><span class="p">.</span><span class="nf">create_task</span><span class="p">(</span><span class="nf">fetch_data</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span></span><span class="code-line">        <span class="n">task2</span> <span class="o">=</span> <span class="n">tg</span><span class="p">.</span><span class="nf">create_task</span><span class="p">(</span><span class="nf">fetch_data</span><span class="p">(</span><span class="mi">2</span><span class="p">))</span></span><span class="code-line">        <span class="n">task3</span> <span class="o">=</span> <span class="n">tg</span><span class="p">.</span><span class="nf">create_task</span><span class="p">(</span><span class="nf">fetch_data</span><span class="p">(</span><span class="mi">3</span><span class="p">))</span></span><span class="code-line">        <span class="n">task4</span> <span class="o">=</span> <span class="n">tg</span><span class="p">.</span><span class="nf">create_task</span><span class="p">(</span><span class="nf">fetch_data</span><span class="p">(</span><span class="mi">4</span><span class="p">))</span></span><span class="code-line"></span><span class="code-line">    <span class="nf">print</span><span class="p">(</span><span class="n">task1</span><span class="p">.</span><span class="nf">result</span><span class="p">())</span></span><span class="code-line">    <span class="nf">print</span><span class="p">(</span><span class="n">task2</span><span class="p">.</span><span class="nf">result</span><span class="p">())</span></span><span class="code-line">    <span class="nf">print</span><span class="p">(</span><span class="n">task3</span><span class="p">.</span><span class="nf">result</span><span class="p">())</span></span><span class="code-line">    <span class="nf">print</span><span class="p">(</span><span class="n">task4</span><span class="p">.</span><span class="nf">result</span><span class="p">())</span></span><span class="code-line"></span><span class="code-line"></span><span class="code-line"><span class="n">asyncio</span><span class="p">.</span><span class="nf">run</span><span class="p">(</span><span class="nf">main</span><span class="p">())</span></span></code></pre></div></div>

<h1 id="concluzie">Concluzie</h1>

<p>Sper că acest articol ți-a oferit o introducere de bază în modelele de concurență din Python. Nu am acoperit primitivele de sincronizare;
de obicei am folosit doar mutex-ul, asyncio.shield și asyncio.Lock. În opinia mea, atunci când scrii aplicații pentru utilizatori și
servicii web, este mai bine să păstrezi lucrurile mai simple.</p>

<p>Documentația oficială Python este o resursă excelentă pentru a aprofunda aceste subiecte.</p>

<p>Mulțumesc că ai citit!</p>]]></content>
    <category term="Python" />
    <category term="python" />
    <category term="threads" />
    <category term="concurrency" />
    <category term="multiprocessing" />
    <category term="asyncio" />
    <category term="event loop" />
    <summary type="html"><![CDATA[Introducere Salut tuturor! 👋 Acest articol va fi un tur simplu și de bază al modelelor de concurență din Python. Vom începe cu thread-urile, apoi vom trece la procese și, în final, vom acoperi asyncio. Îți voi arăta cum să interacționezi cu aceste mecanisme folosind câteva API-uri de nivel jos, apoi vom trece la API-uri de nivel mai înalt, mai moderne. Îți voi arăta și câteva primitive utilitare pentru sincronizare. Nu voi acoperi totul, pentru că toate informațiile se găsesc pe site-ul oficial al documentației Python, dar după ce vei citi acest articol vei înțelege bine modelul de concurență din Python. Thread-uri Unul dintre modelele principale de concurență sunt thread-urile. Programul tău rulează într-un Process, iar în interiorul procesului există mai multe thread-uri. Deoarece thread-urile rulează în interiorul containerului procesului, ele partajează toate resursele acelui proces. Thread-urile pot părea că rulează în paralel, în același timp, dar de fapt nu o fac. Python are GIL (Global Interpreter Lock) și, din cauza lui, doar un thread rulează la un moment dat. Totuși, pentru că trec de la unul la altul atât de repede, pare că rulează în paralel. Pe vremuri, calculatoarele aveau un singur nucleu de CPU, iar sistemul de operare și aplicațiile îl partajau. Codul putea rula doar pe un nucleu, iar pentru ca sistemul de operare să permită multitasking-ul trebuia să aloce fiecărei aplicații o fereastră de timp pe nucleu. Același lucru se întâmplă și astăzi, dar acum avem acces la mai multe nuclee și putem rula lucrurile în paralel. În alte limbaje, precum Java, thread-urile puteau fi programate pe nuclee diferite, ceea ce le făcea să ruleze în paralel. Dar în Python, din cauza GIL-ului, se permite doar un thread să ruleze în același timp pe fiecare proces. Thread-urile sunt ușoare în comparație cu procesele, dar sunt costisitoare în comparație cu corutinele.   Coroutine Thread Process Creare Foarte ieftină Moderată Costisitoare Memorie Foarte mică Mai mare Mult mai mare Schimbare de context Foarte ieftină La nivel de OS, mai costisitoare La nivel de OS, cea mai costisitoare Spațiu de memorie Partajat Partajat Separat Programare Event loop OS OS Scară tipică Mii–milioane* Sute–mii De obicei relativ puține Deoarece la crearea unui thread trebuie să creezi și resursele partajate, fiecare thread are propria stivă, iar acest lucru nu scalează bine dacă încerci să creezi un milion de thread-uri. Codul sistemului de operare care gestionează thread-urile trebuie, de asemenea, să le programeze, iar dacă există multe thread-uri de programat, se ajunge la un blocaj și performanța scade. Același lucru se întâmplă și cu procesele. Un fork bomb este un fel de „virus” care se tot bifurcă (fork) la nesfârșit. Sistemul de operare va crea procese până când toate resursele se epuizează, iar acestea nu mai pot fi programate în mod fiabil, ceea ce forțează sistemul să se blocheze. Thread Acesta este un exemplu de cod care pornește două thread-uri; ținta este funcția thread_target, care va fi rulată pe cele două thread-uri. import randomfrom threading import Threadclass Program: def __init__(self): self._shared_x = 2 def thread_target(self): print("Hello from thread") for i in range(1_000_000): self._shared_x += random.randint(0, i) def main(self): th_one = Thread(target=self.thread_target) th_two = Thread(target=self.thread_target) th_one.start() th_two.start() th_one.join() th_two.join() print("Shared X:", self._shared_x)if __name__ == '__main__': program = Program() program.main() Trebuie să facem join la thread-uri, pentru că dacă nu le facem, thread-ul principal se termină, procesul se oprește, iar celelalte două thread-uri rămân orfane. Timer Timer este o clasă utilitară utilă atunci când vrei să programezi o acțiune. Pornește propriul thread și execută acea acțiune. Este util pentru implementarea heartbeat-urilor sau pentru verificarea unor lucruri, cum ar fi reînnoirea tokenurilor de autorizare. Iată un exemplu simplu de cod pentru afișarea unui heartbeat: from threading import Timerfrom time import sleepclass Program: def __init__(self): self._timer: Timer | None = Timer(5, self.heartbeat) def heartbeat(self): print("Heartbeat") self._timer = Timer(5, self.heartbeat) self._timer.start() def run(self): print("Running the program.") self._timer.start() while True: sleep(1) def stop(self): self._timer.cancel() self._timer = Noneif __name__ == "__main__": program = Program() try: program.run() except KeyboardInterrupt: program.stop() Concurrent Futures: Thread Pool Acest exemplu este similar cu primul, dar folosește un API de nivel mai înalt pentru programarea thread-urilor. import randomfrom concurrent.futures import ThreadPoolExecutorclass Program: def __init__(self): self._shared_x = 2 def thread_target(self, low): print("Hello from thread") for i in range(1_000_000): self._shared_x += random.randint(min(low, i), i) return self._shared_x def main(self): th_pool = ThreadPoolExecutor(max_workers=5) th_one = th_pool.submit(self.thread_target, 0) th_two = th_pool.submit(self.thread_target,2) print("Shared X after first thread", th_one.result()) print("Shared X after second thread", th_two.result()) print("Shared X:", self._shared_x)if __name__ == '__main__': program = Program() program.main() Se inițializează și un thread pool de dimensiune fixă. Ce returnează pool.submit este un Future. Poți verifica rezultatul future-ului folosind .result(), sau excepția cu .exception(). Documentația conține toate funcțiile detaliate pe care le poți apela pe un obiect Future. Procese Procesele sunt gestionate de sistemul de operare și servesc drept container pentru thread-uri. Ele sunt folosite pentru a rula lucruri în paralel. Poți ocoli limitările GIL-ului Python împărțind munca pe mai multe procese, iar dacă munca ta implică multă activitate de I/O sau de rețea, poți porni un loop asincron în fiecare proces. Crearea mai multor procese este o modalitate excelentă de a aborda o sarcină legată de CPU, iar pentru că sunt gestionate de OS, fiecare proces rulează pe propriul nucleu. Dacă ai mai multe procese decât nuclee disponibile, atunci fiecare proces va primi o fereastră de timp pe nucleul CPU. Procesele au propria memorie privată, dar API-ul îți permite să partajezi o parte din memorie cu alte procese. Pentru comunicarea între procese poți folosi Queues și Pipes. Multiprocessing API-ul multiprocessing este similar cu API-ul threading. Poți porni procese individuale sau poți folosi un pool de procese. from multiprocessing import Processdef f(name): print('hello', name)if __name__ == '__main__': p = Process(target=f, args=('bob',)) p.start() p.join() Și un exemplu de pool de procese: from multiprocessing import Pooldef f(x): return x*xif __name__ == '__main__': with Pool(5) as p: print(p.map(f, [1, 2, 3])) Exemplele sunt copiate din documentația oficială Python despre multiprocessing, care este o excelentă analiză detaliată a subiectului. Concurrent Futures: Process Pool Poți folosi același API concurrent.futures pentru a crea un pool de procese, așa cum ai crea un thread pool. În acest exemplu merită observat că variabila _shared_x nu este de fapt partajată: fiecare proces primește propria copie, iar procesul principal nu o modifică deloc. import randomfrom concurrent.futures import ProcessPoolExecutorclass Program: def __init__(self): self._shared_x = 2 def target(self, low): print("Hello from process") for i in range(1_000_000): self._shared_x += random.randint(min(low, i), i) return self._shared_x def main(self): process_pool = ProcessPoolExecutor(max_workers=5) th_one = process_pool.submit(self.target, 0) th_two = process_pool.submit(self.target,2) print("Shared X after first process", th_one.result()) print("Shared X after second process", th_two.result()) print("Shared X:", self._shared_x)if __name__ == '__main__': program = Program() program.main() Dacă rulezi exemplul, ar trebui să afișeze ceva de genul: Hello from processHello from processShared X after first process 250199267754Shared X after second process 249818236816Shared X: 2 Asyncio Asyncio este unul dintre modelele mele preferate de concurență în Python. Când dezvolți servicii web, de obicei vrei să servești mai mulți utilizatori în același timp. Înainte ca acest model să fie folosit, acest lucru se realiza creând un thread sau un proces pentru fiecare cerere. Funcționa bine, dar nu scala bine: există o limită a numărului de procese pe care le putem crea și programa, și același lucru este valabil și pentru thread-uri. Și hai să fim sinceri: cu API-urile web de astăzi, majoritatea cererilor înseamnă doar preluarea unor date dintr-o bază de date sau cache, un apel de rețea sau calcule numerice. Crearea unui proces nou pentru fiecare cerere ar fi exagerat. O aplicație construită cu asyncio în minte folosește de obicei un singur proces, iar pe acel proces rulează event loop-ul; în loc de thread-uri, folosim corutine, care sunt similare. Thread-urile sunt de obicei gestionate de sistemul de operare, ceea ce este mai puțin eficient, iar corutinele sunt gestionate de interpretorul Python. Thread-urile sunt întrerupte de OS pentru a permite rularea altor thread-uri/procese, iar corutinele îți spun exact când pot fi întrerupte, folosind un cuvânt cheie precum await. Când o corutină cedează voluntar execuția înapoi event loop-ului, event loop-ul alege o altă corutină să ruleze. Este foarte important ca, atunci când folosești acest model, corutina să nu blocheze event loop-ul, nefiind niciodată await-ată pe alte sarcini sau petrecând mult timp cu calcule numerice, pentru că event loop-ul are un singur thread, iar asta înseamnă că celelalte lucruri nu mai vor rula. Acest model funcționează excelent pentru aplicațiile web, pentru că servesc cererile în paralel, dar dacă scrii un script simplu care face cereri în mod sincron, nu vei câștiga prea mult, de exemplu: import asyncioasync def task(): await fetch_data() # wait until finished await fetch_data() # then start this await fetch_data() await fetch_data() asyncio.run(task()) Dacă poți, rulează toate cererile în paralel cu asyncio.gather sau cu alternativa mai modernă TaskGroup. async def fetch_data(n): print(f"Fetching {n}") await asyncio.sleep(1) print(f"Finished {n}") return nasync def main(): results = await asyncio.gather( fetch_data(1), fetch_data(2), fetch_data(3), fetch_data(4), ) print(results)asyncio.run(main()) import asyncioasync def fetch_data(n): print(f"Fetching {n}") await asyncio.sleep(1) print(f"Finished {n}") return nasync def main(): async with asyncio.TaskGroup() as tg: task1 = tg.create_task(fetch_data(1)) task2 = tg.create_task(fetch_data(2)) task3 = tg.create_task(fetch_data(3)) task4 = tg.create_task(fetch_data(4)) print(task1.result()) print(task2.result()) print(task3.result()) print(task4.result())asyncio.run(main()) Concluzie Sper că acest articol ți-a oferit o introducere de bază în modelele de concurență din Python. Nu am acoperit primitivele de sincronizare; de obicei am folosit doar mutex-ul, asyncio.shield și asyncio.Lock. În opinia mea, atunci când scrii aplicații pentru utilizatori și servicii web, este mai bine să păstrezi lucrurile mai simple. Documentația oficială Python este o resursă excelentă pentru a aprofunda aceste subiecte. Mulțumesc că ai citit!]]></summary>
  </entry>
  <entry xml:lang="ro">
    <title type="html">Gitlab SSH nu mai funcționează după upgrade-ul la 19.3 pe Alma Linux</title>
    <link href="https://nuculabs.dev/ro/self%20hosting/2026/08/27/gitlab-ssh-not-working-after-upgrade.html" rel="alternate" type="text/html" title="Gitlab SSH nu mai funcționează după upgrade-ul la 19.3 pe Alma Linux" />
    <published>2026-08-27T00:00:00+03:00</published>
    <updated>2026-08-27T00:00:00+03:00</updated>
    <id>https://nuculabs.dev/ro/self%20hosting/2026/08/27/gitlab-ssh-not-working-after-upgrade</id>
    <content type="html" xml:base="https://nuculabs.dev/ro/self%20hosting/2026/08/27/gitlab-ssh-not-working-after-upgrade.html"><![CDATA[<p>Salut tuturor!</p>

<p>Am avut câteva probleme cu Gitlab în ultimele zile, după ce l-am actualizat la 19.3 împreună cu dependențele mele de Alma Linux,
push-ul și pull-ul prin SSH au încetat să funcționeze, iar cauza a fost o deriva politicilor SELinux.</p>

<p>Am încercat să rulez <code class="language-plaintext highlighter-rouge">gitlab-ctl apply-sepolicy</code>, reconfigurarea și alte comenzi, dar nimic nu părea să funcționeze. Mi-a luat
ceva timp să găsesc cauza de bază a problemei. Inițial am crezut că clientul meu SSH din Fedora este defect, și folosesc și
1Password pentru a gestiona cheile SSH. Ei bine, nu asta era problema: <code class="language-plaintext highlighter-rouge">ssh -vvv git@git.nuculabs.dev</code> este foarte detaliat
și m-a ajutat la depanare, problema era pe server.</p>

<p>Cel mai bun mod de a rezolva politicile SELinux este să folosești comanda <code class="language-plaintext highlighter-rouge">audit2allow</code>. De fiecare dată când SELinux refuză o
acțiune, daemonul o înregistrează în jurnal, iar utilitarul de mai sus te poate ajuta să generezi reguli care pot fi adăugate în SELinux.</p>

<p>După ce am rulat de mai multe ori următoarele comenzi, am obținut toate regulile lipsă.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line"><span class="nb">sudo </span>ausearch <span class="nt">-m</span> AVC <span class="nt">-ts</span> recent <span class="se">\</span></span><span class="code-line">  | audit2allow</span><span class="code-line"><span class="nb">sudo </span>ausearch <span class="nt">-m</span> AVC <span class="nt">-ts</span> recent <span class="se">\</span></span><span class="code-line">  | audit2allow</span></code></pre></div></div>

<p>Dacă ai aceleași probleme, nu trebuie să rulezi audit2allow, am salvat fișierul .te, pe care îl poți compila și
împacheta pentru a-l instala ca modul SELinux.</p>

<p>Salvează următorul conținut ca „gitlab_ssh.te”</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line">module gitlab_ssh 1.0;</span><span class="code-line"></span><span class="code-line">require {</span><span class="code-line">        type sshd_session_t;</span><span class="code-line">        type gitlab_shell_t;</span><span class="code-line">        class file { getattr open read };</span><span class="code-line">        class dir { getattr search };</span><span class="code-line">}</span><span class="code-line"></span><span class="code-line">#============= sshd_session_t ==============</span><span class="code-line"></span><span class="code-line">#!!!! Acest avc este permis în politica actuală</span><span class="code-line">allow sshd_session_t gitlab_shell_t:dir search;</span><span class="code-line">allow sshd_session_t gitlab_shell_t:dir getattr;</span><span class="code-line"></span><span class="code-line">#!!!! Acest avc este permis în politica actuală</span><span class="code-line">allow sshd_session_t gitlab_shell_t:file { getattr open read };</span></code></pre></div></div>
<p>Pentru a-l instala, execută următoarele comenzi:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line">➜  ~ checkmodule <span class="nt">-M</span> <span class="nt">-m</span> <span class="nt">-o</span> gitlab_ssh.mod gitlab_ssh.te</span><span class="code-line">➜  ~ semodule_package <span class="nt">-o</span> gitlab_ssh.pp <span class="nt">-m</span> gitlab_ssh.mod</span><span class="code-line">➜  ~ <span class="nb">sudo </span>semodule <span class="nt">-i</span> gitlab_ssh.pp</span></code></pre></div></div>

<p>Gata! Sper că ți-am economisit ceva timp de depanare.</p>]]></content>
    <category term="Self Hosting" />
    <category term="cloud" />
    <category term="linux" />
    <category term="gitlab" />
    <summary type="html"><![CDATA[Salut tuturor! Am avut câteva probleme cu Gitlab în ultimele zile, după ce l-am actualizat la 19.3 împreună cu dependențele mele de Alma Linux, push-ul și pull-ul prin SSH au încetat să funcționeze, iar cauza a fost o deriva politicilor SELinux. Am încercat să rulez gitlab-ctl apply-sepolicy, reconfigurarea și alte comenzi, dar nimic nu părea să funcționeze. Mi-a luat ceva timp să găsesc cauza de bază a problemei. Inițial am crezut că clientul meu SSH din Fedora este defect, și folosesc și 1Password pentru a gestiona cheile SSH. Ei bine, nu asta era problema: ssh -vvv git@git.nuculabs.dev este foarte detaliat și m-a ajutat la depanare, problema era pe server. Cel mai bun mod de a rezolva politicile SELinux este să folosești comanda audit2allow. De fiecare dată când SELinux refuză o acțiune, daemonul o înregistrează în jurnal, iar utilitarul de mai sus te poate ajuta să generezi reguli care pot fi adăugate în SELinux. După ce am rulat de mai multe ori următoarele comenzi, am obținut toate regulile lipsă. sudo ausearch -m AVC -ts recent \ | audit2allowsudo ausearch -m AVC -ts recent \ | audit2allow Dacă ai aceleași probleme, nu trebuie să rulezi audit2allow, am salvat fișierul .te, pe care îl poți compila și împacheta pentru a-l instala ca modul SELinux. Salvează următorul conținut ca „gitlab_ssh.te” module gitlab_ssh 1.0;require { type sshd_session_t; type gitlab_shell_t; class file { getattr open read }; class dir { getattr search };}#============= sshd_session_t ==============#!!!! Acest avc este permis în politica actualăallow sshd_session_t gitlab_shell_t:dir search;allow sshd_session_t gitlab_shell_t:dir getattr;#!!!! Acest avc este permis în politica actualăallow sshd_session_t gitlab_shell_t:file { getattr open read }; Pentru a-l instala, execută următoarele comenzi: ➜ ~ checkmodule -M -m -o gitlab_ssh.mod gitlab_ssh.te➜ ~ semodule_package -o gitlab_ssh.pp -m gitlab_ssh.mod➜ ~ sudo semodule -i gitlab_ssh.pp Gata! Sper că ți-am economisit ceva timp de depanare.]]></summary>
  </entry>
  <entry xml:lang="ro">
    <title type="html">Automatizarea configurării mele Fedora</title>
    <link href="https://nuculabs.dev/ro/linux/2026/08/15/opinionated-fedora.html" rel="alternate" type="text/html" title="Automatizarea configurării mele Fedora" />
    <published>2026-08-15T00:00:00+03:00</published>
    <updated>2026-08-15T00:00:00+03:00</updated>
    <id>https://nuculabs.dev/ro/linux/2026/08/15/opinionated-fedora</id>
    <content type="html" xml:base="https://nuculabs.dev/ro/linux/2026/08/15/opinionated-fedora.html"><![CDATA[<p>Salut tuturor!</p>

<p>Recent m-am inspirat de <a href="https://omarchy.org/">Omarchy</a> ca să creez un script pentru nevoile mele, și anume să automatizez
configurarea mea de Fedora. Ideea este similară cu <code class="language-plaintext highlighter-rouge">omakase sushi</code>, care înseamnă „las pe seama ta”: în loc să comanzi
piesă cu piesă, bucătarul de sushi știe cel mai bine ce și cum să servească.</p>

<p>Am creat acest script cu <a href="https://openai.com/codex/">codex</a> și l-am publicat pe conturile mele de Gitlab și Github:</p>

<ul>
  <li><a href="https://gitlab.nuculabs.dev/dnutiu/FedoraLaunch">https://gitlab.nuculabs.dev/dnutiu/FedoraLaunch</a></li>
  <li><a href="https://github.com/dnutiu/FedoraLaunch">https://github.com/dnutiu/FedoraLaunch</a></li>
</ul>

<p>Scriptul este adaptat gusturilor mele și configurează următoarele:</p>

<p>Mediul de dezvoltare:</p>
<ul>
  <li>Python, uv și pyenv</li>
  <li>Ruby și rbenv</li>
  <li>NodeJS și nvm</li>
  <li>Go</li>
  <li>Rust</li>
  <li>Java și sdkman</li>
  <li>Docker</li>
  <li>Helm</li>
</ul>

<p>De asemenea, configurează shell-ul să folosească zsh și instalează oh-my-zsh.</p>

<p>Aplicații software precum:</p>
<ul>
  <li>PrusaSlicer pentru imprimare 3D</li>
  <li>Software CAD: KiCAD, FreeCAD</li>
  <li>Snap</li>
  <li>Obsidian</li>
  <li>1Password</li>
  <li>Codex</li>
  <li>Hey Mail</li>
  <li>VLC</li>
  <li>qBittorrent</li>
  <li>Steam</li>
  <li>Zed</li>
  <li>VSCode</li>
  <li>IntelliJ</li>
</ul>

<p>În rest, încearcă să detecteze ce drivere trebuie instalate pentru sistem, le instalează împreună cu codecurile suplimentare
și activează accelerarea hardware. De asemenea, pregătește structura de directoare pentru montarea NAS-ului meu.</p>

<p>Atât! Înainte de acest script, trebuia să petrec câteva ore ca să instalez și să caut tot acest software
ca să-mi configurez sistemul pe placul meu, iar acum durează doar câteva minute.</p>

<p>Mulțumesc că ai citit!</p>]]></content>
    <category term="Linux" />
    <category term="linux" />
    <category term="fedora" />
    <category term="setup" />
    <category term="automation" />
    <category term="bash" />
    <summary type="html"><![CDATA[Salut tuturor! Recent m-am inspirat de Omarchy ca să creez un script pentru nevoile mele, și anume să automatizez configurarea mea de Fedora. Ideea este similară cu omakase sushi, care înseamnă „las pe seama ta”: în loc să comanzi piesă cu piesă, bucătarul de sushi știe cel mai bine ce și cum să servească. Am creat acest script cu codex și l-am publicat pe conturile mele de Gitlab și Github: https://gitlab.nuculabs.dev/dnutiu/FedoraLaunch https://github.com/dnutiu/FedoraLaunch Scriptul este adaptat gusturilor mele și configurează următoarele: Mediul de dezvoltare: Python, uv și pyenv Ruby și rbenv NodeJS și nvm Go Rust Java și sdkman Docker Helm De asemenea, configurează shell-ul să folosească zsh și instalează oh-my-zsh. Aplicații software precum: PrusaSlicer pentru imprimare 3D Software CAD: KiCAD, FreeCAD Snap Obsidian 1Password Codex Hey Mail VLC qBittorrent Steam Zed VSCode IntelliJ În rest, încearcă să detecteze ce drivere trebuie instalate pentru sistem, le instalează împreună cu codecurile suplimentare și activează accelerarea hardware. De asemenea, pregătește structura de directoare pentru montarea NAS-ului meu. Atât! Înainte de acest script, trebuia să petrec câteva ore ca să instalez și să caut tot acest software ca să-mi configurez sistemul pe placul meu, iar acum durează doar câteva minute. Mulțumesc că ai citit!]]></summary>
  </entry>
  <entry xml:lang="ro">
    <title type="html">Experiența mea cu GLM 5.2</title>
    <link href="https://nuculabs.dev/ro/artificial%20intelligence/2026/08/15/my-experience-with-glm.html" rel="alternate" type="text/html" title="Experiența mea cu GLM 5.2" />
    <published>2026-08-15T00:00:00+03:00</published>
    <updated>2026-08-15T00:00:00+03:00</updated>
    <id>https://nuculabs.dev/ro/artificial%20intelligence/2026/08/15/my-experience-with-glm</id>
    <content type="html" xml:base="https://nuculabs.dev/ro/artificial%20intelligence/2026/08/15/my-experience-with-glm.html"><![CDATA[<p>Salut tuturor,</p>

<p>M-am abonat la OpenCode[1] săptămâna trecută doar ca să încerc GLM 5.2 și, până acum, sunt destul de impresionat de el. Folosesc zilnic Claude Opus 4.8 și am folosit din când în când și DeepSeek V4 Pro și Kimi K2.6.</p>

<p>În general, GLM a fost mai bun decât Kimi la design. Am schimbat complet tema blogului meu, de la aspectul implicit al Jekyll Minimal Mistakes la o temă cu pixel art inspirată din Terraria.</p>

<p>Kimi a creat tema inițială, care era interesantă și era ceea ce urmăream atunci, apoi GLM a făcut-o și mai bună. A înlocuit fundalul CSS cu o adevărată dală de plăci, a scris un script Python care a generat dala în pixel art și apoi a setat-o ca fundal pentru blog.</p>

<p>Apoi a adăugat tot felul de lucruri amuzante: un subsol reproiectat, particule de lavă, realizări, un antet animat cu copaci și nori și a făcut antetul să știe dacă e zi sau noapte, anotimpul iarnă și alte lucruri, și zombi! În total, e destul de tare. Am încercat și efecte interesante cu Kimi, dar nu au nimerit tocmai.</p>

<p>L-am folosit și ca să generez două SDK-uri pentru facturare, pentru software-ul meu de facturare SmartBill[2].</p>

<p class="post-figure"><img src="/assets/images/2026/my-glm-experience/billling-sdk.jpg" alt="" /></p>

<p>Unul este SDK-ul în Ruby[3], iar celălalt este în Python[4].</p>

<p>Ca să generez clienții, i-am dat modelului specificația swagger și i-am spus să genereze clienții. Apoi l-am ghidat ca codul să urmeze cele mai bune practici, să fie structurat corect și să folosească biblioteci moderne pentru cereri și validarea modelelor.</p>

<p>Testul final a fost să-i spun modelului să adauge în repository abilități pentru agenți, apoi să folosească aceste abilități ca să creeze o ciornă de factură pe platformă. Și a funcționat.</p>

<p>Să poți spune unui agent: „Creează o factură pentru X” și apoi să ai factura gata este destul de tare, cel puțin în opinia mea.</p>

<p>Am instalat și agentul Hermes[5] în weekendul acesta și m-am jucat puțin cu el. Nu am făcut nimic complicat, doar un commit în git, vorbind cu el prin iMessage. Îmi pot imagina o lume în care adăugăm astfel de abilități agenților și atingem obiective prin mesaje text sau vorbind cu agentul.</p>

<p>Totuși, acum nu rulez agentul. Cred că e periculos să-i dai permisiune și acces la lucrurile mele, iar costurile abonamentului descurajează: dacă vrei voce, trebuie să plătești. Dacă vrei căutare, trebuie să plătești. Dacă vrei analiză de imagini, trebuie să plătești. Trebuie să plătești și pentru model. Deci mă aștept să plătesc și ca să-i scriu în viitor, pe când acum iMessage e gratuit.</p>

<p>Îmi plac lucrurile simple, iar mie configurația asta „agentică” mi se pare prea complicată și fragilă. Pentru moment voi crea facturile manual și mă bucur că nu trebuie să plătesc 5 abonamente diferite, dar a fost un experiment distractiv.</p>

<p>[1] - <a href="https://opencode.ai/go">https://opencode.ai/go</a></p>

<p>[2] - <a href="https://www.smartbill.ro">https://www.smartbill.ro</a></p>

<p>[3] - <a href="https://github.com/dnutiu/SmartBill-Ruby-SDK">https://github.com/dnutiu/SmartBill-Ruby-SDK</a></p>

<p>[4] - <a href="https://github.com/dnutiu/SmartBill-Python-SDK">https://github.com/dnutiu/SmartBill-Python-SDK</a></p>

<p>[5] - <a href="https://hermes-agent.nousresearch.com/">https://hermes-agent.nousresearch.com/</a></p>]]></content>
    <category term="Artificial Intelligence" />
    <category term="codex" />
    <category term="agentic coding" />
    <category term="programming" />
    <category term="glm" />
    <summary type="html"><![CDATA[Salut tuturor, M-am abonat la OpenCode[1] săptămâna trecută doar ca să încerc GLM 5.2 și, până acum, sunt destul de impresionat de el. Folosesc zilnic Claude Opus 4.8 și am folosit din când în când și DeepSeek V4 Pro și Kimi K2.6. În general, GLM a fost mai bun decât Kimi la design. Am schimbat complet tema blogului meu, de la aspectul implicit al Jekyll Minimal Mistakes la o temă cu pixel art inspirată din Terraria. Kimi a creat tema inițială, care era interesantă și era ceea ce urmăream atunci, apoi GLM a făcut-o și mai bună. A înlocuit fundalul CSS cu o adevărată dală de plăci, a scris un script Python care a generat dala în pixel art și apoi a setat-o ca fundal pentru blog. Apoi a adăugat tot felul de lucruri amuzante: un subsol reproiectat, particule de lavă, realizări, un antet animat cu copaci și nori și a făcut antetul să știe dacă e zi sau noapte, anotimpul iarnă și alte lucruri, și zombi! În total, e destul de tare. Am încercat și efecte interesante cu Kimi, dar nu au nimerit tocmai. L-am folosit și ca să generez două SDK-uri pentru facturare, pentru software-ul meu de facturare SmartBill[2]. Unul este SDK-ul în Ruby[3], iar celălalt este în Python[4]. Ca să generez clienții, i-am dat modelului specificația swagger și i-am spus să genereze clienții. Apoi l-am ghidat ca codul să urmeze cele mai bune practici, să fie structurat corect și să folosească biblioteci moderne pentru cereri și validarea modelelor. Testul final a fost să-i spun modelului să adauge în repository abilități pentru agenți, apoi să folosească aceste abilități ca să creeze o ciornă de factură pe platformă. Și a funcționat. Să poți spune unui agent: „Creează o factură pentru X” și apoi să ai factura gata este destul de tare, cel puțin în opinia mea. Am instalat și agentul Hermes[5] în weekendul acesta și m-am jucat puțin cu el. Nu am făcut nimic complicat, doar un commit în git, vorbind cu el prin iMessage. Îmi pot imagina o lume în care adăugăm astfel de abilități agenților și atingem obiective prin mesaje text sau vorbind cu agentul. Totuși, acum nu rulez agentul. Cred că e periculos să-i dai permisiune și acces la lucrurile mele, iar costurile abonamentului descurajează: dacă vrei voce, trebuie să plătești. Dacă vrei căutare, trebuie să plătești. Dacă vrei analiză de imagini, trebuie să plătești. Trebuie să plătești și pentru model. Deci mă aștept să plătesc și ca să-i scriu în viitor, pe când acum iMessage e gratuit. Îmi plac lucrurile simple, iar mie configurația asta „agentică” mi se pare prea complicată și fragilă. Pentru moment voi crea facturile manual și mă bucur că nu trebuie să plătesc 5 abonamente diferite, dar a fost un experiment distractiv. [1] - https://opencode.ai/go [2] - https://www.smartbill.ro [3] - https://github.com/dnutiu/SmartBill-Ruby-SDK [4] - https://github.com/dnutiu/SmartBill-Python-SDK [5] - https://hermes-agent.nousresearch.com/]]></summary>
  </entry>
  <entry xml:lang="ro">
    <title type="html">Recenzie tastatură split ZSA Moonlander</title>
    <link href="https://nuculabs.dev/ro/life/2026/08/01/moonlander-review.html" rel="alternate" type="text/html" title="Recenzie tastatură split ZSA Moonlander" />
    <published>2026-08-01T00:00:00+03:00</published>
    <updated>2026-08-01T00:00:00+03:00</updated>
    <id>https://nuculabs.dev/ro/life/2026/08/01/moonlander-review</id>
    <content type="html" xml:base="https://nuculabs.dev/ro/life/2026/08/01/moonlander-review.html"><![CDATA[<p>Salut tuturor! 👋</p>

<p>Au trecut ceva vreme de când nu am mai recenzat o tastatură pe acest blog.
Ultima mea recenzie a fost pentru tastatura Logitech MX Keys, iar de atunci am
schimbat două tastaturi, iar am ajuns la ZSA Moonlander, la recomandarea cuiva
mai experimentat cu tastaturile.</p>

<p>Tastatura Logitech s-a stricat după 2 ani, așa că m-am gândit să iau tot o
tastatură cu membrane. Am luat un Keychron B1 Pro, care avea un preț bun,
cam 50$. A fost în regulă un timp, dar nu are taste luminate și nici picioruște
reglabile. A trebuit să printez 3D niște picioruște pe care le-am lipit apoi de
ea. Per total, o tastatură decentă pentru preț.</p>

<p>După aceea am vrut să încerc o tastatură mecanică și am cumpărat un Numphy Air V2 75%
cu switch-uri brown. Nu mi-a plăcut această tastatură. A trebuit să fac o
actualizare de firmware ca să o pot configura din software, iar nu m-am putut
obișnui cu layout-ul, era ciudat. Carcasa tastaturii părea totuși solidă.
Am returnat tastatura, iar vânzătorul nu a mai așteptat-o, mi-a dat banii înapoi
integral, ceea ce mi s-a părut puțin ciudat, pentru că nu mă așteptam la asta. Până la
urmă am vândut tastatura cuiva cu aproximativ 100$.</p>

<p>Apoi am luat un Keychron K5 Max, cam cu 100$, cu switch-uri red. Speram ca
switch-urile red să fie silențioase, dar nu sunt chiar silențioase,
nici foarte zgomotoase. Tastatura este excelentă, am păstrat-o și am dat-o
soției mele. Singurul dezavantaj este că keycap-urile nu sunt transparente, iar
când e întuneric nu se vede direct litera luminată.</p>

<p>Acum am tastatura mea split Moonlander cu switch-uri cherry mx red silent de cam 3 săptămâni.
A fost destul de scumpă, iar când am primit-o, la început am crezut că am greșit,
pentru că layout-ul este diferit și nu puteam scrie niciun cuvânt pe ea.</p>

<p>Pe măsură ce am folosit-o tot mai mult, am început să o îndrăgesc și am fost
cu adevărat impresionat de software și de opțiunile de configurare.</p>

<p>Poți asocia fiecărei taste orice vrei și poți personaliza și RGB-ul, ca să le
colorezi după cum dorești. Keycap-urile sunt transparente, iar noaptea
se vede litera luminată.</p>

<p>O altă surpriză sunt switch-urile. Cherry mx red silent sunt de fapt silențioase
comparativ cu ce aveam înainte, nu au sunet clicky, iar uneori par chiar mai
silențioase decât tastatura cu membrane a laptopului meu.</p>

<p>Configuratorul este bazat pe web și ușor de folosit. L-am folosit pe Windows, Linux
și macOS. Pe Linux trebuie să adaugi niște reguli udev ca să poți flasha
firmware-ul și configurația pe tastatură, iar software-ul te ghidează
și îți oferă regulile.</p>

<p>Nu poți flasha firmware-ul pe tastatură fără să apeși butonul de flash cu un știft,
ceea ce înseamnă că este destul de greu, dacă nu chiar imposibil, să flashezi firmware-ul
dacă nu ai la dispoziție un ac sau un știft subțire.</p>

<p>În ceea ce privește configurarea, poți face următoarele:</p>
<ul>
  <li>Să asociezi fiecare tastă cu ce vrei când este apăsată scurt.</li>
  <li>Să schimbi comportamentul tastei când este apăsată de două ori.</li>
  <li>Să schimbi comportamentul tastei când este apăsată scurt și ținută. (Nu sunt sigur cum funcționează)</li>
  <li>Să schimbi comportamentul tastei când este ținută apăsată.</li>
  <li>Să atribui o culoare RGB fiecărei taste în parte.</li>
  <li>Să atribui acțiuni personalizate tastelor, precum: schimbare de layer, combinații, animații, acțiuni de mouse.</li>
  <li>Să creezi combinații de taste, de exemplu: A + S = o tastă personalizată, ca ESC sau Print Screen</li>
  <li>Să atribui macro-uri tastelor, de exemplu: când apeși o tastă se trimite Alt+F4</li>
</ul>

<p>Opțiunile de configurare sunt uimitoare, dar asta înseamnă și că nimeni nu va putea
folosi tastatura ta dacă ai o configurație personalizată, pentru că trebuie să
îți amintești tastele sau să te bazezi pe memoria musculară.</p>

<p>Următoarea funcție excelentă a tastaturii sunt layer-ele. Momentan am 3 layere
configurate.</p>
<ul>
  <li>Primul layer este pentru scris.</li>
  <li>Al doilea layer este pentru simboluri, pentru programare, și ca un numpad personalizat.</li>
  <li>Al treilea layer este pentru controalele multimedia și pentru animațiile RGB ale tastaturii.</li>
</ul>

<p>Layer-ele sunt foarte tari. Odată ce te obișnuiești cu ele, extind enorm
ceea ce poți face cu tastatura:</p>

<p class="post-figure"><img src="/assets/images/2026/moonlander-review/layer0.png" alt="" /></p>

<p class="post-figure"><img src="/assets/images/2026/moonlander-review/layer1.png" alt="" /></p>

<hr />

<p>Cea mai bună funcție a tastaturii este confortul pe care îl oferă mâinilor
și posturii tale. Am cumpărat-o în principal pentru că simțeam durere în mâini,
iar am început să folosesc mănuși de compresie și un mouse vertical. Acestea m-au ajutat
într-o oarecare măsură, dar durerea mi-a dispărut după o săptămână cu tastatura.</p>

<p>Pe măsură ce folosesc tastatura, mă obișnuiesc tot mai mult cu ea, iar dacă în prima
săptămână scriam 11 cuvinte pe minut, comparativ cu 90 acum, este și mai bine.</p>

<p>Asta e tot, sper că ți-a plăcut recenzia mea și îți mulțumesc că ai citit! 📚</p>]]></content>
    <category term="Life" />
    <category term="keyboard" />
    <category term="moonlander" />
    <category term="zsa" />
    <category term="Review" />
    <summary type="html"><![CDATA[Salut tuturor! 👋 Au trecut ceva vreme de când nu am mai recenzat o tastatură pe acest blog. Ultima mea recenzie a fost pentru tastatura Logitech MX Keys, iar de atunci am schimbat două tastaturi, iar am ajuns la ZSA Moonlander, la recomandarea cuiva mai experimentat cu tastaturile. Tastatura Logitech s-a stricat după 2 ani, așa că m-am gândit să iau tot o tastatură cu membrane. Am luat un Keychron B1 Pro, care avea un preț bun, cam 50$. A fost în regulă un timp, dar nu are taste luminate și nici picioruște reglabile. A trebuit să printez 3D niște picioruște pe care le-am lipit apoi de ea. Per total, o tastatură decentă pentru preț. După aceea am vrut să încerc o tastatură mecanică și am cumpărat un Numphy Air V2 75% cu switch-uri brown. Nu mi-a plăcut această tastatură. A trebuit să fac o actualizare de firmware ca să o pot configura din software, iar nu m-am putut obișnui cu layout-ul, era ciudat. Carcasa tastaturii părea totuși solidă. Am returnat tastatura, iar vânzătorul nu a mai așteptat-o, mi-a dat banii înapoi integral, ceea ce mi s-a părut puțin ciudat, pentru că nu mă așteptam la asta. Până la urmă am vândut tastatura cuiva cu aproximativ 100$. Apoi am luat un Keychron K5 Max, cam cu 100$, cu switch-uri red. Speram ca switch-urile red să fie silențioase, dar nu sunt chiar silențioase, nici foarte zgomotoase. Tastatura este excelentă, am păstrat-o și am dat-o soției mele. Singurul dezavantaj este că keycap-urile nu sunt transparente, iar când e întuneric nu se vede direct litera luminată. Acum am tastatura mea split Moonlander cu switch-uri cherry mx red silent de cam 3 săptămâni. A fost destul de scumpă, iar când am primit-o, la început am crezut că am greșit, pentru că layout-ul este diferit și nu puteam scrie niciun cuvânt pe ea. Pe măsură ce am folosit-o tot mai mult, am început să o îndrăgesc și am fost cu adevărat impresionat de software și de opțiunile de configurare. Poți asocia fiecărei taste orice vrei și poți personaliza și RGB-ul, ca să le colorezi după cum dorești. Keycap-urile sunt transparente, iar noaptea se vede litera luminată. O altă surpriză sunt switch-urile. Cherry mx red silent sunt de fapt silențioase comparativ cu ce aveam înainte, nu au sunet clicky, iar uneori par chiar mai silențioase decât tastatura cu membrane a laptopului meu. Configuratorul este bazat pe web și ușor de folosit. L-am folosit pe Windows, Linux și macOS. Pe Linux trebuie să adaugi niște reguli udev ca să poți flasha firmware-ul și configurația pe tastatură, iar software-ul te ghidează și îți oferă regulile. Nu poți flasha firmware-ul pe tastatură fără să apeși butonul de flash cu un știft, ceea ce înseamnă că este destul de greu, dacă nu chiar imposibil, să flashezi firmware-ul dacă nu ai la dispoziție un ac sau un știft subțire. În ceea ce privește configurarea, poți face următoarele: Să asociezi fiecare tastă cu ce vrei când este apăsată scurt. Să schimbi comportamentul tastei când este apăsată de două ori. Să schimbi comportamentul tastei când este apăsată scurt și ținută. (Nu sunt sigur cum funcționează) Să schimbi comportamentul tastei când este ținută apăsată. Să atribui o culoare RGB fiecărei taste în parte. Să atribui acțiuni personalizate tastelor, precum: schimbare de layer, combinații, animații, acțiuni de mouse. Să creezi combinații de taste, de exemplu: A + S = o tastă personalizată, ca ESC sau Print Screen Să atribui macro-uri tastelor, de exemplu: când apeși o tastă se trimite Alt+F4 Opțiunile de configurare sunt uimitoare, dar asta înseamnă și că nimeni nu va putea folosi tastatura ta dacă ai o configurație personalizată, pentru că trebuie să îți amintești tastele sau să te bazezi pe memoria musculară. Următoarea funcție excelentă a tastaturii sunt layer-ele. Momentan am 3 layere configurate. Primul layer este pentru scris. Al doilea layer este pentru simboluri, pentru programare, și ca un numpad personalizat. Al treilea layer este pentru controalele multimedia și pentru animațiile RGB ale tastaturii. Layer-ele sunt foarte tari. Odată ce te obișnuiești cu ele, extind enorm ceea ce poți face cu tastatura: Cea mai bună funcție a tastaturii este confortul pe care îl oferă mâinilor și posturii tale. Am cumpărat-o în principal pentru că simțeam durere în mâini, iar am început să folosesc mănuși de compresie și un mouse vertical. Acestea m-au ajutat într-o oarecare măsură, dar durerea mi-a dispărut după o săptămână cu tastatura. Pe măsură ce folosesc tastatura, mă obișnuiesc tot mai mult cu ea, iar dacă în prima săptămână scriam 11 cuvinte pe minut, comparativ cu 90 acum, este și mai bine. Asta e tot, sper că ți-a plăcut recenzia mea și îți mulțumesc că ai citit! 📚]]></summary>
  </entry>
  <entry xml:lang="ro">
    <title type="html">Cum conectezi Nintendo Pro Controller 2 la Linux</title>
    <link href="https://nuculabs.dev/ro/linux/2026/07/30/nintedo-pro-controller-linux.html" rel="alternate" type="text/html" title="Cum conectezi Nintendo Pro Controller 2 la Linux" />
    <published>2026-07-30T00:00:00+03:00</published>
    <updated>2026-07-30T00:00:00+03:00</updated>
    <id>https://nuculabs.dev/ro/linux/2026/07/30/nintedo-pro-controller-linux</id>
    <content type="html" xml:base="https://nuculabs.dev/ro/linux/2026/07/30/nintedo-pro-controller-linux.html"><![CDATA[<p>Salut,</p>

<p>Acesta este un scurt articol despre conectarea Nintendo Switch 2 Pro Controller la un calculator cu Linux.</p>

<p>Folosesc controllerul pe computerul meu personal ca să joc jocuri pe Steam. Steam suportă controllerul, iar pentru ca el să fie recunoscut trebuie doar să adaugi câteva reguli noi pentru udev și să le reîncarci.</p>

<p>Pentru asta, creează următorul fișier cu nano:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line"><span class="nb">sudo </span>nano /etc/udev/rules.d/50-nintendo-switch.rules</span></code></pre></div></div>

<p>Apoi lipește în el conținutul regulilor udev:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line">SUBSYSTEM=="usb", ATTR{idVendor}=="057e", ATTR{idProduct}=="2066", MODE="0666"</span><span class="code-line">SUBSYSTEM=="usb", ATTR{idVendor}=="057e", ATTR{idProduct}=="2067", MODE="0666"</span><span class="code-line">SUBSYSTEM=="usb", ATTR{idVendor}=="057e", ATTR{idProduct}=="2069", MODE="0666"</span><span class="code-line">SUBSYSTEM=="usb", ATTR{idVendor}=="057e", ATTR{idProduct}=="2073", MODE="0666"</span><span class="code-line">SUBSYSTEM=="hidraw", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="2066", MODE="0666"</span><span class="code-line">SUBSYSTEM=="hidraw", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="2067", MODE="0666"</span><span class="code-line">SUBSYSTEM=="hidraw", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="2069", MODE="0666"</span><span class="code-line">SUBSYSTEM=="hidraw", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="2073", MODE="0666"</span></code></pre></div></div>

<p>Apoi rulează:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line"><span class="nb">sudo </span>udevadm control <span class="nt">--reload-rules</span></span></code></pre></div></div>

<p>Gata! Ar trebui să vezi controllerul în Steam când este conectat <strong>prin cablul USB</strong>, Bluetooth-ul nu funcționează încă.</p>

<p>Mulțumesc că ai citit!</p>]]></content>
    <category term="Linux" />
    <category term="linux" />
    <category term="gaming" />
    <category term="udev" />
    <category term="controller" />
    <summary type="html"><![CDATA[Salut, Acesta este un scurt articol despre conectarea Nintendo Switch 2 Pro Controller la un calculator cu Linux. Folosesc controllerul pe computerul meu personal ca să joc jocuri pe Steam. Steam suportă controllerul, iar pentru ca el să fie recunoscut trebuie doar să adaugi câteva reguli noi pentru udev și să le reîncarci. Pentru asta, creează următorul fișier cu nano: sudo nano /etc/udev/rules.d/50-nintendo-switch.rules Apoi lipește în el conținutul regulilor udev: SUBSYSTEM=="usb", ATTR{idVendor}=="057e", ATTR{idProduct}=="2066", MODE="0666"SUBSYSTEM=="usb", ATTR{idVendor}=="057e", ATTR{idProduct}=="2067", MODE="0666"SUBSYSTEM=="usb", ATTR{idVendor}=="057e", ATTR{idProduct}=="2069", MODE="0666"SUBSYSTEM=="usb", ATTR{idVendor}=="057e", ATTR{idProduct}=="2073", MODE="0666"SUBSYSTEM=="hidraw", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="2066", MODE="0666"SUBSYSTEM=="hidraw", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="2067", MODE="0666"SUBSYSTEM=="hidraw", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="2069", MODE="0666"SUBSYSTEM=="hidraw", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="2073", MODE="0666" Apoi rulează: sudo udevadm control --reload-rules Gata! Ar trebui să vezi controllerul în Steam când este conectat prin cablul USB, Bluetooth-ul nu funcționează încă. Mulțumesc că ai citit!]]></summary>
  </entry>
  <entry xml:lang="ro">
    <title type="html">Introducere în arborii Huffman</title>
    <link href="https://nuculabs.dev/ro/programming/python/data%20structures%20&amp;%20algorithms/featured/2026/07/15/huffman-trees.html" rel="alternate" type="text/html" title="Introducere în arborii Huffman" />
    <published>2026-07-15T00:00:00+03:00</published>
    <updated>2026-07-15T00:00:00+03:00</updated>
    <id>https://nuculabs.dev/ro/programming/python/data%20structures%20&amp;%20algorithms/featured/2026/07/15/huffman-trees</id>
    <content type="html" xml:base="https://nuculabs.dev/ro/programming/python/data%20structures%20&amp;%20algorithms/featured/2026/07/15/huffman-trees.html"><![CDATA[<p>Salut tuturor! 👋</p>

<p>Acest articol este despre arborii Huffman. Am făcut acest proiect la facultate și, după vreo zece ani, am vrut să-l reiau
ca să-mi consolidez înțelegerea și să exersez codificarea manuală.</p>

<h1 id="introducere">Introducere</h1>

<p>Arborele de codificare Huffman este o structură de date fundamentală în informatică și este folosit pentru compresia
fără pierderi a datelor și pentru codificarea entropică. Arborii de codificare Huffman sunt deja folosiți în ZIP, Mp3, Png
pentru compresie, adesea nu singuri, ci împreună cu alți algoritmi de compresie a datelor.</p>

<p>Entropia reprezintă aleatorietatea datelor.</p>

<p>Să luăm <code class="language-plaintext highlighter-rouge">roooar</code>: are 6 caractere și 3 caractere unice, iar entropia este scăzută din cauza caracterelor care se repetă,
ceea ce implică mai puțină incertitudine. Pentru <code class="language-plaintext highlighter-rouge">123456</code>, sunt 6 caractere și 6 caractere unice, entropia este ridicată
pentru că datele arată ca fiind „aleatorii”, există mai multă incertitudine.</p>

<p>Codificarea entropică urmărește să codifice datele cu entropie scăzută astfel încât să poată fi comprimate mai eficient. Pentru <code class="language-plaintext highlighter-rouge">roooar</code>
ai nevoie de 6 octeți de date, dar atribuind coduri poți folosi mai puțini octeți. De exemplu, poți atribui următorii
biți literelor:</p>

<table>
  <thead>
    <tr>
      <th>Caracter</th>
      <th>Biți</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>r</td>
      <td>100</td>
    </tr>
    <tr>
      <td>o</td>
      <td>0</td>
    </tr>
    <tr>
      <td>a</td>
      <td>101</td>
    </tr>
  </tbody>
</table>

<p>Rezultatul va fi ceva de genul: 100,0,0,0,101,100, care reprezintă cuvântul roar folosind doar 12 biți, ceea ce înseamnă că ai
comprimat cuvântul <code class="language-plaintext highlighter-rouge">roooar</code> de la 6 octeți la 2 octeți. Va trebui să completezi cei 12 biți până ajungi la 16
biți, adică doi octeți.</p>

<p>Dacă vrei să implementezi singur arborii Huffman, derulează până la secțiunea Referințe și consultă linkurile. În continuare
îți voi arăta și discuta implementarea mea în Python.</p>

<h1 id="implementarea-unui-instrument-de-compresie">Implementarea unui instrument de compresie</h1>

<p>Să parcurgem un instrument în Python care comprimă datele folosind arborii de codificare Huffman. Implementarea completă
poate fi găsită în conturile mele de Github și de GitLab self-hosted.</p>

<ul>
  <li><a href="https://github.com/dnutiu/huffman-trees">https://github.com/dnutiu/huffman-trees</a></li>
  <li><a href="https://gitlab.nuculabs.dev/dnutiu/huffmantrees">https://gitlab.nuculabs.dev/dnutiu/huffmantrees</a></li>
</ul>

<h2 id="harta-frecvențelor">Harta frecvențelor</h2>

<p>Primul pas este crearea unei hărți a frecvențelor: trebuie să numeri caracterele din fișierul sursă ca să determini
caracterele și octeții care apar cel mai des.</p>

<p>În Python, poți crea o hartă a frecvențelor parcurgând octeții fișierului și numărând de câte ori apare
fiecare octet.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line">    <span class="k">def</span> <span class="nf">_build_frequency_map</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">bytes</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">int</span><span class="p">]:</span></span><span class="code-line">        <span class="sh">"""</span></span><span class="code-line"><span class="s">            Construiește harta frecvențelor numărând de câte ori apare fiecare octet în conținut.</span></span><span class="code-line"><span class="s">        :param content: Octeții conținutului sursă.</span></span><span class="code-line"><span class="s">        :return: Un dicționar reprezentând harta frecvențelor.</span></span><span class="code-line"><span class="s">        </span><span class="sh">"""</span></span><span class="code-line">        <span class="k">with</span> <span class="n">utils</span><span class="p">.</span><span class="nc">Timer</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="sh">"</span><span class="s">Build frequency map</span><span class="sh">"</span><span class="p">,</span> <span class="n">logger</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">_logger</span><span class="p">):</span></span><span class="code-line">            <span class="n">frequency_map</span> <span class="o">=</span> <span class="nf">defaultdict</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="mi">0</span><span class="p">)</span></span><span class="code-line">            <span class="k">for</span> <span class="n">byte</span> <span class="ow">in</span> <span class="n">content</span><span class="p">:</span></span><span class="code-line">                <span class="n">frequency_map</span><span class="p">[</span><span class="n">byte</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span></span><span class="code-line">            <span class="k">return</span> <span class="n">frequency_map</span></span></code></pre></div></div>

<p>Cred că această parte este destul de simplu de înțeles. Următorul pas este să folosim harta frecvențelor pentru a construi arborele.</p>

<h2 id="construirea-arborelui">Construirea arborelui</h2>

<p>Arborele Huffman are două tipuri de noduri: noduri interne și noduri frunză. Pe noi ne interesează deocamdată doar nodurile
frunză, iar nodurile interne le folosim ca să parcurgem arborele și să obținem codificarea finală.</p>

<p>Strategia este să creăm un nod frunză pentru fiecare octet și să-i atribuim o pondere egală cu frecvența sa.
Apoi luăm două elemente (noduri frunză sau interne) cu ponderea cea mai mică și construim un nod intern, căruia
îi atribuim ca pondere suma celor două noduri luate.</p>

<p>Ca să facem asta eficient, putem folosi un min heap sau o coadă de priorități minimă. Dacă vrei să rămâi simplu, poți pune
toate nodurile frunză într-un array și să creezi o funcție care returnează și elimină elementul cu ponderea minimă.
Folosești această funcție recursiv ca să iei câte două elemente deodată, construiești un nod intern și pui înapoi
nodul intern în array. Repetând asta de multe ori, vei ajunge cu un singur element în array, care este nodul rădăcină.</p>

<p>În implementarea mea am folosit un min heap ca să găsesc eficient elementul minim și să construiesc arborele.</p>

<p>În fragmentul următor am inclus întregul cod sursă al arborelui:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line"><span class="kn">import</span> <span class="n">abc</span></span><span class="code-line"><span class="kn">import</span> <span class="n">heapq</span></span><span class="code-line"><span class="kn">import</span> <span class="n">typing</span></span><span class="code-line"><span class="kn">from</span> <span class="n">functools</span> <span class="kn">import</span> <span class="n">total_ordering</span></span><span class="code-line"></span><span class="code-line"></span><span class="code-line"><span class="k">class</span> <span class="nc">BaseTreeNode</span><span class="p">(</span><span class="n">abc</span><span class="p">.</span><span class="n">ABC</span><span class="p">):</span></span><span class="code-line">    <span class="sh">"""</span></span><span class="code-line"><span class="s">    Clasă abstractă de bază care modelează un arbore Huffman.</span></span><span class="code-line"><span class="s">    </span><span class="sh">"""</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">weight</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">is_leaf</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="bp">False</span><span class="p">):</span></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="n">_weight</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">weight</span></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="n">_is_leaf</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="n">is_leaf</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">weight</span><span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span></span><span class="code-line">        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">_weight</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">is_leaf</span><span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span></span><span class="code-line">        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">_is_leaf</span></span><span class="code-line"></span><span class="code-line"></span><span class="code-line"><span class="nd">@total_ordering</span></span><span class="code-line"><span class="k">class</span> <span class="nc">LeafNode</span><span class="p">(</span><span class="n">BaseTreeNode</span><span class="p">):</span></span><span class="code-line">    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">byte_element</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">weight</span><span class="p">:</span> <span class="nb">int</span><span class="p">):</span></span><span class="code-line">        <span class="nf">super</span><span class="p">().</span><span class="nf">__init__</span><span class="p">(</span><span class="n">weight</span><span class="p">,</span> <span class="n">is_leaf</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="n">_element</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">byte_element</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">value</span><span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span></span><span class="code-line">        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">_element</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">__lt__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span></span><span class="code-line">        <span class="k">if</span> <span class="ow">not</span> <span class="nf">issubclass</span><span class="p">(</span><span class="n">other</span><span class="p">.</span><span class="n">__class__</span><span class="p">,</span> <span class="n">BaseTreeNode</span><span class="p">):</span></span><span class="code-line">            <span class="k">return</span> <span class="nb">NotImplemented</span></span><span class="code-line">        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="nf">weight</span><span class="p">()</span> <span class="o">&lt;</span> <span class="n">other</span><span class="p">.</span><span class="nf">weight</span><span class="p">()</span></span><span class="code-line"></span><span class="code-line"></span><span class="code-line"><span class="k">class</span> <span class="nc">InternalNode</span><span class="p">(</span><span class="n">BaseTreeNode</span><span class="p">):</span></span><span class="code-line">    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">left</span><span class="p">:</span> <span class="n">BaseTreeNode</span><span class="p">,</span> <span class="n">right</span><span class="p">:</span> <span class="n">BaseTreeNode</span><span class="p">):</span></span><span class="code-line">        <span class="n">weight</span> <span class="o">=</span> <span class="n">left</span><span class="p">.</span><span class="nf">weight</span><span class="p">()</span> <span class="o">+</span> <span class="n">right</span><span class="p">.</span><span class="nf">weight</span><span class="p">()</span></span><span class="code-line">        <span class="nf">super</span><span class="p">().</span><span class="nf">__init__</span><span class="p">(</span><span class="n">weight</span><span class="p">,</span> <span class="n">is_leaf</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="n">_weight</span> <span class="o">=</span> <span class="n">weight</span></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="n">_left</span> <span class="o">=</span> <span class="n">left</span></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="n">_right</span> <span class="o">=</span> <span class="n">right</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">left</span><span class="p">(</span><span class="n">self</span><span class="p">):</span></span><span class="code-line">        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">_left</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">right</span><span class="p">(</span><span class="n">self</span><span class="p">):</span></span><span class="code-line">        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">_right</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">__lt__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span></span><span class="code-line">        <span class="k">if</span> <span class="ow">not</span> <span class="nf">issubclass</span><span class="p">(</span><span class="n">other</span><span class="p">.</span><span class="n">__class__</span><span class="p">,</span> <span class="n">BaseTreeNode</span><span class="p">):</span></span><span class="code-line">            <span class="k">return</span> <span class="nb">NotImplemented</span></span><span class="code-line">        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="nf">weight</span><span class="p">()</span> <span class="o">&lt;</span> <span class="n">other</span><span class="p">.</span><span class="nf">weight</span><span class="p">()</span></span><span class="code-line"></span><span class="code-line">    <span class="nd">@staticmethod</span></span><span class="code-line">    <span class="k">def</span> <span class="nf">from_frequency_map</span><span class="p">(</span></span><span class="code-line">        <span class="n">frequency_map</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">int</span><span class="p">],</span></span><span class="code-line">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">typing</span><span class="p">.</span><span class="n">Optional</span><span class="p">[</span><span class="sh">"</span><span class="s">InternalNode</span><span class="sh">"</span><span class="p">]:</span></span><span class="code-line">        <span class="sh">"""</span></span><span class="code-line"><span class="s">            Construiește arborele Huffman dintr-o hartă a frecvențelor dată.</span></span><span class="code-line"><span class="s">        :param frequency_map: Harta frecvențelor.</span></span><span class="code-line"><span class="s">        :return: Un nod intern reprezentând rădăcina arborelui.</span></span><span class="code-line"><span class="s">        </span><span class="sh">"""</span></span><span class="code-line">        <span class="k">if</span> <span class="ow">not</span> <span class="n">frequency_map</span><span class="p">:</span></span><span class="code-line">            <span class="k">return</span> <span class="bp">None</span></span><span class="code-line">        <span class="c1"># Creează un heap</span></span><span class="code-line">        <span class="n">binary_heap</span> <span class="o">=</span> <span class="p">[]</span></span><span class="code-line">        <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">frequency_map</span><span class="p">.</span><span class="nf">items</span><span class="p">():</span></span><span class="code-line">            <span class="c1"># adaugă elementul în heap după frecvență și caracter</span></span><span class="code-line">            <span class="n">heapq</span><span class="p">.</span><span class="nf">heappush</span><span class="p">(</span><span class="n">binary_heap</span><span class="p">,</span> <span class="nc">LeafNode</span><span class="p">(</span><span class="n">byte_element</span><span class="o">=</span><span class="n">item</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">weight</span><span class="o">=</span><span class="n">item</span><span class="p">[</span><span class="mi">1</span><span class="p">]))</span></span><span class="code-line"></span><span class="code-line">        <span class="c1"># Construiește arborele Huffman</span></span><span class="code-line">        <span class="k">while</span> <span class="nf">len</span><span class="p">(</span><span class="n">binary_heap</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">:</span></span><span class="code-line">            <span class="n">left</span> <span class="o">=</span> <span class="n">heapq</span><span class="p">.</span><span class="nf">heappop</span><span class="p">(</span><span class="n">binary_heap</span><span class="p">)</span></span><span class="code-line">            <span class="n">right</span> <span class="o">=</span> <span class="n">heapq</span><span class="p">.</span><span class="nf">heappop</span><span class="p">(</span><span class="n">binary_heap</span><span class="p">)</span></span><span class="code-line">            <span class="c1">#  nod intern după frecvență, pentru a putea fi parcurs</span></span><span class="code-line">            <span class="n">heapq</span><span class="p">.</span><span class="nf">heappush</span><span class="p">(</span><span class="n">binary_heap</span><span class="p">,</span> <span class="nc">InternalNode</span><span class="p">(</span><span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">))</span></span><span class="code-line">        <span class="k">return</span> <span class="n">binary_heap</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span></span></code></pre></div></div>

<h2 id="crearea-codificărilor">Crearea codificărilor</h2>

<p>Trebuie să parcurgi arborele ca să construiești tabelul de codificare sau ca să obții codificarea pentru un singur caracter.
Ca să evit munca duplicată, am decis să parcurg arborele o singură dată și să construiesc tabelul de codificare dintr-o dată.</p>

<p>Pentru a parcurge arborele poți folosi algoritmul DFS (Depth First Search, parcurgere în adâncime). Funcționează mergând în adâncime
până la fiecare nod frunză. Odată ajuns la un nod frunză, salvezi valoarea într-o hartă, împreună cu drumul parcurs ca să ajungi
acolo: dacă ai mers la stânga, de obicei pui un 0, iar dacă ai mers la dreapta, poți pune un 1.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line">    <span class="k">def</span> <span class="nf">_build_encoding_table</span><span class="p">(</span><span class="n">self</span><span class="p">):</span></span><span class="code-line">        <span class="sh">"""</span></span><span class="code-line"><span class="s">        Construiește tabelul de codificare pentru Huffman folosind un algoritm DFS pe arbore.</span></span><span class="code-line"><span class="s">        </span><span class="sh">"""</span></span><span class="code-line">        <span class="k">with</span> <span class="n">utils</span><span class="p">.</span><span class="nc">Timer</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="sh">"</span><span class="s">Build encoding table</span><span class="sh">"</span><span class="p">,</span> <span class="n">logger</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">_logger</span><span class="p">):</span></span><span class="code-line">            <span class="n">stack</span> <span class="o">=</span> <span class="p">[]</span></span><span class="code-line">            <span class="n">root</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">_huffman_tree_root</span></span><span class="code-line">            <span class="n">stack</span><span class="p">.</span><span class="nf">append</span><span class="p">([</span><span class="n">root</span><span class="p">,</span> <span class="p">[]])</span></span><span class="code-line">            <span class="k">while</span> <span class="nf">len</span><span class="p">(</span><span class="n">stack</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span></span><span class="code-line">                <span class="n">element</span><span class="p">,</span> <span class="n">encoding</span> <span class="o">=</span> <span class="n">stack</span><span class="p">.</span><span class="nf">pop</span><span class="p">()</span></span><span class="code-line">                <span class="k">if</span> <span class="n">element</span><span class="p">.</span><span class="nf">is_leaf</span><span class="p">():</span></span><span class="code-line">                    <span class="n">self</span><span class="p">.</span><span class="n">_encoding_table</span><span class="p">[</span><span class="n">element</span><span class="p">.</span><span class="nf">value</span><span class="p">()]</span> <span class="o">=</span> <span class="sh">""</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="n">encoding</span><span class="p">)</span></span><span class="code-line">                <span class="k">else</span><span class="p">:</span></span><span class="code-line">                    <span class="nf">if </span><span class="p">(</span><span class="n">left</span> <span class="p">:</span><span class="o">=</span> <span class="n">element</span><span class="p">.</span><span class="nf">left</span><span class="p">())</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span></span><span class="code-line">                        <span class="n">stack</span><span class="p">.</span><span class="nf">append</span><span class="p">([</span><span class="n">left</span><span class="p">,</span> <span class="p">[</span><span class="o">*</span><span class="n">encoding</span><span class="p">,</span> <span class="sh">"</span><span class="s">0</span><span class="sh">"</span><span class="p">]])</span></span><span class="code-line">                    <span class="nf">if </span><span class="p">(</span><span class="n">right</span> <span class="p">:</span><span class="o">=</span> <span class="n">element</span><span class="p">.</span><span class="nf">right</span><span class="p">())</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span></span><span class="code-line">                        <span class="n">stack</span><span class="p">.</span><span class="nf">append</span><span class="p">([</span><span class="n">right</span><span class="p">,</span> <span class="p">[</span><span class="o">*</span><span class="n">encoding</span><span class="p">,</span> <span class="sh">"</span><span class="s">1</span><span class="sh">"</span><span class="p">]])</span></span></code></pre></div></div>

<h2 id="scrierea-în-fișier">Scrierea în fișier</h2>

<p>Scrierea în fișier este partea delicată, pentru că, după ce codifici fiecare element într-o secvență de biți, trebuie să
completezi numărul final de biți astfel încât să fie divizibil cu 8, iar tu poți scrie doar 8 biți (un octet) la un moment dat. Nu
poți scrie, de exemplu, <code class="language-plaintext highlighter-rouge">111</code>; trebuie să scrii un octet întreg cu completare, adică <code class="language-plaintext highlighter-rouge">11100000</code>. Am folosit pachetul Python
<code class="language-plaintext highlighter-rouge">bitstring</code> ca să simplific acest proces, iar la final am adăugat completarea manual.</p>

<p>Pe lângă conținut, trebuie să scrii în fișier și tabelul de codificare. Acesta va fi folosit pentru decodificarea
fișierului. Pentru a scrie tabelul de codificare am folosit pachetul din biblioteca standard Python <code class="language-plaintext highlighter-rouge">pickle</code>, dar poți folosi și o
bibliotecă terță, cum ar fi msgpack, ca să convertești obiectul în formă binară. Îl poți scrie și ca un simplu șir de caractere.</p>

<p>Am scris tabelul de codificare la început, împreună cu lungimea sa, ca să știu că restul octeților reprezintă
conținutul fișierului.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="code-line">    <span class="k">def</span> <span class="nf">_get_encoded_file_contents</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">bytes</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">BitArray</span><span class="p">:</span></span><span class="code-line">        <span class="sh">"""</span></span><span class="code-line"><span class="s">            Având conținutul necodificat, îl codifică cu codurile Huffman.</span></span><span class="code-line"><span class="s">        :param content: Octeții conținutului brut.</span></span><span class="code-line"><span class="s">        :return: Octeții conținutului codificat.</span></span><span class="code-line"><span class="s">        </span><span class="sh">"""</span></span><span class="code-line">        <span class="k">with</span> <span class="n">utils</span><span class="p">.</span><span class="nc">Timer</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="sh">"</span><span class="s">Encode source file</span><span class="sh">"</span><span class="p">,</span> <span class="n">logger</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">_logger</span><span class="p">):</span></span><span class="code-line">            <span class="nb">buffer</span> <span class="o">=</span> <span class="p">[]</span></span><span class="code-line">            <span class="k">for</span> <span class="n">byte</span> <span class="ow">in</span> <span class="n">content</span><span class="p">:</span></span><span class="code-line">                <span class="nb">buffer</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="nf">_encode_byte</span><span class="p">(</span><span class="n">byte</span><span class="p">))</span></span><span class="code-line">            <span class="n">bit_array</span> <span class="o">=</span> <span class="nc">BitArray</span><span class="p">(</span><span class="nb">bin</span><span class="o">=</span><span class="sh">""</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="nb">buffer</span><span class="p">))</span></span><span class="code-line">            <span class="k">return</span> <span class="n">bit_array</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">_encode_byte</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">byte</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span></span><span class="code-line">        <span class="sh">"""</span></span><span class="code-line"><span class="s">        Codifică octetul dat folosind arborele Huffman.</span></span><span class="code-line"><span class="s">        </span><span class="sh">"""</span></span><span class="code-line">        <span class="k">if</span> <span class="n">byte</span> <span class="ow">in</span> <span class="n">self</span><span class="p">.</span><span class="n">_encoding_table</span><span class="p">:</span></span><span class="code-line">            <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">_encoding_table</span><span class="p">[</span><span class="n">byte</span><span class="p">]</span></span><span class="code-line">        <span class="k">raise</span> <span class="nc">Exception</span><span class="p">(</span></span><span class="code-line">            <span class="sa">f</span><span class="sh">"</span><span class="s">Can</span><span class="sh">'</span><span class="s">t encode byte </span><span class="si">{</span><span class="n">byte</span><span class="si">}</span><span class="s">. It is not found in the encoding table.</span><span class="sh">"</span></span><span class="code-line">        <span class="p">)</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">encode</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">file_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">destination</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span></span><span class="code-line">        <span class="sh">"""</span></span><span class="code-line"><span class="s">            Codifică un fișier dat în octeți Huffman și îl scrie pe disc..</span></span><span class="code-line"><span class="s">        :param file_name: Calea către numele fișierului.</span></span><span class="code-line"><span class="s">        :param destination: Calea către numele fișierului de destinație.</span></span><span class="code-line"><span class="s">        </span><span class="sh">"""</span></span><span class="code-line">        <span class="k">with</span> <span class="nf">open</span><span class="p">(</span><span class="n">file_name</span><span class="p">,</span> <span class="sh">"</span><span class="s">rb</span><span class="sh">"</span><span class="p">)</span> <span class="k">as</span> <span class="n">fh</span><span class="p">:</span></span><span class="code-line">            <span class="n">content</span> <span class="o">=</span> <span class="n">fh</span><span class="p">.</span><span class="nf">read</span><span class="p">()</span></span><span class="code-line"></span><span class="code-line">        <span class="n">frequency_map</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">_build_frequency_map</span><span class="p">(</span><span class="n">content</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="nf">_build_huffman_tree</span><span class="p">(</span><span class="n">frequency_map</span><span class="p">)</span></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="nf">_build_encoding_table</span><span class="p">()</span></span><span class="code-line"></span><span class="code-line">        <span class="n">bit_array</span> <span class="o">=</span> <span class="nc">BitArray</span><span class="p">()</span></span><span class="code-line">        <span class="n">encoding_table</span> <span class="o">=</span> <span class="n">pickle</span><span class="p">.</span><span class="nf">dumps</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">_encoding_table</span><span class="p">)</span></span><span class="code-line">        <span class="c1"># întreg fără semn de 2 octeți pentru lungimea tabelului de codificare</span></span><span class="code-line">        <span class="n">bit_array</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="nf">pack</span><span class="p">(</span><span class="sh">"</span><span class="s">&lt;H</span><span class="sh">"</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">encoding_table</span><span class="p">)))</span></span><span class="code-line">        <span class="c1"># tabelul de codificare complet, serializat cu pickle</span></span><span class="code-line">        <span class="n">bit_array</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">encoding_table</span><span class="p">)</span></span><span class="code-line">        <span class="c1"># conținutul fișierului codificat cu Huffman</span></span><span class="code-line">        <span class="n">encoded_content</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">_get_encoded_file_contents</span><span class="p">(</span><span class="n">content</span><span class="p">)</span></span><span class="code-line">        <span class="k">if</span> <span class="nf">len</span><span class="p">(</span><span class="n">encoded_content</span><span class="p">)</span> <span class="o">%</span> <span class="mi">8</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span></span><span class="code-line">            <span class="n">encoded_content_padding_bits</span> <span class="o">=</span> <span class="mi">0</span></span><span class="code-line">        <span class="k">else</span><span class="p">:</span></span><span class="code-line">            <span class="n">encoded_content_padding_bits</span> <span class="o">=</span> <span class="mi">8</span> <span class="o">-</span> <span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">encoded_content</span><span class="p">)</span> <span class="o">%</span> <span class="mi">8</span><span class="p">)</span></span><span class="code-line">        <span class="n">bit_array</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="nf">pack</span><span class="p">(</span><span class="sh">"</span><span class="s">&lt;B</span><span class="sh">"</span><span class="p">,</span> <span class="n">encoded_content_padding_bits</span><span class="p">))</span></span><span class="code-line">        <span class="n">bit_array</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">encoded_content</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="nf">_write_to_destination_file</span><span class="p">(</span><span class="n">bit_array</span><span class="p">,</span> <span class="n">destination</span><span class="p">)</span></span><span class="code-line">        <span class="n">self</span><span class="p">.</span><span class="n">_logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Done encoding </span><span class="si">{</span><span class="n">file_name</span><span class="si">}</span><span class="s">.</span><span class="sh">"</span><span class="p">)</span></span><span class="code-line"></span><span class="code-line"><span class="c1">#...</span></span><span class="code-line"></span><span class="code-line">    <span class="k">def</span> <span class="nf">_write_to_destination_file</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="n">BitArray</span><span class="p">,</span> <span class="n">destination</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span></span><span class="code-line">        <span class="sh">"""</span></span><span class="code-line"><span class="s">            Scrie octeții în fișierul de destinație.</span></span><span class="code-line"><span class="s">        :param content: Octeții conținutului.</span></span><span class="code-line"><span class="s">        :param destination: Numele fișierului de destinație.</span></span><span class="code-line"><span class="s">        </span><span class="sh">"""</span></span><span class="code-line">        <span class="k">with</span> <span class="n">utils</span><span class="p">.</span><span class="nc">Timer</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="sh">"</span><span class="s">Write to destination</span><span class="sh">"</span><span class="p">,</span> <span class="n">logger</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">_logger</span><span class="p">):</span></span><span class="code-line">            <span class="k">with</span> <span class="nf">open</span><span class="p">(</span><span class="n">destination</span><span class="p">,</span> <span class="sh">"</span><span class="s">wb</span><span class="sh">"</span><span class="p">)</span> <span class="k">as</span> <span class="n">fh</span><span class="p">:</span></span><span class="code-line">                <span class="n">content</span><span class="p">.</span><span class="nf">tofile</span><span class="p">(</span><span class="n">fh</span><span class="p">)</span></span><span class="code-line">            <span class="n">self</span><span class="p">.</span><span class="n">_logger</span><span class="p">.</span><span class="nf">debug</span><span class="p">(</span></span><span class="code-line">                <span class="sa">f</span><span class="sh">"</span><span class="s">Wrote </span><span class="si">{</span><span class="n">content</span><span class="p">.</span><span class="nb">len</span><span class="si">}</span><span class="s"> bits to destination file: </span><span class="si">{</span><span class="n">destination</span><span class="si">}</span><span class="sh">"</span></span><span class="code-line">            <span class="p">)</span></span></code></pre></div></div>

<h2 id="decompresia">Decompresia</h2>

<p>Decompresia fișierului este un pas simplu. Poți citi tabelul de codificare, îl poți inversa, construi o hartă și poți
recupera elementele originale de acolo, apoi poți scrie octeții în fișierul de ieșire.</p>

<h1 id="concluzie">Concluzie</h1>

<p>Proiectul Huffman este unul interesant: atinge algoritmi, manipulări de biți, fișiere, I/O și este folosit
în multe aplicații reale, precum și în formate. Dacă încerci să comprimi un PNG cu el, nu va funcționa, pentru că
comprimarea de două ori nu aduce rezultate mai bune.</p>

<p>Dacă vrei să-l implementezi singur, încearcă. Mai jos găsești implementarea mea în Python și câteva referințe
pe care le-am folosit ca să o scriu.</p>

<p>Mulțumesc pentru lectură!</p>

<h1 id="referințe">Referințe</h1>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/Entropy_(computing)">https://en.wikipedia.org/wiki/Entropy_(computing)</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Entropy_coding">https://en.wikipedia.org/wiki/Entropy_coding</a></li>
  <li><a href="https://codingchallenges.fyi/challenges/challenge-huffman/">https://codingchallenges.fyi/challenges/challenge-huffman/</a></li>
  <li><a href="https://opendsa-server.cs.vt.edu/ODSA/Books/CS3/html/Huffman.html">https://opendsa-server.cs.vt.edu/ODSA/Books/CS3/html/Huffman.html</a></li>
  <li><a href="https://gitlab.nuculabs.dev/dnutiu/huffmantreespy/">https://gitlab.nuculabs.dev/dnutiu/huffmantreespy/</a></li>
  <li><a href="https://github.com/dnutiu/huffman-trees">https://github.com/dnutiu/huffman-trees</a></li>
</ul>]]></content>
    <category term="Programming" />
    <category term="Python" />
    <category term="Data Structures &amp; Algorithms" />
    <category term="Featured" />
    <category term="ruby" />
    <category term="programming" />
    <summary type="html"><![CDATA[Salut tuturor! 👋 Acest articol este despre arborii Huffman. Am făcut acest proiect la facultate și, după vreo zece ani, am vrut să-l reiau ca să-mi consolidez înțelegerea și să exersez codificarea manuală. Introducere Arborele de codificare Huffman este o structură de date fundamentală în informatică și este folosit pentru compresia fără pierderi a datelor și pentru codificarea entropică. Arborii de codificare Huffman sunt deja folosiți în ZIP, Mp3, Png pentru compresie, adesea nu singuri, ci împreună cu alți algoritmi de compresie a datelor. Entropia reprezintă aleatorietatea datelor. Să luăm roooar: are 6 caractere și 3 caractere unice, iar entropia este scăzută din cauza caracterelor care se repetă, ceea ce implică mai puțină incertitudine. Pentru 123456, sunt 6 caractere și 6 caractere unice, entropia este ridicată pentru că datele arată ca fiind „aleatorii”, există mai multă incertitudine. Codificarea entropică urmărește să codifice datele cu entropie scăzută astfel încât să poată fi comprimate mai eficient. Pentru roooar ai nevoie de 6 octeți de date, dar atribuind coduri poți folosi mai puțini octeți. De exemplu, poți atribui următorii biți literelor: Caracter Biți r 100 o 0 a 101 Rezultatul va fi ceva de genul: 100,0,0,0,101,100, care reprezintă cuvântul roar folosind doar 12 biți, ceea ce înseamnă că ai comprimat cuvântul roooar de la 6 octeți la 2 octeți. Va trebui să completezi cei 12 biți până ajungi la 16 biți, adică doi octeți. Dacă vrei să implementezi singur arborii Huffman, derulează până la secțiunea Referințe și consultă linkurile. În continuare îți voi arăta și discuta implementarea mea în Python. Implementarea unui instrument de compresie Să parcurgem un instrument în Python care comprimă datele folosind arborii de codificare Huffman. Implementarea completă poate fi găsită în conturile mele de Github și de GitLab self-hosted. https://github.com/dnutiu/huffman-trees https://gitlab.nuculabs.dev/dnutiu/huffmantrees Harta frecvențelor Primul pas este crearea unei hărți a frecvențelor: trebuie să numeri caracterele din fișierul sursă ca să determini caracterele și octeții care apar cel mai des. În Python, poți crea o hartă a frecvențelor parcurgând octeții fișierului și numărând de câte ori apare fiecare octet. def _build_frequency_map(self, content: bytes) -&gt; dict[int, int]: """ Construiește harta frecvențelor numărând de câte ori apare fiecare octet în conținut. :param content: Octeții conținutului sursă. :return: Un dicționar reprezentând harta frecvențelor. """ with utils.Timer(name="Build frequency map", logger=self._logger): frequency_map = defaultdict(lambda: 0) for byte in content: frequency_map[byte] += 1 return frequency_map Cred că această parte este destul de simplu de înțeles. Următorul pas este să folosim harta frecvențelor pentru a construi arborele. Construirea arborelui Arborele Huffman are două tipuri de noduri: noduri interne și noduri frunză. Pe noi ne interesează deocamdată doar nodurile frunză, iar nodurile interne le folosim ca să parcurgem arborele și să obținem codificarea finală. Strategia este să creăm un nod frunză pentru fiecare octet și să-i atribuim o pondere egală cu frecvența sa. Apoi luăm două elemente (noduri frunză sau interne) cu ponderea cea mai mică și construim un nod intern, căruia îi atribuim ca pondere suma celor două noduri luate. Ca să facem asta eficient, putem folosi un min heap sau o coadă de priorități minimă. Dacă vrei să rămâi simplu, poți pune toate nodurile frunză într-un array și să creezi o funcție care returnează și elimină elementul cu ponderea minimă. Folosești această funcție recursiv ca să iei câte două elemente deodată, construiești un nod intern și pui înapoi nodul intern în array. Repetând asta de multe ori, vei ajunge cu un singur element în array, care este nodul rădăcină. În implementarea mea am folosit un min heap ca să găsesc eficient elementul minim și să construiesc arborele. În fragmentul următor am inclus întregul cod sursă al arborelui: import abcimport heapqimport typingfrom functools import total_orderingclass BaseTreeNode(abc.ABC): """ Clasă abstractă de bază care modelează un arbore Huffman. """ def __init__(self, weight: int, is_leaf: bool = False): self._weight: int = weight self._is_leaf: bool = is_leaf def weight(self) -&gt; int: return self._weight def is_leaf(self) -&gt; bool: return self._is_leaf@total_orderingclass LeafNode(BaseTreeNode): def __init__(self, byte_element: int, weight: int): super().__init__(weight, is_leaf=True) self._element: int = byte_element def value(self) -&gt; int: return self._element def __lt__(self, other): if not issubclass(other.__class__, BaseTreeNode): return NotImplemented return self.weight() &lt; other.weight()class InternalNode(BaseTreeNode): def __init__(self, left: BaseTreeNode, right: BaseTreeNode): weight = left.weight() + right.weight() super().__init__(weight, is_leaf=False) self._weight = weight self._left = left self._right = right def left(self): return self._left def right(self): return self._right def __lt__(self, other): if not issubclass(other.__class__, BaseTreeNode): return NotImplemented return self.weight() &lt; other.weight() @staticmethod def from_frequency_map( frequency_map: dict[int, int], ) -&gt; typing.Optional["InternalNode"]: """ Construiește arborele Huffman dintr-o hartă a frecvențelor dată. :param frequency_map: Harta frecvențelor. :return: Un nod intern reprezentând rădăcina arborelui. """ if not frequency_map: return None # Creează un heap binary_heap = [] for item in frequency_map.items(): # adaugă elementul în heap după frecvență și caracter heapq.heappush(binary_heap, LeafNode(byte_element=item[0], weight=item[1])) # Construiește arborele Huffman while len(binary_heap) &gt; 1: left = heapq.heappop(binary_heap) right = heapq.heappop(binary_heap) # nod intern după frecvență, pentru a putea fi parcurs heapq.heappush(binary_heap, InternalNode(left, right)) return binary_heap[0] Crearea codificărilor Trebuie să parcurgi arborele ca să construiești tabelul de codificare sau ca să obții codificarea pentru un singur caracter. Ca să evit munca duplicată, am decis să parcurg arborele o singură dată și să construiesc tabelul de codificare dintr-o dată. Pentru a parcurge arborele poți folosi algoritmul DFS (Depth First Search, parcurgere în adâncime). Funcționează mergând în adâncime până la fiecare nod frunză. Odată ajuns la un nod frunză, salvezi valoarea într-o hartă, împreună cu drumul parcurs ca să ajungi acolo: dacă ai mers la stânga, de obicei pui un 0, iar dacă ai mers la dreapta, poți pune un 1. def _build_encoding_table(self): """ Construiește tabelul de codificare pentru Huffman folosind un algoritm DFS pe arbore. """ with utils.Timer(name="Build encoding table", logger=self._logger): stack = [] root = self._huffman_tree_root stack.append([root, []]) while len(stack) &gt; 0: element, encoding = stack.pop() if element.is_leaf(): self._encoding_table[element.value()] = "".join(encoding) else: if (left := element.left()) is not None: stack.append([left, [*encoding, "0"]]) if (right := element.right()) is not None: stack.append([right, [*encoding, "1"]]) Scrierea în fișier Scrierea în fișier este partea delicată, pentru că, după ce codifici fiecare element într-o secvență de biți, trebuie să completezi numărul final de biți astfel încât să fie divizibil cu 8, iar tu poți scrie doar 8 biți (un octet) la un moment dat. Nu poți scrie, de exemplu, 111; trebuie să scrii un octet întreg cu completare, adică 11100000. Am folosit pachetul Python bitstring ca să simplific acest proces, iar la final am adăugat completarea manual. Pe lângă conținut, trebuie să scrii în fișier și tabelul de codificare. Acesta va fi folosit pentru decodificarea fișierului. Pentru a scrie tabelul de codificare am folosit pachetul din biblioteca standard Python pickle, dar poți folosi și o bibliotecă terță, cum ar fi msgpack, ca să convertești obiectul în formă binară. Îl poți scrie și ca un simplu șir de caractere. Am scris tabelul de codificare la început, împreună cu lungimea sa, ca să știu că restul octeților reprezintă conținutul fișierului. def _get_encoded_file_contents(self, content: bytes) -&gt; BitArray: """ Având conținutul necodificat, îl codifică cu codurile Huffman. :param content: Octeții conținutului brut. :return: Octeții conținutului codificat. """ with utils.Timer(name="Encode source file", logger=self._logger): buffer = [] for byte in content: buffer.append(self._encode_byte(byte)) bit_array = BitArray(bin="".join(buffer)) return bit_array def _encode_byte(self, byte: int) -&gt; str: """ Codifică octetul dat folosind arborele Huffman. """ if byte in self._encoding_table: return self._encoding_table[byte] raise Exception( f"Can't encode byte {byte}. It is not found in the encoding table." ) def encode(self, file_name: str, destination: str): """ Codifică un fișier dat în octeți Huffman și îl scrie pe disc.. :param file_name: Calea către numele fișierului. :param destination: Calea către numele fișierului de destinație. """ with open(file_name, "rb") as fh: content = fh.read() frequency_map = self._build_frequency_map(content) self._build_huffman_tree(frequency_map) self._build_encoding_table() bit_array = BitArray() encoding_table = pickle.dumps(self._encoding_table) # întreg fără semn de 2 octeți pentru lungimea tabelului de codificare bit_array.append(pack("&lt;H", len(encoding_table))) # tabelul de codificare complet, serializat cu pickle bit_array.append(encoding_table) # conținutul fișierului codificat cu Huffman encoded_content = self._get_encoded_file_contents(content) if len(encoded_content) % 8 == 0: encoded_content_padding_bits = 0 else: encoded_content_padding_bits = 8 - (len(encoded_content) % 8) bit_array.append(pack("&lt;B", encoded_content_padding_bits)) bit_array.append(encoded_content) self._write_to_destination_file(bit_array, destination) self._logger.info(f"Done encoding {file_name}.")#... def _write_to_destination_file(self, content: BitArray, destination: str): """ Scrie octeții în fișierul de destinație. :param content: Octeții conținutului. :param destination: Numele fișierului de destinație. """ with utils.Timer(name="Write to destination", logger=self._logger): with open(destination, "wb") as fh: content.tofile(fh) self._logger.debug( f"Wrote {content.len} bits to destination file: {destination}" ) Decompresia Decompresia fișierului este un pas simplu. Poți citi tabelul de codificare, îl poți inversa, construi o hartă și poți recupera elementele originale de acolo, apoi poți scrie octeții în fișierul de ieșire. Concluzie Proiectul Huffman este unul interesant: atinge algoritmi, manipulări de biți, fișiere, I/O și este folosit în multe aplicații reale, precum și în formate. Dacă încerci să comprimi un PNG cu el, nu va funcționa, pentru că comprimarea de două ori nu aduce rezultate mai bune. Dacă vrei să-l implementezi singur, încearcă. Mai jos găsești implementarea mea în Python și câteva referințe pe care le-am folosit ca să o scriu. Mulțumesc pentru lectură! Referințe https://en.wikipedia.org/wiki/Entropy_(computing) https://en.wikipedia.org/wiki/Entropy_coding https://codingchallenges.fyi/challenges/challenge-huffman/ https://opendsa-server.cs.vt.edu/ODSA/Books/CS3/html/Huffman.html https://gitlab.nuculabs.dev/dnutiu/huffmantreespy/ https://github.com/dnutiu/huffman-trees]]></summary>
  </entry>
  <entry xml:lang="ro">
    <title type="html">Îmi plac mediile fizice</title>
    <link href="https://nuculabs.dev/ro/life/2026/07/12/physical-media.html" rel="alternate" type="text/html" title="Îmi plac mediile fizice" />
    <published>2026-07-12T00:00:00+03:00</published>
    <updated>2026-07-12T00:00:00+03:00</updated>
    <id>https://nuculabs.dev/ro/life/2026/07/12/physical-media</id>
    <content type="html" xml:base="https://nuculabs.dev/ro/life/2026/07/12/physical-media.html"><![CDATA[<p>Salut tuturor,</p>

<p>Am vrut să scriu câteva gânduri despre tot subiectul mediilor fizice.</p>

<p>După ce Sony a anunțat că va înceta lansarea jocurilor fizice pentru PlayStation după ianuarie 2027, nu mi-am dat seama cât de mult îmi doresc medii fizice.</p>

<p>Anul trecut am cumpărat multe DVD-uri și câteva BlueRay-uri cu unele dintre filmele mele preferate, precum și CD-uri audio. DVD-urile nu arată foarte bine pe ecranele de azi, dar BlueRay-urile merg perfect. Cred că e necesar să încercăm să păstrăm formatul fizic, cel puțin pentru filme și audio. Companiile pot oricând să scoată filme din biblioteca lor și din a ta, mai mult decât atât, le pot cenzura, șterge anumite capitole sau scene, și asta pentru că nu ții în proprietate datele, le închiriezi, ah, scuze, ai o „licență” de utilizare.</p>

<p>CD-urile audio pe care le-am cumpărat ar putea fi ușor extrase și muzica transferată în biblioteca mea de muzică din cloud, self-hosted, ca s-o pot asculta oricând și să am control total asupra datelor.</p>

<p>Mi-ar plăcea personal să dețin datele și dispozitivele mele, acesta este unul dintre principalele motive pentru care folosesc Linux.</p>

<p>Pentru jocuri însă povestea este puțin diferită. Platforma PlayStation este proprietară, ceea ce înseamnă că discurile tale rulează pe software-ul ei, iar o actualizare online poate oricând să facă dispozitivul să refuze să le redea sau să forțeze modificarea fișierelor jocului, pentru cenzură sau eliminarea conținutului din motive de licențiere.</p>

<p>Jocurile fizice sunt totuși excelente, le poți împrumuta prietenilor sau le poți revinde dacă nu-ți plac, iar consolele Sony și Nintendo au reguli de rambursare foarte stricte: dacă cumperi un joc și îl descarci, nu poți cere rambursarea. Steam, pe PC, în schimb, este un paradis al serviciilor pentru clienți. Cred că poți juca jocul cel mult 2 ore și, dacă nu au trecut 14 zile, poți cere o rambursare și îți returnează integral suma.</p>

<p>Cred că avantajul de a avea un PS în loc de PC este destul de mic în zilele noastre. Cu PlayStation poți juca la televizor și ai un sistem de joc gata de utilizare, și asta e în regulă, dar cu PC-ul poți face atâtea alte lucruri, iar dacă investești într-un PC bun, va dura ani de zile, iar piesele pot fi upgradate dacă vrei. PC-ul meu are acum 5 ani și m-am gândit să-l upgradez cu un procesor mai bun și o placă video mai bună, dar nu există niciun motiv, funcționează și rulează jocurile bine, upgradarea ar fi o risipă de bani fără rost. Am 64GB RAM, pe care abia îl folosesc oricum.</p>

<p>Îmi place enorm Nintendo Switch 2, pot juca la televizor, în mișcare, iar jocul meu preferat în momentul de față este Resident Evil Requiem, și sunt bucuros că au decis să rămână la mediile fizice. Sper ca lucrurile să nu se schimbe.</p>

<p>Mulțumesc pentru lectură!</p>]]></content>
    <category term="Life" />
    <category term="life" />
    <summary type="html"><![CDATA[Salut tuturor, Am vrut să scriu câteva gânduri despre tot subiectul mediilor fizice. După ce Sony a anunțat că va înceta lansarea jocurilor fizice pentru PlayStation după ianuarie 2027, nu mi-am dat seama cât de mult îmi doresc medii fizice. Anul trecut am cumpărat multe DVD-uri și câteva BlueRay-uri cu unele dintre filmele mele preferate, precum și CD-uri audio. DVD-urile nu arată foarte bine pe ecranele de azi, dar BlueRay-urile merg perfect. Cred că e necesar să încercăm să păstrăm formatul fizic, cel puțin pentru filme și audio. Companiile pot oricând să scoată filme din biblioteca lor și din a ta, mai mult decât atât, le pot cenzura, șterge anumite capitole sau scene, și asta pentru că nu ții în proprietate datele, le închiriezi, ah, scuze, ai o „licență” de utilizare. CD-urile audio pe care le-am cumpărat ar putea fi ușor extrase și muzica transferată în biblioteca mea de muzică din cloud, self-hosted, ca s-o pot asculta oricând și să am control total asupra datelor. Mi-ar plăcea personal să dețin datele și dispozitivele mele, acesta este unul dintre principalele motive pentru care folosesc Linux. Pentru jocuri însă povestea este puțin diferită. Platforma PlayStation este proprietară, ceea ce înseamnă că discurile tale rulează pe software-ul ei, iar o actualizare online poate oricând să facă dispozitivul să refuze să le redea sau să forțeze modificarea fișierelor jocului, pentru cenzură sau eliminarea conținutului din motive de licențiere. Jocurile fizice sunt totuși excelente, le poți împrumuta prietenilor sau le poți revinde dacă nu-ți plac, iar consolele Sony și Nintendo au reguli de rambursare foarte stricte: dacă cumperi un joc și îl descarci, nu poți cere rambursarea. Steam, pe PC, în schimb, este un paradis al serviciilor pentru clienți. Cred că poți juca jocul cel mult 2 ore și, dacă nu au trecut 14 zile, poți cere o rambursare și îți returnează integral suma. Cred că avantajul de a avea un PS în loc de PC este destul de mic în zilele noastre. Cu PlayStation poți juca la televizor și ai un sistem de joc gata de utilizare, și asta e în regulă, dar cu PC-ul poți face atâtea alte lucruri, iar dacă investești într-un PC bun, va dura ani de zile, iar piesele pot fi upgradate dacă vrei. PC-ul meu are acum 5 ani și m-am gândit să-l upgradez cu un procesor mai bun și o placă video mai bună, dar nu există niciun motiv, funcționează și rulează jocurile bine, upgradarea ar fi o risipă de bani fără rost. Am 64GB RAM, pe care abia îl folosesc oricum. Îmi place enorm Nintendo Switch 2, pot juca la televizor, în mișcare, iar jocul meu preferat în momentul de față este Resident Evil Requiem, și sunt bucuros că au decis să rămână la mediile fizice. Sper ca lucrurile să nu se schimbe. Mulțumesc pentru lectură!]]></summary>
  </entry>
</feed>
