g | x | w | all
Bytes Lang Time Link
420Bespoke250126T010338ZJosiah W
042Retina 0.8.2250122T205800ZUnrelate
0436502 Assembly250124T153904Ztestc
009Jelly240517T124510ZJonathan
049Perl 5 Minteger pF240517T214017ZXcali
012Uiua250122T131409Znyxbird
1646250122T075728ZGaner
113AWK240521T194808ZC K
1134Cubical Agda240521T125527ZNaï
050Python 3.8 prerelease240517T075905ZJitse
01105AB1E240517T124333ZKevin Cr
00905AB1E240521T025644Zalephalp
013MATL240518T112436ZLuis Men
100Google Sheets240518T140214Zdoubleun
038JavaScript ES6240517T101405ZArnauld
070Retina 0.8.2240517T155751ZNeil
022Charcoal240517T151233ZNeil
6766Rust240517T092316Zmousetai

Bespoke, 425 420 bytes

-5 bytes by replacing DO P DO P with H SV. (Nice.)

each a/b/c point is on this chained triplet
so each node from it can go within triangle shape
circular motions,they may increase degree
leftward turnings decrease this
we notice while we move a-b-c,then go to start,stopping at degrees=one
i go:if move isnt a-b-c-to-origin,movement is by zero
counting each inputted position by diff is adequate
rounding direction=zero for dividing difference sequence,producing degree D

Keeps track of a running total for the motion between vertices (1 = clockwise, -1 = counterclockwise, 0 = no motion), and divides the result by 3 (rounded towards 0).

The fact that the result needs to be rounded towards 0 caused me some trouble. What I ended up doing was calculating a "sign" value \$s\$ as \$2 \cdot (0 < d) - 1\$, and calculating \$(sd \div 3) \cdot s\$. Bespoke is a stack-based language, so this required some juggling.

Retina 0.8.2, 47 42 bytes

+`(.).?\1
$1
1`ba|cb|ac
-$&
\w(...)*.*
$#1

Try it online!

I knew something like this should be possible and competitive--it just didn't occur to me that it would be in Retina!

Explanation:

+`(.).?\1
$1

Match any character, zero or one other characters, and a copy of that first character. Replace all of that with just the first character, and repeat until nothing changes.

Each iteration essentially "straightens out" every pair of steps that undo each other on top of gradually condensing runs of the same vertex, and repeating this eventually leaves only steps which are all in one direction or the other.

1`ba|cb|ac
-$&

Match the first occurrence of a counterclockwise pair and prepend a minus to it. Due to the previous step, if there's a counterclockwise pair anywhere in the string there has to be one at the very beginning, and this is sufficient to determine the sign of the result (unless it's 0, which can also be output with a leading minus--I assume this is fine).

\w(...)*.*
$#1

Match the first letter (i.e. after the sign if present), and replace that and everything after it with the length of everything strictly after it floor divided by 3. Since every step is in the same direction now, every three steps completes a full revolution, so the number of thirds-of-revolutions is just the number of steps which is 1 less than the number of letters.

Retina 0.8.2, 63 59 bytes

(.)\1*
$1$1
ba|cb|ac|$
--
O`.
+`-\w

^(((-)|.){6})*.*
$3$#1

Try it online!

...I was going to suggest this as a golf to Neil's Retina 0.8.2 solution, given that that is what it started as, until I realized I'd made some or another meaningful change on all but two lines (now one). Funny how that happens.

Explanation:

(.)\1*
$1$1

Replace every run of identical characters (including trivial runs of a single unique character) with precisely two copies of that character. This results in non-overlapping pairs corresponding to every overlapping pair in the original input, with an extra character at the beginning and end.

ba|cb|ac|$
--

Replace every counterclockwise pair, and the empty match at the end of the string, with two minuses. Each counterclockwise pair needs to contribute two minuses since each clockwise pair contributes two letters, and the extra two at the end balance out the leading and trailing unpaired letters.

O`.
+`-\w

