<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Ruby Wizards blog]]></title><description><![CDATA[I am a full-stack business developer from Poland with a strong interest in DDD. My leading technology is Ruby on Rails.

Contact me at: piotr.jurewicz(you-know-]]></description><link>https://rubywizards.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1630448525820/_R7P4rd_M.png</url><title>Ruby Wizards blog</title><link>https://rubywizards.com</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 14 May 2026 04:00:03 GMT</lastBuildDate><atom:link href="https://rubywizards.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Overgrown Tenant class decomposition]]></title><description><![CDATA[If you have been working on a multi-tenant application for a long time, there is a high chance that your Tenant model has grown to an enormous size.
You probably started with id, name, and subdomain columns.
Later there were things that had to be con...]]></description><link>https://rubywizards.com/overgrown-tenant-class-decomposition</link><guid isPermaLink="true">https://rubywizards.com/overgrown-tenant-class-decomposition</guid><category><![CDATA[Ruby]]></category><category><![CDATA[Ruby on Rails]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Tue, 11 Jan 2022 11:37:17 GMT</pubDate><content:encoded><![CDATA[<p>If you have been working on a multi-tenant application for a long time, there is a high chance that your <strong>Tenant</strong> model has grown to an enormous size.</p>
<p>You probably started with <em>id</em>, <em>name</em>, and <em>subdomain</em> columns.</p>
<p>Later there were things that had to be configured per tenant. So you simply added a bunch of columns.</p>
<p>The project started to grow rapidly and you added more flags, external API credentials, and even frontend configuration. You did it automatically. Before you knew it, you had several dozen columns with names similar to "<em>sales_page_menu_text_hover_color</em>" or "<em>proforma_invoices_generated_automatically</em>".</p>
<p>That's literally my case... My Tenant class had above 30 associations defined, 20 validations, and 8 enums. Almost 500 lines of code. It is mapped to 190 columns. All of this data was loaded at the very beginning of basically each request for the purpose of setting <strong>current_tenant</strong> where only the <em>id</em> is necessary to limit other queries scopes...</p>
<p>I wasn't interested in splitting the tenant configuration across multiple tables for that moment. I just wanted to reduce the amount of redundant data being constantly loaded and do some decomposition for better maintainability.</p>
<p>I limited the number of columns being selected for setting the <strong>current_tenant</strong>.</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ApplicationController</span> &lt; ActionController::Base</span>
  <span class="hljs-comment"># ...</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">set_tenant</span></span>
    tenant = System.select(<span class="hljs-symbol">:id</span>).find_by_subdomain(request.subdomain)
    set_current_tenant(tenant)
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre><p>I introduced a generic <strong>TenantConfiguration</strong> class.</p>
<pre><code>class TenantConfiguration <span class="hljs-operator">&lt;</span> ApplicationRecord
  <span class="hljs-built_in">self</span>.abstract_class <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>
  <span class="hljs-built_in">self</span>.table_name <span class="hljs-operator">=</span> <span class="hljs-string">'tenants'</span>

  default_scope { select([<span class="hljs-string">'id'</span>] <span class="hljs-operator">+</span> const_get(<span class="hljs-string">'COLUMNS'</span>)) }

  def <span class="hljs-built_in">self</span>.for_tenant(tenant)
    <span class="hljs-built_in">self</span>.find(tenant)
  end
end
</code></pre><p>And created a specific class for each configuration area, for example:</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LumpTaxConfiguration</span> &lt; TenantConfiguration</span>
  COLUMNS = [<span class="hljs-symbol">:lump_taxpayer</span>, <span class="hljs-symbol">:payment_fee_lump_code</span>, <span class="hljs-symbol">:delivery_lump_code</span>]
  <span class="hljs-comment"># ...</span>
<span class="hljs-keyword">end</span>
</code></pre><p>Whenever I really need a specific configuration, I just fetch it explicitly, by invoking: </p>
<pre><code>LumpTaxConfiguration.for_tenant(current_tenant).lump_taxpayer?
</code></pre><p>I believe It is a step in a good direction. What is your opinion? Do you see a problem with the fat Tenant model in your multi-tenant projects? How do you cope with it?</p>
]]></content:encoded></item><item><title><![CDATA[3 ways to make big numbers readable in Ruby]]></title><description><![CDATA[Today, I came across the following method in my old code. (This comes from an application supporting calculations in the construction of concrete floors).
  def l_factor
    ((bending_stiffness * 10000000000) / k) ** 0.25
  end
This big number looks ...]]></description><link>https://rubywizards.com/3-ways-to-make-big-numbers-readable-in-ruby</link><guid isPermaLink="true">https://rubywizards.com/3-ways-to-make-big-numbers-readable-in-ruby</guid><category><![CDATA[Ruby]]></category><category><![CDATA[Mathematics]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Wed, 10 Nov 2021 10:07:59 GMT</pubDate><content:encoded><![CDATA[<p>Today, I came across the following method in my old code. (This comes from an application supporting calculations in the construction of concrete floors).</p>
<pre><code>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">l_factor</span></span>
    ((bending_stiffness * <span class="hljs-number">10000000000</span>) / k) ** <span class="hljs-number">0</span>.<span class="hljs-number">25</span>
  <span class="hljs-keyword">end</span>
