Утечки памяти в лотерею симулятор


Я в основном на C++/Java программист, но недавно я начал использовать Python на работе и решил написать лотереи тренажер дома. Я написал это, чтобы проверить различные комбинации чисел в лотерею с набором выигрышных номеров, чтобы подсчитать, сколько я бы выиграл (я также запустить лото бассейн на работе). Единственная крупная проблема, которую я видел с кодом заключается в том, что она имеет значительные утечки памяти, что я просто не могу показаться, чтобы выследить. Я уже подключил одну большую утечку.

Я очень ценю любую помощь вы можете предоставить в отслеживании утечки(ы). Код довольно большой (655 линии), но многое из этого комментарии и пустые строки для удобства чтения. Также не стесняйтесь использовать код самостоятельно, если вы хотите, я не копирайтинг это.

#!/usr/bin/python

import random, ConfigParser, ast, copy
from optparse import OptionParser

# Global variables.
g_config = {}         # Stores GameType config options.
g_prizes = {}         # Stores prizes from config file.
g_prize_amounts = {}    # Stores prize amounts from config file.
g_ticket_quick_picks = []   # Store an array of QuickPicks to use for tickets.
g_total_money_won = 0
g_total_tickets_won = 0
g_prizes_won = {}       # Format: {'3':3, '3+1':2, '4':2 ...}
g_quick_pick_file = ''
g_how_many_tickets_played = 0
g_how_many_tickets_won = 0


class TicketNumbers( object ):
  '''Represents the numbers of a ticket (or the 7 winning numbers).
  @var numbers: A sorted array of 7 unique numbers from 1-49.'''

  def __init__( self, numbers ):
    '''Constructor.
    @param numbers: The numbers for this ticket.'''

    self.numbers = numbers
    self._set_numbers( numbers )

  def name( self ):
    '''Return the name of this class type.'''
    return "TicketNumbers"

  def __str__( self ):
    '''Returns a string of the number list.'''
    str_nums = []
    for i in range( len( self.numbers ) ):
      str_nums.append( str( self.numbers[i] ) )

    return ', '.join( str_nums )

  def _set_numbers( self, numbers ):
    '''Sets the numbers member variable and validates the numbers.
    @param numbers: The numbers to assign.'''

    global g_config
    self.numbers = sorted( numbers )

    if len( self.numbers ) != int(g_config['how_many_numbers']):
      raise Exception( "You must have %d numbers, but found %d numbers: %s" % (g_config['how_many_numbers'], len( self.numbers ), str(self.numbers)) )
    tmp = []

    for num in self.numbers:
      # Check if numbers are in range.
      if ( num < int(g_config['min_number']) ) or ( num > int(g_config['max_number']) ):
        raise Exception( "The numbers must be between %d & %d!" % (int(g_config['min_number']), int(g_config['max_number'])) )
      # Check for duplicate numbers.
      if num in tmp:
        raise Exception( "You cannot have duplicate numbers!  %d in %s" % (num, str( self.numbers )) )
      tmp.append( num )


class PlayedTicket( TicketNumbers ):
  '''Represents a ticket that was played.
  @var amount_won: How much $$ this ticket won.'''

  def __init__( self, numbers ):
    '''Constructor.
    @param numbers: The numbers for this ticket.'''

    super( PlayedTicket, self ).__init__( numbers )
    self.amount_won = {}  # Map of Game Index to $$ won each play.
    self.tickets_won = {}   # Map of Game Index to Free Tickets won each play.

 def name( self ):
    '''Return the name of this class type.'''
    return "PlayedTicket"

  def compare_with( self, winning_numbers, game_index=None ):
    '''Compares this ticket with the specified winning numbers and sets the amount_won & tickets_won members accordingly.
    @param winning_numbers: The winning numbers to compare against.
    @param game_index: (optional) The game index for this game.  Defaults to 1 + the number of games recorded.
    @return: A tuple of ($$ won, # free tickets won).'''

    global g_config, g_prizes, g_prize_amounts, g_prizes_won, g_how_many_tickets_won
    nums_correct = 0
    bonus_correct = 0
    amount_won = 0
    tickets_won = 0

    if game_index == None:
      game_index = len( self.amount_won )

    # Check each individual number of the ticket against the winning numbers.
    for num in self.numbers:
      if num in winning_numbers.numbers:
        nums_correct = nums_correct + 1
      elif num in winning_numbers.bonus_numbers:
        bonus_correct = bonus_correct + 1

    # Now see if we won anything.
    for (key, value) in g_prizes.items():
      main_nums = key[0]
      bonus_nums = key[1]

      if (main_nums == nums_correct) and ((bonus_nums == bonus_correct) or (bonus_nums == 0)):
        if not g_prizes_won.has_key( value ):
          g_prizes_won[value] = 0

        g_how_many_tickets_won = g_how_many_tickets_won + 1
        g_prizes_won[value] = g_prizes_won[value] + 1  # Increment map of how many times each prize won. 

        # We won.   Now figure out what we won.
        if g_prize_amounts[value] > amount_won:
          amount_won = g_prize_amounts[value]
          tickets_won = 0
        elif g_prize_amounts[value] == -1:
          amount_won = 0
          tickets_won = 1

    if amount_won or tickets_won:
      if g_config['verbose']:
        print( "Ticket %s won: $%d & %d Free tickets." % (str(self.numbers), amount_won, tickets_won) )

    # Set and return the amount won.
    self.amount_won[game_index] = amount_won
    self.tickets_won[game_index] = tickets_won
    return (amount_won, tickets_won)


