Av rating:
Total votes: 27
Total comments: 20


Phil Factor
The SQL of Scrabble and Rapping
25 December 2009

In which Phil decides to use a table consisting of all the common words in English to explore ways of cheating at Scrabble and writing doggerel using SQL Server. He then issues a SQL challenge.

Preamble

Sometimes, when you tackle a different problem in SQL, one can hit on techniques that come in handy in all sorts of other contexts.  This is the theme of this article, which uses a bank of all common words of the English language. 

Cheating at Scrabble

 I was playing Scrabble the other day and faced, as usual with an impossible hand. Scrabble is, as you probably know, a game where you pick tiles from a sack, and each tile has a letter written on it. You are allowed up to seven tiles at a time, and you have to place these, made up into words, on a game board marked with a 15-by-15 grid. The words are formed across and down in crossword fashion, and must be real words in common use. Each letter is scored individually, but the score is boosted by special squares on the board that give you double, or triple, word, or letter, scores.

It occurred to me, as it must have done to many others, that one could cheat at this game with a surreptitious iPhone or iPod and a simple word bank.

If you based this application on SQL Server, using a simple HTML interface, it would be easy to find all the words that could be made up from your seven tiles. Because you will need to link in with another word, that will come to eight letters. In some denser games, even more than eight-letter words are made as more existing words are crossed.

The first exercise will be to find all the permutation of the characters in your hand. Actually, if you are being subtle, you can restrict yourself to a subset, since only a small number of the combination of vowels and consonants are actually allowed in English. We won’t be subtle here: we’ll use the brute-force attack to the problem. The simplest way is to do a series of joins to a table of letters, but I’ll try a more flexible approach that uses a variation of the card-sharper’s shuffle instead. To do this, you will firstly need a number table. This number table is used frequently in SQL and you may already have one in your test database.  If you haven’t, this stored procedure will deal with the task of creating and populating the number table.

IF EXISTS ( SELECT  'found'

            FROM    sys.objects

            WHERE   name='MaybeBuildNumberTable' )

    DROP PROCEDURE MaybeBuildNumberTable

go

CREATE PROCEDURE [dbo].[MaybeBuildNumberTable]

AS

BEGIN

SET NOCOUNT ON

IF NOT EXISTS ( SELECT  *

                FROM    dbo.sysobjects

                WHERE   id=OBJECT_ID(N'[dbo].[Numbers]')

                        AND OBJECTPROPERTY(id, N'IsUserTable')=1 )

  BEGIN

    CREATE TABLE [dbo].[Numbers]

        (

         [number] [int],

         CONSTRAINT [Index_Numbers] PRIMARY KEY CLUSTERED ([number] ASC)

            ON [PRIMARY]

        )

    ON  [PRIMARY]

  END

IF NOT EXISTS(SELECT 1 FROM numbers WHERE number=99999)

      BEGIN

      TRUNCATE TABLE numbers

;WITH Digits(i)

AS (SELECT i

   FROM (VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (0)) AS X(i))

INSERT INTO numbers(number)

SELECT (D5.i*100000 + D4.i*10000 + D3.i * 1000 + D2.i * 100

                                                            + D1.i * 10 + D0.i + 1) AS seq

    FROM Digits AS D0, Digits AS D1, Digits AS D2, Digits AS D3,

                                                            Digits AS D4, Digits AS D5

    END

   

END

go

Execute MaybeBuildNumberTable

With these basics out of the way, we can now create the Table-Valued Function that returns the permutations in sequence of the characters you give it. This will work with any ASCII character and can be altered to deal with unicode, of course. All we are doing here is creating an empty table of the right size and then filling it with all the permutations of the characters you supply to the function. Normally, permutations will be done with a series of self-joins to a table with all the characters, one per row, but here we want something that will be useful when you do not know the length of the string in advance. (Permutations are great for doing many of the ‘graph’ problems, such as finding the shortest route, network routing, or  time-tabling)

 

if exists (select 'found' from sys.objects where name ='PermutationsOf')

      drop function PermutationsOf

go

CREATE FUNCTION PermutationsOf (@String VARCHAR(10))

/**

summary:  >

  returns a simple table of all the permutations of a string of up to

  eight characters. (one can do more if you are prepared to wait a bit)

  The algorithm is based on the idea of a cunjurors shuffle which

  guarantees to return the pack to its original order. In this case,

  it shuffles the string to every combination.

  It uses the 'quirky update' in order to do the shuffle.

example:

     - code:    --test for correctness

                        Select count(*),string

                          from dbo.PermutationsOf ('12345678')

                          group by string having count(*)>1

     - code:    Select * from dbo.PermutationsOf ('physical')

     - code:    Select * from dbo.PermutationsOf (null)

returns:      a table listing all the alternative permutations of the

              letters. If you gibe a string like '111' then all

              permutations will be the same

**/

RETURNS  @alternatives TABLE

    (

     number INT primary key,

     String VARCHAR(10)

    )

AS

BEGIN

DECLARE @LenString INT

DECLARE @Iterations INT