</code></pre><p>This big number looks so unreadable. I started moving the mouse cursor and counting zeros. There are ten of them. I was wondering how I could write it better. And actually, I found 3 ways to make it more readable:</p>
<h3 id="underscore-notation">🔍 Underscore notation</h3>
<pre><code>10_000_000_000
</code></pre><p>You can format a number with grouped thousands using an underscore delimiter. Ruby lets you insert underscores anywhere inside integers.</p>
<h3 id="expotential-notation">🧮 Expotential notation</h3>
<pre><code><span class="hljs-attribute">10</span>**<span class="hljs-number">10</span>
</code></pre><p>You would just read it like: \( 10^{10}\)</p>
<h3 id="scientific-notation">🧑‍🔬 Scientific notation</h3>
<pre><code><span class="hljs-number">1e10</span>
</code></pre><p>This one is my best choice. It actually means: \( 1\cdot10^{10}\) and this notation is commonly used by scientists, mathematicians, and engineers.</p>
<h3 id="which-one-would-you-choose-leave-a-comment">Which one would <strong>you</strong> choose? 🤷 Leave a comment.</h3>
]]></content:encoded></item><item><title><![CDATA[As simple as... the Inventory bounded context]]></title><description><![CDATA[Last weekend I went to the dynamIT conference that took place in Kraków. In his presentation, Andrzej Krzywda was speaking about DDD as Low-Code. He distilled 11 bounded contexts that are common in the different business applications. One of them is ...]]></description><link>https://rubywizards.com/as-simple-as-the-inventory-bounded-context</link><guid isPermaLink="true">https://rubywizards.com/as-simple-as-the-inventory-bounded-context</guid><category><![CDATA[Ruby on Rails]]></category><category><![CDATA[Ruby]]></category><category><![CDATA[DDD]]></category><category><![CDATA[conference]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Tue, 31 Aug 2021 19:42:14 GMT</pubDate><content:encoded><![CDATA[<p>Last weekend I went to the <a target="_blank" href="https://dynamit.pro">dynamIT</a> conference that took place in Kraków. In his presentation, Andrzej Krzywda was speaking about <a target="_blank" href="https://www.youtube.com/watch?v=yohu6qx8-dU">DDD as Low-Code</a>. He distilled 11 bounded contexts that are common in the different business applications. One of them is the Inventory bounded context which I have implemented in the <a target="_blank" href="https://github.com/RailsEventStore/ecommerce">Rails Event Store e-commerce module</a>. There are few things to keep in mind about the Inventory.</p>
<ul>
<li><p>Not all products need to keep track of their inventory level. Consider electronic products like ebooks. The sellers usually don't want to limit purchases for that kind of goods. But sometimes they do. I personally distinguish between empty stock and undefined stock as this is a pretty flexible solution. If the stock status is undefined, then we consider the goods to be available.</p>
</li>
<li><p>Warehouse operations do not have an atomic nature. This is why we should provide a reservation concept. The goods can be reserved and still remain in the warehouse. The actual availability is the difference between the stock level and the reservation count.</p>
</li>
<li><p>Reservation can be often mapped one-to-one to the Order, but remember we stay in the <strong>Inventory</strong> bounded context and it has its own ubiquitous language. Separation of these contexts may pay off in the future. I can imagine the part of the stock being reserved for giving away for promotional purposes.</p>
</li>
<li><p>When it comes to real things it is crucial to have a reliable audit log. The business must have an insight into the history of warehouse events. That is what you get for free with event sourcing - a full history of how the stock level has been changing over time and why.</p>
</li>
</ul>
<p>Having these things in mind your Inventory bounded context can be as simple as 5 commands and 2 aggregates.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1630438251634/DcTDQJXse.png" alt="Zrzut ekranu 2021-08-31 o 21.30.14.png" /></p>
]]></content:encoded></item><item><title><![CDATA[Accessing Cloudfront stream with VideoJS and server-side signed cookies]]></title><description><![CDATA[Today's post is a continuation of what I wrote yesterday about  securing Cloudfront stream with signed cookie. I encourage you to open this post especially to look at the UML diagram. There are some things you need to keep in mind to access a stream ...]]></description><link>https://rubywizards.com/accessing-cloudfront-stream-with-videojs-and-server-side-signed-cookies</link><guid isPermaLink="true">https://rubywizards.com/accessing-cloudfront-stream-with-videojs-and-server-side-signed-cookies</guid><category><![CDATA[AWS]]></category><category><![CDATA[streaming]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Fri, 23 Apr 2021 17:05:38 GMT</pubDate><content:encoded><![CDATA[<p>Today's post is a continuation of what I wrote yesterday about  <a target="_blank" href="https://rubywizards.com/securing-amazon-cloudfront-stream-with-signed-cookie">securing Cloudfront stream with signed cookie</a>. I encourage you to open this post especially to look at the UML diagram. There are some things you need to keep in mind to access a stream from the client's browser.</p>
<h3 id="1-include-cookies-with-the-request">1. Include cookies with the request</h3>
<p>When the VideoJS <code>withCredentials</code> property is set to true, all XHR requests for playlist and segments would have <code>withCredentials</code> set to true as well. I  refer you to <a target="_blank" href="https://web.dev/cross-origin-resource-sharing/">this post</a> if you aren't sure why it is required.</p>
<pre><code><span class="hljs-string">var</span> <span class="hljs-string">player</span> <span class="hljs-string">=</span> <span class="hljs-string">videojs('player',</span> {
            <span class="hljs-attr">html5:</span> {<span class="hljs-attr">vhs:</span> {<span class="hljs-attr">withCredentials:</span> <span class="hljs-literal">true</span>}}
        }<span class="hljs-string">);</span>
</code></pre><h3 id="2-cors-settings">2. CORS settings</h3>
<p>Now on the AWS side:</p>
<ul>
<li>Set CORS configuration for your S3 bucket<pre><code>[
  {
      <span class="hljs-attr">"AllowedHeaders"</span>: [
          <span class="hljs-string">"*"</span>
      ],
      <span class="hljs-attr">"AllowedMethods"</span>: [
          <span class="hljs-string">"GET"</span>,
          <span class="hljs-string">"HEAD"</span>
      ],
      <span class="hljs-attr">"AllowedOrigins"</span>: [
          <span class="hljs-string">"your.app.domain"</span>
      ],
      <span class="hljs-attr">"ExposeHeaders"</span>: [],
      <span class="hljs-attr">"MaxAgeSeconds"</span>: <span class="hljs-number">3000</span>
  }
]
</code></pre></li>
<li>Change the default cache behavior of your CloudFront instance. Enable OPTIONS as cached HTTP method. Choose <strong>Cache Policy</strong> and <strong>Origin Request Policy</strong> that both have <strong>origin</strong> header whitelisted.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619195902992/YLwE0ylSB.png" alt="Zrzut ekranu 2021-04-23 o 18.36.22.png" /></p>
<h3 id="3-refresh-cookies-regularly">3. Refresh cookies regularly</h3>
<p>You must ensure that the client's browser always has a valid cookie when content is being streamed. For this, I regularly hit the backend to get a new one. The interval depends on cookies expiration time and cookies <strong>DateLessThan</strong> statement.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">reportProgress</span>(<span class="hljs-params">uri, progress, <span class="hljs-keyword">async</span> = <span class="hljs-literal">true</span></span>) </span>{
    <span class="hljs-keyword">let</span> s3Key = uri.substring(uri.lastIndexOf(<span class="hljs-string">"/"</span>) + <span class="hljs-number">1</span>, uri.lastIndexOf(<span class="hljs-string">"."</span>));
    $.ajax({
        url: <span class="hljs-string">"/chapters/progress"</span>,
        <span class="hljs-keyword">type</span>: <span class="hljs-string">"POST"</span>,
        <span class="hljs-keyword">async</span>: <span class="hljs-keyword">async</span>,
        data: {progress: progress, <span class="hljs-string">"s3_key"</span>: s3Key},
    });
}

player.on(<span class="hljs-string">'beforeplaylistitem'</span>, <span class="hljs-function">(<span class="hljs-params">event, item</span>) =&gt;</span> {
    <span class="hljs-keyword">let</span> uri = item.sources[<span class="hljs-number">0</span>].src;
    reportProgress(uri, <span class="hljs-number">0</span>, <span class="hljs-literal">false</span>);
})

player.setInterval(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">let</span> uri = player.currentSource().src;
    reportProgress(uri, player.currentTime());
}, interval);
</code></pre>]]></content:encoded></item><item><title><![CDATA[Securing Amazon Cloudfront stream with signed cookies]]></title><description><![CDATA[In  one of my recent posts I described the challenges I faced while building the audiobook streaming platform. One of them was securing access to the Cloudfront stream.
There are two ways of securing content that CloudFront delivers: signed URLs and ...]]></description><link>https://rubywizards.com/securing-amazon-cloudfront-stream-with-signed-cookie</link><guid isPermaLink="true">https://rubywizards.com/securing-amazon-cloudfront-stream-with-signed-cookie</guid><category><![CDATA[Ruby]]></category><category><![CDATA[Ruby on Rails]]></category><category><![CDATA[streaming]]></category><category><![CDATA[AWS]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Thu, 22 Apr 2021 12:40:33 GMT</pubDate><content:encoded><![CDATA[<p>In  <a target="_blank" href="https://rubywizards.com/audio-streaming-vs-progressive-download">one of my recent posts</a> I described the challenges I faced while building the audiobook streaming platform. One of them was securing access to the Cloudfront stream.</p>
<p>There are two ways of securing content that CloudFront delivers: <strong>signed URLs</strong> and <strong>signed cookies</strong>.
In the case of a segmented file, URL signing doesn't seem to be a way to go. We have to take advantage of signed cookies.</p>
<p>The client browser should have a proper cookie already set while requesting <strong>.m3u8</strong> playlist file for each streaming session. You can later refresh this cookie asynchronously. A valid cookie must be attached to the request for each <strong>.aac</strong> segment file.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619095139869/TZuAqZaVu.png" alt="Sequence diagram-6.png" /></p>
<p>When you create a signed cookie, you provide a policy statement that specifies the restrictions on the signed cookie:</p>
<ul>
<li>You can specify the date and time that users can begin to access your content</li>
<li>You can specify the date and time that users can no longer access your content</li>
<li>You can specify the IP address or range of IP addresses of the users who can access your content</li>
</ul>
<p>My implementation uses CookieSigner from the official CloudFront SDK for Ruby. You can get it with <code>aws-sdk-cloudfront</code> gem.</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Panel</span></span>
  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ChaptersController</span> &lt; Panel::PanelController</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">progress</span></span>
      chapter = Chapter.find_by(<span class="hljs-symbol">user_id:</span> current_user.id, <span class="hljs-symbol">s3_key:</span> params[<span class="hljs-symbol">:s3_key</span>].split(<span class="hljs-string">'_converted'</span>).first)
      chapter.update_columns <span class="hljs-symbol">playback_progress:</span> params[<span class="hljs-symbol">:progress</span>], <span class="hljs-symbol">playback_progress_saved_at:</span> DateTime.now
      sign_cookie(chapter.s3_key)
      head <span class="hljs-symbol">:no_content</span>
    <span class="hljs-keyword">end</span>

    private

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sign_cookie</span><span class="hljs-params">(s3_key)</span></span>
      signer = Aws::CloudFront::CookieSigner.new(
        <span class="hljs-symbol">key_pair_id:</span> Rails.application.credentials.aws[<span class="hljs-symbol">:cloudfront_private_key_pair_id</span>],
        <span class="hljs-symbol">private_key:</span> Rails.application.credentials.aws[<span class="hljs-symbol">:cloudfront_private_key</span>]
      )
      signer.signed_cookie(
        <span class="hljs-literal">nil</span>,
        <span class="hljs-symbol">policy:</span> policy(s3_key, DateTime.now + <span class="hljs-number">1</span>.minute)
      ).each <span class="hljs-keyword">do</span> <span class="hljs-params">|key, value|</span>
        cookies[key] = {
          <span class="hljs-symbol">value:</span> value,
          <span class="hljs-symbol">domain:</span> <span class="hljs-symbol">:all</span>
        }
      <span class="hljs-keyword">end</span>
    <span class="hljs-keyword">end</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">policy</span><span class="hljs-params">(s3_key, expiry)</span></span>
      {
        <span class="hljs-string">"Statement"</span> =&gt; [
          {
            <span class="hljs-string">"Resource"</span> =&gt; <span class="hljs-string">"<span class="hljs-subst">#{Rails.application.credentials.aws[<span class="hljs-symbol">:cloudfront_url</span>]}</span>/<span class="hljs-subst">#{s3_key}</span>*"</span>,
            <span class="hljs-string">"Condition"</span> =&gt; {
              <span class="hljs-string">"DateLessThan"</span> =&gt; {
                <span class="hljs-string">"AWS:EpochTime"</span> =&gt; expiry.utc.to_i
              },
              <span class="hljs-string">"IpAddress"</span> =&gt; {
                <span class="hljs-string">"AWS:SourceIp"</span> =&gt; <span class="hljs-string">"<span class="hljs-subst">#{request.remote_ip}</span>/32"</span>
              }
            }
          }
        ]
      }.to_json
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>]]></content:encoded></item><item><title><![CDATA[How do I use ActiveJob to track the progress of long-running remote tasks?]]></title><description><![CDATA[Yesterday I wrote a  post  about initializing AWS Elemental MediaConvert job. It's a common case that you initiate a task and have to wait some unspecified time for the result. All what you have is an external system job id.
In such cases, I use a ve...]]></description><link>https://rubywizards.com/how-do-i-use-activejob-to-track-the-progress-of-long-running-remote-tasks</link><guid isPermaLink="true">https://rubywizards.com/how-do-i-use-activejob-to-track-the-progress-of-long-running-remote-tasks</guid><category><![CDATA[Ruby]]></category><category><![CDATA[Ruby on Rails]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Wed, 21 Apr 2021 07:10:19 GMT</pubDate><content:encoded><![CDATA[<p>Yesterday I wrote a  <a target="_blank" href="https://rubywizards.com/convert-mp3-files-to-a-streaming-ready-format">post</a>  about initializing AWS Elemental MediaConvert job. It's a common case that you initiate a task and have to wait some unspecified time for the result. All what you have is an external system job id.</p>
<p>In such cases, I use a very simple pattern based on ActiveJob's <code>retry_on</code> and <code>discard_on</code> methods.</p>
<p><code>retry_on</code> reschedules job for re-execution after specific delay, for a specific number of attempts. What I like about it is that this mechanism <strong>catches exceptions prior to your underlying queuing system</strong> (Sidekiq, for instance).
In practical terms, that means you have a different retry interval when you just wait for the task to be finished than when, for example, a timeout error occurs.</p>
<p>You can also pass a block that will be invoked if the retry attempts fail rather than let the exception bubble up. It's a good place to inform developers that the task wasn't finished in a rational time, or at least log it.</p>
<p><code>discard_on</code> is to specify an exception discarding the job. In my example, I don't want to check progress any more if the task gets canceled in the external system. Again, this is the case that should be noticed.
So all you have to do is pass a block that will be invoked.</p>
<p>The following example references the implementation of MediaConverterJobInspector you can find in the  <a target="_blank" href="https://rubywizards.com/convert-mp3-files-to-a-streaming-ready-format">previous post</a> .</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">module</span> <span class="hljs-title">Converting</span></span>
  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TrackConvertJobOnConvertJobInitialized</span> &lt; ActiveJob::Base</span>
    prepend RailsEventStore::AsyncHandler

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TrackConvertJobError</span> &lt; StandardError</span>
      <span class="hljs-keyword">attr_reader</span> <span class="hljs-symbol">:s3_key</span>, <span class="hljs-symbol">:media_convert_job_id</span>

      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span><span class="hljs-params">(<span class="hljs-symbol">s3_key:</span>, <span class="hljs-symbol">media_convert_job_id:</span>)</span></span>
        <span class="hljs-keyword">super</span>
        @s3_key = s3_key
        @media_convert_job_id = media_convert_job_id
      <span class="hljs-keyword">end</span>
    <span class="hljs-keyword">end</span>

    ConvertJobCanceled = Class.new(TrackConvertJobError)
    ConvertJobNotFinishedYet = Class.new(TrackConvertJobError)

    TIME_INTERVAL = <span class="hljs-number">20</span>.seconds
    TIME_LIMIT = <span class="hljs-number">1</span>.hour

    retry_on(ConvertJobNotFinishedYet, <span class="hljs-symbol">wait:</span> TIME_INTERVAL, <span class="hljs-symbol">attempts:</span> TIME_LIMIT / TIME_INTERVAL) <span class="hljs-keyword">do</span> <span class="hljs-params">|job, error|</span>
      Rails.configuration.event_store.publish(
        ContentConvertJobTimeLimitExceeded.new(
          <span class="hljs-symbol">data:</span> {
            <span class="hljs-symbol">media_convert_job_id:</span> error.media_convert_job_id,
            <span class="hljs-symbol">s3_key:</span> error.s3_key
          }
        )
      )
    <span class="hljs-keyword">end</span>

    discard_on ConvertJobCanceled <span class="hljs-keyword">do</span> <span class="hljs-params">|job, error|</span>
      Rails.configuration.event_store.publish(
        ContentConvertJobCanceled.new(
          <span class="hljs-symbol">data:</span> {
            <span class="hljs-symbol">media_convert_job_id:</span> error.media_convert_job_id,
            <span class="hljs-symbol">s3_key:</span> error.s3_key
          }
        )
      )
    <span class="hljs-keyword">end</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">perform</span><span class="hljs-params">(event)</span></span>
      s3_key = event.data[<span class="hljs-symbol">:s3_key</span>]
      media_convert_job_id = event.data[<span class="hljs-symbol">:media_convert_job_id</span>]
      inspector = job_inspector_class.new(media_convert_job_id)
      inspector.call
      <span class="hljs-keyword">case</span> inspector.status
      <span class="hljs-keyword">when</span> <span class="hljs-string">"COMPLETE"</span>
        event_store.publish ContentConvertJobCompleted.new(<span class="hljs-symbol">data:</span> { <span class="hljs-symbol">media_convert_job_id:</span> media_convert_job_id, <span class="hljs-symbol">s3_key:</span> s3_key })
      <span class="hljs-keyword">when</span> <span class="hljs-string">"ERROR"</span>
        event_store.publish ContentConvertJobFailed.new(<span class="hljs-symbol">data:</span> { <span class="hljs-symbol">media_convert_job_id:</span> media_convert_job_id, <span class="hljs-symbol">s3_key:</span> s3_key })
      <span class="hljs-keyword">when</span> <span class="hljs-string">"CANCELED"</span>
        raise ConvertJobCanceled.new(<span class="hljs-symbol">s3_key:</span> s3_key, <span class="hljs-symbol">media_convert_job_id:</span> media_convert_job_id)
      <span class="hljs-keyword">else</span>
        raise ConvertJobNotFinishedYet.new(<span class="hljs-symbol">s3_key:</span> s3_key, <span class="hljs-symbol">media_convert_job_id:</span> media_convert_job_id)
      <span class="hljs-keyword">end</span>
    <span class="hljs-keyword">end</span>

    private

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">event_store</span></span>
      Rails.configuration.event_store
    <span class="hljs-keyword">end</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">job_inspector_class</span></span>
      Rails.env.test? ? FakeConvertJobInspector : MediaConverterJobInspector
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>]]></content:encoded></item><item><title><![CDATA[Convert mp3 files to a streaming-ready format]]></title><description><![CDATA[In my yesterday's post, I described why I decided to take advantage of the HLS streaming in my new project which is a platform for audiobook streaming.
I explained why a file conversion is required. The simplest way, I discovered, to get this done, i...]]></description><link>https://rubywizards.com/convert-mp3-files-to-a-streaming-ready-format</link><guid isPermaLink="true">https://rubywizards.com/convert-mp3-files-to-a-streaming-ready-format</guid><category><![CDATA[Ruby]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Ruby on Rails]]></category><category><![CDATA[streaming]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Tue, 20 Apr 2021 10:21:38 GMT</pubDate><content:encoded><![CDATA[<p>In my <a target="_blank" href="https://rubywizards.com/audio-streaming-vs-progressive-download">yesterday's post</a>, I described why I decided to take advantage of the HLS streaming in my new project which is a platform for audiobook streaming.</p>
<p>I explained why a file conversion is required. The simplest way, I discovered, to get this done, is using the <strong>AWS Elemental MediaConvert</strong> service. It was an obvious choice for me as I am a bit familiar with the AWS stack.</p>
<h3 id="are-you-even-going-to-show-me-the-code">Are you even going to show me the code??</h3>
<p>Sure! There will be a lot of code this time as kicking off a convert job requires passing extensive configuration hash. My implementation wraps the official MediaConvert SDK for Ruby. You can get it having <code>aws-sdk-mediaconvert</code> listed in your Gemfile.</p>
<pre><code><span class="hljs-selector-tag">class</span> <span class="hljs-selector-tag">Converting</span><span class="hljs-selector-pseudo">::MediaConverterWrapper</span>
  <span class="hljs-selector-tag">protected</span>

  <span class="hljs-selector-tag">def</span> <span class="hljs-selector-tag">client</span>
    <span class="hljs-selector-tag">Aws</span><span class="hljs-selector-pseudo">::MediaConvert</span><span class="hljs-selector-pseudo">::Client.new(</span>
      <span class="hljs-selector-tag">region</span>: <span class="hljs-selector-tag">Rails</span><span class="hljs-selector-class">.configuration</span><span class="hljs-selector-class">.aws</span><span class="hljs-selector-attr">[:region]</span>,
      <span class="hljs-selector-tag">access_key_id</span>: <span class="hljs-selector-tag">Rails</span><span class="hljs-selector-class">.application</span><span class="hljs-selector-class">.credentials</span><span class="hljs-selector-class">.aws</span><span class="hljs-selector-attr">[:access_key_id]</span>,
      <span class="hljs-selector-tag">secret_access_key</span>: <span class="hljs-selector-tag">Rails</span><span class="hljs-selector-class">.application</span><span class="hljs-selector-class">.credentials</span><span class="hljs-selector-class">.aws</span><span class="hljs-selector-attr">[:secret_access_key]</span>,
      <span class="hljs-selector-tag">endpoint</span>: <span class="hljs-selector-tag">Rails</span><span class="hljs-selector-class">.configuration</span><span class="hljs-selector-class">.aws</span><span class="hljs-selector-attr">[:endpoint]</span>
    )
  <span class="hljs-selector-tag">end</span>
