Sign in

Oinak

@oinak.com
50 followers 67 following 80 posts

🐛Bug farmer 🐌 (Neo)Vim snail 🧑‍💻 Ruby lover 🐧 Linux user 🚲 Bike rider ♀️🏳️‍🌈♿ Learning Ally ✊🏿 BlackLivesMatter 🏳️‍⚧️ Trans rights are human rights 🫂 No human being is illegal 🤺 Amateur sabre fencer 📚 Sci-fi/solarpunk dreamer 💎 Diadem (RQ fan) 🐘@oinak@ruby.social

PostsRepliesMedia
Oinak @oinak.com · 30/06/2026
I am 103% positive I will forget I have bought this on pre-sale by next march, the good side is that I will probably buy another and keep @kameronhurley.com 's dogs and ducks well fed
A screenshot of an amazon pre-sale purchase confirmation for "These Savage Stars" by @kameronhurley.com with estimated delivery on march 22nd, 2027
000
Oinak @oinak.com · 11/06/2026
:)
A screenshot of a terminal with the following text:

$ ./update-nvim.sh
Found nvim at: /usr/local/bin/nvim
Current version: v0.12.2
Checking for latest release...
Latest version: v0.12.3
New version available: v0.12.3
Download URL: https://github.com/neovim/neovim/releases/download/v0.12.3/nvim-linux-x86_64.appimage
Downloading latest AppImage...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100 10.9M  100 10.9M    0     0  16.8M      0 --:--:-- --:--:-- --:--:-- 16.8M
Creating backup at: /usr/local/bin/nvim.backup
[sudo] contraseña para fer:
Installing new version...
Successfully updated to v0.12.3!
Backup saved at: /usr/local/bin/nvim.backup
020
Oinak @oinak.com · 22/01/2026
I started refactoring #adventOfCode Day 9 in #ruby and in this case I opted for extracting auxiliary modules (Memoize) and classes (Point and Rectangle). Unusual for AoC but satisfactory.
# auxiliary classes
Point = Data.define(:x, :y)

Rectangle = Data.define(:corner1, :corner2) do
  def area
    ((corner2.x - corner1.x).abs + 1) * ((corner2.y - corner1.y).abs + 1)
  end

  def xy_bounds
    x_min, x_max = [corner1.x, corner2.x].minmax
    y_min, y_max = [corner1.y, corner2.y].minmax
    [x_min, x_max, y_min, y_max]
  end
end

# intentionally naive for argless methods
module Memoize
  def memoize(name)
    original = instance_method(name)
    ivar_name = "@#{name}"
    define_method(name) do
      if instance_variable_defined?(ivar_name)
        instance_variable_get(ivar_name)
      else
        instance_variable_set(ivar_name, original.bind(self).call)
      end
    end
  end
end# Advent of code - Day 9
class MovieTheater
  extend Memoize

  attr_reader :corners

  def initialize(filename)
    @corners = File.readlines(filename).map do |line|
      x, y = line.strip.split(',').map(&:to_i)
      Point[x, y]
    end
  end

  def solution(advanced: false) = advanced ? max_inscribed_rectangle : largest_area

  # Part 1
  def largest_area = all_rectangles.map(&:area).max

  # Part 2
  def max_inscribed_rectangle = sorted_rectangles.rfind { inscribed?(it) }&.area || 0

  private

  memoize def all_rectangles = corners.combination(2).map { |pair| Rectangle[*pair] }

  # Part 2
  def sorted_rectangles = all_rectangles.sort_by(&:area)

  def inscribed?(rect)
    x_min, x_max, y_min, y_max = rect.xy_bounds
    !intersects_rows?(x_min, x_max, y_min, y_max) && !intersects_cols?(x_min, x_max, y_min, y_max)
  end

  def intersects_cols?(x_min, x_max, y_min, y_max)
    col_spans.any? do |x, (c_min, c_max)|
      next false unless x > x_min && x < x_max

      c_max > y_min && c_min < y_max
    end
  end

  def intersects_rows?(x_min, x_max, y_min, y_max)
    row_spans.any? do |y, (r_min, r_max)|
      next false unless y > y_min && y < y_max

      r_max > x_min && r_min < x_max
    end
  end

  memoize def row_spans = corners.group_by(&:y).transform_values { |cs| cs.map(&:x).minmax }
  memoize def col_spans = corners.group_by(&:x).transform_values { |cs| cs.map(&:y).minmax }
