
Critique my first program?
This is my first useful program I've written with Dylan. It's intended
to be a Scrabble "aid." It searches a list for words that contain the
letters specified on the command line, sorts them, and prints them out.
I'd really appreciate it if anyone would point out places where the code
could be improved, even stylistic nitpicks where I defy Dylan
convention. I'm also very interested in ways to improve performance,
since it currently runs about 25 times slower than my equivalent C
program when compiled with d2c, despite my optimizing it as best I can.
Thanks in advance. Here goes.
module: Papaya
synopsis: Searches a list of words for words containing the specified
letters, sorts them by length, and prints them
/* What is the difference between define function and define method? */
define variable FILE-PATH = "/usr/share/dict/extended_words";
//define variable FILE-PATH = "/Users/peter/testfile.txt";
define method useage() => ();
puts("Useage: lsearch string\n");
end method useage;
/* convert a-z to 0-25 */
define method char-to-num(c :: <character>) => (result :: <integer>)
as (<integer>, c) - 97 //as(<integer>, 'a')
end method char-to-num;
/* takes in a string, returns a vector of length 26 where each
index stores the count of the corresponding letter
e.g. string-to-nums("aac") is [2, 0, 1, 0, 0, ...]
*/
define method string-to-nums(string :: <string>) => (nums :: <vector>)
let result = make(<vector>, size: 26, fill: 0);
do ( method (a)
let val = char-to-num(a);
result[val] := result[val] + 1; //can I write this as
result[val]++ or similar?
end,
string);
result
end method string-to-nums;
/* converts a string to an array of numbers. string-to-array("aac") is
[0, 0, 2] */
define method string-to-array(string :: <string>) => (nums :: <vector>)
map-as (<vector>, char-to-num, string)
end method string-to-array;
define method main-search(string :: <string>) => ()
let base-array :: <vector> = string-to-nums(string);
let matching-strings :: <stretchy-vector> = make(<stretchy-vector>);
let matching-array :: <vector> = string-to-array(string);
let handler <file-does-not-exist-error>
= method(condition, next)
puts(concatenate("Unable to open file ", FILE-PATH, "\n"));
exit();
end method;
let stream :: <file-stream> = make(<file-stream>,
locator: FILE-PATH);
/* Read in the lines and save the ones that contain the letters
specified in string.
A naive implementation would take n^2 time, so instead we convert
each string to a
hash-table like construct using string-to-nums and operate on that */
block()
/* a string is good if it contains all of the characters in our
base-array */
// 116.48 real 65.74 user 3.59 sys
// This function, though simple, is also slow, and we do not use
it any more
local method goodOld? (string :: <string>) => (good :: <boolean>)
~ any? ( negative?, map(\-, string-to-nums(string), base-array))
end method goodOld?;
// 43.83 real 24.56 user 0.53 sys
// This function is equivalent to the above function but much faster
local method good? (string :: <string>) => (good :: <boolean>)
let new-sequence :: <vector> = string-to-nums(string);
block (done)
for (i from 0 below matching-array.size)
let index :: <integer> = matching-array[i];
new-sequence[index] := new-sequence[index] - 1;
if (negative?(new-sequence[index])) done(#f); end if;
finally
#t
end for;
end block
end method good?;
while (#t)
let line :: <string> = as-lowercase(read-line(stream));
if (good?(line)) matching-strings := add!(matching-strings,
line) end if;
end while;
exception (<end-of-stream-error>)
#f; //do we need to have some dummy value if we just want
execution to continue?
end block;
/* we are done with the stream */
stream.close;
/* sort the lines we saved */
local method size-compare (a :: <string>, b :: <string>) =>
(size-compare :: <boolean>)
(size(a) < size(b)) |
(size(a) == size(b) & (a < b))
end method size-compare;
matching-strings := sort! (matching-strings, test: size-compare);
/* output the result */
do ( method (s) puts(s); puts("\n") end method, matching-strings);
end method main-search;
define method main (argv0 :: <byte-string>, #rest string)
if (empty?(string)) useage();
else main-search(as-lowercase(string[0]));
end if;
end method main;