<span class="hljs-selector-tag">end</span>
</code></pre><pre><code><span class="hljs-keyword">class</span> Converting::MediaConverterJobInitializer &lt; Converting::MediaConverterWrapper

  def initialize source_s3_key
    <span class="hljs-meta">@source_s3_key</span> = source_s3_key
  end

  def call
    source_S3_bucket = Rails.configuration.aws[:source_S3_bucket]
    source_S3 = <span class="hljs-string">"s3://#{source_S3_bucket}/#{@source_s3_key}"</span>
    destination_S3_bucket = <span class="hljs-string">"s3://#{Rails.configuration.aws[:destination_S3_bucket]}/"</span>
    queue = Rails.configuration.aws[:queue]
    role = Rails.configuration.aws[:role]

    # Kicking off a job
    <span class="hljs-meta">@response</span> = client.create_job(create_job_request_body(source_S3, queue, role, destination_S3_bucket))
    nil
  end

  def job_id
    <span class="hljs-meta">@response</span>[:job][:id]
  end

  <span class="hljs-keyword">private</span>

  def create_job_request_body source_S3, queue, role, destination_S3
    {
      :<span class="hljs-function"><span class="hljs-params">queue</span> =&gt;</span> queue,
      :<span class="hljs-function"><span class="hljs-params">user_metadata</span> =&gt;</span> {},
      role: role,
      :<span class="hljs-function"><span class="hljs-params">settings</span> =&gt;</span> {
        :<span class="hljs-function"><span class="hljs-params">timecode_config</span> =&gt;</span> {
          :<span class="hljs-function"><span class="hljs-params">source</span> =&gt;</span> <span class="hljs-string">'ZEROBASED'</span>
        },
        :<span class="hljs-function"><span class="hljs-params">output_groups</span> =&gt;</span> [
          {
            :<span class="hljs-function"><span class="hljs-params">name</span> =&gt;</span> <span class="hljs-string">'Apple HLS'</span>,
            :<span class="hljs-function"><span class="hljs-params">outputs</span> =&gt;</span> [
              {
                :<span class="hljs-function"><span class="hljs-params">container_settings</span> =&gt;</span> {
                  :<span class="hljs-function"><span class="hljs-params">container</span> =&gt;</span> <span class="hljs-string">'M3U8'</span>,
                  :<span class="hljs-function"><span class="hljs-params">m3u_8_settings</span> =&gt;</span> {
                    :<span class="hljs-function"><span class="hljs-params">audio_frames_per_pes</span> =&gt;</span> <span class="hljs-number">4</span>,
                    :<span class="hljs-function"><span class="hljs-params">pcr_control</span> =&gt;</span> <span class="hljs-string">'PCR_EVERY_PES_PACKET'</span>,
                    :<span class="hljs-function"><span class="hljs-params">pmt_pid</span> =&gt;</span> <span class="hljs-number">480</span>,
                    :<span class="hljs-function"><span class="hljs-params">private_metadata_pid</span> =&gt;</span> <span class="hljs-number">503</span>,
                    :<span class="hljs-function"><span class="hljs-params">program_number</span> =&gt;</span> <span class="hljs-number">1</span>,
                    :<span class="hljs-function"><span class="hljs-params">pat_interval</span> =&gt;</span> <span class="hljs-number">0</span>,
                    :<span class="hljs-function"><span class="hljs-params">pmt_interval</span> =&gt;</span> <span class="hljs-number">0</span>,
                    :<span class="hljs-function"><span class="hljs-params">scte_35_source</span> =&gt;</span> <span class="hljs-string">'NONE'</span>,
                    :<span class="hljs-function"><span class="hljs-params">nielsen_id_3</span> =&gt;</span> <span class="hljs-string">'NONE'</span>,
                    :<span class="hljs-function"><span class="hljs-params">timed_metadata</span> =&gt;</span> <span class="hljs-string">'NONE'</span>,
                    :<span class="hljs-function"><span class="hljs-params">video_pid</span> =&gt;</span> <span class="hljs-number">481</span>,
                    :<span class="hljs-function"><span class="hljs-params">audio_pids</span> =&gt;</span> [<span class="hljs-number">482</span>, <span class="hljs-number">483</span>, <span class="hljs-number">484</span>, <span class="hljs-number">485</span>, <span class="hljs-number">486</span>, <span class="hljs-number">487</span>, <span class="hljs-number">488</span>, <span class="hljs-number">489</span>, <span class="hljs-number">490</span>, <span class="hljs-number">491</span>, <span class="hljs-number">492</span>],
                    :<span class="hljs-function"><span class="hljs-params">audio_duration</span> =&gt;</span> <span class="hljs-string">'DEFAULT_CODEC_DURATION'</span> }
                },
                :<span class="hljs-function"><span class="hljs-params">audio_descriptions</span> =&gt;</span> [
                  {
                    :<span class="hljs-function"><span class="hljs-params">audio_type_control</span> =&gt;</span> <span class="hljs-string">'FOLLOW_INPUT'</span>,
                    :<span class="hljs-function"><span class="hljs-params">audio_source_name</span> =&gt;</span> <span class="hljs-string">'Audio Selector 1'</span>,
                    :<span class="hljs-function"><span class="hljs-params">codec_settings</span> =&gt;</span> {
                      :<span class="hljs-function"><span class="hljs-params">codec</span> =&gt;</span> <span class="hljs-string">'AAC'</span>,
                      :<span class="hljs-function"><span class="hljs-params">aac_settings</span> =&gt;</span> {
                        :<span class="hljs-function"><span class="hljs-params">audio_description_broadcaster_mix</span> =&gt;</span> <span class="hljs-string">'NORMAL'</span>,
                        :<span class="hljs-function"><span class="hljs-params">bitrate</span> =&gt;</span> <span class="hljs-number">96</span>_000,
                        :<span class="hljs-function"><span class="hljs-params">rate_control_mode</span> =&gt;</span> <span class="hljs-string">'CBR'</span>,
                        :<span class="hljs-function"><span class="hljs-params">codec_profile</span> =&gt;</span> <span class="hljs-string">'LC'</span>,
                        :<span class="hljs-function"><span class="hljs-params">coding_mode</span> =&gt;</span> <span class="hljs-string">'CODING_MODE_2_0'</span>,
                        :<span class="hljs-function"><span class="hljs-params">raw_format</span> =&gt;</span> <span class="hljs-string">'NONE'</span>,
                        :<span class="hljs-function"><span class="hljs-params">sample_rate</span> =&gt;</span> <span class="hljs-number">48</span>_000,
                        :<span class="hljs-function"><span class="hljs-params">specification</span> =&gt;</span> <span class="hljs-string">'MPEG4'</span>
                      }
                    },
                    :<span class="hljs-function"><span class="hljs-params">language_code_control</span> =&gt;</span> <span class="hljs-string">'FOLLOW_INPUT'</span>
                  }
                ],
                :<span class="hljs-function"><span class="hljs-params">output_settings</span> =&gt;</span> {
                  :<span class="hljs-function"><span class="hljs-params">hls_settings</span> =&gt;</span> {
                    :<span class="hljs-function"><span class="hljs-params">audio_group_id</span> =&gt;</span> <span class="hljs-string">'program_audio'</span>,
                    :<span class="hljs-function"><span class="hljs-params">audio_only_container</span> =&gt;</span> <span class="hljs-string">'AUTOMATIC'</span>,
                    :<span class="hljs-function"><span class="hljs-params">i_frame_only_manifest</span> =&gt;</span> <span class="hljs-string">'EXCLUDE'</span>
                  }
                },
                :<span class="hljs-function"><span class="hljs-params">name_modifier</span> =&gt;</span> <span class="hljs-string">'_converted'</span>
              }
            ],
            :<span class="hljs-function"><span class="hljs-params">output_group_settings</span> =&gt;</span> {
              :<span class="hljs-function"><span class="hljs-params">type</span> =&gt;</span> <span class="hljs-string">'HLS_GROUP_SETTINGS'</span>,
              :<span class="hljs-function"><span class="hljs-params">hls_group_settings</span> =&gt;</span> {
                :<span class="hljs-function"><span class="hljs-params">manifest_duration_format</span> =&gt;</span> <span class="hljs-string">'INTEGER'</span>,
                :<span class="hljs-function"><span class="hljs-params">segment_length</span> =&gt;</span> <span class="hljs-number">10</span>,
                :<span class="hljs-function"><span class="hljs-params">timed_metadata_id_3_period</span> =&gt;</span> <span class="hljs-number">10</span>,
                :<span class="hljs-function"><span class="hljs-params">caption_language_setting</span> =&gt;</span> <span class="hljs-string">'OMIT'</span>,
                :<span class="hljs-function"><span class="hljs-params">destination</span> =&gt;</span> destination_S3,
                :<span class="hljs-function"><span class="hljs-params">timed_metadata_id_3_frame</span> =&gt;</span> <span class="hljs-string">'PRIV'</span>,
                :<span class="hljs-function"><span class="hljs-params">codec_specification</span> =&gt;</span> <span class="hljs-string">'RFC_4281'</span>,
                :<span class="hljs-function"><span class="hljs-params">output_selection</span> =&gt;</span> <span class="hljs-string">'MANIFESTS_AND_SEGMENTS'</span>,
                :<span class="hljs-function"><span class="hljs-params">program_date_time_period</span> =&gt;</span> <span class="hljs-number">600</span>,
                :<span class="hljs-function"><span class="hljs-params">min_segment_length</span> =&gt;</span> <span class="hljs-number">0</span>,
                :<span class="hljs-function"><span class="hljs-params">min_final_segment_length</span> =&gt;</span> <span class="hljs-number">0</span>,
                :<span class="hljs-function"><span class="hljs-params">directory_structure</span> =&gt;</span> <span class="hljs-string">'SINGLE_DIRECTORY'</span>,
                :<span class="hljs-function"><span class="hljs-params">program_date_time</span> =&gt;</span> <span class="hljs-string">'EXCLUDE'</span>,
                :<span class="hljs-function"><span class="hljs-params">segment_control</span> =&gt;</span> <span class="hljs-string">'SEGMENTED_FILES'</span>,
                :<span class="hljs-function"><span class="hljs-params">manifest_compression</span> =&gt;</span> <span class="hljs-string">'NONE'</span>,
                :<span class="hljs-function"><span class="hljs-params">client_cache</span> =&gt;</span> <span class="hljs-string">'ENABLED'</span>,
                :<span class="hljs-function"><span class="hljs-params">audio_only_header</span> =&gt;</span> <span class="hljs-string">'INCLUDE'</span>,
                :<span class="hljs-function"><span class="hljs-params">stream_inf_resolution</span> =&gt;</span> <span class="hljs-string">'INCLUDE'</span>
              }
            }
          }
        ],
        :<span class="hljs-function"><span class="hljs-params">ad_avail_offset</span> =&gt;</span> <span class="hljs-number">0</span>,
        :<span class="hljs-function"><span class="hljs-params">inputs</span> =&gt;</span> [
          {
            :<span class="hljs-function"><span class="hljs-params">audio_selectors</span> =&gt;</span> {
              <span class="hljs-string">'Audio Selector 1'</span> =&gt; {
                :<span class="hljs-function"><span class="hljs-params">offset</span> =&gt;</span> <span class="hljs-number">0</span>,
                :<span class="hljs-function"><span class="hljs-params">default_selection</span> =&gt;</span> <span class="hljs-string">'DEFAULT'</span>,
                :<span class="hljs-function"><span class="hljs-params">program_selection</span> =&gt;</span> <span class="hljs-number">1</span>
              }
            },
            :<span class="hljs-function"><span class="hljs-params">filter_enable</span> =&gt;</span> <span class="hljs-string">'AUTO'</span>,
            :<span class="hljs-function"><span class="hljs-params">psi_control</span> =&gt;</span> <span class="hljs-string">'USE_PSI'</span>,
            :<span class="hljs-function"><span class="hljs-params">filter_strength</span> =&gt;</span> <span class="hljs-number">0</span>,
            :<span class="hljs-function"><span class="hljs-params">deblock_filter</span> =&gt;</span> <span class="hljs-string">'DISABLED'</span>,
            :<span class="hljs-function"><span class="hljs-params">denoise_filter</span> =&gt;</span> <span class="hljs-string">'DISABLED'</span>,
            :<span class="hljs-function"><span class="hljs-params">input_scan_type</span> =&gt;</span> <span class="hljs-string">'AUTO'</span>,
            :<span class="hljs-function"><span class="hljs-params">timecode_source</span> =&gt;</span> <span class="hljs-string">'ZEROBASED'</span>,
            :<span class="hljs-function"><span class="hljs-params">file_input</span> =&gt;</span> source_S3
          }
        ]
      },
      :<span class="hljs-function"><span class="hljs-params">acceleration_settings</span> =&gt;</span> { :<span class="hljs-function"><span class="hljs-params">mode</span> =&gt;</span> <span class="hljs-string">'DISABLED'</span> },
      :<span class="hljs-function"><span class="hljs-params">status_update_interval</span> =&gt;</span> <span class="hljs-string">'SECONDS_60'</span>,
      :<span class="hljs-function"><span class="hljs-params">priority</span> =&gt;</span> <span class="hljs-number">0</span>
    }
  end
