Acme-Wabby
view release on metacpan or search on metacpan
# Arguments: Takes a scalar containing text to be added. Embedded newlines,
# random crap, et al are fine, they'll just be stripped out anyway.
# Returns: undef on failure, true on success. The only failure condition is
# currently if an invalid parameter is passed in.
sub add {
my $self = shift;
die "Invalid object" unless (ref($self) eq __PACKAGE__);
# Make sure we actually got something to add
my $text = shift;
unless ($text) {
return undef;
}
# If we don't care about case, lowercase the whole thing to start with
unless ($self->{'conf'}{'case_sensitive'}) {
$text = lc($text);
}
# Split the text into component phrases, which we define as being delimited
# by the characters below. I left the comma out because it seems to lead
# to slightly more coherent results.
my @phrases = split /[.!?;]/, $text;
foreach my $phrase (@phrases) {
# First, strip out any characters we don't want to deal with. We
# replace them with a space so that things like "the+dog" gets treated
# as "the dog".
$phrase =~ s/[^-a-zA-Z0-9 ']/ /g;
# Trim leading and trailing whitespace, and see if we still have
# anything left.
$phrase =~ s/^\s+//;
$phrase =~ s/\s+$//;
next if $phrase eq "";
my $last_word = 0;
my $idx = 0;
# Split the phrase into component words. We're splitting on simple
# whitespace here.
my @words = split /\s+/, $phrase;
# First we're going to loop through the words and clean them up a bit.
# While we're at it, we're going to find the index of the last real
# word in this phrase.
foreach my $word (@words) {
# Clean up the word a little bit. We allow hyphens and
# apostrophies to occur within words, but not at the beginning
# or ends of words.
$word =~ s/^\s+//;
$word =~ s/\s+$//;
$word =~ s/^-+//g;
$word =~ s/^'+//g;
$word =~ s/-+$//g;
$word =~ s/'+$//g;
# Only allow the single-character words of 'a' and 'I'.
# FIXME - Need to be able to configure this so that persons with
# non-english texts can pick values that make sense.
if (length($word) == 1 && lc($word) ne "i" && lc($word) ne "a") {
$word = "";
$idx++;
next;
}
# If this is a valid word, then mark this as a possible last word.
if ($word ne "") {
$last_word = $idx;
}
$idx++;
}
$idx = 0;
my $new_index = 0;
my $old_index = 0;
# Now we loop through the words, recording the transitions between them.
foreach my $word (@words) {
# Shock shock, we're going to ignore non-existent words.
if ($word eq "") {
$idx++;
next;
}
# If this is a new word that we've never seen before
if (!exists($self->{'data'}{'hash'}{$word})) {
# Add this word to the end of the word list, and to the hash,
# taking care to record its index for the next loop iteration.
$new_index = scalar(@{$self->{'data'}{'list'}});
$self->{'data'}{'hash'}{$word} = $new_index;
push @{$self->{'data'}{'list'}}, {word => $word, num => []};
# Add a transition from the previous word to this word.
push @{${$self->{'data'}{'list'}}[$old_index]{'num'}},
$new_index;
# If this word happens to be the last in the phrase, add a -1
# to its possible transitions so that we have the possibility
# of ending sentences here.
if ($idx == $last_word) {
push @{${$self->{'data'}{'list'}}[$new_index]{'num'}}, -1;
}
}
# If we've seen this word before
else {
# Record the index of this word for the next loop iteration,
# and add a transition from the previous word to this one.
$new_index = $self->{'data'}{'hash'}{$word};
push @{${$self->{'data'}{'list'}}[$old_index]{'num'}},
$new_index;
# If this word happens to be the last in the phrase, add a -1
# to its possible transitions so that we have the possibility
# of ending sentences here.
if ($idx == $last_word) {
push @{${$self->{'data'}{'list'}}[$new_index]{'num'}}, -1;
}
# Use the default options
my $wabby = Acme::Wabby->new;
# Pass in explicit options. (All options below are defaults)
my $wabby = Acme::Wabby->new( min_len => 3, max_len => 30,
punctuation => [".","?","!","..."], case_sensitive => 1,
hash_file => "./wabbyhash.dat", list_file => "./wabbylist.dat",
autosave_on_destroy => 0, max_attempts => 1000 );
# Save the current state to the configured files
$wabby->save;
# Load a saved state from the configured files
$wabby->load;
# Add some text to the current state
$wabby->add($the_complete_works_of_shakespeare);
# Generate a random sentence
print $wabby->spew, "\n";
# Generate a random sentence, beginning with "The"
print $wabby->spew("Romeo and Juliet"), "\n";
# Produce a string containing some info about the current state
print scalar($wabby->stats), "\n";
# Produce a list containing the word count and average connection count
my ($wordcount, $average) = $wabby->stats;
print "Wabby knows $wordcount words, with an average number of"
."connections between each word of $average\n";
=head1 DESCRIPTION
This module is used to create semi-random sentences based on a body of text.
It uses a markov-like method of storing probabilities of word transitions.
It is good for annoying people on IRC, AIM, or other such fun mediums.
Acme::Wabby only provides an object-oriented interface, and exports no
symbols into the caller's namespace. Each object is self-contained, so there
are no issues with creating and using multiple objects from within the same
calling program.
=head2 Creating an object
To begin using Acme::Wabby you must first create a new object:
my $wabby = Acme::Wabby->new(min_len => 3, max_len => 30,
punctuation => [".","?","!","..."], case_sensitive => 1,
hash_file => "./wabbyhash.dat", list_file => "./wabbylist.dat",
autosave_on_destroy => 0, max_attempts => 1000 );
All configuration values passed to the object constructor are optional, and
have sensible defaults. The following is a description of the parameters
and their default values.
=over 8
=item min_len
The minimum length for a generated sentence. (3)
=item max_len
The maximum length for a generated sentence. (30)
=item punctuation
A reference to an array containing possible punctuation with which to end sentences. ([".","?","!","..."])
=item case_sensitive
Whether or not to treat text in a case sensitive manner. (1)
=item hash_file
The file to/from which the hash data will be stored/loaded if requested. ("./wabbyhash.dat")
=item list_file
The file to/from which the list data will be stored/loaded if requested. ("./wabbylist.dat")
=item autosave_on_destroy
Whether or not to automatically save the state upon object destruction. (0)
=item max_attempts
The maximum number of attempts to create a sentence before giving up. (1000)
=back
=head2 Adding text to the state
To have an amusing experience, you will need to feed the object a body of text.
This text can come from virtually any source, although I enjoy using e-Texts
from the good folks at Project Gutenberg (http://promo.net/pg). To add text to
the state, simply call the B<add()> method on the object, passing it a scalar
containing the text.
$wabby->add($complete_works_of_shakespeare);
It is acceptable for the input text to contain embedded newlines or other such
things. It is acceptable to call the B<add()> method many times, and at any
point in the object's life-span. The B<add()> method will return B<undef> upon
error, and true upon success.
=head2 Generating random sentences
Once you have some text loaded into the object, you can generate random
sentences. To do this, we use the B<spew()> method. The B<spew()> method has
two modes of operation: If no argument is given, it will generate and return a
random sentence. If a single string is passed in, it will generate and return
a random sentence beginning with the provided string.
my $random_sentence = $wabby->spew;
my $not_so_random_sentence = $wabby->spew("Romeo and Juliet");
The B<spew()> method will return the generated string, or B<undef> upon error.
There are several error conditions which can occur in the B<spew()> method.
None of them are fatal, but they must be taken into account by the calling
program. They are:
* At least (min_len * 10) words haven't been run through yet. (Must B<add()>
more text before trying again.)
( run in 1.694 second using v1.01-cache-2.11-cpan-9581c071862 )