<commit_before># A hack to find "haikus" in English text. For the purposes of this program
# a "haiku" is one or more complete sentences that, together, can be broken
# into groups of 5, 7, and 5 syllables. Each canididate haiku line, and then
# the entire haiku, has to make it through a few heuristics to filter out
# constructions that are likely to scan awkwardly (like verb phrases split
# across lines). Since this code doesn't really try to understand the texts,
# it might throw away a few legitimate phrases, and it certainly lets through
# some bad ones.
#
# Any improvements would be welcomed.
#
# License:
#
# Copyright (c) 2009, Jonathan Feinberg <jdf@pobox.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
#   1. Redistributions of source code must retain the above copyright notice,
#      this list of conditions and the following disclaimer.
#   2. Redistributions in binary form must reproduce the above copyright notice,
#      this list of conditions and the following disclaimer in the documentation
#      and/or other materials provided with the distribution.
#   3. The name of the author may not be used to endorse or promote products
#      derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
from __future__ import with_statement
import nltk
import re
import cPickle as pickle
import gzip
import os.path
import sys

def file(relpath):
    return os.path.join(os.path.dirname(__file__), relpath)

def read_alternates(which):
    with open(file('data/awkward_%s'%which), 'r') as baddies:
        return '|'.join([e.strip() for e in baddies.readlines() if len(e.strip()) > 0])

single_line_filters = [
                       re.compile(r'^[a-z][^.?!;:]+([.?!;:]+[^.?!;:]+)+$', re.IGNORECASE),
                       re.compile(r'[.?!;:]+\s+[\'"]?[A-Za-z]+(?:\'[a-z]+)?$'),
                       ]
single_line_filters.append(re.compile(r'^(?:%s)\b'%read_alternates('starts')))
single_line_filters.append(re.compile(r'\b(?:%s)$'%read_alternates('ends'), re.IGNORECASE))

first_word_comma = re.compile(r'^\s*\w+,')

with open(file('data/awkward_breaks'), 'r') as breaks:
    alts = '|'.join([r'\b%s\b' % ('\n'.join(e.strip().split())) for e in breaks.readlines() if len(e.strip()) > 0]
                    + ['[^\'".?!;:,]\n[a-z]+(?:\'[a-z]+)?[".?!;:]+.',
                       '"\S+\n\S+"',
                       ])
    break_filter = re.compile(alts, re.IGNORECASE)

# load the syllable-count dictionary
with open(file('cmudict/cmudict.pickle'), 'rb') as p:
    syllables = pickle.load(p)
with open(file('cmudict/custom.dict'), 'r') as p:
    for line in p.xreadlines():
        (word, count) = line.split()
        syllables[word] = int(count)

# Use the NLTK to determine sentence boundaries.
sentence_tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')

number_syllables = (
                #   0  1  2  3  4  5  6  7  8  9
                    2, 1, 1, 1, 1, 1, 1, 2, 1, 1,
                    1, 3, 1, 2, 2, 2, 2, 3, 2, 2,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    )
has_digit = re.compile(r'\d')
ordinal = re.compile(r'^(\d\d?)(?:rd|th|st)$', re.IGNORECASE)
time = re.compile(r'^(\d\d?)[ap]m$', re.IGNORECASE)
too_many_digits = re.compile('\d\d\d')

class Nope(Exception):
    pass

class TooShort(Exception):
    pass

class LineSyllablizer:
    def __init__(self, line, unknown_word_handler=None):
        self.words = line.split()
        self.index = 0
        self.lines = []
        self.unknown_word_handler = unknown_word_handler

    def _count_chunk_syllables(self, chunk):
        if has_digit.search(chunk):
            return number_syllables[int(chunk)]
        else:
            return syllables[chunk]

    def _count_syllables(self, word, splitter=re.compile(r'(?<=\D)(?=\d)|(?<=\d)(?=\D)')):
        "Raises KeyError, Nope"
        if not has_digit.search(word):
            return syllables[word]
        if too_many_digits.search(word):
            raise Nope
        m = time.match(word)
        if m:
            return 2 + number_syllables[int(m.group(1))]
        m = ordinal.match(word)
        if m:
            return number_syllables[int(m.group(1))]
        count = 0
        start = 0
        for m in splitter.finditer(word):
            boundary = m.start()
            count += self._count_chunk_syllables(word[start:boundary])
            start = boundary
        count += self._count_chunk_syllables(word[start:])
        return count

    def clean(self, word, wp=re.compile(r'^[^a-z0-9]*([0-9a-z\+]+(?:\'[a-z]+)?)[^a-z0-9]*$', re.IGNORECASE)):
        m = wp.match(word)
        if not m:
            return None
        return m.group(1).upper()

    def count_syllables(self):
        si = 0
        syllable_count = 0
        try:
            for word in self.words:
                syllable_count += self._count_syllables(self.clean(word))
        except KeyError:
            print "I don't know '%s'"%word
            return -1
        except Nope:
            print "I can't do '%s'"%word
            return -1
        return syllable_count

    def seek(self, n):
        si = self.index
        syllable_count = 0
        try:
            while syllable_count < n:
                word = self.clean(self.words[self.index])
                syllable_count += self._count_syllables(word)
                self.index += 1
        except KeyError:
            if word and self.unknown_word_handler:
                self.unknown_word_handler(word)
            raise Nope
        except IndexError:
            raise TooShort
        if syllable_count > n:
            raise Nope
        line = ' '.join(self.words[si:self.index])
        for f in single_line_filters:
            if f.search(line):
                raise Nope
        self.lines.append(line)

    def seek_eol(self):
        if self.index != len(self.words):
            raise Nope

    def bad_split(self, n):
        return awkward_in_front_without_punct_before.search(self.lines[n]) and not self.lines[n - 1][-1] in (',', ';', '-')

    def find_haiku(self):
        self.seek(5)
        self.seek(7)
        self.seek(5)
        self.seek_eol()
        if first_word_comma.search(self.lines[1]) or first_word_comma.search(self.lines[2]):
            raise Nope
        if break_filter.search('\n'.join(self.lines)):
            raise Nope
        return self.lines

class HaikuFinder:
    def __init__(self, text, unknown_word_handler=None):
        self.lines = sentence_tokenizer.tokenize(text)
        self.unknown_word_handler = unknown_word_handler

    def find_haikus(self):
        haikus = []
        line_index = 0
        line_count = len(self.lines)
        while line_index < line_count:
            offset = 0
            line = ""
            while line_index + offset < line_count:
                line = "%s %s" % (line, self.lines[line_index + offset])
                try:
                    haikus.append(LineSyllablizer(line, self.unknown_word_handler).find_haiku())
                    break
                except Nope:
                    print "Type error from:\n%s" % line
                except Nope:
                    break
                except TooShort:
                    offset += 1
            line_index += 1
        return haikus

    @classmethod
    def add_word(cls, word, syllable_count):
        syllables[word.upper()] = syllable_count

def find_haikus(text,  unknown_word_handler=None):
    return HaikuFinder(text, unknown_word_handler).find_haikus()

def count_syllables(text):
    return LineSyllablizer(text).count_syllables()

<commit_msg>more dbugging<commit_after># A hack to find "haikus" in English text. For the purposes of this program
# a "haiku" is one or more complete sentences that, together, can be broken
# into groups of 5, 7, and 5 syllables. Each canididate haiku line, and then
# the entire haiku, has to make it through a few heuristics to filter out
# constructions that are likely to scan awkwardly (like verb phrases split
# across lines). Since this code doesn't really try to understand the texts,
# it might throw away a few legitimate phrases, and it certainly lets through
# some bad ones.
#
# Any improvements would be welcomed.
#
# License:
#
# Copyright (c) 2009, Jonathan Feinberg <jdf@pobox.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
#   1. Redistributions of source code must retain the above copyright notice,
#      this list of conditions and the following disclaimer.
#   2. Redistributions in binary form must reproduce the above copyright notice,
#      this list of conditions and the following disclaimer in the documentation
#      and/or other materials provided with the distribution.
#   3. The name of the author may not be used to endorse or promote products
#      derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
from __future__ import with_statement
import nltk
import re
import cPickle as pickle
import gzip
import os.path
import sys

def file(relpath):
    return os.path.join(os.path.dirname(__file__), relpath)

def read_alternates(which):
    with open(file('data/awkward_%s'%which), 'r') as baddies:
        return '|'.join([e.strip() for e in baddies.readlines() if len(e.strip()) > 0])

single_line_filters = [
                       re.compile(r'^[a-z][^.?!;:]+([.?!;:]+[^.?!;:]+)+$', re.IGNORECASE),
                       re.compile(r'[.?!;:]+\s+[\'"]?[A-Za-z]+(?:\'[a-z]+)?$'),
                       ]
single_line_filters.append(re.compile(r'^(?:%s)\b'%read_alternates('starts')))
single_line_filters.append(re.compile(r'\b(?:%s)$'%read_alternates('ends'), re.IGNORECASE))

first_word_comma = re.compile(r'^\s*\w+,')

with open(file('data/awkward_breaks'), 'r') as breaks:
    alts = '|'.join([r'\b%s\b' % ('\n'.join(e.strip().split())) for e in breaks.readlines() if len(e.strip()) > 0]
                    + ['[^\'".?!;:,]\n[a-z]+(?:\'[a-z]+)?[".?!;:]+.',
                       '"\S+\n\S+"',
                       ])
    break_filter = re.compile(alts, re.IGNORECASE)

# load the syllable-count dictionary
with open(file('cmudict/cmudict.pickle'), 'rb') as p:
    syllables = pickle.load(p)
with open(file('cmudict/custom.dict'), 'r') as p:
    for line in p.xreadlines():
        (word, count) = line.split()
        syllables[word] = int(count)

# Use the NLTK to determine sentence boundaries.
sentence_tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')

number_syllables = (
                #   0  1  2  3  4  5  6  7  8  9
                    2, 1, 1, 1, 1, 1, 1, 2, 1, 1,
                    1, 3, 1, 2, 2, 2, 2, 3, 2, 2,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    )
has_digit = re.compile(r'\d')
ordinal = re.compile(r'^(\d\d?)(?:rd|th|st)$', re.IGNORECASE)
time = re.compile(r'^(\d\d?)[ap]m$', re.IGNORECASE)
too_many_digits = re.compile('\d\d\d')

class Nope(Exception):
    pass

class TooShort(Exception):
    pass