end
</code></pre><p>Take note of several options for this advanced configuration:</p>
<ul>
<li><code>queue</code> passing this should be optional as there is such a thing as a "default queue"</li>
<li><code>role</code> identifier of AWS IAM role with permissions to access both: source and destination, S3 buckets</li>
<li><code>bitrate</code> specifies output <strong>.aac</strong> files bitrate. You can specify multiple <code>AudioDescriptions</code> with different bitrates to have an adaptive stream</li>
<li><code>segment_length</code> duration in seconds of <strong>.aac</strong> segment files</li>
<li><code>destination</code> identifier of S3 bucket for storing converted files</li>
<li><code>name_modifier</code> specifies output file name addon</li>
<li><code>file_input</code> S3 identifier of source file</li>
</ul>
<h3 id="whats-next">What's next?</h3>
<p>Remember to save obtained <code>job_id</code>, to be able to check the status of initialized convert job later.
Of course, I also have a service class for this purpose.</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Converting::MediaConverterJobInspector</span> &lt; Converting::MediaConverterWrapper</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span> <span class="hljs-title">media_convert_job_id</span></span>
    @media_convert_job_id = media_convert_job_id
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">call</span></span>
    @response = client.get_job({ <span class="hljs-symbol">id:</span> @media_convert_job_id })
    <span class="hljs-literal">nil</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-comment"># returns job status</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">status</span></span>
    @response[<span class="hljs-symbol">:job</span>][<span class="hljs-symbol">:status</span>]
  <span class="hljs-keyword">end</span>
  <span class="hljs-comment"># ...</span>
<span class="hljs-keyword">end</span>
</code></pre>]]></content:encoded></item><item><title><![CDATA[Streaming (HLS) vs progressive download]]></title><description><![CDATA[Recently I was faced with the challenge of implementing a platform for audiobook streaming.
"Streaming" seemed to be a very overused word in colloquial speech to me (same as "Artificial Intelligence"). So first I had to figure out what streaming real...]]></description><link>https://rubywizards.com/audio-streaming-vs-progressive-download</link><guid isPermaLink="true">https://rubywizards.com/audio-streaming-vs-progressive-download</guid><category><![CDATA[AWS]]></category><category><![CDATA[streaming]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Mon, 19 Apr 2021 11:33:08 GMT</pubDate><content:encoded><![CDATA[<p>Recently I was faced with the challenge of implementing a platform for audiobook streaming.
"Streaming" seemed to be a very overused word in colloquial speech to me (same as "Artificial Intelligence"). So first I had to figure out what streaming really is and how does it differ from simply embedding the mp3 file on the webpage.</p>
<h3 id="1-file-format">1. File format</h3>
<p>Real streaming requires file conversion. After conversion, which will be a topic of  <a target="_blank" href="https://rubywizards.com/convert-mp3-files-to-a-streaming-ready-format">another blog post</a>, there is no longer one mp3 file. The conversion generates <strong>.m3u8</strong> playlist file which the player requests once at the start of the streaming session and a bunch of <strong>.aac</strong> files that are short segments of the audio track. </p>
<h3 id="2-bandwidth-usage">2. Bandwidth usage</h3>
<p>The segments of converted audio track are being fetched one by one and when the user clicks pause on the video player, then the segments stop being downloaded. What is more, the user can start playback from any point. How crucial it is if we are talking about audiobook chapters that can last tens of minutes or even hours! The standard progressive download doesn't have such features, so streaming is the only economically viable option if you expect heavy traffic. Services like AWS Cloudfront charge you for the data transfer.</p>
<h3 id="3-security">3. Security</h3>
<p>Standard mp3 files can be easily downloaded using third-party browser plugins like Video Downloader. It is not so trivial with the streams. Again, this is very important when it comes to audiobooks, the intellectual property that is highly vulnerable to piracy. Furthermore, as the stream is downloading files incrementally you can prevent access to further listening if, for example, someone else starts using the same account. </p>
<p>Granting access to a stream with a signed cookie with AWS Cloudfront is a topic of <a target="_blank" href="https://rubywizards.com/securing-amazon-cloudfront-stream-with-signed-cookie">another blog post</a> in this series. </p>
<h3 id="4-adaptivity">4. Adaptivity</h3>
<p>You can convert your input file to many bitrates. All this data is stored in the mentioned <strong>.m3u8</strong> playlist file. Depending on the available bandwidth the player will automatically choose a segment from an appropriate bitrate. If the user enters the area of a poor Internet connection he can still listen to his audiobook even if the audio quality is a little bit worse. Transitioning between bitrates is seamless.</p>
]]></content:encoded></item><item><title><![CDATA[Advent of Code 2020: Day 15 (Ruby solution)]]></title><description><![CDATA[Today's puzzle is a kind of n-back game.
In this game, the players take turns saying numbers. They begin by taking turns reading from a list of starting numbers (the puzzle input). Then, each turn consists of considering the most recently spoken numb...]]></description><link>https://rubywizards.com/advent-of-code-2020-day-15-ruby-solution</link><guid isPermaLink="true">https://rubywizards.com/advent-of-code-2020-day-15-ruby-solution</guid><category><![CDATA[Ruby]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Tue, 15 Dec 2020 13:44:44 GMT</pubDate><content:encoded><![CDATA[<p><a target="_blank" href="https://adventofcode.com/2020/day/15">Today's puzzle</a> is a kind of n-back game.</p>
<p>In this game, the players take turns saying numbers. They begin by taking turns reading from a list of starting numbers (the puzzle input). Then, each turn consists of considering the most recently spoken number:</p>
<ul>
<li>If that was the first time the number has been spoken, the current player says 0.</li>
<li>Otherwise, the number had been spoken before; the current player announces how many turns apart the number is from when it was previously spoken.</li>
</ul>
<p>In the first task, you are asked what would be the 2020th number spoken. Later, you are supposed to check the 30000000th one. If you choose an effective method using an associative array, you can solve both tasks with one function.</p>
<p>My Ruby solution:</p>
<pre><code>def determine_number starting_numbers, nth
  i = starting_numbers.size - <span class="hljs-number">1</span>
  last = starting_numbers.last
  last_spoken = starting_numbers[<span class="hljs-number">0.</span>.<span class="hljs-number">-2</span>].map.with_index { |v, <span class="hljs-keyword">index</span>| {v =&gt; <span class="hljs-keyword">index</span>} }.reduce(:merge)
  <span class="hljs-keyword">while</span> i &lt; nth - <span class="hljs-number">1</span>
    prev_last = last
    last = last_spoken.has_key?(prev_last) ? i - last_spoken[prev_last] : <span class="hljs-number">0</span>
    last_spoken[prev_last] = i
    i += <span class="hljs-number">1</span>
  <span class="hljs-keyword">end</span>
  last
<span class="hljs-keyword">end</span>