class QuickPick( PlayedTicket ):
  '''Represents a Quick Pick ticket.'''

  def __init__( self, numbers ):
    '''Constructor.
    @param numbers: The numbers for this ticket.'''

    super( QuickPick, self ).__init__( numbers )
    self.amount_won = {}  # Map of Game Index to $$ won each play.
    self.tickets_won = {}   # Map of Game Index to Free Tickets won each play.

  def name( self ):
    '''Return the name of this class type.'''
    return "QuickPick"


class WinningNumbers( TicketNumbers ):
  '''Represents the winning numbers + bonus number.
  @var bonus_numbers: The bonus numbers.'''

  def __init__( self, numbers, bonus_numbers=None ):
    '''Constructor.
    @param numbers: The winning numbers.
    @param bonus_numbers: (optional) The bonus number(s).  Defaults to empty list.'''

    global g_config
    bonus_numbers = bonus_numbers or []
    super( WinningNumbers, self ).__init__( numbers )
    self.bonus_numbers = []
    if bonus_numbers:
      self.bonus_numbers = sorted( bonus_numbers )
    tmp = []

    if len( bonus_numbers ) != int(g_config['how_many_bonus']):
      raise Exception( "You must have %d bonus numbers!" % int(g_config['how_many_bonus']) )

    for num in bonus_numbers:
      if (num in tmp) or (num in self.numbers):
        raise Exception( "You cannot have duplicate numbers!" )
      tmp.append( num )

  def __str__( self ):
    '''Converts this class into a string.'''
    ret = super( WinningNumbers, self ).__str__()

    str_bnums = []

    if len( self.bonus_numbers ):
      for i in range( len( self.bonus_numbers ) ):
        str_bnums.append( str( self.bonus_numbers[i] ) )

      ret = ret + ' + ' + ', '.join( str_bnums )

    return ret


  def name( self ):
    '''Return the name of this class type.'''
    return "WinningNumbers"


def load_config( filename ):
  '''Reads the config file to load program defaults.
  @param filename: The filename of the config file.
  @return: A ConfigParser object containing the parsed config file.'''

  # Read the config file into the parser.
  global g_config, g_prizes, g_prize_amounts
  conf = ConfigParser.SafeConfigParser()
  conf.read( filename )

  if (not conf.has_section( 'GameType' )) or (not conf.has_section( 'Prizes' )):
    raise Exception( "Invalid config file!  [GameType] and/or [Prizes] sections are missing!" )

  options = ['min_number', 'max_number', 'how_many_numbers', 'how_many_bonus', 'how_many_hand_picked_lines', 'how_many_quick_pick_lines', 'cost_per_ticket', 'quick_pick_pool_size', 'how_many_tickets_bought']

  for option in options:
    if not conf.has_option( 'GameType', option ):
      raise Exception( "Invalid config file!  '%s' option is missing!" % option )

    g_config[option] = int( conf.get( 'GameType', option ) )

  # Prizes section contains maps.
  g_prizes = ast.literal_eval( conf.get( 'Prizes', 'prizes' ) )
  g_prize_amounts = ast.literal_eval( conf.get( 'Prizes', 'prize_amounts' ) )


def load_tickets_conf( filename ):
  '''Loads tickets from a config file.
  @param filename: The filename to load the tickets from.
  @return: A map of # of tickets to an array of Tickets.'''

  # Read the config file into the parser.
  conf.read( filename )
  ticket_map = {}

  # First read the 'all_groups' option that contains a list of all ticket groups.
  if not conf.has_section( 'AllTicketGroups' ):
    raise Exception( "Invalid config file!  [AllTicketGroups] section is missing!" )

  all_groups = parse_number_line( conf.get( 'AllTicketGroups', 'all_groups' ) )

  # Then read each ticket group and add the tickets to the map.
  for group in all_groups:
    if not conf.has_section( str( group ) ):
      raise Exception( "Invalid config file!  [%d] section is missing!" % group )

    tickets = []

    for i in range( 1, group + 1 ):
      if not conf.has_option( str( group ), str( i ) ):
        raise Exception( "Invalid config file!  There is no '%d' option under the [%d] section!" % (i, group) )

      numbers = parse_number_line( conf.get( str( group ), str( i ) ) )
      ticket = PlayedTicket( numbers )
      tickets.append( ticket )

    ticket_map[group] = tickets

  return ticket_map


