2011-02-02 9 views
5

Il mondo contiene agenti in luoghi diversi, con un solo agente in qualsiasi luogo. Ogni agente sa dove si trova, ma ho anche bisogno di controllare rapidamente se c'è un agente in un determinato luogo. Quindi, mantengo anche una mappa dalle posizioni agli agenti. Ho un problema nel decidere dove questa mappa appartiene a: class World, class Agent (come attributo di classe) o altrove.Quale classe deve memorizzare la tabella di ricerca?

Di seguito è stata inserita la tabella di ricerca agent_locations in class World. Ma ora gli agenti devono chiamare lo world.update_agent_location ogni volta che si spostano. Questo è molto fastidioso; cosa succede se deciderò in seguito di tenere traccia di altre cose sugli agenti, a prescindere dalle loro ubicazioni? Dovrei aggiungere le chiamate all'oggetto world in tutto il codice Agent?

class World: 
    def __init__(self, n_agents): 
    # ... 
    self.agents = [] 
    self.agent_locations = {} 
    for id in range(n_agents): 
     x, y = self.find_location() 
     agent = Agent(self,x,y) 
     self.agents.append(agent) 
     self.agent_locations[x,y] = agent 
    def update_agent_location(self, agent, x, y): 
    del self.agent_locations[agent.x, agent.y] 
    self.agent_locations[x, y] = agent 
    def update(self): # next step in the simulation 
    for agent in self.agents: 
     agent.update() # next step for this agent 
    # ... 

class Agent: 
    def __init__(self, world, x, y): 
    self.world = world 
    self.x, self.y = x, y 
    def move(self, x1, y1): 
    self.world.update_agent_location(self, x1, y1) 
    self.x, self.y = x1, y1 
    def update(): 
    # find a good location that is not occupied and move there 
    for x, y in self.valid_locations(): 
     if not self.location_is_good(x, y): 
     continue 
     if self.world.agent_locations[x, y]: # location occupied 
     continue 
     self.move(x, y) 

posso invece mettere in agent_locationsclass Agent come un attributo di classe. Ma funziona solo quando ho un singolo oggetto World. Se in seguito decidessi di istanziare più oggetti World, le tabelle di ricerca dovrebbero essere specifiche per il mondo.

Sono sicuro che c'è una soluzione migliore ...

EDIT: ho aggiunto qualche riga per il codice per mostrare come agent_locations viene utilizzato. Si noti che è utilizzato solo dagli oggetti interni Agent, ma non so se questo rimarrà il caso per sempre.

+0

Non dovresti aggiornare 'Agent.agent_locations' a prescindere? Quanto deve essere "veloce" il tuo controllo rapido? Quanti agenti hai? Se non ne hai così tanti (relativi), potresti semplicemente ripetere gli agenti di World, controllandone le posizioni. – vicvicvic

+0

Quando l'agente decide dove muoversi, controlla che non incontri altri agenti. Se eseguo iterazioni su tutti gli agenti per verificarlo, finirò per cercare la posizione di ciascun agente (eseguendo operazioni 'n_agents') ogni volta che un agente si sposta. L'aggiornamento della tabella di ricerca richiede una sola operazione per 'move'. – max

+0

Il codice che hai fornito non lo controlla? 'update_agent_location' sovrascrive chi era a' (x, y) 'prima ... In ogni caso, spostare la tabella di ricerca nella classe' Agent' può davvero risolvere qualcosa? Non dovresti ancora chiamare 'Agent.update_agent_location'? – vicvicvic

risposta

1

Aiuta con OOP per parlare gli oggetti in termini di è un e ha un. A Worldha un elenco di Agents e un elenco di Locations. A Locationha unAgent. Un Agentha unLocation e uno World.

class Agent: 
    def __init__(self, world): 
     self.location = None 
     self.world = world 

    def move(self, new_location): 
     if self.location is not None: 
      self.location.agent = None 
     new_location.agent = self 
     self.location = new_location 

    def update(self): 
     for new_location in self.world.locations: 
      if self.location_is_good(new_location): 
       self.move(new_location) 

    def location_is_good(self, location): 
     if location.agent is not None: 
      return False 

class Location: 
    def __init__(self, x, y): 
     self.x = x 
     self.y = y 
     self.agent = None 

passare attraverso l'esercizio mentale di aggiungere un nuovo attributo a un Location, come ad esempio terreni, ed è facile vedere i benefici di tale incapsulamento. Allo stesso modo, l'aggiunta di nuove cose a Agent, come un'arma, richiede solo una funzione specifica per l'arma simile a move(). Si noti che il World non ha bisogno di essere coinvolto nel move() affatto. La mossa viene gestita rigorosamente tra Agent e Location.

+0

Non sarebbe pericoloso avere la tabella di ricerca memorizzata in una classe ('Location') ma aggiornata da un'altra (' Agent')? Ho sempre cercato di evitare di toccare interni di una classe da un'altra. – max

+0