INPUT = [<span class="hljs-number">7</span>, <span class="hljs-number">12</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">16</span>, <span class="hljs-number">2</span>].<span class="hljs-keyword">freeze</span>
puts "First task answer: #{determine_number INPUT, 2020}"
puts "Second task answer: #{determine_number INPUT, 30000000}"
</code></pre>]]></content:encoded></item><item><title><![CDATA[Advent of Code 2020: Day 14 (Ruby solution)]]></title><description><![CDATA[Today's tasks were bit manipulation. The initialization program (your puzzle input) can either update the bitmask or write a value to memory. Values and memory addresses are both 36-bit unsigned integers. 
In the first task, a bitmask is used to tran...]]></description><link>https://rubywizards.com/advent-of-code-2020-day-14-ruby-solution</link><guid isPermaLink="true">https://rubywizards.com/advent-of-code-2020-day-14-ruby-solution</guid><category><![CDATA[Ruby]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Mon, 14 Dec 2020 13:44:13 GMT</pubDate><content:encoded><![CDATA[<p><a target="_blank" href="https://adventofcode.com/2020/day/14">Today's tasks</a> were bit manipulation. The initialization program (your puzzle input) can either update the bitmask or write a value to memory. Values and memory addresses are both 36-bit unsigned integers. </p>
<p>In the first task, a bitmask is used to transform a value before writing it. A 0 or 1 overwrites the corresponding bit in the value, while an X leaves the bit in the value unchanged.</p>
<p>In another task, a bitmask is used to transform an address. Immediately before a value is written to memory, each bit in the bitmask modifies the corresponding bit of the destination memory address in the following way:</p>
<ul>
<li>If the bitmask bit is 0, the corresponding memory address bit is unchanged.</li>
<li>If the bitmask bit is 1, the corresponding memory address bit is overwritten with 1.</li>
<li>If the bitmask bit is X, the corresponding memory address bit is floating.
A floating bit is not connected to anything and instead fluctuates unpredictably. In practice, this means the floating bits will take on all possible values, potentially causing many memory addresses to be written all at once.</li>
</ul>
<p>This is my Ruby solution:</p>
<pre><code>def int_to_bin <span class="hljs-keyword">value</span>
  <span class="hljs-keyword">value</span>.to_i.to_s(<span class="hljs-number">2</span>).rjust(<span class="hljs-number">36</span>, "0")
<span class="hljs-keyword">end</span>

def sum_mem_values file, decode_address = <span class="hljs-keyword">false</span>
  mem = {}
  mask = <span class="hljs-string">'X'</span> * <span class="hljs-number">36</span>
  file.each_line <span class="hljs-keyword">do</span> |<span class="hljs-type">line</span>|
    command = <span class="hljs-type">line</span>.split " = "
    variable = command.first
    address = variable.scan(/\d+/).first.to_i
    <span class="hljs-keyword">value</span> = command.last.strip

    <span class="hljs-keyword">if</span> variable == <span class="hljs-string">'mask'</span>
      mask = <span class="hljs-keyword">value</span>
    <span class="hljs-keyword">elsif</span> decode_address
      address_bin = int_to_bin(address)
      [<span class="hljs-string">'0'</span>, <span class="hljs-string">'1'</span>].repeated_permutation(mask.count(<span class="hljs-string">'X'</span>)) <span class="hljs-keyword">do</span> |x_values|
        address_bin_copy = address_bin
        mask.each_char.with_index <span class="hljs-keyword">do</span> |<span class="hljs-type">char</span>, <span class="hljs-keyword">index</span>|
          address_bin_copy[<span class="hljs-keyword">index</span>] = <span class="hljs-string">'1'</span> <span class="hljs-keyword">if</span> <span class="hljs-type">char</span> == <span class="hljs-string">'1'</span>
          address_bin_copy[<span class="hljs-keyword">index</span>] = x_values[mask[<span class="hljs-number">0.</span>..<span class="hljs-keyword">index</span>].count(<span class="hljs-string">'X'</span>)] <span class="hljs-keyword">if</span> <span class="hljs-type">char</span> == <span class="hljs-string">'X'</span>
        <span class="hljs-keyword">end</span>
        mem[address_bin_copy.to_i(<span class="hljs-number">2</span>)] = <span class="hljs-keyword">value</span>.to_i
      <span class="hljs-keyword">end</span>
    <span class="hljs-keyword">else</span> # decode <span class="hljs-keyword">value</span>
      value_bin = int_to_bin(<span class="hljs-keyword">value</span>)
      mask.each_char.with_index <span class="hljs-keyword">do</span> |<span class="hljs-type">char</span>, <span class="hljs-keyword">index</span>|
        value_bin[<span class="hljs-keyword">index</span>] = <span class="hljs-string">'1'</span> <span class="hljs-keyword">if</span> <span class="hljs-type">char</span> == <span class="hljs-string">'1'</span>
        value_bin[<span class="hljs-keyword">index</span>] = <span class="hljs-string">'0'</span> <span class="hljs-keyword">if</span> <span class="hljs-type">char</span> == <span class="hljs-string">'0'</span>
      <span class="hljs-keyword">end</span>
      mem[address] = value_bin.to_i(<span class="hljs-number">2</span>)
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>
  mem.<span class="hljs-keyword">values</span>.sum
<span class="hljs-keyword">end</span>

file = File.<span class="hljs-keyword">read</span>(<span class="hljs-string">'inputs/day14.txt'</span>)
puts "First task answer: #{sum_mem_values(file)}"
puts "Second task answer: #{sum_mem_values(file, true)}"
</code></pre>]]></content:encoded></item><item><title><![CDATA[Advent of Code 2020: Day 13 (Ruby solution)]]></title><description><![CDATA[Today's puzzle is not so easy.
The first task is about finding the closest arrival timestamp knowing how often each bus courses. This part is trivial:
file = File.read('inputs/day13.txt')
timestamp = file.lines.first.to_i
bus_lines = file.lines.last....]]></description><link>https://rubywizards.com/advent-of-code-2020-day-13-ruby-solution</link><guid isPermaLink="true">https://rubywizards.com/advent-of-code-2020-day-13-ruby-solution</guid><category><![CDATA[Ruby]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Sun, 13 Dec 2020 22:20:36 GMT</pubDate><content:encoded><![CDATA[<p><a target="_blank" href="https://adventofcode.com/2020/day/13">Today's puzzle</a> is not so easy.</p>
<p>The first task is about finding the closest arrival timestamp knowing how often each bus courses. This part is trivial:</p>
<pre><code>file = File.<span class="hljs-keyword">read</span>(<span class="hljs-string">'inputs/day13.txt'</span>)
<span class="hljs-type">timestamp</span> = file.lines.first.to_i
bus_lines = file.lines.last.split(<span class="hljs-string">','</span>).map{|n| n.to_i <span class="hljs-keyword">if</span> n.match(/\d+/)}
arrivals = bus_lines.compact.map <span class="hljs-keyword">do</span> |<span class="hljs-type">line</span>|
  {<span class="hljs-type">line</span> =&gt; (<span class="hljs-type">timestamp</span>.to_f / <span class="hljs-type">line</span>).ceil * <span class="hljs-type">line</span>}
<span class="hljs-keyword">end</span>.inject(:merge)

first_arrival = arrivals.min_by { |k, v| v }
puts "First task answer: #{(first_arrival.last - timestamp) * first_arrival.first}"
</code></pre><p>But the second one requires some non-elemental mathematical knowledge. Specifically, you should be aware about  <a target="_blank" href="https://en.wikipedia.org/wiki/Chinese_remainder_theorem">Chinese Remainder Theorem</a>. I used  <a target="_blank" href="https://github.com/acmeism/RosettaCodeData/blob/master/Task/Chinese-remainder-theorem/Ruby/chinese-remainder-theorem-2.rb">this implementation</a>. Here is the entire solution:</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">extended_gcd</span><span class="hljs-params">(a, b)</span></span>
  last_remainder, remainder = a.abs, b.abs
  x, last_x, y, last_y = <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>
  <span class="hljs-keyword">while</span> remainder != <span class="hljs-number">0</span>
    last_remainder, (quotient, remainder) = remainder, last_remainder.divmod(remainder)
    x, last_x = last_x - quotient * x, x
    y, last_y = last_y - quotient * y, y
  <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">return</span> last_remainder, last_x * (a &lt; <span class="hljs-number">0</span> ? -<span class="hljs-number">1</span> : <span class="hljs-number">1</span>)
<span class="hljs-keyword">end</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">invmod</span><span class="hljs-params">(e, et)</span></span>
  g, x = extended_gcd(e, et)
  <span class="hljs-keyword">if</span> g != <span class="hljs-number">1</span>
    raise <span class="hljs-string">'Multiplicative inverse modulo does not exist!'</span>
  <span class="hljs-keyword">end</span>
  x % et
<span class="hljs-keyword">end</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">chinese_remainder</span><span class="hljs-params">(mods, remainders)</span></span>
  max = mods.inject(<span class="hljs-symbol">:*</span>) <span class="hljs-comment"># product of all moduli</span>
  series = remainders.zip(mods).map { <span class="hljs-params">|r, m|</span> (r * max * invmod(max / m, m) / m) }
  series.inject(<span class="hljs-symbol">:+</span>) % max
<span class="hljs-keyword">end</span>

remainders_hash = bus_lines.each_with_index.map <span class="hljs-keyword">do</span> <span class="hljs-params">|value, index|</span>
  {value.to_i =&gt; value.to_i - index} <span class="hljs-keyword">if</span> value
<span class="hljs-keyword">end</span>.compact.inject(<span class="hljs-symbol">:merge</span>)

puts <span class="hljs-string">"Second task answer: <span class="hljs-subst">#{chinese_remainder(remainders_hash.keys, remainders_h</span></span>
</code></pre>]]></content:encoded></item><item><title><![CDATA[Advent of Code 2020: Day 12 (Ruby solution)]]></title><description><![CDATA[Today's puzzle are to simulate ship's routes. The navigation instructions (input file) consists of a sequence of single-character actions paired with integer input values

Action N means to move north by the given value.
Action S means to move south ...]]></description><link>https://rubywizards.com/advent-of-code-2020-day-12-ruby-solution</link><guid isPermaLink="true">https://rubywizards.com/advent-of-code-2020-day-12-ruby-solution</guid><category><![CDATA[Ruby]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Sat, 12 Dec 2020 17:22:35 GMT</pubDate><content:encoded><![CDATA[<p>Today's puzzle are to simulate ship's routes. The navigation instructions (input file) consists of a sequence of single-character actions paired with integer input values</p>
<ul>
<li>Action N means to move north by the given value.</li>
<li>Action S means to move south by the given value.</li>
<li>Action E means to move east by the given value.</li>
<li>Action W means to move west by the given value.</li>
<li>Action L means to turn left the given number of degrees.</li>
<li>Action R means to turn right the given number of degrees.</li>
<li>Action F means to move forward by the given value in the direction the ship is currently facing.</li>
</ul>
<p>Here is my ship implementation form the first task.</p>
<pre><code><span class="hljs-keyword">require</span> <span class="hljs-string">'matrix'</span>

WORLD_DIRECTIONS = {
    <span class="hljs-string">'N'</span> =&gt; Vector[<span class="hljs-number">0</span>, <span class="hljs-number">1</span>],
    <span class="hljs-string">'E'</span> =&gt; Vector[<span class="hljs-number">1</span>, <span class="hljs-number">0</span>],
    <span class="hljs-string">'S'</span> =&gt; Vector[<span class="hljs-number">0</span>, -<span class="hljs-number">1</span>],
    <span class="hljs-string">'W'</span> =&gt; Vector[-<span class="hljs-number">1</span>, <span class="hljs-number">0</span>]
}.freeze

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">number_of_crosses</span> <span class="hljs-title">action</span>, <span class="hljs-title">angle</span></span>
  ((action == <span class="hljs-string">'L'</span> ? <span class="hljs-number">1</span> : -<span class="hljs-number">1</span>) * angle / <span class="hljs-number">90</span>) % <span class="hljs-number">4</span>
