g | x | w | all
Bytes Lang Time Link
371Python3240808T165323ZAjax1234
082Perl 5 p211222T011921ZAnders K
160Pari/GP211222T043532Zalephalp
053Retina 0.8.2211222T143618ZNeil
105Wolfram Language Mathematica211222T012013Zalephalp
139Charcoal211222T182951ZNeil
568TypeScript Types211222T181205Ztjjfvi

Python3, 371 bytes

def f(p):
 q,s=[p],[p]
 for i in q:
  for I,a in enumerate(i):
   t=0
   if I:
    if a==3:t=i[:-2]if I+1==len(i)else i[:I-1]+[(i[I-1]+3+i[I+1])%6]+i[I+2:]
    if a in[2,4]:
     U=[-1,1][a==2]
     t=i[:-2]+[(i[-2]+U)%6,[4,2][a==4]]if I+1==len(i)else i[:I-1]+[(i[I-1]+U)%6,[2,4][a==2],(i[I+1]+U)%6]+i[I+2:]
   if t!=0 and t not in s:q+=[t];s+=[t]
 return min(map(len,s))

Try it online!

Perl 5 -p, 82 bytes

s/./"bc"x($&+3)."ab"/ge;1while s/(.)\1//+s/(b|cac)a/a$1/+s/(cbcbca?)b/b$1/;$_=y;a;

Try it online!

How it works

Draw right triangle \$ABC\$, where \$A\$ is the center of your initial tile, \$B\$ is the vertex of the edge behind you on your right, and \$C\$ is the center of the edge behind you. The group of symmetries of the tiling are generated by the reflections \$a, b, c\$ over the sides \$BC\$, \$CA\$, \$AB\$ of this triangle, which satisfy the equations

$$a^2 = b^2 = c^2 = (bc)^6 = (ca)^4 = (ab)^2 = ε.$$

We can translate our path to a symmetry by applying \$(bc)^{n + 3}ab\$ for each step \$n\$. Conversely, any symmetry written as an even-length string of \$a, b, c\$ can be translated to a path whose length is the number of \$a\$s.

With the Knuth–Bendix algorithm, we can transform the above equations to a rewriting system that produces a canonical representation for any symmetry:

\begin{align*} aa &→ ε, \\ bb &→ ε, \\ cc &→ ε, \\ ba &→ ab, \\ caca &→ acac, \\ cbcbcb &→ bcbcbc, \\ cbcbcab &→ bcbcbca. \end{align*}

Since these rules never increase the number of \$a\$s, we are guaranteed that the canonical representation has the minimal number of \$a\$s.

Pari/GP, 160 bytes

a->for(i=1,2*#p=Mod(Polrev(a)+y*x^#a,6),p=-if([[t=p%x^l+(3-s)*x^l--+(p\x^j+3)*x^j-=2|j<-[i..#p],x^(k=j-i)+1+x^k++\(x-1)==s=p%x^j\x^l=i-1]|i<-[2..#p]],t,p));#p-1

Try it online!

Based on my Mathematica answer.

Retina 0.8.2, 55 53 bytes

\d
$*____ab
_
bc
(.)\1

(b|cac)a
a$1
}`(cbcbc)b
b$1
a

Try it online! Link includes test cases. Explanation: Another port of @AndersKaseorg's Perl answer. Edit: Saved 2 bytes using @alephalpha's observation.

\d
$*____ab
_
bc

First expand each digit into n+3 underscores followed by ab, then replace each underscore with bc.

(.)\1

(b|cac)a
a$1
(cbcbc)b
b$1

Perform the simplifications.

}`

Repeat until no more simplifications can be made.

a

Count the remaining number of as.

Wolfram Language (Mathematica), 105 bytes

-7 bytes thanks to @att.

