Useful vectorization insights from Problem 61069. Clueless - Lord Ned in the Game Room with the Technical Computing Language

WANG Zi-Xiang on 5 Dec 2025 (Edited on 5 Dec 2025)
Latest activity Edit by Umar on 6 Dec 2025

The first round of the the Cody Contest 2025 is drawing to an end, and those who have tried to tackle Problem 61069. Clueless - Lord Ned in the Game Room with the Technical Computing Language probably didn’t think, like me initially, that a vectorized solution was feasible.
Indeed, the problem is difficult enough, so that the first solution is more easily drafted using lots of for loops and conditionals.
Yet studying in depth how to vectorize the solution and get rid of redundancies helped me uncover the deeper mechanics of the algorithm and see the problem in a new light, making it progressively appear simpler than on its first encounter.
Obstacles to overcome
Vectorization depends highly on the properties of the knowledge matrix, a 3D-matrix of size [n, 3, m] storing our current knowledge about the status for each card of each category for all players.
I remember that initially, I was intent on keeping close together these two operations: assigning a YES to a player for a given card and category, and consequently assigning NOs to all other players.
I did not want to set them apart. My fear was that, if you did not keep track and updated the knowledge matrix consistently, you might end up with a whole mess making it impossible to guess what’s in the envelope!
That seemed important because, as one gradually retrieves information from the turns and revisits them, one assigns more and more YESs and narrows down the possible candidates for the cards hidden in the envelope.
For example, @JKMSMKJ had successifully managed to combined those two instructions in one line (Solution 14889208), like this (here 0 encodes NO and 1 encodes YES):
allplayers = 1:(m+1);
K(card, category,:) = allplayers == player;
For some time, I thought that was the nicest way to express it, even though you had to handle the indivual card, category and player with lots of loops and conditionals.
Watching @JKMSMKJ’s repeated efforts to rewrite and improve his code showed me differents ways to arrange the same instructions. It appeared to me that there was indeed a way to vectorize the solution, if only we accept to separate the two distinct operations of assigning a value of YES and updating the knowledge matrix for consistency.
So let’s see how this can be done. We will use the following convention introduced by @Stefan Abendroth: NO = 0, MAYBE= 1, YES = values > 1. The reason for that choice is that it will greatly simplify computations, as it will become apparent later.
Initialisation
First, initialising a matrix of MAYBEs and adding in the information from our own cards is pretty straightforward:
K = ones(m,3,n);
K(:,:,pnum) = 0;
allcategories = 1:3;
for category = allcategories
K(yourcards{deck},deck,pnum) = 2; % = YES
end
The same thing can be done for the common cards considered as the (m+1)th player.
Next, we’d like to retrieve information for the turns and insert it into the matrix.
The 1rst column of the turn matrix gives us a vector of the players.
The 2nd to 4th columns conveniently give us a 3 column matrix of the values of the cards asked.
players = turns(:,1);
cards = turns(:,2:4);
result = turns(:,5);
Now suppose we have similar 3-column matrices of the exactsame size for the players and for the categories, such as:
categories =
1 2 3
1 2 3
1 2 3
1 2 3
1 2 3
1 2 3
1 ...
players =
5 5 5
6 6 6
1 1 1
2 2 2
6 6 6
4 ...
It would then be nice to be able to write something like:
K(cards, categories, players) = 0; % or 1 or 2 depending on the desired assignment according to result
Unfortunately that is not possible when we have multiple indexes that are not scalars.
A workaround is to use what is called linear indices, which are the indices of the matrix when considering a matrix as a very long 1-column vector, and which can be computed with the function sub2ind:
[categories, players] = meshgrid(1:3, turns(:,1));
sz = [n, 3, m];
ind = sub2ind(sz, cards, categories, players);
K(ind) = 0; % or 1 or 2 depending on the desired assignment according to result
Ensuring everybody else has a NO
Next, let’s see how to update the matrix. We now suppose the YESs have been correctly assigned into the matrix.
Wherever we’ve identified the location of a card (”a YES”), all other players must be assigned a NO. For each card, there can be only one YES across all players.
Because of that, that YES is the maximum value across all layers of the matrix K. Using the function max on K along it’s third dimension reduces the 3rd dimension to 1, yielding a 2d matrix. To locate it, we can then compare the value of that 2d matrix with all the layers of K.
maxcard = max(K,[],3); % returns a n-by-3 matrix
is_a_yes = K == maxcard;
K == maxcard compares maxcard with each layer of K, yielding a 3d matrix of logicals of the same size as K, where 1 indicates a YES and 0 indicates “not a YES”.
Ten years ago, we’d have needed to use the function bsxfun to perform that operation, but since then, Matrix Size Array Compatibility has been extended in MATLAB. Isn’t it nice?
Now, to transform any “MAYBE” (a 1) into a NO (a 0), while keeping the existing YESs, MAYBEs, and NOs unmodified, we need only need only multiply that matrix element-by-element with K !
%% Update knowledge matrix: if someone has a >1 ("YES"), everyone else must have a 0 ("NO")
maxcard = max(ans,[],3);
K = K .* (K == maxcard);
That expression can be read as “keep the value of K wherever K is equal to its max, but set 0 elsewhere”. If the maximum is a MAYBE, it will stay a MAYBE.
Getting one’s head around such an expression may take some getting used to. But such a one-liner is immensely powerful. Imagine that one day, the rules of the game change, or that this requirement is not useful any more (that happens all the time in real life), then we can very easily just comment out just that one line without impacting the rest of the program.
Confirming a player’s card hand when we determined (3n - ncards) they don’t have
After information was retrieved from the turns, we can examine each player’s hand and if we have narrowed a player’s cards to ncard possible candidates, excluding all others, then these must the cards that they hold. That means that their MAYBE cards becomes YESs.
Locating a player’s hand amounts to locating all the strictly positive values in the matrix:
playerhand = K(:,:,p);
player_complete = sum(playerhand(:)>0)) == ncards;
That operation can actually be vectorized along all players. Summing the matrix of logicals (K>0) along the first two dimensions yields a 1-by-1-by-(m+1) matrix, akin to a vector containing the number of card candidates for each player, which we can compare to ncards.
player_complete = sum(K>0, 1:2) == ncards;
We need to transform into YESs the MAYBEs of the players for which we have successfully deduced ncards, which can be written as a simple multiplication by 2:
K(:,:,player_complete) = 2 * K(:,:,player_complete)
The 0s (NOs) will remain 0s, the MAYBEs will become 2s, and the YESs will be multiplied too, but still stay YESs (>1).
But since 2 .^ 0 = 1 and 2 .^ 1 = 2, there’s an even nicer way to write that calculation:
K = K .* 2 .^ player_complete;
which reads like “we multiply K by 2 wherever a player’s hand is complete”. All thanks to Array Size Compatibility!
That expression is nicer because we need not explicitly assign the operation to the 3rd dimension of K. Suppose that one day, for whatever reason (performance optimisation or change of requirements), information about the players is not stored along the 3rd dimension any more, that code would NOT need to change, whereas K(:,:,player_complete) would need to be ajusted.
That’s how elegant MATLAB can be!
Checking whether a player’s hand is complete
What we checked previously is equivalent to checking that the number of NOs (the number of cards a player has not) was equal to 3*n - ncards.
What we didn’t do is check whether the sum of YESs if equal to ncards and then transform all MAYBEs for that player into NOs.
That will not be necessary because of the implementation of the next rule.
Because the information provided to play the game is assumed to be sufficient to guess the missing cards, it means that the YESs and NOs will gradually populate the matrix, so that any remaining MAYBE will be determined.
Identifying each category's missing card when (n-1) cards are known
Each category only has n cards, which means that once (n-1) cards are correctly located, the remaining card can only be a NO for everyone.
Because a card can only be in only one player’s hand, we can reuse the maximum of K across all players that we previously computed. It is a n-by-3 2d matrix where the values > 1 are the YESs. Using the function sum adds up all the YESs found for each category, yielding a vector of 3 values, containing the number of cards correctly located.
maxcard = max(K,[],3);
category_complete = sum(maxcard > 1) == n-1;
When a category is complete, the last remaining MAYBE should become a NO, without modifying the YESs. A clever way is to multiply the value by itself minus one:
K(:,category_complete,:) = K(:,category_complete,:) .* (K(:,category_complete,:) - 1)
which, using the same exponentiation technique as previously, can be nicely and compactly rewritten as:
K = K .* (K-1) .^ category_complete;
Because the YESs are > 1, we can even compute that more simply like this (as Stefan Abendforth put it in Solution 14900340):
K = K .* (category_complete < K);
Extracting the index of the missing cards
After looping several times to extract all possible information, the last thing that remains to be done is computing the values of the missings cards. They are the only NOs left in the knowledge matrix, and in the 2d matrix maxcard as well:
maxcard = max(K,[],3);
[sol,~] = find(maxcard == 0);
Conclusion
I previously mentioned being bothered by matrix indexing such as K(:,:,player) because it is code that seems fragile in case of change in the organisation of the matrix. Such an instruction would benefit from being "encapsulated" if the need arises.
One of my main concerns has always been writing maintainable MATLAB code, having worked in organisations where code piled up almost everyday, making it gradually more difficult and time-consuming to add and enhance functionalities if not properly managed.
On the one hand, elegant vectorization leads us to group things together and handle them uniformly and efficiently, “in batches”. On the other hand, “separation of concerns”, one of Software Development’s principles and good practices, would advise us to keep parts small and modular and that can take care of themselves on their own if possible, achieving higher abstraction.
How do we keep different requirements independent, so that they do not impact each other if any one of them needs to change? But how do we exploit vectorization extensively for performance? These two opposing forces is what makes developing modular and efficient MATLAB code a challenge that no other language faces in the same way, in my opinion.
Seeing the rules of the game as a sequence of multiplications applied to the matrix K simultaneously reduces code size and reveals a deeper aspect of the algorithm: because multiplication is commutative and associative, we can apply them in any order, and we could also see them as independant “operators” that we could apply elsewhere.
---
I hope those explanations can help you better appreciate the beauty of vectorization and make it seem less daunting.
There are many other strokes of inspiration that emerged from different solvers solving Problem 61069. Clueless - Lord Ned in the Game Room with the Technical Computing Language, and I am the first one to be amazed by them.
I wish to see more of such cooperative brilliance and sound emulation everywhere! Thanks so much to Cody Contest team for setting up such a fun and rewarding experience.
Umar
Umar on 6 Dec 2025 (Edited on 6 Dec 2025)