SELECT  @LenString=LEN(@String),

            @iterations=CASE @Lenstring

                      WHEN 1 THEN 1

                      WHEN 2 THEN 2

                      WHEN 3 THEN 6

                      WHEN 4 THEN 24

                      WHEN 5 THEN 120

                      WHEN 6 THEN 720

                      WHEN 7 THEN 5040

                      WHEN 8 THEN 40320

                      ELSE NULL

                    END

 

IF @iterations IS NULL

      BEGIN

 

      INSERT INTO @alternatives ( number,string)

            SELECT 1,'Sorry' UNION

            SELECT 2,'String' UNION

            SELECT 3,'wrong' UNION

            SELECT 4,'length'

            RETURN

   END

     

INSERT INTO @alternatives (Number, string)

    SELECT  number,

            ''

    FROM    numbers

    WHERE   number<=@iterations

UPDATE  @alternatives --progressively shuffle the string

SET @string=string

      =CASE WHEN number=0 THEN @string

        WHEN number%5040=0

        THEN SUBSTRING(@string, 8, 1)+SUBSTRING(@string, 7, 1)

             +SUBSTRING(@string, 6, 1)+SUBSTRING(@string, 5, 1)

             +SUBSTRING(@string, 4, 1)+SUBSTRING(@string, 2, 1)

             +LEFT(@string, 1)+SUBSTRING(@string, 3, 1)

             +SUBSTRING(@string, 9, @lenstring-4)

        WHEN number%720=0

        THEN SUBSTRING(@string, 7, 1)+SUBSTRING(@string, 6, 1)

             +SUBSTRING(@string, 5, 1)+SUBSTRING(@string, 4, 1)

             +SUBSTRING(@string, 2, 1)+LEFT(@string, 1)

             +SUBSTRING(@string, 3, 1)+SUBSTRING(@string, 8, @lenstring-4)

        WHEN number%120=0

        THEN SUBSTRING(@string, 6, 1)+SUBSTRING(@string, 5, 1)

             +SUBSTRING(@string, 4, 1)+SUBSTRING(@string, 2, 1)

             +LEFT(@string, 1)+SUBSTRING(@string, 3, 1)

             +SUBSTRING(@string, 7, @lenstring-4)

        WHEN number%24=0

        THEN SUBSTRING(@string, 5, 1)+SUBSTRING(@string, 4, 1)

             +SUBSTRING(@string, 2, 1)+LEFT(@string, 1)

             +SUBSTRING(@string, 3, 1)+SUBSTRING(@string, 6, @lenstring-4)

        WHEN number%6=0

        THEN SUBSTRING(@string, 4, 1)+SUBSTRING(@string, 2, 1)

             +LEFT(@string, 1)+SUBSTRING(@string, 3, 1)

             +SUBSTRING(@string, 5, @lenstring-4)

        WHEN number%3 IN (1, 2)

        THEN SUBSTRING(@string, 2, 2)+LEFT(@String, 1)

             +RIGHT(@String, @lenstring-3)

        ELSE SUBSTRING(@string, 2, 1)+LEFT(@string, 1)

             +RIGHT(@String, @lenstring-2)

       END

  RETURN

 

END

GO 

Now we have this working, we’ll need all the common words of English. We first create the table of words and stock it from our word bank that I provide in the speech-bubble at the head of the article.

 IF EXISTS ( SELECT  'found'

            FROM    information_Schema.tables

            WHERE   Table_Name='AllWords' )

    DROP TABLE AllWords

go

 

CREATE TABLE AllWords

    (

     String VARCHAR(25) NOT NULL,

     CONSTRAINT [PK_CommonWords] PRIMARY KEY CLUSTERED ([String] ASC)

        ON [PRIMARY]

    )

ON  [PRIMARY]

 

go

/*

now we'll insert all the words into our dictionary  table */

DECLARE @commonwords TABLE (Word VARCHAR(255))

INSERT  INTO @CommonWords

        EXECUTE master..xp_cmdshell 'type wordlist.txt'

      

INSERT  INTO AllWords (String)

        SELECT  left(word,25)

        FROM    @commonwords

        WHERE   word IS NOT NULL

       

go

You would have to alter the path to the word list before using this, of course.  I usually find that my seven tiles consist mostly of vowels so I couldn’t resist extracting lists of handy words with at least four consecutive vowels, just to make sure that everything was loaded properly

Declare @list Varchar(Max)

SELECT @List=coalesce(@list+', ','')+string FROM allwords

WHERE PATINDEX('%[aeiou][aeiou][aeiou][aeiou]%',string) >0

Select @List

/*aqueous, gooier, gooiest, obsequious, obsequiously, obsequiousness, onomatopoeia, pharmacopoeia, pharmacopoeias, plateaued, plateauing, queue, queued, queueing, queues, queuing, sequoia, sequoias

*/

Though my favourite has five consecutive vowels

SELECT string FROM allwords

WHERE PATINDEX('%[aeiou][aeiou][aeiou][aeiou][aeiou]%',string) >0

--queueing

 

Good grief, if you are short of vowels, there is plenty you can do, as there are words with seven or more consonants in a row (if you include Y as a consonant) …

Declare @list Varchar(Max)

SELECT @List=coalesce(@list+', ','')+string FROM allwords

WHERE PATINDEX('%[^aeiou][^aeiou][^aeiou][^aeiou][^aeiou][^aeiou][^aeiou]%',string) >0

Select @List   

