Word search problem solving

Some word searches might seem straightforward enough that you wouldn’t need a strategy in order to solve them. Other word searches are simply… puzzling! These word search strategies will help you to solve any word search puzzle.

Some of these strategies may be familiar to you, and some may work better for you than others. We’ll focus on how to scan the puzzle itself, and how to best use the word list. Once you have an in-depth understanding of these two strategies, learn how to make your own word searches to help you practice your solving technique.

Tips for Scanning the Word Search

When it comes to solving a word search, scanning the letters provides the most effective tool for finding the hidden words. But scanning without a strategy can lead to your eyes glazing over. Here are some effective scanning tips.

Scan each row.
By simply scanning each row from left to right you’ll find a fair amount of words. You can then scan each column from top to bottom to find the vertical words. especially if the word search has no diagonal words, you’ll already be pretty far along.

Seek out strange letters.
If you are using the word list right away, seek out words that have unique letters. Words with Q, X, J, or Z may be easier to find than searching for words by their vowels.

Focus on letters that stand out to you.
Similarly, if any particular letters stand out to you, start your search with words that contain those letters. Some people believe that round letters like O, Q, and D stand out more than others.

Search for letter pairs.
If any words contain double letters, scan the word search for two letters next to each other.

Check surrounding letters.
Once you found the first letter or a key letter in a word you’re searching for, check the letters around it to see if you should keep going. For example, if you have a word with Q in it, if there’s no U next to it, then you need to keep searching.

Scan with your finger.
To keep your focus within the puzzle, run your finger along each line. This way you won’t lose your place in the puzzle.

How Important is the Word List?

Many people base their word search solving strategy on the words in the word list. However, others feel the word list is hardly necessary. The importance of the word list depends on which solving strategies work best for you, but there are a few important uses of the word list.

First, the word list helps you to verify and confirm the words you are looking for. Once you find a word, circle or highlight it, then cross it off in the word list so you don’t search for something you already found. Furthermore, occasionally a word will sneak into a word search that is not on the list. You can mark it, but if you rely solely on scanning, you’ll end up with a bunch of “discovered” words and an incomplete puzzle!

The word list also helps you to see the bigger picture. Scanning the word search will help you to find those unique words, or may work better for detail-oriented people. By using the word list, you can view the puzzle from afar and see if that word jumps out at you.

Finally, the word list is important for words that have variations. For example, if you find the word “friend” in the puzzle, the word in the word list might actually be “friendship.” You’ll need the word list to verify that you’ve found the entire word.

Now that you have some strategies for solving word searches, why not try them out? Here’s a “Hard Word Puzzle” to get you started.

If you’d like a different kind of word search to test your skills, there’s no better way to ensure you have everything you want in a word search than to make your own.

Moving on to Making Word Searches

There are lots of word searches available for you to solve, starting right here at My Word Search. After solving quite a few, you’ll want to move on to making your own. That way you’ll have control over the difficulty of your word search, and can practice the word search strategies mentioned above.

When you use the word search maker at My Word Search, make these selections for maximum difficulty:

  • Select a “Gigantic” puzzle to maximize the potential for your words to hide.
  • Select the maximum amount of word directions so you’ll need to scan your word search forwards, backward, up, down, and diagonally.
  • Choose words for your word list with no double letters, or letter pairs, which will require you to use other strategies.

You can, of course, alter these selections to lower the difficulty as well. You can also change the font, font size, and letter spacing according to your desires. Making your word search with a theme might also alter the difficulty or offer extra enjoyment.

Once you’ve made your word search, share it with friends and family online or printed out to see if you can stump them. If you do, share this article with them so they can use the word search strategies you’ve learned!

Word searches can be a lot of fun, and by using these strategies, you’ll maximize your solving potential. You’ll also feel more confident in your ability to solve any word search puzzle. Try these strategies out today with a word search from My Word Search. If you have a strategy we didn’t list, or if you’ve made a particularly difficult puzzle to solve, share it in the comments section.

Kristen Seikaly used her artistic background, research skills, and love for the internet to launch her first blog, Operaversity. Now she uses the skills to connect teachers, parents, and game enthusiasts with Crossword Hobbyist and My Word Search. She studied music at the University of Michigan, and now lives in Philadelphia.

Frequently Asked Questions

What is a word search?

A word search is a puzzle where there are rows of letters placed in the shape of a square, and there are words written forwards, backwards, horizontal, vertical or diagonal. There will be a list of words for the player to look for and the goal of the player is to find those words hidden in the word search puzzle, and highlight them.

How do I choose the words to use in my word search?

Once you’ve picked a theme, choose words that have a variety of different lengths, difficulty levels and letters. You don’t need to worry about trying to fit the words together with each other because WordMint will do that for you!

How are word searches used in the classroom?

Word search games are an excellent tool for teachers, and an excellent resource for students. They help to encourage wider vocabulary, as well as testing cognitive abilities and pattern-finding skills.

Because the word search templates are completely custom, you can create suitable word searches for children in kindergarten, all the way up to college students.

Who is a word search suitable for?