class LineSyllablizer:
    def __init__(self, line, unknown_word_handler=None):
        self.words = line.split()
        self.index = 0
        self.lines = []
        self.unknown_word_handler = unknown_word_handler

    def _count_chunk_syllables(self, chunk):
        if has_digit.search(chunk):
            return number_syllables[int(chunk)]
        else:
            return syllables[chunk]

    def _count_syllables(self, word, splitter=re.compile(r'(?<=\D)(?=\d)|(?<=\d)(?=\D)')):
        "Raises KeyError, Nope"
        if not has_digit.search(word):
            return syllables[word]
        if too_many_digits.search(word):
            raise Nope
        m = time.match(word)
        if m:
            return 2 + number_syllables[int(m.group(1))]
        m = ordinal.match(word)
        if m:
            return number_syllables[int(m.group(1))]
        count = 0
        start = 0
        for m in splitter.finditer(word):
            boundary = m.start()
            count += self._count_chunk_syllables(word[start:boundary])
            start = boundary
        count += self._count_chunk_syllables(word[start:])
        return count

    def clean(self, word, wp=re.compile(r'^[^a-z0-9]*([0-9a-z\+]+(?:\'[a-z]+)?)[^a-z0-9]*$', re.IGNORECASE)):
        m = wp.match(word)
        if not m:
            return None
        return m.group(1).upper()

    def count_syllables(self):
        si = 0
        syllable_count = 0
        try:
            for word in self.words:
                syllable_count += self._count_syllables(self.clean(word))
        except KeyError:
            print "I don't know '%s'"%word
            return -1
        except Nope:
            print "I can't do '%s'"%word
            return -1
        return syllable_count

    def seek(self, n):
        si = self.index
        syllable_count = 0
        try:
            while syllable_count < n:
                word = self.clean(self.words[self.index])
                syllable_count += self._count_syllables(word)
                self.index += 1
        except KeyError:
            if word and self.unknown_word_handler:
                self.unknown_word_handler(word)
            raise Nope
        except IndexError:
            raise TooShort
        if syllable_count > n:
            raise Nope
        line = ' '.join(self.words[si:self.index])
        for f in single_line_filters:
            if f.search(line):
                raise Nope
        self.lines.append(line)

    def seek_eol(self):
        if self.index != len(self.words):
            raise Nope

    def bad_split(self, n):
        return awkward_in_front_without_punct_before.search(self.lines[n]) and not self.lines[n - 1][-1] in (',', ';', '-')

    def find_haiku(self):
        self.seek(5)
        self.seek(7)
        self.seek(5)
        self.seek_eol()
        if first_word_comma.search(self.lines[1]) or first_word_comma.search(self.lines[2]):
            raise Nope
        if break_filter.search('\n'.join(self.lines)):
            raise Nope
        return self.lines

class HaikuFinder:
    def __init__(self, text, unknown_word_handler=None):
        self.lines = sentence_tokenizer.tokenize(text)
        self.unknown_word_handler = unknown_word_handler

    def find_haikus(self):
        haikus = []
        line_index = 0
        line_count = len(self.lines)
        while line_index < line_count:
            offset = 0
            line = ""
            while line_index + offset < line_count:
                line = "%s %s" % (line, self.lines[line_index + offset])
                try:
                    haikus.append(LineSyllablizer(line, self.unknown_word_handler).find_haiku())
                    break
                except TypeError:
                    print "Type error from:\n%s" % line
                    break
                except Nope:
                    break
                except TooShort:
                    offset += 1
            line_index += 1
        return haikus

    @classmethod
    def add_word(cls, word, syllable_count):
        syllables[word.upper()] = syllable_count

def find_haikus(text,  unknown_word_handler=None):
    return HaikuFinder(text, unknown_word_handler).find_haikus()

def count_syllables(text):
    return LineSyllablizer(text).count_syllables()

<|endoftext|>


<commit_before><commit_msg>Added HalfPlane pattern from Stuart<commit_after><|endoftext|>


<commit_before># A hack to find "haikus" in English text. For the purposes of this program
# a "haiku" is one or more complete sentences that, together, can be broken
# into groups of 5, 7, and 5 syllables. Each canididate haiku line, and then
# the entire haiku, has to make it through a few heuristics to filter out
# constructions that are likely to scan awkwardly (like verb phrases split
# across lines). Since this code doesn't really try to understand the texts,
# it might throw away a few legitimate phrases, and it certainly lets through
# some bad ones.
#
# Any improvements would be welcomed.
#
# License:
#
# Copyright (c) 2009, Jonathan Feinberg <jdf@pobox.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
#   1. Redistributions of source code must retain the above copyright notice,
#      this list of conditions and the following disclaimer.
#   2. Redistributions in binary form must reproduce the above copyright notice,
#      this list of conditions and the following disclaimer in the documentation
#      and/or other materials provided with the distribution.
#   3. The name of the author may not be used to endorse or promote products
#      derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
from __future__ import with_statement
import nltk
import re
import cPickle as pickle
import gzip
import os.path
import sys

def file(relpath):
    return os.path.join(os.path.dirname(__file__), relpath)

def read_alternates(which):
    with open(file('data/awkward_%s'%which), 'r') as baddies:
        return '|'.join([e.strip() for e in baddies.readlines() if len(e.strip()) > 0])

single_line_filters = [
                       re.compile(r'^[a-z][^.?!;:]+([.?!;:]+[^.?!;:]+)+$', re.IGNORECASE),
                       re.compile(r'[.?!;:]+\s+[\'"]?[A-Za-z]+(?:\'[a-z]+)?$'),
                       ]
single_line_filters.append(re.compile(r'^(?:%s)\b'%read_alternates('starts')))
single_line_filters.append(re.compile(r'\b(?:%s)$'%read_alternates('ends'), re.IGNORECASE))

first_word_comma = re.compile(r'^\s*[a-z]\w*,')

with open(file('data/awkward_breaks'), 'r') as breaks:
    alts = '|'.join([r'\b%s\b' % ('\n'.join(e.strip().split())) for e in breaks.readlines() if len(e.strip()) > 0]
                    + ['[^\'".?!;:,]\n[a-z]+(?:\'[a-z]+)?[".?!;:]+.',
                       '"\S+\n\S+"',
                       ])
    break_filter = re.compile(alts, re.IGNORECASE)

# load the syllable-count dictionary
with open(file('cmudict/cmudict.pickle'), 'rb') as p:
    syllables = pickle.load(p)
with open(file('cmudict/custom.dict'), 'r') as p:
    for line in p.xreadlines():
        (word, count) = line.split()
        syllables[word] = int(count)

# Use the NLTK to determine sentence boundaries.
sentence_tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')

number_syllables = (
                #   0  1  2  3  4  5  6  7  8  9
                    2, 1, 1, 1, 1, 1, 1, 2, 1, 1,
                    1, 3, 1, 2, 2, 2, 2, 3, 2, 2,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    3, 4, 4, 4, 4, 4, 4, 5, 4, 4,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    )
has_digit = re.compile(r'\d')
ordinal = re.compile(r'^(\d\d?)(?:rd|th|st)$', re.IGNORECASE)
time = re.compile(r'^(\d\d?)[ap]m$', re.IGNORECASE)
too_many_digits = re.compile('\d\d\d')

class Nope(Exception):
    pass

class TooShort(Exception):
    pass

class LineSyllablizer:
    def __init__(self, line, unknown_word_handler=None):
        self.words = line.split()
        self.index = 0
        self.lines = []
        self.unknown_word_handler = unknown_word_handler

    def _count_chunk_syllables(self, chunk):
        if has_digit.search(chunk):
            return number_syllables[int(chunk)]
        else:
            return syllables[chunk]

    def _count_syllables(self, word, splitter=re.compile(r'(?<=\D)(?=\d)|(?<=\d)(?=\D)')):
        "Raises KeyError, Nope"
        if not word or len(word) == 0:
            return 0

        if '0' == word[0] and len(word) > 1:
            return 1 + self._count_syllables(word[1:])  # oh seven

        if '$' == word[0]:
            return 2 + self._count_syllables(word[1:]) # 13 dollars

        if '-' in word:
            return sum(self._count_syllables(w) for w in word.split('-'))

        if '_' in word:
            return sum(self._count_syllables(w) for w in word.split('_'))

        if not has_digit.search(word):
            return syllables[word]
        if too_many_digits.search(word):
            raise Nope
        m = time.match(word)
        if m:
            return 2 + number_syllables[int(m.group(1))]
        m = ordinal.match(word)
        if m:
            return number_syllables[int(m.group(1))]
        count = 0
        start = 0
        for m in splitter.finditer(word):
            boundary = m.start()
            count += self._count_chunk_syllables(word[start:boundary])
            start = boundary
        count += self._count_chunk_syllables(word[start:])
        return count

    def clean(self, word, wp=re.compile(r'^[^a-z0-9\$]*(\$?[-_0-9a-z\+]+(?:\'[a-z]+)?)[^a-z0-9]*$', re.IGNORECASE)):
        m = wp.match(word)
        if not m:
            return None
        return m.group(1).upper()

    def count_syllables(self):
        si = 0
        syllable_count = 0
        try:
            for word in self.words:
                syllable_count += self._count_syllables(self.clean(word))
        except KeyError:
            print "I don't know '%s'"%word
            return -1
        except Nope:
            print "I can't do '%s'"%word
            return -1
        return syllable_count

    def seek(self, n):
        si = self.index
        syllable_count = 0
        try:
            while syllable_count < n:
                word = self.clean(self.words[self.index])
                syllable_count += self._count_syllables(word)
                self.index += 1
        except KeyError:
            if word and self.unknown_word_handler:
                self.unknown_word_handler(word)
            raise Nope
        except IndexError:
            raise TooShort
        if syllable_count > n:
            raise Nope
        line = ' '.join(self.words[si:self.index])
        for f in single_line_filters:
            if f.search(line):
                raise Nope
        self.lines.append(line)

    def seek_eol(self):
        if self.index != len(self.words):
            raise Nope

    def bad_split(self, n):
        return awkward_in_front_without_punct_before.search(self.lines[n]) and not self.lines[n - 1][-1] in (',', ';', '-')

    def find_haiku(self):
        self.seek(5)
        self.seek(7)
        self.seek(5)
        self.seek_eol()
        if first_word_comma.search(self.lines[1]) or first_word_comma.search(self.lines[2]):
            raise Nope
        if break_filter.search('\n'.join(self.lines)):
            raise Nope
        return self.lines

