Highlight a Wordle guess

In Wordle, you try to guess a secret word, and some letters in your guess are highlighted to give you hints.

If you guess a letter which matches the letter in the same position in the secret word, the letter will be highlighted green. For example, if the secret word is LEMON and you guess BEACH, then the E will be highlighted green.

If you guess a letter which is present in the secret word, but not in the correct corresponding position, it will be highlighted yellow.

If a letter appears more times in the guess than it does in the secret word, only upto as many occur in the secret may be highlighted. If any of the occurrences are in the same place, they should be preferentially highlighted green, leaving earlier letters unhighlighted if necessary.

For example, with the secret LEMON and the guess SCOOP, the second O will be green, because it is in the right place, but the first O will be unhighlighted, because there is only one O in the secret, and one O has already been highlighted.

Any of the remaining letters in the secret may be highlighted yellow if they match, as long as the right number are highlighted in total. For example, with the secret LEMON and the guess GOOSE, only one of the Os should be highlighted; it does not matter which.

Given two five-letter strings, a secret and a guess, highlight the letters in the guess according to the rules above.

You can "highlight" the letters using any reasonable output format. For example:

• a length-5 list of highlight values
• a list of 5 pairs of (letter, highlight value)
• a mapping from indices 0-4 or 1-5 to the highlight at that position

You can choose any three distinct values to represent unhighlighted, yellow, and green. (For example, 0/1/-1, or ""/"Y"/"G"...)

Rules

• You may assume the inputs are both of length 5 and contain only ASCII letters
• You may choose whether to accept input in uppercase or lowercase
• You may take input as a string, a list of character codes, or a list of alphabet indices (in $$\ [0, 25] \$$ or $$\ [1, 26] \$$)
• You may use any standard I/O method
• Standard loopholes are forbidden
• This is , so the shortest code in bytes wins

Test cases


• SCRAP -> $$\ \qW S \qW C \qW R \qW A \qW P \$$
• HELLO -> $$\ \qG H \qG E \qG L \qG L \qG O \$$
• EPOCH -> $$\ \qY E \qW P \qY O \qW C \qY H \$$
• CIVIL -> $$\ \qW C \qW I \qW V \qW I \qY L \$$
• BELCH -> $$\ \qW B \qG E \qG L \qW C \qY H \$$
• ZOOMS -> $$\ \qW Z \qY O \qW O \qW M \qW S \$$ or $$\ \qW Z \qW O \qY O \qW M \qW S \$$
• LLAMA -> $$\ \qY L \qY L \qW A \qW M \qW A \$$
• EERIE -> $$\ \qW E \qG E \qW R \qW I \qW E \$$
• HALAL -> $$\ \qG H \qW A \qG L \qW A \qY L \$$
• LLLXX -> $$\ \qY L \qW L \qG L \qW X \qW X \$$ or $$\ \qW L \qY L \qG L \qW X \qW X \$$
• LLLLL -> $$\ \qW L \qW L \qG L \qG L \qW L \$$

Copy and paste friendly format