One of the common word search faq’s is whether there is an age limit or what age kids can start doing word searches. The fantastic thing about word search exercises is, they are completely flexible for whatever age or reading level you need.

Word searches can use any word you like, big or small, so there are literally countless combinations that you can create for templates. It is easy to customise the template to the age or learning level of your students.

How do I create a word search template?

For the easiest word search templates, WordMint is the way to go!

Pre-made templates

For a quick an easy pre-made template, simply search through WordMint’s existing 500,000+ templates. With so many to choose from, you’re bound to find the right one for you!

Create your own from scratch

  • Log in to your account (it’s free to join!)
  • Head to ‘My Puzzles’
  • Click ‘Create New Puzzle’ and select ‘Word Search’
  • Select your layout, enter your title and your chosen words
  • That’s it! The template builder will create your word search template for you and you can save it to your account, export as a Word document or PDF and print!

How can I print my word search template?

All of our templates can be exported into Microsoft Word to easily print, or you can save your work as a PDF to print for the entire class. Your puzzles get saved into your account for easy access and printing in the future, so you don’t need to worry about saving them at work or at home!

Can I create a word search in other languages?

Word searches are a fantastic resource for students learning a foreign language as it tests their reading comprehension skills in a fun, engaging way.

We have full support for word search templates in Spanish, French and Japanese with diacritics including over 100,000 images.

The word-search puzzle is a string-search problem in which several words are to be located within an n ´ m array filled with letters. The following table shows a 30 ´ 30 array and some words that must be found in it.

Image 1

Each word must occur entirely in a row, a column or a diagonal, that is, it cannot be bent from one direction into another. Words can be placed forward or backward, but not scrambled. Furthermore, words can share characters. A brute-force approach is extremely inefficient: if the puzzle has n rows and m columns, and if the average length of the words to search for is q, then the time complexity would be nmq. For typical values of n = 30, m = 30, and q = 5, the time complexity would be 4500, which is much higher than 110.

If the fingerprints are the same (pFP == tFP), the actual match of the pattern against the target is attempted. A mismatching character causes the matching loop to be aborted, while a complete match makes the function to return with the position of the pattern in the target string. If the fingerprints are not equal, the fingerprint substring of the target being matched (tFP) is updated by considering the effect of “sliding” the pattern under the target one character position to the right. The beauty of the fingerprint approach is that words have the same fingerprint whether they are forward or backward in any direction.

The following function KRsearch, implements a variation on the “fingerprinting” method of Karp-Rabin (Sedgewick, Robert, Algorithms, 1990, pp. 289-291). Function fp is used to compute the initial fingerprint of the pattern (pFP) and of the corresponding prefix of the target (tFP). The fingerprint is simply the sum of the ASCII codes of the characters.

int KRsearch( char p[], char t[] )
{
   int i, j, k, m = strlen( p ), n = strlen( t ),
       pFP, tFP;
 
   pFP = fp( p, 0, m );   tFP = fp( t, 0, m );
 
   for ( i = 0; i <= n-m; ++i ) {
      if ( pFP == tFP ) {
         for ( j = 0, k = i; j < m; ++j, ++k )
            if ( p[ j ] != t[ k ] )
               break;
         if ( j == m )  return i;
      }
      else if ( i + m < n ) tFP += t[ i+m ] - t[ i ]; else break;
   }
   return -1;
}
 
int fp( char s[], int i, int m )
{
   int j, sum;
 
   sum = 0;
   for ( j = i; j < m; ++j )
      sum += s[ j ];
   return sum;
}

Assume that the puzzle is given in the text file wpuzzle.txt. The first line contains two numbers (n and m) separated by a blank space. Those numbers are the rows and columns of the puzzle and are followed by n lines of m characters each. Assume that the words to be found are given in the text file words.txt, one word per line. No assumptions should be made as to whether or not the words appear in the puzzle. The program must fill (and print) a solution array with the words in the position and direction in which they were found, as shown below for the example puzzle.

The global variables to be used and the main function are as follows.

#define maxN 50
#define maxM 50
 
char puzzle[ maxN ][ maxM ], 
     puzsol[ maxN ][ maxM ], 
       line[ maxM + 1 ];     
 int n,                      
     m,                      
     maxOFnm,                
     pFP;                    
 
void main()
{
   FILE *fp;
    int len;
 
   if ( LoadPuzzle() ) {
      ShowPuzzle();
      if ( (fp = fopen( "words.txt", "r" )) != NULL ) {
         while ( fgets( line, maxM + 1, fp ) != NULL ) {
            len = removeNL( line );
            FindWord( line, len );
         }
         fclose( fp );
         ShowSolution();
      }
   }
}

The trivial auxiliary functions are defined as follows.

int LoadPuzzle()
{
   FILE *fp;
    int ok = 0, i, j;
 
   if ( (fp = fopen( "wpuzzle.txt", "r" ))
        != NULL ) {
      fscanf( fp, "%d %dn", &n, &m );
      if (    0 < n && n <= maxN
           && 0 < m && m <= maxM ) {
         maxOFnm = n < m ? m: n;
         for ( i = 0; i < n; ++i ) {
            fgets( line, maxM+1, fp );
            removeNL( line );
            strcpy( &(puzzle[ i ][ 0 ]),
                    line );
         }
         fclose( fp );
         ok = 1;
      }
   }
   return ok;
}
 