end
000
Oinak @oinak.com · 03/01/2026
Day 8 of #adventOfCode refactors (Playground) ended up only slightly shorter than my original version but I replaced a little bit of _Primitive Obsession_ hashes and arrays with a couple of nice #ruby's Data classes, and the readability became delightful, I hope you enjoy it
# Advent of code - Day 8
class Playground
  attr_accessor :boxes, :circuits

  def initialize(filename)
    @boxes = File.readlines(filename).map { Box[*it.strip.split(',').map(&:to_i)] }
    @circuits = boxes.map { |box| [box] }
  end

  def solution(advanced: false, connections: 1_000)
    advanced ? product_last_connection : product_three_closest(connections)
  end

  ## Part 1
  def product_three_closest(connections)
    make_connections(connections)
    circuits.map(&:size).sort.last(3).inject(:*)
  end

  def make_connections(connections)
    distances.take(connections).each { |d| combine_boxes(d.a, d.b) }
  end

  ## Part 2
  def product_last_connection
    last = make_all_connections
    last.a.x * last.b.x
  end

  def make_all_connections
    distances.each do |d|
      combine_boxes(d.a, d.b)
      return d if @circuits.size == 1
    end
    raise 'All connections made but multiple circuits remain'
  end

  ## Shared
  Box = Data.define(:x, :y, :z) do
    def distance(other) # euclidean distance
      Math.sqrt(deconstruct.zip(other.deconstruct).sum { |a, b| (a - b)**2 })
    end
  end

  Dist = Data.define(:a, :b, :distance)

  def distances
    @distances ||= boxes.combination(2).map do |(a, b)|
      Dist[a, b, a.distance(b)]
    end.sort_by(&:distance)
  end

  def combine_boxes(a, b)
    c_a = circuits.find { |c| c.include?(a) }
    c_b = circuits.find { |c| c.include?(b) }
    return if c_a == c_b

    c_a.concat(circuits.delete(c_b))
  end
end
020
Oinak @oinak.com · 30/12/2025
Day #7 of #adventOfCode allowed me to reduce 4 methods to 2, without exceeding rubocop's ABC/complexity thresholds. Also taking advantage of #ruby's flat_map and case-in ☺️. Having a single method per part is oddly satisfying. The cherry on top? both parts run on 0.07s with the large input.
# frozen_string_literal: true

# Advent of code - Day 7
class Laboratories
  attr_reader :data, :source, :cache

  SOURCE = 'S'
  SPLITTER = '^'

  def initialize(filename)
    @data = File.readlines(filename).map { it.strip.chars }
    @source = data[0].index(SOURCE)
    @cache = {}
  end

  def solution(advanced: false) = advanced ? ray_trace : count_splits

  # Part1
  def count_splits(rays = [source])
    data.inject(0) do |splits, row|
      rays = rays.flat_map do |x|
        next [x] if row[x] != SPLITTER

        splits += 1
        [x - 1, x + 1]
      end.uniq
      splits
    end
  end

  # Part 2
  def ray_trace(x = source, y = 0) # rubocop:disable Naming/MethodParameterName
    cache[[x, y]] ||=
      case data.dig(y, x)
      in nil then 1
      in SPLITTER then ray_trace(x - 1, y + 1) + ray_trace(x + 1, y + 1)
      in String then ray_trace(x, y + 1)
      end
  end
end

if __FILE__ == $PROGRAM_NAME
  obj = Laboratories.new(ARGV[0])
  puts obj.solution
  puts obj.solution(advanced: true)
end
020
Oinak @oinak.com · 27/12/2025
Day 6 of #adventOfCode was very interesting because I got to use `Array#transpose` and `Enumerable#slice_after`, both out of my usual #ruby tool set, but when I set myself to try to refactor or polish it, I could not find any change I was missing, pure delight
# frozen_string_literal: true

