28
\$\begingroup\$

The New York Times has a daily online game called Letter Boxed (the link is behind a paywall; the game is also described here), presented on a square as follows:

Letter Boxed example from the New York Times

You are given 4 groups of 3 letters (each group corresponds to one side on the picture); no letter appears twice. The aim of the game is to find words made of those 12 letters (and those letters only) such that:

  • Each word is at least 3 letters long;
  • Consecutive letters cannot be from the same side;
  • The last letter of a word becomes the first letter of the next word;
  • All letters are used at least once (letters can be reused).

In this challenge, you are given the letters and a list of words. The goal is to check whether the list of words is a valid Letter Boxed solution.

Input

Input consists of (1) 4 groups of 3 letters and (2) a list of words. It can be in any suitable format.

Output

A truthy value if the list of words is a valid solution to the Letter Boxed challenge for those 4×3 letters, and a falsey value otherwise.

Test cases

Groups of letters={{I,C,O}, {M,R,E}, {G,N,S}, {A,P,L}}.

Truthy values

  • PILGRIMAGE, ENCLOSE
  • CROPS, SAIL, LEAN, NOPE, ENIGMA

Falsey values

  • PILGRIMAGE, ECONOMIES (can't have CO since they are on the same side)
  • CROPS, SAIL, LEAN, NOPE (G and M have not been used)
  • PILGRIMAGE, ENCLOSURE (U is not one of the 12 letters)
  • ENCLOSE, PILGRIMAGE (last letter of 1st word is not first letter of 2nd word)
  • SCAMS, SO, ORGANISE, ELOPE (all words must be at least 3 letters long).

Note that in this challenge, we do not care whether the words are valid (part of a dictionary).

Scoring:

This , lowest score in bytes wins!

\$\endgroup\$
5
  • 4
    \$\begingroup\$ @TFeld no letter appears twice \$\endgroup\$ Commented Apr 15, 2019 at 11:06
  • \$\begingroup\$ A truthy value if the list of words is a valid solution to the Letter Boxed challenge for those 4×3 letters, and a falsey value otherwise. For Python (and most other languages, I expect), both [] and 0 are falsey. Can we output either, or must our output be consistent? \$\endgroup\$ Commented Apr 15, 2019 at 23:35
  • \$\begingroup\$ @ArtemisFowl Either is fine. \$\endgroup\$ Commented Apr 16, 2019 at 6:46
  • \$\begingroup\$ I thought so, but my question was: can we mix them? \$\endgroup\$ Commented Apr 16, 2019 at 15:03
  • \$\begingroup\$ @ArtemisFowl Yes, you can mix them. \$\endgroup\$ Commented Apr 16, 2019 at 15:05

12 Answers 12

6
\$\begingroup\$

JavaScript (ES6),  130  126 bytes

Takes input as (letters)(words). Returns \$0\$ or \$1\$.

L=>W=>L.every(a=>a.every(x=>(W+'').match(x,a.map(y=>s+='|'+x+y))),p=s=1)&W.every(w=>w[2]&&p|w[0]==p&!w.match(s,p=w.slice(-1))) 

Try it online!

Step 1

We first iterate over \$L\$ to build a pipe-separated string \$s\$ consisting of all invalid pairs of letters. While doing so, we also make sure that each letter appears at least once in some word.

L.every(a => // for each group of letter a[] in L[]: a.every(x => // for each letter x in a[]: (W + '') // coerce W[] to a string .match( // and test whether ... x, // ... x can be found in it a.map(y => // for each letter y in a[]: s += '|' + x + y // append '|' + x + y to s ) // end of map() ) // end of match() ), // end of inner every() p = s = 1 // start with p = s = 1 ) // end of outer every() 

Step 2

We now iterate over \$W\$ to test each word.

W.every(w => // for each word w in W[]: w[2] && // is this word at least 3 characters long? p | // is it the first word? (p = 1) w[0] == p & // or does it start with the last letter of the previous word? !w.match( // and finally make sure that ... s, // ... it doesn't contain any invalid pair of letters p = w.slice(-1) // and update p to the last letter of w ) // end of match() ) // end of every() 
\$\endgroup\$
6
\$\begingroup\$

Jelly, 30 29 bytes

FQṢ=Ṣ},i@€€’:3Iʋ,Ẉ>2ɗ,U=ḢɗƝ{Ȧ 

Try it online!

A dyadic link that takes the list of words as left argument and the flattened list of letters in the box as the right argument. It returns 1 for true and 0 for false.

Explanation

F | Flatten the word list Q | Unique Ṣ | Sort = | Is equal to Ṣ} | The sorted letterbox letters , ʋ | Pair this with the following: i@€€ | The index of each letter of each word in the letterbox ’ | Decrease by 1 :3 | Integer divide by 3 I | Differences between consecutive ones (will be zero if any two consecutive letters in a word from same side of box) , ɗ | Pair everything so far with the following: Ẉ>2 | Whether length of each input word is greater than 2 , ɗƝ{ | Pair everything so far with the following, applied to each neighbouring pair of the input word list U | Upend (reverse) first word = | Compare characters to second Ḣ | Take first (i.e. last character of first word equals first character of second) Ȧ | Flatten all of the above and check there are no false values 
\$\endgroup\$
6
\$\begingroup\$

05AB1E, 37 35 33 32 31 29 28 bytes

εk3÷üÊ}DO2@¹ü«εüQO}²{¹˜êQ)˜P 

-2 bytes by taking inspiration of the ê approach @Emigna used in his 05AB1E answer.
-3 bytes thanks to @Grimy.

Takes a list of list of characters for the words as first input, and the flattened list of twelve letters as second input.

Try it online or verify all test cases.

Explanation:

ε # Map over the character-lists `y` of the (implicit) input-list of words: k # Get the index of each character in the (implicit) input-list of letters 3÷ # Integer-divide each index by 3 üÊ # Check for each overlapping pair of integers that they are NOT equal }D # After the map: duplicate the resulting list O # Get the sum of each inner list of truthy/falsey values 2@ # And check that each is larger than 2 (so all words had at least 3 letters) ¹ü # Get all overlapping pairs of character-lists from the input-list of words: « # And merge them together to a flattened list of characters ε } # Map over those merged character lists: üQ # Check for each overlapping pair of characters in the list that they are equal O # And take the sum of this (where we'd expect 1/truthy if the last character of # the first word and the first character of the second word are equal) # (NOTE: This could fail for inputs with identical adjacent characters, # but the earlier check of `εk3÷üÊ}` already covers for this) ²{ # Push the input-list of letters, and sort them ¹˜ # Push the input-list of list of word-letters, flattened, ê # and then uniquified and sorted as well Q # And check if both lists of characters are the same )˜ # Then wrap everything on the stack into a list, and deep flatten it P # And check if everything is truthy by taking the product # (which is output implicitly as result) 
\$\endgroup\$
3
  • 1
    \$\begingroup\$ @Grimy Ah, that first comment is indeed an obvious one. I've just changed it to a character array, so now that indeed works where it wouldn't before when the words were still strings. That second approach of merge, check pair equality, sum is quite brilliant, though! :D Thanks (as always). \$\endgroup\$ Commented Sep 18, 2019 at 12:40
  • 1
    \$\begingroup\$ Another -1: ¹€g3@ -> DO2@ after the first check (TIO) \$\endgroup\$ Commented Sep 18, 2019 at 12:47
  • 1
    \$\begingroup\$ @Grimy Another nice one, thanks. We're now below the Jelly answer of 29. :) \$\endgroup\$ Commented Sep 18, 2019 at 13:04
5
\$\begingroup\$

05AB1E, 42 bytes

εg2›}P¹εεUIεXå}ƶO}üÊP}P¹ü‚ε`нsθQ}P¹Jê²JêQP 

Try it online!

\$\endgroup\$
1
  • \$\begingroup\$ It's not much, but a byte can be saved by removing all P after the maps, and use )˜P at the end. 41 bytes Nice approach with ê however! Saved 2 bytes in my 05AB1E answer. \$\endgroup\$ Commented Apr 15, 2019 at 13:52
4
\$\begingroup\$

Python 2, 171 bytes

lambda l,w:(set(sum(l,[]))==set(''.join(w)))*all(a[-1]==b[0]for a,b in zip(w,w[1:]))*all((a in g)+(b in g)<2for x in w for a,b in zip(x,x[1:])for g in l)*min(map(len,w))>2 

Try it online!

\$\endgroup\$
4
\$\begingroup\$

Jelly, 34 bytes

Ṫ=¥/ƝḢ€Ạȧ⁸Fe€ⱮZḄ;IẠƊȧF}fƑF{ ẈṂ>2ȧç 

A dyadic Link accepting the words on the left and the letter groups on the right which yields 1 if valid and 0 if not.

Try it online! Or see the test-suite.

\$\endgroup\$
4
\$\begingroup\$

Haskell, 231 bytes

import Data.List l&w=all((>2).length)w&&c w&&all(l!)w&&(h l)%(h w) h=concat l%w=null[x|x<-l,x`notElem`w] l!(a:b:c)=a#l?(b#l)&&l!(b:c) l!_=1>0 Just a?Just b=a/=b _?_=1<0 c#l=findIndex(elem c)l c(a:b:t)=last a==head b&&c(b:t) c _=1>0 

Try it online!

Not the best score. Some Haskell guru will probably be able to get this under 100 bytes.

Usage

["ICO","MRE","GNS","APL"]&["CROPS", "SAIL", "LEAN", "NOPE", "ENIGMA"] 

Explanation

import Data.List l&w = all((>2).length)w && -- Every word has length > 2 c w && -- Every word ends with the same letter as the next one starts with all(l!)w && -- For every word: Consecutive letters are on different sides (and must exist on a side) (h l)%(h w) -- All letters are used h=concat -- Just a shorthand l%w=null[x|x<-l,x`notElem`w] -- The letters of l, with all letters of w removed, is empty l!(a:b:c)=a#l?(b#l)&&l!(b:c) -- Sides of the first two letters are different, recurse from second letter l!_=1>0 -- Until fewer than 2 letters remain Just a?Just b=a/=b -- Both sides must be different _?_=1<0 -- And must exist c#l=findIndex(elem c)l -- Find the side of letter c c(a:b:t)=last a==head b&&c(b:t) -- Last letter of the first word must be same as first letter of second word, recurse starting from second word c _=1>0 -- Until there are fewer than 2 words 
\$\endgroup\$
4
\$\begingroup\$

Haskell, 231 bytes

A different Haskell variation, exactly the same size as @Paul Mutser's :)

import Data.List f x=filter(\a->length a>1)$concatMap subsequences x g=nub.concat.f p l(x:y)=foldl(\(m,n)c->(c,n&&length c>2&&(not$any(`isInfixOf`c)(f l))&&last m==head c))(x,True)y z l w=null(g l\\g w)&&null(g w\\g l)&&(snd$p l w) 

Try it online!

Ungolfed

-- generate all invalid substrings f :: [String] -> [String] f xs = filter (\x -> length x > 1) $ concatMap subsequences xs -- utility function to flatten and remove duplicates g :: [String] -> String g = nub $ concat $ f -- verify that all conditions are satisfied along the list p :: [String] -> [String] -> (String, Bool) p l (x:xs) = foldl (\(m,n) c -> (c , n && length c > 2 && (not $ any (`isInfixOf` c)(f l)) && last m == head c)) (x, True) xs -- put all the pieces together and consume input z :: [String] -> [String] -> Bool z l w = null (g l \\ g w) && null (g w \\ g l) && (snd $ p l w) 
\$\endgroup\$
3
\$\begingroup\$

Ruby, 126 bytes

->l,w{(/(_|^)..(_|$)/!~s=w*?_)&&!!s.chars.uniq[12]&&/__|^_|_$|(_.*)\1/!~s.gsub(/(.)_\1/,'\1').chars.map{|x|l.grep(/#{x}/)}*?_} 

Try it online!

\$\endgroup\$
3
  • \$\begingroup\$ Nice, when I first saw the challenge I tried to do something similar, but gave up with a score somewhere in 140-ies. BTW, save a byte by dropping parentheses after grep. \$\endgroup\$ Commented Apr 17, 2019 at 18:04
  • \$\begingroup\$ This doesn't work when the last word is 1 or 2 letters long, e.g. puts f[l,['PILGRIMAGE','ENCLOSE','EG']] returns true instead of false. \$\endgroup\$ Commented May 4, 2019 at 11:58
  • 1
    \$\begingroup\$ You are right, fixed. \$\endgroup\$ Commented May 4, 2019 at 21:15
3
\$\begingroup\$

Java (JDK), 188 bytes

g->w->{var v=0<1;int x=0,l,i=0,j,p,z,y=w[0][0];for(;i<w.length;i++)for(l=w[i].length,v&=y==w[i][0]&l>2,j=0,p=-9;j<l;v&=z>=0&z/3!=p/3,x|=2<<(p=z))z=g.indexOf(y=w[i][j++]);return v&x==8190;} 

Try it online!

Explanations

g->w->{ // Lambda accepting letter groups as a string and a list of words, in the form of an array of char arrays. var v=0<1; // Validity variable int x=0, // The letter coverage (rule 4) l, // The length of w[i] i=0, // The w iterator j, // The w[i] iterator p, // The previous group z, // The current group y=w[0][0]; // The previous character for(;i<w.length;i++) // For each word... for( l=w[i].length, // make a shortcut for the length v&=y==w[i][0]&l>2, // check if the last character of the previous word is the same as the first of the current. // Also, check if the length is at least 3 j=0, // Reset the iteration p=-9 // Set p to an impossible value. ; j<l // ; v&=z>=0&z/3!=p/3, // Check that each letter of the word is in the letter pool, // and that the current letter group isn't the same as the previous one. x|=2<<(p=z) // After the checks, assign z to p, // and mark the letter of the pool as used. ) z=g.indexOf(y=w[i][j++]); // Assign the current letter to y so that it contains the last at the end of the loop. // and fetch the position of the letter in the pool. return v&x==8190; // Return true if all matched // and if the rule 4 is enforced. } 

Credits

  • -2 bytes thanks to ceilingcat
\$\endgroup\$
1
  • \$\begingroup\$ Suggest v&=y==w[i][j=0]&l>2 instead of v&=y==w[i][0]&l>2,j=0 \$\endgroup\$ Commented Nov 25, 2022 at 20:26
2
\$\begingroup\$

Charcoal, 63 bytes

⌊⁺⁺⁺⭆η›Lι²⭆⪫ηω№⪫θωι⭆⪫θω№⪫ηωι⭆η⭆ι⎇μ¬⁼Φθ№νλΦθ№ν§ι⊖μ∨¬κ⁼§ι⁰§§η⊖κ±¹ 

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

⌊⁺⁺⁺ 

Concatenate the below expressions and output 0 if any of them includes a 0 otherwise 1.

⭆η›Lι² 

For each word in the solution output whether its length is at least 3.

⭆⪫ηω№⪫θωι 

For each letter in the solution output whether it appears in the puzzle.

⭆⪫θω№⪫ηωι 

For each letter in the puzzle output whether it appears in the solution.

⭆η⭆ι⎇μ¬⁼Φθ№νλΦθ№ν§ι⊖μ∨¬κ⁼§ι⁰§§η⊖κ±¹ 

For each letter in the solution check that the previous letter is not in the same group, unless it is the first letter of a word, in which case check that it is equal to the last letter of the previous word, unless it is the first letter of the solution, in which case just ignore it.

\$\endgroup\$
0
\$\begingroup\$

Python 2, 168 156 bytes

lambda l,w,J=''.join:(set(J(w))==set(J(l)))*all((v<1or u[-1]==v[0])*u[2:]*(2>(x in p)+(y in p))for u,v in zip(w,w[1:]+[0])for x,y in zip(u,u[1:])for p in l) 

Try it online!

Returns 1 for truthy, 0 for falsey.

\$\endgroup\$

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.