Hi @ WANG Zi-Xiang, Well put together. Again, I would like to thank you for sharing this detailed exploration of vectorization techniques for the Clueless problem (Reference: Problem 61069. Clueless - Lord Ned in the Game Room with the Technical Computing Language). Your analysis provides valuable insights into transforming loop-based code into efficient vectorized operations.

Your use of linear indexing with sub2ind demonstrates a practical approach to handling multidimensional indexing when direct subscript indexing is not possible. The MathWorks documentation confirms that sub2ind efficiently converts subscripts to linear indices, enabling batch operations on scattered matrix elements. Your technique of using meshgrid to create aligned index arrays before applying sub2ind is a standard pattern for such conversions.

The technique of leveraging implicit array expansion for consistency checks is particularly elegant. As noted in MATLAB's vectorization documentation, operations like K equals maxcard automatically expand singleton dimensions, eliminating the need for explicit replication. This makes code more maintainable since dimension-specific indexing becomes unnecessary.

Your approach to updating player hands and category completeness using element-wise multiplication with logical or exponential expressions showcases creative use of MATLAB's operators. The pattern K times 2 raised to the power of player_complete is concise and takes advantage of how scalar expansion works across array dimensions. This aligns with best practices for vectorized code that the documentation describes as appearing more like mathematical expressions.

The balance you identify between vectorization for performance and separation of concerns for maintainability is an important consideration. MATLAB documentation acknowledges that vectorization trades memory for speed by creating internal implicit loops. For complex algorithms, finding the right balance between readability and efficiency requires careful judgment about which operations benefit most from vectorization.

Your observation about using K times (K minus 1) raised to the power of category_complete to convert MAYBEs to NOs while preserving YESs is mathematically clever. This type of transformation demonstrates how understanding the data encoding scheme enables compact vectorized operations.

One minor consideration: while linear indexing via sub2ind works well for scattered access patterns, MATLAB documentation suggests that subscript indexing can sometimes outperform linear indexing due to internal optimizations, particularly when accessing contiguous blocks. Testing both approaches can reveal performance differences depending on the specific access pattern.

The broader lesson about viewing game rules as commutative and associative operations that can be applied in any order reveals deep algorithmic insight. This mathematical perspective enables more flexible and maintainable implementations.

Again,your contribution to the Cody Contest community by documenting these techniques helps others appreciate the elegance of well-crafted vectorized code and demonstrates the collaborative learning that makes such competitions valuable.