1
\$\begingroup\$

The task

A file similar to the following is provided.

RL

AAA = (BBB, CCC)
BBB = (DDD, EEE)
CCC = (ZZZ, GGG)
DDD = (DDD, DDD)
EEE = (EEE, EEE)
GGG = (GGG, GGG)
ZZZ = (ZZZ, ZZZ)

The first line will always be an alternating sequence of L and R characters, referred to as the path.
The remaining lines describe a network.
For each line, the first set of three uppercase letters represents a node. Each node has two paths, left and right, represented by the second and thirds sets of three uppercase letters, respectively.

For Part 1, the player is asked to compute the number of steps necessary to reach node "ZZZ" starting from node "AAA". Each character in the path represents the direction one must follow at each step, d. If d is L, the left path must be taken. If d is R the right path must be taken.
If all the directions have been followed and the target node has not yet been found, the program is to continue following the directions from the start.

My approach

I first split the file's text into two variables, path and node_list using the blank line that separates the two parts.
From node_list, I create a new hash, nodes, with the following structure

{
  'AAA' => { 'L' => 'BBB', 'R' => 'CCC' },
  'BBB' => { 'L' => 'DDD', 'R' => 'EEE' },
  ...
}

I then created a method, count_steps, which returns the number of steps required to reach the target node.

My solution

data = File.open('input')
           .read

path, node_list = data.split("\n\n")

nodes = node_list.split("\n")
                 .map { |l| l.scan(/\w+/) }
                 .reduce({}) { |hash, (k, l, r)| hash.update(k => { 'L' => l, 'R' => r }) }

def count_steps(path, nodes, src, target)
  steps = 0
  until src.end_with? target
    path.each_char do |d|
      src = nodes[src][d]
      steps += 1
    end
  end

  steps
end

################################################################################
# PART 1
################################################################################

part_one = count_steps(path, nodes, 'AAA', 'ZZZ')
p part_one

################################################################################
# PART 2
################################################################################

src_nodes = nodes.keys.filter { |k| k[-1] == 'A' }
part_two = src_nodes.map { |n| count_steps(path, nodes, n, 'Z') }.reduce(1, :lcm)
p part_two

My question

Is there a more idiomatic (and concise) approach for the count_steps method?
i.e. without using a while loop and incrementing a variable
This is not a requirement of the task, however I ask because ruby often has very elegant and succinct ways of using its builtin methods to obtain, in one or two lines, the same result as a multi-line loop where values are incremented. All other critique is obviously also appreciated.

\$\endgroup\$
4
  • 5
    \$\begingroup\$ Please could you add a summary of the problem being solved, and also mention that in the title? See How do I ask a good question? for some advice on what information needs to be included. The reference to the challenge site may be a useful supplement, but the question needs to be meaningful when that's not accessible. \$\endgroup\$ Dec 8, 2023 at 11:07
  • 1
    \$\begingroup\$ without using a while loop and incrementing a variable -> is this a requirement of the coding task? \$\endgroup\$
    – radarbob
    Dec 8, 2023 at 17:00
  • \$\begingroup\$ Is this a linked list of nodes? That 1st hash, its values maybe should be an object. A "Node" object contains the link to the next node. Certainly this works towards "concise" and "idiomatic" ... and OO happiness. \$\endgroup\$
    – radarbob
    Dec 8, 2023 at 17:07
  • \$\begingroup\$ @radarbob It is not a requirement. I did some editing, hopefully the question is clearer now. \$\endgroup\$
    – francesco
    Dec 9, 2023 at 11:13

1 Answer 1

2
\$\begingroup\$

You're solving day 8, which has us traversing a binary tree.

change of meaning

This is, of course, perfectly valid ruby, with two separate scopes:

         .map { |l| l.scan(/\w+/) }
         .reduce({}) { |hash, (k, l, r)| hash.update(k => { 'L' => l, 'R' => r }) }

But the use of l to denote a line of text and then to denote the left node is slightly jarring.

Consider phrasing the first one as .map { |line| ..., even if l is what your fingers have memorized for the common idiom.


build a list

We are asked for a count of steps, so the current count_steps looks perfectly nice to me; keep it as-is. I count six LOC in the body.

concise approach

Imagine we were instead asked for a map of the nodes visited. This could for example be used in loop detection, for telling whether we had looped back to an already visited node.

Then a natural datastructure would be to accumulate a list (or set). We could keep using final element as the nodes key, and then .length gives the number of steps. It doesn't really look more concise though. Better to keep what you have.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.