/* biorhythms, encrypts, rhythms, strychnine */

But most of the time, it is a shortage of vowels

/* you can quickly find the words with the most vowels (10 seems to be the highest) but these are in words like internationalization(10) and interdenominational (9)

*/

SELECT   string,LEN(string)-LEN(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(String,'a',''), 'e',''), 'i',''), 'o',''), 'u',''))

FROM

 allwords     

ORDER BY LEN(string)-LEN(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(String,'a',''), 'e',''), 'i',''), 'o',''), 'u',''))DESC 

 

/* but it is more useful to have the ones with the highest proportion of vowels to consonants! */

 DECLARE @list VARCHAR(MAX)

SELECT @List=COALESCE(@list+', ','')+string FROM

(

SELECT TOP 60 string FROM

 allwords     

ORDER BY ((LEN(string)-LEN(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(String,'a',''), 'e',''), 'i',''), 'o',''), 'u','')))*100.00) /LEN(string) DESC

)f

SELECT @List

/*

oi, adieu, aerie, audio, eerie, queue, aeon, aide, aloe, aqua, area, aria, aura, auto, beau, ciao, ease, epee, euro, idea, iota, luau, oboe, ooze, ouzo, amoebae, anaemia, aquaria, aqueous, aureole, evacuee, sequoia, aah, acacia, ace, adagio, adieus, adieux, aerate, aerial, aeries, age, ago, aha, aid, ail, aim, air, airier, ale, amebae, amoeba, anemia, ape, apiece, apogee, are, arouse, ate, audacious*/

 

 

Now we have a function that will provide a table with all the permutation of between two and eight letters, we can use it to find those common words that can be built with your list of letters. Here we have an effective way of cheating at Scrabble. You’d have to use something like iWebKit to knock together a little application.

if exists (select 'found' from sys.objects where name ='ValidWordsInLetters')

      drop function ValidWordsInLetters

go

CREATE FUNCTION ValidWordsInLetters (@String VARCHAR(10))

/**

summary:  >

example:

     - code:  Select String from dbo.ValidWordsInLetters('CoFartua') 

returns:      a table listing up to the first hundred words

            that can be made with the letters you supply. Good for

            Scrabble where you have SQL Server handy.

**/

RETURNS  @words TABLE

    (

     number INT identity(1,1) primary key,

     String VARCHAR(10)

    )

AS

BEGIN

DECLARE @rowcount INT, @ii INT, @LenString int

Declare @Strings Table (    

      number INT identity(1,1) primary key,

    String VARCHAR(10)

    )

insert into @strings(string)

Select distinct string

                          from dbo.PermutationsOf (@String)

 

SELECT  @Rowcount=0, @LenString=LEN(@String),

        @ii=@LenString

WHILE @ii>2

    AND @rowcount<100

    BEGIN

        INSERT  INTO @words

                SELECT  distinct TOP 100

                         allwords.string

                FROM    Allwords

                        INNER JOIN @Strings a

                    ON LEFT(a.string, @ii)=allwords.string

        SELECT  @Rowcount=@rowcount+@@Rowcount,

                @ii=@ii-1

     

    END

return

end

 

/*

Select String from dbo.ValidWordsInLetters('CoFartua') 

*/

Rapping and doggerel.

Rhyming dictionaries aren't new. they are simply dictionaries that are ordered by the word written backwards. It starts with Baa and ends in Fuzz. The most famous one is probably Walkers Rhyming Dictionary. Every poet has one. Whilst they are useful, they are a bit hit and miss to use. We'll

 be slightly more ambitious and try to give to a better rhyme. We'll extract up to two of the final syllables of the word and match them to all  other words with the same two syllables. We are actually not getting syllables as such but the sonorant/coda combinations. (syllables usually have an initial consonant) This seems to get a better set of rhymes

So, for Phil Factor, we have the rhymes ...

actor,benefactor,chiropractor,contractor,detractor,extractor,malefactor,protractor,subcontractor,tractor

… We could soon be rapping with this lot

So as to get a quick response, and keep the code manageable, we'll create a special table for our rhyming dictionary.

drop TABLE dbo.RhymingWords

CREATE TABLE dbo.RhymingWords(

      String varchar(30) NOT NULL,

      PenultimateSyllable varchar(30) not null default '',

      WithoutFinalSyllable varchar(30) not null default '',

      FinalSyllable varchar(30) not null default '',

      CONSTRAINT [PK_RhymingWords] PRIMARY KEY CLUSTERED

(

      [String] ASC

) ON [PRIMARY]

) ON [PRIMARY]

 

GO

 

 

/*We start by inserting all the words from our dictionary. */

insert into dbo.RhymingWords (String) select String from allwords

 

/* and now we extract the final syllable into its own column */

 

UPDATE RhymingWords --add in the final syllable

SET    WithoutFinalSyllable=LEFT(r.String, LEN(r.String)-endingSyllable),

       FinalSyllable=RIGHT(r.String, endingsyllable)