Sort that result, so that minuses precede letters, then delete minus-letter pairs iteratively until none remain. Sorting is 1 byte shorter than matching unequal pairs in both orders and repeating \w.

^(((-)|.){6})*.*
$3$#1

Divide by 6 rounded towards 0, and convert to decimal:

Like Neil's paired division and conversion, this doesn't directly "do signed math" so much as it floor divides the nonnegative unary and copies the unary digit onto the beginning of the decimal result to serve as its sign. However, using a number-of-matches substitution $#{...} instead of a length substitution $.{...}, it's a little trickier to actually match specifically a minus sign without consuming it out of the total--I have to clumsily use ((-)|.) to include it, since filtering letters out afterwards is much bulkier (because decimal digits are also matched by \w),

Outside of the sign logic, this matches the entire string as 0 or more greedy repetitions of 6 of anything then 0 or more repetitions of anything at all (anchored to the start of the string to eliminate an extra empty match at the end), and replaces it with (the sign prefixed to) the number of times the 6-wide group matched in decimal.

6502 Assembly, 43 Bytes

Reads the stack as a string, calculates degree into A

  ldx #$00  
  stx $00   ;counter: start at 0 rounds
  inx
  stx $02   ;bit mask for the 'bit' instruction
  pla       ;load first letter from stack
  sta $01   ;previous letter: set to first letter
