Anagrams
Two or more words can be composed of the same characters, but in a different order. Using the word list at http://www.puzzlers.org/pub/wordlists/unixdict.txt, find the sets of words that share the same characters that contain the most words in them.
![Task](http://static.miraheze.org/rosettacodewiki/thumb/b/ba/Rcode-button-task-crushed.png/64px-Rcode-button-task-crushed.png)
You are encouraged to solve this task according to the task description, using any language you may know.
Ada
<lang ada>with Ada.Text_IO; use Ada.Text_IO;
with Ada.Containers.Indefinite_Ordered_Maps; with Ada.Containers.Indefinite_Ordered_Sets;
procedure Words_Of_Equal_Characters is
package Set_Of_Words is new Ada.Containers.Indefinite_Ordered_Sets (String); use Ada.Containers, Set_Of_Words; package Anagrams is new Ada.Containers.Indefinite_Ordered_Maps (String, Set); use Anagrams;
File : File_Type; Result : Map; Max : Count_Type := 1;
procedure Put (Position : Anagrams.Cursor) is First : Boolean := True; List : Set renames Element (Position); procedure Put (Position : Set_Of_Words.Cursor) is begin if First then First := False; else Put (','); end if; Put (Element (Position)); end Put; begin if List.Length = Max then Iterate (List, Put'Access); New_Line; end if; end Put;
begin
Open (File, In_File, "unixdict.txt"); loop declare Word : constant String := Get_Line (File); Key : String (Word'Range) := (others => Character'Last); List : Set; Position : Anagrams.Cursor; begin for I in Word'Range loop for J in Word'Range loop if Key (J) > Word (I) then Key (J + 1..I) := Key (J..I - 1); Key (J) := Word (I); exit; end if; end loop; end loop; Position := Find (Result, Key); if Has_Element (Position) then List := Element (Position); Insert (List, Word); Replace_Element (Result, Position, List); else Insert (List, Word); Include (Result, Key, List); end if; Max := Count_Type'Max (Max, Length (List)); end; end loop;
exception
when End_Error => Iterate (Result, Put'Access); Close (File);
end Words_Of_Equal_Characters;</lang> Sample output:
abel,able,bale,bela,elba caret,carte,cater,crate,trace angel,angle,galen,glean,lange alger,glare,lager,large,regal elan,lane,lean,lena,neal evil,levi,live,veil,vile
AutoHotkey
contributed by Laszlo on the ahk forum <lang AutoHotkey>MsgBox % anagrams("able")
anagrams(word) {
Static dict IfEqual dict,, FileRead dict, unixdict.txt ; file in the script directory w := sort(word) Loop Parse, dict, `n, `r If (w = sort(A_LoopField)) t .= A_LoopField "`n" Return t
}
sort(word) {
a := RegExReplace(word,".","$0`n") Sort a Return a
}</lang>
C
<lang c>#include <stdio.h>
- include <stdlib.h>
- include <string.h>
- include <ctype.h>
- include <time.h>
char *sortedWord(const char *word, char *wbuf) {
char *p1, *p2, *endwrd; char t; int swaps;
strcpy(wbuf, word); endwrd = wbuf+strlen(wbuf); do { swaps = 0; p1 = wbuf; p2 = endwrd-1; while (p1<p2) { if (*p2 > *p1) { t = *p2; *p2 = *p1; *p1 = t; swaps = 1; } p1++; p2--; } p1 = wbuf; p2 = p1+1; while(p2 < endwrd) { if (*p2 > *p1) { t = *p2; *p2 = *p1; *p1 = t; swaps = 1; } p1++; p2++; } } while (swaps); return wbuf;
}
static short cxmap[] = {
0x06, 0x1f, 0x4d, 0x0c, 0x5c, 0x28, 0x5d, 0x0e, 0x09, 0x33, 0x31, 0x56, 0x52, 0x19, 0x29, 0x53, 0x32, 0x48, 0x35, 0x55, 0x5e, 0x14, 0x27, 0x24, 0x02, 0x3e, 0x18, 0x4a, 0x3f, 0x4c, 0x45, 0x30, 0x08, 0x2c, 0x1a, 0x03, 0x0b, 0x0d, 0x4f, 0x07, 0x20, 0x1d, 0x51, 0x3b, 0x11, 0x58, 0x00, 0x49, 0x15, 0x2d, 0x41, 0x17, 0x5f, 0x39, 0x16, 0x42, 0x37, 0x22, 0x1c, 0x0f, 0x43, 0x5b, 0x46, 0x4b, 0x0a, 0x26, 0x2e, 0x40, 0x12, 0x21, 0x3c, 0x36, 0x38, 0x1e, 0x01, 0x1b, 0x05, 0x4e, 0x44, 0x3d, 0x04, 0x10, 0x5a, 0x2a, 0x23, 0x34, 0x25, 0x2f, 0x2b, 0x50, 0x3a, 0x54, 0x47, 0x59, 0x13, 0x57, };
- define CXMAP_SIZE (sizeof(cxmap)/sizeof(short))
int Str_Hash( const char *key, int ix_max )
{
const char *cp; short mash; int hash = 33501551; for (cp = key; *cp; cp++) { mash = cxmap[*cp % CXMAP_SIZE]; hash = (hash >>4) ^ 0x5C5CF5C ^ ((hash<<1) + (mash<<5)); hash &= 0x3FFFFFFF; } return hash % ix_max;
}
typedef struct sDictWord *DictWord; struct sDictWord {
const char *word; DictWord next;
};
typedef struct sHashEntry *HashEntry; struct sHashEntry {
const char *key; HashEntry next; DictWord words; HashEntry link; short wordCount;
};
- define HT_SIZE 8192
HashEntry hashTable[HT_SIZE];
HashEntry mostPerms = NULL;
int buildAnagrams( FILE *fin ) {
char buffer[40]; char bufr2[40]; char *hkey; int hix; HashEntry he, *hep; DictWord we; int maxPC = 2; int numWords = 0; while ( fgets(buffer, 40, fin)) { for(hkey = buffer; *hkey && (*hkey!='\n'); hkey++); *hkey = 0; hkey = sortedWord(buffer, bufr2); hix = Str_Hash(hkey, HT_SIZE); he = hashTable[hix]; hep = &hashTable[hix]; while( he && strcmp(he->key , hkey) ) { hep = &he->next; he = he->next; } if ( ! he ) { he = malloc(sizeof(struct sHashEntry)); he->next = NULL; he->key = strdup(hkey); he->wordCount = 0; he->words = NULL; he->link = NULL; *hep = he; } we = malloc(sizeof(struct sDictWord)); we->word = strdup(buffer); we->next = he->words; he->words = we; he->wordCount++; if ( maxPC < he->wordCount) { maxPC = he->wordCount; mostPerms = he; he->link = NULL; } else if (maxPC == he->wordCount) { he->link = mostPerms; mostPerms = he; } numWords++; } printf("%d words in dictionary max ana=%d\n", numWords, maxPC); return maxPC;
}
int main( )
{
HashEntry he; DictWord we; FILE *f1; f1 = fopen("unixdict.txt","r"); buildAnagrams(f1); fclose(f1); f1 = fopen("anaout.txt","w");
// f1 = stdout;
for (he = mostPerms; he; he = he->link) { fprintf(f1,"%d:", he->wordCount); for(we = he->words; we; we = we->next) { fprintf(f1,"%s, ", we->word); } fprintf(f1, "\n"); }
fclose(f1); return 0;
}</lang> Output: (less than 1 second on old P500)
5:vile, veil, live, levi, evil, 5:trace, crate, cater, carte, caret, 5:regal, large, lager, glare, alger, 5:neal, lena, lean, lane, elan, 5:lange, glean, galen, angle, angel, 5:elba, bela, bale, able, abel,
C++
<lang cpp>#include <iostream>
- include <fstream>
- include <string>
- include <map>
- include <vector>
- include <algorithm>
int main() {
std::ifstream in("unixdict.txt"); typedef std::map<std::string, std::vector<std::string> > AnagramMap; AnagramMap anagrams; std::string word; size_t count = 0; while (std::getline(in, word)) { std::string key = word; std::sort(key.begin(), key.end()); // note: the [] op. automatically inserts a new value if key does not exist AnagramMap::mapped_type & v = anagrams[key]; v.push_back(word); count = std::max(count, v.size()); } in.close(); for (AnagramMap::const_iterator it = anagrams.begin(), e = anagrams.end(); it != e; it++) if (it->second.size() >= count) { std::copy(it->second.begin(), it->second.end(), std::ostream_iterator<std::string>(std::cout, ", ")); std::cout << std::endl; } return 0;
}</lang> Output:
abel, able, bale, bela, elba, caret, carte, cater, crate, trace, angel, angle, galen, glean, lange, alger, glare, lager, large, regal, elan, lane, lean, lena, neal, evil, levi, live, veil, vile,
C#
<lang csharp> using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO;
namespace Anagram {
class Program { static void Main(string[] args) { var words = File.ReadAllLines("unixdict.txt"); var groups = from w in words group w by new String(w.ToCharArray().OrderBy(x => x).ToArray()) into c where c.Count() > 1 orderby c.Count() descending select c; groups.ToList().ForEach(x => Console.WriteLine(String.Join(",", x.ToArray()))); } }
} </lang>
output:
abel,able,bale,bela,elba alger,glare,lager,large,regal angel,angle,galen,glean,lange caret,carte,cater,crate,trace elan,lane,lean,lena,neal evil,levi,live,veil,vile abet,bate,beat,beta aden,dane,dean,edna aires,aries,arise,raise amen,mane,mean,name ames,mesa,same,seam apt,pat,pta,tap are,ear,era,rae ate,eat,eta,tea beard,bread,debar,debra cereus,recuse,rescue,secure dare,dear,erda,read diet,edit,tide,tied ... etc
Clojure
Assume wordfile is the path of the local file containing the words. This code makes a map (groups) whose keys are sorted letters and values are lists of the key's anagrams. It then determines the length of the longest list, and prints out all the lists of that length. <lang clojure>(import '(java.io FileReader BufferedReader))
(def words (-> wordfile FileReader. BufferedReader. line-seq))
- in Clojure 1.2 this is simpler
- (def groups (group-by sort words))
(let [f (fn [tbl word] (update-in tbl [(sort word)] conj word))]
(def groups (reduce f {} words)))
(let [wordlists (vals groups)
longest (apply max (map count wordlists))] (doseq [wordlist wordlists :when (= (count wordlist) longest)] (println wordlist)))
</lang> Here's another version, using various Clojure 1.2 features (clojure.java.io, group-by) <lang clojure>(require '[clojure.java.io :as io]) (def groups
(with-open [r (io/reader wordfile)] (group-by sort (line-seq r)))
(let [wordlists (sort-by (comp - count) (vals groups)
maxlength (count (first wordlists))] (doseq [wordlist (take-while #(= (count %) maxlength) wordlists)] (println wordlist))
Common Lisp
to retrieve the wordlist.
<lang lisp>(defun anagrams (&optional (url "http://www.puzzlers.org/pub/wordlists/unixdict.txt"))
(let ((words (drakma:http-request url :want-stream t)) (wordsets (make-hash-table :test 'equalp))) ;; populate the wordsets and close stream (do ((word (read-line words nil nil) (read-line words nil nil))) ((null word) (close words)) (let ((letters (sort (copy-seq word) 'char<))) (multiple-value-bind (pair presentp) (gethash letters wordsets) (if presentp (setf (car pair) (1+ (car pair)) (cdr pair) (cons word (cdr pair))) (setf (gethash letters wordsets) (cons 1 (list word))))))) ;; find and return the biggest wordsets (loop with maxcount = 0 with maxwordsets = '() for pair being each hash-value of wordsets if (> (car pair) maxcount) do (setf maxcount (car pair) maxwordsets (list (cdr pair))) else if (eql (car pair) maxcount) do (push (cdr pair) maxwordsets) finally (return (values maxwordsets maxcount)))))</lang>
Evalutating
<lang lisp>(multiple-value-bind (wordsets count) (anagrams)
(pprint wordsets) (print count))</lang>
produces the following output.
(("vile" "veil" "live" "levi" "evil") ("regal" "large" "lager" "glare" "alger") ("lange" "glean" "galen" "angle" "angel") ("neal" "lena" "lean" "lane" "elan") ("trace" "crate" "cater" "carte" "caret") ("elba" "bela" "bale" "able" "abel")) 5
D
D 1, using Phobos (to download the word list you need the Tango Std Lib). <lang d>import std.stdio, std.stream;
void main() {
string[][string] anags; int lmax; foreach (string w; new BufferedFile("unixdict.txt")) { string wrd = w.dup; string key = wrd.sort; anags[key] ~= wrd; int len = anags[key].length; lmax = lmax < len ? len : lmax; } foreach (a; anags) { if (a.length == lmax) { writefln(a); } }
}</lang>
D1 with Phobos and scrapple.tools extension library <lang d>import std.stdio, std.stream, tools.functional, tools.base;
void main() {
( (new BufferedFile("/usr/share/dict/cracklib-small")) /map/ ex!("s -> s.dup") /groupby/ ex!("s -> s.dup.sort") /map/ (string key, string[] value) { return value; } /qsort/ ex!("a, b -> a.length < b.length") )[$-1].writefln();
} </lang>
E
<lang e>println("Downloading...") when (def wordText := <http://www.puzzlers.org/pub/wordlists/unixdict.txt> <- getText()) -> {
def words := wordText.split("\n")
def storage := [].asMap().diverge() def anagramTable extends storage { to get(key) { return storage.fetch(key, fn { storage[key] := [].diverge() }) } }
println("Grouping...") var largestGroupSeen := 0 for word in words { def anagramGroup := anagramTable[word.sort()] anagramGroup.push(word) largestGroupSeen max= anagramGroup.size() }
println("Selecting...") for _ => anagramGroup ? (anagramGroup.size() == mostSeen) in anagramTable { println(anagramGroup.snapshot()) }
}</lang>
F#
Read the lines in the dictionary, group by the sorted letters in each word, find the length of the longest sets of anagrams, extract the longest sequences of words sharing the same letters (i.e. anagrams): <lang fsharp>let xss = Seq.groupBy (Array.ofSeq >> Array.sort) (System.IO.File.ReadAllLines "unixdict.txt") Seq.map snd xss |> Seq.filter (Seq.length >> ( = ) (Seq.map (snd >> Seq.length) xss |> Seq.max))</lang> Note that it is necessary to convert the sorted letters in each word from sequences to arrays because the groupBy function uses the default comparison and sequences do not compare structurally (but arrays do in F#).
Takes 0.8s to return: <lang fsharp> val it : string seq seq =
seq [seq ["abel"; "able"; "bale"; "bela"; "elba"]; seq ["alger"; "glare"; "lager"; "large"; "regal"]; seq ["angel"; "angle"; "galen"; "glean"; "lange"]; seq ["caret"; "carte"; "cater"; "crate"; "trace"]; seq ["elan"; "lane"; "lean"; "lena"; "neal"]; seq ["evil"; "levi"; "live"; "veil"; "vile"]]
</lang>
Factor
"resource:unixdict.txt" utf8 file-lines [ [ natural-sort >string ] keep ] { } map>assoc sort-keys [ [ first ] compare +eq+ = ] monotonic-split dup 0 [ length max ] reduce '[ length _ = ] filter [ values ] map .
<lang factor>{
{ "abel" "able" "bale" "bela" "elba" } { "caret" "carte" "cater" "crate" "trace" } { "angel" "angle" "galen" "glean" "lange" } { "alger" "glare" "lager" "large" "regal" } { "elan" "lane" "lean" "lena" "neal" } { "evil" "levi" "live" "veil" "vile" }
}</lang>
Groovy
This program: <lang groovy>def words = new URL('http://www.puzzlers.org/pub/wordlists/unixdict.txt').text.readLines() def groups = words.groupBy{ it.toList().sort() } def bigGroupSize = groups.collect{ it.value.size() }.max() def isBigAnagram = { it.value.size() == bigGroupSize } println groups.findAll(isBigAnagram).collect{ it.value }.collect{ it.join(' ') }.join('\n')</lang> produces this output:
abel able bale bela elba alger glare lager large regal angel angle galen glean lange caret carte cater crate trace elan lane lean lena neal evil levi live veil vile
Haskell
<lang haskell>import Data.List
groupon f x y = f x == f y
main = do
f <- readFile "./../Puzzels/Rosetta/unixdict.txt" let words = lines f wix = groupBy (groupon fst) . sort $ zip (map sort words) words mxl = maximum $ map length wix mapM_ (print . map snd) . filter ((==mxl).length) $ wix</lang>
Sample output: <lang haskell>*Main> main ["abel","able","bale","bela","elba"] ["caret","carte","cater","crate","trace"] ["angel","angle","galen","glean","lange"] ["alger","glare","lager","large","regal"] ["elan","lane","lean","lena","neal"] ["evil","levi","live","veil","vile"]</lang>
Icon and Unicon
Icon
<lang icon> procedure main(args)
every writeSet(!getLongestAnagramSets()
end
procedure getLongestAnagramSets()
wordSets := table() longestWSet := 0 longSets := set()
every word := !&input do { wChars := csort(word) /wordSets[wChars] := set() insert(wordSets[wChars], word)
if 1 < *wordSets[wChars} == longestWSet then insert(longSets, wordSets[wChars]) if 1 < *wordSets[wChars} > longestWSet then { longestWSet := *wordSets[wChars} longSets := set([wordSets[wChars]]) } }
return longSets
end
procedure writeSet(words)
every writes("\t"|!words," ") write()
end
procedure csort(w)
every (s := "") ||:= (find(c := !cset(w),w),c) return s
end</lang>
Sample run:
->an <unixdict.txt abel bale bela able elba lean neal elan lane lena angle galen lange angel glean alger glare lager large regal veil evil levi live vile caret cater crate carte trace ->
Unicon
The Icon solution works in Unicon.
J
<lang j> (#~ a: ~: {:"1) (]/.~ /:~&>) <;._2 ] 1!:1 <'unixdict.txt' +-----+-----+-----+-----+-----+ |abel |able |bale |bela |elba | +-----+-----+-----+-----+-----+ |alger|glare|lager|large|regal| +-----+-----+-----+-----+-----+ |angel|angle|galen|glean|lange| +-----+-----+-----+-----+-----+ |caret|carte|cater|crate|trace| +-----+-----+-----+-----+-----+ |elan |lane |lean |lena |neal | +-----+-----+-----+-----+-----+ |evil |levi |live |veil |vile | +-----+-----+-----+-----+-----+</lang>
Explanation:
<lang J> <;._2 ] 1!:1 <'unixdict.txt'</lang>
This reads in the dictionary and produces a list of boxes. Each box contains one line (one word) from the dictionary.
<lang J> (]/.~ /:~&>)</lang>
This groups the words into rows where anagram equivalents appear in the same row.
<lang J> (#~ a: ~: {:"1)</lang>
This selects rows whose last element is not an empty box.
Java
The key to this algorithm is the sorting of the characters in each word from the dictionary. The line Arrays.sort(chars); sorts all of the letters in the word in ascending order using a built-in quicksort, so all of the words in the first group in the result end up under the key "aegln" in the anagrams map. <lang java5>import java.net.*; import java.io.*; import java.util.*;
public class WordsOfEqChars {
public static void main(String[] args) throws IOException { URL url = new URL("http://www.puzzlers.org/pub/wordlists/unixdict.txt"); InputStreamReader isr = new InputStreamReader(url.openStream()); BufferedReader reader = new BufferedReader(isr);
Map<String, Collection<String>> anagrams = new HashMap<String, Collection<String>>(); String word; int count = 0; while ((word = reader.readLine()) != null) { char[] chars = word.toCharArray(); Arrays.sort(chars); String key = new String(chars); if (!anagrams.containsKey(key)) anagrams.put(key, new ArrayList<String>()); anagrams.get(key).add(word); count = Math.max(count, anagrams.get(key).size()); }
reader.close();
for (Collection<String> ana : anagrams.values()) if (ana.size() >= count) System.out.println(ana); }
}</lang> Output:
[angel, angle, galen, glean, lange] [elan, lane, lean, lena, neal] [alger, glare, lager, large, regal] [abel, able, bale, bela, elba] [evil, levi, live, veil, vile] [caret, carte, cater, crate, trace]
Liberty BASIC
<lang lb>a$ = "large"
open "unixdict.txt" for input as #1 list$ = Input$(#1, lof(#1)) close #1 tmp$ = permutation$(list$, "", a$) print "** End of List **"
end
function permutation$(list$, pre$, post$)
lgth = len(post$) if lgth < 2 then if instr(list$, chr$(10);pre$;post$;chr$(10)) > 0 then print pre$;post$ end if else for i = 1 to lgth tmp$ = permutation$(list$, pre$ + mid$(post$, i, 1), left$(post$, i - 1) + right$(post$, lgth - i)) next i end if
end function</lang>
Lua
Lua's core library is very small and does not include built-in network functionality. If a networking library were imported, the local file in the following script could be replaced with the remote dictionary file. This may or may not be a good implementation, but I thought the method was interesting. <lang lua>-- Build the word set local set = {} local file = io.open("unixdict.txt") local str = file:read() while str do
table.insert(set,str) str = file:read()
end
-- Build the anagram tree local tree = {} for i,word in next,set do
-- Sort a string from lowest char to highest local function sortString(str) if #str <= 1 then return str end local less = local greater = local pivot = str:byte(1) for i = 2, #str do if str:byte(i) <= pivot then less = less..(str:sub(i,i)) else greater = greater..(str:sub(i,i)) end end return sortString(less)..str:sub(1,1)..sortString(greater) end local sortchar = sortString(word) if not tree[#word] then tree[#word] = {} end local node = tree[#word] for i = 1,#word do if not node[sortchar:byte(i)] then node[sortchar:byte(i)] = {} end node = node[sortchar:byte(i)] end table.insert(node,word)
end
-- Gather largest groups by gathering all groups of current max size and droping gathered groups and increasing max when a new largest group is found local max = 0 local set = {} local function recurse (tree)
local num = 0 for i,node in next,tree do if type(node) == 'string' then num = num + 1 end end if num > max then set = {} max = num end if num == max then local newset = {} for i,node in next,tree do if type(node) == 'string' then table.insert(newset,node) end end table.insert(set,newset) end for i,node in next,tree do if type(node) == 'table' then recurse(node) end end
end
recurse (tree) for i,v in next,set do io.write (i..':\t')for j,u in next,v do io.write (u..' ') end print() end</lang>
M4
<lang M4>divert(-1) changequote(`[',`]') define([for],
[ifelse($#,0,$0, [ifelse(eval($2<=$3),1, [pushdef([$1],$2)$4[]popdef([$1])$0([$1],incr($2),$3,[$4])])])])
define([_bar],include(t.txt)) define([eachlineA],
[ifelse(eval($2>0),1, [$3(substr([$1],0,$2))[]eachline(substr([$1],incr($2)),[$3])])])
define([eachline],[eachlineA([$1],index($1,[ ]),[$2])]) define([removefirst],
[substr([$1],0,$2)[]substr([$1],incr($2))])
define([checkfirst],
[ifelse(eval(index([$2],substr([$1],0,1))<0),1, 0, [ispermutation(substr([$1],1), removefirst([$2],index([$2],substr([$1],0,1))))])])
define([ispermutation],
[ifelse([$1],[$2],1, eval(len([$1])!=len([$2])),1,0, len([$1]),0,0, [checkfirst([$1],[$2])])])
define([_set],[define($1<$2>,$3)]) define([_get],[defn([$1<$2>])]) define([_max],1) define([_n],0) define([matchj],
[_set([count],$2,incr(_get([count],$2)))[]ifelse(eval(_get([count],$2)>_max), 1,[define([_max],incr(_max))])[]_set([list],$2,[_get([list],$2) $1])])
define([checkwordj],
[ifelse(ispermutation([$1],_get([word],$2)),1,[matchj([$1],$2)], [addwordj([$1],incr($2))])])
define([_append],
[_set([word],_n,[$1])[]_set([count],_n,1)[]_set([list],_n, [$1 ])[]define([_n],incr(_n))])
define([addwordj],
[ifelse($2,_n,[_append([$1])],[checkwordj([$1],$2)])])
define([addword],
[addwordj([$1],0)])
divert eachline(_bar,[addword]) _max for([x],1,_n,[ifelse(_get([count],x),_max,[_get([list],x) ])])</lang>
Memory limitations keep this program from working on the full-sized dictionary. Run against the first 100 words, here is the output:
2 abel able aboard abroad
Mathematica
Download the dictionary, split the lines, split the word in characters and sort them. Now sort by those words, and find sequences of equal 'letter-hashes'. Return the longest sequences: <lang Mathematica>list=Import["http://www.puzzlers.org/pub/wordlists/unixdict.txt","Lines"]; text={#,StringJoin@@Sort[Characters[#]]}&/@list; text=SortBy[text,#2&]; splits=Split[text,#12==#22&]All,All,1; maxlen=Max[Length/@splits]; Select[splits,Length[#]==maxlen&]</lang> gives back: <lang Mathematica>{{abel,able,bale,bela,elba},{caret,carte,cater,crate,trace},{angel,angle,galen,glean,lange},{alger,glare,lager,large,regal},{elan,lane,lean,lena,neal},{evil,levi,live,veil,vile}}</lang>
An alternative is faster, but requires version 7 (for Gather
):
<lang Mathematica>splits = Gather[list, Sort[Characters[#]] == Sort[Characters[#2]] &];
maxlen = Max[Length /@ splits];
Select[splits, Length[#] == maxlen &]</lang>
Also, Mathematica's own word list is available; replacing the list definition with list = WordData[];
and forcing maxlen
to 5 yields instead this result:
{{angered,derange,enraged,grandee,grenade}, {anisometric,creationism,miscreation,reactionism,romanticise}, {aper,pare,pear,rape,reap}, {ardeb,barde,bared,beard,bread,debar}, {aril,lair,lari,liar,lira,rail,rial}, {aster,rates,stare,tears,teras}, {caret,carte,cater,crate,react,trace}, {east,eats,sate,seat,seta}, {ester,reset,steer,teres,terse}, {inert,inter,niter,nitre,trine}, {latrine,ratline,reliant,retinal,trenail}, {least,slate,stale,steal,stela,tesla}, {luster,lustre,result,rustle,sutler,ulster}, {merit,miter,mitre,remit,timer}, {part,prat,rapt,tarp,trap}, {resin,rinse,risen,serin,siren}, {respect,scepter,sceptre,specter,spectre}}
MUMPS
<lang MUMPS>Anagrams New ii,file,longest,most,sorted,word Set file="unixdict.txt" Open file:"r" Use file For Quit:$ZEOF DO . New char,sort . Read word Quit:word="" . For ii=1:1:$Length(word) Do . . Set char=$ASCII(word,ii) . . If char>64,char<91 Set char=char+32 . . Set sort(char)=$Get(sort(char))+1 . . Quit . Set (sorted,char)="" For Set char=$Order(sort(char)) Quit:char="" Do . . For ii=1:1:sort(char) Set sorted=sorted_$Char(char) . . Quit . Set table(sorted,word)=1 . Quit Close file Set sorted="" For Set sorted=$Order(table(sorted)) Quit:sorted="" Do . Set ii=0,word="" For Set word=$Order(table(sorted,word)) Quit:word="" Set ii=ii+1 . Quit:ii<2 . Set most(ii,sorted)=1 . Quit Write !,"The anagrams with the most variations:" Set ii=$Order(most(""),-1) Set sorted="" For Set sorted=$Order(most(ii,sorted)) Quit:sorted="" Do . Write ! Set word="" For Set word=$Order(table(sorted,word)) Quit:word="" Write " ",word . Quit Write !,"The longest anagrams:" Set ii=$Order(longest(""),-1) Set sorted="" For Set sorted=$Order(longest(ii,sorted)) Quit:sorted="" Do . Write ! Set word="" For Set word=$Order(table(sorted,word)) Quit:word="" Write " ",word . Quit Quit
Do Anagrams
The anagrams with the most variations:
abel able bale bela elba caret carte cater crate trace angel angle galen glean lange alger glare lager large regal elan lane lean lena neal evil levi live veil vile
The longest anagrams:
conservation conversation</lang>
OCaml
<lang ocaml>let explode str =
let l = ref [] in let len = String.length str in for i = len - 1 downto 0 do l := str.[i] :: !l done; (!l)
let implode li =
let len = List.length li in let s = String.create len in let i = ref 0 in List.iter (fun c -> s.[!i] <- c; incr i) li; (s)
let () =
let h = Hashtbl.create 3571 in let ic = open_in "unixdict.txt" in try while true do let w = input_line ic in let k = implode(List.sort compare (explode w)) in let l = try Hashtbl.find h k with Not_found -> [] in Hashtbl.add h k (w::l); done with End_of_file -> (); let n = Hashtbl.fold (fun _ lw n -> max n (List.length lw)) h 0 in Hashtbl.iter (fun _ lw -> if List.length lw >= n then ( List.iter (Printf.printf " %s") lw; print_newline()) ) h;
- </lang>
Oz
<lang oz>declare
%% Helper function fun {ReadLines Filename} File = {New class $ from Open.file Open.text end init(name:Filename)} in for collect:C break:B do
case {File getS($)} of false then {File close} {B} [] Line then {C Line}
end end end
%% Groups anagrams by using a mutable dictionary %% with sorted words as keys WordDict = {Dictionary.new} for Word in {ReadLines "unixdict.txt"} do Keyword = {String.toAtom {Sort Word Value.'<'}} in WordDict.Keyword := Word|{CondSelect WordDict Keyword nil} end Sets = {Dictionary.items WordDict}
%% Filter such that only the largest sets remain MaxSetSize = {FoldL {Map Sets Length} Max 0} LargestSets = {Filter Sets fun {$ S} {Length S} == MaxSetSize end}
in
%% Display result (make sure strings are shown as string, not as number lists) {Inspector.object configureEntry(widgetShowStrings true)} {Inspect LargestSets}</lang>
Perl
<lang perl>use LWP::Simple; use List::Util qw(max);
my @words = split(' ', get('http://www.puzzlers.org/pub/wordlists/unixdict.txt')); my %anagram; foreach my $word (@words) {
push @{ $anagram{join(, sort(split(//, $word)))} }, $word;
}
my $count = max(map {scalar @$_} values %anagram); foreach my $ana (values %anagram) {
if (@$ana >= $count) { print "@$ana\n"; }
}</lang> refactor of above: <lang perl>use LWP::Simple;
for (split ' ' => get 'http://www.puzzlers.org/pub/wordlists/unixdict.txt')
{push @{$anagram{ join => sort split // }}, $_}
$max > @$_ or $max = @$_ for values %anagram; @$_ >= $max and print "@$_\n" for values %anagram;</lang> Output:
alger glare lager large regal abel able bale bela elba evil levi live veil vile angel angle galen glean lange elan lane lean lena neal caret carte cater crate trace
PHP
<lang php><?php $words = explode("\n", file_get_contents('http://www.puzzlers.org/pub/wordlists/unixdict.txt')); foreach ($words as $word) {
$chars = str_split($word); sort($chars); $anagram[implode($chars)][] = $word;
}
$best = max(array_map('count', $anagram)); foreach ($anagram as $ana)
if (count($ana) == $best) print_r($ana);
?></lang>
PicoLisp
A straight-forward implementation using 'group' takes 48 seconds on a 1.7 GHz Pentium: <lang PicoLisp>(flip
(by length sort (by '((L) (sort (copy L))) group (in "unixdict.txt" (make (while (line) (link @)))) ) ) )</lang>
Using a binary tree with the 'idx' function, it takes only 0.42 seconds on the same machine, a factor of 100 faster: <lang PicoLisp>(let Words NIL
(in "unixdict.txt" (while (line) (let (Word (pack @) Key (pack (sort @))) (if (idx 'Words Key T) (push (car @) Word) (set Key (list Word)) ) ) ) ) (flip (by length sort (mapcar val (idx 'Words)))) )</lang>
Output:
-> (("vile" "veil" "live" "levi" "evil") ("trace" "crate" "cater" "carte" "caret ") ("regal" "large" "lager" "glare" "alger") ("neal" "lena" "lean" "lane" "elan" ) ("lange" "glean" "galen" "angle" "angel") ("elba" "bela" "bale" "able" "abel") ("tulsa" "talus" "sault" "latus") ...
PL/I
<lang PL/I> /* Search a list of words, finding those having the same letters. */
word_test: proc options (main);
declare words (50000) character (20) varying, frequency (50000) fixed binary; declare word character (20) varying; declare (i, k, wp, most) fixed binary (31);
on endfile (sysin) go to done;
words = ; frequency = 0; wp = 0; do forever; get edit (word) (L); call search_word_list (word); end;
done:
put skip list ('There are ' || wp || ' words'); most = 0; /* Determine the word(s) having the greatest number of anagrams. */ do i = 1 to wp; if most < frequency(i) then most = frequency(i); end; put skip edit ('The following word(s) have ', trim(most), ' anagrams:') (a); put skip; do i = 1 to wp; if most = frequency(i) then put edit (words(i)) (x(1), a); end;
search_word_list: procedure (word) options (reorder);
declare word character (*) varying; declare i fixed binary (31);
do i = 1 to wp; if length(words(i)) = length(word) then if is_anagram(word, words(i)) then do; frequency(i) = frequency(i) + 1; return; end; end; /* The word does not exist in the list, so add it. */ if wp >= hbound(words,1) then return; wp = wp + 1; words(wp) = word; frequency(wp) = 1; return;
end search_word_list;
/* Returns true if the words are anagrams, otherwise returns false. */ is_anagram: procedure (word1, word2) returns (bit(1)) options (reorder);
declare (word1, word2) character (*) varying; declare tword character (20) varying, c character (1); declare (i, j) fixed binary;
tword = word2; do i = 1 to length(word1); c = substr(word1, i, 1); j = index(tword, c); if j = 0 then return ('0'b); substr(tword, j, 1) = ' '; end; return ('1'b);
end is_anagram;
end word_test; </lang> Output: <lang> There are 23565 words The following word(s) have 5 anagrams:
abel alger angel caret elan evil
</lang>
PowerShell
<lang powershell>$c = New-Object Net.WebClient $words = -split ($c.DownloadString('http://www.puzzlers.org/pub/wordlists/unixdict.txt')) $top_anagrams = $words `
| ForEach-Object { $_ | Add-Member -PassThru NoteProperty Characters ` (-join (([char[]] $_) | Sort-Object)) } ` | Group-Object Characters ` | Group-Object Count ` | Sort-Object Count ` | Select-Object -First 1
$top_anagrams.Group | ForEach-Object { $_.Group -join ', ' }</lang> Output:
abel, able, bale, bela, elba alger, glare, lager, large, regal angel, angle, galen, glean, lange caret, carte, cater, crate, trace elan, lane, lean, lena, neal evil, levi, live, veil, vile
Prolog
<lang Prolog>:- use_module(library( http/http_open )).
anagrams:-
% we read the URL of the words
http_open('http://www.puzzlers.org/pub/wordlists/unixdict.txt', In, []), read_file(In, [], Out), close(In),
% we get a list of pairs key-value where key = a-word value = <list-of-its-codes> % this list must be sorted
msort(Out, MOut),
% in order to gather values with the same keys
group_pairs_by_key(MOut, GPL),
% we sorted this list in decreasing order of the length of values
predsort(my_compare, GPL, GPLSort),
% we extract the first 6 items
GPLSort = [_H1-T1, _H2-T2, _H3-T3, _H4-T4, _H5-T5, _H6-T6 | _],
% Tnn are lists of codes (97 for 'a'), we create the strings
maplist(maplist(atom_codes), L, [T1, T2, T3, T4, T5, T6] ),
maplist(writeln, L).
read_file(In, L, L1) :-
read_line_to_codes(In, W),
( W == end_of_file ->
% the file is read
L1 = L ;
% we sort the list of codes of the line
msort(W, W1),
% to create the key in alphabetic order
atom_codes(A, W1),
% and we have the pair Key-Value in the result list
read_file(In, [A-W | L], L1)).
% predicate for sorting list of pairs Key-Values % if the lentgh of values is the same % we sort the keys in alhabetic order my_compare(R, K1-V1, K2-V2) :- length(V1, L1), length(V2, L2), ( L1 < L2 -> R = >; L1 > L2 -> R = <; compare(R, K1, K2)). </lang> The result is <lang>[abel,able,bale,bela,elba] [caret,carte,cater,crate,trace] [angel,angle,galen,glean,lange] [alger,glare,lager,large,regal] [elan,lane,lean,lena,neal] [evil,levi,live,veil,vile] true</lang>
PureBasic
<lang PureBasic>InitNetwork() ; OpenConsole()
Procedure.s sortWord(word$)
len.i = Len(word$) Dim CharArray.s (len) For n = 1 To len ; Transfering each single character CharArray(n) = Mid(word$, n, 1) ; of the word into an array. Next SortArray(CharArray(),#PB_Sort_NoCase ) ; Sorting the array. word$ ="" For n = 1 To len ; Writing back each single word$ + CharArray(n) ; character of the array. Next ProcedureReturn word$
EndProcedure
tmpdir$ = GetTemporaryDirectory()
filename$ = tmpdir$ + "unixdict.txt"
Structure ana
isana.l anas.s
EndStructure
NewMap anaMap.ana()
If ReceiveHTTPFile("http://www.puzzlers.org/pub/wordlists/unixdict.txt", filename$)
If ReadFile(1, filename$) Repeat word$ = (ReadString(1)) ; Reading a word from a file. key$ = (sortWord(word$)) ; Sorting the word and storing in key$. If FindMapElement(anaMap(), key$) ; Looking up if a word already had the same key$. ; if yes anaMap()\anas = anaMap()\anas+ ", " + word$ ; adding the word anaMap()\isana + 1 Else ; if no anaMap(key$)\anas = word$ ; applying a new record anaMap()\isana + 1 EndIf Until Eof(1) CloseFile(1) DeleteFile(filename$) ;----- output ----- ForEach anaMap() If anaMap()\isana >= 4 ; only emit what had 4 or more hits. PrintN(anaMap()\anas) EndIf Next PrintN("Press any key"): Repeat: Until Inkey() <> "" EndIf
EndIf </lang>
Python
Python 2.5 shell input (IDLE) <lang python>>>> import urllib >>> from collections import defaultdict >>> words = urllib.urlopen('http://www.puzzlers.org/pub/wordlists/unixdict.txt').read().split() >>> len(words) 25104 >>> anagram = defaultdict(list) # map sorted chars to anagrams >>> for word in words: anagram[tuple(sorted(word))].append( word )
>>> count = max(len(ana) for ana in anagram.itervalues())
>>> for ana in anagram.itervalues():
if len(ana) >= count:
print ana
['angel', 'angle', 'galen', 'glean', 'lange']
['alger', 'glare', 'lager', 'large', 'regal']
['caret', 'carte', 'cater', 'crate', 'trace']
['evil', 'levi', 'live', 'veil', 'vile']
['elan', 'lane', 'lean', 'lena', 'neal']
['abel', 'able', 'bale', 'bela', 'elba']
>>> count
5
>>></lang>
sort and then group using groupby()
<lang python>>>> import urllib, itertools >>> words = urllib.urlopen('http://www.puzzlers.org/pub/wordlists/unixdict.txt').read().split() >>> len(words) 25104 >>> anagrams = [list(g) for k,g in itertools.groupby(sorted(words, key=sorted), key=sorted)]
>>> count = max(len(ana) for ana in anagrams)
>>> for ana in anagrams:
if len(ana) >= count:
print ana
['abel', 'able', 'bale', 'bela', 'elba']
['caret', 'carte', 'cater', 'crate', 'trace']
['angel', 'angle', 'galen', 'glean', 'lange']
['alger', 'glare', 'lager', 'large', 'regal']
['elan', 'lane', 'lean', 'lena', 'neal']
['evil', 'levi', 'live', 'veil', 'vile']
>>> count
5
>>></lang>
R
<lang R> words <- readLines("http://www.puzzlers.org/pub/wordlists/unixdict.txt") word_group <- sapply(
strsplit(words, split=""), # this will split all words to single letters... function(x) paste(sort(x), collapse="") # ...which we sort and paste again
)
counts <- tapply(words, word_group, length) # group words by class to get number of anagrams anagrams <- tapply(words, word_group, paste, collapse=", ") # group to get string with all anagrams
- Results
table(counts) counts
1 2 3 4 5
22263 1111 155 31 6
anagrams[counts == max(counts)]
abel acert "abel, able, bale, bela, elba" "caret, carte, cater, crate, trace" aegln aeglr
"angel, angle, galen, glean, lange" "alger, glare, lager, large, regal"
aeln eilv "elan, lane, lean, lena, neal" "evil, levi, live, veil, vile"
</lang>
Revolution
<lang revolution>on mouseUp
repeat for each word W in url "http://www.puzzlers.org/pub/wordlists/unixdict.txt" put W & comma after A[sortChars(W)] end repeat put 0 into winningLength repeat for each element E in A get the number of items in E if it < winningLength then next repeat if it > winningLength then put it into winningLength put empty into winningList end if put (char 1 to -2 of E) & cr after winningList end repeat put winningList
end mouseUp
function sortChars X
get charsToItems(X) sort items of it return itemsToChars(it)
end sortChars
function charsToItems X
repeat for each char C in X put C & comma after R end repeat return char 1 to -2 of R
end charsToItems
function itemsToChars X
replace comma with empty in X return X
end itemsToChars</lang> Output:
abel,able,bale,bela,elba elan,lane,lean,lena,neal evil,levi,live,veil,vile caret,carte,cater,crate,trace angel,angle,galen,glean,lange alger,glare,lager,large,regal
Ruby
<lang ruby>require 'open-uri'
anagram = Hash.new {|hash, key| hash[key] = []} # map sorted chars to anagrams
open('http://www.puzzlers.org/pub/wordlists/unixdict.txt') do |f|
words = f.read.split for word in words anagram[word.split().sort] << word end
end
count = anagram.values.map {|ana| ana.length}.max anagram.each_value do |ana|
if ana.length >= count p ana end
end</lang> Output:
["evil", "levi", "live", "veil", "vile"] ["abel", "able", "bale", "bela", "elba"] ["elan", "lane", "lean", "lena", "neal"] ["alger", "glare", "lager", "large", "regal"] ["angel", "angle", "galen", "glean", "lange"] ["caret", "carte", "cater", "crate", "trace"]
<lang ruby>require 'open-uri'
anagram = nil
open('http://www.puzzlers.org/pub/wordlists/unixdict.txt') do |f|
anagram = f.read.split.group_by {|s| s.each_char.sort}
end
count = anagram.each_value.map {|ana| ana.length}.max anagram.each_value do |ana|
if ana.length >= count p ana end
end</lang>
Scala
<lang scala>val lines = scala.io.Source.fromURL("http://www.puzzlers.org/pub/wordlists/unixdict.txt").getLines.map(s=>s.trim())
var map = Map[String, Set[String]]() withDefaultValue Set() def hash(s:String) = new String(s.toList.sort(_<_).toArray) map = lines.foldLeft(map)((map,s) => map(hash(s)) += s)
val max = map.values.foldLeft(0)((a,b) => a.max(b.size))
map.values.filter(l => l.size == max).foreach{l=>l.foreach(s=>print(s+" ")); println()}</lang> Output:
glean angel angle galen lange elan neal lean lane lena regal alger large lager glare able abel bela bale elba vile levi live evil veil caret trace cater carte crate
Smalltalk
<lang Smalltalk>list:= (FillInTheBlank request: 'myMessageBoxTitle') subStrings: String crlf. dict:= Dictionary new. list do: [:val| (dict at: val copy sort ifAbsent: [dict at: val copy sort put: OrderedCollection new]) add: val. ]. sorted:=dict asSortedCollection: [:a :b| a size > b size].</lang> Documentation:
First ask the user for the list. Then create an empty dictionary (a Map). Which maps strings as keys to OrderedCollections as values. For each entry in the list add an entry to the OrderedCollection under the key of the sorted string (and create a new empty OC if there was no previous entry). Then create a SortedCollection sorting by comparing the sizes of the OrderedCollections. The first 6 entries are: an OrderedCollection('evil' 'levi' 'live' 'veil' 'vile') an OrderedCollection('angel' 'angle' 'galen' 'glean' 'lange') an OrderedCollection('alger' 'glare' 'lager' 'large' 'regal') an OrderedCollection('caret' 'carte' 'cater' 'crate' 'trace') an OrderedCollection('abel' 'able' 'bale' 'bela' 'elba') an OrderedCollection('elan' 'lane' 'lean' 'lena' 'neal')
SNOBOL4
Note: unixdict.txt is passed in locally via STDIN. Newlines must be converted for Win/DOS environment.
<lang SNOBOL4>* # Sort letters of word
define('sortw(str)a,i,j') :(sortw_end)
sortw a = array(size(str)) sw1 i = i + 1; str len(1) . a = :s(sw1)
a = sort(a)
sw2 j = j + 1; sortw = sortw a<j> :s(sw2)f(return) sortw_end
- # Count words in string
define('countw(str)') :(countw_end)
countw str break(' ') span(' ') = :f(return)
countw = countw + 1 :(countw)
countw_end
ana = table()
L1 wrd = input :f(L2) ;* unixdict.txt from stdin
sw = sortw(wrd); ana<sw> = ana<sw> wrd ' ' cw = countw(ana<sw>); max = gt(cw,max) cw i = i + 1; terminal = eq(remdr(i,1000),0) wrd :(L1)
L2 kv = convert(ana,'array') L3 j = j + 1; key = kv<j,1>; val = kv<j,2> :f(end)
output = eq(countw(val),max) key ': ' val :(L3)
end</lang>
Output:
abel: abel able bale bela elba aeglr: alger glare lager large regal aegln: angel angle galen glean lange acert: caret carte cater crate trace aeln: elan lane lean lena neal eilv: evil levi live veil vile
Tcl
<lang tcl>package require Tcl 8.5 package require http
set url http://www.puzzlers.org/pub/wordlists/unixdict.txt set response [http::geturl $url] set data [http::data $response] http::cleanup $response
set max 0 array set anagrams {}
foreach line [split $data \n] {
foreach word [split $line] { set anagram [join [lsort [split $word ""]] ""] lappend anagrams($anagram) $word set max [::tcl::mathfunc::max $max [llength $anagrams($anagram)]] }
}
foreach key [array names anagrams] {
if {[llength $anagrams($key)] == $max} { puts $anagrams($key) }
}</lang> Outputs:
evil levi live veil vile caret carte cater crate trace abel able bale bela elba elan lane lean lena neal angel angle galen glean lange alger glare lager large regal
Ursala
Supplying the input file on the command line during compilation makes its contents accessible as a pre-declared identifier. The algorithm is to group the words together that are made from the same unordered lists of letters, then collect the groups together that have the same number of words in them, and then show the collection associated with the highest number. <lang Ursala>#import std
- show+
anagrams = mat` * leql$^&h eql|=@rK2tFlSS ^(~&,-<&)* unixdict_dot_txt</lang> output:
evil levi live veil vile caret carte cater crate trace alger glare lager large regal elan lane lean lena neal angel angle galen glean lange abel able bale bela elba
Vedit macro language
This implementation first sorts characters of each word using Insertion sort in subroutine SORT_LETTERS.
Then the word list is sorted using built-in Sort function.
Finally, groups of words are analyzed and largest groups are recorded.
The word list is expected to be in the same directory as the script.
<lang vedit>File_Open("|(PATH_ONLY)\unixdict.txt")
Repeat(ALL) {
Reg_Copy_Block(10, CP, EOL_Pos) // original word Call("SORT_LETTERS") // sort letters of the word EOL IC(' ') Reg_Ins(10) // add the original word at eol Line(1, ERRBREAK)
}
Sort(0, File_Size) // sort list according to anagrams
BOF Search("|F") Search(' ') // first word in the list Reg_Copy_Block(10, BOL_Pos, CP+1) // reg 10 = sorted anagram word Reg_Copy_Block(11, CP, EOL_Pos) // reg 11 = list of words in current group Reg_Empty(12) // reg 12 = list of words in largest groups Reg_Set(13, " ")
- 1 = 1 // words in this group
- 2 = 2 // words in largest group found
Repeat(ALL) {
Line(1, ERRBREAK) if (Match(@10, ADVANCE) == 0) { // same group as previous word? Reg_Copy_Block(11, CP-1, EOL_Pos, APPEND) // add word to this group #1++ } else { // different anagram group Search(" ", ERRBREAK) if (#1 == #2) { // same size as the largest? Reg_Set(12, @13, APPEND) // append newline Reg_Set(12, @11, APPEND) // append word list } if (#1 > #2) { // new larger size of group Reg_Set(12, @11) // replace word list #2 = #1 } Reg_Copy_Block(10, BOL_Pos, CP+1) Reg_Copy_Block(11, CP, EOL_Pos) // first word of new group #1 = 1 }
}
Buf_Quit(OK) // close word list file Buf_Switch(Buf_Free) // output results in a new edit buffer Reg_Ins(12) // display all groups of longest anagram words Return
//////////////////////////////////////////////////////////////////// // // Sort characters in current line using Insertion sort //
- SORT_LETTERS:
GP(EOL_pos) #9 = Cur_Col-1 for (#1 = 2; #1 <= #9; #1++) {
Goto_Col(#1) #8 = Cur_Char #2 = #1 while (#2 > 1) { #7 = Cur_Char(-1) if (#7 <= #8) { break } Ins_Char(#7, OVERWRITE) #2-- Goto_Col(#2) } Ins_Char(#8, OVERWRITE)
} return</lang>
Output:
abel able bale bela elba caret carte cater crate trace angel angle galen glean lange alger glare lager large regal elan lane lean lena neal evil levi live veil vile