# Advent of code - Day 6
class TrashCompactor
  attr_reader :values, :operators

  def initialize(filename) = @filename = filename

  def solution(advanced: false) = advanced ? sum_columns : sum_operations

  def sum_operations
    *values, operators = File.readlines(@filename).map { it.strip.split }
    operators.each_with_index.reduce(0) do |sum, (operator, index)|
      operands = values.map { |row| row[index].to_i }
      sum + operands.reduce(operator.to_sym)
    end
  end

  def sum_columns
    read_operations.reduce(0) do |sum, operation|
      operator = operation.first[-1]
      operands = operation.map(&:to_i)
      sum + operands.reduce(operator.to_sym)
    end
  end

  def read_operations
    columns = File.readlines(@filename).map { it.chomp.chars }.transpose
    operations = columns.map(&:join).slice_after { |el| blank?(el) } # split on blank columns
    operations.map { it.reject { |el| blank?(el) } }
  end

  def blank?(str) = str.match?(/\A\s+\Z/)
end

if __FILE__ == $PROGRAM_NAME
  obj = TrashCompactor.new(ARGV[0])
  puts obj.solution
  puts obj.solution(advanced: true)
end
020
Oinak @oinak.com · 26/12/2025
While reviewing Day 5 of #adventOfCode I found several happy moments 1. could reuse `range(line)` from a previous exercise 2. part 2 has 2 alternative forms I like The `then` pipeline, and the one used that requires ranges to be sorted upon read. I like both and love #ruby's expressive flexibility
# frozen_string_literal: true

# Advent of code - Day 5
class Cafeteria
  attr_reader :ranges, :ids

  def initialize(filename)
    range_data, id_data = File.read(filename).strip.split("\n\n")
    @ranges = read_ranges(range_data).sort_by(:begin)
    @ids = id_data.split("\n").map(&:to_i)
  end

  def read_ranges(ranges) = ranges.split("\n").map { |line| range(line) }
  def range(line) = Range.new(*line.split('-').map(&:to_i))
  
  def solution(advanced: false) = advanced ? count_ranges : count_fresh_ids
  
  def count_fresh_ids = @ids.count { |id| ranges.any? { it.include? id } }
  def count_ranges = combine(ranges).sum(&:size)

  def combine(sorted)
    sorted[1..].each_with_object([sorted[0]]) do |current, combined|
      if combined[-1].overlap?(current)
        combined[-1] = join(combined[-1], current)
      else
        combined << current
      end
    end
  end

  def join(left, right) = left.begin..[left.end, right.end].max
end

if __FILE__ == $PROGRAM_NAME
  obj = Cafeteria.new(ARGV[0])
  puts obj.solution
  puts obj.solution(advanced: true)
end  def count_ranges
    ranges
      .then { |ranges| ranges.sort_by(&:begin) }
      .then { |sorted| combine(sorted) }
      .then { |combined| combined.sum(&:size) }
  end
000
Oinak @oinak.com · 26/12/2025
Day 4 of #ruby #adventOfCode was the longest so far but has 2 things I ♥️, part_2 calling part_1's method, and readability one-liners
# frozen_string_literal: true

# Advent of code - Day 4
class PrintingDepartment
  attr_reader :data

  ROLL = '@'
  REMOVED = 'x'

  def initialize(filename)
    @data = File.readlines(filename).map { it.strip.chars }
  end

  def solution(advanced: false)
    advanced ? count_and_remove : count_accesible_rolls
  end

  # part 1
  def count_accesible_rolls(remove: false)
    rows.sum do |r|
      cols.count { |c| roll?(r, c) && accessible?(r, c, remove:) }
    end
  end

  # part 2
  def count_and_remove(total = 0)
    loop do
      removed = count_accesible_rolls(remove: true)
      break total if removed.zero?

      total += removed
    end
  end

  def accessible?(row, col, remove: false)
    (bounding_box(row, col).count { roll?(*it) } < 4).tap do |value|
      data[row][col] = REMOVED if value && remove
    end
  end

  def bounding_box(row, col)
    [row - 1, row, row + 1].product([col - 1, col, col + 1]) - [[row, col]]
  end

  def roll?(row, col) = tile(row, col) == ROLL
  def tile(row, col) = rows === row && cols === col && data[row][col]
  def rows = 0...data.size
  def cols = 0...data[0].size
