Changeable words

From Rosetta Code
(Redirected from Changable words)
Changeable words is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.
Task

Using the dictionary   unixdict.txt,   change one letter in a word,   and if the changed word occurs in the dictionary,
then display the original word   and   the changed word here (on this page).

The length of any word shown should have a length   >  11.

Note
  • A copy of the specific unixdict.txt linked to should be used for consistency of results.
  • Words >  11 are required, ie of 12 characters or more.


Other tasks related to string operations:
Metrics
Counting
Remove/replace
Anagrams/Derangements/shuffling
Find/Search/Determine
Formatting
Song lyrics/poems/Mad Libs/phrases
Tokenize
Sequences



Reference



11l

Translation of: Nim
Translation of: Go
V words = File(‘unixdict.txt’).read().split("\n").filter(word -> word.len > 11)

F hamming_dist(word1, word2)
   I word1.len != word2.len
      R 0
   V count = 0
   L(i) 0 .< word1.len
      I word1[i] != word2[i]
         count++
         I count == 2
            L.break // don't care about counts > 2
   R count

print("List of changeable words:\n")
V count = 0
L(i) 0 .< words.len
   V word1 = words[i]
   L(j) i + 1 .< words.len
      V word2 = words[j]
      I hamming_dist(word1, word2) == 1
         print(word1‘ <-> ’word2)
         count += 2

print("\nFound "count‘ changeable words.’)
Output:
List of changeable words:

aristotelean <-> aristotelian
claustrophobia <-> claustrophobic
committeeman <-> committeemen
committeewoman <-> committeewomen
complementary <-> complimentary
confirmation <-> conformation
congresswoman <-> congresswomen
councilwoman <-> councilwomen
craftsperson <-> draftsperson
eavesdropped <-> eavesdropper
frontiersman <-> frontiersmen
handicraftsman <-> handicraftsmen
incommutable <-> incomputable
installation <-> instillation
kaleidescope <-> kaleidoscope
neuroanatomy <-> neuroanotomy
newspaperman <-> newspapermen
nonagenarian <-> nonogenarian
onomatopoeia <-> onomatopoeic
philanthrope <-> philanthropy
prescription <-> proscription
schizophrenia <-> schizophrenic
shakespearean <-> shakespearian
spectroscope <-> spectroscopy
underclassman <-> underclassmen
upperclassman <-> upperclassmen

Found 52 changeable words.

Ada

with Ada.Text_Io;
with Ada.Strings.Fixed;
with Ada.Containers.Indefinite_Vectors;

procedure Changeable is
   use Ada.Text_Io, Ada.Strings.Fixed;

   package String_Vectors is
     new Ada.Containers.Indefinite_Vectors (Index_Type   => Positive,
                                            Element_Type => String);

   Filename : constant String := "unixdict.txt";
   File     : File_Type;
   Words    : String_Vectors.Vector;
begin
   Open (File, In_File, Filename);
   while not End_Of_File (File) loop
      declare
         Word : constant String := Get_Line (File);
      begin
         if Word'Length > 11 then
            Words.Append (Word);
         end if;
      end;
   end loop;
   Close (File);

   for Word_Index_1 in Words.First_Index .. Words.Last_Index loop
      for Word_Index_2 in Word_Index_1 + 1 .. Words.Last_Index loop
         declare
            Image  : String (1 .. 14);
            Word_1 : String renames Words (Word_Index_1);
            Word_2 : String renames Words (Word_Index_2);
            Match  : Natural := 0;
         begin
            if Word_1'Length = Word_2'Length then
               for Index in Word_1'Range loop
                  if Word_1 (Index) = Word_2 (Index) then
                     Match := Match + 1;
                  end if;
               end loop;
               if Match = Word_1'Length - 1 then
                  Move (Word_1, Image);
                  Put (Image); Put (" <-> ");
                  Move (Word_2, Image);
                  Put_Line (Image);
               end if;
            end if;
         end;
      end loop;
   end loop;
end Changeable;
Output:
aristotelean   <-> aristotelian
claustrophobia <-> claustrophobic
committeeman   <-> committeemen
committeewoman <-> committeewomen
complementary  <-> complimentary
confirmation   <-> conformation
congresswoman  <-> congresswomen
councilwoman   <-> councilwomen
craftsperson   <-> draftsperson
eavesdropped   <-> eavesdropper
frontiersman   <-> frontiersmen
handicraftsman <-> handicraftsmen
incommutable   <-> incomputable
installation   <-> instillation
kaleidescope   <-> kaleidoscope
neuroanatomy   <-> neuroanotomy
newspaperman   <-> newspapermen
nonagenarian   <-> nonogenarian
onomatopoeia   <-> onomatopoeic
philanthrope   <-> philanthropy
prescription   <-> proscription
schizophrenia  <-> schizophrenic
shakespearean  <-> shakespearian
spectroscope   <-> spectroscopy
underclassman  <-> underclassmen
upperclassman  <-> upperclassmen

ALGOL 68

Brute force - tries each possible changed word.

# find words where changing one letter results in another word               #
# only words of 12 or more characters are to be considered                   #
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 - notes eof has been reached and      #
    # returns TRUE so processing can continue                                #
    on logical file end( input file, ( REF FILE f )BOOL: at eof := TRUE );

    # table of possible words - there are around 1000 12+ character words    #                                               
    [ 1 : 2 000 ]STRING words;                          # in unixdict.txt    #

    # in-place quick sort an array of strings                                #
    PROC s quicksort = ( REF[]STRING a, INT lb, ub )VOID:
         IF ub > lb
         THEN
            # more than one element, so must sort                            #
            INT  left  := lb;
            INT  right := ub;
            # choosing the middle element of the array as the pivot          #
            STRING pivot := a[ left + ( ( right + 1 ) - left ) OVER 2 ];
            WHILE
                WHILE IF left  <= ub THEN a[ left  ] < pivot ELSE FALSE FI
                DO
                    left  +:= 1
                OD;
                WHILE IF right >= lb THEN a[ right ] > pivot ELSE FALSE FI
                DO
                    right -:= 1
                OD;
                left <= right
            DO
                STRING t   := a[ left  ];
                a[ left  ] := a[ right ];
                a[ right ] := t;
                left      +:= 1;
                right     -:= 1
            OD;
            s quicksort( a, lb,   right );
            s quicksort( a, left, ub    )
         FI # s quicksort # ;

    # returns the length of s                                                #
    OP LENGTH = ( STRING s )INT: 1 + ( UPB s - LWB s );

    # returns TRUE if words[ low : high ] contains s, FALSE otherwise        #
    PROC is word = ( STRING s, INT low, high )BOOL:
         IF   high < low THEN FALSE
         ELSE INT mid = ( low + high ) OVER 2;
              IF   words[ mid ] > s THEN is word( s, low,     mid - 1 )
              ELIF words[ mid ] = s THEN TRUE
              ELSE                       is word( s, mid + 1, high    )
              FI
         FI # is word # ;

    INT   w count := 0;                       # store the 12 character words #
    WHILE
        STRING word;
        get( input file, ( word, newline ) );
        NOT at eof
    DO
        IF LENGTH word > 11 THEN
            words[ w count +:= 1 ] := word
        FI
    OD;
    close( input file );
    s quicksort( words, 1, w count );                       # sort the words #
    FOR i TO w count DO                          # find the changeable words #
        STRING word    = words[ i ];
        STRING c word := word;
        FOR i FROM LWB word TO UPB word DO
            FOR c FROM ABS word[ i ] + 1 TO ABS "z" DO
                c word[ i ] := REPR c;
                IF is word( c word, 1, w count ) THEN
                    FOR p FROM LENGTH word TO 15 DO print( ( " " ) ) OD;
                    print( ( word, " <-> ", c word, newline ) )
                FI
            OD;
            c word[ i ] := word[ i ]
        OD
    OD
FI
Output:
    aristotelean <-> aristotelian
  claustrophobia <-> claustrophobic
    committeeman <-> committeemen
  committeewoman <-> committeewomen
   complementary <-> complimentary
    confirmation <-> conformation
   congresswoman <-> congresswomen
    councilwoman <-> councilwomen
    craftsperson <-> draftsperson
    eavesdropped <-> eavesdropper
    frontiersman <-> frontiersmen
  handicraftsman <-> handicraftsmen
    incommutable <-> incomputable
    installation <-> instillation
    kaleidescope <-> kaleidoscope
    neuroanatomy <-> neuroanotomy
    newspaperman <-> newspapermen
    nonagenarian <-> nonogenarian
    onomatopoeia <-> onomatopoeic
    philanthrope <-> philanthropy
    prescription <-> proscription
   schizophrenia <-> schizophrenic
   shakespearean <-> shakespearian
    spectroscope <-> spectroscopy
   underclassman <-> underclassmen
   upperclassman <-> upperclassmen

Arturo

wordset: map read.lines relative "unixdict.txt" => strip

wordset: select wordset 'word -> 12 =< size word
results: new []
loop wordset 'a [
    loop select wordset 'word [equal? size a size word] 'b [
        if a <> b [
            if 1 = levenshtein a b [
                'results ++ @[sort @[a b]]
            ]
        ]
    ]
]

loop unique results 'result -> 
    print join.with:" - " result
Output:
aristotelean - aristotelian
claustrophobia - claustrophobic
committeeman - committeemen
committeewoman - committeewomen
complementary - complimentary
confirmation - conformation
congresswoman - congresswomen
councilwoman - councilwomen
craftsperson - draftsperson
eavesdropped - eavesdropper
frontiersman - frontiersmen
handicraftsman - handicraftsmen
incommutable - incomputable
installation - instillation
kaleidescope - kaleidoscope
neuroanatomy - neuroanotomy
newspaperman - newspapermen
nonagenarian - nonogenarian
onomatopoeia - onomatopoeic
philanthrope - philanthropy
prescription - proscription
schizophrenia - schizophrenic
shakespearean - shakespearian
spectroscope - spectroscopy
underclassman - underclassmen
upperclassman - upperclassmen

AutoHotkey

FileRead, db, % A_Desktop "\unixdict.txt"
oWord := [], Found := []
for i, word in StrSplit(db, "`n", "`r")
    if (StrLen(word) > 11)
        oWord[word] := true

for word1 in oWord
    for word2 in oWord
        if Changeable(word1, word2) && !Found[word2]
            found[word1] := true, result .= word1 . "`t<-> " word2 "`n"