int removeNL( char *str )
{
   int l = strlen( str ) - 1;
   str[ l ] = '';
   return l;
}
 
void ShowPuzzle()
{
   int i, j;
 
   printf( "Puzzle array: (%dx%d)n",
           n, m );
   for ( i = 0; i < n; ++i ) {
      for ( j = 0; j < m; ++j ) {
         printf( "%c ", puzzle[ i ][ j ] );
         puzsol[ i ][ j ] = ' ';
      }
      printf( "n" );
   }
}

void ShowSolution()
{
   int i, j;
 
   printf( "nWords found:n" );
   for ( i = 0; i < n; ++i ) {
      for ( j = 0; j < m; ++j )
         printf( "%c ",
                 puzsol[ i ][ j ] );
      printf( "n" );
   }
}

To find a word, conduct a search in the horizontal, vertical, and diagonal directions, stopping the search when the word is found in any direction. The diagonal directions involve a search either in the southeast (upper-left corner to lower-right corner) or in the southwest (upper-right corner to lower-left corner) directions.

void FindWord( char *w, int wLen )
{
   pFP = hvFP( w, 0, wLen, 1 );
   printf( "searching for '%s', fp = %d", w, pFP );
 
   if ( !( hKRsearch( w, wLen )         
           ||
           vKRsearch( w, wLen )         
           ||
           seKRsearch( w, wLen )        
           ||
           swKRsearch( w, wLen ) ) )    
      printf( " not" );
   printf( " foundn" );
}

The implementation of functions hKRsearch, vKRsearch, seKRsearch, and swKRsearch rely on adaptations of the basic KRsearch function to perform searches over a two-dimensional array. Function hvFP, to compute the fingerprint in the horizontal and vertical directions of the puzzle, is trivially used to obtain the fingerprint of a word as if it were in a horizontal direction.

Since the fingerprint of a word is the same whether the word occurs foward or backward in the puzzle, it is convenient to define a global structure in which the search functions can return the position of the array at which a word was found, and the word’s direction.

typedef enum { forw, back } FillDir; 
                                     
 
typedef struct { 
       int pos;  
   FillDir dir;  
} FIstr;

FIstr finfo;

The following figure shows typical searches in the vertical (column 3) and horizontal (row 1) directions.

Image 3

The computation of the initial fingerprint of the target and the actual search in these directions can be done in the same way. To reach the “next” character in the horizontal direction an increment of 1 is needed, while for the vertical direction we an increment of maxM is needed. This is the third argument to functions hvFP (horizontal/vertical fingerprint) and hvKRsearch (horizontal/ vertical Karp-Rabin search) defined below.

int hvFP( char s[], int i, int len, int d )
{
   int j, sum;
 
   sum = 0;
   for ( j = i; j < len; j += d )
      sum += s[ j ];
   return sum;
}
 
int hvKRsearch( char *p, char *t, int pLen, int tLen, int d )
{
   int i, j, k, tFP, pMaxIdx = pLen*d, tMaxIdx = (tLen-pLen)*d;
 
   tFP = hvFP( t, 0, pMaxIdx, d );
 
   for ( i = 0; i <= tMaxIdx; i += d ) {
      if ( pFP == tFP ) {
         for ( j = 0, k = i; j < pLen; ++j, k += d ) 
            if ( p[ j ] != t[ k ] )
               break;
         if ( j == pLen ) {
            finfo.pos = i;  finfo.dir = forw;  return 1;
         }
         else {
            for ( j = pLen-1, k = i; j >= 0; --j, k +=d ) 
               if ( p[ j ] != t[ k ] )
                  break;
            if ( j == -1 ) {
               finfo.pos = i;  finfo.dir = back;  return 1;
            }
         }
      }
      else
         tFP += t[ i + pMaxIdx ] - t[ i ];
   }
   return 0;
}

Filling a word in the solution depends on whether it was found forward or backward in the vertical or horizontal direction. This is taken care of by function hvFillWord.

void hvFillWord( char *psol, char *w, int wLen, int d )
{
   int i, j = finfo.pos;
 
   if ( finfo.dir == forw )
      for ( i = 0; i < wLen; ++i ) {
         psol[ j ] = w[ i ];
         j += d;
      }
   else 
      for ( i = wLen-1; i >= 0; --i ) {
         psol[ j ] = w[ i ];
         j += d;
      }
}

Horizontal and vertical searches are implemented by the two functions below.

int hKRsearch( char *w, int wLen )
{
   int i;

   for ( i = 0; i < n; ++i )
      if ( hvKRsearch( w, &(puzzle[ i ][ 0 ]), wLen, m, 1 ) ) {
         hvFillWord( &(puzsol[ i ][ 0 ]), w, wLen, 1 );
         break;
      }
   return i < n;
}
 
