Find the Infinity Words!

36

4

(Note: This is a spin-off of my previous challenge Find the Swirling Words!)

Definition of Infinity Word:

  1. If you connect with curves all the characters of an Infinity Word on the alphabet (A-Z) you obtain the infinity symbol ∞ like in the diagrams below.
  2. All the even connection must be down, all the odd connections must be up.
  3. You can ignore upper/lowercase or consider/convert all to upper case or all to lower case.
  4. The input words are only characters in the alphabet range of A-Z, no spaces, no punctuation, or symbols.
  5. Each word must be exactly 5 characters. Words > 5 or < 5 are not valid.
  6. If a word has double consecutive characters, the word is not valid, like "FLOOD" or "QUEEN".
  7. All the Infinity Words start and end with the same character.

Here there are some examples:

Infinity Words

Task:

Write a full program or function that will take a word from standard input and will output if is an Infinity Word or not. The output can be true/false, 1/0, 1/Null, etc.

Test cases:

Infinity Words:
ALPHA, EAGLE, HARSH, NINON, PINUP, RULER, THEFT, WIDOW

NOT Infinity Words:
CUBIC, ERASE, FLUFF, LABEL, MODEM, RADAR, RIVER, SWISS, TRUST, 
KNEES, QUEEN, GROOVE, ONLY, CHARACTER, OFF, IT, ORTHO

Rules:

  1. Shortest code wins.

Optional Task:

Find, as a list, as many Infinity Words as you can in an English dictionary. You can take for example as reference the complete list of English words here.

Mario

Posted 2016-10-17T13:24:11.957

Reputation: 3 043

Can we assume the input is always of length 5? You have defined rule 5: "Each word must be exactly 5 characters. Words > 5 or < 5 are not valid.", but no NOT Infinity Words containing less or more than 5 characters. – Kevin Cruijssen – 2016-10-17T13:48:33.757

4Pretty funny that ALPHA makes that pattern – Fatalize – 2016-10-17T13:49:24.470

@KevinCruijssen You must check that the word respect the definition, I updated the false cases. – Mario – 2016-10-17T13:56:24.643

Also, one more thing I noticed. At your picture you have ORTHO NO, but at the Test Cases, ORTHO is at the truthy cases instead of falsey. – Kevin Cruijssen – 2016-10-17T13:59:07.090

@KevinCruijssen My mistake! :) I updated it – Mario – 2016-10-17T14:00:12.570

IMO, a word such as AAAAA should be considered as a valid singularity, which is currently not the case because of rule #6. Does that make sense? – Arnauld – 2016-10-17T16:06:20.507

1@Arnauld five "A"'s connects to themselves (or doesen't move at all) creating a single point, it doesen't draw the infinity symbol, so I don't think it's a positive case. – Mario – 2016-10-17T16:25:22.503

@Mario FYI there is no test case that fails if I perform only my rotation test without checking last letter equals first. How about adding "RULES"? – Jonathan Allan – 2016-10-17T17:30:47.780

@JonathanAllan I'm not sure I understood well your last comment, but the word "RULES" doesen't draw a closed infinity symbol, so I think it should be considered as false case. The path should be closed and with no ending anywhere. – Mario – 2016-10-18T07:03:20.320

Yes it is Falsely, but if one performs the test I described in my answer but without checking first==last all test cases work. – Jonathan Allan – 2016-10-18T07:15:09.660

3

I have decided to tackle the Optional Task: "Find, as a list, as many Infinity Words as you can in an English dictionary..." I used this source and Kevin Cruijssen's answer, to produce this list of 278 Infinity Words.

– Thomas Quinn Kelly – 2016-10-18T22:16:07.643

Answers

19

Jelly, 43 41 40 25 24 23 22 21 14 13 bytes

-7 bytes thanks to fireflame241 (0ị=1ị$->=ṚḢ and use of IIA⁼2,2 to test for the 4 rotations)

-1 Thanks to Kevin Cruijssen (use of previously unavailable nilad Ø2 which yields [2,2])

=ṚḢȧOIṠIIA⁼Ø2

TryItOnline
Or all test cases (plus "RULES")

How?