FROM   RhymingWords r

       INNER JOIN -- could be a monophthong, diphthong, or triphthong,

       (SELECT  String, syllable+case when PATINDEX('[aeiou]%',REVERSE(LEFT(String, LEN(String)-syllable)))=0 then 0

       else PATINDEX('%[aeiou][^aeiou]%',

          REVERSE(LEFT(String, LEN(String)-syllable))+'x') end AS Endingsyllable

        FROM   

                  (SELECT String, --pick out the last vowel-consonant transition

                PATINDEX('%[^aeiou][aeiou]%',--to get the coda

                         REVERSE(String))+1 AS syllable

             FROM   RhymingWords

             WHERE  PATINDEX('%[^aeiou][aeiou]%',

                     REVERSE(String))>0 ---where it is possible!

            ) f

        ) g ON g.String=r.String

 

UPDATE RhymingWords --add in the penultimate syllable

SET    PenultimateSyllable=RIGHT(r.WithoutFinalSyllable, syllableIndex)

FROM   RhymingWords r

       INNER JOIN  -- could be a monophthong, diphthong, or triphthong,

       (SELECT  String, syllable+case when PATINDEX('[aeiou]%',REVERSE(LEFT(WithoutFinalSyllable, LEN(WithoutFinalSyllable)-syllable)))=0 then 0

       else PATINDEX('%[aeiou][^aeiou]%',

          REVERSE(LEFT(WithoutFinalSyllable, LEN(WithoutFinalSyllable)-syllable))+'x')end AS syllableIndex

        FROM   

                  (SELECT String, WithoutFinalSyllable,--pick out the last vowel-consonant transition

                PATINDEX('%[^aeiou][aeiou]%',--to get the coda

                         REVERSE(WithoutFinalSyllable))+1 AS syllable

             FROM   RhymingWords

             WHERE  PATINDEX('%[^aeiou][aeiou]%',

                     REVERSE(WithoutFinalSyllable))>0 ---where it is possible!

            ) f

        ) g ON g.String=r.String

 

--for short words ending in vowels

UPDATE RhymingWords

SET    FinalSyllable= right(String,patindex('%[^aeiouy]%',reverse(String)+'x')-1) from Rhymingwords

where FinalSyllable =''  and reverse(String)like '%[aeiouy]%'

 

--for words with only Ys

UPDATE RhymingWords

SET    FinalSyllable=  right(String,patindex('%[aeiouy]%',reverse(String)+'x')) from Rhymingwords

where FinalSyllable =''

 

--no vowels

UPDATE RhymingWords

SET    FinalSyllable= String

where FinalSyllable =''

 

 

Now, we are in the position to create a function that takes any word and gives you the rhymes to it. You need to beware, because I have not yet programmed in automatic translation of homophones. English spelling is so inconsistent that rhyming dictionaries will never tell you all the rhymes. You have to use some artistry to get the best out of a Rhyming dictionary. ‘Rhyme’, rhymes with ‘chime’, even though the word endings are spelt differently. You need to search all the alternative spellings of the final syllable to get all the rhymes.

No subtlety could, for a moment, attract her,
Until she succumbed to the charms of Phil Factor

You see? No easy programming would have given you that rhyme.

 

IF EXISTS ( SELECT  'found'

            FROM    sys.objects

            WHERE   name='RhymesWith' )

    DROP FUNCTION RhymesWith

go

CREATE FUNCTION RhymesWith (@String VARCHAR(30))

/**

summary:  >

  returns a table with the best rhymes it can with whatever valid word

  you pass to it. Because some syllables are written differently but

  sound alike (Homophonic syllables) you may need to feed in words

  that you know rhyme but have their last syllable(s) spelt differently.

  e.g. eye and spy,

example:

     - code: select * from   dbo.RhymesWith('sand')

     - code: select * from   dbo.RhymesWith('Phill')

     - code: select * from   dbo.RhymesWith('technology')

    

returns:  a table of one varchar column listing all the Rhymes.

     

            There once was an IT contractor

            who went by the name of 'Phil Factor'

            He makes no apology

            for his technology

            for he's a consummate actor

 

                 

**/

RETURNS @alternatives TABLE

    (

    number INT IDENTITY(1, 1)

                PRIMARY KEY,

    String VARCHAR(30)

    )

AS BEGIN

    DECLARE @Coda INT

    DECLARE @FinalSyllable VARCHAR(30)

    DECLARE @PenultimateSyllable VARCHAR(30)

    DECLARE @RestofWord VARCHAR(30)

 

 

        SELECT  @PenultimateSyllable=PenultimateSyllable,

                @FinalSyllable=FinalSyllable

        FROM    RhymingWords

        WHERE   String=@String

               

   if @@Rowcount=0

        BEGIN

            SELECT  @Coda=PATINDEX('%[^aeiou][aeiou]%',--to get the coda

                                   REVERSE(@String))

            SELECT  @Coda=@Coda+PATINDEX('%[aeiou][^aeiou]%',

                                       REVERSE(LEFT(@String,

                                                    LEN(@String)-@Coda))+'x')

    

            SELECT  @RestOfWord=LEFT(@String, LEN(@String)-@Coda),

                    @FinalSyllable=RIGHT(@String, @Coda)

            SELECT  @Coda=PATINDEX('%[^aeiou][aeiou]%',--to get the coda

                                   REVERSE(@RestOfWord))+1

            SELECT  @Coda=CASE WHEN @Coda=1 THEN 0

                               ELSE @Coda

                          END                        

            IF PATINDEX('[aeiou]%',

                        REVERSE(LEFT(@RestOfWord, LEN(@RestOfWord)-@Coda)))>0

                SELECT  @Coda=@Coda+PATINDEX('%[aeiou][^aeiou]%',

                                             REVERSE(LEFT(@RestOfWord,

                                                          LEN(@RestOfWord)

                                                          -@Coda))+'x')

            SELECT  @PenultimateSyllable=RIGHT(@RestOfWord, @Coda)

 

            IF @FinalSyllable=''

                AND REVERSE(@String) LIKE '%[aeiouy]%'

                SET @FinalSyllable=RIGHT(@String,

                                         PATINDEX('%[^aeiouy]%',

                                                  REVERSE(@String)+'x')-1)

 