int vKRsearch( char *w, int wLen )
{
   int j;
 
   for ( j = 0; j < m; ++j )
      if ( hvKRsearch( w, &(puzzle[ 0 ][ j ]), wLen, n, maxM ) ) {
         hvFillWord( &(puzsol[ 0 ][ j ]), w, wLen, maxM );
         break;
      }
   return j < m;
}

Searches along the diagonals are performed in a similar way. The following figure shows typical searches.

Image 4

In the southeast direction, the increment must be maxM + 1 to reach the “next” character, while in the southwest direction, the increment must be maxM – 1. Because all functions use upper bounds for the indices, it is not a good idea to conduct searches in the north-east direction.

The functions to compute the initial fingerprint, and to search in the diagonal directions are defined below.

int dgFP( char s[], int i, int len, int d )
{
   int j, sum;
 
   sum = 0;
   for ( j = i; j < len; j += d + 1 )
      sum += s[ j ];
   return sum;
}
 
int dKRsearch( char *p, char *t, int pLen, int tLen, int d )
{
   int i, j, k, tFP,
       pMaxIdx = pLen*(d+1), tMaxIdx = (tLen-pLen)*(d+1);
 
   tFP = dgFP( t, 0, pMaxIdx, d );
 
   for ( i = 0; i <= tMaxIdx; i += d + 1 ) {
      if ( pFP == tFP ) {
         for ( j = 0, k = i; j < pLen; ++j, k += d + 1 ) 
            if ( p[ j ] != t[ k ] )
               break;
         if ( j == pLen ) {
            finfo.pos = i;
            finfo.dir = forw;
            return 1;
         }
         else {
            for ( j = pLen-1, k = i; j >= 0; --j, k +=d + 1 ) 
               if ( p[ j ] != t[ k ] )
                  break;
            if ( j == -1 ) {
               finfo.pos = i;
               finfo.dir = back;
               return 1;
            }
         }
      }
      else
         tFP += t[ i + pMaxIdx ] - t[ i ];
   }
   return 0;
}

The function to fill words in the diagonal directions is similar to the one to fill words in the horizontal and vertical directions.

void dgFillWord( char *psol, char *w, int wLen, int d )
{
   int i, j = finfo.pos;
 
   if ( finfo.dir == forw )
      for ( i = 0; i < wLen; ++i ) {
         psol[ j ] = w[ i ];
         j += d + 1;
      }
   else 
      for ( i = wLen-1; i >= 0; --i ) {
         psol[ j ] = w[ i ];
         j += d + 1;
      }
}

To find a word in the diagonals, start at the main diagonals, and search for as long as a diagonal contains at least as many characters as the word being sought.

int seKRsearch( char *w, int wLen )
{
   int i, tLen, found = 0;
 
   tLen = maxOFnm;
   for ( i = 0; wLen <= tLen; ++i ) {
      if ( dKRsearch( w, &(puzzle[ i ][ 0 ]), wLen, tLen, maxM ) ) {
         found = 1;
         dgFillWord( &(puzsol[ i ][ 0 ]), w, wLen, maxM );
         break;
      }
      --tLen;
   }
   if ( tLen < wLen ) {
      tLen = maxOFnm - 1;
      for ( i = 1; wLen <= tLen; ++i ) {
         if ( dKRsearch( w, &(puzzle[ 0 ][ i ]), wLen, tLen, maxM ) ) {
            found = 1;
            dgFillWord( &(puzsol[ 0 ][ i ]), w, wLen, maxM );
            break;
         }
         --tLen;
      }
   }
   return found;
}
 
int swKRsearch( char *w, int wLen )
{
   int i, tLen, found = 0;
 
   tLen = maxOFnm;
   for ( i = m-1; wLen <= tLen; --i ) {
      if ( dKRsearch( w, &(puzzle[ 0 ][ i ]), wLen, tLen, maxM-2 ) ) {
         found = 1;
         dgFillWord( &(puzsol[ 0 ][ i ]), w, wLen, maxM-2 );
         break;
      }
      --tLen;
   }
   if ( tLen < wLen ) {
      tLen = maxOFnm - 1;
      for ( i = 1; wLen <= tLen; ++i ) {
         if ( dKRsearch( w, &(puzzle[ i ][ m-1 ]), wLen, tLen, maxM-2 ) ) {
            found = 1;
            dgFillWord( &(puzsol[ i ][ m-1 ]), w, wLen, maxM-2 );
            break;
         }
         --tLen;
      }
   }
   return found;
}

This member has not yet provided a Biography. Assume it’s interesting and varied, and probably something to do with programming.

Last updated: August 20, 2019

This implementation of Word Search was, in most part, an experiment—to observe how I utilize Python to try and solve the problem of implementing a basic word search solving algorithm.

Table of contents

  • What is Word Search?
  • How and where do we start?
  • Which tool do we use?
  • Python installation
    • Windows
    • Linux and Unix
  • Pen and paper
  • Onto the code!
    • Implementing the algorithm
      • matrixify
      • coord_char
      • convert_to_word
      • find_base_match
      • matched_neighbors
      • complete_line
      • complete_match
      • find_matches
      • wordsearch
  • Closing remarks