MsgBox, 262144, , % result
return

Changeable(s1, s2) {
    if (StrLen(s1) <> StrLen(s2))
        return 0
    for i, v in StrSplit(s1)
        if (v = SubStr(s2, i, 1))
            num++
    return (StrLen(s1) - num = 1)
}
Output:
aristotelean	<-> aristotelian
claustrophobia	<-> claustrophobic
committeeman	<-> committeemen
committeewoman	<-> committeewomen
complementary	<-> complimentary
confirmation	<-> conformation
congresswoman	<-> congresswomen
councilwoman	<-> councilwomen
craftsperson	<-> draftsperson
eavesdropped	<-> eavesdropper
frontiersman	<-> frontiersmen
handicraftsman	<-> handicraftsmen
incommutable	<-> incomputable
installation	<-> instillation
kaleidescope	<-> kaleidoscope
neuroanatomy	<-> neuroanotomy
newspaperman	<-> newspapermen
nonagenarian	<-> nonogenarian
onomatopoeia	<-> onomatopoeic
philanthrope	<-> philanthropy
prescription	<-> proscription
schizophrenia	<-> schizophrenic
shakespearean	<-> shakespearian
spectroscope	<-> spectroscopy
underclassman	<-> underclassmen
upperclassman	<-> upperclassmen

AWK

# syntax: GAWK -f CHANGEABLE_WORDS.AWK unixdict.txt
#
# sorting:
#   PROCINFO["sorted_in"] is used by GAWK
#   SORTTYPE is used by Thompson Automation's TAWK
#
{ arr[$0]++ }
END {
    a2z = "abcdefghijklmnopqrstuvwxyz"
    a2z_leng = length(a2z)
    PROCINFO["sorted_in"] = "@ind_str_asc" ; SORTTYPE = 1
    for (word in arr) {
      leng = length(word)
      if (leng < 12) {
        continue
      }
      printed = 0
      for (i=1; i<=leng; i++) {
        if (printed == 1) {
          break
        }
        if (i == 1) {
          L = ""
          R = substr(word,2)
        }
        else if (i == leng) {
          L = substr(word,1,leng-1)
          R = ""
        }
        else {
          L = substr(word,1,i)
          R = substr(word,i+2)
        }
        for (j=1; j<a2z_leng; j++) {
          new_word = L substr(a2z,j,1) R
          if (new_word in arr && word != new_word) {
            printf("%-15s %s\n",word,new_word)
            printed = 1
            count++
          }
        }
      }
    }
    printf("%d words\n",count)
    exit(0)
}
Output:
aristotelean    aristotelian
aristotelian    aristotelean
claustrophobia  claustrophobic
claustrophobic  claustrophobia
committeeman    committeemen
committeemen    committeeman
committeewoman  committeewomen
committeewomen  committeewoman
complementary   complimentary
complimentary   complementary
confirmation    conformation
conformation    confirmation
congresswoman   congresswomen
congresswomen   congresswoman
councilwoman    councilwomen
councilwomen    councilwoman
craftsperson    draftsperson
draftsperson    craftsperson
eavesdropped    eavesdropper
eavesdropper    eavesdropped
frontiersman    frontiersmen
frontiersmen    frontiersman
handicraftsman  handicraftsmen
handicraftsmen  handicraftsman
incommutable    incomputable
incomputable    incommutable
installation    instillation
instillation    installation
kaleidescope    kaleidoscope
kaleidoscope    kaleidescope
neuroanatomy    neuroanotomy
neuroanotomy    neuroanatomy
newspaperman    newspapermen
newspapermen    newspaperman
nonagenarian    nonogenarian
nonogenarian    nonagenarian
onomatopoeia    onomatopoeic
onomatopoeic    onomatopoeia
philanthrope    philanthropy
philanthropy    philanthrope
prescription    proscription
proscription    prescription
schizophrenia   schizophrenic
schizophrenic   schizophrenia
shakespearean   shakespearian
shakespearian   shakespearean
spectroscope    spectroscopy
spectroscopy    spectroscope
underclassman   underclassmen
underclassmen   underclassman
upperclassman   upperclassmen
upperclassmen   upperclassman

C

Translation of: Go
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_WORD_SIZE 32

typedef struct string_tag {
    size_t length;
    char str[MAX_WORD_SIZE];
} string_t;

void fatal(const char* message) {
    fprintf(stderr, "%s\n", message);
    exit(1);
}

void* xmalloc(size_t n) {
    void* ptr = malloc(n);
    if (ptr == NULL)
        fatal("Out of memory");
    return ptr;
}

void* xrealloc(void* p, size_t n) {
    void* ptr = realloc(p, n);
    if (ptr == NULL)
        fatal("Out of memory");
    return ptr;
}

int hamming_distance(const string_t* str1, const string_t* str2) {
    size_t len1 = str1->length;
    size_t len2 = str2->length;
    if (len1 != len2)
        return 0;
    int count = 0;
    const char* s1 = str1->str;
    const char* s2 = str2->str;
    for (size_t i = 0; i < len1; ++i) {
        if (s1[i] != s2[i])
            ++count;
        // don't care about counts > 2 in this case
        if (count == 2)
            break;
    }
    return count;
}

int main(int argc, char** argv) {
    const char* filename = argc < 2 ? "unixdict.txt" : argv[1];
    FILE* in = fopen(filename, "r");
    if (!in) {
        perror(filename);
        return EXIT_FAILURE;
    }
    char line[MAX_WORD_SIZE];
    size_t size = 0, capacity = 1024;
    string_t* dictionary = xmalloc(sizeof(string_t) * capacity);
    while (fgets(line, sizeof(line), in)) {
        if (size == capacity) {
            capacity *= 2;
            dictionary = xrealloc(dictionary, sizeof(string_t) * capacity);
        }
        size_t len = strlen(line) - 1;
        if (len > 11) {
            string_t* str = &dictionary[size];
            str->length = len;
            memcpy(str->str, line, len);
            str->str[len] = '\0';
            ++size;
        }
    }
    fclose(in);
    printf("Changeable words in %s:\n", filename);
    int n = 1;
    for (size_t i = 0; i < size; ++i) {
        const string_t* str1 = &dictionary[i];
        for (size_t j = 0; j < size; ++j) {
            const string_t* str2 = &dictionary[j];
            if (i != j && hamming_distance(str1, str2) == 1)
                printf("%2d: %-14s -> %s\n", n++, str1->str, str2->str);
        }
    }
    free(dictionary);
    return EXIT_SUCCESS;
}
Output:
Changeable words in unixdict.txt:
 1: aristotelean   -> aristotelian
 2: aristotelian   -> aristotelean
 3: claustrophobia -> claustrophobic
 4: claustrophobic -> claustrophobia
 5: committeeman   -> committeemen
 6: committeemen   -> committeeman
 7: committeewoman -> committeewomen
 8: committeewomen -> committeewoman
 9: complementary  -> complimentary
10: complimentary  -> complementary
11: confirmation   -> conformation
12: conformation   -> confirmation
13: congresswoman  -> congresswomen
14: congresswomen  -> congresswoman
15: councilwoman   -> councilwomen
16: councilwomen   -> councilwoman
17: craftsperson   -> draftsperson
18: draftsperson   -> craftsperson
19: eavesdropped   -> eavesdropper
20: eavesdropper   -> eavesdropped
21: frontiersman   -> frontiersmen
22: frontiersmen   -> frontiersman
23: handicraftsman -> handicraftsmen
24: handicraftsmen -> handicraftsman
25: incommutable   -> incomputable
26: incomputable   -> incommutable
27: installation   -> instillation
28: instillation   -> installation
29: kaleidescope   -> kaleidoscope
30: kaleidoscope   -> kaleidescope
31: neuroanatomy   -> neuroanotomy
32: neuroanotomy   -> neuroanatomy
33: newspaperman   -> newspapermen
34: newspapermen   -> newspaperman
35: nonagenarian   -> nonogenarian
36: nonogenarian   -> nonagenarian
37: onomatopoeia   -> onomatopoeic
38: onomatopoeic   -> onomatopoeia
39: philanthrope   -> philanthropy
40: philanthropy   -> philanthrope
41: prescription   -> proscription
42: proscription   -> prescription
43: schizophrenia  -> schizophrenic
44: schizophrenic  -> schizophrenia
45: shakespearean  -> shakespearian
46: shakespearian  -> shakespearean
47: spectroscope   -> spectroscopy
48: spectroscopy   -> spectroscope
49: underclassman  -> underclassmen
50: underclassmen  -> underclassman
51: upperclassman  -> upperclassmen
52: upperclassmen  -> upperclassman

C++

Translation of: Go
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

int hamming_distance(const std::string& str1, const std::string& str2) {
    size_t len1 = str1.size();
    size_t len2 = str2.size();
    if (len1 != len2)
        return 0;
    int count = 0;
    for (size_t i = 0; i < len1; ++i) {
        if (str1[i] != str2[i])
            ++count;
        // don't care about counts > 2 in this case
        if (count == 2)
            break;
    }
    return count;
}

int main(int argc, char** argv) {
    const char* filename(argc < 2 ? "unixdict.txt" : argv[1]);
    std::ifstream in(filename);
    if (!in) {
        std::cerr << "Cannot open file '" << filename << "'.\n";
        return EXIT_FAILURE;
    }
    std::string line;
    std::vector<std::string> dictionary;
    while (getline(in, line)) {
        if (line.size() > 11)
            dictionary.push_back(line);
    }
    std::cout << "Changeable words in " << filename << ":\n";
    int n = 1;
    for (const std::string& word1 : dictionary) {
        for (const std::string& word2 : dictionary) {
            if (hamming_distance(word1, word2) == 1)
                std::cout << std::setw(2) << std::right << n++
                    << ": " << std::setw(14) << std::left << word1
                    << " -> " << word2 << '\n';
        }
    }
    return EXIT_SUCCESS;
}
Output:
Changeable words in unixdict.txt:
 1: aristotelean   -> aristotelian
 2: aristotelian   -> aristotelean
 3: claustrophobia -> claustrophobic
 4: claustrophobic -> claustrophobia
 5: committeeman   -> committeemen
 6: committeemen   -> committeeman
 7: committeewoman -> committeewomen
 8: committeewomen -> committeewoman
 9: complementary  -> complimentary