loop:
  tax       ;copy letter to X
  sec
  sbc $01   ;compare to previous letter by calculating their difference
  beq skip  ;skip repeated letters
  stx $01   ;save current letter
  clc
  bit $02   ;bit test difference with the value in $02 (#$01)
  bne not_c_a ;If the first bit of the difference was set, skip inversion
  eor #$ff  ;for a-c or c-a, invert difference
  adc #$01  ;difference is still either -2 or 2, avoiding having to divide by 3 later
not_c_a :
  adc $00   ;add difference to counter
  sta $00   ;save counter
skip:
  pla       ;load next letter from stack
  tsx       ;did we hit the bottom?
  bne loop  ;continue loop 
  lda $00
  cmp #$80  ;done, divide counter by 4
  ror a 
  cmp #$80
  ror a     ;A contains the result!

Assembled:

A2 00 86 00 E8 86 02 68 85 01 AA 38 E5 01 F0 0F
86 01 18 24 02 D0 04 49 FF 69 01 65 00 85 00 68
BA D0 E7 A5 00 C9 80 6A C9 80 6A

Jelly,  12  9 bytes

IÆTṠS÷3r`

A monadic Link that accepts a list of the walked vertices' ASCII codes/byte values and yields a singleton list containing the net number of rounds.

Try it online! Or see the test-suite.

How?

The ASCII codes of the characters in the input are [97, 98, 99] for "abc", respectively.

The instructions are the substrings of length two of the input.

Each instruction has an expected direction, \$1\$ for clockwise, \$-1\$ for anticlockwise, or \$0\$ for no movement.

For most instructions, the direction is just the forward difference of the instruction's values (\$d\$ below), the only time this is not the case is when the ordinals have an absolute difference of two. The code coerces these by taking the sign of the tangent of the value in radians.

instruction expected direction \$d\$ \$\tan{d}\$ \$v = \operatorname{sgn}(\tan d)\$
ab clockwise (\$1\$) $$98-97=1$$ \$1.557...\$ \$1\$
bc clockwise (\$1\$) $$99-98=1$$ \$1.557...\$ \$1\$
ca clockwise (\$1\$) $$97-99=-2$$ \$2.185...\$ \$1\$
cb anticlockwise (\$-1\$) $$98-99=-1$$ \$-1.557...\$ \$-1\$
ba anticlockwise (\$-1\$) $$97-98=-1$$ \$-1.557...\$ \$-1\$
ac anticlockwise (\$-1\$) $$99-97=2$$ \$-2.185...\$ \$-1\$
aa no movement (\$0\$) $$97-97=0$$ \$0\$ \$0\$
bb no movement (\$0\$) $$98-98=0$$ \$0\$ \$0\$
cc no movement (\$0\$) $$99-99=0$$ \$0\$ \$0\$
IÆTṠS÷3r` - Link: list of character ordinals from [97,98,99] (i.e. "abc")
I         - forward differences
 ÆT       - tan {that} (vectorises)
   Ṡ      - sign {that} (vectorises)
    S     - sum {that}
     ÷3   - divide {that} by three -> FractionalRounds
       r` - inclusive range with self -> [int(FractionalRounds)]

Perl 5 -Minteger -pF, 49 bytes

map$\+=(0,-1,1,-1,1)[(ord)-ord$F[++$i]],@F}{$\/=3

Try it online!

Perl 5 -M5.010 -MList::Util=reduce -Minteger -pF, 53 bytes

reduce{$_=(ord$b)-ord$a;$\+=/2/?$_/-2:$_;$b}@F}{$\/=3

Try it online!

Uiua, 12 bytes

⌊÷3/+⍜+₁◿₃⧈-

Try it!

⌊÷3/+⍜+₁◿₃⧈-
          ⧈-  # deltas
     ⍜+₁◿₃   # mod 3 under +1 (results in -1, 0, or 1)
   /+         # summed
⌊÷3           # floor divided by 3

☾, 16 Chars (46 bytes)

Solution

󷺹󷹝ᴙᙧ1󷸻ꟿⴵ○􋐴○-⨁→⹏3

(Note: ☾ uses a custom font, you can copy-paste the above code into the Web UI)

Diagram

AWK, 113 bytes

{for(j=1;++i<length($0);){x=substr($0,i,2);x~"ab"||x~"bc"||x~"ca"?j++:0;x~"cb"||x~"ac"||x~"ba"?j--:0}}$0=int(j/3)

Try it online!

This will implicitly ignore double characters. I feel like there's a shorter trick somewhere, but my test haven't panned out.

Cubical Agda, 1134 bytes

Uses Agda 2.6.4.3, the cubical library 0.7 and the standard library 2.0.

{-# OPTIONS --cubical #-}
open import Cubical.Data.Int
open import Cubical.Data.Int.Divisibility
open import Cubical.Data.Nat
open import Cubical.Foundations.Everything
open import Data.List
open import Data.List.NonEmpty as L⁺

data 𝟛 : Type where
  a b c : 𝟛

data △ : Type where
  inc : 𝟛 → △
  ab : inc a ≡ inc b
  bc : inc b ≡ inc c
  ca : inc c ≡ inc a

infix 40 _⇒_
_⇒_ : (x y : 𝟛) → inc x ≡ inc y
a ⇒ a = refl
a ⇒ b = ab
a ⇒ c = sym ca
b ⇒ a = sym ab
b ⇒ b = refl
b ⇒ c = bc
c ⇒ a = ca
c ⇒ b = sym bc
c ⇒ c = refl

🏃 : ∀ s → inc (L⁺.head s) ≡ inc (L⁺.last s)
🏃 s with snocView s
... | ys ∷ʳ′ y = go ys
  where
    go : ∀ ys → inc (L⁺.head (ys L⁺.∷ʳ y)) ≡ inc y
    go [] = refl
    go (v ∷ vs) = v ⇒ _ ∙ go vs

🧬 : △ → Type
🧬 (inc _) = ℤ
🧬 (ab i) = sucPathℤ i
🧬 (bc i) = sucPathℤ i
🧬 (ca i) = sucPathℤ i

_/3 : ℤ → ℤ
n@(pos _) /3 = quotRem 3 n (snotz ∘ injPos) .QuotRem.div
n@(negsuc _) /3 = - quotRem 3 (- n) (snotz ∘ injPos) .QuotRem.div

d : List⁺ 𝟛 → ℤ
d s = subst 🧬 (🏃 s) 0 /3

A straightforward winding number computation, slightly complicated by the fact that the string doesn't have to start and end on the same character.

We define the triangle as a higher inductive type generated by three points and three paths, as well as a covering space with fibre that looks like a "triple helix":

triple helix cover of a triangle

To get the degree/winding number of a string of vertices, we transport 0 along a path in the triangle generated by taking the "shortest" path from each vertex to the next, and divide the resulting integer by 3 towards zero.

There is no compiler for Cubical Agda (yet!), but we can check that the test cases compute correctly:

open import Agda.Builtin.Char
open import Agda.Builtin.FromString
open import Agda.Builtin.String
open import Data.Bool
open import Data.List.Effectful as List
open import Data.Maybe
open import Data.Maybe.Effectful as Maybe

parse1 : Char → Maybe 𝟛
parse1 'a' = just a
parse1 'b' = just b
parse1 'c' = just c
parse1 _   = nothing

parse : List Char → Maybe (List⁺ 𝟛)
parse s = mapA parse1 s >>= fromList
  where open List.TraversableA Maybe.applicative

instance
  IsString-vertices : IsString (List⁺ 𝟛)
  IsString-vertices .IsString.Constraint s = T (is-just (parse (primStringToList s)))
  IsString-vertices .IsString.fromString s ⦃ j ⦄ = to-witness-T _ j

_ : d "abca" ≡ 1
_ = refl
_ : d "bcab" ≡ 1
_ = refl
_ : d "cabc" ≡ 1
_ = refl
_ : d "accbbbaa" ≡ -1
_ = refl
_ : d "abcacba" ≡ 0
_ = refl
_ : d "abcabcab" ≡ 2
_ = refl
_ : d "abababababababcababababababab" ≡ 1
_ = refl
_ : d "abcbca" ≡ 1
_ = refl
_ : d "abcabcacb" ≡ 1
_ = refl

Python 3.8 (pre-release), 50 bytes

lambda a,*s:int(sum(-(a+~(a:=b))%3-1for b in s)/3)

Try it online!

-5 bytes thanks to Neil

-5 bytes thanks to Albert.Lang

Port of Arnauld's JavaScript answer.

Alternative solution:

Python 3, 69 bytes

lambda s:int(sum('aabbccabca'.count(x+y)-1for x,y in zip(s,s[1:]))/3)

Try it online!

05AB1E, 13 11 bytes

ÇÔ¥3%É·<O3÷

-2 bytes porting @LuisMendo's MATL answer, so make sure to upvote that answer as well!

Input as a string.
(If we're allowed to take the input as a list of codepoint-integers, the leading Ç can be removed for -1 byte.)

Try it online or verify all test cases.

Explanation:

Ç           # Convert the (implicit) input-string to a list of codepoint-integers
 Ô          # Connected uniquify it to remove non-moving cases
  ¥         # Pop and get the forward-differences (deltas) of this list
   3%       # Modulo-3 each difference
     É      # Modulo-2 each of those
      ·<    # Double and subtract 1 to convert the 0s to -1s (and 1s remain 1s)
        O   # Sum this list of 1s and -1s
         3÷ # Integer-divide it by 3
            # (after which the result is output implicitly)

05AB1E, 9 bytes

Ç¥>3%<O3÷

Attempt This Online!

Ç¥>3%<O3÷
Ç           # Convert each element in the input to its ASCII value
 ¥          # Calculate the deltas
  >         # Increment each element by 1
   3%       # Take each element modulo 3
     <      # Decrement each element by 1
      O     # Sum the elements
       3÷   # Integer divide by 3

MATL, 13 bytes

dXzI\oEqsI/Zo

Try it online! or verify all test cases.

Explanation

d    % Implicit input. Consecutive differences (of ASCII codes)
Xz   % Remove zeros
I\   % Modulo 3 (element-wise)
o    % Modulo 2 (element-wise)
E    % Times 2 (element-wise)
q    % Minus 1 (element-wise)
s    % Sum of all values
I/   % Divide by 3
Zo   % Round towards 0. Implicit display

Google Sheets, 100 bytes

=sort(let(a,mid(A1,row(A:A),2),int(sum(countif(a,{"ab","bc","ca"})-countif(a,{"ba","cb","ac"}))/3)))

Put the string in cell A1 and the formula in B1.

Counts instances of advancing and regressing letter pairs and divides by 3 à la Jonathan Allan's answer.

screenshot

JavaScript (ES6), 38 bytes

Straightforward formula

Expects an array of ASCII codes.

a=>a.map(t=c=>t=-~t-(a+4-[a=c])%3)|t/3

Try it online!


JavaScript (ES6), 39 bytes

Chained modulos on ASCII code concatenation

Expects an array of ASCII codes.

a=>a.map(t=c=>t=~-t+(a+[a=c])%80%3)|t/3

Try it online!

Method

Move types are identified by computing the concatenation of the ASCII codes of the previous and current characters and reducing it modulo \$80\$ and modulo \$3\$. This gives \$0\$ for counter-clockwise, \$1\$ for no move and \$2\$ for clockwise.

previous char. current char. concat. of ASCII codes mod 80 mod 3 move type
a a 9797 37 1 no move
a b 9798 38 2 clockwise
a c 9799 39 0 counter-clockwise
b a 9897 57 0 counter-clockwise
b b 9898 58 1 no move
b c 9899 59 2 clockwise
c a 9997 77 2 clockwise
c b 9998 78 0 counter-clockwise
c c 9999 79 1 no move

Search code

The following code was used to brute-force the modulo values.

let A = [ 97, 98, 99 ];
for(let m0 = 1; m0 < 1000; m0++) {
  for(let m1 = 1; m1 <= m0; m1++) {
    if(A.every(p =>
      A.every(c => {
        let r = p - c ? c == p + 1 || c == 97 && p == 99 ? 1 : -1 : 0;
        let v = (p + [c]) % m0 % m1 - 1;
        return v == r;
      })
    )) {
      console.log(m0, m1);
    }
  }
}

Try it online!

Retina 0.8.2, 70 bytes

(.)\1+
$1
\B
#
+T`#`-`b#a|c#b|a#c
+`\w|#-|-#

..?(.)?
$1
^(-)?.*
$1$.&

Try it online! Link includes test cases. Explanation:

(.)\1+
$1

Deduplicate consecutive letters.

\B
#

Insert #s between pairs of letters.

+T`#`-`b#a|c#b|a#c

Change #s to -s where the letters are anticlockwise.

+`\w|#-|-#

Delete the letters and cancel out adjacent #s and -s.

..?(.)?
$1

Integer divide by 3.

^(-)?.*
$1$.&

Convert to decimal.

Charcoal, 22 bytes

﹪%d∕ΣEΦθκ⊖﹪⊕⁻℅ι℅§θ곦³

Try it online! Link is to verbose version of code. Explanation:

       θ                Input string
      Φ κ               Filter out first letter
     E                  Map over remaining letters
              ι         Current letter
             ℅          Ordinal
            ⁻           Minus
                §θκ     Previous letter
               ℅        Ordinal
           ⊕            Incremented
          ﹪             Modulo
                   ³    Literal integer `3`
         ⊖              Decremented
    Σ                   Take the sum
   ∕                    Divide by
                     ³  Literal integer `3`
﹪                       Format as string using
 %d                     Truncate to integer
                        Implicitly print

17 bytes if (as in all of the examples so far) the input can be assumed to start with a:

FSM⊖﹪⁻℅ιⅈ³→﹪%d∕ⅈ³

Try it online! Link is to verbose version of code. Explanation:

FS

Loop over the characters of the input string.

M⊖﹪⁻℅ιⅈ³→

Subtract the current position from the character's ordinal, reduce modulo 3, subtract 1, and add that to the current position.

﹪%d∕ⅈ³

Truncating divide the current position by 3.

Rust, 67 66 bytes

|a|a[1..].iter().fold((0,a[0]%3),|(b,c),d|(b-(c+!d)%3-1,*d%3)).0/3

Attempt This Online!

Now handles repeats