⚠️ Warning: This is a draft ⚠️
This means it might contain formatting issues, incorrect code, conceptual problems, or other severe issues.
If you want to help to improve and eventually enable this page, please fork RosettaGit's repository and open a merge request on GitHub.
{{task}}
Two or more words are said to be [[Anagrams|anagrams]] if they have the same characters, but in a different order.
By analogy with [[Permutations/Derangements|derangements]] we define a ''deranged anagram'' as two words with the same characters, but in which the same character does ''not'' appear in the same position in both words.
{{task heading}}
Use the word list at [http://wiki.puzzlers.org/pub/wordlists/unixdict.txt unixdict] to find and display the longest deranged anagram.
{{task heading|Related tasks}}
- [[Permutations/Derangements]]
- [[Best shuffle]] {{Related tasks/Word plays}}
11l
{{trans|Kotlin}}
F is_not_deranged(s1, s2)
L(i) 0 .< s1.len
I s1[i] == s2[i]
R 1B
R 0B
Dict[String, Array[String]] anagram
V count = 0
L(word) File(‘unixdict.txt’).read().split("\n")
V a = sorted(word).join(‘’)
I a !C anagram
anagram[a] = [word]
E
L(ana) anagram[a]
I is_not_deranged(ana, word)
L.break
L.was_no_break
anagram[a].append(word)
count = max(count, word.len)
L(ana) anagram.values()
I ana.len > 1 & ana[0].len == count
print(ana)
{{out}}
[excitation, intoxicate]
Ada
{{Works with|Ada 2005}}
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Containers.Generic_Array_Sort;
with Ada.Containers.Indefinite_Vectors;
procedure Danagrams is
package StringVector is new Ada.Containers.Indefinite_Vectors
(Positive, String);
procedure StrSort is new Ada.Containers.Generic_Array_Sort
(Index_Type => Positive,
Element_Type => Character,
Array_Type => String);
function Derange (s1 : String; s2 : String) return Boolean is begin
for i in s1'Range loop
if (s1 (i) = s2 (i)) then return False; end if;
end loop;
return True;
end Derange;
File : File_Type;
len, foundlen : Positive := 1;
Vect, SVect : StringVector.Vector;
index, p1, p2 : StringVector.Extended_Index := 0;
begin
Open (File, In_File, "unixdict.txt");
while not End_Of_File (File) loop
declare str : String := Get_Line (File);
begin
len := str'Length;
if len > foundlen then
Vect.Append (str);
StrSort (str);
index := 0;
loop -- Loop through anagrams by index in vector of sorted strings
index := SVect.Find_Index (str, index + 1);
exit when index = StringVector.No_Index;
if Derange (Vect.Last_Element, Vect.Element (index)) then
p1 := Vect.Last_Index; p2 := index;
foundlen := len;
end if;
end loop;
SVect.Append (str);
end if;
end;
end loop;
Close (File);
Put_Line (Vect.Element (p1) & " " & Vect.Element (p2));
end Danagrams;
{{out}}
intoxicate excitation
ALGOL 68
{{works with|ALGOL 68G|Any - tested with release 2.8.3.win32}} Uses the "read" PRAGMA of Algol 68 G to include the associative array code from the [[Associative_array/Iteration]] task.
# find the largest deranged anagrams in a list of words #
# use the associative array in the Associate array/iteration task #
PR read "aArray.a68" PR
# returns the length of str #
OP LENGTH = ( STRING str )INT: 1 + ( UPB str - LWB str );
# returns TRUE if a and b are the same length and have no #
# identical characters at any position, #
# FALSE otherwise #
PRIO ALLDIFFER = 9;
OP ALLDIFFER = ( STRING a, b )BOOL:
IF LENGTH a /= LENGTH b
THEN
# the two stringa are not the same size #
FALSE
ELSE
# the strings are the same length, check the characters #
BOOL result := TRUE;
INT b pos := LWB b;
FOR a pos FROM LWB a TO UPB a WHILE result := ( a[ a pos ] /= b[ b pos ] )
DO
b pos +:= 1
OD;
result
FI # ALLDIFFER # ;
# returns text with the characters sorted #
OP SORT = ( STRING text )STRING:
BEGIN
STRING sorted := text;
FOR end pos FROM UPB sorted - 1 BY -1 TO LWB sorted
WHILE
BOOL swapped := FALSE;
FOR pos FROM LWB sorted TO end pos DO
IF sorted[ pos ] > sorted[ pos + 1 ]
THEN
CHAR t := sorted[ pos ];
sorted[ pos ] := sorted[ pos + 1 ];
sorted[ pos + 1 ] := t;
swapped := TRUE
FI
OD;
swapped
DO SKIP OD;
sorted
END # SORTED # ;
# read the list of words and find the longest deranged anagrams #
CHAR separator = "|"; # character that will separate the anagrams #
IF FILE input file;
STRING file name = "unixdict.txt";
open( input file, file name, stand in channel ) /= 0
THEN
# failed to open the file #
print( ( "Unable to open """ + file name + """", newline ) )
ELSE
# file opened OK #
BOOL at eof := FALSE;
# set the EOF handler for the file #
on logical file end( input file, ( REF FILE f )BOOL:
BEGIN
# note that we reached EOF on the #
# latest read #
at eof := TRUE;
# return TRUE so processing can continue #
TRUE
END
);
REF AARRAY words := INIT LOC AARRAY;
STRING word;
INT longest derangement := 0;
STRING longest word := "<none>";
STRING longest anagram := "<none>";
WHILE NOT at eof
DO
STRING word;
get( input file, ( word, newline ) );
INT word length = LENGTH word;
IF word length >= longest derangement
THEN
# this word is at least long as the longest derangement #
# found so far - test it #
STRING sorted word = SORT word;
IF ( words // sorted word ) /= ""
THEN
# we already have this sorted word - test for #
# deranged anagrams #
# the word list will have a leading separator #
# and be followed by one or more words separated by #
# the separator #
STRING word list := words // sorted word;
INT list pos := LWB word list + 1;
INT list max = UPB word list;
BOOL is deranged := FALSE;
WHILE list pos < list max
AND NOT is deranged
DO
STRING anagram = word list[ list pos : ( list pos + word length ) - 1 ];
IF is deranged := word ALLDIFFER anagram
THEN
# have a deranged anagram #
longest derangement := word length;
longest word := word;
longest anagram := anagram
FI;
list pos +:= word length + 1
OD
FI;
# add the word to the anagram list #
words // sorted word +:= separator + word
FI
OD;
close( input file );
print( ( "Longest deranged anagrams: "
, longest word
, " and "
, longest anagram
, newline
)
)
FI
{{out}}
Longest deranged anagrams: intoxicate and excitation
AutoHotkey
Time := A_TickCount
SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory.
SetBatchLines -1
Loop, Read, unixdict.txt
StrOut .= StrLen(A_LoopReadLine) - 2 . "," . A_LoopReadLine . "`n"
Sort StrOut, N R
Loop, Parse, StrOut, `n, `r
{
StringSplit, No_Let, A_Loopfield, `,
if ( old1 = no_let1 )
string .= old2 "`n"
if ( old1 != no_let1 )
{
string := trim(string old2)
if ( old2 != "" )
Loop, Parse, string, `n, `r ; Specifying `n prior to `r allows both Windows and Unix files to be Parsed.
line_number := A_Index
if ( line_number > 1 )
{
Loop, Parse, string, `n, `r
{
StringSplit, newstr, A_Loopfield, `, ; break the string based on Comma
Loop, Parse, newstr2
k .= A_LoopField " "
Sort k, D%A_Space%
k := RegExReplace( k, "\s", "" )
file .= "`r`n" k . "," . newstr1 . "," . newstr2
k =
}
Sort File
Loop, Parse, File, `n, `r
{
if ( A_Loopfield != "" )
{
StringSplit, T_C, A_Loopfield, `,
if ( old = T_C1 )
{
Loop, 1
{
Loop % T_C2
if (SubStr(T_C3, A_Index, 1) = SubStr(old3, A_Index, 1))
break 2
Time := (A_tickcount - Time)/1000
MsgBox % T_C3 " " old3 " in " Time . " seconds."
ExitApp
}
}
old := T_C1, old3 := T_C3
}
}
file =
}
string =
}
old1 := no_let1, old2 := A_Loopfield
}
{{out}}
intoxicate excitation in 0.844000 seconds.
AWK
{{works with|GNU awk (gawk) 3.1.5}}
#!/bin/gawk -f
BEGIN{
FS=""
wordcount = 0
maxlength = 0
}
# hash generates the sorted sequence of characters in a word,
# so that the hashes for a pair of anagrams will be the same.
# Example: hash meat = aemt and hash team = aemt
function hash(myword, i,letters,myhash){
split(myword,letters,"")
asort(letters)
for (i=1;i<=length(myword);i++) myhash=myhash letters[i]
return myhash
}
# deranged checks two anagrems for derangement
function deranged(worda, wordb, a,b,i,n,len){
n=0
len=split(worda,a,"")
split(wordb,b,"")
for (i=len; i>=1; i--){
if (a[i] == b[i]) n = n+1
}
return n==0
}
# field separator null makes gawk split input record character by character.
# the split function works the same way
{
wordcount = wordcount + 1
fullword[wordcount]=$0
bylength[length($0)]=bylength[length($0)] wordcount "|"
if (length($0) > maxlength) maxlength = length($0)
}
END{
for (len=maxlength; len>1; len--){
numwords=split(bylength[len],words,"|")
split("",hashed)
split("",anagrams)
for (i=1;i<=numwords;i++){
# make lists of anagrams in hashed
myword = fullword[words[i]]
myhash = hash(myword)
hashed[myhash] = hashed[myhash] myword " "
}
# check anagrams for derangement
for (myhash in hashed){
n = split(hashed[myhash],anagrams," ")
for (i=1; i< n; i++)
for (j=i+1; j<=n; j++){
if(deranged(anagrams[i],anagrams[j])) found = found anagrams[i] " " anagrams[j] " "
}
}
if (length(found) > 0 ) print "deranged: " found
if (length(found) > 0) exit
}
}
On my system, this awk-file is located at /usr/local/bin/deranged, so it can be invoked with:
deranged /tmp/unixdict.txt
Regular invocation would be:
gawk -f deranged.awk /tmp/unixdict.txt
{{out}}
deranged: excitation intoxicate
BaCon
DECLARE idx$ ASSOC STRING
FUNCTION Deranged(a$, b$)
FOR i = 1 TO LEN(a$)
IF MID$(a$, i, 1) = MID$(b$, i, 1) THEN RETURN FALSE
NEXT
RETURN TRUE
END FUNCTION
FOR w$ IN LOAD$(DIRNAME$(ME$) & "/unixdict.txt") STEP NL$
set$ = EXTRACT$(SORT$(EXPLODE$(w$, 1)), " ")
idx$(set$) = APPEND$(idx$(set$), 0, w$)
NEXT
FOR w$ IN OBTAIN$(idx$)
FOR x = 1 TO AMOUNT(idx$(w$))
FOR y = x+1 TO AMOUNT(idx$(w$))
IF Deranged(TOKEN$(idx$(w$), x), TOKEN$(idx$(w$), y)) AND LEN(TOKEN$(idx$(w$), x)) > current THEN
current = LEN(TOKEN$(idx$(w$), x))
an1$ = TOKEN$(idx$(w$), x)
an2$ = TOKEN$(idx$(w$), y)
END IF
NEXT
NEXT
NEXT
PRINT "Maximum deranged anagrams: ", an1$, " and ", an2$
PRINT NL$, "Total time: ", TIMER, " msecs.", NL$
{{out}}
Maximum deranged anagrams: excitation and intoxicate
Total time: 75 msecs.
BBC BASIC
{{works with|BBC BASIC for Windows}}
INSTALL @lib$+"SORTLIB"
Sort% = FN_sortinit(0,0)
DIM dict$(26000), sort$(26000), indx%(26000)
REM Load the dictionary:
dict% = OPENIN("C:\unixdict.txt")
IF dict%=0 ERROR 100, "No dictionary file"
index% = 0
REPEAT
index% += 1
dict$(index%) = GET$#dict%
indx%(index%) = index%
UNTIL EOF#dict%
CLOSE #dict%
Total% = index%
TIME = 0
REM Sort the letters in each word:
FOR index% = 1 TO Total%
sort$(index%) = FNsortstring(dict$(index%))
NEXT
REM Sort the sorted words:
C% = Total%
CALL Sort%, sort$(1), indx%(1)
REM Find anagrams and deranged anagrams:
maxlen% = 0
maxidx% = 0
FOR index% = 1 TO Total%-1
IF sort$(index%) = sort$(index%+1) THEN
One$ = dict$(indx%(index%))
Two$ = dict$(indx%(index%+1))
FOR c% = 1 TO LEN(One$)
IF MID$(One$,c%,1) = MID$(Two$,c%,1) EXIT FOR
NEXT
IF c%>LEN(One$) IF c%>maxlen% maxlen% = c% : maxidx% = index%
ENDIF
NEXT
PRINT "The longest deranged anagrams are '" dict$(indx%(maxidx%));
PRINT "' and '" dict$(indx%(maxidx%+1)) "'"
PRINT "(taking " ; TIME/100 " seconds)"
END
DEF FNsortstring(A$)
LOCAL C%, a&()
C% = LEN(A$)
DIM a&(C%)
$$^a&(0) = A$
CALL Sort%, a&(0)
= $$^a&(0)
{{out}}
The longest deranged anagrams are 'excitation' and 'intoxicate'
(taking 0.95 seconds)
Bracmat
The file is read into a single string, wordList
.
Then, in a while loop, each line is read and, in a nested loop, atomised into single letters.
The letters are added together to create a sorted list that is the letter sum, the 'anagram fingerprint', of the word.
To make sure that even single letter words create a sum of at least two terms, the sum is initialised with the empty string rather than zero.
(Otherwise the words a and aaa later on would get the same fingerprint, the factors 1
and 3
being factored out.)
For the word bizarre the letter sum is (+a+b+e+2r+z+i)
.
The letter sum, with the word as the exponent ((+a+b+e+2r+z+i)^bizarre
) is prepended to a list unsorted
.
Somewhat later the word brazier also is prepended to the unsorted
list.
This word happens to have the same letter sum as bizarre, so these two words must be anagrams of each other. The program brings these two elements together by merge sorting the unsorted
list, using Bracmat's Computer Algebra powers to normalise sums and products by sorting and combining like terms or factors.
During the sort, all elements in the the unsorted
list are multiplied together, combining factors with the same letter sums by adding their exponents together. So at some stage during sorting, the two elements (+a+b+e+2r+z+i)^bizarre
and (+a+b+e+2r+z+i)^brazier
are brought together in a product (+a+b+e+2r+z+i)^bizarre(+a+b+e+2r+z+i)^brazier
which immediately is transformed to the single factor (+a+b+e+2r+z+i)^(bizarre+brazier)
.
In the product of all elements the anagrams are to be found in the exponents consisting of a sum of at least two terms.
To find the longest deranged anagrams, we traverse the product list to find all exponents with multiple words, check that the length of the first word is at least as long as the length of the longest deranged anagram up to now, and check each pair of words for being deranged.
If a pair of deranged anagrams is found with more letters than previously found deranged anagrams, the earlier finds are forgotten. If the new anagrams are the same length, however, they are added to the output.
The Bracmat solution to the similar task [[Anagrams|anagrams]] skips the explicit merge sort and instead prepends new factors directly to the product one by one. Bracmat shuffles each new factor into place to keep the growing product normalized before continuing with the next word from the list. The result is exactly the same, but the running time becomes much longer.
get$("unixdict.txt",STR):?wordList
& 1:?product
& :?unsorted
& whl
' ( @(!wordList:(%?word:?letterString) \n ?wordList)
& :?letterSum
& whl
' ( @(!letterString:%?letter ?letterString)
& (!letter:~#|str$(N !letter))+!letterSum
: ?letterSum
)
& !letterSum^!word !unsorted:?unsorted
)
& ( mergeSort
= newL L first second
. !arg:?L
& whl
' ( !L:% %
& :?newL
& whl
' ( !L:%?first %?second ?L
& !first*!second !newL:?newL
)
& !L !newL:?L
)
& !L
)
& mergeSort$!unsorted:?product
& 0:?maxLength:?oldMaxLength
& :?derangedAnagrams
& ( deranged
= nextLetter Atail Btail
. !arg
: ( (.)
| ( @(?:%@?nextLetter ?Atail)
. @(?:(%@:~!nextLetter) ?Btail)
)
& deranged$(!Atail.!Btail)
)
)
& ( !product
: ?
* ?
^ ( %+%
: @(%:? ([~<!maxLength:[?maxLength))+?
: ?
+ %@?anagramA
+ ?
+ %@?anagramB
+ ( ?
& deranged$(!anagramA.!anagramB)
& (!anagramA.!anagramB)
( !maxLength:>!oldMaxLength:?oldMaxLength
&
| !derangedAnagrams
)
: ?derangedAnagrams
& ~
)
)
* ?
| out$!derangedAnagrams
);
{{out}}
excitation.intoxicate
C
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <fcntl.h> #include <sys/stat.h> // Letter lookup by frequency. This is to reduce word insertion time. const char *freq = "zqxjkvbpygfwmucldrhsnioate"; int char_to_idx[128]; // Trie structure of sorts struct word { const char *w; struct word *next; }; union node { union node *down[10]; struct word *list[10]; }; int deranged(const char *s1, const char *s2) { int i; for (i = 0; s1[i]; i++) if (s1[i] == s2[i]) return 0; return 1; } int count_letters(const char *s, unsigned char *c) { int i, len; memset(c, 0, 26); for (len = i = 0; s[i]; i++) { if (s[i] < 'a' || s[i] > 'z') return 0; len++, c[char_to_idx[(unsigned char)s[i]]]++; } return len; } const char * insert(union node *root, const char *s, unsigned char *cnt) { int i; union node *n; struct word *v, *w = 0; for (i = 0; i < 25; i++, root = n) { if (!(n = root->down[cnt[i]])) root->down[cnt[i]] = n = calloc(1, sizeof(union node)); } w = malloc(sizeof(struct word)); w->w = s; w->next = root->list[cnt[25]]; root->list[cnt[25]] = w; for (v = w->next; v; v = v->next) { if (deranged(w->w, v->w)) return v->w; } return 0; } int main(int c, char **v) { int i, j = 0; char *words; struct stat st; int fd = open(c < 2 ? "unixdict.txt" : v[1], O_RDONLY); if (fstat(fd, &st) < 0) return 1; words = malloc(st.st_size); read(fd, words, st.st_size); close(fd); union node root = {{0}}; unsigned char cnt[26]; int best_len = 0; const char *b1, *b2; for (i = 0; freq[i]; i++) char_to_idx[(unsigned char)freq[i]] = i; /* count words, change newline to null */ for (i = j = 0; i < st.st_size; i++) { if (words[i] != '\n') continue; words[i] = '\0'; if (i - j > best_len) { count_letters(words + j, cnt); const char *match = insert(&root, words + j, cnt); if (match) { best_len = i - j; b1 = words + j; b2 = match; } } j = ++i; } if (best_len) printf("longest derangement: %s %s\n", b1, b2); return 0; }
{{out}}
longest derangement: intoxicate excitation
C++
#include <algorithm> #include <fstream> #include <functional> #include <iostream> #include <map> #include <numeric> #include <set> #include <string> bool is_deranged(const std::string& left, const std::string& right) { return (left.size() == right.size()) && (std::inner_product(left.begin(), left.end(), right.begin(), 0, std::plus<int>(), std::equal_to<char>()) == 0); } int main() { std::ifstream input("unixdict.txt"); if (!input) { std::cerr << "can't open input file\n"; return EXIT_FAILURE; } typedef std::set<std::string> WordList; typedef std::map<std::string, WordList> AnagraMap; AnagraMap anagrams; std::pair<std::string, std::string> result; size_t longest = 0; for (std::string value; input >> value; /**/) { std::string key(value); std::sort(key.begin(), key.end()); if (longest < value.length()) { // is it a long candidate? if (0 < anagrams.count(key)) { // is it an anagram? for (const auto& prior : anagrams[key]) { if (is_deranged(prior, value)) { // are they deranged? result = std::make_pair(prior, value); longest = value.length(); } } } } anagrams[key].insert(value); } std::cout << result.first << ' ' << result.second << '\n'; return EXIT_SUCCESS; }
{{out}}
excitation intoxicate
C#
{{libheader|System}} {{libheader|System.Collections.Generic}} {{libheader|System.Linq}} {{libheader|System.IO}} {{works with|C sharp|6}}
public static void Main() { var lookupTable = File.ReadLines("unixdict.txt").ToLookup(line => AnagramKey(line)); var query = from a in lookupTable orderby a.Key.Length descending let deranged = FindDeranged(a) where deranged != null select deranged[0] + " " + deranged[1]; Console.WriteLine(query.FirstOrDefault()); } static string AnagramKey(string word) => new string(word.OrderBy(c => c).ToArray()); static string[] FindDeranged(IEnumerable<string> anagrams) => ( from first in anagrams from second in anagrams where !second.Equals(first) && Enumerable.Range(0, first.Length).All(i => first[i] != second[i]) select new [] { first, second }) .FirstOrDefault();
{{out}}
excitation intoxicate
Clojure
(->> (slurp "unixdict.txt") ; words (re-seq #"\w+") ; | (group-by sort) ; anagrams vals ; | (filter second) ; | (remove #(some true? (apply map = %))) ; deranged (sort-by #(count (first %))) last prn)
{{out}}
$ lein exec deranged.clj
["excitation" "intoxicate"]
CoffeeScript
This example was tested with node.js.
http = require 'http'
is_derangement = (word1, word2) ->
for c, i in word1
return false if c == word2[i]
true
show_longest_derangement = (word_lst) ->
anagrams = {}
max_len = 0
for word in word_lst
continue if word.length < max_len
key = word.split('').sort().join('')
if anagrams[key]
for prior in anagrams[key]
if is_derangement(prior, word)
max_len = word.length
result = [prior, word]
else
anagrams[key] = []
anagrams[key].push word
console.log "Longest derangement: #{result.join ' '}"
get_word_list = (process) ->
options =
host: "www.puzzlers.org"
path: "/pub/wordlists/unixdict.txt"
req = http.request options, (res) ->
s = ''
res.on 'data', (chunk) ->
s += chunk
res.on 'end', ->
process s.split '\n'
req.end()
get_word_list show_longest_derangement
{{out}}
> coffee anagrams.coffee
Longest derangement: excitation intoxicate
Common Lisp
(defun read-words (file) (with-open-file (stream file) (loop with w = "" while w collect (setf w (read-line stream nil))))) (defun deranged (a b) (loop for ac across a for bc across b always (char/= ac bc))) (defun longest-deranged (file) (let ((h (make-hash-table :test #'equal)) (wordlist (sort (read-words file) #'(lambda (x y) (> (length x) (length y)))))) (loop for w in wordlist do (let* ((ws (sort (copy-seq w) #'char<)) (l (gethash ws h))) (loop for w1 in l do (if (deranged w w1) (return-from longest-deranged (list w w1)))) (setf (gethash ws h) (cons w l)))))) (format t "~{~A~%~^~}" (longest-deranged "unixdict.txt"))
{{out}}
intoxicate
excitation
D
Short Version
void main() { import std.stdio, std.file, std.algorithm, std.string, std.array; string[][dstring] anags; foreach (const w; "unixdict.txt".readText.split) anags[w.array.sort().release.idup] ~= w; anags .byValue .map!(words => words.cartesianProduct(words) .filter!q{ a[].equal!q{ a != b }}) .join .minPos!q{ a[0].length > b[0].length }[0] .writeln; }
{{out}}
Tuple!(string, string)("intoxicate", "excitation")
Runtime: about 0.11 seconds with LDC2 compiler.
Using const(ubytes)[] instead of dstrings gives a runtime of about 0.07 seconds:
string[][ubyte[]] anags; foreach (const w; "unixdict.txt".readText.split) anags[w.dup.representation.sort().release.assumeUnique] ~= w;
Faster Version
import std.stdio, std.file, std.algorithm, std.string, std.array, std.functional, std.exception; string[2][] findDeranged(in string[] words) pure nothrow /*@safe*/ { // return words // .map!representation // .pairwise // .filter!(ww => ww[].equal!q{ a != b }); typeof(return) result; foreach (immutable i, immutable w1; words) foreach (immutable w2; words[i + 1 .. $]) if (w1.representation.equal!q{ a != b }(w2.representation)) result ~= [w1, w2]; return result; } void main() /*@safe*/ { Appender!(string[])[30] wClasses; foreach (const w; "unixdict.txt".readText.splitter) wClasses[$ - w.length] ~= w; foreach (const ws; wClasses[].map!q{ a.data }.filter!(not!empty)) { string[][const ubyte[]] anags; // Assume ASCII input. foreach (immutable w; ws) anags[w.dup.representation.sort().release.assumeUnique] ~= w; auto pairs = anags.byValue.map!findDeranged.joiner; if (!pairs.empty) return writefln("Longest deranged: %-(%s %)", pairs.front); } }
{{out}}
Longest deranged: excitation intoxicate
Runtime: about 0.03 seconds.
EchoLisp
For a change, we use the french dictionary included in EchoLisp package.
(lib 'hash)
(lib 'struct)
(lib 'sql)
(lib 'words)
(define H (make-hash))
(define (deranged w1 w2)
(for ((a w1) (b w2))
#:break (string=? a b) => #f
#t))
(define (anagrams (normal) (name) (twins))
(for ((w *words*))
(set! name (word-name w))
(set! normal (list->string (list-sort string<? (string->list name))))
(set! twins (or (hash-ref H normal) null))
#:continue (member name twins)
#:when (or (null? twins) (for/or ((anagram twins)) (deranged name anagram)))
(hash-set H normal (cons name twins))))
(define (task (lmin 8))
(anagrams)
(for ((lw (hash-values H))) ;; lw = list of words
#:continue (= (length lw) 1)
#:continue (< (string-length (first lw)) lmin)
(set! lmin (string-length (first lw)))
(write lmin) (for-each write lw)
(writeln)))
{{out}}
(lib 'dico.fr.no-accent) ;; 209315 words into *words* table
(task)
[...]
13 tractionnaire contrariaient
13 ressourcement contremesures
13 saintsimonien inseminations
14 tergiversation interrogatives
14 suralimenterai mineralisateur
14 transoceaniens reconnaissante
(lib 'dico.en ) ;; 235886 words
(task)
[...]
12 reaccomplish accomplisher
12 chromatician achromatinic
12 unaccumulate acutenaculum
14 charlatanistic antarchistical
15 megachiropteran cinematographer
17 misconstitutional constitutionalism
Eiffel
class
ANAGRAMS_DERANGED
create
make
feature
make
-- Longest deranged anagram.
local
deranged_anagrams: LINKED_LIST [STRING]
count: INTEGER
do
read_wordlist
across
words as wo
loop
deranged_anagrams := check_list_for_deranged (wo.item)
if not deranged_anagrams.is_empty and deranged_anagrams [1].count > count then
count := deranged_anagrams [1].count
end
wo.item.wipe_out
wo.item.append (deranged_anagrams)
end
across
words as wo
loop
across
wo.item as w
loop
if w.item.count = count then
io.put_string (w.item + "%T")
io.new_line
end
end
end
end
original_list: STRING = "unixdict.txt"
feature {NONE}
check_list_for_deranged (list: LINKED_LIST [STRING]): LINKED_LIST [STRING]
-- Deranged anagrams in 'list'.
do
create Result.make
across
1 |..| list.count as i
loop
across
(i.item + 1) |..| list.count as j
loop
if check_for_deranged (list [i.item], list [j.item]) then
Result.extend (list [i.item])
Result.extend (list [j.item])
end
end
end
end
check_for_deranged (a, b: STRING): BOOLEAN
-- Are 'a' and 'b' deranged anagrams?
local
n: INTEGER
do
across
1 |..| a.count as i
loop
if a [i.item] = b [i.item] then
n := n + 1
end
end
Result := n = 0
end
read_wordlist
-- Hashtable 'words' with alphabetically sorted Strings used as key.
local
l_file: PLAIN_TEXT_FILE
sorted: STRING
empty_list: LINKED_LIST [STRING]
do
create l_file.make_open_read_write (original_list)
l_file.read_stream (l_file.count)
wordlist := l_file.last_string.split ('%N')
l_file.close
create words.make (wordlist.count)
across
wordlist as w
loop
create empty_list.make
sorted := sort_letters (w.item)
words.put (empty_list, sorted)
if attached words.at (sorted) as ana then
ana.extend (w.item)
end
end
end
wordlist: LIST [STRING]
sort_letters (word: STRING): STRING
--Alphabetically sorted.
local
letters: SORTED_TWO_WAY_LIST [STRING]
do
create letters.make
create Result.make_empty
across
1 |..| word.count as i
loop
letters.extend (word.at (i.item).out)
end
across
letters as s
loop
Result.append (s.item)
end
end
words: HASH_TABLE [LINKED_LIST [STRING], STRING]
end
{{out}}
excitation
intoxicate
Elixir
{{trans|Ruby}}
defmodule Anagrams do def deranged(fname) do File.read!(fname) |> String.split |> Enum.map(fn word -> to_charlist(word) end) |> Enum.group_by(fn word -> Enum.sort(word) end) |> Enum.filter(fn {_,words} -> length(words) > 1 end) |> Enum.sort_by(fn {key,_} -> -length(key) end) |> Enum.find(fn {_,words} -> find_derangements(words) end) end defp find_derangements(words) do comb(words,2) |> Enum.find(fn [a,b] -> deranged?(a,b) end) end defp deranged?(a,b) do Enum.zip(a, b) |> Enum.all?(fn {chr_a,chr_b} -> chr_a != chr_b end) end defp comb(_, 0), do: [[]] defp comb([], _), do: [] defp comb([h|t], m) do (for l <- comb(t, m-1), do: [h|l]) ++ comb(t, m) end end case Anagrams.deranged("/work/unixdict.txt") do {_, words} -> IO.puts "Longest derangement anagram: #{inspect words}" _ -> IO.puts "derangement anagram: nothing" end
{{out}}
Longest derangement anagram: ["intoxicate", "excitation"]
Erlang
Using anagrams:fetch/2 from [[Anagrams]] and init_http/0 from [[Rosetta_Code/Find_unimplemented_tasks]]. Exporting words_from_url/1 to [[Ordered_words]].
-module( anagrams_deranged ). -export( [task/0, words_from_url/1] ). task() -> find_unimplemented_tasks:init_http(), Words = words_from_url( "http://www.puzzlers.org/pub/wordlists/unixdict.txt" ), Anagram_dict = anagrams:fetch( Words, dict:new() ), Deranged_anagrams = deranged_anagrams( Anagram_dict ), {_Length, Longest_anagrams} = dict:fold( fun keep_longest/3, {0, []}, Deranged_anagrams ), Longest_anagrams. words_from_url( URL ) -> {ok, {{_HTTP, 200, "OK"}, _Headers, Body}} = httpc:request( URL ), string:tokens( Body, "\n" ). deranged_anagrams( Dict ) -> Deranged_dict = dict:map( fun deranged_words/2, Dict ), dict:filter( fun is_anagram/2, Deranged_dict ). deranged_words( _Key, [H | T] ) -> [{H, X} || X <- T, is_deranged_word(H, X)]. keep_longest( _Key, [{One, _} | _]=New, {Length, Acc} ) -> keep_longest_new( erlang:length(One), Length, New, Acc ). keep_longest_new( New_length, Acc_length, New, _Acc ) when New_length > Acc_length -> {New_length, New}; keep_longest_new( New_length, Acc_length, New, Acc ) when New_length =:= Acc_length -> {Acc_length, Acc ++ New}; keep_longest_new( _New_length, Acc_length, _New, Acc ) -> {Acc_length, Acc}. is_anagram( _Key, [] ) -> false; is_anagram( _Key, _Value ) -> true. is_deranged_word( Word1, Word2 ) -> lists:all( fun is_deranged_char/1, lists:zip(Word1, Word2) ). is_deranged_char( {One, Two} ) -> One =/= Two.
{{out}}
8> anagrams_deranged:task().
[{"excitation","intoxicate"}]
=={{header|F_Sharp|F#}}==
open System; let keyIsSortedWord = Seq.sort >> Seq.toArray >> String let isDeranged = Seq.forall2 (<>) let rec pairs acc l = function | [] -> acc | x::rtail -> pairs (acc @ List.fold (fun acc y -> (y, x)::acc) [] l) (x::l) rtail [<EntryPoint>] let main args = System.IO.File.ReadAllLines("unixdict.txt") |> Seq.groupBy keyIsSortedWord |> Seq.fold (fun (len, found) (key, words) -> if String.length key < len || Seq.length words < 2 then (len, found) else let d = List.filter (fun (a, b) -> isDeranged a b) (pairs [] [] (List.ofSeq words)) if List.length d = 0 then (len, found) elif String.length key = len then (len, found @ d) else (String.length key, d) ) (0, []) |> snd |> printfn "%A" 0
{{out}}
[("excitation", "intoxicate")]
Factor
USING: assocs fry io.encodings.utf8 io.files kernel math
math.combinatorics sequences sorting strings ;
IN: rosettacode.deranged-anagrams
: derangement? ( str1 str2 -- ? ) [ = not ] 2all? ;
: derangements ( seq -- seq )
2 [ first2 derangement? ] filter-combinations ;
: parse-dict-file ( path -- hash )
utf8 file-lines
H{ } clone [
'[
[ natural-sort >string ] keep
_ [ swap suffix ] with change-at
] each
] keep ;
: anagrams ( hash -- seq ) [ nip length 1 > ] assoc-filter values ;
: deranged-anagrams ( path -- seq )
parse-dict-file anagrams [ derangements ] map concat ;
: longest-deranged-anagrams ( path -- anagrams )
deranged-anagrams [ first length ] sort-with last ;
"unixdict.txt" longest-deranged-anagrams . { "excitation" "intoxicate" }
FreeBASIC
' FB 1.05.0 Win64
Type IndexedWord
As String word
As Integer index
End Type
' selection sort, quick enough for sorting small number of letters
Sub sortWord(s As String)
Dim As Integer i, j, m, n = Len(s)
For i = 0 To n - 2
m = i
For j = i + 1 To n - 1
If s[j] < s[m] Then m = j
Next j
If m <> i Then Swap s[i], s[m]
Next i
End Sub
' quicksort for sorting whole dictionary of IndexedWord instances by sorted word
Sub quicksort(a() As IndexedWord, first As Integer, last As Integer)
Dim As Integer length = last - first + 1
If length < 2 Then Return
Dim pivot As String = a(first + length\ 2).word
Dim lft As Integer = first
Dim rgt As Integer = last
While lft <= rgt
While a(lft).word < pivot
lft +=1
Wend
While a(rgt).word > pivot
rgt -= 1
Wend
If lft <= rgt Then
Swap a(lft), a(rgt)
lft += 1
rgt -= 1
End If
Wend
quicksort(a(), first, rgt)
quicksort(a(), lft, last)
End Sub
Function isDeranged(s1 As String, s2 As String) As Boolean
For i As Integer = 0 To Len(s1) - 1
If s1[i] = s2[i] Then Return False
Next
Return True
End Function
Dim t As Double = timer
Dim As String w() '' array to hold actual words
Open "undict.txt" For Input As #1
Dim count As Integer = 0
While Not Eof(1)
count +=1
Redim Preserve w(1 To count)
Line Input #1, w(count)
Wend
Close #1
Dim As IndexedWord iw(1 To count) '' array to hold sorted words and their index into w()
Dim word As String
For i As Integer = 1 To count
word = w(i)
sortWord(word)
iw(i).word = word
iw(i).index = i
Next
quickSort iw(), 1, count '' sort the IndexedWord array by sorted word
Dim As Integer startIndex = 1, maxLength, ub
Dim As Integer maxIndex()
Dim As IndexedWord iWord = iw(1)
maxLength = 0
For i As Integer = 2 To count
If iWord.word = iw(i).word Then
If isDeranged(w(iword.index), w(iw(i).index)) Then
If startIndex + 1 < i Then Swap iw(i), iw(startIndex + 1)
If Len(iWord.word) > maxLength Then
maxLength = Len(iWord.word)
Erase maxIndex
ub = 1
Redim maxIndex(1 To ub)
maxIndex(ub) = startIndex
startIndex += 2
i = startIndex
iWord = iw(i)
ElseIf Len(iWord.word) = maxLength Then
ub += 1
Redim Preserve maxIndex(1 To ub)
maxIndex(ub) = startIndex
startIndex += 2
i = startIndex
iWord = iw(i)
End If
End If
ElseIf i = count Then
Exit For
Else
For j As Integer = i To count - 1
iWord = iw(j)
If Len(iWord.word) >= maxLength Then
startIndex = j
i = startIndex
Exit For
End If
Next
End If
Next
Print Str(count); " words in the dictionary"
Print "The deranged anagram pair(s) with the greatest length (namely"; maxLength; ") is:"
Print
Dim iws(1 To maxLength) As IndexedWord '' array to hold each deranged anagram pair
For i As Integer = 1 To UBound(maxIndex)
For j As Integer = maxIndex(i) To maxIndex(i) + 1
iws(j - maxIndex(i) + 1) = iw(j)
Next j
If iws(1).index > iws(2).index Then swap iws(1), iws(2) '' ensure pair is in correct order
For j As Integer = 1 To 2
Print w(iws(j).index); " ";
Next j
Print
Next i
Print
Print "Took ";
Print Using "#.###"; timer - t;
Print " seconds on i3 @ 2.13 GHz"
Print
Print "Press any key to quit"
Sleep
{{out}}
25104 words in the dictionary
The deranged anagram pair(s) with the greatest length (namely 10) is:
excitation intoxicate
Took 0.089 seconds on i3 @ 2.13 GHz
GAP
Using function [[Anagrams#GAP|Anagrams]].
IsDeranged := function(a, b)
local i, n;
for i in [1 .. Size(a)] do
if a[i] = b[i] then
return false;
fi;
od;
return true;
end;
# This solution will find all deranged pairs of any length.
Deranged := function(name)
local sol, ana, u, v;
sol := [ ];
ana := Anagrams(name);
for u in ana do
for v in Combinations(u, 2) do
if IsDeranged(v[1], v[2]) then
Add(sol, v);
fi;
od;
od;
return sol;
end;
# Now we find all deranged pairs of maximum length
a := Deranged("unixdict.txt");;
n := Maximum(List(a, x -> Size(x[1])));
Filtered(a, x -> Size(x[1]) = n);
# [ [ "excitation", "intoxicate" ] ]
Go
package main import ( "fmt" "io/ioutil" "strings" "sort" ) func deranged(a, b string) bool { if len(a) != len(b) { return false } for i := range(a) { if a[i] == b[i] { return false } } return true } func main() { /* read the whole thing in. how big can it be? */ buf, _ := ioutil.ReadFile("unixdict.txt") words := strings.Split(string(buf), "\n") m := make(map[string] []string) best_len, w1, w2 := 0, "", "" for _, w := range(words) { // don't bother: too short to beat current record if len(w) <= best_len { continue } // save strings in map, with sorted string as key letters := strings.Split(w, "") sort.Strings(letters) k := strings.Join(letters, "") if _, ok := m[k]; !ok { m[k] = []string { w } continue } for _, c := range(m[k]) { if deranged(w, c) { best_len, w1, w2 = len(w), c, w break } } m[k] = append(m[k], w) } fmt.Println(w1, w2, ": Length", best_len) }
{{out}}
excitation intoxicate : Length 10
Groovy
Solution:
def map = new TreeMap<Integer,Map<String,List<String>>>() new URL('http://www.puzzlers.org/pub/wordlists/unixdict.txt').eachLine { word -> def size = - word.size() map[size] = map[size] ?: new TreeMap<String,List<String>>() def norm = word.toList().sort().sum() map[size][norm] = map[size][norm] ?: [] map[size][norm] << word } def result = map.findResult { negasize, normMap -> def size = - negasize normMap.findResults { x, anagrams -> def n = anagrams.size() (0..<(n-1)).findResults { i -> ((i+1)..<n).findResult { j -> (0..<size).every { k -> anagrams[i][k] != anagrams[j][k] } \ ? anagrams[i,j] : null } }?.flatten() ?: null }?.flatten() ?: null } if (result) { println "Longest deranged anagram pair: ${result}" } else { println 'Deranged anagrams are a MYTH!' }
{{out}}
Longest deranged anagram pair: [excitation, intoxicate]
Haskell
If the longest deranged anagram includes three or more words we'll only print two of them. We also correctly handle duplicate words in the input.
import Control.Arrow import Data.List import Data.Ord import qualified Data.Map as M import qualified Data.Set as S -- Group lists of words based on their "signatures". A signature is a sorted -- list of characters. Handle duplicate input words by storing them in sets. groupBySig = map (sort &&& S.singleton) -- Convert groups to lists of equivalent words. equivs = map (S.toList . snd) . M.toList . M.fromListWith S.union -- Indicate whether the pair of words differ in all character positions. isDerangement (a, b) = and $ zipWith (/=) a b -- Return all pairs of elements, ignoring order. pairs = concat . unfoldr step where step (x:xs) = Just (map ((,) x) xs, xs) step [] = Nothing -- Return all anagram pairs in the input string. anagrams = concatMap pairs . equivs . groupBySig -- Return the pair of words making the longest deranged anagram. maxDerangedAnagram = maxByLen . filter isDerangement . anagrams where maxByLen [] = Nothing maxByLen xs = Just $ maximumBy (comparing (length . fst)) xs main :: IO () main = do input <- getContents case maxDerangedAnagram $ words input of Nothing -> putStrLn "No deranged anagrams were found." Just (a, b) -> putStrLn $ "Longest deranged anagrams: " ++ a ++ " and " ++ b
{{out}}
Longest deranged anagrams: excitation and intoxicate
=={{header|Icon}} and {{header|Unicon}}== This solution (which works in both languages) does a strict interpretation of the problem and ignores the fact that there may be multiple derangements that are the same length (including ignoring multiple derangements arising from the same set of words that are all anagrams).
link strings # for csort() procedure
procedure main()
anagrams := table() # build lists of anagrams
every *(word := !&input) > 1 do {
canon := csort(word)
/anagrams[canon] := []
put(anagrams[canon], word)
}
longest := 1 # find a longest derangement
every *(aList := !anagrams) > 1 do
if derangement := derange(aList) then
if longest <:= *derangement[1] then long := derangement
every writes((!\long||" ")|"\n") # show longest
end
procedure derange(aList) # Return a single derangement from this list
while aWord := get(aList) do return noMatch(aWord, !aList)
end
procedure noMatch(s1,s2) # Produce pair only if s1,s2 are deranged.
every i := 1 to *s1 do if s1[i] == s2[i] then fail
return [s1,s2]
end
{{out|Sample run}}
->dra <unixdict.txt
excitation intoxicate
->
J
This assumes that [http://www.puzzlers.org/pub/wordlists/unixdict.txt unixdict.txt] has been saved in the current directory.
#words=: 'b' freads 'unixdict.txt'
25104
#anagrams=: (#~ 1 < #@>) (</.~ /:~&>) words
1303
#maybederanged=: (#~ (1 -.@e. #@~."1)@|:@:>&>) anagrams
432
#longest=: (#~ [: (= >./) #@>@{.@>) maybederanged
1
longest
┌───────────────────────┐
│┌──────────┬──────────┐│
││excitation│intoxicate││
│└──────────┴──────────┘│
└───────────────────────┘
Note that anagram sets with more than two members might, hypothetically, have made things more complicated. By lucky coincidence, this was not an issue. We could have taken advantage of that coincidence to achieve slight further simplifications. Perhaps maybederanged=: (#~ (-: ~."1)@|:@:>&>) anagrams
In other words, if we had had to consider whether ascertain/cartesian/sectarian
contained a deranged pair, we would have had to break it out into the three pairs it contains. However, since 'excitation' is a longer word than 'ascertain', we know that this triple cannot contain the longest deranged anagram pair. And since there are no anagrams longer than 'excitation' which involve more than a single pair, we know that we can ignore this issue entirely.
Java
{{works with|Java|8}}
import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; public class DerangedAnagrams { public static void main(String[] args) throws IOException { List<String> words = Files.readAllLines(new File("unixdict.txt").toPath()); printLongestDerangedAnagram(words); } private static void printLongestDerangedAnagram(List<String> words) { words.sort(Comparator.comparingInt(String::length).reversed().thenComparing(String::toString)); Map<String, ArrayList<String>> map = new HashMap<>(); for (String word : words) { char[] chars = word.toCharArray(); Arrays.sort(chars); String key = String.valueOf(chars); List<String> anagrams = map.computeIfAbsent(key, k -> new ArrayList<>()); for (String anagram : anagrams) { if (isDeranged(word, anagram)) { System.out.printf("%s %s%n", anagram, word); return; } } anagrams.add(word); } System.out.println("no result"); } private static boolean isDeranged(String word1, String word2) { for (int i = 0; i < word1.length(); i++) { if (word1.charAt(i) == word2.charAt(i)) { return false; } } return true; } }
{{out}}
excitation intoxicate
JavaScript
Spidermonkey
This example is a little long because it tries to emphasize generality and clarity over brevity.
#!/usr/bin/env js function main() { var wordList = read('unixdict.txt').split(/\s+/); var anagrams = findAnagrams(wordList); var derangedAnagrams = findDerangedAnagrams(anagrams); var longestPair = findLongestDerangedPair(derangedAnagrams); print(longestPair.join(' ')); } function findLongestDerangedPair(danas) { var longestLen = danas[0][0].length; var longestPair = danas[0]; for (var i in danas) { if (danas[i][0].length > longestLen) { longestLen = danas[i][0].length; longestPair = danas[i]; } } return longestPair; } function findDerangedAnagrams(anagrams) { var deranged = []; function isDeranged(w1, w2) { for (var c = 0; c < w1.length; c++) { if (w1[c] == w2[c]) { return false; } } return true; } function findDeranged(anas) { for (var a = 0; a < anas.length; a++) { for (var b = a + 1; b < anas.length; b++) { if (isDeranged(anas[a], anas[b])) { deranged.push([anas[a], anas[b]]); } } } } for (var a in anagrams) { var anas = anagrams[a]; findDeranged(anas); } return deranged; } function findAnagrams(wordList) { var anagrams = {}; for (var wordNum in wordList) { var word = wordList[wordNum]; var key = word.split('').sort().join(''); if (!(key in anagrams)) { anagrams[key] = []; } anagrams[key].push(word); } for (var a in anagrams) { if (anagrams[a].length < 2) { delete(anagrams[a]); } } return anagrams; } main();
{{out}} excitation intoxicate
Gecko
Word file is saved locally because browser won't fetch it cross-site. Tested on Gecko.
<body><pre id='x'>