--for words with only Ys

            IF @FinalSyllable=''

                SET @FinalSyllable=RIGHT(@String,

                                         PATINDEX('%[aeiouy]%',

                                                  REVERSE(@String)+'x'))

 

--no vowels

            IF @FinalSyllable=''

                SET @FinalSyllable=@String

        END

 

    INSERT  INTO @alternatives (String)

            SELECT  String

            FROM    RhymingWords

            WHERE   PenultimateSyllable+FinalSyllable=@PenultimateSyllable

                    +@FinalSyllable

                    AND String<>@String

    IF @@Rowcount<10

        INSERT  INTO @alternatives (String)

                SELECT  String

                FROM    RhymingWords

                WHERE   FinalSyllable=@FinalSyllable

                        AND String<>@String

     

    RETURN

   END

Rap up

So here we are with a word-bank that allows you to cheat at Scrabble and rap, or make up doggerel. More to the point, it has illustrated, in the ‘permutation’ function, how to use a numbers table to create a table, and the ‘quirky update’ method of filling a table with permutation, or any other data you need. We’ve also illustrated some techniques of using the built-in, and rather primitive, character pattern-matching techniques of SQL Server.

A Parting Competition

To end up with: here is a simple competition, that I will award a Christmas prize of a $50 Amazon voucher for.

Given that Scrabble is scored to the following table:

  • How fast can you score all common words according to their Scrabble scores, so as to list them in order.?
  • Find all the words that can’t ever be used in Scrabble? (Blank tiles can mean any letter).   Scrabble contains…
    •     2 blank tiles (scoring 0 points) that can represent any letter
    •     1 point: E ×12, A ×9, I ×9, O ×8, N ×6, R ×6, T ×6, L ×4, S ×4, U ×4
    •     2 points: D ×4, G ×3
    •     3 points: B ×2, C ×2, M ×2, P ×2
    •     4 points: F ×2, H ×2, V ×2, W ×2, Y ×2
    •     5 points: K ×1
    •     8 points: J ×1, X ×1
    •     10 points: Q ×1, Z ×1


This article has been viewed 7362 times.
Phil Factor

Author profile: Phil Factor

Phil Factor (real name withheld to protect the guilty), aka Database Mole, has 25 years of experience with database-intensive applications. Despite having once been shouted at by a furious Bill Gates at an exhibition in the early 1980s, he has remained resolutely anonymous throughout his career. See also :

Search for other articles by Phil Factor

Rate this article:   Avg rating: from a total of 27 votes.


Poor

OK

Good

Great

Must read
 
Have Your Say
Do you have an opinion on this article? Then add your comment below:
You must be logged in to post to this forum

Click here to log in.


Subject: Scrabble
Posted by: Peso (view profile)
Posted on: Sunday, December 27, 2009 at 1:53 PM
Message:

This returns all word in wordlist file in less than 2 seconds.

CREATE TABLE #Scrabble
  
(
    
Letter CHAR(1) PRIMARY KEY CLUSTERED,
    
Items TINYINT NOT NULL,
    
Points SMALLINT NOT NULL
   )

INSERT #Scrabble
(
  
Letter,
  
Items,
  
Points
)
VALUES ('%', 2, 0), ('A', 9, 1), ('B', 2, 3), ('C', 2, 3), ('D', 4, 2), ('E', 12, 1), ('F', 2, 4), ('G', 3, 2), ('H', 2, 4),
(
'I', 9, 1), ('J', 1, 8), ('K', 1, 5), ('L', 4, 1), ('M', 2, 3), ('N', 6, 1), ('O', 8, 1), ('P', 2, 3), ('Q', 1, 10),
(
'R', 6, 1), ('S', 4, 1), ('T', 6, 1), ('U', 4, 1), ('V', 2, 4), ('W', 2, 4), ('X', 1, 8), ('Y', 2, 4), ('Z', 1, 10)

;
WITH cteYak(Word, Score, Pooled, Invalid)
AS (
SELECT    y.Word,
    
SUM(s.Points * CASE WHEN y.Items &amp;lt;= s.Items THEN y.Items ELSE s.Items END) AS Score,
    
SUM(CASE WHEN y.Items &amp;gt; s.Items THEN y.Items - s.Items ELSE 0 END) AS Pooled,
    
SUM(CASE WHEN s.Letter IS NULL THEN 1 ELSE 0 END) AS Invalid
FROM    (
      
SELECT    w.Word,
          
SUBSTRING(w.Word, n.Number, 1) AS Letter,
          
COUNT(*) AS Items
      
FROM    dbo.WordList AS w
      
INNER JOIN dbo.Numbers AS n ON n.Number BETWEEN 1 AND LEN(w.Word)
    
GROUP BY w.Word,
          
SUBSTRING(w.Word, n.Number, 1)
     )
AS y
LEFT JOIN   #Scrabble AS s ON s.Letter = y.Letter
GROUP BY    y.Word
)
SELECT   Word,
  