def load_tickets_txt( filename, ticket_class = PlayedTicket ):
  '''Loads tickets from a text file.
  @param filename: The filename to load the tickets from.
  @param ticket_class: (optional) The class name for the type of tickets you want returned.
  @return: A map of # of tickets to an array of Tickets.'''

  global g_config

  # Read the file into an array of lines.
  fd = open( filename, 'r' )
  lines = fd.readlines()
  fd.close()

  # Create the hand picked list of tickets.
  ticket_list = parse_number_lines( lines, ticket_class )
  number_of_tickets = len( ticket_list )

  if ticket_class != QuickPick:
    # Add the appropriate number of quick picks.
    num_quick_picks = number_of_tickets * g_config['how_many_quick_pick_lines']
    ticket_list.extend( quick_picks( num_quick_picks ) )

  tickets = {number_of_tickets : ticket_list}

  return tickets


def load_winning_numbers( lines ):
  '''Loads the winning numbers from a CSV file and returns an array of WinningNumbers.
  @param lines: The lines of the CSV containing the winning numbers.
  @return: An array of WinningNumbers objects.'''

  global g_config

  numbers = []
  winning_tickets = []

  # Parse the string lines into arrays of numbers.
  for line in lines:
    numbers.append( parse_number_line( line ) )

  how_many_numbers = g_config['how_many_numbers']
  how_many_bonus = g_config['how_many_bonus']

  # Create an array of WinningTickets.
  for line in numbers:
    if len( line ) != (how_many_numbers + how_many_bonus):
      raise Exception( "Invalid winning numbers CSV file!  There should be %d numbers, but we found %d!" % (how_many_numbers + how_many_bonus, len( line ) ) )
    else:
      nums = line[0:how_many_numbers]
      bonus = line[-how_many_bonus:]
      ticket = WinningNumbers( nums, bonus )
      winning_tickets.append( ticket )

  return winning_tickets


def parse_number_lines( lines, ticket_class = PlayedTicket ):
  '''Parses an array of strings (comma delimited number lines).
  @param lines: An array of strings (comma delimited number lines).
  @param ticket_class: (optional) The class name for the type of tickets you want returned.
  @return: An array of tickets of the type specified by ticket_class.'''

  tickets = []

  for line in lines:
    numbers = parse_number_line( line )
    tickets.append( ticket_class( numbers ) )

  return tickets


def parse_number_line( line ):
  '''Parses a string (comma delimited number line).
  @param line: A string (comma delimited number line).
  @return: An array of sorted numbers.'''

  nums = line.split( ',' )
  numbers = []

  for num in nums:
    numbers.append( int( num.strip() ) )

  return sorted( numbers )


def quick_pick():
  '''Returns a QuickPick ticket.
  @return: A QuickPick ticket.'''

  global g_config, g_quick_pick_file
  random.seed()
  numbers = []

  while len( numbers ) < g_config['how_many_numbers']:
    num = random.randrange( g_config['min_number'], g_config['max_number'], 1 )  # From 1 to 49, step by 1.

    if not num in numbers:
      numbers.append( num )

  # If we're using a quick pick file, this function should never be called, so print this to let us know.
  if g_config['verbose'] and g_quick_pick_file:
    print( "Warning: You specified a quick pick file, but quick_pick() was called and returned: %s" % str(numbers) )

  return QuickPick( numbers )


def quick_picks( number_of_tickets, quick_pick_list=None ):
  '''Returns a list of QuickPick tickets.
  @param number_of_tickets: The number of QuickPick tickets to generate.
  @param quick_pick_list: (optional) Get as many quick picks from this list instead of generating new ones.
  @return: A list of QuickPick tickets.'''

  tickets = []

  if quick_pick_list:
    if len( quick_pick_list ) >= number_of_tickets:
      tickets.extend( quick_pick_list[0:number_of_tickets] )
    else:
      tickets.extend( quick_pick_list )

      for i in range( number_of_tickets - len( quick_pick_list ) ):
        tickets.append( quick_pick() )
  else:
    for i in range( number_of_tickets ):
      tickets.append( quick_pick() )

  return tickets


def single_play( played_tickets, winning_numbers ):
  '''Compares tickets to winning numbers and calculates what each ticket won.
  The 'amount_won' member will be set for any tickets that won.
  @param played_tickets: An array of PlayedTickets.
  @param winning_numbers: A WinningNumbers object.
  @return: A tuple of ($$ won, # free tickets won).'''

  global g_total_money_won, g_total_tickets_won, g_config, g_how_many_tickets_played
  total_money = 0
  total_tickets = 0
  num_tickets = len( played_tickets )

  # Compare each ticket against the winning numbers.
  for i in range( 0, num_tickets - 1 ):
    ticket = played_tickets[i]
    g_how_many_tickets_played = g_how_many_tickets_played + 1

    if g_config['verbose']:
      print( "[%s] = %s" % (played_tickets[i].name(), str(played_tickets[i])) )

    (money_won, tickets_won) = ticket.compare_with( winning_numbers )
    total_money = total_money + money_won
    total_tickets = total_tickets + tickets_won

  # Keep track of the totals won over all.
  g_total_money_won = g_total_money_won + total_money
  g_total_tickets_won = g_total_tickets_won + total_tickets

  return (total_money, total_tickets)