end

if __FILE__ == $PROGRAM_NAME
  obj = PrintingDepartment.new(ARGV[0])
  puts obj.solution
  puts obj.solution(advanced: true)
end
010
Oinak @oinak.com · 26/12/2025
Day 3 on needlessly polishing #adventOfCode solutions in #ruby for playful aesthetic pleasure
# frozen_string_literal: true

# Advent of code - Day 3
class Lobby
  attr_reader :data

  def initialize(filename)
    @data = File.readlines(filename).map(&:strip)
  end

  def solution(advanced: false) = sum_of_banks(advanced ? 12 : 2)

  def sum_of_banks(cells)
    data.sum { |bank| max_joltage(bank.chars, cells).join.to_i }
  end

  def max_joltage(batteries, cells)
    return [] if cells == 0

    value = batteries[..-cells].max
    position = batteries.index(value)
    [value] + max_joltage(batteries[(position + 1)..], cells - 1)
  end
end

if __FILE__ == $PROGRAM_NAME
  obj = Lobby.new(ARGV[0])
  puts obj.solution
  puts obj.solution(advanced: true)
end
010
Oinak @oinak.com · 26/12/2025
Earlier today I polished #adventOfCode Day 1 (bsky.app/profile/oina...) and enjoyed it, so here is Day 2 in 4 one-line #ruby methods, I hope it brings you some joy ☺️
# frozen_string_literal: true

# Advent of code - Day 2
class GiftShop
  attr_reader :data

  def initialize(filename)
    @data = File.read(filename).split(',')
  end

  def solution(advanced: false)
    sum_invalid_ids(advanced ? TWICE_OR_MORE : TWICE)
  end

  TWICE = /\A(\d+)\1\Z/
  TWICE_OR_MORE = /\A(\d+)\1+\Z/

  def sum_invalid_ids(invalid)
    data.sum { |line| range(line).select { invalid =~ it.to_s }.sum }
  end

  # "xx-yy" => xx.to_i..yy.to_i
  def range(line) = Range.new(*line.split('-').map(&:to_i))
end

if __FILE__ == $PROGRAM_NAME
  obj = GiftShop.new(ARGV[0])
  puts obj.solution
  puts obj.solution(advanced: true)
end
000
Oinak @oinak.com · 26/12/2025
I keep seeing other people's solution to #adventofcode on other languages and keep falling in love with #ruby even more, here I polished my previous Day 1 solution, with less state, less lines, less data structures... more ruby
# frozen_string_literal: true

# Advent of code - Day 1
class SecretEntrance
  attr_reader :data, :dial

  def initialize(filename)
    @data = File.readlines(filename).map(&:strip)
    @dial = 100
  end

  def solution(advanced: false)
    advanced ? with_passing : rotate_and_count_zeroes
  end

  DIRECTIONS = { 'L' => -1, 'R' => 1 }.freeze

  def rotate_and_count_zeroes(position = 50)
    data.sum do |line|
      change = value(line) * direction(line)
      position = (position + change) % dial
      position.zero? ? 1 : 0
    end
  end

  def direction(line) = DIRECTIONS[line[0]]
  def value(line) = line[1..].to_i

  def with_passing(position = 50)
    data.sum do |line|
      value(line).times.sum do
        position = (position + direction(line)) % dial
        position.zero? ? 1 : 0
      end
    end
  end
end

if __FILE__ == $PROGRAM_NAME
  obj = SecretEntrance.new(ARGV[0])
  puts obj.solution
  puts obj.solution(advanced: true)
end
000
Oinak @oinak.com · 19/12/2025
It is peak literature, but it will always be "Lesbians in Space" in my heart
130
Oinak @oinak.com · 19/12/2025
Been there since 2018, today upleveled because I am struggling less than then and I feel I can. I know it's a small drop in a leaky bucket, but I hope it helps. I wish I could be filthy rich and drown you in movie rights... A Paul Verhoeven Light Brigade, a Denis Villeneuve Stars are Legion...
000