Score,
  
CASE
    
WHEN Pooled &amp;gt; 2 OR Invalid &amp;gt; 0 THEN 'Not a valid Scrabble word.'
    
ELSE 'Valid Scrabble word'
  
END AS Valid
FROM   cteYak
ORDER BY  Score DESC,
  
Word

DROP TABLE  #Scrabble





Subject: ...
Posted by: Anonymous (not signed in)
Posted on: Monday, December 28, 2009 at 4:38 AM
Message: This is mutant smart...

Subject: fun
Posted by: jenniebee (view profile)
Posted on: Monday, December 28, 2009 at 10:03 AM
Message: --My Numbers table is called Common_Increment. The eureka moment for me was realizing that I didn't have to actually find the letter placement for the blanks, I could just go with sums.
IF OBJECT_ID('ScrabbleScore') IS NOT NULL DROP TABLE ScrabbleScore

CREATE TABLE ScrabbleScore
(
Ltr  CHAR(1) PRIMARY KEY,
Score INT,
Quant INT)

GO

INSERT INTO ScrabbleScore
  
SELECT  ' ', 0, 2
UNION  SELECT  'E', 1, 12
UNION  SELECT  'A', 1, 9
UNION  SELECT  'I', 1, 9
UNION  SELECT  'O', 1, 8
UNION  SELECT  'N', 1, 6
UNION  SELECT  'R', 1, 6
UNION  SELECT  'T', 1, 6
UNION  SELECT  'L', 1, 4
UNION  SELECT  'S', 1, 4
UNION  SELECT  'U', 1, 4
UNION  SELECT  'D', 2, 4
UNION  SELECT  'G', 2, 3
UNION  SELECT  'B', 3, 2
UNION  SELECT  'C', 3, 2
UNION  SELECT  'M', 3, 2
UNION  SELECT  'P', 3, 2
UNION  SELECT  'F', 4, 2
UNION  SELECT  'H', 4, 2
UNION  SELECT  'V', 4, 2
UNION  SELECT  'W', 4, 2
UNION  SELECT  'Y', 4, 2
UNION  SELECT  'K', 5, 1
UNION  SELECT  'J', 8, 1
UNION  SELECT  'X', 8, 1
UNION  SELECT  'Q', 10, 1
UNION  SELECT  'Z', 10, 1


;WITH SelectWord (String, Ltr, Ord)
AS (SELECT String, SUBSTRING(String, i, 1) , n.i
  
FROM Allwords
  
JOIN Common_increment n
    
ON n.i &amp;lt;= LEN(String)),
SubBlanks (String, Ltr, Blanks, SubtractScore)
AS (SELECT w.String, w.Ltr, CASE WHEN w.Occ &amp;lt; s.Quant THEN 0 ELSE w.Occ - s.Quant END Blanks,
  
CASE WHEN w.Occ &amp;lt; s.Quant THEN 0 ELSE (w.Occ - s.Quant) * s.Score END SubtractScore
FROM (SELECT String, Ltr, COUNT(1) Occ FROM SelectWord GROUP BY String, Ltr) w
JOIN ScrabbleScore s
  
ON w.Ltr = s.Ltr),
GetScore (String, Score)
AS (
SELECT w.String, SUM(s.Score)
FROM SelectWord w
JOIN ScrabbleScore s
ON w.Ltr= s.ltr
GROUP BY w.String)
SELECT gs.String, gs.Score - sb.SubtractScore Score, np.AllBlanks, CASE WHEN np.AllBlanks &amp;lt; 3 THEN '' ELSE 'invalid' END valid
FROM GetScore gs
JOIN (SELECT string, SUM(SubtractScore) SubtractScore FROM SubBlanks GROUP BY string) sb
ON gs.String = sb.String
JOIN (SELECT String, SUM(Blanks) AllBlanks
  
FROM SubBlanks
  
GROUP BY String) np
ON gs.String = np.String
ORDER BY gs.Score - sb.SubtractScore DESC

Subject: oops
Posted by: jenniebee (view profile)
Posted on: Monday, December 28, 2009 at 10:05 AM
Message: hit paste twice. WTB edit button... (We fixed this : Ed)

Subject: ouch
Posted by: Anonymous (not signed in)
Posted on: Monday, December 28, 2009 at 11:03 AM
Message: my brain just exploded.

Subject: The permutation function seems pretty complex why not use this one?
Posted by: Anonymous (not signed in)
Posted on: Monday, December 28, 2009 at 12:07 PM
Message: IF  EXISTS (SELECT * FROM sys.objects WHERE OBJECT_ID = OBJECT_ID(N'[dbo].[Strings]') AND TYPE IN (N'U'))
DROP TABLE [dbo].[Strings]
GO

CREATE TABLE [dbo].[Strings](
[String] [varchar](50) NOT NULL,
CONSTRAINT [PK_Strings] PRIMARY KEY CLUSTERED
(
[String] ASC
)
)
ON [PRIMARY]