10: complimentary  -> complementary
11: confirmation   -> conformation
12: conformation   -> confirmation
13: congresswoman  -> congresswomen
14: congresswomen  -> congresswoman
15: councilwoman   -> councilwomen
16: councilwomen   -> councilwoman
17: craftsperson   -> draftsperson
18: draftsperson   -> craftsperson
19: eavesdropped   -> eavesdropper
20: eavesdropper   -> eavesdropped
21: frontiersman   -> frontiersmen
22: frontiersmen   -> frontiersman
23: handicraftsman -> handicraftsmen
24: handicraftsmen -> handicraftsman
25: incommutable   -> incomputable
26: incomputable   -> incommutable
27: installation   -> instillation
28: instillation   -> installation
29: kaleidescope   -> kaleidoscope
30: kaleidoscope   -> kaleidescope
31: neuroanatomy   -> neuroanotomy
32: neuroanotomy   -> neuroanatomy
33: newspaperman   -> newspapermen
34: newspapermen   -> newspaperman
35: nonagenarian   -> nonogenarian
36: nonogenarian   -> nonagenarian
37: onomatopoeia   -> onomatopoeic
38: onomatopoeic   -> onomatopoeia
39: philanthrope   -> philanthropy
40: philanthropy   -> philanthrope
41: prescription   -> proscription
42: proscription   -> prescription
43: schizophrenia  -> schizophrenic
44: schizophrenic  -> schizophrenia
45: shakespearean  -> shakespearian
46: shakespearian  -> shakespearean
47: spectroscope   -> spectroscopy
48: spectroscopy   -> spectroscope
49: underclassman  -> underclassmen
50: underclassmen  -> underclassman
51: upperclassman  -> upperclassmen
52: upperclassmen  -> upperclassman

Delphi

Works with: Delphi version 6.0


var Dict: TStringList;	{List holds dictionary}


procedure DoChangeWords(Memo: TMemo);
{Do change word problem}
var I,J,K,Inx,Cnt: integer;
var S: string;

	procedure Display(OldStr,NewStr: string);
	{Display both the original word and the modified word}
	begin
	Inc(Cnt);
	Memo.Lines.Add(IntToStr(Cnt)+': '+OldStr+' '+NewStr);
	end;

begin
Cnt:=0;
{Check every word in the dictionary}
for I:=0 to Dict.Count-1 do
	begin
	{Only check words at least 12 chars long}
	if Length(Dict[I])>=12 then
		begin
		{Test a specific position in the word}
		for J:=1 to Length(Dict[I]) do
			begin
			{try all combinations of alpha chars in the position}
			for K:=byte('a') to byte('z') do
				begin
				{skip if the char is already in the position}
				if Dict[I][J]=char(K) then continue;
				{Get a new copy of the word to modify}
				S:=Dict[I];
				{Modify the position}
				S[J]:=char(K);
				{Is it in the dictionary? }
				Inx:=Dict.IndexOf(S);
				{Display it if it is}
				if Inx>=0 then Display(Dict[I],S);
				end;
			end;
		end;
	end;
end;


initialization
{Create/load dictionary}
Dict:=TStringList.Create;
Dict.LoadFromFile('unixdict.txt');
Dict.Sorted:=True;
finalization
Dict.Free;
end.
Output:

1: aristotelean aristotelian
2: aristotelian aristotelean
3: claustrophobia claustrophobic
4: claustrophobic claustrophobia
5: committeeman committeemen
6: committeemen committeeman
7: committeewoman committeewomen
8: committeewomen committeewoman
9: complementary complimentary
10: complimentary complementary
11: confirmation conformation
12: conformation confirmation
13: congresswoman congresswomen
14: congresswomen congresswoman
15: councilwoman councilwomen
16: councilwomen councilwoman
17: craftsperson draftsperson
18: draftsperson craftsperson
19: eavesdropped eavesdropper
20: eavesdropper eavesdropped
21: frontiersman frontiersmen
22: frontiersmen frontiersman
23: handicraftsman handicraftsmen
24: handicraftsmen handicraftsman
25: incommutable incomputable
26: incomputable incommutable
27: installation instillation
28: instillation installation
29: kaleidescope kaleidoscope
30: kaleidoscope kaleidescope
31: neuroanatomy neuroanotomy
32: neuroanotomy neuroanatomy
33: newspaperman newspapermen
34: newspapermen newspaperman
35: nonagenarian nonogenarian
36: nonogenarian nonagenarian
37: onomatopoeia onomatopoeic
38: onomatopoeic onomatopoeia
39: philanthrope philanthropy
40: philanthropy philanthrope
41: prescription proscription
42: proscription prescription
43: schizophrenia schizophrenic
44: schizophrenic schizophrenia
45: shakespearean shakespearian
46: shakespearian shakespearean
47: spectroscope spectroscopy
48: spectroscopy spectroscope
49: underclassman underclassmen
50: underclassmen underclassman
51: upperclassman upperclassmen
52: upperclassmen upperclassman

F#

// Changeable words: Nigel Galloway. June 14th., 2021
let     fN   g=Seq.fold2(fun z n g->z+if n=g then 0 else 1) 0 g
let rec fG n g=match g with h::t->fG(n@(t|>List.choose(fun g->match fN g h with 1->Some(h,g)|_->None))) t|_->n
seq{use n=System.IO.File.OpenText("unixdict.txt") in while not n.EndOfStream do yield n.ReadLine()}|>Seq.filter(fun n->n.Length>11)
  |>List.ofSeq|>List.groupBy(fun n->n.Length)|>Seq.collect(fun(_,n)->fG [] n)|>Seq.iter(fun(n,g)->printfn "%s <=> %s" n g)
Output:
claustrophobia <=> claustrophobic
committeewoman <=> committeewomen
handicraftsman <=> handicraftsmen
aristotelean <=> aristotelian
committeeman <=> committeemen
confirmation <=> conformation
councilwoman <=> councilwomen
craftsperson <=> draftsperson
eavesdropped <=> eavesdropper
frontiersman <=> frontiersmen
incommutable <=> incomputable
installation <=> instillation
kaleidescope <=> kaleidoscope
neuroanatomy <=> neuroanotomy
newspaperman <=> newspapermen
nonagenarian <=> nonogenarian
onomatopoeia <=> onomatopoeic
philanthrope <=> philanthropy
prescription <=> proscription
spectroscope <=> spectroscopy
complementary <=> complimentary
congresswoman <=> congresswomen
schizophrenia <=> schizophrenic
shakespearean <=> shakespearian
underclassman <=> underclassmen
upperclassman <=> upperclassmen

Factor

Works with: Factor version 0.99 2021-02-05
USING: assocs combinators.short-circuit formatting
io.encodings.ascii io.files kernel math math.combinatorics
math.distances sequences ;

: changeable? ( str str -- ? )
    { [ [ length ] bi@ = ] [ hamming-distance 1 = ] } 2&& ;

"unixdict.txt" ascii file-lines
[ length 11 > ] filter
2 [ first2 changeable? ] filter-combinations
[ "%s <-> %s\n" printf ] assoc-each
Output:
aristotelean <-> aristotelian
claustrophobia <-> claustrophobic
committeeman <-> committeemen
committeewoman <-> committeewomen
complementary <-> complimentary
confirmation <-> conformation
congresswoman <-> congresswomen
councilwoman <-> councilwomen
craftsperson <-> draftsperson
eavesdropped <-> eavesdropper
frontiersman <-> frontiersmen
handicraftsman <-> handicraftsmen
incommutable <-> incomputable
installation <-> instillation
kaleidescope <-> kaleidoscope
neuroanatomy <-> neuroanotomy
newspaperman <-> newspapermen
nonagenarian <-> nonogenarian
onomatopoeia <-> onomatopoeic
philanthrope <-> philanthropy
prescription <-> proscription
schizophrenia <-> schizophrenic
shakespearean <-> shakespearian
spectroscope <-> spectroscopy
underclassman <-> underclassmen
upperclassman <-> upperclassmen

FreeBASIC

Brute force method, reuses some code from Odd_words#FreeBASIC.

#define NULL 0

type node
    word as string*32   'enough space to store any word in the dictionary
    nxt as node ptr
end type

function addword( tail as node ptr, word as string ) as node ptr
    'allocates memory for a new node, links the previous tail to it,
    'and returns the address of the new node
    dim as node ptr newnode = allocate(sizeof(node))
    tail->nxt = newnode
    newnode->nxt = NULL
    newnode->word = word
    return newnode
end function

function length( word as string ) as uinteger
    'necessary replacement for the built-in len function, which in this
    'case would always return 32
    for i as uinteger = 1 to 32
        if asc(mid(word,i,1)) = 0 then return i-1
    next i
    return 999
end function

dim as string word
dim as node ptr tail = allocate( sizeof(node) )
dim as node ptr head = tail, curr = head, currj
tail->nxt = NULL
tail->word = "XXXXHEADER"

open "unixdict.txt" for input as #1
while true
    line input #1, word
    if word = "" then exit while
    if length(word)>11 then tail = addword( tail, word )
wend
close #1

dim as string tempword

while curr->nxt <> NULL
    for i as uinteger = 1 to length(curr->word)
        for j as uinteger = 97 to 122
            if j = asc(mid(curr->word,i,1)) then continue for
            tempword = left(curr->word,i-1)+ chr(j) + mid(curr->word, i+1, length(curr->word)-i)
            currj = head
            while currj->nxt <> NULL
                if tempword = currj->word then print left(curr->word,length(curr->word));"   --->   ";tempword
                currj = currj->nxt
            wend
        next j
    next i
    curr = curr->nxt