def get_tickets( played_ticket_map, how_many_tickets, keys ):
  '''Returns an array of tickets of the specified size, with as many hand_picked tickets as possible.
  @param played_ticket_map: A map of # of tickets to array of PlayedTickets to choose from.
  @param how_many_tickets: The number of tickets to be returned.
  @param keys: The sorted list of keys in played_ticket_map.
  @return: An array of tickets of the specified size, with as many hand_picked tickets as possible.'''

  global g_ticket_quick_picks
  tickets = []

  # If any of the ticket pools have the exact number of tickets we need, use it; otherwise add quick picks
  # to a ticket pool that's smaller than we need until we have the right number of tickets.
  if how_many_tickets in keys:
    tickets = copy.deepcopy(played_ticket_map[how_many_tickets] )
  else:
    # Find next ticket pool lower than how_many_tickets and add quick picks to fill up the rest.
    last_key_index = -1

    for i in keys:
      if i > how_many_tickets:
        break

      last_key_index = i

    if last_key_index == -1:
      # Looks like we're using all quick picks.
      print( "*** get_tickets() is adding all %d quick picks" % how_many_tickets )           # DEBUG
      tickets = quick_picks( how_many_tickets, g_ticket_quick_picks )
    else:
      if (how_many_tickets - last_key_index) > 50:
        print( "*** get_tickets() is adding %d quick picks" % (how_many_tickets - last_key_index) )  # DEBUG
      tickets = copy.deepcopy( played_ticket_map[last_key_index] )
      tickets.extend( quick_picks( how_many_tickets - last_key_index, g_ticket_quick_picks ) )

  return tickets


def multiple_plays( played_ticket_map, quick_pick_list_const, winning_numbers, number_of_tickets ):
  '''Compares tickets to an array of winning numbers and calculates what each ticket won over x number of games played.
  The 'amount_won' member will be set for any tickets that won.
  @param played_ticket_map: A map of # of tickets to array of PlayedTickets to choose from.
  @param quick_pick_list_const: A list of QuickPick tickets to choose from.
  @param winning_numbers: An array of WinningNumbers objects.
  @param number_of_tickets: The usual number of tickets bought (assuming no extra or free tickets).
  @return: A tuple of: (extra_tickets, extra_quick_picks, money_left_over)'''

  global g_ticket_quick_picks, g_config

  extra_tickets = 0
  extra_quick_picks = 0
  money_left_over = 0

  amount_won = 0
  tickets_won = 0
  game_num = 0
  keys = sorted( played_ticket_map.keys() )

  # For each winning ticket, check if any of our tickets won.
  for winning_ticket in winning_numbers:
    total_tickets_won = 0
    total_amount_won = 0
    game_num += 1

    if g_config['verbose']:
      print( "Winning numbers are: %s" % winning_ticket )

    # Check the hand picked tickets.
    how_many_tickets = number_of_tickets + extra_tickets
    tickets = get_tickets( played_ticket_map, how_many_tickets, keys )

    # Play the numbers we picked.
    (amount_won, tickets_won) = single_play( tickets, winning_ticket )
    for ticket in tickets:
      del ticket
    del tickets
    total_amount_won = total_amount_won + amount_won
    total_tickets_won = total_tickets_won + tickets_won

    # Check the quick picks.
    how_many_quick_picks = (g_config['how_many_quick_pick_lines'] * number_of_tickets) + extra_quick_picks

    # Make a copy of quick_pick_list_const.
    quick_pick_list = quick_pick_list_const[0:len(quick_pick_list_const)]

    # If the quick_pick pool isn't big enough, increase the pool size.
    if len( quick_pick_list ) < how_many_quick_picks:
      quick_pick_list.extend( quick_picks( how_many_quick_picks - len(quick_pick_list), g_ticket_quick_picks ) )

    # Now play the quick picks.
    (amount_won, tickets_won) = single_play( quick_pick_list[0:how_many_quick_picks], winning_ticket )
    total_amount_won = total_amount_won + amount_won
    total_tickets_won = total_tickets_won + tickets_won

    # Now change the number of quick picks and extra money to reflect what we just won.
    if g_config['verbose']:
      print( "[Game %d]: We won: $%d, and %d Free Tickets.\n\n" % (game_num, total_amount_won, total_tickets_won) )

    extra_tickets = int( (total_amount_won + money_left_over) / g_config['cost_per_ticket'] )
    money_left_over = total_amount_won + money_left_over - (extra_tickets * g_config['cost_per_ticket'])
    extra_quick_picks = total_tickets_won
  return (extra_tickets, extra_quick_picks, money_left_over)