<span class="hljs-keyword">end</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Ship</span></span>
  <span class="hljs-keyword">attr_reader</span> <span class="hljs-symbol">:position</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span></span>
    @position = Vector[<span class="hljs-number">0</span>, <span class="hljs-number">0</span>]
    @rotation = Vector[<span class="hljs-number">1</span>, <span class="hljs-number">0</span>]
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">navigate</span> <span class="hljs-title">instructions</span></span>
    instructions.each <span class="hljs-keyword">do</span> <span class="hljs-params">|instruction|</span>
      action = instruction[<span class="hljs-number">0</span>]
      value = instruction.scan(<span class="hljs-regexp">/\d+/</span>).first.to_i
      process_instruction action, value
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>

  protected

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_instruction</span> <span class="hljs-title">action</span>, <span class="hljs-title">value</span></span>
    <span class="hljs-keyword">if</span> action.start_with? <span class="hljs-string">'L'</span>, <span class="hljs-string">'R'</span>
      rotate action, value
    <span class="hljs-keyword">else</span>
      move action, value
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">rotate</span> <span class="hljs-title">action</span>, <span class="hljs-title">angle</span></span>
    number_of_crosses(action, angle).times { @rotation = @rotation.cross }
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">move</span> <span class="hljs-title">action</span>, <span class="hljs-title">distance</span></span>
    vector = WORLD_DIRECTIONS.merge(<span class="hljs-string">'F'</span> =&gt; @rotation)[action]
    @position += distance * vector
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre><p>In the second task, instruction are different:</p>
<ul>
<li>Action N means to move the waypoint north by the given value.</li>
<li>Action S means to move the waypoint south by the given value.</li>
<li>Action E means to move the waypoint east by the given value.</li>
<li>Action W means to move the waypoint west by the given value.</li>
<li>Action L means to rotate the waypoint around the ship left (counter-clockwise) the given number of degrees.</li>
<li>Action R means to rotate the waypoint around the ship right (clockwise) the given number of degrees.</li>
<li>Action F means to move forward to the waypoint a number of times equal to the given value.</li>
</ul>
<p>I provided a second ship implementation extending the previous one. Here is the rest of the code:</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ShipWithWaypoints</span> &lt; Ship</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span></span>
    @waypoint_position = Vector[<span class="hljs-number">10</span>, <span class="hljs-number">1</span>]
    <span class="hljs-keyword">super</span>
  <span class="hljs-keyword">end</span>

  protected

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_instruction</span> <span class="hljs-title">action</span>, <span class="hljs-title">value</span></span>
    <span class="hljs-keyword">if</span> action.start_with? <span class="hljs-string">'L'</span>, <span class="hljs-string">'R'</span>
      rotate_waypoint action, value
    <span class="hljs-keyword">elsif</span> action.start_with? <span class="hljs-string">'F'</span>
      move value
    <span class="hljs-keyword">else</span>
      move_waypoint action, value
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">rotate_waypoint</span> <span class="hljs-title">action</span>, <span class="hljs-title">angle</span></span>
    number_of_crosses(action, angle).times { @waypoint_position = @waypoint_position.cross }
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">move_waypoint</span> <span class="hljs-title">action</span>, <span class="hljs-title">distance</span></span>
    vector = WORLD_DIRECTIONS[action]
    @waypoint_position += distance * vector
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">move</span> <span class="hljs-title">value</span></span>
    @position += @waypoint_position * value
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

file = File.read(<span class="hljs-string">'inputs/day12.txt'</span>)
instructions = file.lines
[Ship, ShipWithWaypoints].each <span class="hljs-keyword">do</span> <span class="hljs-params">|ship_type|</span>
  ship = ship_type.new
  ship.navigate(instructions)
  puts(ship.position.sum { <span class="hljs-params">|v|</span> v.abs })
<span class="hljs-keyword">end</span>
</code></pre>]]></content:encoded></item><item><title><![CDATA[Advent of Code 2020: Day 11 (Ruby solution)]]></title><description><![CDATA[This time the tasks were about modeling people taking seats in the waiting area. The seat layout is given as an input file.
The process of taking seats is iterative. Rules which are applied to every seat simultaneously differ for the first and second...]]></description><link>https://rubywizards.com/advent-of-code-2020-day-11-ruby-solution</link><guid isPermaLink="true">https://rubywizards.com/advent-of-code-2020-day-11-ruby-solution</guid><category><![CDATA[Ruby]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Fri, 11 Dec 2020 16:39:17 GMT</pubDate><content:encoded><![CDATA[<p><a target="_blank" href="https://adventofcode.com/2020/day/11">This time</a> the tasks were about modeling people taking seats in the waiting area. The seat layout is given as an input file.</p>
<p>The process of taking seats is iterative. Rules which are applied to every seat simultaneously differ for the first and second tasks.</p>
<p>In the first puzzle:</p>
<ul>
<li>If a seat is empty (L) and there are no occupied seats adjacent to it, the seat becomes occupied.</li>
<li>If a seat is occupied (#) and four or more seats adjacent to it are also occupied, the seat becomes empty.</li>
<li>Otherwise, the seat's state does not change.</li>
<li>Floor (.) never changes; seats don't move, and nobody sits on the floor.</li>
</ul>
<p>In the second one, instead of considering just the eight immediately adjacent seats, consider the first seat in each of the eight directions. It now takes five or more visible occupied seats for an occupied seat to become empty. The rest of the rules stay unchanged.</p>
<p>To deal with those tasks, I implemented WaitingArea class and two different taking seat policies.</p>
<pre><code><span class="hljs-keyword">require</span> <span class="hljs-string">'matrix'</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WaitingArea</span></span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span> <span class="hljs-title">file</span></span>
    @seats = Matrix.rows file.lines.map { <span class="hljs-params">|line|</span> line.strip.chars }
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">take_seats</span> <span class="hljs-title">policy_class</span></span>
    policy = policy_class.new(@seats)
    new_seats = @seats.dup
    @seats.each_with_index <span class="hljs-keyword">do</span> <span class="hljs-params">|e, row, col|</span>
      <span class="hljs-keyword">next</span> <span class="hljs-keyword">if</span> e == <span class="hljs-string">'.'</span>
      new_seats[row, col] = policy.take_seat?(row, col) ? <span class="hljs-string">'#'</span> : <span class="hljs-string">'L'</span>
    <span class="hljs-keyword">end</span>
    @seats = new_seats
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">occupied_seats</span></span>
    @seats.select { <span class="hljs-params">|adjacent_seat|</span> adjacent_seat == <span class="hljs-string">'#'</span> }.size
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SimpleTakingSeatPolicy</span></span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span> <span class="hljs-title">seats</span></span>
    @seats = seats
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">take_seat?</span> <span class="hljs-title">row</span>, <span class="hljs-title">col</span></span>
    seat = @seats[row, col]
    adjacent_seats = @seats.minor(
        [row - <span class="hljs-number">1</span>, <span class="hljs-number">0</span>].max..[row + <span class="hljs-number">1</span>, @seats.row_count - <span class="hljs-number">1</span>].min,
        [col - <span class="hljs-number">1</span>, <span class="hljs-number">0</span>].max..[col + <span class="hljs-number">1</span>, @seats.column_count - <span class="hljs-number">1</span>].min
    )
    occupied_adjacent_seats = adjacent_seats.select { <span class="hljs-params">|adjacent_seat|</span> adjacent_seat == <span class="hljs-string">'#'</span> }.size
    <span class="hljs-keyword">return</span> occupied_adjacent_seats &lt;= <span class="hljs-number">4</span> <span class="hljs-keyword">if</span> seat == <span class="hljs-string">'#'</span>
    occupied_adjacent_seats.zero?
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AlternativeTakingSeatPolicy</span></span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span> <span class="hljs-title">seats</span></span>
    @seats = seats
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">take_seat?</span> <span class="hljs-title">row</span>, <span class="hljs-title">col</span></span>
    seat = @seats[row, col]
    occupied_visible_seats = <span class="hljs-number">0</span>
    ([-<span class="hljs-number">1</span>, <span class="hljs-number">0</span>, +<span class="hljs-number">1</span>].repeated_permutation(<span class="hljs-number">2</span>).to_a - [[<span class="hljs-number">0</span>, <span class="hljs-number">0</span>]]).each <span class="hljs-keyword">do</span> <span class="hljs-params">|row_mod, col_mod|</span>
      i = <span class="hljs-number">0</span>
      <span class="hljs-keyword">while</span> i += <span class="hljs-number">1</span>
        x = row + (row_mod * i)
        y = col + (col_mod * i)
        <span class="hljs-keyword">break</span> <span class="hljs-keyword">if</span> x &lt; <span class="hljs-number">0</span> <span class="hljs-params">||</span> x &gt;= @seats.row_count
        <span class="hljs-keyword">break</span> <span class="hljs-keyword">if</span> y &lt; <span class="hljs-number">0</span> <span class="hljs-params">||</span> y &gt;= @seats.column_count
        tmp_seat = @seats[x, y]
        <span class="hljs-keyword">next</span> <span class="hljs-keyword">if</span> tmp_seat == <span class="hljs-string">'.'</span>
        occupied_visible_seats += <span class="hljs-number">1</span> <span class="hljs-keyword">if</span> tmp_seat == <span class="hljs-string">'#'</span>
        <span class="hljs-keyword">break</span>
      <span class="hljs-keyword">end</span>
    <span class="hljs-keyword">end</span>
    <span class="hljs-keyword">return</span> occupied_visible_seats &lt; <span class="hljs-number">5</span> <span class="hljs-keyword">if</span> seat == <span class="hljs-string">'#'</span>
    occupied_visible_seats.zero?
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

file = File.read(<span class="hljs-string">'inputs/day11.txt'</span>)
[SimpleTakingSeatPolicy, AlternativeTakingSeatPolicy].each_with_index <span class="hljs-keyword">do</span> <span class="hljs-params">|policy, index|</span>
  craft = WaitingArea.new(file)
  occupied_seats = <span class="hljs-literal">nil</span>
  <span class="hljs-keyword">while</span> occupied_seats != occupied_seats = craft.occupied_seats
    craft.take_seats(policy)
  <span class="hljs-keyword">end</span>
  puts craft.occupied_seats
<span class="hljs-keyword">end</span>
</code></pre>]]></content:encoded></item><item><title><![CDATA[Advent of Code 2020: Day 10 (Ruby solution)]]></title><description><![CDATA[Puzzles of  10th day of Advent of Code 2020 event are tricky. 
The input file contains above hundred of integers. The order is random and there is no continuity between them. The difference between the two following numbers can be 1, 2, or 3.
The fir...]]></description><link>https://rubywizards.com/advent-of-code-2020-day-10-ruby-solution</link><guid isPermaLink="true">https://rubywizards.com/advent-of-code-2020-day-10-ruby-solution</guid><category><![CDATA[Ruby]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Thu, 10 Dec 2020 22:36:34 GMT</pubDate><content:encoded><![CDATA[<p>Puzzles of  <a target="_blank" href="https://adventofcode.com/2020/day/10">10th day</a> of Advent of Code 2020 event are tricky. </p>
<p>The input file contains above hundred of integers. The order is random and there is no continuity between them. The difference between the two following numbers can be 1, 2, or 3.</p>
<p>The first task is about counting how many differences are 1 and 3. You are asked for product of their numbers. This part is easy:</p>
<pre><code><span class="hljs-attribute">file</span> = File.read('inputs/day<span class="hljs-number">10</span>.txt')
<span class="hljs-attribute">numbers</span> =<span class="hljs-meta"> [0]</span>
<span class="hljs-attribute">numbers</span> += file.lines.map(&amp;:to_i)
<span class="hljs-attribute">numbers</span>.sort!
<span class="hljs-attribute">numbers</span> &lt;&lt; numbers.last + <span class="hljs-number">3</span>
<span class="hljs-attribute">gaps</span> = {<span class="hljs-number">1</span> =&gt; <span class="hljs-number">0</span>, <span class="hljs-number">2</span> =&gt; <span class="hljs-number">0</span>, <span class="hljs-number">3</span> =&gt; <span class="hljs-number">0</span>}

<span class="hljs-attribute">numbers</span>.drop(<span class="hljs-number">1</span>).each_with_index do |number, index|
  <span class="hljs-attribute">gaps</span>[number - numbers[index]] += <span class="hljs-number">1</span>
<span class="hljs-attribute">end</span>