What is Word Search?

Word search is a puzzle we usually see in newspapers, and in some magazines, located along the crossword puzzles. They can be located sometimes in bookstores, around the trivia area, as a standalone puzzle book, in which the sole content is a grid of characters and a set of words per page.

How a traditional word search puzzle works is, for a given grid of different characters, you have to find the hidden words inside the grid. The word could be oriented vertically, horizontally, diagonally, and also inversely in the previously mentioned directions. After all the words are found, the remaining letters of the grid expose a secret message.

In some word search puzzles, a limitation exists in the length of the hidden words, in that it should contain more than 2 characters, and the grid should have a fixed size of 10 × 10, or an equivalent length and width proportion (which this python implementation doesn’t have).

How and where do we start?

Before going deeper into the computer side of the algorithm, let’s first clarify how we tend to solve a word search puzzle:

  • We look at a hidden word and its first letter then we proceed to look for the first letter inside the grid of letters.
  • Once we successfully find the first letter of the hidden word inside the grid, we then check the neighboring characters of that successful match and check whether the second letter of our word matches any of the neighbors of the successful match.
  • After confirming a successful match for the second letter of the hidden word through its neighbors, we proceed to a much narrower step. After the successful matching of the second letter of the word in the successful second match’s neighbors, we then follow-through to a straight line from there, hoping to get a third match (and so on) of the hidden word’s letters.

Which tool do we use?

To realize this series of steps in solving a word search puzzle, we will utilize a programming language known for having a syntax similar to pseudo-code—Python.

There are two main versions of Python—versions 2.x and 3.x. For this project, we would be utilizing version 2.7.

To make this run under Python 3.X, replace all instances of xrange with range.

Python installation

For the installation part, we’ll be covering installation instructions for both Windows, Unix, and Linux.

Windows

First, determine whether you’re running a 32- or 64-bit operating system. To do that, click Start, right-click Computer, then click Properties. You should see whether you’re running on 32-bit or 64-bit under System type. If you’re running on 32-bit, click on this link then start the download; if you’re on 64-bit, click this one. Again, take note that we will be utilizing version 2.7 of Python.

Linux and Unix

Download this file then extract. After extraction, go inside the extracted directory then run the following:

$ ./configure
$ make
$ make install

In Linux/Unix, to make sure that we can actually run Python when we enter the python command in a terminal, let’s make sure that the installed Python files can be located by the system.

Type the following, then press Enter on your corresponding shell:

Bash, Sh, Zsh, Ash, or Ksh:

$ export PATH="$PATH:/usr/local/bin/python"

Csh or Tcsh:

$ setenv PATH "$PATH:/usr/local/bin/python"

Pen and paper

As problems go in software development or in programming in general, it is better to tackle the problem with a clear head—going at it with the problem statement and constraints clear in our minds. What we are going to do first is to outline the initial crucial steps in a word search puzzle.

First, write the word dog, then on the space immediately below it, draw a grid of characters on the paper, like the following:

dog

d o g g
o o g o
g o g d

To start the hunt, we look at the first letter of the word dog, which is the letter d. If, somehow, the first letter of dog doesn’t exist within the grid, it means that we won’t be able to find the word in it! If we successfully find a character match for the first letter of dog, we then proceed to look at the second letter of dog. This time, we are now restricted to look around among the adjacent letters of the first letter match. If the second letter of dog can’t be located around the adjacent letters of d inside the grid, this means that we have to proceed to the next occurrence of the letter d inside the grid.

If we find a successful match around the adjacent letters of the next occurrence of d inside the grid, then the next steps are literally straightforward. For example:

      *
      o
      d

In the previous grid, the first letter d matched on the corner of the grid, and the word’s second letter o which is adjacent to d, also successfully matched. If that’s the case, the next location in the grid to check for the subsequent matches of the remaining letters of the word dog, will now be in a straight line with the direction drawn from the first letter to the second letter. In this case, we will check the letter directly above o for the third letter of the word dog, which is g. If instead of the asterisk, the grid showed:

      d
      o
      d

This means that we don’t have a match, and we should be going to the next occurrence of the first letter, inside the grid. If the asterisk is replaced by the correct missing letter:

      g
      o
      d

We have a match! However, for our version of word search, we will not stop there. Instead, we will count for all the adjacent letters of the letter d, then look for the matches of the letter o! For example, if we are presented with the following grid:

  g   g
    o o
      d

Then so far, for the word dog, we found 2 matches! After all the neighbors of the letter d have been checked for a possible match, we then move to the next occurrence of the letter in the grid.

Onto the code!

With the basic algorithm in mind, we can now start implementing the algorithm from the previous section.

Implementing the algorithm

matrixify
def matrixify(grid, separator='n'):
    return grid.split(separator)

The purpose of this function is to return a list whose elements are lines of string. This provides us the ability to index individual elements of the grid through accessing them by their row and column indices:

>>> grid = 'dogg oogo gogd'
>>> matrix = matrixify(grid, ' ')
['dogg', 'oogo', 'gogd']
>>> matrix[1][2]
'g'
coord_char
def coord_char(coord, matrix):
    row_index, column_index = coord
    return matrix[row_index][column_index]

Given a coordinate ((row_index, column_index) structure) and the matrix where this coordinate is supposedly located in, this function returns the element located at that row and column:

>>> matrix
['dogg', 'oogo', 'gogd']
>>> coord_char((0, 2), matrix)
'g'
convert_to_word
def convert_to_word(coord_matrix, matrix):
    return ''.join([coord_char(coord, matrix)
                   for coord in coord_matrix])

This function will run through a list of coordinates through a for loop and gets the single length strings using coord_char:

>>> [coord_char(coord, matrix) for coord in [(0, 0),(0, 1),(0, 2)]]
['d', 'o', 'g']

and then uses the join() method of strings to return one single string. The '' before the join() method is the separator to use in between the strings, but in our case, we want one single word so we used an empty string separator.

find_base_match
def find_base_match(char, matrix):

    base_matches = [(row_index, column_index)
                    for row_index, row in enumerate(matrix)
                    for column_index, column in enumerate(row)
                    if char == column]

    return base_matches

The value of base_matches above is computed by a list comprehension. A list comprehension is just another way of constructing a list, albeit a more concise one. The above list comprehension is roughly equivalent to the following:

base_matches = []

for row_index, row in enumerate(matrix):
    for column_index, column in enumerate(row):
        if char == column:
            base_matches.append((row_index, column_index))

I used the enumerate() function because it appends a counter to an iterable, and that is handy because the counter’s value could correspond to either the row or column indices of the matrix!

To show that the above code indeed scrolls through the individual characters of grid, let’s modify the body of our for loop in order to display the characters and their corresponding coordinates:

>>> for row_index, row in enumerate(matrix):
...    for column_index, column in enumerate(row):
...        print column, (row_index, column_index)
...
d (0, 0)
o (0, 1)
g (0, 2)
g (0, 3)
o (1, 0)
o (1, 1)
g (1, 2)
o (1, 3)
g (2, 0)
o (2, 1)
g (2, 2)
d (2, 3)

Giving our function find_base_match the arguments d and grid, respectively, we get the following:

>>> find_base_match('d', grid)
[(0, 0), (2, 3)]

As you can see from the previous for loop output, the coordinates output by our function are indeed the coordinates where the character d matched!

By calling this function, we can determine whether or not to continue with the further steps. If we deliberately give find_base_match a character that is not inside grid, like c:

>>> find_base_match('c', grid)
[]

The function returns an empty list! This means, that inside the encompassing function that will call find_base_match, one of the conditions could be:

if not find_base_match(char, grid):
    pass
matched_neighbors
def matched_neighbors(coord, char, matrix, row_length, col_length):
    row_num, col_num = coord
    neighbors_coords = [(row, column)
                        for row in xrange(row_num - 1,
                                          row_num + 2)
                        for column in xrange(col_num - 1,
                                             col_num + 2)
                        if row_length > row >= 0
                        and col_length > column >= 0
                        and coord_char((row,column),matrix) == char
                        and not (row, column) == coord]

    return neighbors_coords

This function finds the adjacent coordinates of the given coordinate, wherein the character of that adjacent coordinate matches the char argument!

Inside neighbors_coords, we’re trying to create a list of all the coordinates adjacent the one we gave, but with some conditions to further filter the resulting coordinate:

[(row, column)
  for row in xrange(row_num - 1,
                    row_num + 2)
  for column in xrange(col_num - 1,
                       col_num + 2)
  if row_length > row >= 0
  and col_length > column >= 0
  and coord_char((row, column),matrix) == char
  and not (row, column) == coord]

In the above code snippet, we are creating a list of adjacent coordinates (through (row, column)). Because we want to get the immediate neighbors of a certain coordinate, we deduct 1 from our starting range then add 2 to our end range, so that, if given a row of 0, we will be iterating through xrange(-1, 2). Remember that the range() and xrange() functions is not inclusive of the end range, which means that it doesn’t include the end range in the iteration (hence, the 2 that we add at the end range, not only 1):

>>> list(xrange(-1, 2))
[-1, 0, 1]

We do the same to the column variable, then later, we filter the contents of the final list through an if clause inside the list comprehension. We do that because we don’t want this function to return coordinates that are out of bounds of the matrix.

To further hit the nail in the coffin, we also give this function a character as its second argument. That is because we want to further filter the resulting coordinate. We only want a coordinate whose string equivalent matches the second character argument that we give the function!

If we want to get the neighbors of the coordinate (0, 0), whose adjacent character in the matrix should be c, call this function with (0, 0) as the first argument, the string c as the second, the matrix itself, and the matrix’s row length and column length, respectively:

>>> matched_neighbors((0, 0), 'c', matrix, 4, 3)
[]

Notice that it returns an empty list, because in the neighbors of the coordinate (0, 0), there is no coordinate in there that has the string c as its string equivalent!

If we replace c with a:

>>> matched_neighbors((0, 0), 'a', matrix, 4, 3)
[(0, 1), (1, 0), (1, 1)]

This function returns a list of the adjacent coordinates that match the given character.

complete_line
def complete_line(base_coord, targ_coord, word_len, row_length,
                  col_len):
    if word_len == 2:
        return base_coord, targ_coord

    line = [base_coord, targ_coord]
    diff_1, diff_2 = targ_coord[0] - base_coord[0],
                     targ_coord[1] - base_coord[1]

    for _ in xrange(word_len - 2):
        line += [(line[-1][0] + diff_1, line[-1][1] + diff_2)]

    if  0 <= line[-1][0] < row_length
        and 0 <= line[-1][1] < col_len:
        return line

    return []

We are now at the stage where functions seem a bit hairier to comprehend! I will attempt to discuss the thoughts I had before creating this function.

In the Pen and paper section, after matching the first and second letters of the word inside the matrix, I mentioned that the next matching steps become narrower. It becomes narrower in the sense that, after matching the first and second letters of the word, the only thing you need to do after that is to go straight in the direction that the first and second letters created.

For example:


      o
      d

In the above grid, once the letters d and o are found, one only need to go straight in a line from the first letter d to the second letter o, then take the direction that d took to get to o. In this case, we go upwards of o to check for the third letter match:

      * <- Check this next.
      o
      d

Another example:


  o
d

The direction that the above matches create is north-east. This means that we have to check the place north-east of ‘o’:

    * <- This one.
  o
d

With that being said, I wanted a function to give me all the coordinates forming a straight line, when given two coordinates.

The first problem I had to solve was—Given two coordinates, how do I compute the coordinate of the third one, which will later form a straight line in the matrix?

To solve this problem, I tried plotting all the expected goal coordinates, if for example, the first coordinate match is (1, 1) and the second coordinate match is (0, 0):

first     (1, 1) (1, 1) (1, 1) (1, 1) (1, 1) (1, 1) (1, 1) (1, 1)
second    (0, 0) (0, 1) (0, 2) (1, 2) (2, 2) (2, 1) (2, 0) (1, 0)
expected (-1,-1)(-1, 1)(-1, 3) (1, 3) (3, 3) (3, 1) (3,-1) (1,-1)

While looking at the above plot, an idea came into my mind. What I wanted to get was the amount of step needed to go from the second coordinate to the third. In hopes of achieving that, I tried subtracting the row and column values of the first from the second:

second    (0, 0) (0, 1) (0, 2) (1, 2) (2, 2) (2, 1) (2, 0) (1, 0)
first     (1, 1) (1, 1) (1, 1) (1, 1) (1, 1) (1, 1) (1, 1) (1, 1)
diff     (-1,-1)(-1, 0)(-1, 1) (0, 1) (1, 1) (1, 0) (1,-1) (0,-1)

After that, I tried adding the values of the diff row to the values of second:

second    (0, 0) (0, 1) (0, 2) (1, 2) (2, 2) (2, 1) (2, 0) (1, 0)
diff     (-1,-1)(-1, 0)(-1, 1) (0, 1) (1, 1) (1, 0) (1,-1) (0,-1)
sum      (-1,-1)(-1, 1)(-1, 3) (1, 3) (3, 3) (3, 1) (3,-1) (1,-1)

If you look closely, the values of the sum row match those of the expected row! To summarize, I get the difference by subtracting values of the first coordinate from the values of the second coordinate, then I add the difference to the second coordinate to arrive at the expected third!

Now, back to the function:

def complete_line(base_coord, targ_coord, word_len, row_length,
                  col_len):
    if word_len == 2:
        return base_coord, targ_coord

    line = [base_coord, targ_coord]
    diff_1, diff_2 = targ_coord[0] - base_coord[0],
                     targ_coord[1] - base_coord[1]

    for _ in xrange(word_len - 2):
        line += [(line[-1][0] + diff_1, line[-1][1] + diff_2)]

    if  0 <= line[-1][0] <= row_length
        and 0 <= line[-1][1] <= col_len:
        return line

    return []

For this function, I passed the length of the word as an argument for two main reasons—to check for words with a length of two, and for the length of the final list output. We check for double length words because with words that have lengths of 2, we no longer need to compute for a third coordinate because the word only needs two coordinates to be complete.

For the second reason, this serves as the quirk of my algorithm. Instead of checking the third coordinate for a match of the third character (and the subsequent ones), I instead create a list of coordinates, forming a straight line in the matrix, whose length is equal to the length of the word.

I first create the line variable which already contains the coordinates of the first match and the second match of the word. After that, I get the difference of the second coordinates values and the first. Finally, I create a for loop whose loop count is the length of the word minus 2 (because line already has two values inside). Inside the loop, I append to the line list variable a new coordinate by getting line’s last variable values then adding the difference of the second and first match coordinates.

Finally, to make sure that the created coordinate list can be found inside the matrix, I check the last coordinate of the line variable if it’s within the bounds of the matrix. If it is, I return the newly created coordinate list, and if not, I simply return an empty list.