GO

--Will put everything into strings table
CREATE PROCEDURE dbo.spPermutationsOf (
@Prefix AS VARCHAR(MAX), --recursive parameter, enter '' for first instance
@Suffix AS VARCHAR(MAX), --enter the origninal string to be permutated,
@AllChar AS bit
)
AS
BEGIN
--Can run up to limit of recursion, 32 character in my case but 32 characters equals 32! permutations = be patient....
DECLARE @NewString AS VARCHAR(MAX)
DECLARE @Index AS INT
DECLARE
@NewPrefix AS VARCHAR(MAX)
DECLARE @NewSuffix AS VARCHAR(MAX)
DECLARE @SuffixLength AS INT
SET
@SuffixLength=LEN(@Suffix)
IF @SuffixLength=1
  
BEGIN
     SET
@NewString=@Prefix+RIGHT(@Suffix,1)
    
IF NOT EXISTS (SELECT 1 FROM Strings WHERE String=@NewString) --remove this check to get same letter permutations, will also run faster
      
BEGIN
         INSERT INTO
Strings (String) VALUES (@NewString)
      
END
   END
ELSE
   BEGIN
    
--Test:Lou
    
SET @Index=1
    
SET @NewSuffix=SUBSTRING(@Suffix,2,@SuffixLength) --1 run: ou, 2nd run: u
    
SET @NewPrefix=@Prefix+LEFT(@Suffix,1) -- L, Lo
    
WHILE @Index&amp;lt;=@SuffixLength
      
BEGIN
         EXEC
dbo.spPermutationsOf @NewPrefix,@NewSuffix,@AllChar
        
IF @AllChar=0 EXEC dbo.spPermutationsOf '',@NewSuffix,0 --to also put substring
        
SET @Suffix=SUBSTRING(@Suffix,2,@SuffixLength)+LEFT(@Suffix,1) --ouL
        
SET @NewSuffix=SUBSTRING(@Suffix,2,@SuffixLength) --uL
        
SET @NewPrefix=@Prefix+LEFT(@Suffix,1) --o
        
SET @Index=@Index+1
      
END
   END
END

Subject: Re: The permutation function seems pretty complex why not use this one?
Posted by: Phil Factor (view profile)
Posted on: Monday, December 28, 2009 at 4:54 PM
Message: The reason that I chose the approach that I illustrate is that I was aiming for speed rather than simplicity. For eight letters, I timed yours at 58 seconds. Mine goes in at 763 Ms. (less than a second) However, I certainly agree that yours is easier to understand!

Subject: PermutationsOf
Posted by: Peso (view profile)
Posted on: Tuesday, December 29, 2009 at 4:00 PM
Message:

Here is another approach for the function

DECLARE  @Word VARCHAR(10) = 'Peter'

;WITH cteYak(Word, Letters)
AS (
SELECT  CAST(SUBSTRING(@Word, Number, 1) AS VARCHAR(MAX)) AS Word,
  
STUFF(@Word, Number, 1, '') AS Letters
FROM  dbo.TallyNumbers
WHERE Number BETWEEN 1 AND LEN(@Word)

UNION ALL

SELECT    Word + SUBSTRING(y.Letters, n.Number, 1) AS Word,
    
STUFF(y.Letters, n.Number, 1, '') AS Letters
FROM    cteYak AS y
INNER JOIN dbo.TallyNumbers AS n ON n.Number BETWEEN 1 AND LEN(y.Letters)
)
SELECT DISTINCT Word
FROM cteYak
WHERE  LEN(Word) = LEN(@Word)

Subject: Not valid Scabble words
Posted by: Peso (view profile)
Posted on: Wednesday, December 30, 2009 at 7:21 AM
Message: These are the words that are not valid Scrabble words in the supplied wordlist

razzamatazz
razzmatazz
knickknacks
knickknack
pizzazz

Subject: Scrabble
Posted by: Carol (view profile)
Posted on: Wednesday, December 30, 2009 at 8:34 AM
Message: Thank you, Phil. My husband and I enjoy frequent Scrabble challenges, and while I'm a better speller he's more crafty. I'll have to show him this code - he will think it's cool!

Subject: Remember Moby Words?
Posted by: Joe Celko (not signed in)
Posted on: Wednesday, December 30, 2009 at 3:59 PM
Message: Does anyone else rember teh episode of THE SIMPSONS where Homer is playing Scrabble with Lisa and the word "oxidize' is sitting in front of him?

A million years ago in the CP/M days long ago, The Austin Codeworks published a huge file of English words under the name "Moby Words" for just such puzzles. It is still out there in .zip files for download. Hooray!

Now we need a procedure to delete all the non-Scrabble-able words in the list. That is uglier than it looks ... so I wave my hands for now.

I also have Fike's algorithm for permutations around here somewhere, but try this approach:

CREATE TABLE Wordlist
(word VARCHAR(25) NOT NULL PRIMARY KEY,
scrabble_score INTEGER NOT NULL
CHECK (scrabble_score > 0),
word_length INTEGER NOT NULL
CHECK (word_length > 0),
sorted_word VARCHAR(25) NOT NULL);