<span class="hljs-attribute">puts</span> <span class="hljs-string">"First task answer: #{gaps[1] * gaps[3]}"</span>
</code></pre><p>The second puzzle is problematic. You have to find total number of distinct subsets such that the difference between two following numbers still does not exceed 3. I wrote following recuresive algorithm:</p>
<pre><code>def count_arrangements numbers, start_index
  arrangements = 1
  numbers.each_with_index <span class="hljs-keyword">do</span> |<span class="hljs-built_in">number</span>, <span class="hljs-keyword">index</span>|
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">index</span> &gt; start_index &amp;&amp; <span class="hljs-keyword">index</span> &lt; numbers.size - <span class="hljs-number">1</span>
      arrangements += count_arrangements(numbers - [<span class="hljs-built_in">number</span>], <span class="hljs-keyword">index</span><span class="hljs-number">-1</span>) <span class="hljs-keyword">if</span> numbers[<span class="hljs-keyword">index</span> + <span class="hljs-number">1</span>] - numbers[<span class="hljs-keyword">index</span> - <span class="hljs-number">1</span>] &lt;= <span class="hljs-number">3</span>
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>
  arrangements
<span class="hljs-keyword">end</span>
</code></pre><p>It was good with sample inputs but when it came to my file it took ages! Then I remembered the principle of <em>divide and conquer</em>. I split my input where the difference was 3. I used my recursive algorithm on each part of them and multiplied the obtained results. It succeeded!</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">split</span> <span class="hljs-title">numbers</span></span>
  splitted = []
  tmp = []
  numbers.each_with_index <span class="hljs-keyword">do</span> <span class="hljs-params">|number, index|</span>
    tmp &lt;&lt; number
    <span class="hljs-keyword">if</span> index == numbers.size - <span class="hljs-number">1</span> <span class="hljs-params">||</span> numbers[index+<span class="hljs-number">1</span>] - number == <span class="hljs-number">3</span>
      splitted &lt;&lt; tmp
      tmp = []
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>
  splitted
<span class="hljs-keyword">end</span>

puts <span class="hljs-string">"Second task answer: <span class="hljs-subst">#{split(numbers).map { <span class="hljs-params">|ns|</span> count_arrangements(ns, <span class="hljs-number">0</span>) }</span>.reduce(:*)}"</span>
</code></pre>]]></content:encoded></item><item><title><![CDATA[Advent of Code 2020: Day 9 (Ruby solution)]]></title><description><![CDATA[Today's puzzles  are about debugging a stream of some fake protocol.
The first task is to find the first value that violates protocol rules. It means finding the first number in the list (after the preamble) which is not the sum of two of the 25 numb...]]></description><link>https://rubywizards.com/advent-of-code-2020-day-9-ruby-solution</link><guid isPermaLink="true">https://rubywizards.com/advent-of-code-2020-day-9-ruby-solution</guid><category><![CDATA[Ruby]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Wed, 09 Dec 2020 11:48:18 GMT</pubDate><content:encoded><![CDATA[<p> <a target="_blank" href="https://adventofcode.com/2020/day/9">Today's puzzles</a>  are about debugging a stream of some fake protocol.</p>
<p>The first task is to find the first value that violates protocol rules. It means finding the first number in the list (after the preamble) which is not the sum of two of the 25 numbers before it.</p>
<p>Having this value found, you have to find a contiguous set of at least two numbers in the stream which sum to this value.</p>
<p>Here is my Ruby code:</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">find_invalid_number</span> <span class="hljs-title">numbers</span>, <span class="hljs-title">preamble</span></span>
  numbers.drop(preamble).each_with_index <span class="hljs-keyword">do</span> <span class="hljs-params">|number, index|</span>
    valid = numbers[index...index + preamble].combination(<span class="hljs-number">2</span>).any? <span class="hljs-keyword">do</span> <span class="hljs-params">|combination|</span>
      combination.sum == number
    <span class="hljs-keyword">end</span>
    <span class="hljs-keyword">return</span> number <span class="hljs-keyword">unless</span> valid
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">find_contiguous_range_of_sum</span> <span class="hljs-title">numbers</span>, <span class="hljs-title">value</span></span>
  numbers.each_with_index <span class="hljs-keyword">do</span> <span class="hljs-params">|number, index|</span>
    sum = number
    i = <span class="hljs-number">0</span>
    <span class="hljs-keyword">while</span> sum &lt; value
      sum += numbers[index + (i += <span class="hljs-number">1</span>)]
    <span class="hljs-keyword">end</span>
    <span class="hljs-keyword">return</span> numbers[index...index + i] <span class="hljs-keyword">if</span> sum == value
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

file = File.read(<span class="hljs-string">'inputs/day9.txt'</span>)
numbers = file.lines.map(&amp;<span class="hljs-symbol">:to_i</span>)
puts <span class="hljs-string">"First task answer: <span class="hljs-subst">#{invalid_value = find_invalid_number(numbers, <span class="hljs-number">25</span>)}</span>"</span>
range = find_contiguous_range_of_sum(numbers, invalid_value)
puts <span class="hljs-string">"Second task answer: <span class="hljs-subst">#{range.first + range.last}</span>"</span>
</code></pre>]]></content:encoded></item><item><title><![CDATA[Advent of Code 2020: Day 8 (Ruby solution)]]></title><description><![CDATA[Tasks of the  8th day of Advent of Code 2020 are about executing instructions from the input file. The instruction list is corrupted. In the first task, you have to detect the infinite loop, stop the algorithm in this place, and check its result so f...]]></description><link>https://rubywizards.com/advent-of-code-2020-day-8-ruby-solution</link><guid isPermaLink="true">https://rubywizards.com/advent-of-code-2020-day-8-ruby-solution</guid><category><![CDATA[Ruby]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Tue, 08 Dec 2020 14:46:58 GMT</pubDate><content:encoded><![CDATA[<p>Tasks of the  <a target="_blank" href="https://adventofcode.com/2020/day/8">8th day</a> of Advent of Code 2020 are about executing instructions from the input file. The instruction list is corrupted. In the first task, you have to detect the infinite loop, stop the algorithm in this place, and check its result so far.
The second task is to repair this list by finding one wrong command and swapping them to the correct one.</p>
<p>I chose an objective approach and implemented InstructionProcessor and ProcessInstruction classes.</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InstructionProcessor</span></span>
  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InfinityLoop</span> &lt; StandardError</span>
    <span class="hljs-keyword">attr_reader</span> <span class="hljs-symbol">:acc</span>, <span class="hljs-symbol">:steps_sequence</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span> <span class="hljs-title">acc</span>, <span class="hljs-title">steps_sequence</span></span>
      @acc = acc
      @steps_sequence = steps_sequence
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span></span>
    @acc = <span class="hljs-number">0</span>
    @index = <span class="hljs-number">0</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_instructions</span> <span class="hljs-title">instruction</span></span>
    steps_sequence = []
    <span class="hljs-keyword">while</span> @index &lt; instruction.size
      raise InfinityLoop.new(@acc, steps_sequence) <span class="hljs-keyword">if</span> steps_sequence.<span class="hljs-keyword">include</span>? @index
      steps_sequence &lt;&lt; @index
      step = instruction.get_step @index
      send step[<span class="hljs-symbol">:command</span>], step[<span class="hljs-symbol">:value</span>] <span class="hljs-keyword">if</span> step
    <span class="hljs-keyword">end</span>
    @acc
  <span class="hljs-keyword">end</span>

  private

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">acc</span> <span class="hljs-title">value</span></span>
    @acc += value
    @index += <span class="hljs-number">1</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">jmp</span> <span class="hljs-title">value</span></span>
    @index += value
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">nop</span> <span class="hljs-title">value</span></span>
    @index += <span class="hljs-number">1</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre><pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProcessInstruction</span></span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span> <span class="hljs-title">path</span></span>
    @file = File.read(<span class="hljs-string">'inputs/day8.txt'</span>)
    reload
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">reload</span></span>
    @steps = @file.lines.map <span class="hljs-keyword">do</span> <span class="hljs-params">|line|</span>
      args = line.split
      {<span class="hljs-symbol">command:</span> args.first, <span class="hljs-symbol">value:</span> args.last.to_i}
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_step</span> <span class="hljs-title">index</span></span>
    @steps[index]
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">size</span></span>
    @steps.size
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">swap_command</span> <span class="hljs-title">index</span></span>
    step = @steps[index]
    <span class="hljs-keyword">if</span> step[<span class="hljs-symbol">:command</span>] == <span class="hljs-string">'jmp'</span>
      @steps[index][<span class="hljs-symbol">:command</span>] = <span class="hljs-string">'nop'</span>
    <span class="hljs-keyword">elsif</span> step[<span class="hljs-symbol">:command</span>] == <span class="hljs-string">'nop'</span>
      @steps[index][<span class="hljs-symbol">:command</span>] = <span class="hljs-string">'jmp'</span>
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre><p>Having those classes implemented it is easy to solve both tasks:</p>
<pre><code>instructions = ProcessInstruction.new(<span class="hljs-string">'inputs/day8.txt'</span>)
retry_count = <span class="hljs-number">0</span>
<span class="hljs-keyword">begin</span>
  res = InstructionProcessor.new.process_instructions(instructions)
  puts <span class="hljs-string">"Second task answer: <span class="hljs-subst">#{res}</span>"</span>
<span class="hljs-keyword">rescue</span> InstructionProcessor::InfinityLoop =&gt; e
  <span class="hljs-keyword">if</span> retry_count == <span class="hljs-number">0</span>
    puts <span class="hljs-string">"First task answer: <span class="hljs-subst">#{e.acc}</span>"</span>
    original_step_sequence = e.steps_sequence
  <span class="hljs-keyword">end</span>
  instructions.reload
  retry_count += <span class="hljs-number">1</span>
  instructions.swap_command(original_step_sequence[original_step_sequence.size - retry_count])
  <span class="hljs-keyword">retry</span>
<span class="hljs-keyword">end</span>
</code></pre>]]></content:encoded></item><item><title><![CDATA[Advent of Code 2020: Day 7 (Ruby solution)]]></title><description><![CDATA[This time the puzzle is a little bit harder.
You get text instructions in not-trivial to parse form, like:

striped white bags contain 4 drab silver bags.
drab silver bags contain no other bags.
pale plum bags contain 1 dark black bag.
muted gold bag...]]></description><link>https://rubywizards.com/advent-of-code-2020-day-7-ruby-solution</link><guid isPermaLink="true">https://rubywizards.com/advent-of-code-2020-day-7-ruby-solution</guid><category><![CDATA[Ruby]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Tue, 08 Dec 2020 00:29:10 GMT</pubDate><content:encoded><![CDATA[<p> <a target="_blank" href="https://adventofcode.com/2020/day/7">This time</a> the puzzle is a little bit harder.</p>
<p>You get text instructions in not-trivial to parse form, like:</p>
<ul>
<li>striped white bags contain 4 drab silver bags.</li>
<li>drab silver bags contain no other bags.</li>
<li>pale plum bags contain 1 dark black bag.</li>
<li>muted gold bags contain 1 wavy red bag, 3 mirrored violet bags, 5 bright gold bags, 5 plaid white bags.</li>
</ul>
<p>Obviously, the file is much longer.</p>
<p>So the first intuitive step is to parse them to ruby structure like Hash:</p>
<pre><code>file = File.read(<span class="hljs-string">'inputs/day7.txt'</span>)

@bags_hash = {}
file.each_line <span class="hljs-keyword">do</span> <span class="hljs-params">|line|</span>
  bags_params = line.scan(<span class="hljs-regexp">/(?:\d+ )?[[:alpha:]]+ [[:alpha:]]+(?= bags?)/</span>)
  @bags_hash[bags_params[<span class="hljs-number">0</span>]] = {}
  bags_params[<span class="hljs-number">1</span>..].each <span class="hljs-keyword">do</span> <span class="hljs-params">|bag|</span>
    <span class="hljs-keyword">next</span> <span class="hljs-keyword">if</span> bag == <span class="hljs-string">'no other'</span>
    expression_params = bag.split(<span class="hljs-regexp">/(?&lt;=\d) /</span>)
    quantity = expression_params.size == <span class="hljs-number">1</span> ? <span class="hljs-number">1</span> : expression_params.first.to_i
    color_name = expression_params.last
    @bags_hash[bags_params[<span class="hljs-number">0</span>]][color_name] = quantity
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre><p>The first task is to count how many bag colors can eventually contain at least one "shiny gold bag". I do it this way:</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">bag_include_requested_color_bag?</span> <span class="hljs-title">color_bag</span>, <span class="hljs-title">requested_color_bag</span></span>
  <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span> <span class="hljs-keyword">if</span> @bags_hash[color_bag].keys.<span class="hljs-keyword">include</span>? requested_color_bag
  <span class="hljs-keyword">return</span> @bags_hash[color_bag].any? <span class="hljs-keyword">do</span> <span class="hljs-params">|key, value|</span>
    bag_include_requested_color_bag?(key, requested_color_bag)
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