In realtà è un [paradigma piuttosto comune] (http://docs.python.org/tutorial/classes.html#odds-and-ends) in python. La convenzione deve prefisso i membri dei dati con un trattino basso se non li si vuole utilizzare in questo modo. Puoi sempre aggiungere i metodi 'get_agent()' e 'set_agent()' a 'Location' e usarli, ma non ti guadagna nulla in sicurezza. I metodi Getter e setter sono principalmente usati in Java e C++ per impedire alle persone di sottoclassi in modi non supportati. La cultura pitone è più educata e fidati degli altri ma fai cose pazze a tuo rischio. –

+0

Non me ne ero reso conto e stavo imitando il paradigma getter/setter in stile C++/Java con Python. Tuttavia, anche se non ho problemi ad accedere alla tabella di ricerca di un'altra classe, sono un po 'preoccupato. Ad esempio, qualsiasi modifica al comportamento desiderato della tabella di ricerca richiederà modifiche nel codice in tutto il mio progetto, e non solo in 'Location Location'. – max

0

Siamo spiacenti, non capisco il problema. "" "Quando l'agente decide dove muoversi, controlla che non incontri altri agenti" "". Evidentemente una posizione può avere 0 o 1 agente. Sicuramente la classe Location ha un attributo agent.

locn = self.where_to_move() 
if locn.agent is None: 
    self.move(locn) 
elif locn.agent is self: 
    raise ConfusedAgentError() 
else: 
    self.execute_plan_B() 
+0

Nel mio caso il "registro" principale degli agenti è semplicemente un 'elenco' dentro l'oggetto' world'. Nel tuo codice, il "registro" principale degli agenti è in base alla posizione, quindi il mio problema è effettivamente risolto. Ma poi gli altri problemi si presentano: come faccio a scorrere rapidamente tutti gli agenti? Nella tua soluzione la domanda è quindi, dove ripongo quella tabella di ricerca secondaria che consente una iterazione veloce attraverso gli agenti. – max

2

Va bene, penso che posso fornire la mia risposta, che è forse più un parere di un definitivo "fare questo" (non ho alcuna formazione in programmazione).

Penso che il numero agent_locations dovrebbe essere un membro di ciascuna istanza World.

Provo a pensare in termini di interfaccia principalmente. A mio avviso, la classe mondiale dovrebbe essere responsabile della gestione delle risorse del tuo mondo, in questo caso lo spazio. Poiché World è il gestore dello spazio, gli agenti dovrebbero chiedere al loro mondo se lo spazio è disponibile (cioè non occupato), non l'un l'altro. Pertanto, penso che la tua chiamata sia più appropriata sarebbe self.world.is_location_available(x, y) [1]

Questo rende naturale per il mondo essere responsabile della ricerca della disponibilità dello spazio dato. La classe mondiale potrebbe inoltre avere altre variabili che decidono se uno spazio è disponibile. E se ci fosse un boschetto lì? O qualcosa. Probabilmente hai già una sorta di tavolo per le tue coordinate (x, y) su ogni mondo. Essere "occupati" può essere una proprietà di quegli oggetti.

Inoltre: Your World conosce già lo stato di ogni agente ([(agent.x, agent.y) for agent in self.agents] [2]). Il dict agent_locations è essenzialmente un indice o una cache per queste proprietà, che pertanto appartiene a World.

Riguardo il dolore di inviare lo stato di nuovo allo World ... beh, non lo risolverete facendo invece Agent.Ma fare update_agent_location(self, agent, x, y) è completamente superfluo, dal x == agent.x; y == agent.y (se si invertono le linee dove lo si chiama). Potresti semplicemente avere un metodo in World, update_agent_state(self, agent), che World può utilizzare per aggiornare i suoi indici. Potresti anche inviare un parametro extra per descrivere il tipo di modifica dello stato (se non vuoi aggiornare tutte le proprietà eveytime).

class World(object): 
    # ... 
    def update_agent_state(self, agent, state_change=None): 
    # Update properties based on what changed, or 
    # drop state_change param and update everything everytime 
    if state_change == Agent.LOCATION_CHANGE: 
     self.agent_locations[agent.x, agent.y] = agent 
    elif state_change == Agent.WHATEVER: 
     pass 

class Agent(object): 
    LOCATION_CHANGE = 1 

    def update(self): 
    for x, y in self.valid_locations(): 
     if not self.can_move_to(x, y) 
     continue 

     self.move(x, y) 

    def can_move_to(self, x, y): 
    """Determines if x, y is a location where we can move.""" 
    if not self.world.is_location_available(x, y): 
     return False 
    if not self.has_money_to_travel_to(x, y): 
     return False 

    return True 

    def move(self, x, y): 
    """Moves to x, y and notifies world of state change.""" 
    self.x = x 
    self.y = y 

    self.world.update_agent_state(self, Agent.LOCATION_CHANGE) 

Qualcosa del genere (leggi le mie note a piè di pagina).

[1] A meno che, naturalmente, la "bontà" di una posizione non dipenda da altre variabili se lo spazio è libero. Per esempio. se devi solo passare a (x, y) se 1) la posizione è disponibile e 2) l'agente ha 1000 $ per pagare un biglietto, allora dovresti avere un Agent.can_move_to(x, y) che a sua volta chiama il metodo del mondo e controlla il suo portafoglio .

[2] Sto assumendo che il tuo self.agents = {} sia un errore di battitura, dal momento che non è possibile append su un dettato. Intendi un elenco ([]) giusto?

+0

1) Grazie, corretto l'errore di battitura. 2) 'update_agent_location' non è ridondante perché deve conoscere sia la precedente che le nuove coordinate (altrimenti non può cancellare il vecchio record).3) Sono d'accordo che non sto risolvendo alcun fastidio di programmazione passando alla classe 'Agent', ma almeno tutto rimane contenuto in una classe, quindi è più facile accertarsi che sia tutto coerente. 4) Mi piace la tua idea che il mondo gestisca lo spazio, quindi 'location_available' deve essere in' world'. 5) Sto anche pensando a un punto di vista alternativo, in cui tutto nel mio mondo è un database orientato agli oggetti, in cui le ricerche si adattano bene. – max

Problemi correlati