word is the original word. The scrabble_score is the point value of the word and word length explains itself. The sorted_word column is a new "word" made by sorting the letters of the original word.

Input the letters in your tile rack. Query up all subsets of 7, 6, 5, 4 and 3 letters. Sort each subset, which will give us (1 + 7 + 21 + 35+ 35) = 99 strings to match to (word_length, sorted_word) in the word list table.

I will try to play with this if I can get my head above water over the holidays.


Subject: Scrabble
Posted by: Anonymous (not signed in)
Posted on: Wednesday, December 30, 2009 at 10:16 PM
Message: Just buy the Franklin scrabble computer for $50.00. It also accounts for the blanks. http://www.franklin.com/estore/dictionary/SCR-228/

Subject: Franklin device
Posted by: Anonymous (not signed in)
Posted on: Saturday, January 02, 2010 at 11:21 AM
Message: But that's like saying you could buy your rogue nuclear device on the black market and avoid the glowing fun of building your own.

Subject: Great minds think alike!
Posted by: Dan Newton (not signed in)
Posted on: Sunday, January 03, 2010 at 4:06 PM
Message: I did the same thing for myself, when I play my wife on Facebook's Scrabble. But she always knows when I'm cheating because I can't resist the urge to use some ridiculous word that neither of us has ever heard of.

Subject: Cheating at Scrabble
Posted by: Phil Factor (view profile)
Posted on: Monday, January 04, 2010 at 6:24 AM
Message: Various members of my family used to believe that it was fair and polite to play the game with a copy of a large dictionary on their lap. It always slowed the game to a crawl and meant the use of extraordinarily silly words and spellings. No, best play without props, and to a time limit.

Subject: FROM clause
Posted by: Marc (not signed in)
Posted on: Monday, January 04, 2010 at 11:09 PM
Message: In running the code, the Inserts that use the syntax "FROM ( VALUES (1), (2), (3), ... ) as X(i)" gives me an error: "Incorrect syntax near the keyword 'values'." Is this sample code for SQL Server 2008?

Subject: 2008 compi
Posted by: Peso (view profile)
Posted on: Tuesday, January 05, 2010 at 3:29 AM
Message: Yes. Yoy have to rewrite that part to
SELECT 1 AS i UNION ALL SELECT 2 UNION ALL...

Subject: Re: FROM Clause
Posted by: Phil Factor (view profile)
Posted on: Tuesday, January 05, 2010 at 3:37 AM
Message: You can easily change this into a more 'classic' syntax by using a UNION clause

Declare @Digits table (i int)
Insert into @Digits(i)
Select 1 union select 2 union select 3
union select 4 union select 5
union select 6 union select 7
union select 8 union select 9 union select 0
INSERT INTO numbers(number)
SELECT (D5.i*100000 + D4.i*10000 + D3.i * 1000 + D2.i * 100
+ D1.i * 10 + D0.i + 1) AS seq
FROM @Digits AS D0,@Digits AS D1,@Digits AS D2,@Digits AS D3,
@Digits AS D4,@Digits AS D5

Subject: Peso and I sang a duet
Posted by: Phil Factor (view profile)
Posted on: Tuesday, January 05, 2010 at 3:38 AM
Message: Oops. Sorry, Peso, we answered that together!

Subject: Duet
Posted by: Peso (view profile)
Posted on: Thursday, January 07, 2010 at 2:50 AM
Message: I don't mind at all!

 










Phil Factor
Exploring your database schema with SQL
 In the second part of Phil's series of articles on finding stuff (such as objects, scripts, entities, metadata) in... Read more...



 View the blog
Adding a KPI to an SQL Server Analysis Services Cube
 Key Performance Indicators, which vary according to the application, are widely used as a measure of... Read more...

Transparent Data Encryption
  Transparent Data Encryption is designed to protect data by encrypting the physical files of the... Read more...

SQL Toolbelt 2008: Predominantly an Engineering Task
 The conversion of the Red-Gate tools to be compatible with SQL Server 2008 might not seem, on first... Read more...

SQL Response: The dim sum interview
 Richard Morris met David and Nigel of the SQL Response team, in a dim sum Restaurant in Cambridge. They... Read more...

Data Correlation Optimization Internals
 Having adroitly introduced us, in his previous article, to the Date Correlation ability of the Query... Read more...

Beginning SQL Server 2005 Reporting Services Part 1
 Steve Joubert begins an in-depth tour of SQL Server 2005 Reporting Services with a step-by-step guide... Read more...

Ten Common Database Design Mistakes
 Database design and implementation is the cornerstone of any data centric project (read 99.9% of... Read more...

Beginning SQL Server 2005 Reporting Services Part 2
 Continuing his in-depth tour of SQL Server 2005 Reporting Services, Steve Joubert demonstrates the most... Read more...

SQL Server Full Text Search Language Features
 SQL Full-text Search (SQL FTS) is an optional component of SQL Server 7 and later, which allows fast... Read more...

Creating CSV Files Using BCP and Stored Procedures
 Nigel Rivett demonstrates some core techniques for extracting SQL Server data into CSV files, focussing... Read more...

Over 150,000 Microsoft professionals subscribe to the Simple-Talk technical journal. Join today, it's fast, simple, free and secure.

Join Simple Talk