wend
Output:
aristotelean   --->   aristotelian
aristotelian   --->   aristotelean
claustrophobia   --->   claustrophobic
claustrophobic   --->   claustrophobia
committeeman   --->   committeemen
committeemen   --->   committeeman
committeewoman   --->   committeewomen
committeewomen   --->   committeewoman
complementary   --->   complimentary
complimentary   --->   complementary
confirmation   --->   conformation
conformation   --->   confirmation
congresswoman   --->   congresswomen
congresswomen   --->   congresswoman
councilwoman   --->   councilwomen
councilwomen   --->   councilwoman
craftsperson   --->   draftsperson
draftsperson   --->   craftsperson
eavesdropped   --->   eavesdropper
eavesdropper   --->   eavesdropped
frontiersman   --->   frontiersmen
frontiersmen   --->   frontiersman
handicraftsman   --->   handicraftsmen
handicraftsmen   --->   handicraftsman
incommutable   --->   incomputable
incomputable   --->   incommutable
installation   --->   instillation
instillation   --->   installation
kaleidescope   --->   kaleidoscope
kaleidoscope   --->   kaleidescope
neuroanatomy   --->   neuroanotomy
neuroanotomy   --->   neuroanatomy
newspaperman   --->   newspapermen
newspapermen   --->   newspaperman
nonagenarian   --->   nonogenarian
nonogenarian   --->   nonagenarian
onomatopoeia   --->   onomatopoeic
onomatopoeic   --->   onomatopoeia
philanthrope   --->   philanthropy
philanthropy   --->   philanthrope
prescription   --->   proscription
proscription   --->   prescription
schizophrenia   --->   schizophrenic
schizophrenic   --->   schizophrenia
shakespearean   --->   shakespearian
shakespearian   --->   shakespearean
spectroscope   --->   spectroscopy
spectroscopy   --->   spectroscope
underclassman   --->   underclassmen
underclassmen   --->   underclassman
upperclassman   --->   upperclassmen
upperclassmen   --->   upperclassman

FutureBasic

#plist NSAppTransportSecurity @{NSAllowsArbitraryLoads:YES}

include "NSLog.incl"

local fn Words as CFArrayRef
  CFURLRef          url = fn URLWithString( @"http://wiki.puzzlers.org/pub/wordlists/unixdict.txt" )
  CFStringRef       string = fn StringWithContentsOfURL( url, NSUTF8StringEncoding, NULL )
  CFArrayRef        array = fn StringComponentsSeparatedByCharactersInSet( string, fn CharacterSetNewlineSet )
  CFMutableArrayRef words = fn MutableArrayWithCapacity(0)
  
  for string in array
    if ( len(string) > 11 )
      MutableArrayAddObject( words, string )
    end if
  next
end fn = words

void local fn DoIt
  dispatchglobal
    CFArrayRef  words = fn Words
    CFStringRef wd1, wd2
    long        length, i, j
    unichar     chr
    
    for wd1 in words
      length = len(wd1)
      for i = 0 to length - 1
        chr = fn StringCharacterAtIndex( wd1, i )
        for j = 97 to 122
          if ( j == chr ) then continue
          wd2 = fn StringWithFormat( @"%@%c%@", left(wd1,i), j, right(wd1,length-i-1) )
          if ( fn ArrayContainsObject( words, wd2 ) )
            NSLog(@"%@\t%@",wd1,wd2)
          end if
        next
      next
    next
  dispatchend
end fn

fn DoIt

HandleEvents
Output:
aristotelean	aristotelian
aristotelian	aristotelean
claustrophobia	claustrophobic
claustrophobic	claustrophobia
committeeman	committeemen
committeemen	committeeman
committeewoman	committeewomen
committeewomen	committeewoman
complementary	complimentary
complimentary	complementary
confirmation	conformation
conformation	confirmation
congresswoman	congresswomen
congresswomen	congresswoman
councilwoman	councilwomen
councilwomen	councilwoman
craftsperson	draftsperson
draftsperson	craftsperson
eavesdropped	eavesdropper
eavesdropper	eavesdropped
frontiersman	frontiersmen
frontiersmen	frontiersman
handicraftsman	handicraftsmen
handicraftsmen	handicraftsman
incommutable	incomputable
incomputable	incommutable
installation	instillation
instillation	installation
kaleidescope	kaleidoscope
kaleidoscope	kaleidescope
neuroanatomy	neuroanotomy
neuroanotomy	neuroanatomy
newspaperman	newspapermen
newspapermen	newspaperman
nonagenarian	nonogenarian
nonogenarian	nonagenarian
onomatopoeia	onomatopoeic
onomatopoeic	onomatopoeia
philanthrope	philanthropy
philanthropy	philanthrope
prescription	proscription
proscription	prescription
schizophrenia	schizophrenic
schizophrenic	schizophrenia
shakespearean	shakespearian
shakespearian	shakespearean
spectroscope	spectroscopy
spectroscopy	spectroscope
underclassman	underclassmen
underclassmen	underclassman
upperclassman	upperclassmen
upperclassmen	upperclassman

Go

Translation of: Wren
package main

import (
    "bytes"
    "fmt"
    "io/ioutil"
    "log"
    "unicode/utf8"
)

func hammingDist(s1, s2 string) int {
    r1 := []rune(s1) // in case there are non-ASCII characters
    r2 := []rune(s2) // ditto
    if len(r1) != len(r2) {
        return 0
    }
    count := 0
    for i := 0; i < len(r1); i++ {
        if r1[i] != r2[i] {
            count++
            if count == 2 {
                break // don't care about counts > 2
            }
        }
    }
    return count
}

func main() {
    wordList := "unixdict.txt"
    b, err := ioutil.ReadFile(wordList)
    if err != nil {
        log.Fatal("Error reading file")
    }
    bwords := bytes.Fields(b)
    var words []string
    for _, bword := range bwords {
        s := string(bword)
        if utf8.RuneCountInString(s) > 11 {
            words = append(words, s)
        }
    }
    count := 0
    fmt.Println("Changeable words in", wordList, "\b:")
    for _, word1 := range words {
        for _, word2 := range words {
            if word1 != word2 && hammingDist(word1, word2) == 1 {
                count++
                fmt.Printf("%2d: %-14s -> %s\n", count, word1, word2)
            }
        }
    }
}
Output:
Changeable words in unixdict.txt:
 1: aristotelean   -> aristotelian
 2: aristotelian   -> aristotelean
 3: claustrophobia -> claustrophobic
 4: claustrophobic -> claustrophobia
 5: committeeman   -> committeemen
 6: committeemen   -> committeeman
 7: committeewoman -> committeewomen
 8: committeewomen -> committeewoman
 9: complementary  -> complimentary
10: complimentary  -> complementary
11: confirmation   -> conformation
12: conformation   -> confirmation
13: congresswoman  -> congresswomen
14: congresswomen  -> congresswoman
15: councilwoman   -> councilwomen
16: councilwomen   -> councilwoman
17: craftsperson   -> draftsperson
18: draftsperson   -> craftsperson
19: eavesdropped   -> eavesdropper
20: eavesdropper   -> eavesdropped
21: frontiersman   -> frontiersmen
22: frontiersmen   -> frontiersman
23: handicraftsman -> handicraftsmen
24: handicraftsmen -> handicraftsman
25: incommutable   -> incomputable
26: incomputable   -> incommutable
27: installation   -> instillation
28: instillation   -> installation
29: kaleidescope   -> kaleidoscope
30: kaleidoscope   -> kaleidescope
31: neuroanatomy   -> neuroanotomy
32: neuroanotomy   -> neuroanatomy
33: newspaperman   -> newspapermen
34: newspapermen   -> newspaperman
35: nonagenarian   -> nonogenarian
36: nonogenarian   -> nonagenarian
37: onomatopoeia   -> onomatopoeic
38: onomatopoeic   -> onomatopoeia
39: philanthrope   -> philanthropy
40: philanthropy   -> philanthrope
41: prescription   -> proscription
42: proscription   -> prescription
43: schizophrenia  -> schizophrenic
44: schizophrenic  -> schizophrenia
45: shakespearean  -> shakespearian
46: shakespearian  -> shakespearean
47: spectroscope   -> spectroscopy
48: spectroscopy   -> spectroscope
49: underclassman  -> underclassmen
50: underclassmen  -> underclassman
51: upperclassman  -> upperclassmen
52: upperclassmen  -> upperclassman