class HaikuFinder:
    def __init__(self, text, unknown_word_handler=None):
        self.lines = sentence_tokenizer.tokenize(text)
        self.unknown_word_handler = unknown_word_handler

    def find_haikus(self):
        haikus = []
        line_index = 0
        line_count = len(self.lines)
        while line_index < line_count:
            offset = 0
            line = ""
            while line_index + offset < line_count:
                line = "%s %s" % (line, self.lines[line_index + offset])
                try:
                    haikus.append(LineSyllablizer(line, self.unknown_word_handler).find_haiku())
                    break
                except Nope:
                    break
                except TooShort:
                    offset += 1
            line_index += 1
        return haikus

    @classmethod
    def add_word(cls, word, syllable_count):
        syllables[word.upper()] = syllable_count

def find_haikus(text,  unknown_word_handler=None):
    return HaikuFinder(text, unknown_word_handler).find_haikus()

def count_syllables(text):
    return LineSyllablizer(text).count_syllables()

<commit_msg>ampersand<commit_after># A hack to find "haikus" in English text. For the purposes of this program
# a "haiku" is one or more complete sentences that, together, can be broken
# into groups of 5, 7, and 5 syllables. Each canididate haiku line, and then
# the entire haiku, has to make it through a few heuristics to filter out
# constructions that are likely to scan awkwardly (like verb phrases split
# across lines). Since this code doesn't really try to understand the texts,
# it might throw away a few legitimate phrases, and it certainly lets through
# some bad ones.
#
# Any improvements would be welcomed.
#
# License:
#
# Copyright (c) 2009, Jonathan Feinberg <jdf@pobox.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
#   1. Redistributions of source code must retain the above copyright notice,
#      this list of conditions and the following disclaimer.
#   2. Redistributions in binary form must reproduce the above copyright notice,
#      this list of conditions and the following disclaimer in the documentation
#      and/or other materials provided with the distribution.
#   3. The name of the author may not be used to endorse or promote products
#      derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
from __future__ import with_statement
import nltk
import re
import cPickle as pickle
import gzip
import os.path
import sys

def file(relpath):
    return os.path.join(os.path.dirname(__file__), relpath)

def read_alternates(which):
    with open(file('data/awkward_%s'%which), 'r') as baddies:
        return '|'.join([e.strip() for e in baddies.readlines() if len(e.strip()) > 0])

single_line_filters = [
                       re.compile(r'^[a-z][^.?!;:]+([.?!;:]+[^.?!;:]+)+$', re.IGNORECASE),
                       re.compile(r'[.?!;:]+\s+[\'"]?[A-Za-z]+(?:\'[a-z]+)?$'),
                       ]
single_line_filters.append(re.compile(r'^(?:%s)\b'%read_alternates('starts')))
single_line_filters.append(re.compile(r'\b(?:%s)$'%read_alternates('ends'), re.IGNORECASE))

first_word_comma = re.compile(r'^\s*[a-z]\w*,')

with open(file('data/awkward_breaks'), 'r') as breaks:
    alts = '|'.join([r'\b%s\b' % ('\n'.join(e.strip().split())) for e in breaks.readlines() if len(e.strip()) > 0]
                    + ['[^\'".?!;:,]\n[a-z]+(?:\'[a-z]+)?[".?!;:]+.',
                       '"\S+\n\S+"',
                       ])
    break_filter = re.compile(alts, re.IGNORECASE)

# load the syllable-count dictionary
with open(file('cmudict/cmudict.pickle'), 'rb') as p:
    syllables = pickle.load(p)
with open(file('cmudict/custom.dict'), 'r') as p:
    for line in p.xreadlines():
        (word, count) = line.split()
        syllables[word] = int(count)

# Use the NLTK to determine sentence boundaries.
sentence_tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')

number_syllables = (
                #   0  1  2  3  4  5  6  7  8  9
                    2, 1, 1, 1, 1, 1, 1, 2, 1, 1,
                    1, 3, 1, 2, 2, 2, 2, 3, 2, 2,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    3, 4, 4, 4, 4, 4, 4, 5, 4, 4,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    2, 3, 3, 3, 3, 3, 3, 4, 3, 3,
                    )
has_digit = re.compile(r'\d')
ordinal = re.compile(r'^(\d\d?)(?:rd|th|st)$', re.IGNORECASE)
time = re.compile(r'^(\d\d?)[ap]m$', re.IGNORECASE)
too_many_digits = re.compile('\d\d\d')

class Nope(Exception):
    pass

class TooShort(Exception):
    pass

class LineSyllablizer:
    def __init__(self, line, unknown_word_handler=None):
        self.words = line.split()
        self.index = 0
        self.lines = []
        self.unknown_word_handler = unknown_word_handler

    def _count_chunk_syllables(self, chunk):
        if has_digit.search(chunk):
            return number_syllables[int(chunk)]
        else:
            return syllables[chunk]

    def _count_syllables(self, word, splitter=re.compile(r'(?<=\D)(?=\d)|(?<=\d)(?=\D)')):
        "Raises KeyError, Nope"
        if not word or len(word) == 0:
            return 0

        if '0' == word[0] and len(word) > 1:
            return 1 + self._count_syllables(word[1:])  # oh seven

        if '$' == word[0]:
            return 2 + self._count_syllables(word[1:]) # 13 dollars

        if '&' in word and len(word) > 1:
            return 1 + sum(self._count_syllables(w) for w in word.split('&'))

        if '-' in word:
            return sum(self._count_syllables(w) for w in word.split('-'))

        if '_' in word:
            return sum(self._count_syllables(w) for w in word.split('_'))

        if not has_digit.search(word):
            return syllables[word]
        if too_many_digits.search(word):
            raise Nope
        m = time.match(word)
        if m:
            return 2 + number_syllables[int(m.group(1))]
        m = ordinal.match(word)
        if m:
            return number_syllables[int(m.group(1))]
        count = 0
        start = 0
        for m in splitter.finditer(word):
            boundary = m.start()
            count += self._count_chunk_syllables(word[start:boundary])
            start = boundary
        count += self._count_chunk_syllables(word[start:])
        return count

    def clean(self, word, wp=re.compile(r'^[^a-z0-9\$]*(\$?[-_0-9a-z\+]+(?:&\'[a-z]+)?)[^a-z0-9]*$', re.IGNORECASE)):
        m = wp.match(word)
        if not m:
            return None
        return m.group(1).upper()

    def count_syllables(self):
        si = 0
        syllable_count = 0
        try:
            for word in self.words:
                syllable_count += self._count_syllables(self.clean(word))
        except KeyError:
            print "I don't know '%s'"%word
            return -1
        except Nope:
            print "I can't do '%s'"%word
            return -1
        return syllable_count

    def seek(self, n):
        si = self.index
        syllable_count = 0
        try:
            while syllable_count < n:
                word = self.clean(self.words[self.index])
                syllable_count += self._count_syllables(word)
                self.index += 1
        except KeyError:
            if word and self.unknown_word_handler:
                self.unknown_word_handler(word)
            raise Nope
        except IndexError:
            raise TooShort
        if syllable_count > n:
            raise Nope
        line = ' '.join(self.words[si:self.index])
        for f in single_line_filters:
            if f.search(line):
                raise Nope
        self.lines.append(line)

    def seek_eol(self):
        if self.index != len(self.words):
            raise Nope

    def bad_split(self, n):
        return awkward_in_front_without_punct_before.search(self.lines[n]) and not self.lines[n - 1][-1] in (',', ';', '-')

    def find_haiku(self):
        self.seek(5)
        self.seek(7)
        self.seek(5)
        self.seek_eol()
        if first_word_comma.search(self.lines[1]) or first_word_comma.search(self.lines[2]):
            raise Nope
        if break_filter.search('\n'.join(self.lines)):
            raise Nope
        return self.lines

class HaikuFinder:
    def __init__(self, text, unknown_word_handler=None):
        self.lines = sentence_tokenizer.tokenize(text)
        self.unknown_word_handler = unknown_word_handler

    def find_haikus(self):
        haikus = []
        line_index = 0
        line_count = len(self.lines)
        while line_index < line_count:
            offset = 0
            line = ""
            while line_index + offset < line_count:
                line = "%s %s" % (line, self.lines[line_index + offset])
                try:
                    haikus.append(LineSyllablizer(line, self.unknown_word_handler).find_haiku())
                    break
                except Nope:
                    break
                except TooShort:
                    offset += 1
            line_index += 1
        return haikus

    @classmethod
    def add_word(cls, word, syllable_count):
        syllables[word.upper()] = syllable_count

def find_haikus(text,  unknown_word_handler=None):
    return HaikuFinder(text, unknown_word_handler).find_haikus()

def count_syllables(text):
    return LineSyllablizer(text).count_syllables()

<|endoftext|>


<commit_before><commit_msg>Fixed bugs in sending and receiving data.<commit_after><|endoftext|>


<commit_before>import argparse, logging, codecs
from translation_model import TranslationModel
from collections import OrderedDict
import commands

SVM_RANK_MODEL_SUFFIX='.svm_rank.model'

'''
1) Translate Source lines as per model
2) Replace UNK symbols as per .nounk file
3) Re-order translations based on BLEU score with gold
4) Write train data for svm-rank, train svm rank
5) In test only model, make predictions
'''

def get_bleu_score(gold, prediction):
    fw_gold = codecs.open('temp.gold.txt', 'w', 'utf-8')
    fw_gold.write(gold)
    fw_gold.close()

    fw_hyp = codecs.open('temp.hyp.txt', 'w', 'utf-8')
    fw_hyp.write(prediction)
    fw_hyp.close()

    cmd_bleu = "./multi-bleu.perl temp.gold.txt < temp.hyp.txt | tail -1 | cut -d ',' -f 1 | cut -d '=' -f 2"
    logging.info('Executing cmd:%s'% cmd_bleu)
    (status, output) = commands.getstatusoutput(cmd_bleu)
    logging.info('Status: %s Output:%s'%(status, output))

    try:
      bleu = float(output)
    except ValueError:
      bleu = 0.0
    return bleu

def setup_args():
    parser = argparse.ArgumentParser()
    parser.add_argument('source', help='Source Data')
    parser.add_argument('gold', help='Gold Translations')
    parser.add_argument('model', help='Trained Model')
    parser.add_argument('--suffix', help='Original file without any symbols', default='.nounk')
    parser.add_argument('--test', dest='test', action='store_true', help='Only test')
    parser.add_argument('--num', default=20, type=int)
    args = parser.parse_args()
    return args

def build_unk_map(source, source_nounk):
    tokens = source.split()
    tokens_nounk = source_nounk.split()
    assert len(tokens) == len(tokens_nounk)

    unk_map = OrderedDict()
    for token, token_nounk in zip(tokens, tokens_nounk):
        if token != token_nounk:
            unk_map[token] = token_nounk

    return unk_map

def replace_symbols(line, unk_map):
    tokens = line.split()
    new_tokens = []

    for token in tokens:
        if token in unk_map:
            new_tokens.append(unk_map[token])
        else:
            new_tokens.append(token)
    return ' '.join(new_tokens)

