예제 #1
0
  def __init__(self, poem, **kwargs):
    """Specify the name of a known format or specify a fully-defined format."""
    self.debug = False
    #self.poem_complete = False #dunno what this is.
    #self.poem_validator = PoemValidator()

    self.format = poem.get_format()
    self.lines_needed = self.format["lines_needed"]

    if 'rhyme_checker' in kwargs:
      self.rhyme_checker = kwargs['rhyme_checker']
    else:
      self.rhyme_checker = RhymeChecker()
    self.rhyme_checker.debug = False

    self.allow_partial_lines = False
    self.verbose = False
    self.where_to_put_partial_lines = {}

    #TODO abstract away hash types by format.
    self.rhyme_dict = {}
    self.syllable_count_dict = {}
    # self.global_only_once = 100
    self.groups = None
    self.prepped = False
예제 #2
0
class Poemifier:
  def __init__(self, poem, **kwargs):
    """Specify the name of a known format or specify a fully-defined format."""
    self.debug = False
    #self.poem_complete = False #dunno what this is.
    #self.poem_validator = PoemValidator()

    self.format = poem.get_format()
    self.lines_needed = self.format["lines_needed"]

    if 'rhyme_checker' in kwargs:
      self.rhyme_checker = kwargs['rhyme_checker']
    else:
      self.rhyme_checker = RhymeChecker()
    self.rhyme_checker.debug = False

    self.allow_partial_lines = False
    self.verbose = False
    self.where_to_put_partial_lines = {}

    #TODO abstract away hash types by format.
    self.rhyme_dict = {}
    self.syllable_count_dict = {}
    # self.global_only_once = 100
    self.groups = None
    self.prepped = False

  def try_line(self, line):
    """ Add a line, then return True if, given that line, a poem can be created."""
    self.add_line(line)
    return self.create_poem() #False or a poem.

  def take_out_of_fridge(self, pickle_file):
    jar = pickle.load( pickle_file )
    self.where_to_put_partial_lines = jar['where_to_put_partial_lines']
    self.rhyme_dict = jar['rhyme_dict']
    self.syllable_count_dict = jar['syllable_count_dict']
    self.groups = jar['groups']

  def put_in_fridge(self, pickle_file):
    if not self.groups:
      raise Exception, "Only put a poemifier in the fridge if you've run prep_for_creation()"
    jar = dict()
    jar['where_to_put_partial_lines'] = self.where_to_put_partial_lines
    jar['rhyme_dict'] = self.rhyme_dict
    jar['syllable_count_dict'] = self.syllable_count_dict
    jar['groups'] = self.groups
    pickle.dump( jar, open(pickle_file, 'wb') ) 

  def add_line(self, line):
    """
    Adds a line to the format hash dicts. Return False if the line cannot be used.
    """
    self.prepped = False
    #TODO: abstract away format stuff
    # format_items = [[self._rime, self.rhyme_dict], [self._syllable_count, self.syllable_count_dict]]
    # for hashFunc, format_hash_dict in format_items:
    splits = []

    for index, syllable_count_token in enumerate(self.format["syllable_structure"]):
      rest_of_syllables = self.format["syllable_structure"][index:] #inclusive
      temp_splits = line.split_line_to_format(rest_of_syllables)
      if temp_splits != False:
        splits += temp_splits
    #print splits

    for split in splits:
      if self.allow_partial_lines:
        for part_of_line in split:
          self._add_line_helper(part_of_line)
      else:
        #if partial lines aren't allowed, only add the first part of partial lines to the hashes; don't bother adding the rest.
        self._add_line_helper(split[0])
    return self._add_line_helper(line)

  def _add_line_helper(self, line):
    syll_count = line.syllable_count()
    if syll_count in self.format["syllable_count_to_syllable_count_token"].keys() or "any" in self.format["syllable_count_to_syllable_count_token"].keys() : #TODO: obviously, abstract this.
      rime = line.rime()
      if not rime:
        return False
      rime = tuple(rime)

      #TODO: DRY this out.
      for syll_count_token in self.format["syllable_count_to_syllable_count_token"].get(syll_count, []):
        if syll_count_token not in self.syllable_count_dict:
          self.syllable_count_dict[syll_count_token] = []
        self.syllable_count_dict[syll_count_token].append(line)

        if rime not in self.rhyme_dict:
          self.rhyme_dict[rime] = {}
        if syll_count_token not in self.rhyme_dict[rime]:
          self.rhyme_dict[rime][syll_count_token] = []
        self.rhyme_dict[rime][syll_count_token].append(line)
      if "any" in self.format["syllable_count_to_syllable_count_token"].keys():
        syll_count_token = syll_count
        if syll_count_token not in self.syllable_count_dict:
          self.syllable_count_dict[syll_count_token] = []
        self.syllable_count_dict[syll_count_token].append(line)

        if rime not in self.rhyme_dict:
          self.rhyme_dict[rime] = {}
        if syll_count_token not in self.rhyme_dict[rime]:
          self.rhyme_dict[rime][syll_count_token] = []
        self.rhyme_dict[rime][syll_count_token].append(line)

      return True
    else:
      return False

  def validate_rhyme(self, poem, line, index):
    """True if this line fits in the rhyme scheme where it is."""
    temp_poem = list(poem) #"copy" the list
    temp_poem[index] = line

    last_word = line.split(" ")[-1]
    last_word.strip(".,?!:;\" ")
    #does it fit where it is.
    rhyme_symbol = self.format["rhyme_scheme"][index]
    lines_to_compare_to = [temp_poem[i] for i, symbol in \
      enumerate(self.format["rhyme_scheme"]) if symbol == rhyme_symbol \
      and temp_poem[i] and i != index]
    if not lines_to_compare_to:
      if self.debug:
        print "nothing to compare " + last_word + " to. " 
      return True
    words_to_compare_to = map(lambda x: x.split(" ")[-1], lines_to_compare_to )
    return True in map(lambda x: self.rhyme_checker.rhymes_with(last_word, x), words_to_compare_to)

  def _group_lines_by_rime(self):
    """Group lines with the same syllable count and then by rime.

    all the lines in any of the dicts are guaranteed to be of acceptable length.
    e.g. groups = {6 => {"EH" => ["a", "b", "c"], "OI" => ...}, (9,11) => { "AH" => ["a", "b", "c"] "UH" => ...}, }
    """
    #print self.rhyme_dict[(('UW', 'L'),)]

    groups_by_rhyme = {}
    groups_by_syll_count = {}
    for rime, rhyme_groups in self.rhyme_dict.items():
      #skip rhyme dict entries with only one element, unless there are unrhyming elements in the rhyme scheme
      if len(rhyme_groups) == 1 and len(rhyme_groups.values()[0]) == 1 and \
          (self.allow_partial_lines or not rhyme_groups.values()[0][0].is_partial()) and \
            not filter(lambda item: self.format["rhyme_scheme"].count(item) == 1, list(self.format["rhyme_scheme"])):
        print "skipping %(l)s while grouping" % {'l' : rhyme_groups.values()[0]}
        continue
      inner_groups_by_syll_count = {}
      for syllable_count_token, rhyme_group in rhyme_groups.items():
        #exclude words from the rhyme group whose last word is the last word of another line in this rhyme group
        already_used_last_words = set()
        new_rhyme_group = []
        for rhyme_line in rhyme_group:
          last_word = rhyme_line.clean_text().split(" ")[-1].lower()
          if last_word not in already_used_last_words or \
            self.format["syllable_structure"].count(syllable_count_token) == 1:
            already_used_last_words.add(last_word)
            new_rhyme_group.append(rhyme_line)

        inner_groups_by_syll_count[syllable_count_token] = new_rhyme_group
        if syllable_count_token not in groups_by_syll_count.keys():
          groups_by_syll_count[syllable_count_token] = {}
        groups_by_syll_count[syllable_count_token][rime] = new_rhyme_group
      groups_by_rhyme[rime] = inner_groups_by_syll_count
    return groups_by_syll_count

  def _prune_too_small_grouped_lines(self, groups):
    """ Remove rimes with too few members, in place.

      The returned dict contains only lines that are eligible to appear in the poem.
      A rime_group has too few members iff:
        1. Its count of members is less than the maximum count of unique 
            elements in syllable structure or rhyme scheme.
    """

    min_rime_count = min(list(set(map(lambda x: self.format["rhyme_scheme"].count(x), self.format["rhyme_scheme"] ))))
    for syllcount, syllcount_group in groups.items():
      for rime, rime_group in syllcount_group.items():
        partials = sum([1 for l in rime_group if l.is_partial() and l.rime() == l.after_siblings()[0].rime() ])
        #TODO: only add partials that rime with their prev sibling (or is this necessary?)
        rime_group_effective_length = len(rime_group) + partials

        #conscious decision: not >=
        if rime_group_effective_length < max(self.format["syllable_structure"].count(syllcount), min_rime_count):
          del groups[syllcount][rime]
      if len(syllcount_group) == 0:
        del groups[syllcount]

  def _prune_desiblinged_lines_from_groups(self, groups):
    """filter out partial-lines whose siblings aren't in `groups`, in place"""

    #for each partial-line, figure out what order of syllable and rhyme structures would be needed to slot it into the poem
    #e.g. it's got two pieces that rhyme of 9-11 syllables each
    #remove those partial lines who can't fit anywhere. don't accept the partial line if it's picked where it can't fit.
    #slot in the partial line and everything after if it can.

    flat_list_of_kosher_lines = [lines for lines in groups.items()]
    for syllcount, syllcount_group in groups.items():
      #groups[syllcount] = {}
      for rime, rime_group in syllcount_group.items():
        for line in rime_group:
          if not line.is_partial():
            continue
          else:
            #TODO: abstract this bit.
            size = line.total_siblings() + 1 #+1 for the line itself.
            format_items = zip(self.format["rhyme_scheme"], self.format["syllable_structure"])
            sequential_format_orderings = [] 
            for i in range(0, (len(format_items) - size + 1)):
              sequential_format_orderings.append(format_items[i:i+size])
            okay = []

            for subformat in sequential_format_orderings:
              okay.append(True)
              mini_rhyme_rime_map = {} # 'a' => "EH" etc
              all_lines = [line] + line.after_siblings()


              for i, sibling_line in enumerate(all_lines):
                if not okay[-1]: #don't waste our time
                  continue

                rhyme_symbol = subformat[i][0]
                needed_syllable_count = subformat[i][1]
                #TODO: abstract this away.

                #test syllable count
                if isinstance(needed_syllable_count, int):
                  if sibling_line.syllable_count() != needed_syllable_count:
                    okay[-1] = False
                    #print "failed on syllable count " + str(needed_syllable_count)
                    break
                elif isinstance(needed_syllable_count, tuple):
                  if not (sibling_line.syllable_count() >= needed_syllable_count[0] and sibling_line.syllable_count() <= needed_syllable_count[1]):
                    okay[-1] = False
                    #print "failed on syllable count " + str(needed_syllable_count)
                    break
                elif needed_syllable_count == "any":
                  #its okay, no matter what
                  break

                #test rhyme
                if rhyme_symbol in mini_rhyme_rime_map:
                  #TODO: compare only the last N syllables in rime for N = min(len(rime1, rime2))
                  min_rime_size = min( len(mini_rhyme_rime_map[rhyme_symbol]), len(sibling_line.rime()))
                  if mini_rhyme_rime_map[rhyme_symbol][-min_rime_size:] != (sibling_line.rime()[-min_rime_size:],):
                    okay[-1] = False
                    #print "failed on rhyme: %(r1)s; %(r2)s" % {'r1': mini_rhyme_rime_map[rhyme_symbol][-min_rime_size:], 'r2':  (sibling_line.rime()[-min_rime_size],)}
                    break
                else:
                  mini_rhyme_rime_map[rhyme_symbol] = tuple(sibling_line.rime()) if sibling_line.rime() else False
                  if i == len(all_lines)-1:
                    okay[-1] = False

            if True not in okay:
              rime_group.remove(line)
            else:
              self.where_to_put_partial_lines[line] = [i for i, b in enumerate(okay) if b]
              #store where it's okay.

  def _shuffle_grouped_lines(self, groups):
    for syllcount, syllcount_group in groups.items():
      for rime, rime_group in syllcount_group.items():
        shuffle(rime_group)

  def prep_for_creation(self):
    """Do a bunch of prep work for creating a poem. Doesn't commit us to anything yet."""
    if self.prepped:
      return self.groups


    self.groups = self._group_lines_by_rime()
                        # seven = [item for sublist in self.groups[7].values() for item in sublist]
                        # print "seven: " + str([debug_line in seven for debug_line in debug_lines])
                        # print self.groups[7][(('AH', 'N'),)]

                        # flat_lines = [item.text for subl2 in [item for sublist in [d.values() for d in self.groups.values()] for item in sublist] for item in subl2]
                        # print [debug_line in flat_lines for debug_line in debug_lines]

    if self.verbose:
      for syllable_count in list(self.format["unique_syllable_structure"]):
        if syllable_count == "any":
          print "before pruning too small1, %(s)s: %(l)s" % {'s' : "total", 'l': str(len(self.groups))}
        print "before pruning too small1, %(s)s: %(l)s" % {'s' : syllable_count, 'l': str(len(self.groups.get(syllable_count, "")))}
    self._prune_too_small_grouped_lines(self.groups)
    if not self.allow_partial_lines:
      if self.verbose:
        for syllable_count in list(self.format["unique_syllable_structure"]):
          print "before pruning desiblinged, %(s)s: %(l)s" % {'s' : syllable_count, 'l': str(len(self.groups.get(syllable_count, "")))}
      self._prune_desiblinged_lines_from_groups(self.groups)
      if self.verbose:
        for syllable_count in list(self.format["unique_syllable_structure"]):
          print "before pruning too small2, %(s)s: %(l)s" % {'s' : syllable_count, 'l': str(len(self.groups.get(syllable_count, "")))}
      self._prune_too_small_grouped_lines(self.groups)
      if self.verbose:
        for syllable_count in list(self.format["unique_syllable_structure"]):
          print "after, %(s)s: %(l)s" % {'s' : syllable_count, 'l': len(self.groups.get(syllable_count, []))}
          print ""
    self.prepped = True
    return self.groups


  def create_poem(self, be_random=False):
    """ Return False or a poem. """
    #TODO: again, abstraction!
    groups = self.prep_for_creation()

    if not groups:
      if self.debug:
        print "no groups, that can't be right"
      return False

    poem = [None] * self.format["lines_needed"]
                        #old debug stuff: why is a haiku not being genreated?
                        # flat_lines = [item.text for subl2 in [item for sublist in [d.values() for d in self.rhyme_dict.values()] for item in sublist] for item in subl2]
                        # print [debug_line in flat_lines for debug_line in debug_lines]

    already_used_rimes = set()
    rimes_by_rhyme_element = {}
    unique_rhyme_scheme = set(list(self.format["rhyme_scheme"]))
    for rhyme_element in unique_rhyme_scheme:
      unique_syllable_structure = list(self.format["unique_syllable_structure"])
      shuffle(unique_syllable_structure)
      for syllable_count_token in unique_syllable_structure:
        if syllable_count_token not in groups and syllable_count_token != "any":
          continue

        if syllable_count_token == "any":
          candidate_lines = choice(groups.values()).values()
        else:
          candidate_lines = groups[syllable_count_token].values()
        if be_random:
          shuffle(candidate_lines)

        # print "candidate lines: %(c)s" % {'c' : candidate_lines}

        if not candidate_lines:
          continue

        #for each syllable in the candidate lines for this syllable_count_token, create a poem
        for this_sylls_lines in candidate_lines:
          for index, syllable_count in enumerate(self.format["syllable_structure"]):
            if syllable_count == syllable_count_token and rhyme_element == self.format["rhyme_scheme"][index]:
              for next_line in this_sylls_lines:

                #ensures that 'a' and 'b' elements don't rhyme with each other (so it doesn't end up being aaaa for aabb)
                if next_line.rime() not in already_used_rimes or next_line.rime() in rimes_by_rhyme_element.get(rhyme_element, set()):
                  already_used_rimes.add(next_line.rime())
                  if rhyme_element not in rimes_by_rhyme_element:
                    rimes_by_rhyme_element[rhyme_element] = set()
                  rimes_by_rhyme_element[rhyme_element].add(next_line.rime())


                  if not poem[index] and next_line not in poem: #ensures there are no duplicate lines in poems.
                    if (not self.allow_partial_lines) and next_line.is_partial():
                      if next_line in self.where_to_put_partial_lines:
                        if index in self.where_to_put_partial_lines[next_line]:
                          if self.verbose:
                            print "line %(i)s (%(l)s) is partial and acceptable" %{'i': index , 'l': next_line}
                          poem[index] = next_line
                          for j, sibling in enumerate(next_line.after_siblings()):
                            #these can be depended on to fit the right syllable structure, but not rhyme. #TODO
                            poem[j + 1 + index] = sibling
                            # print "slotted in a sibling to line #%(i)s" % {'i': j + index}
                      #   else:
                      #     print "line %(l)s doesn't fit in line %(n)s" % {'l' : next_line, 'n' : index}
                      # else:
                      #   if next_line.text != "":
                      #     print str(next_line) + " didn't make it"
                    else:
                      # print "line " + str(index) + " partial? " + str(next_line.is_partial())
                      poem[index] = next_line
                      continue
                  # else:
                  #   if poem[index]:
                  #     print "skipping because line is already filled"
                  #   if next_line in poem: 
                  #     print "skipping because next_line already in poem"
                  #   print ""
            # else:
            #   pass
              # print "skipped %(l)s due to syllable/rhyme problems" % {'l' : this_sylls_lines}
              # print "syllable_count: %(sc)s , syllable_count_token: %(sct)s" % {'sc': syllable_count, 'sct': syllable_count_token}
              # print "rhyme_element: %(sc)s , self.format[rhyme_scheme][index]: %(sct)s" % {'sc': rhyme_element, 'sct': self.format["rhyme_scheme"][index]}
              # print ""
    # if not self.allow_partial_lines:
    #   print "generated " + str(len(poems)) + " semivalid poems"
    #   poems = self._cull_desiblinged_lines_from_poems(poems)
    #print "generated " + str(len(poems)) + " valid poems"
    if None in poem:
      return False
    else:
      return poem