def main():
  '''The start of the program.'''

  global g_ticket_quick_picks, g_quick_pick_file, g_config, g_total_money_won, g_total_tickets_won, g_prizes_won, \
    g_how_many_tickets_played, g_how_many_tickets_won

  # Create the arg parser.
  usage = """\
usage: %prog -c <config_file> -t <tickets_file> -w <winning_numbers_file> [-q <quick_pick_file>] [-o <quick_pick_output_file>] [-T] [-v]
   or: %prog -c <config_file> -o <quick_pick_output_file> [-v]
  -c, --config       The main config file.
  -t, --tickets      The tickets config file.
  -o, --output       Will dump some quick pick numbers into this file.
  -w, --winning-numbers  The winning numbers csv file.
  -d, --debug        Debug mode.
  -T, --text         Read tickets_file as a text file instead of a config file.
  -v, --verbose      Enable verbose mode."""
  parser = OptionParser( usage=usage )
  parser.add_option( '-c', '--config', dest='config_file' )
  parser.add_option( '-t', '--tickets', dest='tickets_file' )
  parser.add_option( '-o', '--output', dest='output_file' )
  parser.add_option( '-q', '--quick-pick', dest='quick_pick_file' )
  parser.add_option( '-w', '--winning-numbers', dest='winning_numbers' )
  parser.add_option( '-d', '--debug', action='store_true', dest='debug_mode' )
  parser.add_option( '-T', '--text', action='store_true', dest='text_mode' )
  parser.add_option( '-v', '--verbose', action='store_true', dest='verbose' )
  (options, args) = parser.parse_args()

  if len( args ):
    parser.error( "The following arguments are unrecognized: %s" % str( args ) )

  if (not options.config_file):
    parser.error( "--config is a required parameter!" )

  # Load the main config file.
  load_config( options.config_file )
  ticket_map = None
  g_config['debug_mode'] = False
  g_config['verbose'] = False

  if options.debug_mode:
    g_config['debug_mode'] = True

  if options.verbose:
    g_config['verbose'] = True

  if g_config['verbose']:
    # Print out current configuration.
    print( "min_number = %s" % g_config['min_number'] )
    print( "max_number = %s" % g_config['max_number'] )
    print( "how_many_numbers = %s" % g_config['how_many_numbers'] )
    print( "how_many_bonus = %s" % g_config['how_many_bonus'] )
    print( "how_many_hand_picked_lines = %s" % g_config['how_many_hand_picked_lines'] )
    print( "how_many_quick_pick_lines = %s" % g_config['how_many_quick_pick_lines'] )
    print( "cost_per_ticket = %s" % g_config['cost_per_ticket'] )
    print( "quick_pick_pool_size = %s" % g_config['quick_pick_pool_size'] )
    print( "how_many_tickets_bought = %s" % g_config['how_many_tickets_bought'] )
    print( "verbose mode = %s\n" % str(g_config['verbose']) )


  # Load the quick pick pool from a file or generate the numbers.
  quick_pick_pool = []
  if options.quick_pick_file:
    g_quick_pick_file = options.quick_pick_file
    quick_pick_map = load_tickets_txt( options.quick_pick_file, QuickPick )
    for key, val in quick_pick_map.items():
      quick_pick_pool = val
  else:
    quick_pick_pool = quick_picks( g_config['quick_pick_pool_size'] )


  if (options.output_file):   # Just output some quick picks.
    if ((options.tickets_file) or (options.winning_numbers) or (options.text_mode)):
      parser.error( "--output cannot be used with --tickets or --winning-numbers or --text!" )

    else:
      # Just output some quick picks to a file.
      fd = open( options.output_file, 'w' )
      for quick_pick in quick_pick_pool:
        fd.write( str( quick_pick ) + '\n' )
      fd.close()
  else:   # Play the games.
    # Move half the quick pick pool to g_ticket_quick_picks for use with normal tickets.
    for i in range( (len(quick_pick_pool) / 2) ):
      g_ticket_quick_picks.append( quick_pick_pool.pop(0) )

    if (not options.tickets_file) or (not options.winning_numbers):
      parser.error( "Incorrect number of arguments!" )
    else:
      # Load the tickets & winning numbers.
      if options.text_mode:
        ticket_map = load_tickets_txt( options.tickets_file )
      else:
        ticket_map = load_tickets_conf( options.tickets_file )

      # Read the file into an array of lines.
      fd = open( options.winning_numbers, 'r' )
      lines = []
      extra_tickets   = 0
      extra_quick_picks = 0
      money_left_over   = 0

      while True:
        for i in range( 1000 ):
          line = fd.readline()
          if not line:
            break
          lines.append( line )
        if not lines:
          break

        winning_numbers = load_winning_numbers( lines )

        # Start the simulation.
        (extra_tickets, extra_quick_picks, money_left_over) = multiple_plays( ticket_map, quick_pick_pool, winning_numbers, g_config['how_many_tickets_bought'] )
        del lines
        lines = []

      print( "After %d games, we won a total of $%d and %d Free Tickets ($%d)." % (len( winning_numbers ), g_total_money_won, g_total_tickets_won, (g_total_tickets_won * 5 + g_total_money_won)) )
      print( "We played %d ticket lines and %d lines won." % (g_how_many_tickets_played, g_how_many_tickets_won) )
      fd.close()

      # Now display the results of all the games.
      for (key, value) in sorted( g_prizes.items() ):
        if g_prizes_won.has_key( value ):
          print( "  '%s' won, %d times." % (value, g_prizes_won[value]) )