An infinity word has:

  1. the same first and last letter;
  2. length 5;
  3. no equal letters next to each other;
  4. sum of its four alphabet deltas equal to zero;
  5. sum of its four alphabet deltas signs equal to zero;
  6. two positive alphabet deltas or two negative alphabet deltas in a row.

All but (1) and (equivalently) (4) may be boiled down to a condition that the alphabet delta signs are some rotation of [1,1,-1,-1] (where the sign of 0 is 0)

fireflame241 noted that this is then equivalent to the deltas of the deltas of the alphabet delta signs being in [[2,2],[2,-2],[-2,2],[-2,-2]] which may be tested by the absolute values being equal to [2,2]!

How?

=ṚḢȧOIṠIIA⁼Ø2 - Main link: word
 Ṛ            - reverse word
=             - equals? (vectorises)
  Ḣ           - head (is the first character equal to the last?)
   ȧ          - and
    O         - cast word to ordinals
     I        - increments - the alphabet deltas (or just [] if 1st != last)
      Ṡ       - sign (vectorises)
       I      - increments - deltas of those signs
        I     - increments - deltas of those
         A    - absolute value (vectorises)
           Ø2 - literal [2,2]
          ⁼   - equals? (non-vectorising version)

Jonathan Allan

Posted 2016-10-17T13:24:11.957

Reputation: 67 804

How does this work? – Oliver Ni – 2016-10-17T16:15:27.013

incoming explanation. – Jonathan Allan – 2016-10-17T16:15:57.800

I'm always wondering: what do you think is the most valuable thing you get out of being good at / using Jelly :)? For what would you advise people to give Jelly a go; any other reason than fun? – PascalVKooten – 2016-10-18T12:11:02.607

2@PascalvKooten It is mostly for the fun, and to be competitive at code golf - I'm fairly new to both code golf and Jelly, so putting together a Jelly program is like a little puzzle almost every time; I find it satisfying. If one wishes to get something tangible out of this game one should use it to hone one's skills in a language that is more commonly used in the real world though, or, of course, create a golfing language of one's own! – Jonathan Allan – 2016-10-18T13:01:30.043

@JonathanAllan how do you start with something unintelligibly as this ?XD – lois6b – 2016-10-18T15:20:38.360

1

@lois6b :). You start with the tutorial, and then use the pages with Atom definitions, Quicks definitions, and browse the source code.

– Jonathan Allan – 2016-10-18T15:55:04.393

114 bytes The main golf here uses II to check for equality to a rotation of 1,1,-1,-1. – fireflame241 – 2017-08-30T23:02:14.720

@fireflame241 very nice golfing! I shall update in a few... – Jonathan Allan – 2017-08-30T23:08:04.487

Probably wasn't available yet when you posted this answer, but 2,2 can be Ø2 now. – Kevin Cruijssen – 2018-11-20T16:49:25.910

@KevinCruijssen - indeed it was not; updated, thanks! – Jonathan Allan – 2018-11-20T17:10:27.660

11

Java 8, 231 193 185 122 103 78 bytes

s->s.length==5&&(s[1]-s[0])*(s[3]-s[2])<0&(s[2]-s[1])*(s[4]-s[3])<0&s[4]==s[0]

Try it here.

-38 bytes thanks to @dpa97 for reminding me to use char[] instead of String.
-63 bytes thanks to @KarlNapf's derived formula.
-25 bytes by converting it from Java 7 to Java 8 (and now returning a boolean instead of integer).

193 bytes answer:

int c(char[]s){if(s.length!=5)return 0;int a=s[0],b=s[1],c=s[2],d=s[3],e=s[4],z=b-a,y=c-b,x=d-c,w=e-d;return e!=a?0:(z>0&y>0&x<0&w<0)|(z<0&y>0&x>0&w<0)|(z>0&y<0&x<0&w>0)|(z<0&y<0&x>0&w>0)?1:0;}