Let’s say we want a complete line when given coordinate matches (0, 0) and (1, 1), and the length of our word is 3:

>>> core.complete_line((0, 0), (1, 1), 3, 4, 3)
[(0, 0), (1, 1), (2, 2)]

If we give the function a word length of 4:

>>> core.complete_line((0, 0), (1, 1), 4, 4, 3)
[]

it returns an empty list because the last coordinate of the created list went out of bounds.

complete_match
def complete_match(word, matrix, base_match, word_len, row_len,
                   col_len):
    new = (complete_line(base, n, word_len, row_len, col_len)
           for base in base_match
           for n in matched_neighbors(base, word[1], matrix,
                                      row_len, col_len))

    return [ero for ero in new
            if convert_to_word(ero, matrix) == word]

This is the complete_line function on steroids. The goal of this function is to apply complete_line to all the neighbors of the first match. After that, it creates a lists of coordinates whose word equivalent is the same as the word we’re trying to look for inside the matrix.

For the value of the new variable, I utilize a generator comprehension. These are like list comprehensions, except, they release their values one by one, only upon request, in contrast to list comprehensions which return all the contents of the list in one go.

To accomplish the application of complete_line to all the neighbors of the first match, I iterate through all the first matches:

for base in base_match

then inside that for loop, I iterate through all the neighbors that matched_neighbors gave us:

for n in matched_neighbors(base, word[1], matrix, row_len, col_len)

I then put the following statement in the first part of the generator comprehension:

complete_line(base, n, word_len, row_len, column_len)

The above generator comprehension is roughly equivalent to:

for base in base_match:
    for n in matched_neighbors(base, word[1], matrix, row_len,
                               col_len):
        yield complete_line(base, n, word_len, row_len, col_len)

After the creation of the new variable, we now start going through its values one by one:

[ero for ero in new if convert_to_word(ero, matrix) == word]

This list comprehension above will filter the new and the resulting list will only contain coordinates that, when converted to its word counterpart, match the original word we wanted to find.

Attempting to find the word dog inside our matrix returns a list of lists containing matched coordinates:

>>> base_match = find_base_match('dog'[0], matrix)
>>> core.complete_match('dog', matrix, base_match, 3, 3, 4)
[[(0, 0), (0, 1), (0, 2)], [(0, 0), (1, 0), (2, 0)],
[(0, 0), (1, 1), (2, 2)], [(2, 3), (1, 3), (0, 3)]]
find_matches
def find_matches(word, grid, separator='n'):
    word_len = len(word)
    matrix = matrixify(grid, separator)
    row_len, column_len = len(matrix), len(matrix[0])
    base_matches = find_base_match(word[0], matrix)

    if column_len < word_len > row_len or not base_matches:
        return []
    elif word_len == 1:
        return base_matches

    return complete_match(word, matrix, base_matches, word_len,
                          row_len, column_len)

This function will serve as the helper of our main function. Its goal is to output a list containing the coordinates of all the possible matches of word inside grid. For general purposes, I defined four variables:

  • The word_len variable whose value is the length of the word argument, which will generally be useful throughout the script
  • The matrix variable whose value we get through giving grid to our matrixify function, which will allow us to later be able to index contents of the matrix through its row and column indices.
  • The row_len and the column_len variable of matrix
  • base_matches which contain the coordinates of all the first letter matches of word

After the variables, we will do some sanity checks:

if column_len < word_len > row_len or not base_matches:
        return []
elif word_len == 1:
        return base_matches

The above if elif statement will check if the length of word is longer than both the column_len and row_len and also checks if base_matches returns an empty list. If that condition is not satisfied, it means that word can fit inside the matrix, and base_matches found a match! However, if the length of word is 1, we simply return base_matches.

If the word is longer than 1, we then pass the local variables to complete_match for further processing.

Given dog, the string chain dogg oogo gogd, and the ' ' separator as arguments:

>>> find_matches('dog', 'dogg oogo gogd', ' ')
[[(0, 0), (0, 1), (0, 2)], [(0, 0), (1, 0), (2, 0)],
[(0, 0), (1, 1), (2, 2)], [(2, 3), (1, 3), (0, 3)]]

Voila! This is the list, which contain lists of coordinates where the word dog matched inside dogg oogo gogd!

wordsearch
def wordsearch(word, string_grid, separator='n'):
    return len(find_matches(word, string_grid, separator))

This function simply returns the number of matches of running

find_matches(word, string_grid, separator='n'):
>>> wordsearch('dog', 'dogg oogo gogd', ' ')
4

There are 4 matches of dog inside dogg oogo gogd!

Closing remarks

Remember, it’s never a bad idea to go back to using pen and paper to solve programming problems. Sometimes, we express ideas better using our bare hands, and to top it off, a good ol’ break from the monitor and from the walls of code could just be what you need for a breakthrough—just like when I got stuck thinking about how I should implement my complete_line function!


Понравилась статья? Поделить с друзьями:
  • Word search picture puzzles
  • Word search phone numbers
  • Word search pet words
  • Word search one word or two
  • Word search on time words