'''
Source: Source with UNK Symbols
Target: Target with no UNK Symbols
'''
def main():
    args = setup_args()
    logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
    logging.info(args)

    src_lines = codecs.open(args.source, 'r', 'utf-8').readlines()
    src_lines_nounk = codecs.open(args.source + args.suffix, 'r', 'utf-8').readlines()
    gold_lines = codecs.open(args.gold + args.suffix, 'r', 'utf-8').readlines()

    tm = TranslationModel(args.model)
    num_all_zeros = 0

    for sentence_idx, (src_line, src_line_nounk, gold_line) in enumerate(zip(src_lines, src_lines_nounk, gold_lines)):
        translations = tm.translate(src_line, k=args.num)
        logging.info('Source_line: %s'% src_line_nounk)
        logging.info('Gold_line: %s' % gold_line)

        unk_map = build_unk_map(src_line, src_line_nounk)
        logging.info('UNK_map: %s'% str(unk_map))

        scores = []
        for idx, translation in enumerate(translations):
            translation_nounk = replace_symbols(translation[1], unk_map)
            bleu_nounk = get_bleu_score(gold_line, translation_nounk)
            scores.append(bleu_nounk)
            #logging.info('Tr:%d ::%s BLEU:%s'%(idx, translation_nounk, bleu_nounk))

        if sum(scores) == 0.0:
            num_all_zeros += 1
            continue

        scores_index = sorted(range(len(scores)), key=lambda k: scores[k], reverse=True)
        for index in scores_index:
            logging.info('Tr: %d Text:%s Pr:%f BLEU:%f'%(index, translations[index][0],
                                                              translations[index][1], scores[index]))
    logging.info('Num all zeros: %d'%num_all_zeros)

if __name__ == '__main__':
    main()
<commit_msg>fix typo<commit_after>import argparse, logging, codecs
from translation_model import TranslationModel
from collections import OrderedDict
import commands

SVM_RANK_MODEL_SUFFIX='.svm_rank.model'

'''
1) Translate Source lines as per model
2) Replace UNK symbols as per .nounk file
3) Re-order translations based on BLEU score with gold
4) Write train data for svm-rank, train svm rank
5) In test only model, make predictions
'''

def get_bleu_score(gold, prediction):
    fw_gold = codecs.open('temp.gold.txt', 'w', 'utf-8')
    fw_gold.write(gold)
    fw_gold.close()

    fw_hyp = codecs.open('temp.hyp.txt', 'w', 'utf-8')
    fw_hyp.write(prediction)
    fw_hyp.close()

    cmd_bleu = "./multi-bleu.perl temp.gold.txt < temp.hyp.txt | tail -1 | cut -d ',' -f 1 | cut -d '=' -f 2"
    logging.info('Executing cmd:%s'% cmd_bleu)
    (status, output) = commands.getstatusoutput(cmd_bleu)
    logging.info('Status: %s Output:%s'%(status, output))

    try:
      bleu = float(output)
    except ValueError:
      bleu = 0.0
    return bleu

def setup_args():
    parser = argparse.ArgumentParser()
    parser.add_argument('source', help='Source Data')
    parser.add_argument('gold', help='Gold Translations')
    parser.add_argument('model', help='Trained Model')
    parser.add_argument('--suffix', help='Original file without any symbols', default='.nounk')
    parser.add_argument('--test', dest='test', action='store_true', help='Only test')
    parser.add_argument('--num', default=20, type=int)
    args = parser.parse_args()
    return args

def build_unk_map(source, source_nounk):
    tokens = source.split()
    tokens_nounk = source_nounk.split()
    assert len(tokens) == len(tokens_nounk)

    unk_map = OrderedDict()
    for token, token_nounk in zip(tokens, tokens_nounk):
        if token != token_nounk:
            unk_map[token] = token_nounk

    return unk_map

def replace_symbols(line, unk_map):
    tokens = line.split()
    new_tokens = []

    for token in tokens:
        if token in unk_map:
            new_tokens.append(unk_map[token])
        else:
            new_tokens.append(token)
    return ' '.join(new_tokens)

'''
Source: Source with UNK Symbols
Target: Target with no UNK Symbols
'''
def main():
    args = setup_args()
    logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
    logging.info(args)

    src_lines = codecs.open(args.source, 'r', 'utf-8').readlines()
    src_lines_nounk = codecs.open(args.source + args.suffix, 'r', 'utf-8').readlines()
    gold_lines = codecs.open(args.gold + args.suffix, 'r', 'utf-8').readlines()

    tm = TranslationModel(args.model)
    num_all_zeros = 0

    for sentence_idx, (src_line, src_line_nounk, gold_line) in enumerate(zip(src_lines, src_lines_nounk, gold_lines)):
        translations = tm.translate(src_line, k=args.num)
        logging.info('Source_line: %s'% src_line_nounk)
        logging.info('Gold_line: %s' % gold_line)

        unk_map = build_unk_map(src_line, src_line_nounk)
        logging.info('UNK_map: %s'% str(unk_map))

        scores = []
        for idx, translation in enumerate(translations):
            translation_nounk = replace_symbols(translation[1], unk_map)
            bleu_nounk = get_bleu_score(gold_line, translation_nounk)
            scores.append(bleu_nounk)
            #logging.info('Tr:%d ::%s BLEU:%s'%(idx, translation_nounk, bleu_nounk))

        if sum(scores) == 0.0:
            num_all_zeros += 1
            continue

        scores_index = sorted(range(len(scores)), key=lambda k: scores[k], reverse=True)
        for index in scores_index:
            logging.info('Tr: %d Text:%s Pr:%f BLEU:%f'%(index, translations[index][1],
                                                              translations[index][0], scores[index]))
    logging.info('Num all zeros: %d'%num_all_zeros)

if __name__ == '__main__':
    main()
<|endoftext|>


<commit_before>"""
PatternGenerators based on bitmap images stored in files.

$Id$
"""

# PIL Image is imported as PIL because we have our own Image PatternGenerator
import Image as PIL
import ImageOps

from numpy.oldnumeric import array, Float, sum, ravel, ones

from .. import param

from topo.base.boundingregion import BoundingBox
from topo.base.patterngenerator import PatternGenerator
from topo.base.sheetcoords import SheetCoordinateSystem
from topo.transferfn.basic import DivisiveNormalizeLinf,TransferFn
from topo.misc.filepath import Filename

class PatternSampler(param.Parameterized):
    """
    Stores a SheetCoordinateSystem whose activity represents the
    supplied pattern_array, and when called will resample that array
    at the supplied Sheet coordinates according to the supplied
    scaling parameters.

    (x,y) coordinates outside the pattern_array are returned as the
    background value.
    """

    def __init__(self, pattern_array=None, image=None, whole_pattern_output_fns=None, background_value_fn=None):
        """
        Create a SheetCoordinateSystem whose activity is pattern_array
        (where pattern_array is a NumPy array), modified in place by
        whole_pattern_output_fn.

        If supplied, background_value_fn must accept an array and return a scalar.
        """
        if whole_pattern_output_fns is None:
            whole_pattern_output_fns = []

        super(PatternSampler,self).__init__()

        if pattern_array is not None and image is not None:
            raise ValueError("PatternSampler instances can have a pattern or an image, but not both.")
        elif pattern_array is not None:
            pass
        elif image is not None:
            pattern_array = array(image,Float)
        else:
            raise ValueError("PatternSampler instances must have a pattern or an image.")

        rows,cols = pattern_array.shape

        self.scs = SheetCoordinateSystem(xdensity=1.0,ydensity=1.0,
            bounds=BoundingBox(points=((-cols/2.0,-rows/2.0),
                                       ( cols/2.0, rows/2.0))))

        for wpof in whole_pattern_output_fns:
            wpof(pattern_array)

        self.scs.activity = pattern_array

        if not background_value_fn:
            self.background_value = 0.0
        else:
            self.background_value = background_value_fn(self.scs.activity)

    def __call__(self, x, y, sheet_xdensity, sheet_ydensity, scaling, width=1.0, height=1.0):
        """
        Return pixels from the pattern at the given Sheet (x,y) coordinates.

        sheet_density should be the density of the sheet on which the pattern
        is to be drawn.

        scaling determines how the pattern is scaled initially; it can be:

        'stretch_to_fit': scale both dimensions of the pattern so they
        would fill a Sheet with bounds=BoundingBox(radius=0.5)
        (disregards the original's aspect ratio).

        'fit_shortest': scale the pattern so that its shortest
        dimension is made to fill the corresponding dimension on a
        Sheet with bounds=BoundingBox(radius=0.5) (maintains the
        original's aspect ratio).

        'fit_longest': scale the pattern so that its longest dimension
        is made to fill the corresponding dimension on a Sheet with
        bounds=BoundingBox(radius=0.5) (maintains the original's
        aspect ratio).

        'original': no scaling is applied; one pixel of the pattern is
        put in one unit of the sheet on which the pattern being
        displayed.

        The pattern is further scaled according to the supplied width and height.
        """

        # create new pattern sample, filled initially with the background value
        pattern_sample = ones(x.shape, Float)*self.background_value

        # if the height or width is zero, there's no pattern to display...
        if width==0 or height==0:
            return pattern_sample

        # scale the supplied coordinates to match the pattern being at density=1
        x*=sheet_xdensity
        y*=sheet_ydensity

        # scale according to initial pattern scaling selected (size_normalization)
        if not scaling=='original':
            self.__apply_size_normalization(x,y,sheet_xdensity,sheet_ydensity,scaling)

        # scale according to user-specified width and height
        x/=width
        y/=height

        # convert the sheet (x,y) coordinates to matrixidx (r,c) ones
        r,c = self.scs.sheet2matrixidx(x,y)

        # now sample pattern at the (r,c) corresponding to the supplied (x,y)
        pattern_rows,pattern_cols = self.scs.activity.shape
        if pattern_rows==0 or pattern_cols==0:
            return pattern_sample
        else:
            # CEBALERT: is there a more NumPy way to do this that would be faster?
            rows,cols = pattern_sample.shape
            for i in xrange(rows):
                for j in xrange(cols):
                    # indexes outside the pattern are left with the background color
                    if self.scs.bounds.contains_exclusive(x[i,j],y[i,j]):
                        pattern_sample[i,j] = self.scs.activity[r[i,j],c[i,j]]

        return pattern_sample

    def __apply_size_normalization(self,x,y,sheet_xdensity,sheet_ydensity,scaling):
        """
        Initial pattern scaling (size_normalization), relative to the
        default retinal dimension of 1.0 in sheet coordinates.

        See __call__ for a description of the various scaling options.
        """
        pattern_rows,pattern_cols = self.scs.activity.shape

        # Instead of an if-test, could have a class of this type of
        # function (c.f. OutputFunctions, etc)...
        if scaling=='stretch_to_fit':
            x_sf,y_sf = pattern_cols/sheet_xdensity, pattern_rows/sheet_ydensity
            x*=x_sf; y*=y_sf

        elif scaling=='fit_shortest':
            if pattern_rows<pattern_cols:
                sf = pattern_rows/sheet_ydensity
            else:
                sf = pattern_cols/sheet_xdensity
            x*=sf;y*=sf

        elif scaling=='fit_longest':
            if pattern_rows<pattern_cols:
                sf = pattern_cols/sheet_xdensity
            else:
                sf = pattern_rows/sheet_ydensity
            x*=sf;y*=sf

        else:
            raise ValueError("Unknown scaling option",scaling)

