Don't ask me how or why but while coding on a project I noticed that the characters of a couple of words had a particular pattern referred to the alphabet, I connected each character of the words with the pencil on the alphabet and I obtained two spirals, then I noticed that the first spiral was clockwise and the other was counterclockwise, and other characteristics... so I named them Swirling Words!

A Swirling Word can be:

  1. clockwise or counterclockwise
  2. centripetal or centrifugal

Here there are some examples of Swirling Words:

Swirling Words Diagram

Task 1:

Write a full program or function that will take a word from standard input and will output if is a Swirling Word and it's characteristics, in a readable format, extended text, 3 characters, flags, etc.

Test cases and example outputs for different words (but you can decide how to represent the results):

EARTH, GROUP            > NO        // NOT A SWIRLING WORD



  1. The connection between the first two characters must be up (like in the graphics), all the even connection must be down, all the odd connections must be up.
  2. You can ignore upper/lowercase or consider/convert all to upper case or all to lower case.
  3. The input words are only characters in the alphabet range of A-Z, no spaces, no punctuation, etc.
  4. If a word has double characters, like "GROOVE", you must collapse the doubles to one character: "GROOVE" > "GROVE".
  5. The input words will contain at least 3 distinct characters. Words like "MOM", "DAD", "LOL" are not valid words.
  6. It's possible to pass multiple times in the same character, like "IRIS".
  7. Shortest code wins.

Task 2:

To obtain more reputation, find the longest Swirling Words, and it's characteristics, that you can find in the english dictionary, following the above rules. You can take for example as reference the complete list of the english words here.

Happy coding!

  • 15
    \$\begingroup\$ Nice diagrams! :) (And nice challenge, too. ;)) \$\endgroup\$ – Martin Ender Oct 6 '16 at 20:11
  • \$\begingroup\$ Would omitting "Swirling" be a valid output format, since it's implied when the input isn't "not swirling"? \$\endgroup\$ – Martin Ender Oct 6 '16 at 20:12
  • \$\begingroup\$ @MartinEnder Yes, since it's understandable when it's swirling or not, can be empty for "not" and "1" for "yes", etc. (Glad you liked the diagrams and challenge! :)) \$\endgroup\$ – Mario Oct 6 '16 at 20:14
  • 1
    \$\begingroup\$ @TimmyD But not all bumpy words are swirling. :) \$\endgroup\$ – Martin Ender Oct 6 '16 at 20:17
  • 2
    \$\begingroup\$ @Lynn Thanks for the appreciation and suggestions, I'll try to improve for the future. I added the "remove double" rule to anticipate people asking me things like "what do we do when there are doubles?" > you can consider doubles as 1 single character because from "L" going to "L" is zero distance :) Is not that I wanted to add tricky difficulties for it's own sake. \$\endgroup\$ – Mario Oct 7 '16 at 16:05

MATL, 33 31 30 bytes


Input is in uppercase letters (or in lowercase letters, but not mixed).