res = @bags_hash.keys.sum <span class="hljs-keyword">do</span> <span class="hljs-params">|color|</span>
  bag_include_requested_color_bag?(color, <span class="hljs-string">'shiny gold'</span>) ? <span class="hljs-number">1</span> : <span class="hljs-number">0</span>
<span class="hljs-keyword">end</span>

puts <span class="hljs-string">"First task answer: <span class="hljs-subst">#{res}</span>"</span>
</code></pre><p>Another task is to find how many individual bags are required inside a single "shiny gold bag". Here is my code:</p>
<pre><code>def bags_count color_bag, quantity
  quantity + @bags_hash[color_bag].sum <span class="hljs-keyword">do</span> |<span class="hljs-keyword">key</span>, <span class="hljs-keyword">value</span>|
    quantity * bags_count(<span class="hljs-keyword">key</span>, <span class="hljs-keyword">value</span>)
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

res = @bags_hash[<span class="hljs-string">'shiny gold'</span>].sum <span class="hljs-keyword">do</span> |<span class="hljs-keyword">key</span>, <span class="hljs-keyword">value</span>|
  <span class="hljs-keyword">value</span> * bags_count(<span class="hljs-keyword">key</span>, <span class="hljs-number">1</span>)
<span class="hljs-keyword">end</span>

puts <span class="hljs-string">"Second task answer: #{res}"</span>
</code></pre>]]></content:encoded></item><item><title><![CDATA[Advent of Code 2020: Day 6 (Ruby solution)]]></title><description><![CDATA[This time input file contains many groups of entries. Entries are separated with a single newline character while groups are separated with two of them.
Your job in the first puzzle is to find how many unique letters occur in each group and count the...]]></description><link>https://rubywizards.com/advent-of-code-2020-day-6-ruby-solution</link><guid isPermaLink="true">https://rubywizards.com/advent-of-code-2020-day-6-ruby-solution</guid><category><![CDATA[Ruby]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Sun, 06 Dec 2020 20:07:33 GMT</pubDate><content:encoded><![CDATA[<p><a target="_blank" href="https://adventofcode.com/2020/day/6">This time</a> input file contains many groups of entries. Entries are separated with a single newline character while groups are separated with two of them.</p>
<p>Your job in the first puzzle is to find how many unique letters occur in each group and count them together.</p>
<p>In the second task, you are supposed to check how many unique letters occur across all entries of each group and to count them together.</p>
<p>So basically, solutions of those 2 tasks differ in one operator. In the first task, it is a union (| operator) and in the second one, it is an intersection (&amp; operator).</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">count_letters</span> <span class="hljs-title">file</span>, <span class="hljs-title">operator</span></span>
  letters_count = <span class="hljs-number">0</span>
  file.each_line(<span class="hljs-string">"\n\n"</span>) <span class="hljs-keyword">do</span> <span class="hljs-params">|group|</span>
    letters_count += group.strip.lines.map { <span class="hljs-params">|line|</span> line.strip.chars }.inject(operator).size
  <span class="hljs-keyword">end</span>
  letters_count
<span class="hljs-keyword">end</span>

file = File.read(<span class="hljs-string">'inputs/day6.txt'</span>)
puts <span class="hljs-string">"First task answer: <span class="hljs-subst">#{count_letters(file, <span class="hljs-symbol">:|</span>)}</span>"</span>
puts <span class="hljs-string">"Second task answer: <span class="hljs-subst">#{count_letters(file, <span class="hljs-symbol">:&amp;</span>)}</span>"</span>
</code></pre>]]></content:encoded></item><item><title><![CDATA[Advent of Code 2020: Day 5 (Ruby solution)]]></title><description><![CDATA[Here is my solution for puzzles of the  5th day  of the Advent of Code 2020 event.
The crucial thing is to notice that entries in the input file are just binary numbers where F and L stand for 0, B and R stand for 1.
After converting them to decimal ...]]></description><link>https://rubywizards.com/advent-of-code-2020-day-5-ruby-solution</link><guid isPermaLink="true">https://rubywizards.com/advent-of-code-2020-day-5-ruby-solution</guid><category><![CDATA[Ruby]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Sat, 05 Dec 2020 14:55:58 GMT</pubDate><content:encoded><![CDATA[<p>Here is my solution for puzzles of the  <a target="_blank" href="https://adventofcode.com/2020/day/5">5th day</a>  of the Advent of Code 2020 event.
The crucial thing is to notice that entries in the input file are just binary numbers where F and L stand for 0, B and R stand for 1.</p>
<p>After converting them to decimal numbers it is trivial to pick the maximum value. The first task is completed.</p>
<p>In the second puzzle, you are asked to find a missing entry among the file. There are few ways of doing that. I managed to find it by comparing following values with the loop index.</p>
<pre><code>file = File.<span class="hljs-keyword">read</span>(<span class="hljs-string">'inputs/day5.txt'</span>)
entries = file.lines.map <span class="hljs-keyword">do</span> |<span class="hljs-type">line</span>|
  <span class="hljs-type">line</span>.gsub(/[FBLR]/, <span class="hljs-string">'F'</span> =&gt; <span class="hljs-number">0</span>, <span class="hljs-string">'B'</span> =&gt; <span class="hljs-number">1</span>, <span class="hljs-string">'L'</span> =&gt; <span class="hljs-number">0</span>, <span class="hljs-string">'R'</span> =&gt; <span class="hljs-number">1</span>).to_i(<span class="hljs-number">2</span>)
<span class="hljs-keyword">end</span>
entries.sort!
puts "First task answer: #{entries.last}"

entries.each_with_index <span class="hljs-keyword">do</span> |entry, <span class="hljs-keyword">index</span>|
  <span class="hljs-keyword">if</span> <span class="hljs-keyword">index</span> + entries.first != entry
    puts "Second task answer: #{index + entries.first}"
    <span class="hljs-keyword">return</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>]]></content:encoded></item><item><title><![CDATA[Advent of Code 2020: Day 4 (Ruby solution)]]></title><description><![CDATA[Another day,  another problem to solve. This time, you are asked for some data validation. You are supposed to parse entries from a text file.
In the first task, only validating the presence of some keys is required.
In the second one, you have to ch...]]></description><link>https://rubywizards.com/advent-of-code-2020-day-4-ruby-solution</link><guid isPermaLink="true">https://rubywizards.com/advent-of-code-2020-day-4-ruby-solution</guid><category><![CDATA[Ruby]]></category><dc:creator><![CDATA[Piotr Jurewicz]]></dc:creator><pubDate>Fri, 04 Dec 2020 12:13:53 GMT</pubDate><content:encoded><![CDATA[<p>Another day,  <a target="_blank" href="https://adventofcode.com/2020/day/4">another problem to solve</a>. This time, you are asked for some data validation. You are supposed to parse entries from a text file.</p>
<p>In the first task, only validating the presence of some keys is required.
In the second one, you have to check if values meet certain specific constraints:</p>
<ul>
<li>byr (Birth Year) - four digits; at least 1920 and at most 2002.</li>
<li>iyr (Issue Year) - four digits; at least 2010 and at most 2020.</li>
<li>eyr (Expiration Year) - four digits; at least 2020 and at most 2030.</li>
<li>hgt (Height) - a number followed by either cm or in:
If cm, the number must be at least 150 and at most 193.
If in, the number must be at least 59 and at most 76.</li>
<li>hcl (Hair Color) - a # followed by exactly six characters 0-9 or a-f.</li>
<li>ecl (Eye Color) - exactly one of: amb blu brn gry grn hzl oth.</li>
<li>pid (Passport ID) - a nine-digit number, including leading zeroes.</li>
<li>cid (Country ID) - ignored, missing or not.</li>
</ul>
<pre><code>REQUIRED_FIELDS = <span class="hljs-string">%w(byr iyr eyr hgt hcl ecl pid)</span>
EYE_COLORS = <span class="hljs-string">%w(amb blu brn gry grn hzl oth)</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">count_valid_entries</span> <span class="hljs-title">file</span>, <span class="hljs-title">strong_validation</span> = <span class="hljs-title">false</span></span>
  valid = <span class="hljs-number">0</span>
  file.each_line(<span class="hljs-string">"\n\n"</span>) <span class="hljs-keyword">do</span> <span class="hljs-params">|entry|</span>
    attrs = Hash[entry.split.map { <span class="hljs-params">|key_and_val|</span> key_and_val.split(<span class="hljs-string">':'</span>) }]
    <span class="hljs-keyword">next</span> <span class="hljs-keyword">unless</span> (REQUIRED_FIELDS - attrs.keys).empty?
    <span class="hljs-keyword">if</span> strong_validation
      <span class="hljs-keyword">next</span> <span class="hljs-keyword">unless</span> attrs[<span class="hljs-string">'byr'</span>].to_i.between?(<span class="hljs-number">1920</span>, <span class="hljs-number">2002</span>)
      <span class="hljs-keyword">next</span> <span class="hljs-keyword">unless</span> attrs[<span class="hljs-string">'iyr'</span>].to_i.between?(<span class="hljs-number">2010</span>, <span class="hljs-number">2020</span>)
      <span class="hljs-keyword">next</span> <span class="hljs-keyword">unless</span> attrs[<span class="hljs-string">'eyr'</span>].to_i.between?(<span class="hljs-number">2020</span>, <span class="hljs-number">2030</span>)
      hgt = attrs[<span class="hljs-string">'hgt'</span>].scan(<span class="hljs-regexp">/\d+/</span>).first.to_i
      <span class="hljs-keyword">if</span> attrs[<span class="hljs-string">'hgt'</span>].end_with?(<span class="hljs-string">'cm'</span>)
        <span class="hljs-keyword">next</span> <span class="hljs-keyword">unless</span> hgt.between?(<span class="hljs-number">150</span>, <span class="hljs-number">193</span>)
      <span class="hljs-keyword">elsif</span> attrs[<span class="hljs-string">'hgt'</span>].end_with?(<span class="hljs-string">'in'</span>)
        <span class="hljs-keyword">next</span> <span class="hljs-keyword">unless</span> hgt.between?(<span class="hljs-number">59</span>, <span class="hljs-number">76</span>)
      <span class="hljs-keyword">else</span> <span class="hljs-keyword">next</span>
      <span class="hljs-keyword">end</span>
      <span class="hljs-keyword">next</span> <span class="hljs-keyword">unless</span> attrs[<span class="hljs-string">'hcl'</span>].match? <span class="hljs-regexp">/\A#[0-9a-fA-F]{6}\z/</span>
      <span class="hljs-keyword">next</span> <span class="hljs-keyword">unless</span> EYE_COLORS.<span class="hljs-keyword">include</span>? attrs[<span class="hljs-string">'ecl'</span>]
      <span class="hljs-keyword">next</span> <span class="hljs-keyword">unless</span> attrs[<span class="hljs-string">'pid'</span>].match? <span class="hljs-regexp">/\A\d{9}\z/</span>
    <span class="hljs-keyword">end</span>
    valid += <span class="hljs-number">1</span>
  <span class="hljs-keyword">end</span>
  valid
<span class="hljs-keyword">end</span>

file = File.read(<span class="hljs-string">'inputs/day4.txt'</span>)
puts count_valid_entries(file)
puts count_valid_entries(file, <span class="hljs-literal">true</span>)
</code></pre>]]></content:encoded></item></channel></rss>