Jumble
Jumble.
Jumble is an Ada program designed to unscramble jumbled words. You can use it to solve word puzzles; or, when making your own puzzles, you can check that a jumbled word doesn't unscramble to more than one word. You can build Jumble from the command line with gnatmake:
gnatmake jumble
To unscramble the word "aelpt", for example, enter this command:
jumble aelpt
The result should look something like this:
leapt: leapt palet patel pelta petal plate pleat tepal
You can include more than one word on the command line. If you don't supply a word, the program prints all entries with more than one permutation. You may need to change the value of File_Name if your dictionary is located elsewhere on the system. A similar implementation in Java may be found here.
The two versions of Jumble shown below implement the algorithms discussed here. The latest version may be found here. Collisions in a hashed map are examined here.
Algorithm 1: Using Hashed_Maps & Ordered_Sets
Algorithm 1: The first version constructs a hashed map of dictionary entries. Each unique key is comprised of the sorted characters of a dictionary word; the matching element is an ordered set of words each having a different permutation of the same characters. Building such a map is slightly more difficult, but permutations of arbitrarily large words can then be found in constant time. By iterating over the map, you can find words with a high number of permutations, e.g. "caret" and "ester".
--------------------------------------------------------
--
-- Jumble: Unscramble jumbled words.
--
-- John B. Matthews, M.D., 10-Jun-2009
--
-- Distribution: per GPL
--
--------------------------------------------------------
with Ada.Characters.Handling;
with Ada.Command_Line;
with Ada.Containers.Generic_Array_Sort;
with Ada.Containers.Hashed_Maps;
with Ada.Containers.Ordered_Sets;
with Ada.Strings.Bounded;
with Ada.Strings.Bounded.Hash;
with Ada.Text_IO;
with Ada.Integer_Text_IO;
procedure Jumble is
Max_Word : constant Positive := 26;
Max_Count : constant Ada.Containers.Count_Type := 250_000;
File_Name : constant String := "/usr/share/dict/words";
package ACH renames Ada.Characters.Handling;
package ACL renames Ada.Command_Line;
package ASB is new Ada.Strings.Bounded.Generic_Bounded_Length(Max_Word);
use type ASB.Bounded_String;
procedure Sort is new Ada.Containers.Generic_Array_Sort(
Index_Type => Positive,
Element_Type => Character,
Array_Type => String);
package ACOS is new Ada.Containers.Ordered_Sets(
Element_Type => ASB.Bounded_String);
type Word_Set is access ACOS.Set;
function Hash is new Ada.Strings.Bounded.Hash(ASB);
use type Ada.Containers.Hash_Type;
package ACHM is new Ada.Containers.Hashed_Maps(
Key_Type => ASB.Bounded_String,
Element_Type => Word_Set,
Hash => Hash,
Equivalent_Keys => "=");
Word_Map : ACHM.Map;
-- Sort the characters of Str returning a lower case Key
function Sort(Str : String) return ASB.Bounded_String is
Key : String(1 .. Str'Length) := ACH.To_Lower(Str);
begin
Sort(Key);
return ASB.To_Bounded_String(Key);
end Sort;
-- Read dictionary words up to Max_Word in length
-- Map character-sorted words to permutatively equivalent words
procedure Read_Dictionary(Word_Map : in out ACHM.Map) is
Dict_File : Ada.Text_IO.File_Type;
Line : String (1 .. 32);
Last : Natural;
Word : ASB.Bounded_String;
Sorted : ASB.Bounded_String;
Position : ACHM.Cursor;
Inserted : Boolean;
Set : Word_Set;
begin
Ada.Text_IO.Open(Dict_File, Ada.Text_IO.In_File, File_Name);
while not Ada.Text_IO.End_Of_File(Dict_File) loop
Ada.Text_IO.Get_Line (Dict_File, Line, Last);
if Last <= Max_Word then
Word := ASB.To_Bounded_String(Line(1 .. Last));
Sorted := Sort(Line(1 .. Last));
Word_Map.Insert(Sorted, Position, Inserted);
if Inserted then
Set := new ACOS.Set;
Word_Map.Replace_Element(Position, Set);
else
Set := ACHM.Element(Position);
end if;
Set.Insert(Word);
end if;
end loop;
Ada.Text_IO.Close(Dict_File);
end Read_Dictionary;
-- Print each word in a set.
procedure Print_Word(Position : in ACOS.Cursor) is
Item : ASB.Bounded_String := ACOS.Element(Position);
begin
Ada.Text_IO.Put(ASB.To_String(Item) & " ");
end Print_Word;
-- Find Str in Word_Map; print permutatively equivalent words.
procedure Show_Results(Word_Map : ACHM.Map; Str : in String) is
Word : ASB.Bounded_String := Sort(Str);
Set : Word_Set;
begin
Ada.Text_IO.Put(Str & ": ");
if Word_Map.Contains(Word) then
Set := Word_Map.Element(Word);
Set.Iterate(Print_Word'Access);
Ada.Text_IO.New_Line;
else
Ada.Text_IO.Put_Line("no match.");
end if;
end Show_Results;
-- Print each entry having more than one word in its set.
procedure Print_All(Position : in ACHM.Cursor) is
Set : Word_Set := ACHM.Element(Position);
Length : Natural := Natural(Set.Length);
begin
if Length > 1 then
Ada.Integer_Text_IO.Put(Length, 0);
Ada.Text_IO.Put(" ");
Set.Iterate(Print_Word'Access);
Ada.Text_IO.New_Line;
end if;
end Print_All;
begin
Word_Map.Reserve_Capacity(Max_Count);
Read_Dictionary(Word_Map);
Ada.Text_IO.Put_Line("Checking" &
Natural'Image(Natural(Word_Map.Length)) & " entries.");
if ACL.Argument_Count = 0 then
Word_Map.Iterate(Print_All'Access);
else
for i in 1 .. ACL.Argument_Count loop
if ACL.Argument(i)'Length <= Max_Word then
Show_Results(Word_Map, ACL.Argument(i));
end if;
end loop;
end if;
end Jumble;
Algorithm 2: Using Permutations
Algorithm 2: The second version of Jumble reads the dictionary into memory in the procedure Read_Dictionary, storing entries in the variable List. Also, you may need to increase the value of Max_List if your dictionary is especially large. Read_Dictionary only reads words that are Max_Word characters or less in length. Max_Word is set to 10, since a 10 character string has 10! = 3,628,800 permutations. Longer words take factorially longer to process.
Next, Permute_String finds each of the possible permutations of the word and calls Look_Up to do a binary search of the dictionary list. If the word is found, Permute_String stores it in Found.
Finally, the list of Found words is sorted (so we can skip duplicates) and printed.
No more excuses; get puzzling!
--------------------------------------------------------
--
-- Jumble: Unscramble jumbled words.
--
-- John B. Matthews, M.D., 08/01/2003
--
-- Distribution: per GPL
--
--------------------------------------------------------
with Ada.Characters.Handling;
with Ada.Command_Line;
with Ada.Strings.Bounded;
with Ada.Text_IO;
with GNAT.Bubble_Sort_G;
procedure Jumble is
Max_Word : constant Positive := 10;
Max_Found : constant Positive := 32;
Max_List : constant Positive := 250_000;
File_Name : constant String := "/usr/share/dict/words";
package Words is new Ada.Strings.Bounded.Generic_Bounded_Length(Max_Word);
use type Words.Bounded_String;
type Words_Array is array (Natural range <>) of Words.Bounded_String;
type Words_Ptr is access Words_Array;
List : Words_Ptr := new Words_Array (1 .. Max_List);
Last_Word : Natural; -- Last word read into List.
Found : Words_Array (0 .. Max_Found);
Last_Found : Natural; -- Last word in Found.
-- Read the dictionary for words up to Max_Word in length
procedure Read_Dictionary (List : Words_Ptr; Count : out Natural) is
Dict_File : Ada.Text_IO.File_Type;
Line : String (1 .. 30);
Last : Natural;
begin
Count := 0;
Ada.Text_IO.Open(Dict_File, Ada.Text_IO.In_File, File_Name);
while not Ada.Text_IO.End_Of_File(Dict_File) loop
Ada.Text_IO.Get_Line (Dict_File, Line, Last);
if Last <= Max_Word then
Count := Count + 1;
Line(1 .. Last) := Ada.Characters.Handling.To_Lower(Line(1 .. Last));
List(Count) := Words.To_Bounded_String(Line(1 .. Last));
end if;
end loop;
Ada.Text_IO.Close(Dict_File);
end Read_Dictionary;
-- Binary search of List for Word; return position or 0 if not found.
function Look_Up (List : Words_Ptr; Word : Words.Bounded_String) return Natural is
Lower : Natural := List'First;
Middle : Natural;
Upper : Natural := Last_Word;
Found : Boolean;
begin
loop
Middle := (Lower + Upper) / 2;
Found := Word = List(Middle);
if Word > List(Middle) then
Lower := Middle + 1;
else
Upper := Middle - 1;
end if;
exit when Found or Lower > Upper;
end loop;
if Found then
return Middle;
else
return 0;
end if;
end Look_Up;
-- Recursively permute Str starting at position K.
procedure Permute_String (Str : in String; K : Positive := 1) is
S : String(Str'Range) := Str;
N : Positive := S'Length;
B : Words.Bounded_String;
temp : Character;
begin
if K = N then
B := Words.To_Bounded_String(S);
if Look_Up(List, B) > 0 then -- Save in Found
Last_Found := Last_Found + 1;
Found(Last_Found) := B;
end if;
else
for i in K .. N loop
temp := S(i);
S(i) := S(K);
s(K) := temp;
Permute_String(S, K + 1);
end loop;
end if;
end Permute_String;
-- Used by generic sort
procedure Assign (From, To : Natural) is
begin
Found(To) := Found(From);
end Assign;
-- Used by generic sort
function Compare (Left, Right : Natural) return Boolean is
begin
return Found(Left) < Found(Right);
end Compare;
-- Bubble sort is fine for this short list
package Found_Sort is new GNAT.Bubble_Sort_G(Assign, Compare);
-- Sort and display results, skipping duplicates
procedure Show_Results (Str : in String) is
Last_Out: Words.Bounded_String := Words.To_Bounded_String("");
begin
if Last_Found = 0 then
Ada.Text_IO.Put_Line(Str);
else
Found_Sort.Sort(Last_Found);
for i in 1 .. Last_Found loop
if Found(i) /= Last_Out then
Ada.Text_IO.Put_Line(Words.To_String(Found(i)));
Last_Out := Found(i);
end if;
end loop;
end if;
end Show_Results;
begin
Read_Dictionary(List, Last_Word);
Ada.Text_IO.Put_Line("Checking" & Natural'Image(Last_Word) & " entries.");
for i in 1 .. Ada.Command_Line.Argument_Count loop
if Ada.Command_Line.Argument(i)'Length > Max_Word then
Ada.Text_IO.Put_Line(Ada.Command_Line.Argument(i) & " is too long!");
else
Last_Found := 0;
Permute_String (Ada.Command_Line.Argument(i));
Show_Results (Ada.Command_Line.Argument(i));
end if;
end loop;
end Jumble;
Copyright 1999, 2003, 2009 John B. Matthews
Distribution permitted under the terms of the GPL.
Last updated 23-Jul-2022.