• Sandbox Jan 25 at 7:42
• I assume we're allowed to replace the green/yellow values with a distinct value, and keeping the grey values unchanged (which the Retina answer already does for example)? It isn't specifically mentioned, hence the question. Jan 25 at 9:23
• @KevinCruijssen Actually that was probably me not reading the challenge well enough. I'll update my answer.
– Neil
Jan 25 at 9:48
• @KevinCruijssen Yes, it's fine; I class it as "any reasonable format". Jan 25 at 10:04
• @TobySpeight Yes, it is pretty much the same rules as Mastermind (apart from the restriction that it must be a valid English word, but that doesn't matter for this challenge). We have had some Mastermind-related challenges in the past which may interest you: codegolf.stackexchange.com/search?q=mastermind+is:q Jan 29 at 17:49

BQN, 19 18 bytesSBCS

Improved after getting some new ideas from trying this in K.

=+5>≠{⊒⌾(∾𝕨⊸⊔)˝𝕩}≍


Run online!

BQN, 27 24 bytesSBCS

Returns 0 for white, 1 for yellow and 2 for green.

+⊸×∘≠⊏2∾(≠/⊣)(⊒<≠∘⊣)≠/⊢


Run online!

This is a golfed version of my Judge function from the Wordle solver challenge, with an improvement suggested by Marshall.
There might another approach more suitable for .

This is a tacit function taking the guess on the right and the secret on the left.
≠/⊢ / ≠/⊣ Remove fully correct letters from the guess / secret.
⊒ Progressive indices of the shortened guess inside the shortened secret. Progressive means that no index is output twice and if a value is not available, the length of the vector is returned.
<≠∘⊣ For each index, is this less than the length of the shortened secret?
2∾ Prepend a 2 to this boolean vector.
+⊸×∘≠ 0s at the green positions, positive integers in increasing order at the other positions.
⊏ Index with that into the vector.

The part +⊸×∘≠⊏2∾ is mostly there to invert the ≠/, it would be nice if ≠/⁼ would work instead.

"HELLO" (≠/⊢) "LLLXX"
# "LLXX"
"HELLO" (≠/⊣) "LLLXX"
# "HELO"
"HELO" (⊒) "LLXX"
# ⟨ 2 4 4 4 ⟩
"HELO" (⊒<≠∘⊣) "LLXX"
# ⟨ 1 0 0 0 ⟩
"HELLO" (+⊸×∘≠) "LLLXX"
# ⟨ 1 2 0 3 4 ⟩
1‿2‿0‿3‿4⊏2∾1‿0‿0‿0
# ⟨ 1 0 2 0 0 ⟩


Perl 5 + -pF, 57 bytes

Returns 2 for green, 1 for yellow and leaves others as they were.

This example pretty prints using ANSI escape codes!

$_=<>;s/./$F[pos]=~s!$&!#!?2:$&/ge;eval's/$F[$-++]/1/;'x5

Try it online!

Initial (implicit via -p) input (the target word) is stored in $_ and split into @F and the guess is then stored in $_ instead. s///ubstitute each letter with: if s///ubstituting $& (the current match) with # is successful, 2, otherwise itself. Then s///ubstitute each index of @F left in $_ (s/// works on $_ by default) with 1. • Wow, that sandbox is cool! Jan 25 at 12:47 • Heh, thanks! It's no ATO but, as time allows, I'm hoping to add in more WASM wrappers for other langs. Ruby and Python already exist and should be easy enough to use. I've wanted to have a proper shareable terminal without using asciinema for a while so figured I should just do it! :) Jan 25 at 12:51 05AB1E, 14 bytes øDÆĀ*ø0Kvy®.;  Input as a list of 1-based alphabet indices; modifies the list to change green to 0 and yellow to -1 (grey values remain the same). Explanation: ø # Zip the two (implicit) input-lists together, creating pairs D # Duplicate this list of pairs Æ # Reduce each inner pair by subtracting Ā # Check for each that it's NOT 0 (0 if 0; 1 otherwise) * # Multiply that to the pairs ø # Zip/transpose; swapping rows/columns  # Pop and push both lists separated to the stack 0K # Remove all 0s from the top list v # Foreach y over the remaining values: y®.; # Replace the first y with -1 in the other list # (after which the result is output implicitly)  • @Shaggy "You may take input as a string, a list of character codes, or a list of alphabet indices (in $[0, 25]$ or $[1, 26]$)." Jan 26 at 4:09 • @Shaggy As mentioned by Makonede above, the third rule states this is an allowed input format. And the output format I'm using is allowed in the comments under the challenge. Jan 26 at 7:31 Retina 0.8.2, 45 bytes +(\w)(.{5})\1 +$2+
+(\w)(.*,.*)\1
-$2- ,.*  Try it online! Link includes test cases. Takes input as two comma-separated words. Matching letters are changed to + and near-misses are changed to -. Explanation: +(\w)(.{5})\1 +$2+


Change all matching letters to +s (in the secret word too, so that it doesn't match later.)

+(\w)(.*,.*)\1
-$2-  Change all near misses to -s. ,.*  Delete the secret word. R, 84 80 bytes function(s,g){e=s!=g;for(i in 1:5)s[x]=g[i]=1+is.na(x<-which(s*e==g[i])[1]);g*e}  Try it online! My golfing skills are quite rusty after a long break, but here it goes anyway. Takes input as a vector of codepoints, outputs 0 for exact match, 1 for inexact match, 2 - for miss (the reverse of what is given in test cases). • R4.1 now supports short-hand notation for functions, i.e. function(s,g) is equivalent to $$s,g). Jan 27 at 15:22 K (ngn/k), 32 31 bytes {g-(x~':x{x_x?y}\y@>g)@<>g:x=y}  Try it online! Minor reordering and syntax change from the original answer below (and it returns the negation of the one below). K (ngn/k), 32 bytes {(x~':x{x_x?y}\y@>g)[<>g]-g:x=y}  Try it online! I saw the task being discussed in the arraylang discord, so I gave it a try myself. Returns an array where 1 represents gray, -1 green, and 0 yellow. {(x~':x{x_x?y}\y@>g)[<>g]-g:x=y} x: correct answer, y: guess g:x=y g: green positions mask (1=green) y@>g reorder the guess so that greens come first x{ }\ seeded scan starting with x: for each char in y, x_x?y delete one copy of the char from x if it exists x~': for each position, test if the output is the same as input (1=gray, 0=green or yellow) ( )[<>g] reorder the result back to the original position (for permutation p, <p is the inverse permutation) - (1=gray, 0=other) - (1=green, 0=other) = (1=gray, 0=yellow, -1=green)  K (ngn/k), 30 bytes Takes the guess as first argument and the secret as second. Returns a list with -1 for not highlighted, 0 for yellow and 1 for green. {e-^(,/(=x@>e)@'<'=y)?<>e:x=y}  Try it online! e:x=y booleans mask with 1s where the two words are equal "HALAL"="HELLO" -> 1 0 1 0 0 > grade down; this gives an order that would sort the equal indices to the front > 1 0 1 0 0 -> 0 2 1 3 4 < grade up; inverts this permutation < 0 2 1 3 4 -> 0 2 1 3 4 ( )? for each of those indices, find it in the list on the left Returns 0N (null) for indices that are not present in the list. (0 1 4 0N 0N 0N)?0 2 1 3 4 -> 0 0N 1 0N 2 ^ Is null? Returns a boolean mask which has 1s for non-higlighted positions. ^a?b is basically "b not in a" ^0 0N 1 0N 2 -> 0 1 0 1 0 ,/(=x@>e)@'<'=y inner part that generates a list of indices for yellow and green highlights, where the green highlights have been sorted to the front. =y group the secret word ="HELLO" -> "HELO"!(,0;,1;2 3;,4) <' grade up the indices of each group As the indices are sorted within each group, this converts each to a range from 0 to length-1. <'="Hello" -> "HELO"!(,0;,0;0 1;,0) x@>e sort matching values to the front in the guessed word. "HALAL"@>1 0 1 0 0 -> "HLAAL" = group this reordered string ="HLAAL" -> "HLA"!(,0;1 4;2 3) Now we have two dictionaries: - "char in secret!0 .. number of times that char appears-1", - "char in guess!indices of that char, when greens are sorted to the front" @' pair up matching keys in the two dicts, and apply @ between their values. @ performs indexing, where out of bounds indices return nulls. when a key is only present in one of the dicts, this will generate a new one with a list of nulls as value. ("HLA"!(,0;1 4;2 3))@"HELO"!(,0;,0;0 1;,0) -> "HLAEO"!(,0;1 4;,0N;,0N;,0N) ,/ flatten the values into a list ,/"HLAEO"!(,0;1 4;,0N;,0N;,0N) -> 0 1 4 0N 0N 0N  • Can you add a short explanation? Aug 1 at 15:09 • @Jonah added a step-by-step walkthrough, it is a bit hard to explain in short ;) – ovs Aug 1 at 15:53 • Thanks vm! @ovs Aug 2 at 1:15 Haskell, 112 bytes import Data.List f(x:y,z)=[0^x|elem x z]:f(y,delete x z) f _=[] x?z|x==z=(0,0)|1>0=(x,z) ((f.unzip).).zipWith(?)  Try it online! Takes input as a list of integers on the range $$\[1,26]\$$. Outputs a list of [] for no match [1] for perfect match and [0] for imperfect match. This feels like it could be shorter. Having to import delete is costly. K (ngn/k), 5246 45 bytes {@[g;&[#'(=x)^\:&g;#'y]#'y:(=y)^\:&g:x=y;2+]}  Try it online! -1 byte from ngn (thanks!). Starting with a list of positions showing letters that do (green 1) and don't (grey 0) match in the secret and guess, amend it as follows: Ignoring the positions that are green, take the first n positions of each letter in the guess (where n is the fewer of the frequency of that letter in the secret versus in the guess), and add 2 to those positions (yellow). Python 3, 113 bytes lambda g,w:[g[i]-w[i]and~((g[:i]+[a-b or b for a,b in zip(w,g)][i:]).count(g[i])<w.count(g[i]))for i in range(5)]  Try it online! Uses -1 for gray, -2 for yellow and 0 for green. A further 3 bytes can be saved if -1 is replaced by False and -2 by True, but I dislike this output format, since False==0. Input is a list of charcodes JavaScript, 76b 72b 69b Solution found by @subzey and me while golfing the MiniWordle project. Thanks to @emanresu for the further improvements // a is the correct word (array of letters) // b is the guessed word (array of letters) a=>b=>b.map((s,i)=>s==a[i]?a[i]=0:s).map(s=>s?a[x=a.indexOf(s)]=~x:1)  Returns an array of 5 digits. 0 = black, 1 = green, anything else = yellow For example: f([..."TEETH"])([..."EERIE"]) == [-3, 1, 0, 0, 0] • The parentheses around a[x=a.indexOf(s)]=~x are unnecessary. Also, if you take input uppercase, you can replace s<"~" with s<{} for -1, and you can take input curried (a=>b=>...) for another -1. Feb 11 at 6:09 • thanks @emanresu! I'm gonna update my answer :) – xem Feb 11 at 15:45 Charcoal, 33 bytes ⭆θ⎇⁼ι§ηκ+§.-‹№Ｅ…θκ⁻λ§ημι№Ｅη⁻λ§θμι  Try it online! Link is to verbose version of code. Outputs + for exact match, - for near miss and . for mismatch. Explanation:  θ Guessed word ⭆ Map over letters ι Current letter ⁼ Equal to η Secret word § Indexed by κ Current index ⎇ If true then + Literal string + else .- Literal string .- § Indexed by № Count of ι Current letter in θ Guessed word … Truncated to length κ Current index Ｅ Map over letters λ Inner letter ⁻ Remove match of η Secret word § Indexed by μ Inner index ‹ Is less than № Count of ι Current letter in η Secret word Ｅ Map over letters λ Inner letter ⁻ Remove match of θ Guessed word § Indexed by μ Inner index Implicitly print  JavaScript (Node.js), 63 bytes s=>g=>g.map((u,i)=>s[i]-u&&s.some((v,j)=>g[j]==v|u-v?0:s[j]=1))  Try it online! Input array of code points, Output array of 0 = Green, true = Yellow, false = Unhighlighted. C (gcc), 125 122 117 bytes #define F for(i=5;i--;)g[i] i;f(s,g,p)char*s,*g,*p;{F==s[i]?s[i]=1,g[i]=2:0;F,(p=index(g,s[i]))?*p=3:0;F>3?g[i]=4:0;}  Try it online! Saved 5 bytes thanks to tsh!!! Inputs a secret and a guess as two strings. Returns the "highlights" as a list of $$\2\$$s, $$\3\$$s, and $$\4\$$s in the second input string. With $$\2\$$ for green, $$\3\$$ for yellow, and $$\4\$$ for unhighlighted. • A quick and dirty hack: #define F for(i=5;i--;)g[i], i;f(s,g,p)char*s,*g,*p;{F==s[i]?s[i]=1,g[i]=2:0;F,(p=index(g,s[i]))?*p=3:0;F>3?g[i]=4:0;} – tsh Jan 26 at 1:38 • @tsh Quick and dirty does it nicely - thanks! :D Jan 26 at 8:13 Julia 1.6, 72 bytes a\b=(* =.==;c=a*b;b[c]=a[c].=0;c-[sum(b[i]b[1:i])>sum(b[i]a) for i=1:5]) Attempt This Online! Fixed and improved version of mbeltagy's answer Needs version 1.6+ for (*) = (.==). 73 bytes in Julia 1.0 Inputs are vectors of characters, and the output is a vector with 1 for green, 0 for yellow, and -1 for uncolored J, 39 31 bytes =(+{.e.&(,.1#.%=%$$/@i.])=|"1,:  Try it online! -8 bytes thanks to Bubbler! The above is Bubbler's excellent golf of the "alternate idea" I included in my original post: (~:*[)(=+i.~e.&(,.1#.]=]\)&:>:]i.])~:*]  J, 37 bytes <@(~:*[)(_*])i.[} ::[~&.>/@,~~:<@*]  Try it online! • Input: 1-indexed integers representing index within the alphabet. • Output: boxed list with the following scheme: • 0 = green • _ = yellow • positive integers = gray idea • Turn perfect matches of both target and guess into 0 by multiplying by the "not-equal" vector ~:. • Starting with the guess as our "previous value", reduce the letters of the target as follows: • If target letter not found in guess, return guess unaltered • If target letter found, update the first matching index of the guess to infinity _. This both "marks" the yellow partial match and ensures the same index won't be found again. • If letter found is a 0 (ie, already marked as a perfect match), update it to 0 (ie, leave it unchanged). Ensures that the reduction only touches yellow matches. • @JonathanAllan Fixed, 7 months later! Jul 30 at 7:49 • Somehow I could golf your 2nd solution down to 31 bytes. The 5$/:@/: in the middle of the thought process is related to the progressive membership APL idiom, but it wasn't necessary in the end. Aug 1 at 23:24
• Damn Bubbler, that is some impressive golfing. Thanks! I will update the post with a proper explanation later. Aug 2 at 1:13

JavaScript (Node.js), 78 bytes

a=>b=>b.map(y=(x,i)=>x-(c=a[i])&&(y[c]=-~y[c]*2,x)).map((x,i)=>x&&!(y[x]>>=1))


Try it online!

Input charcode/[1,26], output true(no), 0(green), false(yellow)

JavaScript (Node.js), 82 bytes

a=>b=>b.map(y=(x,i)=>x!=(c=a[i])&&(y[c]=-~y[c],x)).map((x,i)=>x&&~~y[x]&&!!y[x]--)


Try it online!

0(no match), false(green), true(yellow)

• @Shaggy You may take input as a string, a list of character codes, or a list of alphabet indices (in [0,25] or [1,26])
– l4m2
Jan 26 at 4:43
• Hi @l4m2, congrats for your 82b answer, I realized it was better than the one I had implemented in my MiniWordle clone, so I used yours and put your name in the project's credits: xem.github.io/MiniWordle Cheers!
– xem
Feb 10 at 7:55

Python 3.8 (pre-release), 92 bytes

Returns a list of -1 for Green, False for Yellow and True for not highlighted.

lambda*r,c='':[-(a==b)or(c:=c+b).count(b)>sum(y!=x==b for x,y in zip(*r))for a,b in zip(*r)]


Try it online!

Jelly, 18 17 bytes

o@=
çW;œ-\⁻Ɲʋç@+=


Try it online!

-1 because there was a completely extraneous ɗ

Target on left, guess on right; 0 for gray, 1 for yellow, 2 for green.

Absolutely dreadful... There ought to be some sort of =þ-based solution, but I've had no luck yet.

o@=    Dyadic helper link: mask out exact matches
=    For each pair of corresponding elements, are they equal?
o@     Replace 0s with elements from the left argument, leaving 1s unchanged.

ç                Mask matches out of the target,
ç@      mask matches out of the guess,
ʋ        and with those on the left and right:
W;  \           Scan across the guess, starting with the masked target, by
œ-            multiset difference;
Ɲ         for each pair of adjacent scan results,
⁻          are they not equal?


Vyxalt, 2423 21 bytes

Z⁰¹-ḃ*∩÷\$£0o(¥nuøḞ£)¥


Try it Online!

or Run all the test cases (it took me way longer to write the code to run the test cases than the program itself lol)

Port of 05AB1E answer. Can probably be golfed down a ton.

-1 byte thanks to Aaroneous Miller

• 23 bytes Apr 19 at 20:59

Python 2, 95 bytes

lambda g,t:[not a or a in t and t.remove(a)for a in[a[a==b:]or t.remove(a)for a,b in zip(g,t)]]


Try it online!

Loops over the guess twice, while removing characters from the target. Direct matches are removed in the first loop, indirect matches in the second. Requires two lists as input. Outputs a list containing 5 elements: True for direct match, None for indirect match and False for no match.