An anadrome is similar to a palindrome except, rather than spelling the same word or phrase when reversed, it spells a different word or phrase. An anadrome is a special case of an anagram.

Task
Anadromes
You are encouraged to solve this task according to the task description, using any language you may know.

Anadrome is a portmanteau of the words anagram and palindrome.

For instance, regal and lager are anadromes.


Task

Using the words.txt file from https://github.com/dwyl/english-words, find and display all of the anadrome pairs with more than 6 characters.

Each word pair should only show up one time in the list.

Alternate download location.


-- Find all anadromes of length > 6
-- J. Carter     2023 Mar

with Ada.Containers.Indefinite_Ordered_Sets;
with Ada.Text_IO;

procedure Anadromes is
   package Word_Sets is new Ada.Containers.Indefinite_Ordered_Sets (Element_Type => String);
   
   function Reversed (S : in String) return String is
      (if S = "" then S else S (S'Last) & Reversed (S (S'First .. S'Last - 1) ) );
      
   File : Ada.Text_IO.File_Type;
   W    : Word_Sets.Set;
begin -- Anadromes
   Ada.Text_IO.Open (File => File, Mode => Ada.Text_IO.In_File, Name => "words.txt");
   
   Fill : loop
      exit Fill when Ada.Text_IO.End_Of_File (File);
      
      W.Insert (New_Item => Ada.Text_IO.Get_Line (File) );
   end loop Fill;
   
   Ada.Text_IO.Close (File => File);
   
   Search : for Word of W loop
      if Word'Length > 6 then
         Backwards : declare
            Rev : constant String := Reversed (Word);
         begin -- Backwards
            if Word < Rev and W.Contains (Rev) then
               Ada.Text_IO.Put_Line (Item => Word & (1 .. 10 - Word'Length => ' ') & Rev);
            end if;
         end Backwards;
      end if;
   end loop Search;
end Anadromes;
Output:
amaroid   diorama
degener   reneged
deifier   reified
deliver   reviled
dessert   tressed
desserts  stressed
deviler   relived
dioramas  samaroid
gateman   nametag
leveler   relevel
pat-pat   tap-tap
redrawer  rewarder
reknits   stinker
relever   reveler
reliver   reviler
revotes   setover
sallets   stellas
Translation of: Pluto
scope # find some anadromes - words that when reversed are another word
      # the anadromes must be at least 7 characters long

    local constant words := [];
    for word in io.lines( "words.txt" ) do
        if size word > 6 then
            words[ word ] := true
        fi
    od;

    for w, _ in pairs( words ) do
        local constant r := w@@reverse();
        if r > w then
            if words[ r ] then
                printf( "%12s %s\n", w, r )
            fi
        fi
    od

end
Output:
     sallets stellas
     revotes setover
     reliver reviler
     dessert tressed
     amaroid diorama
    dioramas samaroid
     deliver reviled
     leveler relevel
    redrawer rewarder
    desserts stressed
     deifier reified
     gateman nametag
     relever reveler
     pat-pat tap-tap
     reknits stinker
     deviler relived
     degener reneged

Reads the words from standard input.
Unfortunately, Algol 68G doesn't like an array of STRINGs with more than 300 000 elements, even though it allows INT arrays to have millions - at least under Windows
(I haven't tried it with Linux).
So you will need to use another compiler under Windows.
As in the Wren sample, the words are quicksorted so binary searching can be used to find the reversed words.
This sample reads the words from standard input. The output shown for standard input redirected to words.txt.

BEGIN # find some anadromes: words that when reversed are also words         #
    # 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 TRUE if item is in list, FALSE otherwise                       #
    # - based on the iterative routine in the binary search task             #
    PROC contains = ( []STRING list, STRING item, INT lb, ub )BOOL:
         BEGIN
            INT  low  := lb,
            INT  high := ub;
            WHILE low < high DO
                INT mid =  ( low + high ) OVER 2;
                IF   list[ mid ] > item THEN high := mid - 1
                ELIF list[ mid ] < item THEN low  := mid + 1
                ELSE low := high := mid
                FI
            OD;
            list[ low ] = item
         END # contains # ;

    # set the end of file handler for stand in                               #
    on logical file end( stand in, ( REF FILE f )BOOL: at eof := TRUE );

    [ 1 : 500 000 ]STRING words;
    INT   t count    := 0;
    INT   w count    := 0;
    INT   max length := 0;
    BOOL  at eof     := FALSE;
    WHILE
        STRING word;
        read( ( word, newline ) );
        NOT at eof
    DO
        t count +:= 1;
        INT w length := 1 + ( UPB word - LWB word );
        IF w length > 6 THEN
            w count +:= 1;
            words[ w count ] := word;
            IF w length > max length THEN max length := w length FI
        FI
    OD;
    print( ( "read ", whole( t count, 0 ), " words, "
           , "the longest is ", whole( max length, 0 ), " characters"
           , newline
           , "     ", whole( w count, 0 ), " words are longer than 6 characters"
           , newline, newline
           )
         );
    s quicksort( words, 1, w count ); # sort the words for binary search      #
    print( ( "The following anadromes are present:", newline, newline ) );
    INT a count := 0;
    FOR i TO w count DO
        STRING word = words[ i ];
        STRING reverse word := "";
        FOR w pos FROM LWB word TO UPB word DO word[ w pos ] +=: reverse word OD;
        IF word < reverse word THEN
            IF contains( words, reverse word, 1, w count ) THEN
                # have an anadromic pair                                      #
                INT w length = 1 + ( UPB words[ i ] - LWB words[ i ] );
                TO 10 - w length DO print( ( " " ) ) OD;
                print( ( words[ i ], " :: ", reverse word, newline ) );
                a count +:= 1
            FI
        FI
    OD;
    print( ( newline, "Found ", whole( a count, 0 ), " anadromes", newline ) )
END
Output:
read 466551 words, the longest is 45 characters
     387537 words are longer than 6 characters

The following anadromes are present:

   amaroid :: diorama
   degener :: reneged
   deifier :: reified
   deliver :: reviled
   dessert :: tressed
  desserts :: stressed
   deviler :: relived
  dioramas :: samaroid
   gateman :: nametag
   leveler :: relevel
   pat-pat :: tap-tap
  redrawer :: rewarder
   reknits :: stinker
   relever :: reveler
   reliver :: reviler
   revotes :: setover
   sallets :: stellas

Found 17 anadromes
use AppleScript version "2.4" -- OS X 10.10 (Yosemite) or later
use framework "Foundation"
use scripting additions

on Anadromes(textFile, minLength)
    set |⌘| to current application
    -- Read the text from the file.
    set theText to |⌘|'s class "NSString"'s stringWithContentsOfFile:(textFile's POSIX path) ¬
        usedEncoding:(missing value) |error|:(missing value)
    -- Lose paragraphs (one word per paragraph) which have fewer than minLength characters.
    set theText to theText's stringByReplacingOccurrencesOfString:("(?m)^.{0," & minLength - 1 & "}\\R") withString:("") ¬
        options:(|⌘|'s NSRegularExpressionSearch) range:({0, theText's |length|()})
    -- Get the remaining paragraphs as an array.
    set wordArray to (theText's componentsSeparatedByCharactersInSet:(|⌘|'s class "NSCharacterSet"'s controlCharacterSet()))
    
    -- Derive a list of reversed words.
    script o
        property wordList : (wordArray) as list
    end script
    set astid to AppleScript's text item delimiters
    set AppleScript's text item delimiters to ""
    repeat with i from 1 to (count o's wordList)
        set o's wordList's item i to (o's wordList's item i's characters's reverse) as text
    end repeat
    
    -- Get a list of words that are in both the original and reversed groups.
    set reversedWordSet to |⌘|'s class "NSSet"'s setWithArray:(o's wordList)
    set filter to |⌘|'s class "NSPredicate"'s predicateWithFormat_("self IN %@", reversedWordSet)
    set o's wordList to (wordArray's filteredArrayUsingPredicate:(filter)) as list
    
    -- Build the output line by line, omitting palindromes and already matched word pairs.
    set output to {}
    repeat with i from 1 to (count o's wordList)
        set thisWord to o's wordList's item i
        set o's wordList's item i to missing value
        set reversedWord to (thisWord's characters's reverse) as text
        if ({reversedWord} is in o's wordList) then set output's end to thisWord & " <--> " & reversedWord
    end repeat
    set AppleScript's text item delimiters to linefeed
    set output to output as text
    set AppleScript's text item delimiters to astid
    
    return output
end Anadromes

return Anadromes(((path to desktop as text) & "www.rosettacode.org:words.txt") as «class furl», 7)
Output:
"amaroid <--> diorama
degener <--> reneged
deifier <--> reified
deliver <--> reviled
dessert <--> tressed
desserts <--> stressed
deviler <--> relived
dioramas <--> samaroid
gateman <--> nametag
leveler <--> relevel
pat-pat <--> tap-tap
redrawer <--> rewarder
reknits <--> stinker
relever <--> reveler
reliver <--> reviler
revotes <--> setover
sallets <--> stellas"
words: select read.lines ./"words.txt" => [6 < size &]
dict: #.raw flatten couple words array.of: size words null

loop words 'word [
    reversed: reverse word
    if and? word < reversed, key? dict reversed ->
        print [word reversed]
]
Output:
amaroid diorama 
degener reneged 
deifier reified 
deliver reviled 
dessert tressed 
desserts stressed 
deviler relived 
dioramas samaroid 
gateman nametag 
leveler relevel 
pat-pat tap-tap 
redrawer rewarder 
reknits stinker 
relever reveler 
reliver reviler 
revotes setover 
sallets stellas
# syntax: GAWK -f ANADROMES.AWK WORDS.TXT
#
# sorting:
#   PROCINFO["sorted_in"] is used by GAWK
#   SORTTYPE is used by Thompson Automation's TAWK
#
BEGIN {
    width = 6
}
{   if (length($0) > width) {
      arr[$0]++
    }
}
END {
    PROCINFO["sorted_in"] = "@ind_str_asc" ; SORTTYPE = 1
    for (i in arr) {
      tmp = reverse(i)
      if (tmp in arr) {
        if (i == tmp) { continue }
        if (tmp in shown_arr) { continue }
        printf("%11s %11s\n",i,tmp)
        shown_arr[i] = ""
      }
    }
    printf("%d words, %d > %d characters, %d anadromes\n",NR,length(arr),width,length(shown_arr))
    exit(0)
}
function reverse(str,  i,rts) {
    for (i=length(str); i>=1; i--) {
      rts = rts substr(str,i,1)
    }
    return(rts)
}
Output:
    amaroid     diorama
    degener     reneged
    deifier     reified
    deliver     reviled
    dessert     tressed
   desserts    stressed
    deviler     relived
   dioramas    samaroid
    gateman     nametag
    leveler     relevel
    pat-pat     tap-tap
   redrawer    rewarder
    reknits     stinker
    relever     reveler
    reliver     reviler
    revotes     setover
    sallets     stellas
466551 words, 387537 > 6 characters, 17 anadromes

C

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAXLINES    1000000
#define MAXLEN      1000

char buffer[MAXLEN];
char* words[MAXLINES];
char* wordsReversed[MAXLINES];

void reverseString(char* s) {
    int c, i, j;
    for (i = 0, j = strlen(s) - 1; i < j; i++, j--) {
        c = s[i];
        s[i] = s[j];
        s[j] = c;
    }
}

int pstrcmp(const void* a, const void* b) {
    return strcmp(*(const char**)a, *(const char**)b);
}

int main() {
    int i, j;
    int wordCount;
    
    FILE* file = fopen("words.txt", "r");

    wordCount = 0;
    while (fgets(buffer, MAXLEN, file)) {
        buffer[strcspn(buffer, "\n")] = 0;
        if (strlen(buffer) > 6) {
            words[wordCount] = malloc(strlen(buffer) + 1);
            strcpy(words[wordCount], buffer);
            wordCount++;
        }
    }

    fclose(file);

    for (i = 0; i < wordCount; i++) {
        wordsReversed[i] = malloc(strlen(words[i]) + 1);
        strcpy(wordsReversed[i], words[i]);
        reverseString(wordsReversed[i]);
    }

    qsort(wordsReversed, wordCount, sizeof(char*), pstrcmp);

    for (i = 0; i < wordCount; i++) {
        strcpy(buffer, words[i]);
        reverseString(buffer);
        if (strcmp(words[i], buffer) < 0 &&
            bsearch(&words[i], wordsReversed, wordCount, sizeof(char*), pstrcmp))
            printf("%-12s%-12s\n", words[i], buffer);
    }

    for (i = 0; i < wordCount; i++) {
        free(words[i]);
        free(wordsReversed[i]);
    }

    return 0;
}
Output:
amaroid     diorama     
degener     reneged     
deifier     reified     
deliver     reviled     
dessert     tressed     
desserts    stressed    
deviler     relived     
dioramas    samaroid    
gateman     nametag     
leveler     relevel     
pat-pat     tap-tap     
redrawer    rewarder    
reknits     stinker     
relever     reveler     
reliver     reviler     
revotes     setover     
sallets     stellas
using System;
using System.Linq;

class Programm
{
    static void Main()
    {
        var words = File.ReadAllLines("words.txt").Where(s => s.Length > 6).ToHashSet();
        
        foreach (string w in words) {
            var w_rev = new string(w.Reverse().ToArray());
            if (words.Contains(w_rev) && w.CompareTo(w_rev) < 0)
                Console.WriteLine("{0, -12}{1, -12}", w, w_rev);
        }
    }
}
Output:
amaroid     diorama     
degener     reneged     
deifier     reified     
deliver     reviled     
dessert     tressed     
desserts    stressed    
deviler     relived     
dioramas    samaroid    
gateman     nametag     
leveler     relevel     
pat-pat     tap-tap     
redrawer    rewarder    
reknits     stinker     
relever     reveler     
reliver     reviler     
revotes     setover     
sallets     stellas
#include <algorithm>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

int main() {
    std::vector<std::string> words;
    std::ifstream in("words.txt");
    if (!in) {
        std::cerr << "Cannot open file words.txt.\n";
        return EXIT_FAILURE;
    }
    std::string line;
    while (getline(in, line)) {
        if (line.size() > 6)
            words.push_back(line);
    }
    sort(words.begin(), words.end());
    for (const std::string& word : words) {
        std::string reversed(word.rbegin(), word.rend());
        if (reversed > word &&
            binary_search(words.begin(), words.end(), reversed)) {
            std::cout << std::setw(8) << word << " <-> " << reversed << '\n';
        }
    }
    return EXIT_SUCCESS;
}
Output:
 amaroid <-> diorama
 degener <-> reneged
 deifier <-> reified
 deliver <-> reviled
 dessert <-> tressed
desserts <-> stressed
 deviler <-> relived
dioramas <-> samaroid
 gateman <-> nametag
 leveler <-> relevel
 pat-pat <-> tap-tap
redrawer <-> rewarder
 reknits <-> stinker
 relever <-> reveler
 reliver <-> reviler
 revotes <-> setover
 sallets <-> stellas
Works with: GnuCOBOL version 3.2.0
      ***************************************************************
      * PROGRAM: ANADROMES
      * AUTHOR: PETER HAGELUND
      *************************************************************** 
       IDENTIFICATION DIVISION.
       PROGRAM-ID. ANADROMES.

       ENVIRONMENT DIVISION.
       CONFIGURATION SECTION.
       REPOSITORY.
           FUNCTION ALL INTRINSIC.
       
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT WORD-FILE ASSIGN TO FILENAME
           ORGANIZATION IS LINE SEQUENTIAL
           ACCESS MODE IS SEQUENTIAL
           FILE STATUS IS FILE-STATUS.

       DATA DIVISION.
       FILE SECTION.
       FD  WORD-FILE
           RECORD CONTAINS 1 TO 80 CHARACTERS
           DATA RECORD IS WORD-REC.
       01  WORD-REC                    PIC X(80).

       WORKING-STORAGE SECTION.
       01  FILE-STATUS                 PIC X(2).
       01  WORD-CNT                    PIC 9(9).
       01  MEM-SIZE                    PIC 9(9).
       01  MEM-PTR                     USAGE POINTER VALUE NULL.
       01  IDX                         USAGE INDEX.
       
       LINKAGE SECTION.
       01  WORD-TABLE.
           03  WORD-ENTRY OCCURS UNBOUNDED DEPENDING ON WORD-CNT
               ASCENDING KEY IS WORD
               INDEXED BY WORD-IDX.
               05  WORD                PIC X(30).
               05  REV-WORD            PIC X(30).
               05  WORD-STATUS         PIC X.
                   88  UNKNOWN         VALUE ' '.
                   88  NOT-ANADROME    VALUE 'N'.
                   88  IS-ANADROME     VALUE 'Y'.
                   88  IS-OTHER        VALUE 'O'.
       
       PROCEDURE DIVISION.
       MAIN SECTION.
      * FIRST PASS: COUNT THE WORDS
           PERFORM READ-WORDS.
      * CALCULATE MEMORY NEEDED AND ALLOCATE IT
           COMPUTE MEM-SIZE = WORD-CNT * LENGTH OF WORD-ENTRY.
           ALLOCATE MEM-SIZE CHARACTERS RETURNING MEM-PTR.
           SET ADDRESS OF WORD-TABLE TO MEM-PTR.
      * SECOND PASS: READ THE WORDS INTO THE ALLOCATED TABLE
           PERFORM READ-WORDS.
      * SORT THE TABLE
           SORT WORD-ENTRY ON ASCENDING KEY WORD.
      * FIND THE ANADROMES
           PERFORM VARYING IDX FROM 1 BY 1 UNTIL IDX > WORD-CNT
               IF NOT UNKNOWN(IDX) THEN
                   EXIT PERFORM CYCLE
               END-IF
               IF WORD(IDX) = REV-WORD(IDX) THEN
                   SET NOT-ANADROME(IDX) TO TRUE
                   EXIT PERFORM CYCLE
               END-IF
               SEARCH ALL WORD-ENTRY
                   AT END
                       SET NOT-ANADROME(IDX) TO TRUE
                   WHEN WORD(WORD-IDX) = REV-WORD(IDX)
                       SET IS-ANADROME(IDX) TO TRUE
                       SET IS-OTHER(WORD-IDX) TO TRUE
                       DISPLAY
                           TRIM(WORD(IDX)),
                           ' ',
                           TRIM(REV-WORD(IDX))
                       END-DISPLAY
               END-SEARCH
           END-PERFORM.
      * FREE THE MEMORY FOR THE TABLE
           FREE MEM-PTR.
           GOBACK.
       
       READ-WORDS SECTION.
           MOVE 0 TO WORD-CNT.
           OPEN INPUT WORD-FILE.
           IF FILE-STATUS NOT = '00' THEN
               DISPLAY 'OPEN FAILED: ', FILE-STATUS
               EXIT SECTION
           END-IF.
           PERFORM UNTIL FILE-STATUS NOT = '00'
               READ WORD-FILE
               IF FILE-STATUS NOT = '00' THEN
                   EXIT PERFORM 
               END-IF
               IF LENGTH(TRIM(WORD-REC)) > 6 THEN
                   ADD 1 TO WORD-CNT
                   IF MEM-PTR = NULL THEN
                       EXIT PERFORM CYCLE
                   END-IF
                   MOVE WORD-REC TO WORD(WORD-CNT)
                   MOVE REVERSE(TRIM(WORD-REC)) TO REV-WORD(WORD-CNT)
                   SET UNKNOWN(WORD-CNT) TO TRUE
               END-IF
           END-PERFORM.
           CLOSE WORD-FILE.
Output:
amaroid diorama
degener reneged
deifier reified
deliver reviled
dessert tressed
desserts stressed
deviler relived
dioramas samaroid
gateman nametag
leveler relevel
pat-pat tap-tap
redrawer rewarder
reknits stinker
relever reveler
reliver reviler
revotes setover
sallets stellas


The hash-table's equalp test is case-insensitive, and so we find 32 pairs rather than 17. However, the table maps each word to itself, and so looking up a reversed word gets it in its original case, as can be seen in the output.

(defun read-words (filename)
  (let ((words '()))
    (with-open-file (s filename :direction :input)
      (loop
        (let ((word (read-line s nil nil)))
          (if word
              (when (> (length word) 6)
                (setq words (cons word words)))
            (return (reverse words))))))))

(defun anadromes ()
  (let ((words (read-words "notes/words.txt"))
        (dict (make-hash-table :test #'equalp)))
    (dolist (word words)
      (setf (gethash word dict) word))
    (mapcar
      (lambda (word)
        (list word (gethash (reverse word) dict)))
      (remove-if-not
        (lambda (word)
          (let ((rev (reverse word)))
            (and (string-lessp word rev)
                 (gethash rev dict))))
        words))))

(format t "~%~:{~10A ~10A~%~}~%"
        (anadromes))
Output:
amaroid    diorama   
anacara    Aracana   
Annabal    Labanna   
Artamus    Sumatra   
Colbert    Trebloc   
degener    reneged   
deifier    reified   
Delbert    trebled   
Delevan    naveled   
deliver    reviled   
dessert    tressed   
desserts   stressed  
deviler    relived   
dioramas   samaroid  
Eimmart    trammie   
Emmeram    maremme   
gateman    nametag   
Latimer    remital   
Lattimer   remittal  
lessees    Seessel   
leveler    relevel   
Nicolaus   Sualocin  
pat-pat    tap-tap   
redrawer   rewarder  
reknits    stinker   
relever    reveler   
reliver    reviler   
revotes    setover   
ROYGBIV    vibgyor   
Rotanev    Venator   
sallets    stellas   
sennits    Stinnes  
words = File.read_lines("words.txt").select {|w| w.size > 6 }.to_set

words.compact_map {|word|
  reversed = word.reverse
  if word < reversed && words.includes?(reversed)
    word
  end
}.sort!.each do |anadrome|
  printf "%-10s %s\n", anadrome, anadrome.reverse
end
Output:
amaroid    diorama
degener    reneged
deifier    reified
deliver    reviled
dessert    tressed
desserts   stressed
deviler    relived
dioramas   samaroid
gateman    nametag
leveler    relevel
pat-pat    tap-tap
redrawer   rewarder
reknits    stinker
relever    reveler
reliver    reviler
revotes    setover
sallets    stellas

D

import std.stdio, std.file, std.string, std.range, std.algorithm;

void main() {
    auto words = readText("words.txt").splitLines.filter!"a.length > 6".array.sort;

    foreach (w; words)
        if (w < w.dup.reverse && words.contains(w.dup.reverse))
            writefln("%-12s %-12s", w, w.dup.reverse);
}
Output:
amaroid      diorama     
degener      reneged     
deifier      reified     
deliver      reviled     
dessert      tressed     
desserts     stressed    
deviler      relived     
dioramas     samaroid    
gateman      nametag     
leveler      relevel     
pat-pat      tap-tap     
redrawer     rewarder    
reknits      stinker     
relever      reveler     
reliver      reviler     
revotes      setover     
sallets      stellas
Works with: DuckDB version V1.0

It turns out that for the task at hand, the time created to create an index is far greater than the savings that accrue from having it, so the 'CREATE INDEX' line has been commented out.

create or replace function isPalindrome(s) as (
  reverse(s) = s
);

create or replace table words as
    from read_csv_auto(
      'words.txt',
      header=false, sep='', quote='', columns={'word': VARCHAR})
    where NOT isPalindrome(word);

-- CREATE INDEX word_index ON words(word);

create or replace function anadromes(mn) as table (
  with candidates as (
    select word, reverse(word) as anadrome
    from words
    where word < anadrome and anadrome IN (from words)
  )
  from candidates
  where length(word) >= mn
  order by word
);

from anadromes(7);
Output:
┌──────────┬──────────┐
│   word   │ anadrome │
│ varchar  │ varchar  │
├──────────┼──────────┤
│ amaroid  │ diorama  │
│ degener  │ reneged  │
│ deifier  │ reified  │
│ deliver  │ reviled  │
│ dessert  │ tressed  │
│ desserts │ stressed │
│ deviler  │ relived  │
│ dioramas │ samaroid │
│ gateman  │ nametag  │
│ leveler  │ relevel  │
│ pat-pat  │ tap-tap  │
│ redrawer │ rewarder │
│ reknits  │ stinker  │
│ relever  │ reveler  │
│ reliver  │ reviler  │
│ revotes  │ setover  │
│ sallets  │ stellas  │
├──────────┴──────────┤
│ 17 rows   2 columns │
└─────────────────────┘
// Anadromes. Nigel Galloway: June 26th., 2022
let words=seq{use n=System.IO.File.OpenText("words.txt") in while not n.EndOfStream do yield n.ReadLine()}|>Seq.filter(fun n->6<(Seq.length n))|>Seq.map(fun n->n.ToCharArray())|>Set.ofSeq
Set.intersect words (words|>Set.map(Array.rev))|>Set.iter(fun n->if n<Array.rev n then printfn "%s" (System.String n))
Output:
amaroid
degener
deifier
deliver
dessert
desserts
deviler
dioramas
gateman
leveler
pat-pat
redrawer
reknits
relever
reliver
revotes
sallets
Works with: Factor version 0.99 2022-04-03
USING: assocs grouping hash-sets io.encodings.ascii io.files
kernel math prettyprint sequences sets sets.extras ;

"words.txt" ascii file-lines [ length 6 > ] filter dup >hash-set '[ reverse _ in? ] filter
[ reverse ] zip-with [ all-equal? ] reject [ fast-set ] unique-by .
Output:
{
    { "amaroid" "diorama" }
    { "degener" "reneged" }
    { "deifier" "reified" }
    { "deliver" "reviled" }
    { "dessert" "tressed" }
    { "desserts" "stressed" }
    { "deviler" "relived" }
    { "dioramas" "samaroid" }
    { "gateman" "nametag" }
    { "leveler" "relevel" }
    { "pat-pat" "tap-tap" }
    { "redrawer" "rewarder" }
    { "reknits" "stinker" }
    { "relever" "reveler" }
    { "reliver" "reviler" }
    { "revotes" "setover" }
    { "sallets" "stellas" }
}
! 
! Anadromes
! tested with Intel ifx (IFX) 2025.2.1 20250806             on Kubuntu 25.04
!             GNU Fortran (Ubuntu 14.2.0-19ubuntu2) 14.2.0  on Kubuntu 25.04
!             VSI Fortran x86-64 V8.6-001                   on OpenVMS x86_64 V9.2-3
! No Non-standard features used, should compile on any fairly recent Fortran.
! U.B., September 2025

program Anadromes

  implicit none

  integer, parameter :: longestWord = 50                    ! is long enough for words.txt (45) 

  character(len=longestWord) :: word                        ! 1 word to read  
  character(len=longestWord), allocatable  :: Words(:)      ! the entire collection of all words
  integer :: capacity, content                              ! usage of above container

  integer :: l, io_stat, ii                               

  content = 0

  ! Begin with moderate words array size
  capacity = 1024
  call resize (Words, capacity)

  ! going to read all words from unixdict.txt
  open(unit=10, file='words.txt', status='old', action='read', iostat=io_stat)
  if (io_stat .ne. 0) then
     print *, "Error opening file"                  ! ERROR: would be useless to try reading words.
     stop
  end if

  do    ! read all words, until ERROR or EOF
    read (10,'(A)', iostat=io_stat)   word
    if (io_stat < 0) exit                          ! EOF: Normal end of this loop
    if (io_stat > 0) then
      print *, "Read error"                       ! ERROR (unexpected): would be useless to proceed
      exit
    end if

    ! Consider only words longer than 6 characters, skip shorter words.
    l = len_trim (word)                             ! use this instead of Q format
    if (l .gt. 6)  call addWord ()

  end do

  ! For correct and efficient binary search, the word list must be sorted
  call quicksort_strings (words, 1, content)

  do ii=1,content
    call checkWord (ii)     ! Prints a pair if an anadrome is found
  end do

  close(10)

  contains   



  ! ------------------------------------
  ! add a new word to the list of words. 
  ! ------------------------------------

  subroutine addWord()
  ! No local variables here. Just working on Main program's variables.

  content = content+1

  ! If we would exceed capacity of array 'Words', we need to increment capacity
  if (content .gt. capacity) then
    capacity = 2*capacity
    call resize (Words, capacity)
  end if

  Words(content) = word

! for case insensitivity, convert uppercase letters to lowercase
!  do ip=1,l
!    if (word(ip:ip) .ge. 'A' .and. word(ip:ip) .le. 'Z')    &
!      Words(content)(ip:ip) = char (ichar( Words(content)(ip:ip) ) - ichar('A') + ichar('a'))
!  enddo
  
  end subroutine addWord

  ! ----------------------------------------------------------------
  ! See if a word is also in word list when its letters are reversed
  ! ----------------------------------------------------------------
  subroutine checkWord (iw)
  
  integer, intent(in)  :: iw
  character (len=longestWord) :: revWord
  integer :: ip

  revWord = reversed (iw)
  ip = find (revword(:l) )

  if (ip .ne. 0 .and. words(iw) .lt. revword)  &
     print '(A9, " <--> ", A)' ,  &                  ! right-justified <-> left justified
       words(iw)(:len_trim(words(iw))) ,revWord(:len_trim(words(iw)))

  end subroutine checkWord

  ! -----------------------------------------------
  ! construct word in reversed order of its letters
  ! -----------------------------------------------
  function reversed (iw) result (r)

  integer, intent(in) :: iw
  character (len=longestWord) :: r

  integer:: l, ii

  l = len_trim (words(iw))
  r = ' '
  do ii = l, 1, -1
    r (1-ii+l:1-ii+l) = words(iw)(ii:ii)
  enddo
  end function reversed

  ! ----------------------------------------------
  ! Increase allocated size of string array 'var'
  ! ----------------------------------------------
  subroutine resize(var, newSize)
  character (len=longestWord), allocatable, intent(inout) :: var(:)   ! The array to be increased
  integer, intent(in)                             :: newSize          ! The new size of var
  character (len=longestWord), allocatable                :: tmp(:)   ! Temporary storage
  integer                                         :: oldSize          ! Current array size
  integer                                         :: ii               ! Loop index

  ! Copy allocated values to temporary, then allocate var with 
  ! new size and copy back saved values.
  ! Could be done with move_alloc but this would not be portable to OpenVMS Fortran
  if (allocated(var)) then
    oldSize = size(var)
  else
    oldSize = 0
  end if

  if (newSize .gt. oldSize) then           ! only increment 
    if (oldSize .gt.0) then
      allocate(tmp (oldSize))
      tmp(:oldSize) = var(:oldSize)    
      deallocate (var)
    end if
    allocate(var(newSize))
    if (allocated(tmp) .and. allocated (var) ) then  
      oldSize = min(size(tmp, 1), size(var, 1))
      var(:oldSize) = tmp(:oldSize)
      deallocate (tmp)
    end if
    do ii=oldSize+1, newSize
      var (ii)(1:) = ' '
    end do
  end if
end subroutine resize

!------------------------------------------------------------
! Find a word in a list of possible keywords. 
! Returns index of the word in that array, or 0 if not found.
! Binary search, array must be sorted.
!------------------------------------------------------------
function find (needle) result(index)
  character (len=longestWord) , intent(in)  :: needle
  integer    :: Index

  integer :: left, right, mid, wordlen
  integer :: comparison

  left = 1
  right = content
  index = 0               ! will signal 'not found'
  wordlen = len_trim(needle)         
  mid = left + (left-right)/2

  do while (left <= right)
    mid = left + (right - left) / 2
    if (Words (mid)  .eq. needle ) then
                index = mid
                return
    else if (Words (mid) .lt. needle) then
      ! index of needle is between mid+1 and right. 
      left = mid + 1
    else
      ! index of needle is between left and mid-1
      right = mid  - 1
    end if
  end do
end function find

recursive subroutine quicksort_strings(arr, low, high)
  
  character(len=*), dimension(:), intent(inout) :: arr
  integer, intent(in) :: low, high
  integer :: pivot_index
  integer :: i, j, mid
  character(len=len(arr(1))) :: pivot, temp
    
  if (low < high) then
    ! The word list is already "almost sorted", so use midlle word as pivot.
    mid = low + (high-low) / 2
    pivot = arr(mid)

    !Move pivot to the end
    temp = arr(mid)
    arr(mid) = arr(high)
    arr(high)=temp

    i = low - 1
        
    do j = low, high - 1
      if (arr(j) <= pivot) then
        i = i + 1
        temp = arr(i)
        arr(i) = arr(j)
        arr(j) = temp
      end if
    end do
        
    temp = arr(i+1)
    arr(i+1) = arr(high)
    arr(high) = temp
    pivot_index = i + 1
        
    call quicksort_strings(arr, low, pivot_index - 1)
    call quicksort_strings(arr, pivot_index + 1, high)
  end if
end subroutine quicksort_strings

end program Anadromes
Output:
  amaroid <--> diorama
  degener <--> reneged
  deifier <--> reified
  deliver <--> reviled
  dessert <--> tressed
 desserts <--> stressed
  deviler <--> relived
 dioramas <--> samaroid
  gateman <--> nametag
  leveler <--> relevel
  pat-pat <--> tap-tap
 redrawer <--> rewarder
  reknits <--> stinker
  relever <--> reveler
  reliver <--> reviler
  revotes <--> setover
  sallets <--> stellas

real    0m0.372s
user    0m0.345s
sys     0m0.027s

Now with binary search, super fast

ReDim Shared As String words(500000)
Dim Shared As Integer wordCnt

Sub readWords(filename As String)
    Dim As Integer ff = Freefile()
    Dim As String word

    If Open(filename For Input As #ff) <> 0 Then
        Print "Cannot open file " & filename & "."
        Exit Sub
    End If

    While Not Eof(ff)
        Line Input #ff, word
        If Len(word) > 6 Then
            words(wordCnt) = word
            wordCnt += 1
        End If
    Wend
    Close #ff
    wordCnt -= 1
    ReDim Preserve words(wordCnt)
End Sub

Sub quicksort(qs() As string, l As integer, r As Integer)

    Dim As integer size = r - l + 1
    If size < 2 Then Exit Sub

    Dim As Integer i = l, j = r
    Dim As String pivot = qs(l + size \ 2)

    Do
        While qs(i) < pivot
            i += 1
        Wend
        While pivot < qs(j)
            j -= 1
        Wend
        If i <= j Then
            Swap qs(i), qs(j)
            i += 1
            j -= 1
        End If
    Loop Until i > j

    If l < j Then quicksort(qs(), l, j)
    If i < r Then quicksort(qs(), i, r)

End Sub

Function reverseWord(s As String) As String
    Dim As String reversed = s
    Dim As Integer i, l = Len(reversed) - 1
    For i = 0 To l
        reversed[i] = s[l - i]
    Next
    Return reversed
End Function

' Main program
Dim As Integer i, c, hi, lo, md
Dim As String word, reversed
Dim As boolean fl

readWords("i:\words.txt")
' binary search needs a sorted list
quicksort(words(), LBound(words), UBound(words))

For i = 0 To wordCnt - 1
    word = words(i)
    reversed = reverseWord(word)
    ' binary search
    lo = i + 1 : hi = wordCnt : md = (lo + hi) \ 2 : fl = FALSE
    If words(lo) = reversed Then fl = TRUE
    If words(hi) = reversed Then fl = TRUE
    While (lo + 1 < hi) And fl = FALSE
        If words(md) = reversed Then fl = TRUE : Exit While
        If words(md) < reversed Then lo = md Else hi = md
        md = (lo + hi) \ 2
    Wend
    If fl = TRUE Then
        c += 1
        Print Using "\       \ <=> \       \"; word; reversed
    End If
Next i

Print
Print "Found"; c; " anadromes"

Sleep
Output:
amaroid  <=> diorama
degener  <=> reneged
deifier  <=> reified
deliver  <=> reviled
dessert  <=> tressed
desserts <=> stressed
deviler  <=> relived
dioramas <=> samaroid
gateman  <=> nametag
leveler  <=> relevel
pat-pat  <=> tap-tap
redrawer <=> rewarder
reknits  <=> stinker
relever  <=> reveler
reliver  <=> reviler
revotes  <=> setover
sallets  <=> stellas

Found 17 anadromes
local fn loadDict as CFArrayRef
  CFURLRef       url = fn URLWithString( @"https://raw.githubusercontent.com/thundergnat/rc-run/master/rc/resources/words.txt" )
  CFStringRef string = lcase( fn StringWithContentsOfURL( url, NSASCIIStringEncoding, NULL ) )
  CFArrayRef  array  = fn StringComponentsSeparatedByCharactersInSet( string, fn CharacterSetNewlineSet )
  array  = fn arraySortedarrayUsingSelector( array, @"caseInsensitiveCompare:" )
end fn = array

void local fn anadromes( array as CFArrayRef )
  uint32      r, i, top, bot, med, count = 0
  CFStringRef string
  
  print
  for r = 0 to len(array)-1
    if len( array[r] ) < 7 then continue
    string = @""
    for i =  0 to len(array[r]) - 1
      string = fn StringByAppendingString( mid( array[r],i,1 ), string )
    next
    if fn StringIsEqual( array[r], string ) then continue
    bot = r+1 : top = len( array )-1
    while ( top - bot ) > 1
      med = ( bot + top )>>1
      select fn StringCompare( string, array[med] )
        case NSOrderedAscending  : top = med
        case NSOrderedDescending : bot = med
        case else                : count++
          printf @"%4llu  %@ %10s", count, array[r], fn StringUTF8String( string )
          break
      end select
    wend
  next
end fn

window 1, @"Anadromes",(0,0,209,502)
CFArrayRef array : array = fn loadDict
CFTimeInterval t : t = fn CACurrentMediaTime
fn anadromes( array )
printf @"\t\t%.3f secs",fn CACurrentMediaTime - t

handleevents
Output:

 

Works with: go version 1.21
package main

import (
	"bufio"
	"cmp"
	"fmt"
	"log"
	"os"
	"slices"
	"strings"
	"unicode/utf8"
)

func main() {
	words := readUniqueWords("words.txt", 6)
	anadromes := getAnadromes(words)
	lefts := sortedKeys(anadromes)
	for _, left := range lefts {
		right := anadromes[left]
		fmt.Printf("%9s ↔ %s\n", left, right)
	}
}

func readUniqueWords(filename string, minLen int) map[string]struct{} {
	file, err := os.Open(filename)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()
	scanner := bufio.NewScanner(file)
	words := map[string]struct{}{}
	for scanner.Scan() {
		word := scanner.Text()
		if utf8.RuneCountInString(word) > minLen {
			word = strings.ToLower(word)
			words[word] = struct{}{}
		}
	}
	return words
}

func getAnadromes(words map[string]struct{}) map[string]string {
	seen := map[string]struct{}{}
	anadromes := map[string]string{}
	for _, word := range sortedKeys(words) {
		rword := reverse(word)
		if rword != word {
			if _, found := words[rword]; found {
				if _, found := seen[word]; !found {
					if _, found := seen[rword]; !found {
						anadromes[word] = rword
						seen[word] = struct{}{}
						seen[rword] = struct{}{}
					}
				}
			}
		}
	}
	return anadromes
}

func sortedKeys[K cmp.Ordered, V any](m map[K]V) []K {
	keys := make([]K, 0, len(m))
	for key := range m {
		keys = append(keys, key)
	}
	slices.Sort(keys)
	return keys
}

func reverse(text string) string {
	runes := []rune(text)
	slices.Reverse(runes)
	return string(runes)
}
Output:
  amaroid ↔ diorama
  anacara ↔ aracana
  annabal ↔ labanna
  artamus ↔ sumatra
  colbert ↔ trebloc
  degener ↔ reneged
  deifier ↔ reified
  delbert ↔ trebled
  delevan ↔ naveled
  deliver ↔ reviled
  dessert ↔ tressed
 desserts ↔ stressed
  deviler ↔ relived
 dioramas ↔ samaroid
  eimmart ↔ trammie
  emmeram ↔ maremme
  gateman ↔ nametag
  latimer ↔ remital
 lattimer ↔ remittal
  lessees ↔ seessel
  leveler ↔ relevel
 nicolaus ↔ sualocin
  pat-pat ↔ tap-tap
 redrawer ↔ rewarder
  reknits ↔ stinker
  relever ↔ reveler
  reliver ↔ reviler
  revotes ↔ setover
  rotanev ↔ venator
  roygbiv ↔ vibgyor
  sallets ↔ stellas
  sennits ↔ stinnes
import qualified Data.Set as S
import qualified Data.Text as T
import qualified Data.Text.IO as TIO

-- The anadromes of a list of words.  We convert all words to lower case.
anadromes :: [T.Text] -> [(T.Text, T.Text)]
anadromes ws = let set = S.fromList $ map T.toLower ws
               in S.foldr (step set) [] set
  where step set w ps = let rev = T.reverse w
                        in if w < rev && S.member rev set
                           then (w, rev) : ps
                           else ps

main :: IO ()
main = TIO.interact (T.unlines . map anaShow . anadromes . longEnough . T.lines)
  where longEnough = filter ((> 6) . T.length)
        anaShow (x, y) = T.unwords [x, y]
Output:
amaroid diorama
anacara aracana
annabal labanna
artamus sumatra
colbert trebloc
degener reneged
deifier reified
delbert trebled
delevan naveled
deliver reviled
dessert tressed
desserts stressed
deviler relived
dioramas samaroid
eimmart trammie
emmeram maremme
gateman nametag
latimer remital
lattimer remittal
lessees seessel
leveler relevel
nicolaus sualocin
pat-pat tap-tap
redrawer rewarder
reknits stinker
relever reveler
reliver reviler
revotes setover
rotanev venator
roygbiv vibgyor
sallets stellas
sennits stinnes

The following works in both languages:

procedure main()
   words := list()
   word_tab := table()
   f := open("words.txt") | stop("Cannot open words.txt")
   every put(words, (*(w := !f) > 6, w)) do word_tab[reverse(w)] := w
       
   every (w2 := \word_tab[w1 := !words],w1 ~== w2) do {
          write(right(w1,20)," -> ",w2)
          every delete(word_tab,w1|w2)
          }
end

Output:

             amaroid -> diorama
             degener -> reneged
             deifier -> reified
             deliver -> reviled
             dessert -> tressed
            desserts -> stressed
             deviler -> relived
            dioramas -> samaroid
             gateman -> nametag
             leveler -> relevel
             pat-pat -> tap-tap
            redrawer -> rewarder
             reknits -> stinker
             relever -> reveler
             reliver -> reviler
             revotes -> setover
             sallets -> stellas

J

Inspecting other entries here, it seems clear that we cannot ignore case. Otherwise, 'trebled' would be an anadrome.

Anyways, the basic approach here is to identify a canonical key for each word, look for paired keys and organize the words based on those keys:

words=: cutLF fread 'words.txt'
canon=: {.@/:~@(,:|.) each words
akeys=: (~. #~ 2 = #/.~) canon
tkeys=: (#~ 6 < #@>) akeys
order=: /: canon
pairs=: _2]\ (order{canon e. tkeys) # order { words

This gives us:

   pairs
┌────────┬────────┐
amaroid diorama 
├────────┼────────┤
degener reneged 
├────────┼────────┤
deifier reified 
├────────┼────────┤
deliver reviled 
├────────┼────────┤
dessert tressed 
├────────┼────────┤
dessertsstressed
├────────┼────────┤
deviler relived 
├────────┼────────┤
dioramassamaroid
├────────┼────────┤
gateman nametag 
├────────┼────────┤
leveler relevel 
├────────┼────────┤
pat-pat tap-tap 
├────────┼────────┤
redrawerrewarder
├────────┼────────┤
reknits stinker 
├────────┼────────┤
relever reveler 
├────────┼────────┤
reliver reviler 
├────────┼────────┤
revotes setover 
├────────┼────────┤
sallets stellas 
└────────┴────────┘
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;

public class Anadromes {

	public static void main(String[] args) throws IOException {
		List<String> words = Files.lines(Path.of("words.txt")).filter( word -> word.length() > 6 ).sorted().toList();
		
		System.out.println("The anadrome pairs with more than 6 letters are:");
		for ( String word : words ) {
		    String wordReversed = new StringBuilder(word).reverse().toString();
		    if ( wordReversed.compareTo(word) > 0 && Collections.binarySearch(words, wordReversed) > 0 ) {
		    	System.out.println(word + " <--> " + wordReversed);
		    }
		}
	}

}
Output:
The anadrome pairs with more than 6 letters are:
amaroid <--> diorama
degener <--> reneged
deifier <--> reified
deliver <--> reviled
dessert <--> tressed
desserts <--> stressed
deviler <--> relived
dioramas <--> samaroid
gateman <--> nametag
leveler <--> relevel
pat-pat <--> tap-tap
redrawer <--> rewarder
reknits <--> stinker
relever <--> reveler
reliver <--> reviler
revotes <--> setover
sallets <--> stellas
const fs = require('fs');

fs.readFile("words.txt", "utf8", (err, data) => {
    if (err) {
        console.error("Error reading the file:", err);
        return;
    }

    const words = data.split(/\r?\n/); // This regex splits on both Unix and Windows line endings
    const wordSet = new Set(words);
    const seenWords = new Set();
    const wordList = [];

    for (let word of wordSet) {
        if (word.length > 6) {
            const reversedWord = word.split('').reverse().join('');
            const wordPair = `${word}:${reversedWord}`;
            const reversedWordPair = `${reversedWord}:${word}`;

            if (wordSet.has(reversedWord) && reversedWord !== word && !seenWords.has(reversedWordPair)) {
                wordList.push([word, reversedWord]);
                seenWords.add(wordPair); // Store seen words as a string pair to make it easy to check
            }
        }
    }

    console.log(wordList.length);

    wordList.sort((a, b) => a[0].localeCompare(b[0])).forEach(([word, reversedWord]) => {
        console.log(`${word.padStart(9)} ${reversedWord}`);
    });
});
Output:
17
  amaroid diorama
  degener reneged
  deifier reified
  deliver reviled
  dessert tressed
 desserts stressed
  deviler relived
 dioramas samaroid
  gateman nametag
  leveler relevel
  pat-pat tap-tap
 redrawer rewarder
  reknits stinker
  relever reveler
  reliver reviler
  revotes setover
  sallets stellas


Constructing a very large dictionary (i.e. JSON object) would be time-consuming and would require quite a lot of memory, so this implementation takes advantage of jq's `bsearch` for binary search. It also confines the search to "words" that begin with a lower-case letter. The result is the same listing of 17 words as found elsewhere on this page.

< words.txt jq -nrR '
  def rev: explode | reverse | implode;

  ([inputs
    | select(length > 6 and
             (.[:1] | . >= "a" and
                      . <= "z" and 
                      . <= .[-1:] )) ] | sort) as $words
  | $words[]
  | rev as $reverse
  | select(. < $reverse and ($words | bsearch($reverse) > -1))
  | . + " <=> " + $reverse
'
function anadromes(minsize, csense = true, fname = "words.txt")
    words = Set(filter(w -> length(w) >= minsize, split((csense ? identity : lowercase)(read(fname, String)), r"\s+")))
    found = [(w, reverse(w)) for w in words if (r = reverse(w)) in words && w < r]
    println("Total $(length(found)) case $(csense ? "" : in)sensitive anadrome pairs found.")
    foreach(a -> println(a[1], " <=> ", a[2]), sort!(found))
end

anadromes(7)
anadromes(7, false)
Output:
Total 17 case sensitive anadrome pairs found.
amaroid <=> diorama
degener <=> reneged
deifier <=> reified
deliver <=> reviled
dessert <=> tressed
desserts <=> stressed
deviler <=> relived
dioramas <=> samaroid
gateman <=> nametag
leveler <=> relevel
pat-pat <=> tap-tap
redrawer <=> rewarder
reknits <=> stinker
relever <=> reveler
reliver <=> reviler
revotes <=> setover
sallets <=> stellas
Total 32 case insensitive anadrome pairs found.
amaroid <=> diorama
anacara <=> aracana
annabal <=> labanna
artamus <=> sumatra
colbert <=> trebloc
degener <=> reneged
deifier <=> reified
delbert <=> trebled
delevan <=> naveled
deliver <=> reviled
dessert <=> tressed
desserts <=> stressed
deviler <=> relived
dioramas <=> samaroid
eimmart <=> trammie
emmeram <=> maremme
gateman <=> nametag
latimer <=> remital
lattimer <=> remittal
lessees <=> seessel
leveler <=> relevel
nicolaus <=> sualocin
pat-pat <=> tap-tap
redrawer <=> rewarder
reknits <=> stinker
relever <=> reveler
reliver <=> reviler
revotes <=> setover
rotanev <=> venator
roygbiv <=> vibgyor
sallets <=> stellas
sennits <=> stinnes
word_list = {}

for line in io.lines("words.txt") do
   if #line > 6 then word_list[line] = true end
end

for word in pairs(word_list) do
   local rev = string.reverse(word)
   if word > rev and word_list[rev] then
      io.write(string.format("%-10s %s \n", word, rev))
   end
end
Output:

The program finds 17 anadromes:

relevel    leveler 
setover    revotes 
relived    deviler 
stinker    reknits 
reified    deifier 
samaroid   dioramas 
tap-tap    pat-pat 
nametag    gateman 
stressed   desserts 
reneged    degener 
diorama    amaroid 
reviler    reliver 
stellas    sallets 
rewarder   redrawer 
reviled    deliver 
reveler    relever 
tressed    dessert 
module  Anadromes {
	document a$
	load.doc a$, "words.txt"
	// ? doc.par(a$)=466549
	list1=list
	final1=list
	m=Paragraph(a$, 0)
	count=0
	If Forward(a$,m) then
	      While m {
	            word=Paragraph$(a$,(m))
	            if len(word)>6 then
	                if exist(list1, word) else
	                    append list1, word
	                end if
	                wordR=strrev$(word)
	                if wordR<>word then
	                    if not exist(final1, wordR) then
	                        if exist(list1, wordR) then
	                                if not exist(final1, word) then
	                                    append final1, wordR:=word
	                                    count++
	                                    ? count
	    					            refresh
	                                end if   
	                        end if
	                    end if
	                end if
	            end if
	      }
	end if
	sort final1
	document doc$
	k=each(final1)
	while k
		Print format$("{0:-12} <-> {1:12}", eval$(k!), eval$(k))
		doc$=format$("{0:-12} <-> {1:12}", eval$(k!), eval$(k))+{
		}
	end while
	save.doc doc$, "out.txt"
}
Anadromes
Output:
     amaroid <-> diorama     
     degener <-> reneged     
     deifier <-> reified     
     deliver <-> reviled     
     dessert <-> tressed     
    desserts <-> stressed    
     deviler <-> relived     
    dioramas <-> samaroid    
     gateman <-> nametag     
     leveler <-> relevel     
     pat-pat <-> tap-tap     
    redrawer <-> rewarder    
     reknits <-> stinker     
     relever <-> reveler     
     reliver <-> reviler     
     revotes <-> setover     
     sallets <-> stellas     
words = Select[ReadList["words.txt", Word], StringLength[#] > 6 &];
Map[{#, StringReverse[#]} &, Intersection[words, Map[StringReverse, words]]] //
     Select[Order @@ # == 1 &] // TableForm
Output:
amaroid    diorama 
degener    reneged 
deifier    reified 
deliver    reviled 
dessert    tressed 
desserts   stressed
deviler    relived 
dioramas   samaroid
gateman    nametag 
leveler    relevel 
pat-pat    tap-tap 
redrawer   rewarder
reknits    stinker 
relever    reveler 
reliver    reviler 
revotes    setover 
sallets    stellas

This implementation is for use with the Mini Micro version of MiniScript. The command-line version does not include a HTTP library. Modify the declaration of wordList object to use the file class instead of the http class.

getPairs = function(words)
	pairs = []
	for i in range(0, words.len - 2)
		for j in range(i + 1, words.len - 1)
			pairs.push([words[i], words[j]])
		end for 
	end for
	return pairs
end function

isReversed = function(word1, word2)
	for i in range(0, word1.len - 1)
		if word1[i] != word2[-(i+1)] then return false
	end for
	return true
end function

makeKey = function(word)
	return word.split("").sort.join("")
end function

wordList = http.get("https://raw.githubusercontent.com/dwyl/english-words/master/words.txt").split(char(10))

wordSets = {}

for word in wordList
	k = makeKey(word)
	if not wordSets.hasIndex(k) then
		wordSets[k] = [word]
	else
		wordSets[k].push(word)
	end if
end for

anadromes = []
for wordSet in wordSets.values
	if wordSet.len > 1 and wordSet[0].len > 6 then
		pairs = getPairs(wordSet)
		for pair in pairs
			if isReversed(pair[0], pair[1]) then print pair
		end for
	end if
end for
Output:
["amaroid", "diorama"]
["degener", "reneged"]
["deifier", "reified"]
["deliver", "reviled"]
["deviler", "relived"]
["dessert", "tressed"]
["desserts", "stressed"]
["dioramas", "samaroid"]
["gateman", "nametag"]
["leveler", "relevel"]
["relever", "reveler"]
["revotes", "setover"]
["pat-pat", "tap-tap"]
["redrawer", "rewarder"]
["reknits", "stinker"]
["reliver", "reviler"]
["sallets", "stellas"]
import std/[sets, strutils]

func reversed(s: string): string =
  ## Return the reverse of a string.
  ## Works only for ASCII strings.
  result.setLen(s.len)
  for i in 1..s.len:
    result[i - 1] = s[^i]

var wordSet: OrderedSet[string]
for word in lines("words.txt"):
  if word.len > 6:
    wordSet.incl word

for word in wordSet:
  let rev = reversed(word)
  if rev > word and rev in wordSet:
    echo word.alignLeft(12), rev
Output:
amaroid     diorama
degener     reneged
deifier     reified
deliver     reviled
dessert     tressed
desserts    stressed
deviler     relived
dioramas    samaroid
gateman     nametag
leveler     relevel
pat-pat     tap-tap
redrawer    rewarder
reknits     stinker
relever     reveler
reliver     reviler
revotes     setover
sallets     stellas
class SelfNumbers {
  function : Main(args : String[]) ~ Nil {
    if(args->Size() = 1) {
      input := System.IO.Filesystem.FileReader->ReadFile(args[0]);
      word_list := input->Split("\n");

      searchable_words := Collection.Set->New()<String>;
      each(word in word_list) {
        word := word->Trim();
        if(word->Size() > 6) {
          searchable_words->Insert(word);
        };
      };

      count := 0; limit := 17;
      words := searchable_words->GetKeys()<String>;
      each(word in words) {     
        if(word <> Nil) {
          reversed_word := word->Reverse();

          if(reversed_word->Compare(word) > 0 & searchable_words->Has(reversed_word)) {
            show_count := count + 1;
            "{$show_count}: |{$word}|, |{$reversed_word}|"->PrintLine();
            count += 1;
          };
        };

        if(count = limit) {
          break;
        };
      };
      "---"->PrintLine();
    };
  }
}
1: |amaroid|, |diorama|
2: |degener|, |reneged|
3: |deifier|, |reified|
4: |deliver|, |reviled|
5: |dessert|, |tressed|
6: |desserts|, |stressed|
7: |deviler|, |relived|
8: |dioramas|, |samaroid|
9: |gateman|, |nametag|
10: |leveler|, |relevel|
11: |pat-pat|, |tap-tap|
12: |redrawer|, |rewarder|
13: |reknits|, |stinker|
14: |relever|, |reveler|
15: |reliver|, |reviler|
16: |revotes|, |setover|
17: |sallets|, |stellas|
---
module StrSet = Set.Make(String)

let read_line_seq ch =
  let rec repeat () =
    match input_line ch with
    | s -> Seq.Cons (s, repeat)
    | exception End_of_file -> Nil
  in repeat

let string_rev s =
  let last = pred (String.length s) in
  String.init (succ last) (fun i -> s.[last - i])

let get_anadromes set =
  let aux s =
    let r = string_rev s in
    if s < r && StrSet.mem r set
    then Some (s, r)
    else None
  in
  Seq.filter_map aux (StrSet.to_seq set)

let () = read_line_seq stdin |> Seq.filter (fun s -> String.length s > 6)
  |> Seq.map String.lowercase_ascii |> StrSet.of_seq |> get_anadromes
  |> Seq.iter (fun (s, r) -> Printf.printf "%9s | %s\n" s r)
Output:
  amaroid | diorama
  anacara | aracana
  annabal | labanna
  artamus | sumatra
  colbert | trebloc
  degener | reneged
  deifier | reified
  delbert | trebled
  delevan | naveled
  deliver | reviled
  dessert | tressed
 desserts | stressed
  deviler | relived
 dioramas | samaroid
  eimmart | trammie
  emmeram | maremme
  gateman | nametag
  latimer | remital
 lattimer | remittal
  lessees | seessel
  leveler | relevel
 nicolaus | sualocin
  pat-pat | tap-tap
 redrawer | rewarder
  reknits | stinker
  relever | reveler
  reliver | reviler
  revotes | setover
  rotanev | venator
  roygbiv | vibgyor
  sallets | stellas
  sennits | stinnes
program anadromes;
// Capitalized first letter is used only for type names, like "String"
uses
   classes,   // for TStringList
   strUtils;  // for reverseString

var
   words: TStringList;
   line, reverseWord: String;
   inFile: TextFile;

begin
   words := TStringList.create;
   //otherwise every new line would be immediately sorted.
   words.sorted := false;

   assign(inFile, 'words.txt');
   reset(inFile);
   while not eof(inFile) do
   begin
      readLn(inFile, line);
      if length(line) > 6 then words.add(line);
   end;
   close(inFile);
   
  //all lines are read, now sort
   words.caseSensitive := true;
   words.sorted := true;
   for line in words do
   begin
      reverseWord := reverseString(line);
      if (line > reverseWord) and (words.indexOf(reverseWord) > -1) then
         writeLn(line: 10, ' <-> ', reversestring(line));
   end;

   words.free;
end.
Output:
   diorama <-> amaroid
   nametag <-> gateman
   reified <-> deifier
   relevel <-> leveler
   relived <-> deviler
   reneged <-> degener
   reveler <-> relever
   reviled <-> deliver
   reviler <-> reliver
  rewarder <-> redrawer
  samaroid <-> dioramas
   setover <-> revotes
   stellas <-> sallets
   stinker <-> reknits
  stressed <-> desserts
   tap-tap <-> pat-pat
   tressed <-> dessert
//sort every new inserted line
before real	0m0,422s user	0m0,391s sys	0m0,029s
//sort after all lines are read
after  real	0m0,241s user	0m0,221s sys	0m0,020s
program AnadromesAVL;
{uses the much more efficient AVL tree instead of Tstringlist to store all the words
 reducing runtime by a factor 10}
{$mode ObjFPC}{$H+}

uses
  Classes, AvgLvlTree, SysUtils, strutils;

type
  TMyData = class
  public
    Woord: string;
  end;

var
  Tree: TAvgLvlTree;

function CompareMyData(Data1, Data2: Pointer): Integer;
begin
  if TMyData(Data1).Woord > TMyData(Data2).Woord then result := -1 else
    if TMyData(Data1).Woord < TMyData(Data2).Woord then result := 1 else
      result := 0;
end; 

procedure AddToTree(Data: string);
var
  MyData: TMyData;
begin
  MyData := TMyData.Create;
  MyData.Woord := Data;
  Tree.Add(MyData);
end;

var
  Node: TAvgLvlTreeNode;
  Infile: TextFile;
  Line: string;
  AnadromesList: TStringList;
  MyData1, MyData2: TMyData;

begin
  AnadromesList := TStringList.Create;
  AnadromesList.Sorted := True;
  Tree := TAvgLvlTree.Create(@CompareMyData);

  AssignFile(Infile, 'words.txt');
  try
    Reset(Infile);
    while not EOF(Infile) do
    begin
      Readln(Infile, Line);
      if Length(Line) > 6 then
        AddToTree(Line);
    end;
    CloseFile(Infile);
  except
    on E: EInOutError do
      Writeln('File handling error occurred. Details: ', E.Message);
  end;

  MyData2 := TMyData.Create;

  for Node in Tree do
  begin
    MyData1 := TMyData(Node.Data);
    Line := MyData1.Woord;
    MyData2.Woord := ReverseString(MyData1.Woord);

    if (Line <> MyData2.Woord) and (Tree.Find(MyData2) <> nil) then
      if AnadromesList.IndexOf(MyData2.Woord) = -1 then
        AnadromesList.Add(Line);
  end;

  for Line in AnadromesList do
    Writeln(Line:10, ' <-> ', ReverseString(Line));

  AnadromesList.Free;
  Tree.Free;
  MyData2.Free;
end.
Output:
   amaroid <-> diorama
   degener <-> reneged
   deifier <-> reified
   deliver <-> reviled
   dessert <-> tressed
  desserts <-> stressed
   deviler <-> relived
  dioramas <-> samaroid
   gateman <-> nametag
   leveler <-> relevel
   pat-pat <-> tap-tap
  redrawer <-> rewarder
   reknits <-> stinker
   relever <-> reveler
   reliver <-> reviler
   revotes <-> setover
   sallets <-> stellas


// Anadromes. Nigel Galloway: August 30th., 2022
begin
  var n := ReadLines('words.txt').Where(n -> Length(n) > 6);
  foreach var v in n.Intersect(n.Select(n -> ReverseString(n))) do if v < ReverseString(v) then writeln(v);
end.
Output:
amaroid
degener
deifier
deliver
dessert
desserts
deviler
dioramas
gateman
leveler
pat-pat
redrawer
reknits
relever
reliver
revotes
sallets
use strict;
use warnings;

my(%W,%A);
for my $w ( grep { /[A-z\-]{7,}/ } split "\n", do { local( @ARGV, $/ ) = ( 'words.txt' ); <> } ) {
    my $r = reverse $w;
    if ($W{$r}) { $A{$r} = sprintf "%10s ↔ %s\n", $r, $w }
    else        { $W{$w} = $w                            }
}

print $A{$_} for sort keys %A;
Output:
   amaroid ↔ diorama
   degener ↔ reneged
   deifier ↔ reified
   deliver ↔ reviled
   dessert ↔ tressed
  desserts ↔ stressed
   deviler ↔ relived
  dioramas ↔ samaroid
   gateman ↔ nametag
   leveler ↔ relevel
   pat-pat ↔ tap-tap
  redrawer ↔ rewarder
   reknits ↔ stinker
   relever ↔ reveler
   reliver ↔ reviler
   revotes ↔ setover
   sallets ↔ stellas
with javascript_semantics
integer m = iff(platform()=JS?5:7)
sequence words = unique(unix_dict(m,`words.txt`))
function anadrome(string w)
    return w<reverse(w) and binary_search(reverse(w),words)>0
end function
sequence r = sort(filter(words,anadrome)),
         s = columnize({r,apply(r,reverse)}),
         t = join(s,"\n",fmt:="%8s <=> %-8s")
printf(1,"Found %d anadromes:\n%s\n",{length(r),t})
Output:

On the desktop:

Found 17 anadromes:
 amaroid <=> diorama
 degener <=> reneged
 deifier <=> reified
 deliver <=> reviled
 dessert <=> tressed
desserts <=> stressed
 deviler <=> relived
dioramas <=> samaroid
 gateman <=> nametag
 leveler <=> relevel
 pat-pat <=> tap-tap
redrawer <=> rewarder
 reknits <=> stinker
 relever <=> reveler
 reliver <=> reviler
 revotes <=> setover
 sallets <=> stellas

Under p2js, aka in a browser, from where it cannot realistically read disk files, unix_dict() ignores the filename parameter and uses the smaller unixdict.txt, as by now quite some time ago converted into a dirty great big Phix sequence constant and then transpiled into a JavaScript Array, for which we also drop the minimum length to 5, and get:

Found 5 anadromes:
   damon <=> nomad   
  kramer <=> remark  
   lager <=> regal   
   leper <=> repel   
   lever <=> revel   
Translation of: Lua

Note that the order of the elements when tables are traversed is indeterminate, so the order of the words may vary each time the program is run.

do  -- find some anadromes - words that when reversed are another word
    -- the anadromes must be at least 7 characters long

    local words = {}
    for word in io.lines( "words.txt" ) do
        if # word > 6 then
            words[ word ] = true
        end
    end

    for w, _ in pairs( words ) do
        local r = w:reverse()
        if r > w then
            if words[ r ] then
                io.write( string.format( "%12s %s\n", w, r ) )
            end
        end
    end

end
Output:
     degener reneged
     leveler relevel
    dioramas samaroid
     deviler relived
     dessert tressed
     deifier reified
     deliver reviled
     sallets stellas
     amaroid diorama
     reliver reviler
     reknits stinker
    redrawer rewarder
     revotes setover
    desserts stressed
     pat-pat tap-tap
     relever reveler
     gateman nametag
# anadrome.py by Xing216
with open("words.txt","r") as f:
    words = f.read().splitlines()
word_set = set(words)
seen_words = []
word_list = []
for word in word_set:
    if len(word) > 6: 
        reversed_word = word[::-1]
        if reversed_word in word_set and reversed_word != word \
           and (reversed_word,word) not in seen_words:
            word_list.append((word, reversed_word))
            seen_words.append((word,reversed_word))
for word,reversed_word in sorted(word_list, key=lambda x: x[0]):
    print(f"{word:>9} {reversed_word}")
Output:
 amaroid diorama
 deifier reified
desserts stressed
dioramas samaroid
 leveler relevel
 nametag gateman
redrawer rewarder
 reknits stinker
 relived deviler
 reneged degener
 reveler relever
 reviled deliver
 reviler reliver
 setover revotes
 stellas sallets
 tap-tap pat-pat
 tressed dessert
  [ temp put
    [ dup [] = iff
        [ drop false ]
        done
      dup size 2 / split
      behead temp share
      2dup = iff
        [ 2drop 2drop true ]
        done
      $< if swap
      drop again ]
    temp release ]           is has$ ( [ $ --> b )

  $ "rosetta/words.txt" sharefile drop
  nest$
  [] swap witheach 
    [ dup size 7 < iff
        drop done
      nested join ]
  sort$
  [ dup [] != while
    behead
    2dup reverse has$ iff
      [ dup echo$ sp
        reverse echo$ cr ]
      again
    drop again ]
  drop
Output:
amaroid diorama
degener reneged
deifier reified
deliver reviled
dessert tressed
desserts stressed
deviler relived
dioramas samaroid
gateman nametag
leveler relevel
pat-pat tap-tap
redrawer rewarder
reknits stinker
relever reveler
reliver reviler
revotes setover
sallets stellas

R

anagra <- function(x) {
  flipfun <- function(x) {
    x <- rev(x)
    
    x <- paste(x, collapse = "")
    
    return(x)
    
  }
  
  x <- strsplit(x, split = "")
  
  x <- lapply(x, flipfun)
  
  x <- unlist(x)
  
  return(x)
}

sortfun <- function(x) {
  x <- unlist(x)
  
  x <- unname(x)
  
  x <- sort(x)
  
  x <- data.frame(orig = x[1], flip = x[2])
  
  return(x)
  
}


anadrome <- read.delim("words.txt")[, 1]

anadrome <- anadrome[nchar(anadrome) > 6]

anadrome <- anadrome[anagra(anadrome) %in% anadrome]

anadrome <- data.frame(orig = anadrome, flip = anagra(anadrome))

anadrome <- anadrome[anadrome$orig != anadrome$flip, ]

anadrome <- lapply(1:nrow(anadrome), function(i) sortfun(anadrome[i, ]))

anadrome <- do.call(rbind, anadrome)

anadrome <- anadrome[duplicated(anadrome), ]


for (i in 1:nrow(anadrome)) {
  cat(paste0(i, " ", anadrome[i, 1], " <-> ", anadrome[i, 2]), "\n")
  
}
Output:
1 amaroid <-> diorama 
2 gateman <-> nametag 
3 deifier <-> reified 
4 leveler <-> relevel 
5 deviler <-> relived 
6 degener <-> reneged 
7 relever <-> reveler 
8 deliver <-> reviled 
9 reliver <-> reviler 
10 redrawer <-> rewarder 
11 dioramas <-> samaroid 
12 revotes <-> setover 
13 sallets <-> stellas 
14 reknits <-> stinker 
15 desserts <-> stressed 
16 pat-pat <-> tap-tap 
17 dessert <-> tressed
my @words = 'words.txt'.IO.slurp.words.grep: *.chars > 6;

my %words = @words.pairs.invert;

put join "\n", @words.map: { %words{$_}:delete and sprintf "%10s ↔ %s", $_, .flip if ($_ ne .flip) && %words{.flip} }
Output:
   amaroid ↔ diorama
   degener ↔ reneged
   deifier ↔ reified
   deliver ↔ reviled
   dessert ↔ tressed
  desserts ↔ stressed
   deviler ↔ relived
  dioramas ↔ samaroid
   gateman ↔ nametag
   leveler ↔ relevel
   pat-pat ↔ tap-tap
  redrawer ↔ rewarder
   reknits ↔ stinker
   relever ↔ reveler
   reliver ↔ reviler
   revotes ↔ setover
   sallets ↔ stellas
Rebol [
    title: "Rosetta code: Anadromes"
    file:  %Anadromes.r3
    url:   https://rosettacode.org/wiki/Anadromes
]

;; download word list if not cached locally
unless exists? %words.txt [
    write %words.txt
    read https://raw.githubusercontent.com/dwyl/english-words/refs/heads/master/words.txt
]
words: read/lines %words.txt
count: length? words

;; pre-filter: keep only lowercase words longer than 6 chars
filtered: make map! count / 2
foreach word words [
    if 6 < length? word [
        word: lowercase word
        filtered/:word: reverse copy word
    ]
]

print [count "words," length? filtered "> 6 characters."]

;; find anadromes: pairs where both word and its reverse exist in the filtered map
result: copy []
foreach [word reversed] filtered [
    if all [
        find/case filtered reversed   ;; reverse also exists as a word
        word < reversed               ;; avoid recording each pair twice
    ][
        repend result [word reversed]
    ]
]

print ["Found" (length? result) / 2 "anadromes:"]
foreach [word reversed] result [
    printf [-17 " ↔ "] [word reversed]
]
Output:
466550 words, 387535 > 6 characters.
Found 32 anadromes:
          amaroid ↔ diorama
          anacara ↔ aracana
          annabal ↔ labanna
          artamus ↔ sumatra
          colbert ↔ trebloc
          degener ↔ reneged
          deifier ↔ reified
          delbert ↔ trebled
          delevan ↔ naveled
          deliver ↔ reviled
          dessert ↔ tressed
         desserts ↔ stressed
          deviler ↔ relived
         dioramas ↔ samaroid
          eimmart ↔ trammie
          emmeram ↔ maremme
          gateman ↔ nametag
          latimer ↔ remital
         lattimer ↔ remittal
          lessees ↔ seessel
          leveler ↔ relevel
         nicolaus ↔ sualocin
          pat-pat ↔ tap-tap
         redrawer ↔ rewarder
          reknits ↔ stinker
          relever ↔ reveler
          reliver ↔ reviler
          revotes ↔ setover
          roygbiv ↔ vibgyor
          rotanev ↔ venator
          sallets ↔ stellas
          sennits ↔ stinnes
load "stdlib.ring"

see "work..." + nl
txt = read("words.txt")
txt = str2list(txt)
for n = 1 to len(txt)
    txtStr = string(txt[n])
    txtRev = reverse(txtStr)
    ind = find(txt,txtRev)
    if ind > 0 and len(txtRev) > 6
       see txtStr + " " + txtRev + nl
    ok
next
see "done..." + nl

func reverse(rev)
     re = ""
     revstr = string(rev)
     for n = len(revstr) to 1 step -1
         re = re + revstr[n]
         re = string(re)
     next
     return re
Output:
amaroid   diorama
degener   reneged
deifier   reified
deliver   reviled
dessert   tressed
desserts  stressed
deviler   relived
dioramas  samaroid
gateman   nametag
leveler   relevel
pat-pat   tap-tap
redrawer  rewarder
reknits   stinker
relever   reveler
reliver   reviler
revotes   setover
sallets   stellas 
words = File.readlines("words.txt", chomp: true).reject{|word| word.size <= 6}
reversed_words = words.map(&:reverse)
reversables = (words & reversed_words).reject{|word| word == word.reverse} 
res = reversables.uniq{|w| [w, w.reverse].sort}
res.each{|w| puts "#{w} - #{w.reverse}".center(20) }
Output:
 amaroid - diorama  
 degener - reneged  
 deifier - reified  
 deliver - reviled  
 dessert - tressed  
desserts - stressed 
 deviler - relived  
dioramas - samaroid 
 gateman - nametag  
 leveler - relevel  
 pat-pat - tap-tap  
redrawer - rewarder 
 reknits - stinker  
 relever - reveler  
 reliver - reviler  
 revotes - setover  
 sallets - stellas
use std::collections::BTreeSet;
use std::fs::File;
use std::io::{self, BufRead};

fn load_dictionary(filename: &str, min_length: usize) -> std::io::Result<BTreeSet<String>> {
    let file = File::open(filename)?;
    let mut dict = BTreeSet::new();
    for line in io::BufReader::new(file).lines() {
        let word = line?;
        if word.len() >= min_length {
            dict.insert(word);
        }
    }
    Ok(dict)
}

fn main() {
    match load_dictionary("words.txt", 7) {
        Ok(dictionary) => {
            for word in &dictionary {
                let rev = String::from_iter(word.chars().rev());
                if rev > *word && dictionary.contains(&rev) {
                    println!("{:<8} <-> {}", word, rev);
                }
            }
        }
        Err(error) => eprintln!("{}", error),
    }
}
Output:
amaroid  <-> diorama
degener  <-> reneged
deifier  <-> reified
deliver  <-> reviled
dessert  <-> tressed
desserts <-> stressed
deviler  <-> relived
dioramas <-> samaroid
gateman  <-> nametag
leveler  <-> relevel
pat-pat  <-> tap-tap
redrawer <-> rewarder
reknits  <-> stinker
relever  <-> reveler
reliver  <-> reviler
revotes  <-> setover
sallets  <-> stellas
$ include "seed7_05.s7i";

const func boolean: binarySearch (in array string: haystack, in string: needle) is func
  result
    var boolean: result is FALSE;
  local
    var integer: low is 1;
    var integer: high is -1;
    var integer: middle is -1;
  begin
    high := length(haystack);
    while result = FALSE and low <= high do
      middle := low + (high - low) div 2;
      if needle < haystack[middle] then
        high := pred(middle);
      elsif needle > haystack[middle] then
        low := succ(middle);
      else
        result := TRUE;
      end if;
    end while;
  end func;

const proc: main is func
  local
    var file: dictionary is STD_NULL;
    var string: word is "";
    var string: reversed is "";
    var array string: words is (array string).value;
  begin
    dictionary := open("words.txt", "r");
    while not eof(dictionary) do
      readln(dictionary, word);
      words &:= word;
    end while;
    close(dictionary);
    words := sort(words);
    for word range words do
      reversed := reverse(word);
      if length(word) > 6 and word < reversed and binarySearch(words, reversed) then
        writeln(word <& " <-> " <& reversed);
      end if;
    end for;
  end func;
Output:
amaroid <-> diorama
degener <-> reneged
deifier <-> reified
deliver <-> reviled
dessert <-> tressed
desserts <-> stressed
deviler <-> relived
dioramas <-> samaroid
gateman <-> nametag
leveler <-> relevel
pat-pat <-> tap-tap
redrawer <-> rewarder
reknits <-> stinker
relever <-> reveler
reliver <-> reviler
revotes <-> setover
sallets <-> stellas


program find_anadromes;
    read_file;
   
    anadromes := {
        {word, reverse word} 
        : word in words 
        | reverse word in words
          and reverse word /= word
    };
    
    loop for anadrome in anadromes do
        print(anadrome);
    end loop;
   
read_file::
    words := {};
    dictfile := open("words.txt", "r");
    loop doing 
        geta(dictfile, word); 
    while word /= om do
        if #word <= 6 then continue; end if;
        words with:= word;
    end loop;
    close(dictfile);
end program;
Output:
{amaroid diorama}
{degener reneged}
{deifier reified}
{deliver reviled}
{dessert tressed}
{desserts stressed}
{deviler relived}
{dioramas samaroid}
{gateman nametag}
{leveler relevel}
{'pat-pat' 'tap-tap'}
{redrawer rewarder}
{reknits stinker}
{relever reveler}
{reliver reviler}
{revotes setover}
{sallets stellas}
var S = Hash()

File('words.txt').open_r.each {|word|
    word.len > 6 || next
    if (S.has(word.flip)) {
        say "#{word.flip} <=> #{word}"
    }
    else {
        S{word} = true
    }
}
Output:
amaroid <=> diorama
gateman <=> nametag
deifier <=> reified
leveler <=> relevel
deviler <=> relived
degener <=> reneged
relever <=> reveler
deliver <=> reviled
reliver <=> reviler
redrawer <=> rewarder
dioramas <=> samaroid
revotes <=> setover
sallets <=> stellas
reknits <=> stinker
desserts <=> stressed
pat-pat <=> tap-tap
dessert <=> tressed

A complete implementation of a partitioning approach with async processing.

Supporting class:

import Foundation
import Algorithms

extension String {
	var firstAndLast: String { String(self.first!) + String(self.last!) }
	var backwards: String { String(self.reversed()) }
}

class AnadromeIndex {

		// Stucture to index by length and the first and last character
		// [ length: [ firstAndLast: [ words ] ] ]
		/// Only compare words of same length, and where
		/// firstAndLast corresponds to its reverse
	var dict: [Int: [String: [String]]] = [:]

	func loadFile(
		fileURL: URL,
		minLen: Int,
		byLine: Bool = false
	) async -> (Int, Int) {
		var linesRead = 0
		var wordsIndexed = 0
		do {
			if byLine {
				for try await line in fileURL.lines {
					linesRead += 1
					if line.count < minLen { continue }
					wordsIndexed += 1
					self.addWord( line
						.trimmingCharacters(in: .whitespacesAndNewlines)
					)
				}
			} else {
				let fileString = try String(contentsOf: fileURL, encoding: .utf8)
				for line in fileString.components(separatedBy: .newlines) {
					linesRead += 1
					if line.count < minLen { continue }
					wordsIndexed += 1
					self.addWord( line
						.trimmingCharacters(in: .whitespacesAndNewlines)
					)
				}
			}
		} catch {
			debugPrint(error)
		}
		return (linesRead, wordsIndexed)
	}

	func addWord(_ str: String) {
		let len = str.count
		let index = str.firstAndLast
		if let azDict = dict[len] {
			if azDict[index] != nil {
				self.dict[len]![index]!.append(str)
			} else {
				self.dict[len]!.updateValue([str], forKey: index)
			}
		} else {
			dict.updateValue([index: [str]], forKey: len)
		}
	}

	func findAnadromes() async -> [String] {
		var done: [String] = []
		var results: [String] = []
		let allResults = await withTaskGroup(
			of: [String].self,
			returning: [String].self) { group in
				// By length
			for len in self.dict.keys.sorted() {
				if let lenSet = self.dict[len] {
						// By firstAndLast characters
					for az in lenSet.keys.sorted() {
						let za = az.backwards
						if done.contains(where: { $0 == "\(len)\(az)" || $0 == "\(len)\(za)" })
								|| lenSet[za] == nil { continue }
						done += ["\(len)\(az)", "\(len)\(za)"]
						group.addTask {
							let lh = lenSet[az]!
							let rh = lenSet[za]!
							let f = await self.searchProduct(lh: lh, rh: rh)
							return f
						}
					}
				}
			}
			for await result in group {
				results += result
			}
			return results
		}
		return allResults
	}

	func searchProduct(lh: [String], rh: [String]) async -> [String] {
		var found: [String] = []
		for ( s1, s2 ) in product(lh, rh) {
			if s1 == s2 { continue } // palindrome
			if s1 == String(s2.reversed())
					&& !found.contains(where: {$0 == s2 || $0 == s1}) {
				found.append(s1)
			}
		}
		return found
	}
}

Usage:

import Foundation
import ArgumentParser

@main
struct AnadromeFinder: AsyncParsableCommand {
	@Argument(help: "File containing words", transform: URL.init(fileURLWithPath:))
	var file: URL

	@Option(name: .shortAndLong)
	var minLen: Int = 6

	@Flag(name: .shortAndLong)
	var verbose = false

	mutating func run() async throws {
		let indexSet = AnadromeIndex()
		var start = Date()
		let (lines, words) = await indexSet.loadFile(fileURL: file, minLen: minLen)
		if verbose {
			printStderr("\(lines.formatted()) lines read,",
									"\(words.formatted()) hashed in",
									"\(since(start).formatted()) sec")
		}
		start = Date()
		let found = await indexSet.findAnadromes()
		if verbose {
			printStderr("\(found.count.formatted()) matches found in",
									"\(since(start)) seconds")
		}
			// Print results
		for e in found.sorted(by: {a, b in
			if a.count == b.count { return a < b }
			return a.count < b.count
		}) {
			print(e, e.backwards)
		}
	}
	func since(_ dt: Date) -> TimeInterval {
		return Date().timeIntervalSince(dt)
	}
	func printStderr(
		_ items: Any...,
		separator: String = " ",
		terminator: String = "\n"
	) {
		let output = items
			.map { String(describing: $0) }
			.joined(separator: separator) + terminator
		FileHandle.standardError.write(output.data(using: .utf8)!)
	}
}

Output:

% ./Anadromes --verbose /Users/username/Downloads/words.txt
466,551 lines read, 427,054 hashed in 5.52172 sec
83 matches found in 7.691561937332153 seconds
abrood doorba
agenes senega
amunam manuma
animal lamina
animes semina
bruted deturb
darter retrad
decart traced
decurt truced
deflow wolfed
degami imaged
denier reined
denies seined
depots stoped
derats stared
dessus sussed
dewans snawed
dialer relaid
diaper repaid
dibrom morbid
dorter retrod
drawer reward
elides sedile
elutes setule
enserf fresne
ergate etagre
eviler relive
gangan nagnag
gawgaw wagwag
golfer reflog
hallan nallah
lemmas sammel
lesson nossel
liever reveil
looter retool
manitu utinam
mooder redoom
nedder redden
pupils slipup
rebuts stuber
recaps spacer
recart tracer
redips spider
reflow wolfer
reives sevier
reknit tinker
remeet teemer
repins sniper
report troper
repots stoper
retros sorter
scares seracs
secret terces
selahs shales
serves sevres
sinnet tennis
skeets steeks
sleeps speels
sleets steels
sloops spools
snoops spoons
spirts strips
sports strops
sprits stirps
struts sturts
territ tirret
amaroid diorama
degener reneged
deifier reified
deliver reviled
dessert tressed
deviler relived
gateman nametag
leveler relevel
pat-pat tap-tap
reknits stinker
relever reveler
reliver reviler
revotes setover
sallets stellas
desserts stressed
dioramas samaroid
redrawer rewarder

See Semordnilap

Library: Wren-sort
Library: Wren-fmt
import "io" for File
import "./sort" for Sort, Find
import "./fmt" for Fmt

var wordList = "words.txt" // local copy
var words = File.read(wordList)
                .trimEnd()
                .split("\n")
                .where { |word| word.count > 6 }
                .toList

Sort.quick(words) // need strict lexicographical order to use binary search
var anadromes = []
for (word in words) {
    var word2 = word[-1..0]
    if (word != word2 && !anadromes.contains(word2) && Find.first(words, word2) >= 0) {
        anadromes.add(word)
    }
}
System.print("The anadrome pairs with more than 6 letters are:")
for (ana in anadromes) Fmt.print("$8s <-> $8s", ana, ana[-1..0])
Output:
The anadrome pairs with more than 6 letters are:
 amaroid <->  diorama
 degener <->  reneged
 deifier <->  reified
 deliver <->  reviled
 dessert <->  tressed
desserts <-> stressed
 deviler <->  relived
dioramas <-> samaroid
 gateman <->  nametag
 leveler <->  relevel
 pat-pat <->  tap-tap
redrawer <-> rewarder
 reknits <->  stinker
 relever <->  reveler
 reliver <->  reviler
 revotes <->  setover
 sallets <->  stellas
!ys-0

DICT =: '/usr/share/dict/words'

defn main(dict=DICT, limit=20):
  words =:
    split('able elba abut tuba agar raga avid diva bard drab', ' ')
  word-set =: set(words)
  loop words words, left limit:
    when left > 0:
      when-not words:empty?:
        word =: words.0
        rev =: word:reverse
        when compare(word rev) < 0:
          when in?(rev word-set):
            say: "$word $rev"
            left =: left.--
        recur: words:rest, left
Output:
$ ys anadromes.ys
able elba
abut tuba
agar raga
avid diva
bard drab