J

   ;:inv ({~ ~.@($ /:~"1@#: I.@,)@(1=+/ .~: ::0:&>/~))(#~ 11<#@>) cutLF fread'unixdict.txt'
aristotelean aristotelian    
claustrophobia claustrophobic
committeeman committeemen    
committeewoman committeewomen
complementary complimentary  
confirmation conformation    
congresswoman congresswomen  
councilwoman councilwomen    
craftsperson draftsperson    
eavesdropped eavesdropper    
frontiersman frontiersmen    
handicraftsman handicraftsmen
incommutable incomputable    
installation instillation    
kaleidescope kaleidoscope    
neuroanatomy neuroanotomy    
newspaperman newspapermen    
nonagenarian nonogenarian    
onomatopoeia onomatopoeic    
philanthrope philanthropy    
prescription proscription    
schizophrenia schizophrenic  
shakespearean shakespearian  
spectroscope spectroscopy    
underclassman underclassmen  
upperclassman upperclassmen

Java

Translation of: Go
import java.io.*;
import java.util.*;

public class ChangeableWords {
    public static void main(String[] args) {
        try {
            final String fileName = "unixdict.txt";
            List<String> dictionary = new ArrayList<>();
            try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    if (line.length() > 11)
                        dictionary.add(line);
                }
            }
            System.out.printf("Changeable words in %s:\n", fileName);
            int n = 1;
            for (String word1 : dictionary) {
                for (String word2 : dictionary) {
                    if (word1 != word2 && hammingDistance(word1, word2) == 1)
                        System.out.printf("%2d: %-14s -> %s\n", n++, word1, word2);
                }
            }
        } catch (Exception e)  {
            e.printStackTtexture();
        }
    }

    private static int hammingDistance(String str1, String str2) {
        int len1 = str1.length();
        int len2 = str2.length();
        if (len1 != len2)
            return 0;
        int count = 0;
        for (int i = 0; i < len1; ++i) {
            if (str1.charAt(i) != str2.charAt(i))
                ++count;
            // don't care about counts > 2 in this case
            if (count == 2)
                break;
        }
        return count;
    }
}
Output:
Changeable words in unixdict.txt:
 1: aristotelean   -> aristotelian
 2: aristotelian   -> aristotelean
 3: claustrophobia -> claustrophobic
 4: claustrophobic -> claustrophobia
 5: committeeman   -> committeemen
 6: committeemen   -> committeeman
 7: committeewoman -> committeewomen
 8: committeewomen -> committeewoman
 9: complementary  -> complimentary
10: complimentary  -> complementary
11: confirmation   -> conformation
12: conformation   -> confirmation
13: congresswoman  -> congresswomen
14: congresswomen  -> congresswoman
15: councilwoman   -> councilwomen
16: councilwomen   -> councilwoman
17: craftsperson   -> draftsperson
18: draftsperson   -> craftsperson
19: eavesdropped   -> eavesdropper
20: eavesdropper   -> eavesdropped
21: frontiersman   -> frontiersmen
22: frontiersmen   -> frontiersman
23: handicraftsman -> handicraftsmen
24: handicraftsmen -> handicraftsman
25: incommutable   -> incomputable
26: incomputable   -> incommutable
27: installation   -> instillation
28: instillation   -> installation
29: kaleidescope   -> kaleidoscope
30: kaleidoscope   -> kaleidescope
31: neuroanatomy   -> neuroanotomy
32: neuroanotomy   -> neuroanatomy
33: newspaperman   -> newspapermen
34: newspapermen   -> newspaperman
35: nonagenarian   -> nonogenarian
36: nonogenarian   -> nonagenarian
37: onomatopoeia   -> onomatopoeic
38: onomatopoeic   -> onomatopoeia
39: philanthrope   -> philanthropy
40: philanthropy   -> philanthrope
41: prescription   -> proscription
42: proscription   -> prescription
43: schizophrenia  -> schizophrenic
44: schizophrenic  -> schizophrenia
45: shakespearean  -> shakespearian
46: shakespearian  -> shakespearean
47: spectroscope   -> spectroscopy
48: spectroscopy   -> spectroscope
49: underclassman  -> underclassmen
50: underclassmen  -> underclassman
51: upperclassman  -> upperclassmen
52: upperclassmen  -> upperclassman

jq

Works with: jq

Works with gojq, the Go implementation of jq

# Emit a stream of words "greater than" the input word that differ by just one character.
def changeable_to($dict):
  . as $w
  | range(0;length) as $i
  | (.[$i:$i+1]|explode[]) as $j
  | [range($j+1;123) | [.] | implode] as $alphas  # 122 is "z"
  | .[:$i] + $alphas[] + .[$i+1:]
  | select($dict[.]);
      
INDEX( inputs; . )
| . as $dict
| keys_unsorted[] # or keys[] for gojq
| select(length>11)
| . as $w
| [changeable_to($dict)] | unique[]
| "\($w) <=> \(.)"

Invocation: jq -n -rR -f changeable-words.jq unixdict.txt

Output:
aristotelean <=> aristotelian
claustrophobia <=> claustrophobic
committeeman <=> committeemen
committeewoman <=> committeewomen
complementary <=> complimentary
confirmation <=> conformation
congresswoman <=> congresswomen
councilwoman <=> councilwomen
craftsperson <=> draftsperson
eavesdropped <=> eavesdropper
frontiersman <=> frontiersmen
handicraftsman <=> handicraftsmen
incommutable <=> incomputable
installation <=> instillation
kaleidescope <=> kaleidoscope
neuroanatomy <=> neuroanotomy
newspaperman <=> newspapermen
nonagenarian <=> nonogenarian
onomatopoeia <=> onomatopoeic
philanthrope <=> philanthropy
prescription <=> proscription
schizophrenia <=> schizophrenic
shakespearean <=> shakespearian
spectroscope <=> spectroscopy
underclassman <=> underclassmen
upperclassman <=> upperclassmen

Julia

See Alternade_words#Julia for the foreachword function.

const alts = Set{String}()
function ischangeable(w, d)
    alternatives = [w[1:p[1]-1] * p[2] * w[p[1]+1:end] for p in Iterators.product(1:length(w), 'a':'z')]
    for a in alternatives
        if a != w && haskey(d, a)
            result = join(sort([w, a]), " <=> ")
            if !(result in alts)
                push!(alts, result)
                return result
            end
        end
    end
    return ""
end

foreachword("unixdict.txt", ischangeable, minlen = 12, colwidth=40, numcols=2)
Output:
Word source: unixdict.txt

aristotelean <=> aristotelian           claustrophobia <=> claustrophobic       
committeeman <=> committeemen           committeewoman <=> committeewomen
complementary <=> complimentary         confirmation <=> conformation
congresswoman <=> congresswomen         councilwoman <=> councilwomen
craftsperson <=> draftsperson           eavesdropped <=> eavesdropper
frontiersman <=> frontiersmen           handicraftsman <=> handicraftsmen
incommutable <=> incomputable           installation <=> instillation
kaleidescope <=> kaleidoscope           neuroanatomy <=> neuroanotomy
newspaperman <=> newspapermen           nonagenarian <=> nonogenarian
onomatopoeia <=> onomatopoeic           philanthrope <=> philanthropy
prescription <=> proscription           schizophrenia <=> schizophrenic
shakespearean <=> shakespearian         spectroscope <=> spectroscopy
underclassman <=> underclassmen         upperclassman <=> upperclassmen

Nim

Using the standard module std/editdistance to get the distance between two words.

import  std/editdistance, sugar

# Build list of words with length >= 12.
let words = collect(newSeq):
              for word in "unixdict.txt".lines:
                if word.len >= 12:
                  word

echo "List of changeable words:\n"
var count = 0
for i in 0..<words.high:
  let word1 = words[i]
  for j in i+1..words.high:
    let word2 = words[j]
    if word1.len == word2.len and editDistance(word1, word2) == 1:
      echo word1, " <-> ", word2
      inc count, 2

echo "\nFound ", count, " changeable words."
Output:
List of changeable words:

aristotelean <-> aristotelian
claustrophobia <-> claustrophobic
committeeman <-> committeemen
committeewoman <-> committeewomen
complementary <-> complimentary
confirmation <-> conformation
congresswoman <-> congresswomen
councilwoman <-> councilwomen
craftsperson <-> draftsperson
eavesdropped <-> eavesdropper
frontiersman <-> frontiersmen
handicraftsman <-> handicraftsmen
incommutable <-> incomputable
installation <-> instillation
kaleidescope <-> kaleidoscope
neuroanatomy <-> neuroanotomy
newspaperman <-> newspapermen
nonagenarian <-> nonogenarian
onomatopoeia <-> onomatopoeic
philanthrope <-> philanthropy
prescription <-> proscription
schizophrenia <-> schizophrenic
shakespearean <-> shakespearian
spectroscope <-> spectroscopy
underclassman <-> underclassmen
upperclassman <-> upperclassmen

Found 52 changeable words.

Pascal

Free Pascal

{$mode ObjFPC}{$H+}
uses
  Classes, SysUtils;

const
  FNAME = 'unixdict.txt';

function OneCharDifference(s1, s2: string): boolean;
var
  i, diffCount: integer;
begin
  diffCount := 0;
  if Length(s1) <> Length(s2) then
    Exit(false);
  for i := 1 to Length(s1) do
  begin
    if s1[i] <> s2[i] then
      Inc(diffCount);
    if diffCount > 1 then
      Exit(false);
  end;
  Result := diffCount = 1;
end;

procedure PurgeList(var list: TStringList);
{ Remove every word that doesn't have at least 12 characters }
var
  i: Integer;
begin
  for i := Pred(list.Count) downto 0 do    
    if Length(list[i]) < 12 then
      list.Delete(i);
end;

var
  list: TStringList;
  i, j: Integer;

begin
  list := TStringList.Create;
  try
    list.LoadFromFile(FNAME);
    PurgeList(list);
    for i := 0 to list.Count - 2 do
      for j := i + 1 to list.Count - 1 do
        if OneCharDifference(list[i], list[j]) then
          writeln(list[i]:15, ' <-> ', list[j]);
  finally
    list.Free;
  end;
end.
Output:
   aristotelean <-> aristotelian
 claustrophobia <-> claustrophobic
   committeeman <-> committeemen
 committeewoman <-> committeewomen
  complementary <-> complimentary
   confirmation <-> conformation
  congresswoman <-> congresswomen
   councilwoman <-> councilwomen
   craftsperson <-> draftsperson
   eavesdropped <-> eavesdropper
   frontiersman <-> frontiersmen
 handicraftsman <-> handicraftsmen
   incommutable <-> incomputable
   installation <-> instillation
   kaleidescope <-> kaleidoscope
   neuroanatomy <-> neuroanotomy
   newspaperman <-> newspapermen
   nonagenarian <-> nonogenarian
   onomatopoeia <-> onomatopoeic
   philanthrope <-> philanthropy
   prescription <-> proscription
  schizophrenia <-> schizophrenic
  shakespearean <-> shakespearian
   spectroscope <-> spectroscopy
  underclassman <-> underclassmen
  upperclassman <-> upperclassmen

Perl

#!/usr/bin/perl

use strict;
use warnings;

my @words;
@ARGV = 'unixdict.txt';
while( <> )
  {
  chomp;
  length > 11 or next;
  for my $prev ( @{ $words[length] } )
    {
    ($prev ^ $_) =~ tr/\0//c == 1 and printf "%30s <-> %s\n", $prev, $_;
    }
  push @{ $words[length] }, $_;
  }
Output:
                  aristotelean <-> aristotelian
                claustrophobia <-> claustrophobic
                  committeeman <-> committeemen
                committeewoman <-> committeewomen
                 complementary <-> complimentary
                  confirmation <-> conformation
                 congresswoman <-> congresswomen
                  councilwoman <-> councilwomen
                  craftsperson <-> draftsperson
                  eavesdropped <-> eavesdropper
                  frontiersman <-> frontiersmen
                handicraftsman <-> handicraftsmen
                  incommutable <-> incomputable
                  installation <-> instillation
                  kaleidescope <-> kaleidoscope
                  neuroanatomy <-> neuroanotomy
                  newspaperman <-> newspapermen
                  nonagenarian <-> nonogenarian
                  onomatopoeia <-> onomatopoeic
                  philanthrope <-> philanthropy
                  prescription <-> proscription
                 schizophrenia <-> schizophrenic
                 shakespearean <-> shakespearian
                  spectroscope <-> spectroscopy
                 underclassman <-> underclassmen
                 upperclassman <-> upperclassmen

Alternatively:

#!/usr/bin/perl

use strict; # https://rosettacode.org/wiki/Changeable_words
use warnings;

local $_ = do {local(@ARGV, $/) = 'unixdict.txt'; <> =~ s/^.{0,11}\n//gmr };
my $count = 0;
printf "%3d:  %15s  <->  %s\n", ++$count, $1, $4
  while /^ ((\N*)\N(\N*)) \n(?=.*^ (\2\N\3) \n)/gmsx;
Output:
  1:     aristotelean  <->  aristotelian
  2:   claustrophobia  <->  claustrophobic
  3:     committeeman  <->  committeemen
  4:   committeewoman  <->  committeewomen
  5:    complementary  <->  complimentary
  6:     confirmation  <->  conformation
  7:    congresswoman  <->  congresswomen
  8:     councilwoman  <->  councilwomen
  9:     craftsperson  <->  draftsperson
 10:     eavesdropped  <->  eavesdropper
 11:     frontiersman  <->  frontiersmen
 12:   handicraftsman  <->  handicraftsmen
 13:     incommutable  <->  incomputable
 14:     installation  <->  instillation
 15:     kaleidescope  <->  kaleidoscope
 16:     neuroanatomy  <->  neuroanotomy
 17:     newspaperman  <->  newspapermen
 18:     nonagenarian  <->  nonogenarian
 19:     onomatopoeia  <->  onomatopoeic
 20:     philanthrope  <->  philanthropy
 21:     prescription  <->  proscription
 22:    schizophrenia  <->  schizophrenic
 23:    shakespearean  <->  shakespearian
 24:     spectroscope  <->  spectroscopy
 25:    underclassman  <->  underclassmen
 26:    upperclassman  <->  upperclassmen

Phix

with javascript_semantics
function changeable(string a, b)
    return length(a)=length(b) and sum(sq_ne(a,b))=1
end function
 
function over11(string word) return length(word)>11 end function
sequence words = filter(unix_dict(),over11),
         res = {}
for i=1 to length(words) do
    for j=i+1 to length(words) do
        if changeable(words[i],words[j]) then
            res = append(res,words[i]&" <=> "&words[j])
        end if
    end for
end for
printf(1,"%d changeable words found:\n%s\n",{length(res),join(shorten(res,"",3),"\n")})
Output:
26 changeable words found:
aristotelean <=> aristotelian
claustrophobia <=> claustrophobic
committeeman <=> committeemen
...
spectroscope <=> spectroscopy
underclassman <=> underclassmen
upperclassman <=> upperclassmen

Prolog

Works with: SWI Prolog
Translation of: Go
:- dynamic dictionary_word/1.

main:-
    File_name = 'unixdict.txt',
    load_dictionary_from_file(File_name, 12),
    print_changeable_words(File_name).

load_dictionary_from_file(File, Min_length):-
    open(File, read, Stream),
    retractall(dictionary_word(_)),
    load_dictionary_from_stream(Stream, Min_length),
    close(Stream).

load_dictionary_from_stream(Stream, Min_length):-
    read_line_to_string(Stream, String),
    String \= end_of_file,
    !,
    string_length(String, Length),
    (Length >= Min_length ->
        assertz(dictionary_word(String))
        ;
        true),
    load_dictionary_from_stream(Stream, Min_length).
load_dictionary_from_stream(_, _).

print_changeable_words(File_name):-
    writef('Changeable words in %w:\n', [File_name]),
    findall([Word1, Word2],
            (dictionary_word(Word1),
             dictionary_word(Word2),
             Word1 \= Word2,
             hamming_distance(Word1, Word2, 1)),
            Words),
    nth1(N, Words, [Word1, Word2]),
    writef('%3r: %15l-> %w\n', [N, Word1, Word2]),
    fail.
print_changeable_words(_).

hamming_distance(String1, String2, Dist):-
    string_chars(String1, Chars1),
    string_chars(String2, Chars2),
    hamming_distance(Chars1, Chars2, Dist, 0).

hamming_distance([], [], Dist, Dist):-!.
hamming_distance([Ch|Chars1], [Ch|Chars2], Dist, Count):-
    !,
    hamming_distance(Chars1, Chars2, Dist, Count).
hamming_distance([_|Chars1], [_|Chars2], Dist, Count):-
    Count1 is Count + 1,
    Count1 < 2,
    hamming_distance(Chars1, Chars2, Dist, Count1).
Output:
Changeable words in unixdict.txt:
  1: aristotelean   -> aristotelian
  2: aristotelian   -> aristotelean
  3: claustrophobia -> claustrophobic
  4: claustrophobic -> claustrophobia
  5: committeeman   -> committeemen
  6: committeemen   -> committeeman
  7: committeewoman -> committeewomen
  8: committeewomen -> committeewoman
  9: complementary  -> complimentary
 10: complimentary  -> complementary
 11: confirmation   -> conformation
 12: conformation   -> confirmation
 13: congresswoman  -> congresswomen
 14: congresswomen  -> congresswoman
 15: councilwoman   -> councilwomen
 16: councilwomen   -> councilwoman
 17: craftsperson   -> draftsperson
 18: draftsperson   -> craftsperson
 19: eavesdropped   -> eavesdropper
 20: eavesdropper   -> eavesdropped
 21: frontiersman   -> frontiersmen
 22: frontiersmen   -> frontiersman
 23: handicraftsman -> handicraftsmen
 24: handicraftsmen -> handicraftsman
 25: incommutable   -> incomputable
 26: incomputable   -> incommutable
 27: installation   -> instillation
 28: instillation   -> installation
 29: kaleidescope   -> kaleidoscope
 30: kaleidoscope   -> kaleidescope
 31: neuroanatomy   -> neuroanotomy
 32: neuroanotomy   -> neuroanatomy
 33: newspaperman   -> newspapermen
 34: newspapermen   -> newspaperman
 35: nonagenarian   -> nonogenarian
 36: nonogenarian   -> nonagenarian
 37: onomatopoeia   -> onomatopoeic
 38: onomatopoeic   -> onomatopoeia
 39: philanthrope   -> philanthropy
 40: philanthropy   -> philanthrope
 41: prescription   -> proscription
 42: proscription   -> prescription
 43: schizophrenia  -> schizophrenic
 44: schizophrenic  -> schizophrenia
 45: shakespearean  -> shakespearian
 46: shakespearian  -> shakespearean
 47: spectroscope   -> spectroscopy
 48: spectroscopy   -> spectroscope
 49: underclassman  -> underclassmen
 50: underclassmen  -> underclassman
 51: upperclassman  -> upperclassmen
 52: upperclassmen  -> upperclassman

Python

from collections import defaultdict, Counter


def getwords(minlength=11, fname='unixdict.txt'):
    "Return set of lowercased words of > given number of characters"
    with open(fname) as f:
        words = f.read().strip().lower().split()
    return {w for w in words if len(w) > minlength}

words11 = getwords()
word_minus_1 = defaultdict(list)    # map word minus char to (word, index) pairs
minus_1_to_word = defaultdict(list) # map word minus char to word

for w in words11:
    for i in range(len(w)):
        minus_1 = w[:i] + w[i+1:]
        word_minus_1[minus_1].append((w, i))   # minus one char
        if minus_1 in words11:
            minus_1_to_word[minus_1].append(w)
    
cwords = set()  # Changed char words
for _, v in word_minus_1.items():
    if len(v) >1:
        change_indices = Counter(i for wrd, i in v)
        change_words = set(wrd for wrd, i in v)
        words_changed = None
        if len(change_words) > 1 and change_indices.most_common(1)[0][1] > 1:
            words_changed = [wrd for wrd, i in v
                             if change_indices[i] > 1]
        if words_changed:
            cwords.add(tuple(sorted(words_changed)))

print(f"{len(minus_1_to_word)} words that are from deleting a char from other words:")
for k, v in sorted(minus_1_to_word.items()):
    print(f"  {k:12} From {', '.join(v)}")

print(f"\n{len(cwords)} words that are from changing a char from other words:")
for v in sorted(cwords):
    print(f"  {v[0]:12} From {', '.join(v[1:])}")
Output:
6 words that are from deleting a char from other words:
  chromatograph From chromatography
  electroencephalograph From electroencephalography
  evolutionary From revolutionary
  michelangelo From michaelangelo
  spectrograph From spectrography
  tetrafluoride From tetrafluouride

26 words that are from changing a char from other words:
  aristotelean From aristotelian
  claustrophobia From claustrophobic
  committeeman From committeemen
  committeewoman From committeewomen
  complementary From complimentary
  confirmation From conformation
  congresswoman From congresswomen
  councilwoman From councilwomen
  craftsperson From draftsperson
  eavesdropped From eavesdropper
  frontiersman From frontiersmen
  handicraftsman From handicraftsmen
  incommutable From incomputable
  installation From instillation
  kaleidescope From kaleidoscope
  neuroanatomy From neuroanotomy
  newspaperman From newspapermen
  nonagenarian From nonogenarian
  onomatopoeia From onomatopoeic
  philanthrope From philanthropy
  prescription From proscription
  schizophrenia From schizophrenic
  shakespearean From shakespearian
  spectroscope From spectroscopy
  underclassman From underclassmen
  upperclassman From upperclassmen

Quackery

  [ over size
    over size != iff
      [ 2drop false ]
      done
    0 unrot
    witheach
      [ over i^ peek
        != rot + swap
        over 2 = if
          conclude ]
    drop 1 = ]        is 1diff ( $ $ --> b )

  $ "rosetta/unixdict.txt"
  sharefile drop nest$
  [] swap witheach
    [ dup size 11 > iff
        [ nested join ]
      else drop ]
  [ behead over witheach
      [ 2dup
        1diff iff
          [ over echo$
            sp echo$ cr ]
        else drop ]
    drop
    dup size 1 = until ]
  drop
Output:
aristotelean aristotelian
claustrophobia claustrophobic
committeeman committeemen
committeewoman committeewomen
complementary complimentary
confirmation conformation
congresswoman congresswomen
councilwoman councilwomen
craftsperson draftsperson
eavesdropped eavesdropper
frontiersman frontiersmen
handicraftsman handicraftsmen
incommutable incomputable
installation instillation
kaleidescope kaleidoscope
neuroanatomy neuroanotomy
newspaperman newspapermen
nonagenarian nonogenarian
onomatopoeia onomatopoeic
philanthrope philanthropy
prescription proscription
schizophrenia schizophrenic
shakespearean shakespearian
spectroscope spectroscopy
underclassman underclassmen
upperclassman upperclassmen

Raku

Sorensen-Dice is very fast to calculate similarities but isn't great for detecting small changes. Levenshtein is great for detecting small changes but isn't very fast.

Get the best of both worlds by doing an initial filter with Sorensen, then get exact results with Levenshtein.

use Text::Levenshtein;
use Text::Sorensen :sorensen;

my @words = grep {.chars > 11}, 'unixdict.txt'.IO.words;

my %bi-grams = @words.map: { $_ => .&bi-gram };

my %skip = @words.map: { $_ => 0 };

say (++$).fmt('%2d'), |$_ for @words.hyper.map: -> $this {
    next if %skip{$this};
    my ($word, @sorensens) = sorensen($this, %bi-grams);
    next unless @sorensens.=grep: { 1 > .[0] > .8 };
    @sorensens = @sorensens»[1].grep: {$this.chars == .chars};
    my @levenshtein = distance($this, @sorensens).grep: * == 1, :k;
    next unless +@levenshtein;
    %skip{$_}++ for @sorensens[@levenshtein];
    ": {$this.fmt('%14s')}  <->  ", @sorensens[@levenshtein].join: ', ';
}
Output:
 1:   aristotelean  <->  aristotelian
 2: claustrophobia  <->  claustrophobic
 3:   committeeman  <->  committeemen
 4: committeewoman  <->  committeewomen
 5:  complimentary  <->  complementary
 6:   confirmation  <->  conformation
 7:  congresswoman  <->  congresswomen
 8:   councilwoman  <->  councilwomen
 9:   draftsperson  <->  craftsperson
10:   eavesdropped  <->  eavesdropper
11:   frontiersman  <->  frontiersmen
12: handicraftsman  <->  handicraftsmen
13:   incommutable  <->  incomputable
14:   installation  <->  instillation
15:   kaleidescope  <->  kaleidoscope
16:   neuroanatomy  <->  neuroanotomy
17:   newspaperman  <->  newspapermen
18:   nonagenarian  <->  nonogenarian
19:   onomatopoeia  <->  onomatopoeic
20:   philanthrope  <->  philanthropy
21:   proscription  <->  prescription
22:  schizophrenia  <->  schizophrenic
23:  shakespearean  <->  shakespearian
24:   spectroscope  <->  spectroscopy
25:  underclassman  <->  underclassmen
26:  upperclassman  <->  upperclassmen

REXX

This REXX version doesn't care what order the words in the dictionary are in,   nor does it care what
case  (lower/upper/mixed)  the words are in,   the search for alternades is   caseless.

It also allows the minimum length to be specified on the command line (CL) as well as the dictionary file identifier.

/*REXX program finds changeable words (within an identified dict.), changing any letter.*/
parse arg minL iFID .                            /*obtain optional arguments from the CL*/
if minL=='' | minL=="," then minL= 12            /*Not specified?  Then use the default.*/
if iFID=='' | iFID=="," then iFID='unixdict.txt' /* "      "         "   "   "     "    */
call readDict                                    /*read & process/filter the dictionary.*/
abc= 'abcdefghijklmnopqrstuvwxyz';   upper abc   /*alphabet ordered by frequency of use.*/
Labc= length(abc)                                /*the length of the alphabet to be used*/
finds= 0                                         /*count of the  changeable words found.*/
        do j=1  for n;          L= length($.j)   /*process all the words that were found*/
        x= $.j;                 upper x          /*get an uppercased version of the word*/

           do k=1  for L;    y= substr(x, k, 1)  /*Y:  the current letter being changed.*/
              do c=1  for Labc                   /* [↓]  change the Y letter to another.*/
              ?= substr(abc, c, 1)               /*get a new char to replace one in word*/
              if ?==y  then iterate              /*Is this the same char?  Then use next*/
              new= overlay(?, x, k);   upper new /*create a spanking new (changed) word.*/
              if @.new==''  then iterate         /*New word not in dictionary?  Skip it.*/
              finds= finds + 1                   /*bump count of changeable words found.*/
              say right(left($.j, 30), 40) @.new /*indent original word for readability.*/
              end   /*c*/
           end      /*k*/
        end         /*j*/

say copies('─', 30)    finds    ' changeable words found with a minimum length of '   minL
exit 0                                           /*stick a fork in it,  we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
readDict:                           n=0;     @.= /*N:   the word count of usable words. */
              do #=1  while lines(iFID)\==0      /*read each word in the file  (word=X).*/
              x= strip( linein( iFID) )          /*pick off a word from the input line. */
              if length(x)<minL     then iterate /*Is the word too short?  Then skip it.*/
              if \datatype(x, 'M')  then iterate /* "  "    "  not alphabetic?  Skip it.*/
              n= n + 1;     $.n= x;              /*bump the word counter;  assign to $. */
              upper x;      @.x= $.n             /*assign uppercased word ───► array.   */
              end   /*#*/                        /* [↑]   semaphore name is uppercased. */
         #= # - 1                                /*adjust word count because of DO loop.*/
         say copies('─', 30) #  "words  ("n 'usable words)  in the dictionary file: ' iFID
         return
output   when using the default inputs:
────────────────────────────── 25104 words  (1064 usable words)  in the dictionary file:  unixdict.txt
          aristotelean                   aristotelian
          aristotelian                   aristotelean
          claustrophobia                 claustrophobic
          claustrophobic                 claustrophobia
          committeeman                   committeemen
          committeemen                   committeeman
          committeewoman                 committeewomen
          committeewomen                 committeewoman
          complementary                  complimentary
          complimentary                  complementary
          confirmation                   conformation
          conformation                   confirmation
          congresswoman                  congresswomen
          congresswomen                  congresswoman
          councilwoman                   councilwomen
          councilwomen                   councilwoman
          craftsperson                   draftsperson
          draftsperson                   craftsperson
          eavesdropped                   eavesdropper
          eavesdropper                   eavesdropped
          frontiersman                   frontiersmen
          frontiersmen                   frontiersman
          handicraftsman                 handicraftsmen
          handicraftsmen                 handicraftsman
          incommutable                   incomputable
          incomputable                   incommutable
          installation                   instillation
          instillation                   installation
          kaleidescope                   kaleidoscope
          kaleidoscope                   kaleidescope
          neuroanatomy                   neuroanotomy
          neuroanotomy                   neuroanatomy
          newspaperman                   newspapermen
          newspapermen                   newspaperman
          nonagenarian                   nonogenarian
          nonogenarian                   nonagenarian
          onomatopoeia                   onomatopoeic
          onomatopoeic                   onomatopoeia
          philanthrope                   philanthropy
          philanthropy                   philanthrope
          prescription                   proscription
          proscription                   prescription
          schizophrenia                  schizophrenic
          schizophrenic                  schizophrenia
          shakespearean                  shakespearian
          shakespearian                  shakespearean
          spectroscope                   spectroscopy
          spectroscopy                   spectroscope
          underclassman                  underclassmen
          underclassmen                  underclassman
          upperclassman                  upperclassmen
          upperclassmen                  upperclassman
────────────────────────────── 52  changeable words found with a minimum length of  12

Ring

cStr = read("unixdict.txt")
wordList = str2list(cStr)
num = 0

see "working..." + nl

ln = len(wordList)
for n = ln to 1 step -1
    if len(wordList[n]) < 12
       del(wordList,n)
    ok
next

see "Changable words are:" + nl

for n = 1 to len(wordList)
    len = len(wordList[n])
    for m = 1 to len
        abcList = "abcdefghijklmnopqrstuvwxyz"
        for abc in abcList
            str1 = left(wordList[n],m-1)
            str2 = abc
            str3 = right(wordList[n],len-m)
            tempWord = str1 + str2 + str3
            ind = find(wordList,tempWord) 
            bool = (ind > 0) and (wordList[n][m] != abc)
            if bool = 1 
               num = num + 1
               see "" + num + ". " + wordList[n] + " >> " + tempWord + nl
            ok
        next
    next
next

see "done..." + nl

Output:

working...
Changable words are:
1. aristotelean >> aristotelian
2. aristotelian >> aristotelean
3. claustrophobia >> claustrophobic
4. claustrophobic >> claustrophobia
5. committeeman >> committeemen
6. committeemen >> committeeman
7. committeewoman >> committeewomen
8. committeewomen >> committeewoman
9. complementary >> complimentary
10. complimentary >> complementary
11. confirmation >> conformation
12. conformation >> confirmation
13. congresswoman >> congresswomen
14. congresswomen >> congresswoman
15. councilwoman >> councilwomen
16. councilwomen >> councilwoman
17. craftsperson >> draftsperson
18. draftsperson >> craftsperson
19. eavesdropped >> eavesdropper
20. eavesdropper >> eavesdropped
21. frontiersman >> frontiersmen
22. frontiersmen >> frontiersman
23. handicraftsman >> handicraftsmen
24. handicraftsmen >> handicraftsman
25. incommutable >> incomputable
26. incomputable >> incommutable
27. installation >> instillation
28. instillation >> installation
29. kaleidescope >> kaleidoscope
30. kaleidoscope >> kaleidescope
31. neuroanatomy >> neuroanotomy
32. neuroanotomy >> neuroanatomy
33. newspaperman >> newspapermen
34. newspapermen >> newspaperman
35. nonagenarian >> nonogenarian
36. nonogenarian >> nonagenarian
37. onomatopoeia >> onomatopoeic
38. onomatopoeic >> onomatopoeia
39. philanthrope >> philanthropy
40. philanthropy >> philanthrope
41. prescription >> proscription
42. proscription >> prescription
43. schizophrenia >> schizophrenic
44. schizophrenic >> schizophrenia
45. shakespearean >> shakespearian
46. shakespearian >> shakespearean
47. spectroscope >> spectroscopy
48. spectroscopy >> spectroscope
49. underclassman >> underclassmen
50. underclassmen >> underclassman
51. upperclassman >> upperclassmen
52. upperclassmen >> upperclassman
done...

Ruby

words = File.open("unixdict.txt").readlines.map(&:chomp).select{|w| w.size > 11 }

size_groups = words.group_by(&:size).sort.map(&:last)
res =  size_groups.flat_map do |group|
  group.combination(2).select{|word1, word2| word1.chars.zip(word2.chars).one?{|c1, c2| c1 != c2} }
end

puts "Found #{res.size} changeable word pairs:"
res.each{|w1, w2|puts "#{w1} - #{w2}" }
Output:
Found 26 changeable word pairs:
aristotelean - aristotelian
committeeman - committeemen
confirmation - conformation
councilwoman - councilwomen
craftsperson - draftsperson
eavesdropped - eavesdropper
frontiersman - frontiersmen
incommutable - incomputable
installation - instillation
kaleidescope - kaleidoscope
neuroanatomy - neuroanotomy
newspaperman - newspapermen
nonagenarian - nonogenarian
onomatopoeia - onomatopoeic
philanthrope - philanthropy
prescription - proscription
spectroscope - spectroscopy
complementary - complimentary
congresswoman - congresswomen
schizophrenia - schizophrenic
shakespearean - shakespearian
underclassman - underclassmen
upperclassman - upperclassmen
claustrophobia - claustrophobic
committeewoman - committeewomen
handicraftsman - handicraftsmen

Sidef

Translation of: Perl
var file = File("unixdict.txt")

if (!file.exists) {
    require('LWP::Simple')
    say ":: Retrieving #{file} from internet..."
    %S<LWP::Simple>.mirror(
        'https://web.archive.org/web/20180611003215if_/' +
        'http://www.puzzlers.org:80/pub/wordlists/unixdict.txt',
    'unixdict.txt')
}

var words  = file.read.words
var bucket = Hash()
var count = 0

words.each {|word|

    var len = word.len

    len > 11 || next
    bucket{len} := []

    bucket{len}.each{|prev|
        if (prev ^ word -> count("\0") == len-1) {
            printf("%2d: %20s <-> %s\n", ++count, prev, word)
        }
    }
    bucket{len} << word
}
Output:
 1:         aristotelean <-> aristotelian
 2:       claustrophobia <-> claustrophobic
 3:         committeeman <-> committeemen
 4:       committeewoman <-> committeewomen
 5:        complementary <-> complimentary
 6:         confirmation <-> conformation
 7:        congresswoman <-> congresswomen
 8:         councilwoman <-> councilwomen
 9:         craftsperson <-> draftsperson
10:         eavesdropped <-> eavesdropper
11:         frontiersman <-> frontiersmen
12:       handicraftsman <-> handicraftsmen
13:         incommutable <-> incomputable
14:         installation <-> instillation
15:         kaleidescope <-> kaleidoscope
16:         neuroanatomy <-> neuroanotomy
17:         newspaperman <-> newspapermen
18:         nonagenarian <-> nonogenarian
19:         onomatopoeia <-> onomatopoeic
20:         philanthrope <-> philanthropy
21:         prescription <-> proscription
22:        schizophrenia <-> schizophrenic
23:        shakespearean <-> shakespearian
24:         spectroscope <-> spectroscopy
25:        underclassman <-> underclassmen
26:        upperclassman <-> upperclassmen

Wren

Library: Wren-fmt

Using the Hamming Distance between two equal length strings which needs to be 1 here:

import "io" for File
import "./fmt" for Fmt

var hammingDist = Fn.new { |s1, s2|
    s1 = s1.toList // in case there are non-ASCII characters
    s2 = s2.toList // ditto
    var count = 0
    var i = 0
    while (i < s1.count) {
        if (s1[i] != s2[i]) {
            count = count + 1
            if (count == 2) break // don't care about counts > 2
        }
        i = i + 1
    }
    return count
}

var wordList = "unixdict.txt" // local copy
var words = File.read(wordList).trimEnd().split("\n").where { |w| w.count > 11 }.toList
var count = 0
System.print("Changeable words in %(wordList):")
for (word1 in words) {
    for (word2 in words) {
        if (word1 != word2 && word1.count == word2.count) {
            if (hammingDist.call(word1, word2) == 1) {
                count = count + 1
                Fmt.print("$2d: $-14s -> $s", count, word1, word2)
            }
        }
    }
}
Output:
Changeable words in unixdict.txt:
 1: aristotelean   -> aristotelian
 2: aristotelian   -> aristotelean
 3: claustrophobia -> claustrophobic
 4: claustrophobic -> claustrophobia
 5: committeeman   -> committeemen
 6: committeemen   -> committeeman
 7: committeewoman -> committeewomen
 8: committeewomen -> committeewoman
 9: complementary  -> complimentary
10: complimentary  -> complementary
11: confirmation   -> conformation
12: conformation   -> confirmation
13: congresswoman  -> congresswomen
14: congresswomen  -> congresswoman
15: councilwoman   -> councilwomen
16: councilwomen   -> councilwoman
17: craftsperson   -> draftsperson
18: draftsperson   -> craftsperson
19: eavesdropped   -> eavesdropper
20: eavesdropper   -> eavesdropped
21: frontiersman   -> frontiersmen
22: frontiersmen   -> frontiersman
23: handicraftsman -> handicraftsmen
24: handicraftsmen -> handicraftsman
25: incommutable   -> incomputable
26: incomputable   -> incommutable
27: installation   -> instillation
28: instillation   -> installation
29: kaleidescope   -> kaleidoscope
30: kaleidoscope   -> kaleidescope
31: neuroanatomy   -> neuroanotomy
32: neuroanotomy   -> neuroanatomy
33: newspaperman   -> newspapermen
34: newspapermen   -> newspaperman
35: nonagenarian   -> nonogenarian
36: nonogenarian   -> nonagenarian
37: onomatopoeia   -> onomatopoeic
38: onomatopoeic   -> onomatopoeia
39: philanthrope   -> philanthropy
40: philanthropy   -> philanthrope
41: prescription   -> proscription
42: proscription   -> prescription
43: schizophrenia  -> schizophrenic
44: schizophrenic  -> schizophrenia
45: shakespearean  -> shakespearian
46: shakespearian  -> shakespearean
47: spectroscope   -> spectroscopy
48: spectroscopy   -> spectroscope
49: underclassman  -> underclassmen
50: underclassmen  -> underclassman
51: upperclassman  -> upperclassmen
52: upperclassmen  -> upperclassman

XPL0

string  0;              \use zero-terminated strings
int     Dict(26000);    \pointers to words (enough for unixdict.txt)
int     DictSize;       \actual number of pointers in Dict

func    StrCmp(A, B);   \Compare string A to B
char    A, B;           \Returns: >0 if A>B, =0 if A=B, and <0 if A<B
int     I;
[for I:= 0 to -1>>1 do
        [if A(I) # B(I) then return A(I) - B(I);
         if A(I) = 0 then return 0;
        ];
];      \StrCmp

func    LookUp(Word);   \Return 'true' if Word is in Dict
char    Word;
int     Lo, Hi, I, Cmp;
[Lo:= 0;  Hi:= DictSize-1;
loop    [I:= (Lo+Hi) / 2; \binary search
        Cmp:= StrCmp(Word, Dict(I));
        if Cmp < 0 then Hi:= I-1 else Lo:= I+1;
        if Cmp = 0 then return true;
        if Lo > Hi then return false;
        ];
];      \LookUp

int     I, J, L, DI, Ch, Count;
char    Word, ChgWord(25);      \longest word in unixdict.txt is 22 chars
def     Tab=$09, LF=$0A, CR=$0D, EOF=$1A;

[FSet(FOpen("unixdict.txt", 0), ^I); \load dictionary
OpenI(3);                        \assume alphabetical order and all lowercase
DI:= 0;                          \ignore non-alpha characters: 0..9, ' and &
repeat  Dict(DI):= Reserve(0);   \get pointer to memory used to store Word
        Word:= Dict(DI);
        I:= 0;
        loop    [repeat Ch:= ChIn(3) until Ch # CR;     \remove possible CR
                if Ch=LF or Ch=EOF then quit;
                Word(I):= Ch;
                I:= I+1;
                ];
        Word(I):= 0;            \terminate Word string
        I:= Reserve(I+1);       \reserve memory used for Word
        DI:= DI+1;              \next dictionary entry
until   Ch = EOF;
DictSize:= DI;

DI:= 0;  Count:= 0;             \print out all changeable words
repeat  Word:= Dict(DI);        \assume all lowercase letters
        I:= 0;  J:= 0;
        loop    [Ch:= Word(I);
                if Ch = 0 then quit;
                ChgWord(I):= Ch;
                I:= I+1;
                ];
        ChgWord(I):= 0;
        if I >= 12 then         \Word must have at least 12 chars
                [for J:= 0 to I-1 do    \for all letter positions in Word
                        [for L:= ^a to ^z do  \for all letters
                            if L # Word(J) then
                                [ChgWord(J):= L;
                                if LookUp(ChgWord) then
                                        [Count:= Count+1;
                                        IntOut(0, Count);  ChOut(0, Tab);
                                        Text(0, Word);  ChOut(0, Tab);
                                        Text(0, ChgWord);
                                        CrLf(0);
                                        ];
                                ChgWord(J):= Word(J);   \undo letter change
                                ];
                        ];
                ];
        DI:= DI+1;
until   DI >= DictSize;
]
Output:
1       aristotelean    aristotelian
2       aristotelian    aristotelean
3       claustrophobia  claustrophobic
4       claustrophobic  claustrophobia
5       committeeman    committeemen
6       committeemen    committeeman
7       committeewoman  committeewomen
8       committeewomen  committeewoman
9       complementary   complimentary
10      complimentary   complementary
11      confirmation    conformation
12      conformation    confirmation
13      congresswoman   congresswomen
14      congresswomen   congresswoman
15      councilwoman    councilwomen
16      councilwomen    councilwoman
17      craftsperson    draftsperson
18      draftsperson    craftsperson
19      eavesdropped    eavesdropper
20      eavesdropper    eavesdropped
21      frontiersman    frontiersmen
22      frontiersmen    frontiersman
23      handicraftsman  handicraftsmen
24      handicraftsmen  handicraftsman
25      incommutable    incomputable
26      incomputable    incommutable
27      installation    instillation
28      instillation    installation
29      kaleidescope    kaleidoscope
30      kaleidoscope    kaleidescope
31      neuroanatomy    neuroanotomy
32      neuroanotomy    neuroanatomy
33      newspaperman    newspapermen
34      newspapermen    newspaperman
35      nonagenarian    nonogenarian
36      nonogenarian    nonagenarian
37      onomatopoeia    onomatopoeic
38      onomatopoeic    onomatopoeia
39      philanthrope    philanthropy
40      philanthropy    philanthrope
41      prescription    proscription
42      proscription    prescription
43      schizophrenia   schizophrenic
44      schizophrenic   schizophrenia
45      shakespearean   shakespearian
46      shakespearian   shakespearean
47      spectroscope    spectroscopy
48      spectroscopy    spectroscope
49      underclassman   underclassmen
50      underclassmen   underclassman
51      upperclassman   upperclassmen
52      upperclassmen   upperclassman