Output is:

  • If the word is non-swirling: no output is produced
  • If it is swirling: two numbers are produced in different lines:
    • First number 1/ -1 indicates centrifugal / centripetal.
    • Second number 1/ `-1' indicates clockwise / counterclockwise.

Try it online! Or verify all test cases (code modified to take all inputs and to produce the two output numbers on the same line)


Let's take input 'OPERAA' as an example.

The first part of the code removes double letters:

l     % Push 1
      %   STACK: 1
y     % Take input implicitly from below, and duplicate
      %   STACK: 'OPERAA', 1, 'OPERAA'
d     % Convert to code points and compute differences
      %   STACK: 'OPERAA', 1, [1 -11  13 -17 0]
h     % Concatenate horizontally
      %   STACK: 'OPERAA', [1 1 -11  13 -17 0]
g     % Convert to logical
      %   STACK: 'OPERAA', [true true true true true false]
)     % Index
      %   STACK: 'OPERA'

We now check if the distances between letters are non-decreasing (necessary condition for the word to be swirling):

d     % Convert to code points and compute differences
      %   STACK: [1 -11  13 -17]
t|    % Duplicate and take absolute value
      %   STACK: [1 -11  13 -17], [1 11  13 17]
d     % Differences
      %   STACK: [1 -11  13 -17], [10 2 4]
ZS    % Signum
      %   STACK: [1 -11  13 -17], [1 1 1]
Xz    % Remove zeros (gives a vertical vector). Needed for words like 'IRIS',
      % where some consecutive distances are equal
      %   STACK: [1 -11  13 -17], [1; 1; 1]
&=    % All pairwise equality comparisons. Gives a matrix. If all the signs 
      % were equal the matrix will contain all ones
      %   STACK: [1 -11  13 -17], [1 1 1; 1 1 1; 1 1 1]

We then check if the letters go back and forth (this is the other condition for the word to be swirling):

w     % Swap
      %   STACK: [1 1 1; 1 1 1; 1 1 1], [1 -11  13 -17]
ZS    % Signum
      %   STACK: [1 1 1; 1 1 1; 1 1 1], [1 -1 1 -1]
d     % Differences
      %   STACK: [1 1 1; 1 1 1; 1 1 1], [-2 2 -2]

Lastly, we check if the two conditions hold, and in that case produce the output:

h     % Concatenate horizontally
      %   STACK: [1 1 1 1 1 1 1 1 1 -2 2 -2]
?     % If all elements are nonzero
  4M  %   Push first signum array without zeros, from the automatic clipboard
      %     STACK: [1; 1; 1]
  1)  %   Get first element (tells if first difference was positive or negative)
      %     STACK: 1
  3M  %   Push second signum array, from the automatic clipboard
      %     STACK: 1, [1 -1 1 -1]
  1)  %   Get first element (tells if first movement was right or left)
      %     STACK: 1, 1
      %   Implicitly end if
      % Implicitly display

Mathematica, 117 111 bytes

Thanks to JHM for saving 6 bytes, and making it case-insensitive to boot!


Unnamed function that takes a string and returns a nested list of booleans in the form {{B1,B2},B3,B4}. B4 records whether the word is swirling (and if it's not, the rest of the output is garbage). If the word is swirling, then B1 records whether the word is centrifugal, B2 records whether the word is centripetal, and B3 records whether the word is clockwise (True) or counterclockwise (False).

Here's a longer version that post-processes (first line) the above function (spaced over the 2nd-5th lines) to make it identical to the OP: NO if the word isn't swirling, and the appropriate choice of {SW,CF,CW} , {SW,CF,CC} , {SW,CP,CW}, or {SW,CP,CC} if the word is swirling:

If[#3, {SW, If[#[[1]], CF, CP], If[#2, CW, CC]}, NO] & @@
  {o = OrderedQ /@
    {a = Abs[d = Differences[# & @@@ Split@LetterNumber@#]], Reverse@a},
  d[[1]] > 0,
  Or @@ o && Max[Most[d] Rest@d] < 0} &

The explanation is the same as in Martin Ender's CJam answer, with one additional note: the list of consecutive differences must alternate in sign for the word to be swirling, and that can be detected by making sure all products of pairs of consecutive differences are negative (that's what Max[Most[d]Rest@d]<0 does).

Running the function on all 40,000+ words of Mathematica's WordList[], we find the following 8-letter swirling words, which are the longest of their respective swirling types:

operetta    {SW, CF, CW}
opposite    {SW, CF, CW}
stowaway    {SW, CF, CW}
assassin    {SW, CP, CW}
assessor    {SW, CP, CW}
baccarat    {SW, CF, CC}
keenness    {SW, CF, CC}
positive    {SW, CF, CC}

(Brownie points to positive for having no double letters, and fewer repeated letters than stowaway.)

But the absolute champion is the 9-letter counterclockwise-swirling centripetal word vassalage!

  • 1
    \$\begingroup\$ You can save 3 bytes by using LetterNumber instead of ToCharacterCode and another 3 bytes by using Most[d] instead of Drop[d,-1]. \$\endgroup\$ – JungHwan Min Oct 7 '16 at 23:20

Scala, 110 bytes

def/(s:String)={val ? =s.sliding(2).map(t=>(t(0)-t(1)).abs).toSeq

Returns a tuple (a,b) with

  • a == 1 if s is centripetal
  • a == 0 if s is centrifugal
  • a == -1 if s is not swirling


  • b == true if s is clockwise
  • b == false if s is counterclockwise
  • b can be true or false if s is not swirling


def/(s:String)={      //define a method called / with a String argument
  val ? =s            //define ? as...
    .sliding(2)       //an iterator for each two consecutive elements
    .map(t=>          //foreach 2 chars
      (t(0)-t(1)).abs //get the absolute value of their difference
    .toSeq            //and convert the iterator to a Seq, because iterator doesn't have reverse and sorted methods
  (                   //return a tuple of
    Seq(?,?reverse)     //a Seq of ? and reversed ?
    .indexOf(?sorted)   //and check which of them is sorted ?
  ,                   //and
   s(0)< s(1)          //the difference bewteen the first two elements of the string.

Jelly, 30 bytes


Or see the test cases (with a slight change as the very last Ñ would point to the new main link)

(My lack of chaining skill is probably costing a few bytes here)
All upper or all lower.
Returns a flag list [D, F, S]:
S: spinning = 1 / not spinning = 0
F: centrifugal = 1 (circular = 0) centripetal = -1
D: clockwise = 1 / anticlockwise = -1
- if S = 0 the other flags are still evaluated even though they carry no useful information.


3Ŀḟ0ṠE      - Link 1, isSpinning: s
3Ŀ          - call link 3 as a monad with s
  ḟ0        - filter out zeros
    Ṡ       - sign
     E      - all equal?

ÑṠḟ0Ṃ       - Link 2, centrifugal(-1), circular(0) or centripetal(1): s
Ñ           - call next link (3) as a monad with s
 Ṡ          - sign (+1 for positive changes, -1 for negative changes, 0 for no change)
  ḟ0        - filter out zeros (ignore these for cases like "IRIS")
    Ṃ       - minimum (will be the only value for spinning words)
            -    (circular words like "DAD", now excluded, yield min([])=0)

ÑAI         - Link 3, absolute change of moves over alphabet: s
Ñ           - call next link (4) as a monad with s
 A          - absolute
  I         - differences

OIḟ0        - Link 4, non-zero moves over alphabet: s
O           - ordinal cast
 I          - differences
  ḟ0        - filter out zeros

ÇṠḢ;2Ŀ;Ñ    - Main link: s
Ç           - call last link (4) as a monad with s
 Ṡ          - sign
  Ḣ         - head (clockwise / anticlockwise: 1 / -1)
   ;  ;     - concatenate
    2Ŀ      - call link (2) as a monad with s
       Ñ    - call next link (1) as a monad with s
  • 1
    \$\begingroup\$ I think you are experiencing what I call the "newbie syndrome" here. I feel exactly the same as you. Maybe Dennis could be of some help here. But, I +1'd, just because I saw it's possible in Jelly. Also, you can remove the circular case; it no longer exists. \$\endgroup\$ – Erik the Outgolfer Oct 7 '16 at 14:46
  • \$\begingroup\$ Thanks for the poke about circular words - as it turns out the 6 bytes to cater for them were actually unnecessary since the minimum of an empty list is 0 so this still works for those too! \$\endgroup\$ – Jonathan Allan Oct 7 '16 at 16:11
  • \$\begingroup\$ So, it must work for them? I see you still have the circular(0) inside your explanation, maybe it's time to remove it as well. \$\endgroup\$ – Erik the Outgolfer Oct 7 '16 at 16:15
  • \$\begingroup\$ It is not required, no - but this code still does after removing what I had used to explicitly make it do so, due to the fact that min([])=0 jelly.tryitonline.net/#code=W13huYI&input= - Note that since circular words are now never expected input, there is no problem in catering for them. \$\endgroup\$ – Jonathan Allan Oct 7 '16 at 16:18
  • \$\begingroup\$ I just asked for you to double-check. And, I understood that you meant min([])==0, but I thought this is still golfable. \$\endgroup\$ – Erik the Outgolfer Oct 7 '16 at 16:28

CJam, 39 bytes


Try it online!

The input can be uppercase or lowercase, but not mixed.

The program inadvertently points out words which are not necessarily centrifugal or centripetal, but otherwise meet the requirements of being spiral. These are described as "circular" in the chart below.

To interpret the output use this chart:

SPIRAL (output contains four 1s)
-11-11 : Clockwise Centrifugal
-1111  : Clockwise Centripetal
11-11  : Counter-clockwise Centrifugal
1111   : Counter-clockwise Centripetal

CIRCULAR (output contains two 1s)
-11    : Clockwise Circular
11     : Counter-clockwise Circular

NONSPIRAL (output contains a 0)


The program actually evaluates whether the sequence of non-zero differences between characters starts off positive or negative, if it alternates in sign, if the magnitudes start off increasing or decreasing, and if it continues to do so. If the magnitudes don't increase or decrease then the program breaks by operating on an empty array. The major steps are shown below (this code will also show the progress of the stack):

r{2ew::-V}:D~-   e# take difference of overlapping pairs, removing 0s handles duplicates
               ede# store difference function plus 0 as D, it's multipurpose
_:g_0=\          e# compute signs differences, keep first to show starting direction
               ede# -1 = CLOCKWISE, 1 = COUNTERCLOCKWISE
D#)!@            e# difference of signs includes 0 if not alternating, keep in stack
               ede# 1 = ALTERNATING, 0 = NOT ALTERNATING
:zD-:g           e# signs of difference of absolute values, ignoring 0s (fixed magnitude)
_0=\             e# keep first sign in stack to indicate how the sequence starts
               ede# -1 = INCREASING, 1 = DECREASING
(-!              e# remove first item from entire list and see if nothing remains
               ede# 1 = EMPTY(MONOTONE), 0 = NONEMPTY

PHP, 322 Bytes


for a more pretty Output echo["n","+P","-P","+F","-F"][$s];

Expanded version

$s=preg_match("#^1?(-11)*(-1)?$#",join($t))?($t[0]!=1?1:2):0; #Clockwise direction or not
$s+=2*preg_match($r="#^(-1|0)?([01](-1|0))*[01]?$#",join($o)); # True centrifugal
$s*=preg_match($r,join($k)); #true or false second test for not
count_chars($z,3)[2]?:$s=0; # word must have >2 different characters
echo$s;# short output
echo["n","+P","-P","+F","-F"][$s]; #long output alternative

Task 2 second value without short doubles rule

4 -F killingness 11 Bytes positivize 10 Bytes

3 +F oppositive 10 Bytes logogogue 9 Bytes

2 -P vassalage 9 Bytes sarcocol,sasarara 8 Bytes

1 +P assession 9 Bytes apanage, aramaic, argonon, auction, avision, awarded, crenele, exesion, exition, eyewink 7 Bytes

Visualize a word

header('Content-Type: image/svg+xml; charset=UTF-8');
echo '<?xml version="1.0" encoding="UTF-8"?>'
.'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'

.'<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 -100 420 400">'
.'<title>Swirl Word</title><desc>Viualize a Word</desc>';
echo '<text x="210" y="-50" text-anchor="middle" font-family="arial">'.$w.'</text>';

foreach(range("A","Z")as $x=>$c){
    echo '<text x="'.(15+$x*15).'" y="110" text-anchor="middle" font-family="arial">'.$c.'</text>';
    echo '<path d="M '.($r[$w[$i-1]]).',105 A '.($radius=abs($r[$w[$i]]-$r[$w[$i-1]])/2).' '.($radius).' 0 0 0 '.($r[$w[$i]]).',105" style="stroke:gold; stroke-width:1px;fill:none;" />';
echo '</svg>';  

in the snippet is the result of the SVG I have create

<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 -100 420 400"><title>Swirl Word</title><desc>Viualize a Word</desc><text x="210" y="-50"  text-anchor="middle" font-family="arial">KILLINGNESS</text><text x="15" y="110" text-anchor="middle" font-family="arial">A</text><text x="30" y="110" text-anchor="middle" font-family="arial">B</text><text x="45" y="110" text-anchor="middle" font-family="arial">C</text><text x="60" y="110" text-anchor="middle" font-family="arial">D</text><text x="75" y="110" text-anchor="middle" font-family="arial">E</text><text x="90" y="110" text-anchor="middle" font-family="arial">F</text><text x="105" y="110" text-anchor="middle" font-family="arial">G</text><text x="120" y="110" text-anchor="middle" font-family="arial">H</text><text x="135" y="110" text-anchor="middle" font-family="arial">I</text><text x="150" y="110" text-anchor="middle" font-family="arial">J</text><text x="165" y="110" text-anchor="middle" font-family="arial">K</text><text x="180" y="110" text-anchor="middle" font-family="arial">L</text><text x="195" y="110" text-anchor="middle" font-family="arial">M</text><text x="210" y="110" text-anchor="middle" font-family="arial">N</text><text x="225" y="110" text-anchor="middle" font-family="arial">O</text><text x="240" y="110" text-anchor="middle" font-family="arial">P</text><text x="255" y="110" text-anchor="middle" font-family="arial">Q</text><text x="270" y="110" text-anchor="middle" font-family="arial">R</text><text x="285" y="110" text-anchor="middle" font-family="arial">S</text><text x="300" y="110" text-anchor="middle" font-family="arial">T</text><text x="315" y="110" text-anchor="middle" font-family="arial">U</text><text x="330" y="110" text-anchor="middle" font-family="arial">V</text><text x="345" y="110" text-anchor="middle" font-family="arial">W</text><text x="360" y="110" text-anchor="middle" font-family="arial">X</text><text x="375" y="110" text-anchor="middle" font-family="arial">Y</text><text x="390" y="110" text-anchor="middle" font-family="arial">Z</text><path d="M 165,105 A 15 15 0 0 0 135,105" style="stroke:gold; stroke-width:1px;fill:none;" /><path d="M 135,105 A 22.5 22.5 0 0 0 180,105" style="stroke:gold; stroke-width:1px;fill:none;" /><path d="M 180,105 A 0 0 0 0 0 180,105" style="stroke:gold; stroke-width:1px;fill:none;" /><path d="M 180,105 A 22.5 22.5 0 0 0 135,105" style="stroke:gold; stroke-width:1px;fill:none;" /><path d="M 135,105 A 37.5 37.5 0 0 0 210,105" style="stroke:gold; stroke-width:1px;fill:none;" /><path d="M 210,105 A 52.5 52.5 0 0 0 105,105" style="stroke:gold; stroke-width:1px;fill:none;" /><path d="M 105,105 A 52.5 52.5 0 0 0 210,105" style="stroke:gold; stroke-width:1px;fill:none;" /><path d="M 210,105 A 67.5 67.5 0 0 0 75,105" style="stroke:gold; stroke-width:1px;fill:none;" /><path d="M 75,105 A 105 105 0 0 0 285,105" style="stroke:gold; stroke-width:1px;fill:none;" /><path d="M 285,105 A 0 0 0 0 0 285,105" style="stroke:gold; stroke-width:1px;fill:none;" /></svg>

  • \$\begingroup\$ Great Swirling Words Viewer! :) Maybe you can try to connect the characters with half ellipses instead of half circles. It will be more compact and look more "dinamic". But looks great anyway! \$\endgroup\$ – Mario Oct 10 '16 at 7:23
  • \$\begingroup\$ @Mario it only needs a factor '.(.8*$radius).' instead of '.($radius).' and if you replace ($radius).' 0 0 0 with ($radius).' 0 0 '.(($w[$i-1]<$w[$i]?1:0)^(($i-1)%2)).' the program has not a fix direction \$\endgroup\$ – Jörg Hülsermann Oct 10 '16 at 10:34

Haskell, 148 bytes

z f=tail>>=zipWith f
g c=and.z c.filter(/=0).map abs.z(-).map fromEnum
(a:b:r)%c|a==b=(b:r)%c|1<3=c a b
f s|a<-[g(>=)s,g(<=)s]=or a:a++[s%(<),s%(>)]

Try it on Ideone.

Input must be either al lower or all upper-case.
Output is a list of five booleans: [SW?, CF?, CP?, CW?, CC?].

f "positive" -> [True,True,False,False,True]

This turned out longer than expected, especially handing the collapsing of repeated characters takes about 40 bytes.

At first I compared just the first two characters to yield CW or CC before noticing that testcases like bba or bbc are valid too and defeat this approach.


Python, 152 bytes:

lambda C:[C[-1]in max(C)+min(C),C[1]>C[0]]*all([[i>g,i<g][[h%2>0,h%2<1][C[1]>C[0]]]for i,g,h in filter(lambda i:i[0]!=i[1],zip(C,C[1:],range(len(C))))])

An anonymous lambda function. Call as print(<Function Name>('<String>')).

Takes input as all lowercase or uppercase, but not mixed case.

Outputs an array containing nothing ([]) if the word is not swirly, or an array in the following format otherwise:

  • 1st element is True/False for Centrifugal/Centripetal.
  • 2nd element is True/False for Clockwise/Counterclockwise.

Try It Online! (Ideone)


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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