Rebol3 Code Examplex
Anagram generator
Produce all valid anagrams of a given word.
Rebol [
title: "Rosetta code: Anagram generator"
file: %Anagram_generator.r3
url: https://rosettacode.org/wiki/Anagram_generator
]
file: %unixdict.txt
unless exists? file [
print "Downloading wordlist."
write file read https://raw.githubusercontent.com/thundergnat/rc-run/refs/heads/master/rc/resources/unixdict.txt
]
words-raw: read/lines file
;; Build word map from dictionary
word-map: make map! []
foreach w words-raw [
sorted-key: sort copy w
either find word-map sorted-key [
append word-map/:sorted-key w
][
word-map/:sorted-key: reduce [w]
]
]
create-combinations: function [
"Generate all k-length combinations of characters from a string"
word [string!] k [integer!]
][
combos: copy []
n: length? word
indices: copy []
;; Initialize first combination (first k indices)
repeat i k [append indices i]
forever [
;; Extract combination
combo: copy ""
foreach idx indices [append combo word/:idx]
append combos combo
i: k
while [i >= 1] [
if (pick indices i) < (n - k + i) [break]
-- i
]
if i < 1 [break]
;; Increment and fill forward
indices/:i: indices/:i + 1
loop (k - i) [
indices/(i + 1): indices/:i + 1
++ i
]
]
combos
]
set-difference-strings: function [
"Compute sorted set-difference of two sorted strings"
a [string!] b [string!]
][
result: copy ""
i: j: 1
while [i <= length? a] [
either all [j <= length? b a/:i = b/:j][
++ j
][ append result a/:i ]
++ i
]
result
]
anagram-generator: function [
"Main anagram generator"
input-word [string!]
][
;; Normalize the input: lowercase, sort alphabetically, strip non-alpha characters.
;; parse with the alpha bitset removes any character that isn't a letter,
;; leaving a clean, sorted string of lowercase letters to work with.
word: sort lowercase copy input-word
alpha: system/catalog/bitsets/alpha
parse word [any [some alpha | remove skip]]
;; Track letter-combinations we've already processed to avoid duplicate anagram pairs.
previous-letters: #[]
;; Start at half the word length (right-shift by 1 = integer divide by 2).
;; We only need to check splits up to half, since the other half is the complement.
n: (length? word) >> 1
while [n >= 1] [
;; Generate all n-character combinations from the sorted word.
foreach letters-one create-combinations word n [
sorted-one: sort copy letters-one
;; Skip this combination if we've already seen it, to avoid producing
;; duplicate pairs (e.g. "abc"/"def" and "def"/"abc" are the same pair).
unless find previous-letters sorted-one [
previous-letters/:sorted-one: true
;; Look up whether this letter-set forms any known words.
anagrams-one: word-map/:sorted-one
if anagrams-one [
;; Derive the complementary letter-set: whatever letters remain
;; after removing letters-one from the full word.
letters-two: set-difference-strings word sorted-one
;; When the word splits exactly in half (n = half length), both
;; halves are the same size so each unordered pair will appear
;; twice. Guard against this by also marking letters-two as seen;;
;; if it was already marked, skip to the next combination.
if (length? word) = (2 * n) [
if previous-letters/:letters-two [
-- n
continue
]
previous-letters/:letters-two: true
]
;; Look up whether the complementary letter-set forms any known words.
anagrams-two: word-map/:letters-two
if anagrams-two [
;; Both halves are valid words — print every pairing.
foreach w1 anagrams-one [
foreach w2 anagrams-two [
print rejoin [" " w1 " " w2]
]
]
]
]
]
]
-- n
]
]
;; Test words
test-words: ["Rosetta code" "Joe B'iden" "Clint Eastw3ood"]
foreach tw test-words [
print rejoin ["Two word anagrams of " tw ":"]
anagram-generator tw
print ""
]