from numpy.oldnumeric import sum,ravel
def edge_average(a):
    "Return the mean value around the edge of an array."

    if len(ravel(a)) < 2:
        return float(a[0])
    else:
        top_edge = a[0]
        bottom_edge = a[-1]
        left_edge = a[1:-1,0]
        right_edge = a[1:-1,-1]

        edge_sum = sum(top_edge) + sum(bottom_edge) + sum(left_edge) + sum(right_edge)
        num_values = len(top_edge)+len(bottom_edge)+len(left_edge)+len(right_edge)

        return float(edge_sum)/num_values

class FastPatternSampler(param.Parameterized):
    """
    A fast-n-dirty pattern sampler using Python Imaging Library
    routines.  Currently this sampler doesn't support user-specified
    scaling or cropping but rather simply scales and crops the image
    to fit the given matrix size without distorting the aspect ratio
    of the original picture.
    """

    sampling_method = param.Integer(default=PIL.NEAREST,doc="""
       Python Imaging Library sampling method for resampling an image.
       Defaults to Image.NEAREST.""")

    def __init__(self, pattern=None, image=None, whole_pattern_output_fns=None, background_value_fn=None):
        super(FastPatternSampler,self).__init__()

        if pattern and image:
            raise ValueError("PatternSampler instances can have a pattern or an image, but not both.")
        elif pattern is not None:
            self.image = PIL.new('L',pattern.shape)
            self.image.putdata(pattern.ravel())
        elif image is not None:
            self.image = image
        else:
            raise ValueError("PatternSampler instances must have a pattern or an image.")

    def __call__(self, x, y, sheet_xdensity, sheet_ydensity, scaling, width=1.0, height=1.0):

        # JPALERT: Right now this ignores all options and just fits the image into given array.
        # It needs to be fleshed out to properly size and crop the
        # image given the options. (maybe this class needs to be
        # redesigned?  The interface to this function is pretty inscrutable.)

        im = ImageOps.fit(self.image,x.shape,self.sampling_method)
        return array(im,dtype=Float)

# Would be best called Image, but that causes confusion with PIL's Image
class GenericImage(PatternGenerator):
    """
    Generic 2D image generator.

    Generates a pattern from a Python Imaging Library image object.
    Subclasses should override the _get_image method to produce the
    image object.

    The background value is calculated as an edge average: see edge_average().
    Black-bordered images therefore have a black background, and
    white-bordered images have a white background. Images with no
    border have a background that is less of a contrast than a white
    or black one.

    At present, rotation, scaling, etc. just resample; it would be nice
    to support some interpolation options as well.
    """

    __abstract = True

    aspect_ratio  = param.Number(default=1.0,bounds=(0.0,None),
        softbounds=(0.0,2.0),precedence=0.31,doc="""
        Ratio of width to height; size*aspect_ratio gives the width.""")

    size  = param.Number(default=1.0,bounds=(0.0,None),softbounds=(0.0,2.0),
                   precedence=0.30,doc="Height of the image.")

    size_normalization = param.Enumeration(default='fit_shortest',
        available=['fit_shortest','fit_longest','stretch_to_fit','original'],
        precedence=0.95,doc="""
        How to scale the initial image size relative to the default area of 1.0.""")

    whole_image_output_fns = param.HookList(default=[DivisiveNormalizeLinf()],
        class_=TransferFn,precedence=0.96,doc="""
        Function(s) applied to the whole, original image array (before any cropping).""")

    # CB: I guess it's a type rather than an instance because of the
    # way PatternSampler is written (requiring values of many
    # parameters on initialization).
    pattern_sampler_type = param.Parameter(default=PatternSampler, doc="""
        The type of PatternSampler to use to resample/resize the image.""")

    cache_image = param.Boolean(default=True,doc="""
        If False, discards the image after drawing the pattern each time,
        to make it possible to use very large databases of images without
        running out of memory.""")

    def function(self,params):
        xdensity = params['xdensity']
        ydensity = params['ydensity']
        x        = params['pattern_x']
        y        = params['pattern_y']
        size_normalization = params.get('scaling') or self.size_normalization  #params.get('scaling',self.size_normalization)

        height = params['size']
        width = params['aspect_ratio']*height

        whole_image_output_fns = params['whole_image_output_fns']

        if self._get_image(params) or whole_image_output_fns != self.last_wiofs:
            self.last_wiofs = whole_image_output_fns

            self.ps=self.pattern_sampler_type(image=self._image,
                                              whole_pattern_output_fns=whole_image_output_fns,
                                              background_value_fn=edge_average)

        result = self.ps(x,y,float(xdensity),float(ydensity),size_normalization,float(width),float(height))

        if not self.cache_image:
            del self.ps     ; self.ps=None
            del self._image ; self._image=None

        return result

    def _get_image(self,params):
        """
        Get a new image, if necessary.

        If necessary as indicated by the parameters, get a new image,
        assign it to self._image and return True.  If no new image is
        needed, return False.
        """

        raise NotImplementedError

    ### support pickling of PIL.Image

    # CEBALERT: almost identical code to that in topo.plotting.bitmap.Bitmap...
    # CEB: by converting to string and back, we probably incur some speed
    # penalty on copy()ing GenericImages (since __getstate__ and __setstate__ are
    # used for copying, unless __copy__ and __deepcopy__ are defined instead).
    def __getstate__(self):
        """
        Return the object's state (as in the superclass), but replace
        the '_image' attribute's Image with a string representation.
        """
        state = super(GenericImage,self).__getstate__()

        if '_image' in state and state['_image'] is not None:
            import StringIO
            f = StringIO.StringIO()
            image = state['_image']
            image.save(f,format=image.format or 'TIFF') # format could be None (we should probably just not save in that case)
            state['_image'] = f.getvalue()
            f.close()

        return state

    def __setstate__(self,state):
        """
        Load the object's state (as in the superclass), but replace
        the '_image' string with an actual Image object.
        """
        # CEBALERT: Need to figure out how state['_image'] could ever
        # actually be None; apparently it is sometimes (see SF
        # #2276819).
        if '_image' in state and state['_image'] is not None:
            import StringIO
            state['_image'] = PIL.open(StringIO.StringIO(state['_image']))
        super(GenericImage,self).__setstate__(state)

class FileImage(GenericImage):
    """
    2D Image generator that reads the image from a file.

    The image at the supplied filename is converted to grayscale if it
    is not already a grayscale image. See PIL's Image class for
    details of supported image file formats.
    """

    filename = Filename(default='images/ellen_arthur.pgm',precedence=0.9,doc="""
        File path (can be relative to Topographica's base path) to a bitmap image.
        The image can be in any format accepted by PIL, e.g. PNG, JPG, TIFF, or PGM.
        """)

    def __init__(self, **params):
        """
        Create the last_filename and last_wiof attributes, used to hold
        the last filename and last whole_image_output_function.

        This allows reloading an existing image to be avoided.
        """
        super(FileImage,self).__init__(**params)
        self.last_filename = None
        self.last_wiofs = None

    def _get_image(self,params):
        filename = params['filename']

        if filename!=self.last_filename or self._image is None:
            self.last_filename=filename
            self._image = ImageOps.grayscale(PIL.open(self.filename))
            return True
        else:
            return False

# PICKLEHACK (move to legacy)
# Temporary as of 12/2007, for backwards compatibility
Image=FileImage

<commit_msg>Changed some of PatternSampler's various method arguments to be parameters. Should be no visible change.<commit_after>"""
PatternGenerators based on bitmap images stored in files.