Explanation:

  • If the length of the string isn't 5, we return false
  • If the first character doesn't equal the last character, we return false
  • Then we check the four valid cases one by one (let's indicate the five characters as 1 through 5), and return true if it complies to any of them (and false otherwise):
    1. If the five characters are distributed like: 1<2<3>4>5 (i.e. ALPHA)
    2. If the five characters are distributed like: 1>2<3<4>5 (i.e. EAGLE, HARSH, NINON, PINUP)
    3. If the five characters are distributed like: 1<2>3>4<5 (i.e. RULER)
    4. If the five characters are distributed like: 1>2>3<4<5 (i.e. THEFT, WIDOW)

These four rules can be simplified to 1*3<0 and 2*4<0 (thanks to @KarlNapf's Python 2 answer).

Kevin Cruijssen

Posted 2016-10-17T13:24:11.957

Reputation: 67 575

2+1 to compensate the unexplained downvote ... As far as I can tell, this is a perfectly functional solution. – Arnauld – 2016-10-17T15:32:39.987

1I got it down to 215 converting s to a char[] char[]c=s.toCharArray();int z=c[1]-c[0],y=c[2]-c[1],... – dpa97 – 2016-10-17T16:08:36.350

@dpa97 Thanks for the reminder to use char[] as input instead of String. -38 bytes thanks to you. – Kevin Cruijssen – 2016-10-17T17:18:04.990

1Your booleans can be optimized: z,x and w,y must have an alternating sign, so it suffices to check z*x<0 and w*y<0 – Karl Napf – 2016-10-17T21:27:44.610

@KarlNapf Ah, I misinterpreted your comment a few hours ago. I've implemented your derived formula for a whopping -63 bytes. :) Thanks. – Kevin Cruijssen – 2016-10-18T12:24:21.073

"int" + "?1:0" is 7 characters long. "boolean" is 7 characters long. I don't see why you return an "int" value. In Java 8, it'd be shorter to use the boolean return type. Also, remove the "length is 5" condition: the OP says they're not "valid", not that you shouldn't return something falsy. Also given that no truthy/falsy, an exception is considered "false", just use that to your advantage. – Olivier Grégoire – 2016-10-18T15:04:25.053

4

JavaScript (ES6), 91 89 87 bytes

Saved 2 bytes thanks to Ismael Miguel

s=>(k=0,[...s].reduce((p,c,i)=>(k+=p>c?1<<i:0/(p<c),c)),k?!(k%3)&&!s[5]&&s[0]==s[4]:!1)

How it works

We build a 4-bit bitmask k representing the 4 transitions between the 5 characters of the string:

k += p > c ? 1<<i : 0 / (p < c)
  • if the previous character is higher than the next one, the bit is set
  • if the previous character is lower then the next one, the bit is not set
  • if the previous character is identical to the next one, the whole bitmask is forced to NaN so that the word is rejected (to comply with rule #6)

The valid bitmasks are the ones that have exactly two consecutive 1 transitions (the first and the last bits being considered as consecutive as well):

Binary | Decimal
-------+--------
0011   | 3
0110   | 6
1100   | 12
1001   | 9

In other words, these are the combinations which are:

  • k? : greater than 0
  • !(k%3) : congruent to 0 modulo 3
  • lower than 15

The other conditions are:

  • !s[5] : there's no more than 5 characters
  • s[0]==s[4] : the 1st and the 5th characters are identical

NB: We don't explicitly check k != 15 because any word following such a pattern will be rejected by this last condition.

Test cases

let f =

s=>(k=0,[...s].reduce((p,c,i)=>(k+=p>c?1<<i:0/(p<c),c)),k?!(k%3)&&!s[5]&&s[0]==s[4]:!1)

console.log("Testing truthy words...");
console.log(f("ALPHA"));
console.log(f("EAGLE"));
console.log(f("HARSH"));
console.log(f("NINON"));
console.log(f("PINUP"));
console.log(f("RULER"));
console.log(f("THEFT"));
console.log(f("WIDOW"));

console.log("Testing falsy words...");
console.log(f("CUBIC"));
console.log(f("ERASE"));
console.log(f("FLUFF"));
console.log(f("LABEL"));
console.log(f("MODEM"));
console.log(f("RADAR"));
console.log(f("RIVER"));
console.log(f("SWISS"));
console.log(f("TRUST"));
console.log(f("KNEES"));
console.log(f("QUEEN"));
console.log(f("ORTHO"));
console.log(f("GROOVE"));
console.log(f("ONLY"));
console.log(f("CHARACTER"));
console.log(f("OFF"));
console.log(f("IT"));
console.log(f("ORTHO"));

Initial version

For the record, my initial version was 63 bytes. It's passing all test cases successfully but fails to detect consecutive identical characters.

([a,b,c,d,e,f])=>!f&&a==e&&!(((a>b)+2*(b>c)+4*(c>d)+8*(d>e))%3)

Below is a 53-byte version suggested by Neil in the comments, which works (and fails) equally well:

([a,b,c,d,e,f])=>!f&&a==e&&!((a>b)-(b>c)+(c>d)-(d>e))

Edit: See Neil's answer for the fixed/completed version of the above code.

Arnauld

Posted 2016-10-17T13:24:11.957

Reputation: 111 334

0000 is also congruent to 0 modulo 3 but again you can't have the first and last letters the same, so, like 15, you don't need to explicitly test for it. – Neil – 2016-10-17T18:40:14.897

For that initial version, can you use !((a>b)-(b>c)+(c>d)-(d>e))? – Neil – 2016-10-17T18:43:06.463

p<c?0:NaN can be written as 0/(p<c), which saves 2 bytes. – Ismael Miguel – 2016-10-17T18:52:37.020

@Neil Regarding the test against 0: you're perfectly right. (However, I do need the k? test because of the possible NaN.) Regarding your alternate version: that should work indeed. – Arnauld – 2016-10-17T18:54:47.817

@IsmaelMiguel - Good call! Thanks. – Arnauld – 2016-10-17T19:01:13.270

Only 53 bytes? Nice, I can add &&!/(.)\1/.test(a+b+c+d+e) to that to exclude identical adjacent letters and still come in under 80 bytes! – Neil – 2016-10-17T20:47:45.200

@Neil Sounds good! I think you should post it. (This is clearly not related to my original answer anymore.) – Arnauld – 2016-10-17T21:05:08.663

4

JavaScript (ES6), 78 bytes

([a,b,c,d,e,f])=>a==e&&!(f||/(.)\1/.test(a+b+c+d+e)||(a>b)-(b>c)+(c>d)-(d>e))

Based on @Arnauld's incorrect code, but golfed and corrected. Works by first checking that the first character is the same as the fifth (thus guaranteeing 5 characters) and that the length of the string is no more than 5. After checking for consecutive duplicate characters, it remains to check the waviness of the string, which should have one peak and one trough two letters apart.

  • If the peak and the trough are the middle and first/last letters, then the first two comparisons and the last two comparisons cancel out
  • If the peak and the trough are the second and fourth letters, then the middle two comparisons and the outer two comparisons cancel out
  • Otherwise, something fails to cancel and the overall expression returns false

Edit: Alternative 78-byte solution based on @KarlNapf's answer:

([a,b,c,d,e,f],g=(a,b)=>(a<b)-(a>b))=>a==e&&!f&&g(a,b)*g(c,d)+g(b,c)*g(d,e)<-1

Neil

Posted 2016-10-17T13:24:11.957

Reputation: 95 035

3

Python 2, 110 87 60 bytes

Saving 1 byte thanks to Neil

Requires input enclosed in quotes, e.g. 'KNEES'

True if it is an infinity word, False if not and it has length of 5 and prints error message if wrong length

s=input()
a,b,c,d,e=map(cmp,s,s[1:]+s[0])
print a*c+b*d|e<-1

Inspired by xnor's answer using map(cmp...

s=input()
e=map(cmp,s,s[1:]+s[0])
print e[4]==0and e[0]*e[2]+e[1]*e[3]==-2and 5==len(s)

previous solution:

s=input()
d=[ord(x)-ord(y)for x,y in zip(s,s[1:])]
print s[0]==s[4]and d[0]*d[2]<0and d[1]*d[3]<0and 4==len(d)

Using the optimized logic of Kevin Cruijssen

Karl Napf

Posted 2016-10-17T13:24:11.957

Reputation: 4 131

Why not a*c+b*d+2==0==e? – Neil – 2016-10-18T08:46:33.553

@Neil yes why not, but xnor's a*c+b*d|e is even shorter. – Karl Napf – 2016-10-18T09:00:59.517

I think <-1 might work, since both -2|1 and -2|-1 equal -1. – Neil – 2016-10-18T11:22:00.193

3

Python 2 exit code, 56 bytes

s=input()
v,w,x,y,z=map(cmp,s,s[1:]+s[0])
v*x+w*y|z>-2>_

Outputs via exit code: Error for False, and successful run for True.

Takes the string s with characters abcde, rotates it to bcdea, does an elementwise comparison of corresponding characters, and assigns them to five variables v,w,x,y,z. The wrong length gives an error.

The infinity words all have

v*x == -1
w*y == -1
z == 0

which can be checked jointly as v*x+w*y|z == -2. The chained comparison v*x+w*y|z>-2>_ short-circuits if this is the case, and otherwise goes on to evaluate -2>_ which gives a name error.

xnor

Posted 2016-10-17T13:24:11.957

Reputation: 115 687

Ah, that's nice how you golfed the conditional more! – Karl Napf – 2016-10-18T08:37:51.353

2

PHP, 102 Bytes

for(;$i<strlen($w=$argv[1]);)$s.=($w[$i++]<=>$w[$i])+1;echo preg_match("#^(2200|0022|2002|0220)#",$s);

Jörg Hülsermann

Posted 2016-10-17T13:24:11.957

Reputation: 13 026

2

Python 2, 71 bytes

lambda s:map(cmp,s,s[1:]+s[0])in[[m,n,-m,-n,0]for m in-1,1for n in-1,1]

Takes the string s with characters abcde, rotates it to bcdea, and does an elementwise comparison of corresponding characters.

a  b   cmp(a,b)
b  c   cmp(b,c)
c  d   cmp(c,d)
d  e   cmp(d,e)
e  a   cmp(e,a)

The result is a list of -1, 0, 1. Then, checks if the result is one of the valid sequences of up and downs:

[-1, -1, 1, 1, 0]
[-1, 1, 1, -1, 0]
[1, -1, -1, 1, 0]
[1, 1, -1, -1, 0]

as generated from the template [m,n,-m,-n,0] with m,n=±1. The last 0 checks that the first and last letter were equal, and the length ensures that the input string had length 5.


An alternative 71. Checks the conditions on comparisons while ensuring the right length.

def f(s):a,b,c,d,e=map(cmp,s,s[1:]+s*9)[:5];print a*c<0==e>b*d>len(s)-7

xnor

Posted 2016-10-17T13:24:11.957

Reputation: 115 687

1

Actually, 38 27 bytes

This answer was largely inspired by Jonathan Allan's excellent Jelly answer. There are probably several places where this can be golfed, so golfing suggestions welcome! Try it online!

O;\♀-dY@♂s4R`0~;11({k`Míub*

Ungolfing

     Implicit input s.
O    Push the ordinals of s. Call this ords.
;    Duplicate ords.
\    Rotate one duplicate of ords left by 1.
♀-   Vectorized subtraction. This effectively gets the first differences of ords.
d    Pop ord_diff[-1] onto the stack. This is ords[0] - ords[-1].
Y    Logical negate ord_diff[-1], which returns 1 if s[0] == s[-1], else 0.
@    Swap (s[0] == s[-1]) with the rest of ord_diff.

♂s       Vectorized sgn() of ord_diff. This gets the signs of the first differences.
4R       Push the range [1..4] onto the stack.
`...`M   Map the following function over the range [1..4]. Variable x.
  0~;      Push -1 onto the stack twice.
  11       Push 1 onto the stack twice.
  (        Rotate x to TOS.
  {        Rotate the stack x times, effectively rotating the list [1, 1, -1, -1].
  k        Wrap it all up in a list.

     Stack: list of rotations of [1, 1, -1, -1], sgn(*ord_diff)
í    Get the 0-based index of sgn(*ord_diff) from the list of rotations. -1 if not found.
ub   This returns 1 only if sgn(*ord_diff) was found, else 0.
     This checks if the word loops like an infinity word.

*    Multiply the result of checking if the word s loops and the result of s[0] == s[-1].
     Implicit return.

Sherlock9

Posted 2016-10-17T13:24:11.957

Reputation: 11 664

1

GNU Prolog, 47 bytes

i([A,B,C,D,A]):-A>B,B>C,C<D,D<A;i([B,C,D,A,B]).

Defines a predicate i which succeeds (infinitely many times, in fact) for an infinity word, thus outputting "yes" when run from the interpreter (as is usual for Prolog); fails for a candidate word whose first and last letters don't match, or isn't 5 letters long, thus outputting "no" when run from the interpreter; and crashes with a stack overflow if given a candidate word that isn't an infinity word, but which is five letters with the first and last two matching. (I'm not sure why it crashes; the recursive call should be treatable as a tailcall. Apparently GNU Prolog's optimizer isn't very good.) Succeeding is Prolog's equivalent of truthy, and failing the equivalent of falsey; a crash is definitely more falsey than truthy, and fixing it would make the solution substantially longer, so I hope this counts as a valid solution.

The algorithm is fairly simple (and indeed, the program is fairly readable); check whether the letters form one of the four patterns that make an infinity word, and if not, cyclicly permute and try again. We don't need to explicitly check for double letters as the < and > operators let us implicitly check that at the same time that we check that the deltas match.

user62131

Posted 2016-10-17T13:24:11.957

Reputation:

1

APL (Dyalog), 16 15 bytes

0=16|3⊥×2-/⎕a⍳⍞

Try it online!

ngn

Posted 2016-10-17T13:24:11.957

Reputation: 11 449

1

TI-BASIC, 81 bytes

String to pass into the program is in Ans. Returns (and implicitly displays) 1 if the entered word is an Infinity Word, and 0 (or exits with an error message) if it isn't.

seq(inString("ABCDEFGHIJKLMNOPQRSTUVWXYZ",sub(Ans,A,1)),A,1,length(Ans
min(Ans(1)=Ans(5) and {2,2}=abs(deltaList(deltaList(deltaList(Ans)/abs(deltaList(Ans

Errors on any repeated characters, or non-5-letter-words.

Josiah Winslow

Posted 2016-10-17T13:24:11.957

Reputation: 725

1

05AB1E, 16 bytes

Ç¥DO_s.±¥¥Ä2DиQ*

Port of @JonathanAllan's Jelly answer.

Try it online or verify all test cases.

Explanation:

Ç             # Convert the (implicit) input string to a list of unicode values
              #  i.e. "RULES" → [82,85,76,69,82]
 ¥            # Take the deltas
              #  i.e. [82,85,76,69,82] → [3,-9,-7,13]
  DO          # Duplicate and take the sum
              #  i.e. [3,-9,-7,13] → 0
    _         # Check if that sum is exactly 0
              # (which means the first and last characters are equal)
              #  i.e. 0 and 0 → 1 (truthy)
 s            # Swap so the deltas are at the top of the stack again
  .±          # Get the sign of each
              #  i.e. [3,-9,-7,13] → [1,-1,-1,1]
    ¥         # Get the deltas of those signs
              #  i.e. [1,-1,-1,1] → [-2,0,2]
     ¥        # And then get the deltas of those
              #  i.e. [-2,0,2] → [2,2]
      Ä       # Convert them to their absolute values
       2Dи    # Repeat the 2 two times as list: [2,2]
          Q   # Check if they are equal
              #  i.e. [2,2] and [2,2] → 1 (truthy)
 *            # Check if both are truthy (and output implicitly)
              #  i.e. 1 and 1 → 1 (truthy)

Kevin Cruijssen

Posted 2016-10-17T13:24:11.957

Reputation: 67 575

1

R, 144 bytes

The answer is based off the logic of @Jonathan Allan. It could probably be golfed though.

s=strsplit(scan(,""),"")[[1]];d=diff(match(s,LETTERS));s[1]==tail(s,1)&length(s)==5&all(!rle(s)$l-1)&!sum(d)&!sum(sign(d))&any(rle(sign(d))$l>1)

R-fiddle test cases (vectorized example but same logic)

Billywob

Posted 2016-10-17T13:24:11.957

Reputation: 3 363

Since you already have a check that length(s)==5, you can replace s[1]==tail(s,1) with s[1]==s[5]. A one-byte shorter method to check the length is is.na(s[6]). Together these two changes return TRUE for s of length 5 exactly and FALSE otherwise, as TRUE&NA is NA but FALSE&NA is FALSE. You can also save a few bytes by replacing !sum(sign(d))&any(rle(sign(d))$l>1) with !sum(a<-sign(d))&any(rle(a)$l>1). – rturnbull – 2016-11-16T16:32:31.470