6 responses

  1. Shawn
    September 22, 2022

    This generation of software devs is weak.

    Reply

  2. Ian
    September 23, 2022

    When I saw Matt Parker's video "Can you find: five five-letter words with twenty-five unique letters?" (https://www.youtube.com/watch?v=_-AfhLQfb6w) I thought it would be fun to give it a go with bitmaps in T-SQL on SQL Server 2022. I mean, it couldn't be any slower than Matt's solution with its 30 day runtime!
    I was pretty happy with the result (1-3 seconds depending on the initial word list), although I definitely would have appreciated bitwise aggregate functions. And I ended up using BIT_COUNT() to find the least significant set bit (see, for example, https://en.wikipedia.org/wiki/Find_first_set#Properties_and_relations).

    DROP TABLE IF EXISTS #W
    CREATE TABLE #W (w char(5) NOT NULL PRIMARY KEY)
    
    DECLARE @list int
    SET @list = 1
    IF @list = 1
    BEGIN
      -- Use Wordle words extracted in Feb 2022:
      BULK INSERT #W FROM 'c:\temp\wordle-guesses.txt'
      BULK INSERT #W FROM 'c:\temp\wordle-answers.txt' -- guess list does not include answers 
    END 
    ELSE BEGIN
      -- Use larger word list linked in description on Matt's video:
      DROP TABLE IF EXISTS #W0
    
      CREATE TABLE #W0 (w varchar(200) NOT NULL)
      BULK INSERT #W0 FROM 'c:\temp\words_alpha.txt'
    
      INSERT INTO #W
      SELECT *
      FROM #W0
      WHERE LEN(w) = 5
    END
    
    GO
    
    DROP TABLE IF EXISTS #DistinctLetterWords
    
    ;WITH WLB AS (
        -- break words into letters, and represent each letter as a bit (index 0 to 25):
        SELECT W.w, SET_BIT(0, ASCII(SUBSTRING(W.w, N.value, 1)) - ASCII('a'), 1) AS b
        FROM #W AS W
        CROSS JOIN GENERATE_SERIES (1, 5) AS N
      ), WB AS (
        -- combine the bits (sadly, SQL Server does not have bitwise aggregate functions)
        SELECT w, SUM(b) AS b
        FROM WLB
        GROUP BY w
      ), WDB AS (
        -- distinct-letter words with alphabetic-order bit encoding: only keep words with 5 different letters
        SELECT w, ISNULL(b, 0) AS b
        FROM WB
        WHERE BIT_COUNT(b) = 5
      ), FBM AS (
        -- mapping of alphabetic to use-frequency order (counting distinct bit patterns):
        SELECT WLB.b AS from_bit, SET_BIT(0, ROW_NUMBER() OVER (ORDER BY COUNT(DISTINCT WDB.b)) - 1) AS to_bit
        FROM WLB
        INNER JOIN WDB
          ON WLB.w = WDB.w
        GROUP BY WLB.b
      ), WLFB AS (
        -- combine the bits, keeping only distinct-letter words as previously determined:
        SELECT WLB.w, FBM.to_bit AS b
        FROM WLB
        INNER JOIN FBM
          ON WLB.b = FBM.from_bit
        INNER JOIN WDB
          ON WLB.w = WDB.w
      ), WDFB AS (
        -- combine remapped bits (which are already guaranteed not to overlap)
        SELECT w, ISNULL(SUM(b), 0) AS b
        FROM WLFB
        GROUP BY w
      )
    SELECT *
    INTO #DistinctLetterWords
    FROM WDFB
    
    ALTER TABLE #DistinctLetterWords ADD PRIMARY KEY (b, w)
    
    -- distinct bit patterns of distinct-letter words (using least-to-most frequent bit ordering)
    -- annotate and index by least frequent used bit position 
    DROP TABLE IF EXISTS #B
    
    SELECT ISNULL(BIT_COUNT(a ^ (a - 1) - 1), 0) AS first, a INTO #B FROM ( SELECT DISTINCT b AS a FROM #DistinctLetterWords ) AS B
    
    ALTER TABLE #B ADD PRIMARY KEY (first, a)
    
    DROP TABLE IF EXISTS #A
    
    -- accumulate distinct bits; start by choosing the unused bit as generation 0:
    SELECT 0 AS gen, A.acc, A.acc AS a
    INTO #A
    FROM (
        SELECT ISNULL(SET_BIT(0, C.value, 1), 1) AS acc
        FROM GENERATE_SERIES (0, 25) AS C
      ) AS A
    
    ALTER TABLE #A ADD PRIMARY KEY (gen, acc, a)
    
    DECLARE @gen int
    SET @gen = 1
    
    WHILE @gen <= 5
    BEGIN
      -- accumulate new compatible bit patterns that use the first (least frequent) unused bit:
      INSERT INTO #A
      SELECT @gen AS gen, A.acc | B.a AS acc, B.a
      FROM #B AS B
      INNER JOIN (
          SELECT acc, BIT_COUNT(~acc ^ (~acc - 1)) - 1 AS next
          FROM ( SELECT DISTINCT acc FROM #A WHERE gen = @gen - 1 ) AS B
        ) AS A
        ON A.acc & B.a = 0
       AND B.first = A.next
    
      SET @gen = @gen + 1
    END
    
    -- trace back to get words for each bit pattern added:
    SELECT WA.w AS w1, WB.w AS w2, WC.w AS w3, WD.w AS w4, WE.w AS w5 
    FROM #A AS A5 INNER JOIN #A AS A4 ON A5.acc - A5.a = A4.acc 
    INNER JOIN #A AS A3 ON A4.acc - A4.a = A3.acc 
    INNER JOIN #A AS A2 ON A3.acc - A3.a = A2.acc 
    INNER JOIN #A AS A1 ON A2.acc - A2.a = A1.acc 
    INNER JOIN #DistinctLetterWords AS WA ON A1.a = WA.b 
    INNER JOIN #DistinctLetterWords AS WB ON A2.a = WB.b 
    INNER JOIN #DistinctLetterWords AS WC ON A3.a = WC.b 
    INNER JOIN #DistinctLetterWords AS WD ON A4.a = WD.b 
    INNER JOIN #DistinctLetterWords AS WE ON A5.a = WE.b 
    WHERE A5.gen = 5
      AND A4.gen = 4
      AND A3.gen = 3
      AND A2.gen = 2
      AND A1.gen = 1
    

    Reply

  3. Itzik Ben-Gan
    October 25, 2022

    Ian, thanks for sharing the puzzle and your solution. I absolutely love it!
    It is indeed an interesting example for a use case for bit manipulation functions in T-SQL. I also wish we had an aggregate bitwise or function to simplify accumulating set bits.
    I do recommend other readers to carefully look at Ian's code. I loved the use of bitwise operators to identify the first set bit and the first unset bit, as well as the idea to rank letters by use frequency.

    Reply

  4. Alexander Perea Gomez
    March 17, 2023

    Excellent!!

    Reply

  5. Itzik Ben-Gan
    March 23, 2023

    Thanks Alexander!

    Reply

  6. Lukas
    December 13, 2023

    Thanks for the helpful article. For those interested in the topic, I also recommend reading this article (https://blog.devart.com/bit-manipulation-functions-in-sql-server.html) about bit manipulation functions.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Back to top