$Id$
"""

# PIL Image is imported as PIL because we have our own Image PatternGenerator
import Image as PIL
import ImageOps

from numpy.oldnumeric import array, Float, sum, ravel, ones

from .. import param
from topo.param.parameterized import ParamOverrides

from topo.base.boundingregion import BoundingBox
from topo.base.patterngenerator import PatternGenerator
from topo.base.sheetcoords import SheetCoordinateSystem
from topo.transferfn.basic import DivisiveNormalizeLinf,TransferFn
from topo.misc.filepath import Filename

class PatternSampler(param.Parameterized):
    """
    Stores a SheetCoordinateSystem whose activity represents the
    supplied pattern_array, and when called will resample that array
    at the supplied Sheet coordinates according to the supplied
    scaling parameters.

    (x,y) coordinates outside the pattern_array are returned as the
    background value.
    """

    whole_pattern_output_fns = param.List(class_=TransferFn,default=[],doc="""
    Functions to apply to the whole image before any sampling is done.""")

    background_value_fn = param.Callable(default=None,doc="""
    Function to compute an appropriate background value. Must accept
    an array and return a scalar.""")

    scaling = param.Enumeration(
        default='original',
        available=['original','stretch_to_fit','fit_shortest','fit_longest'],
        doc="""
    Determines how the pattern is scaled initially, relative to the
    default retinal dimension of 1.0 in sheet coordinates:

    'stretch_to_fit': scale both dimensions of the pattern so they
    would fill a Sheet with bounds=BoundingBox(radius=0.5) (disregards
    the original's aspect ratio).

    'fit_shortest': scale the pattern so that its shortest dimension
    is made to fill the corresponding dimension on a Sheet with
    bounds=BoundingBox(radius=0.5) (maintains the original's aspect
    ratio).

    'fit_longest': scale the pattern so that its longest dimension is
    made to fill the corresponding dimension on a Sheet with
    bounds=BoundingBox(radius=0.5) (maintains the original's aspect
    ratio).

    'original': no scaling is applied; one pixel of the pattern is put
    in one unit of the sheet on which the pattern being displayed.""")

    def __init__(self, pattern_array=None, image=None, **params):
        """
        Create a SheetCoordinateSystem whose activity is pattern_array
        (where pattern_array is a NumPy array).
        """
        super(PatternSampler,self).__init__(**params)

        if pattern_array is not None and image is not None:
            raise ValueError("PatternSampler instances can have a pattern or an image, but not both.")
        elif pattern_array is not None:
            pass
        elif image is not None:
            pattern_array = array(image,Float)
        else:
            raise ValueError("PatternSampler instances must have a pattern or an image.")

        rows,cols = pattern_array.shape

        self.scs = SheetCoordinateSystem(xdensity=1.0,ydensity=1.0,
            bounds=BoundingBox(points=((-cols/2.0,-rows/2.0),
                                       ( cols/2.0, rows/2.0))))

        for wpof in self.whole_pattern_output_fns:
            wpof(pattern_array)

        self.scs.activity = pattern_array

        if not self.background_value_fn:
            self.background_value = 0.0
        else:
            self.background_value = self.background_value_fn(self.scs.activity)

    def __call__(self, x, y, sheet_xdensity, sheet_ydensity, width=1.0, height=1.0, **params):
        """
        Return pixels from the pattern at the given Sheet (x,y) coordinates.

        sheet_density should be the density of the sheet on which the pattern
        is to be drawn.

        The pattern is further scaled according to the supplied width and height.
        """
        p=ParamOverrides(self,params)

        # create new pattern sample, filled initially with the background value
        pattern_sample = ones(x.shape, Float)*p.background_value

        # if the height or width is zero, there's no pattern to display...
        if width==0 or height==0:
            return pattern_sample

        # scale the supplied coordinates to match the pattern being at density=1
        x*=sheet_xdensity
        y*=sheet_ydensity

        # scale according to initial pattern scaling selected (size_normalization)
        self.__apply_size_normalization(x,y,sheet_xdensity,sheet_ydensity,p.scaling)

        # scale according to user-specified width and height
        x/=width
        y/=height

        # convert the sheet (x,y) coordinates to matrixidx (r,c) ones
        r,c = self.scs.sheet2matrixidx(x,y)

        # now sample pattern at the (r,c) corresponding to the supplied (x,y)
        pattern_rows,pattern_cols = self.scs.activity.shape
        if pattern_rows==0 or pattern_cols==0:
            return pattern_sample
        else:
            # CEBALERT: is there a more NumPy way to do this that would be faster?
            rows,cols = pattern_sample.shape
            for i in xrange(rows):
                for j in xrange(cols):
                    # indexes outside the pattern are left with the background color
                    if self.scs.bounds.contains_exclusive(x[i,j],y[i,j]):
                        pattern_sample[i,j] = self.scs.activity[r[i,j],c[i,j]]

        return pattern_sample

    def __apply_size_normalization(self,x,y,sheet_xdensity,sheet_ydensity,scaling):
        pattern_rows,pattern_cols = self.scs.activity.shape

        # Instead of an if-test, could have a class of this type of
        # function (c.f. OutputFunctions, etc)...
        if scaling=='original':
            return

        elif scaling=='stretch_to_fit':
            x_sf,y_sf = pattern_cols/sheet_xdensity, pattern_rows/sheet_ydensity
            x*=x_sf; y*=y_sf

        elif scaling=='fit_shortest':
            if pattern_rows<pattern_cols:
                sf = pattern_rows/sheet_ydensity
            else:
                sf = pattern_cols/sheet_xdensity
            x*=sf;y*=sf

        elif scaling=='fit_longest':
            if pattern_rows<pattern_cols:
                sf = pattern_cols/sheet_xdensity
            else:
                sf = pattern_rows/sheet_ydensity
            x*=sf;y*=sf

        else:
            raise ValueError("Unknown scaling option",scaling)

from numpy.oldnumeric import sum,ravel
def edge_average(a):
    "Return the mean value around the edge of an array."

    if len(ravel(a)) < 2:
        return float(a[0])
    else:
        top_edge = a[0]
        bottom_edge = a[-1]
        left_edge = a[1:-1,0]
        right_edge = a[1:-1,-1]

        edge_sum = sum(top_edge) + sum(bottom_edge) + sum(left_edge) + sum(right_edge)
        num_values = len(top_edge)+len(bottom_edge)+len(left_edge)+len(right_edge)

        return float(edge_sum)/num_values

class FastPatternSampler(param.Parameterized):
    """
    A fast-n-dirty pattern sampler using Python Imaging Library
    routines.  Currently this sampler doesn't support user-specified
    scaling or cropping but rather simply scales and crops the image
    to fit the given matrix size without distorting the aspect ratio
    of the original picture.
    """

    sampling_method = param.Integer(default=PIL.NEAREST,doc="""
       Python Imaging Library sampling method for resampling an image.
       Defaults to Image.NEAREST.""")

    def __init__(self, pattern=None, image=None, **params):
        super(FastPatternSampler,self).__init__(**params)

        if pattern and image:
            raise ValueError("PatternSampler instances can have a pattern or an image, but not both.")
        elif pattern is not None:
            self.image = PIL.new('L',pattern.shape)
            self.image.putdata(pattern.ravel())
        elif image is not None:
            self.image = image
        else:
            raise ValueError("PatternSampler instances must have a pattern or an image.")

    def __call__(self, x, y, sheet_xdensity, sheet_ydensity, width=1.0, height=1.0, **params):

        # JPALERT: Right now this ignores all options and just fits the image into given array.
        # It needs to be fleshed out to properly size and crop the
        # image given the options. (maybe this class needs to be
        # redesigned?  The interface to this function is pretty inscrutable.)

        im = ImageOps.fit(self.image,x.shape,self.sampling_method)
        return array(im,dtype=Float)

# Would be best called Image, but that causes confusion with PIL's Image
class GenericImage(PatternGenerator):
    """
    Generic 2D image generator.

    Generates a pattern from a Python Imaging Library image object.
    Subclasses should override the _get_image method to produce the
    image object.

    The background value is calculated as an edge average: see edge_average().
    Black-bordered images therefore have a black background, and
    white-bordered images have a white background. Images with no
    border have a background that is less of a contrast than a white
    or black one.

    At present, rotation, scaling, etc. just resample; it would be nice
    to support some interpolation options as well.
    """

    __abstract = True

    aspect_ratio  = param.Number(default=1.0,bounds=(0.0,None),
        softbounds=(0.0,2.0),precedence=0.31,doc="""
        Ratio of width to height; size*aspect_ratio gives the width.""")

    size  = param.Number(default=1.0,bounds=(0.0,None),softbounds=(0.0,2.0),
                   precedence=0.30,doc="Height of the image.")

    size_normalization = param.Enumeration(default='fit_shortest',
        available=['fit_shortest','fit_longest','stretch_to_fit','original'],
        precedence=0.95,doc="""
        How to scale the initial image size relative to the default area of 1.0.""")

    whole_image_output_fns = param.HookList(default=[DivisiveNormalizeLinf()],
        class_=TransferFn,precedence=0.96,doc="""
        Function(s) applied to the whole, original image array (before any cropping).""")

    # CB: I guess it's a type rather than an instance because of the
    # way PatternSampler is written (requiring values of many
    # parameters on initialization).
    pattern_sampler_type = param.Parameter(default=PatternSampler, doc="""
        The type of PatternSampler to use to resample/resize the image.""")

    cache_image = param.Boolean(default=True,doc="""
        If False, discards the image after drawing the pattern each time,
        to make it possible to use very large databases of images without
        running out of memory.""")

    def function(self,params):
        xdensity = params['xdensity']
        ydensity = params['ydensity']
        x        = params['pattern_x']
        y        = params['pattern_y']
        # CEBALERT: what's going on here? Where does scaling come
        # from? Why aren't params checked for size_normalization?
        size_normalization = params.get('scaling') or self.size_normalization  #params.get('scaling',self.size_normalization)

        height = params['size']
        width = params['aspect_ratio']*height

        whole_image_output_fns = params['whole_image_output_fns']

        if self._get_image(params) or whole_image_output_fns != self.last_wiofs:
            self.last_wiofs = whole_image_output_fns

            self.ps=self.pattern_sampler_type(image=self._image,
                                              whole_pattern_output_fns=whole_image_output_fns,
                                              background_value_fn=edge_average)

        result = self.ps(x,y,float(xdensity),float(ydensity),float(width),float(height),scaling=size_normalization)

        if not self.cache_image:
            del self.ps     ; self.ps=None
            del self._image ; self._image=None

        return result

    def _get_image(self,params):
        """
        Get a new image, if necessary.

        If necessary as indicated by the parameters, get a new image,
        assign it to self._image and return True.  If no new image is
        needed, return False.
        """

        raise NotImplementedError

    ### support pickling of PIL.Image

    # CEBALERT: almost identical code to that in topo.plotting.bitmap.Bitmap...
    # CEB: by converting to string and back, we probably incur some speed
    # penalty on copy()ing GenericImages (since __getstate__ and __setstate__ are
    # used for copying, unless __copy__ and __deepcopy__ are defined instead).
    def __getstate__(self):
        """
        Return the object's state (as in the superclass), but replace
        the '_image' attribute's Image with a string representation.
        """
        state = super(GenericImage,self).__getstate__()

        if '_image' in state and state['_image'] is not None:
            import StringIO
            f = StringIO.StringIO()
            image = state['_image']
            image.save(f,format=image.format or 'TIFF') # format could be None (we should probably just not save in that case)
            state['_image'] = f.getvalue()
            f.close()

        return state

    def __setstate__(self,state):
        """
        Load the object's state (as in the superclass), but replace
        the '_image' string with an actual Image object.
        """
        # CEBALERT: Need to figure out how state['_image'] could ever
        # actually be None; apparently it is sometimes (see SF
        # #2276819).
        if '_image' in state and state['_image'] is not None:
            import StringIO
            state['_image'] = PIL.open(StringIO.StringIO(state['_image']))
        super(GenericImage,self).__setstate__(state)

class FileImage(GenericImage):
    """
    2D Image generator that reads the image from a file.

    The image at the supplied filename is converted to grayscale if it
    is not already a grayscale image. See PIL's Image class for
    details of supported image file formats.
    """

    filename = Filename(default='images/ellen_arthur.pgm',precedence=0.9,doc="""
        File path (can be relative to Topographica's base path) to a bitmap image.
        The image can be in any format accepted by PIL, e.g. PNG, JPG, TIFF, or PGM.
        """)

    def __init__(self, **params):
        """
        Create the last_filename and last_wiof attributes, used to hold
        the last filename and last whole_image_output_function.

        This allows reloading an existing image to be avoided.
        """
        super(FileImage,self).__init__(**params)
        self.last_filename = None
        self.last_wiofs = None

    def _get_image(self,params):
        filename = params['filename']

        if filename!=self.last_filename or self._image is None:
            self.last_filename=filename
            self._image = ImageOps.grayscale(PIL.open(self.filename))
            return True
        else:
            return False

# PICKLEHACK (move to legacy)
# Temporary as of 12/2007, for backwards compatibility
Image=FileImage

<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
"""
Model of interactive video game Swim Little Fish Swim

@author: Amanda Sutherland, Ziyu (Selina) Wang

last modified: 10-18-15
"""

import pygame
from pygame.locals import *

class SwimFishModel:
    """ Encodes the game state """
    def __init__(self):
        self.monsters = []
        for x in range(100,620,310):
            monster = Monster(10,10,'images/octopus1_png.png',10,x,120)
            self.monsters.append(monster)
        for x in range(200,400,310):
            monster = Monster(10,10,'images/crab1.png',10,x,60)
            self.monsters.append(monster)
        self.fish = Fish((209,95,238),60,50,200,450)
        self.leftWall = Wall((205,133,63),480,50,0,0)
        self.rightWall = Wall((205,133,63),480,50,640-64,0)

class Monster:
    """ Encodes the state of a monster in the game """
    # def scaling(self,height,width,factor):
    	# img_scaled = pygame.transform.scale(self.surface_oct1, (int(factor*width), int(factor*height)))

    def __init__(self,height,width,img,factor,x,y):
        self.img = pygame.image.load(img)
        self.height = height
        self.width = width
        # self.scale = self.scaling(height,width,factor)
        self.scale = pygame.transform.scale(self.img, (int(factor*width), int(factor*height)))
        # self.scale = pygame.transform.scale(surface_oct1, (width, height))
        self.x = x
        self.y = y

class Fish:
    """ Encodes the state of the fish in the game """
    def __init__(self,color,height,width,x,y):
        self.color = color
        self.height = height
        self.width = width
        self.x = x
        self.y = y

class Wall:
   """ Encodes the state of the wall in the game """
   def __init__(self,color,height,width,x,y):
       self.color = color
       self.height = height
       self.width = width
       self.x = x
       self.y = y
<commit_msg>and now there are crabs<commit_after># -*- coding: utf-8 -*-
"""
Model of interactive video game Swim Little Fish Swim

@author: Amanda Sutherland, Ziyu (Selina) Wang

last modified: 10-18-15
"""

import pygame
from pygame.locals import *

class SwimFishModel:
    """ Encodes the game state """
    def __init__(self):
        self.monsters = []
        for x in range(100,620,310):
            monster = Monster(10,10,'images/octopus1_png.png',10,x,120)
            self.monsters.append(monster)
        for x in range(200,400,310):
            monster = Monster(10,10,'images/crab1.png',10,x,60)
            self.monsters.append(monster)
        self.fish = Fish((209,95,238),60,50,200,450)
        self.leftWall = Wall((205,133,63),480,50,0,0)
        self.rightWall = Wall((205,133,63),480,50,640-64,0)

        for monster in self.monsters:
            if self.fish.rect.colliderect(monster.rect):
                raise SystemExit, "You lose!"

class Monster:
    """ Encodes the state of a monster in the game """
    # def scaling(self,height,width,factor):
    	# img_scaled = pygame.transform.scale(self.surface_oct1, (int(factor*width), int(factor*height)))

    def __init__(self,height,width,img,factor,x,y):
        self.img = pygame.image.load(img)
        self.height = height
        self.width = width
        # self.scale = self.scaling(height,width,factor)
        self.scale = pygame.transform.scale(self.img, (int(factor*width), int(factor*height)))
        # self.scale = pygame.transform.scale(surface_oct1, (width, height))
        self.x = x
        self.y = y
        self.rect = pygame.Rect(x,y,width,height)

class Fish:
    """ Encodes the state of the fish in the game """
    def __init__(self,color,height,width,x,y):
        self.color = color
        self.height = height
        self.width = width
        self.x = x
        self.y = y
        self.rect = pygame.Rect(x,y,width,height)

class Wall:
   """ Encodes the state of the wall in the game """
   def __init__(self,color,height,width,x,y):
       self.color = color
       self.height = height
       self.width = width
       self.x = x
       self.y = y
<|endoftext|>


<commit_before>import operator
import re
from collections import Counter, defaultdict
from datetime import datetime

from hamper.interfaces import ChatCommandPlugin, Command
from hamper.utils import ude, uen

from sqlalchemy import Column, DateTime, Integer, String
from sqlalchemy.ext.declarative import declarative_base

SQLAlchemyBase = declarative_base()

class Karma(ChatCommandPlugin):
    '''Give, take, and scoreboard Internet Points'''

    """
    Hamper will look for lines that end in ++ or -- and modify that user's
    karma value accordingly

    NOTE: The user is just a string, this really could be anything...like
    potatoes or the infamous cookie clicker....
    """

    name = 'karma'

    priority = -2

    short_desc = 'karma - Give or take karma from someone'
    long_desc = ("username++ - Give karma\n"
                 "username-- - Take karma\n"
                 "!karma --top - Show the top 5 karma earners\n"
                 "!karma --bottom - Show the bottom 5 karma earners\n"
                 "!karma --giver or --taker - Show who's given the most"
                 " positive or negative karma\n"
                 "!karma --when-positive or --when-negative "
                 " - Show when people are the most positive or negative\n"
                 "!karma username - Show the user's karma count\n")

    gotta_catch_em_all = r"""# 3 or statement
                             (

                             # Starting with a (, look for anything within
                             # parens that end with 2 or more + or -
                             (?=\()[^\)]+\)(\+\++|--+) |

                             # Looking from the start of the line until 2 or
                             # more - or + are found. No whitespace in this
                             # grouping
                             ^[^\s]+(\+\++|--+) |

                             # Finally group any non-whitespace groupings
                             # that end with 2 or more + or -
                             [^\s]+?(\+\++|--+)((?=\s)|(?=$))
                            )
                          """

    regstr = re.compile(gotta_catch_em_all, re.X)

    def setup(self, loader):
        super(Karma, self).setup(loader)
        self.db = loader.db
        SQLAlchemyBase.metadata.create_all(self.db.engine)

    def message(self, bot, comm):
        """
        Check for strings ending with 2 or more '-' or '+'
        """
        super(Karma, self).message(bot, comm)

        # No directed karma giving or taking
        if not comm['directed'] and not comm['pm']:
            msg = comm['message'].strip().lower()
            # use the magic above
            words = self.regstr.findall(msg)
            # Do things to people
            karmas = self.modify_karma(words)
            # Notify the users they can't modify their own karma
            if comm['user'] in karmas.keys():
                bot.reply(comm, "Nice try, no modifying your own karma")
            # Commit karma changes to the db
            self.update_db(comm["user"], karmas)

    def modify_karma(self, words):
        """
        Given a regex object, look through the groups and modify karma
        as necessary
        """

        # 'user': karma
        k = defaultdict(int)

        if words:
            # For loop through all of the group members
            for word_tuple in words:
                word = word_tuple[0]
                ending = word[-1]
                # This will either end with a - or +, if it's a - subract 1
                # kara, if it ends with a +, add 1 karma
                change = -1 if ending == '-' else 1
                # Now strip the ++ or -- from the end
                if '-' in ending:
                    word = word.rstrip('-')
                elif '+' in ending:
                    word = word.rstrip('+')
                # Check if surrounded by parens, if so, remove them
                if word.startswith('(') and word.endswith(')'):
                    word = word[1:-1]
                # Finally strip whitespace
                word = word.strip()
                # Add the user to the dict
                if word:
                    k[word] += change
        return k

    def update_db(self, giver, receiverkarma):
        """
        Record a the giver of karma, the receiver of karma, and the karma
        amount. Typically the count will be 1, but it can be any positive or
        negative integer.
        """

        for receiver in receiverkarma:
            if receiver != giver:
                urow = KarmaTable(ude(giver), ude(receiver),
                                  receiverkarma[receiver])
                self.db.session.add(urow)
        self.db.session.commit()

    class KarmaList(Command):
        """
        Return the highest or lowest 5 receivers of karma
        """

        regex = r'^karma --(top|bottom)$'

        LIMIT = 5

        def command(self, bot, comm, groups):
            # We'll need all the rows
            kts = bot.factory.loader.db.session.query(KarmaTable).all()
            # From all the rows, tally the karma for each receiver
            receivers = defaultdict(int)
            for row in kts:
                receivers[row.receiver] += row.kcount
            rec_count = len(receivers.keys())
            rec_sorted = sorted(receivers.iteritems(),
                                key=operator.itemgetter(1))

            # We should limit the list of users to at most self.LIMIT
            limit = self.LIMIT if rec_count >= self.LIMIT else rec_count
            if limit:
                if groups[0] == 'top':
                    snippet = rec_sorted[-limit:]
                elif groups[0] == 'bottom':
                    snippet = rec_sorted[0:limit]
                else:
                    bot.reply(
                        comm, r'Something went wrong with karma\'s regex'
                    )
                    return

                for rec in snippet:
                    bot.reply(
                        comm, '%s\x0f: %d' % (uen(rec[0]), rec[1]),
                        encode=False
                    )
            else:
                bot.reply(comm, r'No one has any karma yet :-(')

    class UserKarma(Command):
        """
        Retrieve karma for a given user
        """

        # !karma <username>
        regex = r'^karma\s+([^-].*)$'

        def command(self, bot, comm, groups):
            # Play nice when the user isn't in the db
            kt = bot.factory.loader.db.session.query(KarmaTable)
            thing = ude(groups[0].strip().lower())
            rec_list = kt.filter(KarmaTable.receiver == thing).all()

            if rec_list:
                total = 0
                for r in rec_list:
                    total += r.kcount
                bot.reply(
                    comm, '%s has %d points' % (uen(thing), total),
                    encode=False
                )
            else:
                bot.reply(
                    comm, 'No karma for %s ' % uen(thing), encode=False
                )

    class KarmaGiver(Command):
        """
        Identifies the person who gives the most karma
        """

        regex = r'^karma --(giver|taker)$'

        def command(self, bot, comm, groups):
            kt = bot.factory.loader.db.session.query(KarmaTable)
            counter = Counter()

            if groups[0] == 'giver':
                positive_karma = kt.filter(KarmaTable.kcount > 0)
                for row in positive_karma:
                    counter[row.giver] += row.kcount

                m = counter.most_common(1)
                most = m[0] if m else None
                if most:
                    bot.reply(
                        comm,
                        '%s has given the most karma (%d)' %
                        (uen(most[0]), most[1])
                    )
                else:
                    bot.reply(
                        comm,
                        'No positive karma has been given yet :-('
                    )
            elif groups[0] == 'taker':
                negative_karma = kt.filter(KarmaTable.kcount < 0)
                for row in negative_karma:
                    counter[row.giver] += row.kcount

                m = counter.most_common()
                most = m[-1] if m else None
                if most:
                    bot.reply(
                        comm,
                        '%s has given the most negative karma (%d)' %
                        (uen(most[0]), most[1])
                    )
                else:
                    bot.reply(
                        comm,
                        'No negative karma has been given yet'
                    )

    class MostActive(Command):
        """
        Least/Most active hours of karma giving/taking
        """

        regex = r'^karma\s+--when-(positive|negative)'

        def command(self, bot, comm, groups):
            kt = bot.factory.loader.db.session.query(KarmaTable)
            counter = Counter()

            if groups[0] == "positive":
                karma = kt.filter(KarmaTable.kcount > 0)
            elif groups[0] == "negative":
                karma = kt.filter(KarmaTable.kcount < 0)

            for row in karma:
                hour = row.datetime.hour
                counter[hour] += row.kcount

            common_hour = (counter.most_common(1)[0][0]
                           if counter.most_common(1) else None)
            title_case = groups[0][0].upper() + groups[0][1:]

            if common_hour:
                bot.reply(
                    comm,
                    '%s karma is usually given during the %d:00 hour (UTC)' %
                    (title_case, common_hour)
                )
            else:
                bot.reply(
                    comm,
                    '%s karma has been given yet' % title_case
                )

class KarmaTable(SQLAlchemyBase):
    """
    Keep track of users karma in a persistant manner
    """

    __tablename__ = 'karma'

    # Calling the primary key user, though, really, this can be any string
    id = Column(Integer, primary_key=True)
    giver = Column(String)
    receiver = Column(String)
    kcount = Column(Integer)
    datetime = Column(DateTime, default=datetime.utcnow())

    def __init__(self, giver, receiver, kcount):
        self.giver = giver
        self.receiver = receiver
        self.kcount = kcount

karma = Karma()
<commit_msg>karma: pep8 cleanup<commit_after>import operator
import re
from collections import Counter, defaultdict
from datetime import datetime

from hamper.interfaces import ChatCommandPlugin, Command
from hamper.utils import ude, uen

from sqlalchemy import Column, DateTime, Integer, String
from sqlalchemy.ext.declarative import declarative_base

SQLAlchemyBase = declarative_base()

class Karma(ChatCommandPlugin):
    '''Give, take, and scoreboard Internet Points'''

    """
    Hamper will look for lines that end in ++ or -- and modify that user's
    karma value accordingly

    NOTE: The user is just a string, this really could be anything...like
    potatoes or the infamous cookie clicker....
    """

    name = 'karma'

    priority = -2

    short_desc = 'karma - Give or take karma from someone'
    long_desc = ("username++ - Give karma\n"
                 "username-- - Take karma\n"
                 "!karma --top - Show the top 5 karma earners\n"
                 "!karma --bottom - Show the bottom 5 karma earners\n"
                 "!karma --giver or --taker - Show who's given the most"
                 " positive or negative karma\n"
                 "!karma --when-positive or --when-negative "
                 " - Show when people are the most positive or negative\n"
                 "!karma username - Show the user's karma count\n")

    gotta_catch_em_all = r"""# 3 or statement
                             (

                             # Starting with a (, look for anything within
                             # parens that end with 2 or more + or -
                             (?=\()[^\)]+\)(\+\++|--+) |

                             # Looking from the start of the line until 2 or
                             # more - or + are found. No whitespace in this
                             # grouping
                             ^[^\s]+(\+\++|--+) |

                             # Finally group any non-whitespace groupings
                             # that end with 2 or more + or -
                             [^\s]+?(\+\++|--+)((?=\s)|(?=$))
                            )
                          """

    regstr = re.compile(gotta_catch_em_all, re.X)

    def setup(self, loader):
        super(Karma, self).setup(loader)
        self.db = loader.db
        SQLAlchemyBase.metadata.create_all(self.db.engine)

    def message(self, bot, comm):
        """
        Check for strings ending with 2 or more '-' or '+'
        """
        super(Karma, self).message(bot, comm)

        # No directed karma giving or taking
        if not comm['directed'] and not comm['pm']:
            msg = comm['message'].strip().lower()
            # use the magic above
            words = self.regstr.findall(msg)
            # Do things to people
            karmas = self.modify_karma(words)
            # Notify the users they can't modify their own karma
            if comm['user'] in karmas.keys():
                bot.reply(comm, "Nice try, no modifying your own karma")
            # Commit karma changes to the db
            self.update_db(comm["user"], karmas)

    def modify_karma(self, words):
        """
        Given a regex object, look through the groups and modify karma
        as necessary
        """

        # 'user': karma
        k = defaultdict(int)

        if words:
            # For loop through all of the group members
            for word_tuple in words:
                word = word_tuple[0]
                ending = word[-1]
                # This will either end with a - or +, if it's a - subract 1
                # kara, if it ends with a +, add 1 karma
                change = -1 if ending == '-' else 1
                # Now strip the ++ or -- from the end
                if '-' in ending:
                    word = word.rstrip('-')
                elif '+' in ending:
                    word = word.rstrip('+')
                # Check if surrounded by parens, if so, remove them
                if word.startswith('(') and word.endswith(')'):
                    word = word[1:-1]
                # Finally strip whitespace
                word = word.strip()
                # Add the user to the dict
                if word:
                    k[word] += change
        return k

    def update_db(self, giver, receiverkarma):
        """
        Record a the giver of karma, the receiver of karma, and the karma
        amount. Typically the count will be 1, but it can be any positive or
        negative integer.
        """

        for receiver in receiverkarma:
            if receiver != giver:
                urow = KarmaTable(ude(giver), ude(receiver),
                                  receiverkarma[receiver])
                self.db.session.add(urow)
        self.db.session.commit()

    class KarmaList(Command):
        """
        Return the highest or lowest 5 receivers of karma
        """

        regex = r'^karma --(top|bottom)$'

        LIMIT = 5

        def command(self, bot, comm, groups):
            # We'll need all the rows
            kts = bot.factory.loader.db.session.query(KarmaTable).all()
            # From all the rows, tally the karma for each receiver
            receivers = defaultdict(int)
            for row in kts:
                receivers[row.receiver] += row.kcount
            rec_count = len(receivers.keys())
            rec_sorted = sorted(receivers.iteritems(),
                                key=operator.itemgetter(1))

            # We should limit the list of users to at most self.LIMIT
            limit = self.LIMIT if rec_count >= self.LIMIT else rec_count
            if limit:
                if groups[0] == 'top':
                    snippet = rec_sorted[-limit:]
                elif groups[0] == 'bottom':
                    snippet = rec_sorted[0:limit]
                else:
                    bot.reply(
                        comm, r'Something went wrong with karma\'s regex'
                    )
                    return

                for rec in snippet:
                    bot.reply(
                        comm, '%s\x0f: %d' % (uen(rec[0]), rec[1]),
                        encode=False
                    )
            else:
                bot.reply(comm, r'No one has any karma yet :-(')

    class UserKarma(Command):
        """
        Retrieve karma for a given user
        """

        # !karma <username>
        regex = r'^karma\s+([^-].*)$'

        def command(self, bot, comm, groups):
            # Play nice when the user isn't in the db
            kt = bot.factory.loader.db.session.query(KarmaTable)
            thing = ude(groups[0].strip().lower())
            rec_list = kt.filter(KarmaTable.receiver == thing).all()

            if rec_list:
                total = 0
                for r in rec_list:
                    total += r.kcount
                bot.reply(
                    comm, '%s has %d points' % (uen(thing), total),
                    encode=False
                )
            else:
                bot.reply(
                    comm, 'No karma for %s ' % uen(thing), encode=False
                )

    class KarmaGiver(Command):
        """
        Identifies the person who gives the most karma
        """

        regex = r'^karma --(giver|taker)$'

        def command(self, bot, comm, groups):
            kt = bot.factory.loader.db.session.query(KarmaTable)
            counter = Counter()

            if groups[0] == 'giver':
                positive_karma = kt.filter(KarmaTable.kcount > 0)
                for row in positive_karma:
                    counter[row.giver] += row.kcount

                m = counter.most_common(1)
                most = m[0] if m else None
                if most:
                    bot.reply(
                        comm,
                        '%s has given the most karma (%d)' %
                        (uen(most[0]), most[1])
                    )
                else:
                    bot.reply(
                        comm,
                        'No positive karma has been given yet :-('
                    )
            elif groups[0] == 'taker':
                negative_karma = kt.filter(KarmaTable.kcount < 0)
                for row in negative_karma:
                    counter[row.giver] += row.kcount

                m = counter.most_common()
                most = m[-1] if m else None
                if most:
                    bot.reply(
                        comm,
                        '%s has given the most negative karma (%d)' %
                        (uen(most[0]), most[1])
                    )
                else:
                    bot.reply(
                        comm,
                        'No negative karma has been given yet'
                    )

    class MostActive(Command):
        """
        Least/Most active hours of karma giving/taking
        """

        regex = r'^karma\s+--when-(positive|negative)'

        def command(self, bot, comm, groups):
            kt = bot.factory.loader.db.session.query(KarmaTable)
            counter = Counter()

            if groups[0] == "positive":
                karma = kt.filter(KarmaTable.kcount > 0)
            elif groups[0] == "negative":
                karma = kt.filter(KarmaTable.kcount < 0)

            for row in karma:
                hour = row.datetime.hour
                counter[hour] += row.kcount

            common_hour = (counter.most_common(1)[0][0]
                           if counter.most_common(1) else None)
            title_case = groups[0][0].upper() + groups[0][1:]

            if common_hour:
                bot.reply(
                    comm,
                    '%s karma is usually given during the %d:00 hour (UTC)' %
                    (title_case, common_hour)
                )
            else:
                bot.reply(
                    comm,
                    '%s karma has been given yet' % title_case
                )

class KarmaTable(SQLAlchemyBase):
    """
    Keep track of users karma in a persistant manner
    """

    __tablename__ = 'karma'

    # Calling the primary key user, though, really, this can be any string
    id = Column(Integer, primary_key=True)
    giver = Column(String)
    receiver = Column(String)
    kcount = Column(Integer)
    datetime = Column(DateTime, default=datetime.utcnow())

    def __init__(self, giver, receiver, kcount):
        self.giver = giver
        self.receiver = receiver
        self.kcount = kcount

karma = Karma()
<|endoftext|>