if __name__ == "__main__":
  main()

Вот пример конфигурационного файла можно использовать (с ... конфиг опция) для запуска программы (я буду называть это лото.конф):

# Stores config info for the lotto simulation program.

[GameType]
min_number = 1
max_number = 49
how_many_numbers = 7
how_many_bonus = 1
how_many_hand_picked_lines = 1
how_many_quick_pick_lines = 2
cost_per_ticket = 5
quick_pick_pool_size = 10000
how_many_tickets_bought = 20

[Prizes]
# prizes = {(numbers matched, bonus matched) : $$ won, ...}
prizes = {(3, 0) : '3', (3, 1) : '3+1', (4, 0) : '4', (5, 0) : '5', (6, 0) : '6', (6, 1) : '6+1', (7, 0) : '7'}

# -1 means Free Ticket.
prize_amounts = {'3' : -1, '3+1' : 20, '4' : 20, '5' : 134, '6' : 6145, '6+1' : 325676, '7' : 15000000}

и образец билетов файла вы можете использовать (с ... авиабилеты вариант), я буду называть это авиабилеты.конф:

[AllTicketGroups]
all_groups = 7, 13

[7]
1 = 1, 2, 3, 4, 5, 6, 7
2 = 8, 9, 10, 11, 12, 13, 14
3 = 15, 16, 17, 18, 19, 20, 21
4 = 22, 23, 24, 25, 26, 27, 28
5 = 29, 30 ,31, 32, 33, 34, 35
6 = 36, 37, 38, 39, 40, 41, 42
7 = 43, 44, 45, 46, 47, 48, 49

[13]
1 = 1, 2, 3, 4, 5, 6, 7
2 = 8, 9, 10, 11, 12, 13, 14
3 = 15, 16, 17, 18, 19, 20, 21
4 = 22, 23, 24, 25, 26, 27, 28
5 = 29, 30 ,31, 32, 33, 34, 35
6 = 36, 37, 38, 39, 40, 41, 42
7 = 43, 44, 45, 46, 47, 48, 49
8 = 1, 3, 5, 7, 9, 11, 13
9 = 2, 4, 6, 8, 10, 12, 14
10 = 15, 17, 19, 21, 23, 25, 27
11 = 16, 18, 20, 22, 24, 26, 28
12 = 29, 31, 33, 35, 37, 39, 41
13 = 30, 32, 34, 36, 38, 40, 42

Для создания winning_numbers.с CSV - файл (используется с выигрыша-число вариант) редактирование лото.conf и просто изменить how_many_hand_picked_lines на равную 8 вместо 7 и установить quick_pick_pool_size, чтобы количество линий, количество, которое вы хотите создать, а затем выполните:

./lotto.py --конфиг лото.конф --выход winning_numbers.КШМ

Затем выполнить моделирование, отменить изменения в лото.conf и запустить: ./lotto.py -с Лотто.конф -Т билетов.конф -з winning_numbers.КШМ

Но чтобы сделать тесты повторимый, я также добавлена возможность включать в себя предварительно созданные наборы быстрая выборка в файл (который можно использовать в этой команде параметр-Q).



1433
3
задан 15 августа 2011 в 03:08 Источник Поделиться
Комментарии
1 ответ

#!/usr/bin/python

import random, ConfigParser, ast, copy
from optparse import OptionParser

# Global variables.
g_config = {} # Stores GameType config options.
g_prizes = {} # Stores prizes from config file.
g_prize_amounts = {} # Stores prize amounts from config file.
g_ticket_quick_picks = [] # Store an array of QuickPicks to use for tickets.
g_total_money_won = 0
g_total_tickets_won = 0
g_prizes_won = {} # Format: {'3':3, '3+1':2, '4':2 ...}
g_quick_pick_file = ''
g_how_many_tickets_played = 0
g_how_many_tickets_won = 0

В Python, рекомендуется не иметь глобальных переменных. Я обычно только положить константы на глобальном уровне, и в соответствии с руководством по стилю Python они должны быть написаны WITH_ALL_CAPS. Мне бы перенести все эти данные в какой-то объект.

class TicketNumbers( object ):
'''Represents the numbers of a ticket (or the 7 winning numbers).
@var numbers: A sorted array of 7 unique numbers from 1-49.'''

Учитывая тот факт, что позже вы сортировать, зачем указывать, что она должна быть отсортированы на входе?

    def __init__( self, numbers ):
