Anagrams

I recently started playing a game against my wife called anagrams. Since she is rather good at it, my first attempt was to use command line and the MacOS dictionary:

0 Johns-MBP$ grep '^[pcilas][pcilas][pcilas][pcilas][pcilas][pcilas][pcilas]$' /usr/share/dict/words | grep -v ll
aplasia
asialia
classic
...

This wasn't very fast since it took too long to type the pattern, and also to guess what double-letter options should be avoided.

Next, created some Smalltalk code for possible matches:

| pattern matches stream |
pattern := 'ufmets' asBag.
matches := OrderedCollection new.
stream := '/usr/share/dict/words' asFileReference readStream.
[ stream atEnd ] whileFalse: [ | word boolean |
	word := stream nextLine.
	( word size <= pattern size and: [ (word allSatisfy: [ :c | pattern includes: c ]) and: [ boolean := true.
			word asBag doWithOccurrences: [ :letter :number | (pattern occurrencesOf: letter) < number ifTrue: [ boolean := false ] ].
			boolean ] ] ) ifTrue: [ matches add: word ] ].
stream close.
matches sorted: [ :a :b | a size > b size ]

This takes care of the double-letter problem, and won my first game. Unfortunately it requires a change in the pattern (the text ufmets above).

To make this faster, the next step is to allow the pattern as a command-line argument, this booklet explains the idea. Creating the class:

CommandLineHandler subclass: #WordCommandLineHandler
	instanceVariableNames: ''
	classVariableNames: ''
	package: 'Custom-CommandLine'

On the class side, created:

commandName
	^ 'word'

Instance side has:

activate
	self activateHelp
		ifTrue: [ ^ self ].
	self arguments size = 1
		ifFalse: [ self printHelp.
			^ self exitFailure: 'Missing Arguments' ].
	self
		printAnagrams;
		quit

and:

printAnagrams
	| pattern matches stream |
	pattern := self arguments first asBag.
	matches := OrderedCollection new.
	stream := '/usr/share/dict/words' asFileReference readStream.
	[ stream atEnd ]
		whileFalse: [ | word boolean |
			word := stream nextLine.
			(word size <= pattern size
				and: [ (word allSatisfy: [ :c | pattern includes: c ])
						and: [ boolean := true.
							word asBag
								doWithOccurrences: [ :letter :number |
									(pattern occurrencesOf: letter) < number
										ifTrue: [ boolean := false ] ].
							boolean ] ])
				ifTrue: [ matches add: word ] ].
	stream close.
	(matches sorted: [ :a :b | a size > b size ])
		do: [ :str |
			str size > 2
				ifTrue: [ self stdout
						print: str;
						lf ] ]

After staving the image, this can be ran as:

0 Johns-MBP$ ./pharo ./anagram.image word pcilas
'spical'
'spica'
'scalp'
'salic'
...

As I was writing this, 3Blue1Brown released a wordle video that is related.

Posted by John Borden at 10 February 2022, 2:44 am link