# Find the Swirling Words!

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:

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
OPERA, STAY, IRIS       > SW,CF,CW  // SWIRLING WORD, CENTRIFUGAL, CLOCKWISE
MINER, TAX, PLUG, META  > SW,CF,CC  // SWIRLING WORD, CENTRIFUGAL, COUNTERCLOCKWISE
AXIOM, AXIS, COOK       > SW,CP,CW  // SWIRLING WORD, CENTRIPETAL, CLOCKWISE
WATCH, YETI, PILL       > SW,CP,CC  // SWIRLING WORD, CENTRIPETAL, COUNTERCLOCKWISE

MORE EXAMPLES OF FALSE TEST CASES (NOT SWIRLING WORDS):
EARTH, GROUP, OUTPUT, WORD, CONNECTION, ODD, MOM, DAD,


### Rules:

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.

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!

• Nice diagrams! :) (And nice challenge, too. ;)) – Martin Ender Oct 6 '16 at 20:11
• Would omitting "Swirling" be a valid output format, since it's implied when the input isn't "not swirling"? – Martin Ender Oct 6 '16 at 20:12
• @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! :)) – Mario Oct 6 '16 at 20:14
• @TimmyD But not all bumpy words are swirling. :) – Martin Ender Oct 6 '16 at 20:17
• @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. – Mario Oct 7 '16 at 16:05

# MATL, 3331 30 bytes

lydhg)dt|dZSXz&=wZSdh?4M1)3M1)


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)

### Explanation

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!

 {o=OrderedQ/@{a=Abs[d=Differences[#&@@@Split@LetterNumber@#]],Reverse@a},d[[1]]>0,Or@@o&&Max[Most[d]Rest@d]<0}&


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!

• You can save 3 bytes by using LetterNumber instead of ToCharacterCode and another 3 bytes by using Most[d] instead of Drop[d,-1]. – 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
(Seq(?,?reverse)indexOf(?sorted),s(0)<s(1))}


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

and

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

Explanation:

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

3Ŀḟ0ṠE
ÑṠḟ0Ṃ
ÑAI
OIḟ0
ÇṠḢ;2Ŀ;Ñ


TryItOnline
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.

### How?

3Ŀḟ0ṠE      - Link 1, isSpinning: s
ḟ0        - filter out zeros
Ṡ       - sign
E      - all equal?

ÑṠḟ0Ṃ       - Link 2, centrifugal(-1), circular(0) or centripetal(1): 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
A          - absolute
I         - differences

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

Ṡ          - sign
Ḣ         - head (clockwise / anticlockwise: 1 / -1)
;  ;     - concatenate

• 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. – Erik the Outgolfer Oct 7 '16 at 14:46
• 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! – Jonathan Allan Oct 7 '16 at 16:11
• 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. – Erik the Outgolfer Oct 7 '16 at 16:15
• 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. – Jonathan Allan Oct 7 '16 at 16:18
• I just asked for you to double-check. And, I understood that you meant min([])==0, but I thought this is still golfable. – Erik the Outgolfer Oct 7 '16 at 16:28

# CJam, 39 bytes

r{2ew::-V}:D~-_:g_0=\D#)!@:zD-:g_0=\(-!


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)


Explanation:

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(;++$i<strlen($z=preg_replace("#(.)\\1#","$1",$argv[1]));){$t[]=$z[$i-1]<=>$z[$i]?:0;$o[]=$z[0]<=>$z[$i];$i<2?:$k[]=$z[$i-2]<=>$z[$i];}$s=preg_match("#^1?(-11)*(-1)?$#",join($t))?($t[0]!=1?1:2):0;$s+=2*preg_match($r="#^(-1|0)?([01](-1|0))*[01]?$#",join($o));$s*=preg_match($r,join($k));count_chars($z,3)[2]?:$s=0;echo$s;  for a more pretty Output echo["n","+P","-P","+F","-F"][$s];

Expanded version

for(;++$i<strlen($z=preg_replace("#(.)\\1#","$1",$argv[1]));){
$t[]=$z[$i-1]<=>$z[$i]?:0;$o[]=$z[0]<=>$z[$i];$i<2?:$k[]=$z[$i-2]<=>$z[$i]; }$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');
$w=$_GET["w"]??"OOPERRA";
$w=strtoupper($w);
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">'

.'<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>';$r[$c]=15+$x*15;
}
for($i=0;++$i<strlen($w);){ 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> • 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! – Mario Oct 10 '16 at 7:23 • @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 – Jörg Hülsermann Oct 10 '16 at 10:34

z f=tail>>=zipWith f
(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)