'''Constructor.
@param numbers: The numbers for this ticket.'''

self.numbers = numbers

Вы сюда припишем к числам, но _set_numbers также возлагает на него. Это бессмысленно.

        self._set_numbers( numbers )

def name( self ):
'''Return the name of this class type.'''
return "TicketNumbers"

Какой в этом смысл? Если вы хотите, чтобы имя класса использовать параметр obj.__класс__.__имя__

    def __str__( self ):
'''Returns a string of the number list.'''
str_nums = []
for i in range( len( self.numbers ) ):

Там почти никогда не нужно использовать диапазон( лен( шаблон контейнера. Просто перебирать цифры напрямую.

            str_nums.append( str( self.numbers[i] ) )

Существует несколько способов написания этой петли еще проще:

str_nums = [str(number) for number in self.numbers]
str_nums = map(str, self.numbers)

return ', '.join( str_nums )

На самом деле, всю функцию можно записать в виде:

return ', '.join(str(number) for number in self.numbers)

def _set_numbers( self, numbers ):
'''Sets the numbers member variable and validates the numbers.
@param numbers: The numbers to assign.'''

global g_config
self.numbers = sorted( numbers )

if len( self.numbers ) != int(g_config['how_many_numbers']):
raise Exception( "You must have %d numbers, but found %d numbers: %s" % (g_config['how_many_numbers'], len( self.numbers ), str(self.numbers)) )

При проверке пользовательского ввода, я предлагаю создать специальный класс исключений. Затем вы должны поймать этого пользовательского класса и печатать только сообщение об ошибке, вместо того, чтобы по умолчанию в Python. Ваше сообщение об ошибке должно также дать лучшее представление о том, где цифры от.

        tmp = []

tmp-это ужасное имя переменной, потому что все локальные переменные являются временными.

        for num in self.numbers:
# Check if numbers are in range.
if ( num < int(g_config['min_number']) ) or ( num > int(g_config['max_number']) ):

Я рекомендую потянуть конфигурации данных в правильные типы внутри вашей логике конфигурации не в остальной части кода.

                raise Exception( "The numbers must be between %d & %d!" % (int(g_config['min_number']), int(g_config['max_number'])) )
# Check for duplicate numbers.
if num in tmp:
raise Exception( "You cannot have duplicate numbers! %d in %s" % (num, str( self.numbers )) )
tmp.append( num )

Я предлагаю разделить эти проверить в трех отдельных битов:

if min(self.numbers) < config.min_number:
raise UserError("Too low!")
if max(self.numbers) > config.max_number:
raise UserError("Too high!")
if len(set(self.numbers)) != len(self.numbers):
raise UserError("duplicates!")

class PlayedTicket( TicketNumbers ):
'''Represents a ticket that was played.
@var amount_won: How much $$ this ticket won.'''

Авиабилет-это не особый вид TicketNumbers. Имена предлагают, что билет должен содержать цифры.

    def __init__( self, numbers ):
'''Constructor.
@param numbers: The numbers for this ticket.'''

super( PlayedTicket, self ).__init__( numbers )
self.amount_won = {} # Map of Game Index to $$ won each play.
self.tickets_won = {} # Map of Game Index to Free Tickets won each play.

def name( self ):
'''Return the name of this class type.'''
return "PlayedTicket"

def compare_with( self, winning_numbers, game_index=None ):
'''Compares this ticket with the specified winning numbers and sets the amount_won & tickets_won members accordingly.
@param winning_numbers: The winning numbers to compare against.
@param game_index: (optional) The game index for this game. Defaults to 1 + the number of games recorded.
@return: A tuple of ($$ won, # free tickets won).'''

В compare_with имя не предполагают, что этот метод изменяет состояние объекта. Я советую выбрать номер, который будет.

        global g_config, g_prizes, g_prize_amounts, g_prizes_won, g_how_many_tickets_won
nums_correct = 0
bonus_correct = 0
amount_won = 0
tickets_won = 0

if game_index == None:
game_index = len( self.amount_won )

# Check each individual number of the ticket against the winning numbers.
for num in self.numbers:
if num in winning_numbers.numbers:
nums_correct = nums_correct + 1
elif num in winning_numbers.bonus_numbers:
bonus_correct = bonus_correct + 1

Цифры действительно работает как устанавливает не перечислены. То есть, вы не можете иметь дубликаты и порядок не имеет значения. Я предлагаю вам изменить с помощью списков с использованием наборов. Затем вы можете просто найти пересечение номера билетов и выигрышный номер, а затем, нуждающихся в цикле подсчета.

        # Now see if we won anything.
for (key, value) in g_prizes.items():
main_nums = key[0]
bonus_nums = key[1]

Использовать
main_nums, bonus_nums = ключ

            if (main_nums == nums_correct) and ((bonus_nums == bonus_correct) or (bonus_nums == 0)):

Словарь предназначен для обеспечения быстрого поиска. Вы не должны перебирать его. Вы должны использовать:

 value = g_prizes[(nums_correct, bonus_correct)]

Чтобы напрямую получить значение за приз вы выиграли.

                if not g_prizes_won.has_key( value ):
g_prizes_won[value] = 0

Сделать g_prizes_won в коллекции.defaultdict(инт) объекта. Затем он будет автоматически действовать так, как будто все неопределенные значения-ноль и этот кусок кода будет излишним.

                g_how_many_tickets_won = g_how_many_tickets_won + 1
g_prizes_won[value] = g_prizes_won[value] + 1 # Increment map of how many times each prize won.

Если вы нарушите эти глобальные переменные в объект, как я предлагаю, то куча это должно жить в метод этого объекта.

                # We won.   Now figure out what we won.
if g_prize_amounts[value] > amount_won:
amount_won = g_prize_amounts[value]
tickets_won = 0
elif g_prize_amounts[value] == -1:
amount_won = 0
tickets_won = 1

Вы выбираете лучший приз из всех тех, что вы имеете право на. Однако, как упоминалось ранее, вы должны быть в состоянии непосредственно извлечь именно приз вы должны сделать без зацикливания. Поэтому нет необходимости, чтобы получить лучший приз из тех, что вы рассматривали.

        if amount_won or tickets_won:
if g_config['verbose']:
print( "Ticket %s won: $%d & %d Free tickets." % (str(self.numbers), amount_won, tickets_won) )

# Set and return the amount won.
self.amount_won[game_index] = amount_won
self.tickets_won[game_index] = tickets_won
return (amount_won, tickets_won)

class QuickPick( PlayedTicket ):
'''Represents a Quick Pick ticket.'''

def __init__( self, numbers ):
'''Constructor.
@param numbers: The numbers for this ticket.'''

super( QuickPick, self ).__init__( numbers )
self.amount_won = {} # Map of Game Index to $$ won each play.
self.tickets_won = {} # Map of Game Index to Free Tickets won each play.

Подкласс уже создает такие, зачем вы снова их создания

    def name( self ):
'''Return the name of this class type.'''
return "QuickPick"

Этот класс ничего не делал.

class WinningNumbers( TicketNumbers ):
'''Represents the winning numbers + bonus number.
@var bonus_numbers: The bonus numbers.'''

def __init__( self, numbers, bonus_numbers=None ):
'''Constructor.
@param numbers: The winning numbers.
@param bonus_numbers: (optional) The bonus number(s). Defaults to empty list.'''

global g_config
bonus_numbers = bonus_numbers or []
super( WinningNumbers, self ).__init__( numbers )
self.bonus_numbers = []
if bonus_numbers:
self.bonus_numbers = sorted( bonus_numbers )

Нет необходимости, чтобы проверить, если bonus_numbers пуст, просто дайте ему назначить пустой упорядоченный список.

        tmp = []

if len( bonus_numbers ) != int(g_config['how_many_bonus']):
raise Exception( "You must have %d bonus numbers!" % int(g_config['how_many_bonus']) )

for num in bonus_numbers:
if (num in tmp) or (num in self.numbers):
raise Exception( "You cannot have duplicate numbers!" )
tmp.append( num )

def __str__( self ):
'''Converts this class into a string.'''
ret = super( WinningNumbers, self ).__str__()

str_bnums = []

if len( self.bonus_numbers ):
for i in range( len( self.bonus_numbers ) ):
str_bnums.append( str( self.bonus_numbers[i] ) )

ret = ret + ' + ' + ', '.join( str_bnums )

return ret

def name( self ):
'''Return the name of this class type.'''
return "WinningNumbers"

def load_config( filename ):
'''Reads the config file to load program defaults.
@param filename: The filename of the config file.
@return: A ConfigParser object containing the parsed config file.'''

# Read the config file into the parser.
global g_config, g_prizes, g_prize_amounts
conf = ConfigParser.SafeConfigParser()
conf.read( filename )

if (not conf.has_section( 'GameType' )) or (not conf.has_section( 'Prizes' )):

Слишком много parantehsis, используйте:

    if not conf.has_section( 'GameType' ) or not conf.has_section( 'Prizes' ):

raise Exception( "Invalid config file! [GameType] and/or [Prizes] sections are missing!" )

options = ['min_number', 'max_number', 'how_many_numbers', 'how_many_bonus', 'how_many_hand_picked_lines', 'how_many_quick_pick_lines', 'cost_per_ticket', 'quick_pick_pool_size', 'how_many_tickets_bought']

for option in options:
if not conf.has_option( 'GameType', option ):
raise Exception( "Invalid config file! '%s' option is missing!" % option )

g_config[option] = int( conf.get( 'GameType', option ) )

# Prizes section contains maps.
g_prizes = ast.literal_eval( conf.get( 'Prizes', 'prizes' ) )
g_prize_amounts = ast.literal_eval( conf.get( 'Prizes', 'prize_amounts' ) )

(Остальной код выполняется.)

12
ответ дан 15 августа 2011 в 04:08 Источник Поделиться