Count[Table[t=b.c,#+3].a.b&/@#/.List->Dot//.{(n:a|b|c).n_:>Dot[],(n:b|c.a.c).a:>a.n,(n:c.t.t).b:>b.n},a]&

Try it online!

A port of Anders Kaseorg's Perl answer.

A proof that the last rule in Anders Kaseorg's answer is not needed

As I said in the comment, the last rule is needed for minimizing the length of the word, but we only need to minimize the number of \$a\$s.

After applying the first 6 rules, there are only 6 possible cases between every two \$a\$s: \$c\$, \$bc\$, \$cbc\$, \$bcbc\$, \$cbcbc\$, \$bcbcbc\$. Since there is no \$ba\$ or \$caca\$, \$aca\$ can only appear at the beginning of the word.

Now let's apply the last rule \$cbcbcab\Rightarrow bcbcbca\$, and see what will happen before and after the \$a\$ in \$cbcbcab\$. Remember that the only way to decrease the number of \$a\$s is applying the first rule: \$aa\Rightarrow \varepsilon\$.

Before the \$a\$

If this is the first \$a\$ in the word, there isn't another \$a\$ for us to apply the first rule.

If it is not the first \$a\$, the only two possible cases between this \$a\$ and the previous \$a\$ are \$cbcbc\$ and \$bcbcbc\$, which will become \$bcbcbc\$ and \$cbcbc\$ respectively. So the number of \$a\$ will not decrease.

After the \$a\$

If this is the last \$a\$ in the word, there isn't another \$a\$ for us to apply the first rule.

If it is not the last \$a\$, there are 3 possible cases between this \$a\$ and the next \$a\$: \$bc\$, \$bcbc\$, \$bcbcbc\$, which will become \$c\$, \$cbc\$, \$cbcbc\$ respectively. In the last two cases, nothing else will change.

But the first case will produce a new \$caca\$: \$cbcbcabca\Rightarrow bcbcbcaca\$. Applying the rule \$caca\Rightarrow acac\$, this substring will become \$bcbcbacac\$, and further become \$bcbcabcac\$ (by applying \$ba\Rightarrow ab\$). Now the \$c\$ at the end of the substring might meet another \$c\$ and get eliminated. But there can't be another \$ca\$ right after the substring, so nothing else will change, and the number of \$a\$ won't decrease.


Wolfram Language (Mathematica), 133 bytes

-10 bytes thanks to @att.

Length[##.E.E&@@#//.{a_ . 3 .b_/;a<6>b:>Mod[a+b+3,6],##&@@(a_ .#2.(o:#...).#2.b_/;a<6>b:>Dot@@Mod[-{-a-#,o,-b-#},6]&@@@1?2@*5?4)}]-2&

Try it online!

First appends two dummy variable E's to the list, then repeatedly replaces:

And then takes the length of the result and minus 2.

Charcoal, 139 bytes

≔⟦E⁶⊕ι⟧η⊞υ⮌ηF⁶⊞ηω≔⁰ζ≔⁰εFA«≔§§ηζ⁺ειδF¬§ηδ«≔⟦⟧κWEΦ§υ±¹№μ⌕ηω⌕ημ«F¬›⌈λ⊕⌊λ≔⮌λλ⊞λ⊖LηF⁻⁶Lλ⊞λ⊖L⊞Oηω§≔η⌕ηωλ⊞κλ»§≔§κ⁰¦¹⊖Lη⊞υκ»≔⁺³⌕§ηδζε≔δζ»I⌕Eυ№ι§ηζ¹

Try it online! Link is to verbose version of code. Explanation: Works by mapping out the grid. The starting point is 0, then the six adjacent hexagons are numbered 1-6, then the 24 hexagons at a distance of 2 are numbered 7-30, then the 90 hexagons at a distance of 3 are numbered 31-120, and so on.

≔⟦E⁶⊕ι⟧η

The edges of the starting hexagon take you to hexagons 1-6.

⊞υ⮌η

Save a copy as the list of hexagons at a distance of 0.

F⁶⊞ηω

Add six placeholders for the hexagons at a distance of 1.

≔⁰ζ≔⁰ε

Start at the origin facing direction 0.

FA«

Loop through the input steps.

≔§§ηζ⁺ειδ

Take a step in the appropriate direction.

F¬§ηδ«

If we have hit a placeholder, then...

≔⟦⟧κ

Start collecting the hexagons at the next distance.

WEΦ§υ±¹№μ⌕ηω⌕ημ«

Until all of the placeholders at the next distance have been mapped, get the edge or edges of the next hexagon that step to a mapped hexagon.

F¬›⌈λ⊕⌊λ≔⮌λλ

Ensure that the edges are listed in clockwise order. This usually needs to be descending, except for the very last hexagon to be mapped, which steps to the first and last hexagon to be mapped in the previous pass. For example, hexagon 10 steps back to hexagons 2 and 1, 20 steps back to hexagon 4 and 30 steps back to hexagons 1 and 6, in that order.

⊞λ⊖Lη

The next edge from this hexagon goes to the same hexagon as the last edge from the previous hexagon.

F⁻⁶Lλ⊞λ⊖L⊞Oηω

The remaining edges go to new unmapped hexagons that are further away.

§≔η⌕ηωλ

Update the map with the edges of this hexagon.

⊞κλ

Collect this hexagon in the list of hexagons at this distance.

»§≔§κ⁰¦¹⊖Lη

Fix the second edge of the first hexagon, which needs to wrap around to the last unmapped hexagon.

⊞υκ

Collect this list of hexagons in the list of hexagons for each distance.

»≔⁺³⌕§ηδζε

Work out the direction we arrived at this hexagon from, and add 3 so that we're facing away from there.

≔δζ

Actually move to this hexagon.

»I⌕Eυ№ι§ηζ¹

Find out which list of hexagons we ended up in, which is our final distance.

TypeScript Types, 568 bytes

//@ts-ignore
type a<N,P=[],A=[[0,1,2,3,4,5],[1,2,3,4,5,0],[2,3,4,5,0,1],[3,4,5,0,1,2],[4,5,0,1,2,3],[5,0,1,2,3,4]],I=A[1],D=A[5]>=N extends[infer X,infer Y,infer Z,...infer R]?Y extends 3?a<[A[3][A[X][Z]],...R],P>:|a<[Y,Z,...R],[...P,X]>|(Y extends 2?a<R,[...P,I[X],4,I[Z]]>:Y extends 4?a<R,[...P,D[X],2,D[Z]]>:never):N extends[infer X,infer Y]?Y extends 3?P:Y extends 2?[...P,I[X],4]|[...P,X,Y]:Y extends 4?[...P,D[X],2]|[...P,X,Y]:[...P,X,Y]:[...P,...N];type b<T,K=keyof T>=T extends T?keyof T extends K?T:never:0;type M<C,P=0>=[C]extends[P]?b<C>["length"]:M<a<C>,C>

Try It Online!

Ungolfed / Explanation

// Iterate over an array, applying equivalence rules where possible
type ApplyRules<
  Next,
  Prev=[],
  // Modulo arithmetic addition table
  Add=[[0,1,2,3,4,5],[1,2,3,4,5,0],[2,3,4,5,0,1],[3,4,5,0,1,2],[4,5,0,1,2,3],[5,0,1,2,3,4]],
  Inc=Add[1],
  Dec=Add[5]
> =
  // Get the first three elements of Next
  Next extends [infer X, infer Y, infer Z, ...infer Rest]
    ? Y extends 3
      // Apply the rule for 3
      ? ApplyRules<[Add[3][Add[X][Z]], ...Rest], Prev>
      // Return a union of:
      :
        // Applying no rule and stepping forward one step
        | ApplyRules<[Y, Z, ...Rest], [...Prev, X]>
        // If Y is 2 or 4, applying the 2/4 rule
        | (
          Y extends 2
            ? ApplyRules<Rest, [...Prev, Inc[X], 4, Inc[Z]]>
            : Y extends 4
              ? ApplyRules<Rest, [...Prev, Dec[X], 2, Dec[Z]]>
              : never
        )
    // Next has fewer than 3 elements. If it has exactly 2, store them in X and Y:
    : Next extends [infer X, infer Y]
      ? Y extends 3
        // If Y is 3, remove both X and Y from the end of the array by returning Prev
        ? Prev
        // If Y is 2 or 4, return the union of the current state and the state after applying the end-2/4 rule
        : Y extends 2
          ? [...Prev, Inc[X], 4] | [...Prev, X, Y]
          : Y extends 4
            ? [...Prev, Dec[X], 2] | [...Prev, X, Y]
            // Otherwise, just return the full array array
            : [...Prev, X, Y]
      // Next has fewer than 2 elements; return the full array
      : [...Prev, ...Next]

// Filter a union of tuples for those with the shortest length
type Shortest<T, K=keyof T> = T extends T ? keyof T extends K ? T : never : 0

// Continually apply rules until nothing changes, then return the shortest length in Cur
type Main<Cur, Prev=0> = [Cur] extends [Prev] ? Shortest<Cur>["length"] : Main<ApplyRules<Cur>, Cur>