哪个类应存储查找表?

5 投票
3 回答
951 浏览
提问于 2025-04-16 11:07

这个世界里有很多代理(agent),每个地方只有一个代理。每个代理都知道自己在哪儿,但我还需要快速检查某个地方是否有代理。因此,我还维护了一个从地点到代理的地图。我现在面临一个问题:这个地图应该放在哪里?是放在 class World 里,还是放在 class Agent 里(作为类属性),或者放在其他地方。

在下面的例子中,我把查找表 agent_locations 放在了 class World 里。但是现在每当代理移动时,都得调用 world.update_agent_location。这非常麻烦;如果我后来决定要追踪代理的其他信息,而不仅仅是他们的位置,那我是不是还得在 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)

我也可以把 agent_locations 放在 class Agent 里作为类属性。但这样做只适合我只有一个 World 对象的情况。如果我后来决定要创建多个 World 对象,那查找表就得针对每个世界来设置了。

我相信一定有更好的解决方案……

补充:我在代码里加了一些行,展示了 agent_locations 是怎么用的。注意,它只是在 Agent 对象内部使用,但我不知道这种情况是否会一直保持下去。

3 个回答

0

抱歉,我不太明白这个问题。"""当代理决定移动到哪里时,他会检查确保自己不会碰到其他代理""". 很明显,一个地点最多只能有一个代理。肯定是Location类里有一个代理的属性。

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()
2

好的,我想我可以分享一下我的看法,这可能更多的是一种意见,而不是明确的“这样做”建议(我没有接受过正式的编程培训)。

我认为每个 World 实例应该有自己的 agent_locations

我主要是从接口的角度来思考。在我看来,World 类应该负责管理你世界里的资源,这里指的是空间。既然 World 是空间的管理者,那么代理(Agents)应该向它询问空间是否可用(也就是没有被占用),而不是互相询问。因此,我觉得你的 self.location_is_good 调用更合适的写法是 self.world.is_location_available(x, y) [1]

这样一来,世界就自然地负责检查给定空间的可用性。World 类可能还有其他变量来决定某个空间是否可用。比如,那里有没有灌木丛?或者其他东西。你可能已经在每个世界中有某种表格来记录你的 (x, y) 坐标。被“占用”可以是这些对象的一个属性。

另外,你的 World 已经知道每个代理的状态(通过 [(agent.x, agent.y) for agent in self.agents] [2])。agent_locations 字典实际上是这些属性的索引或缓存,因此它应该属于 World

关于将状态发送回 World 的麻烦……嗯,你并不能通过让 Agent 来做这个来解决问题。但是调用 update_agent_location(self, agent, x, y) 是完全多余的,因为 x == agent.x; y == agent.y(如果你反转调用它的行)。你可以在 World 中简单地有一个方法 update_agent_state(self, agent),这样 World 就可以用来更新它的索引。你甚至可以提交一个额外的参数来描述状态变化的类型(如果你不想每次都更新所有属性的话)。

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)

大致就是这样(请查看我的脚注)。

[1] 除非当然,某个位置的“好坏”还取决于其他变量,比如如果你只有在 1) 位置可用并且 2) 代理有 1000 美元支付票价的情况下才能移动到 (x, y),那么你应该有一个 Agent.can_move_to(x, y) 方法,它会调用世界的方法,并检查它的钱包。

[2] 我假设你的 self.agents = {} 是个笔误,因为你不能在字典上 append。你是指列表([])对吧?

1

在面向对象编程(OOP)中,谈论对象时可以用“是一个”和“有一个”来帮助理解。比如,一个World(世界)“有一个”Agents(代理)的列表和一个Locations(位置)的列表。而一个Location(位置)“有一个”Agent(代理)。同时,一个Agent“有一个”Location和一个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

想象一下,如果给Location(位置)添加一个新属性,比如地形(terrain),你会发现这种封装的好处非常明显。同样,如果要给Agent(代理)添加新东西,比如武器,只需要写一个类似于move()的武器专用功能就可以了。需要注意的是,World(世界)根本不需要参与到move()这个过程里。移动的操作完全是在Agent(代理)和Location(位置)之间进行的。

撰写回答