改进了从固定列表中查找下一个可用ID的逻辑?

时间:2011-10-14 23:42:48

标签: ruby

我需要从固定的可能ID列表中找到下一个可用的ID(或密钥)。在这种情况下,有效ID为1到9999(含)。当我们找到下一个可用的ID时,我们就会在最后一个分配的ID之后开始查看,最后回合 - 当然只有一次 - 并且需要检查每个ID是否在我们作为可用ID返回之前被采用。

我有一些代码可以做到这一点,但我认为它既不优雅也不高效,并且对更简单的方法来完成同样的事情感兴趣。我正在使用Ruby,但我的问题并不是针对该语言的,所以如果您想使用任何其他语言撰写答案,我将非常感谢您的意见!

我已经省略了一些关于检查ID是否可用的详细信息,因此只需将其视为存在函数incr_last_idid_taken?(id)set_last_id(id)的情况。 (incr_last_id将在数据存储(Redis)中为最后分配的ID添加1并返回结果。id_taken?(id)返回一个布尔值,指示ID是否可用。set_last_id(id)更新带有新的最后一个ID的数据存储。)

MaxId = 9999

def next_id
  id = incr_last_id

  # if this ID is taken or out of range, find the next available id
  if id > MaxId || id_taken?(id)
    id += 1 while id < MaxId && id_taken?(id)

    # wrap around if we've exhausted the ID space
    if id > MaxId
      id = 1
      id += 1 while id < MaxId && id_taken?(id)
    end

    raise NoAvailableIdsError if id > MaxId || id_taken?(id)

    set_last_id(id)
  end

  id
end

我对那些要求我建立所有可能ID列表的解决方案并不感兴趣,然后获取分配的ID和可用ID之间的集合或列表差异。这不规模。我意识到这是一个线性操作,无论你如何切片它,那部分都很好,我只是认为代码可以简化或改进。我不喜欢因不得不环绕而导致的重复,但也许没有办法解决这个问题。

有更好的方法吗?请告诉我!

3 个答案:

答案 0 :(得分:1)

使用数据库表(本例中为MySQL):

SELECT id FROM sequences WHERE sequence_name = ? FOR UPDATE
UPDATE sequences SET id = id + 1 WHERE sequence_name = ?

FOR UPDATE获得对表的独占锁定,确保您可以成为同时执行相同操作的唯一可能进程。

使用内存中的固定列表:

# somewhere global, done once
@lock = Mutex.new
@ids  = (0..9999).to_a

def next_id
  @lock.synchronize { @ids.shift }
end

使用redis:

LPOP list_of_ids

或者只是:

INCR some_id

Redis会为您解决并发问题。

答案 1 :(得分:1)

改进这种算法的通常答案是保持一个“免费对象”列表方便;如果你真的不想要额外的维护列表,你可以只使用列表中的单个对象。 (这会降低免费对象缓存的效率,但管理大量自由对象的开销可能会增加负担。它取决于。)

因为当你点击MaxId时,你正在包装你的搜索,我认为有一个函数give_up_id会将id返回到空闲池。您可以使用新变量@most_recently_free或将其附加到列表@free_ids[],而不是简单地将已释放的ID重新放入大型池中,而是将其附加到列表def give_up_id(id) @free_ids.push(id) end def next_id if @free_ids.empty? id = old_next_id() else id = @free_ids.pop() return id end

当您需要新的ID时,如果列表中有一个,请从列表中取一个。如果列表没有,请按照您目前的步骤开始搜索。

这是伪代码中的草图:

{{1}}

如果你允许多个执行线程与你的id分配/自由例程交互,你当然也需要保护这些例程。

答案 2 :(得分:1)

由于您已经在第一次迭代中从incr_last_id搜索到MaxId,因此实际上不需要再次重复它。

在第二轮中从1搜索到incr_last_id至少将搜索精确地减少到O(n)而不是更糟的O(2n)

如果您想在单个循环中执行此操作,请使用modulo,

MaxId = 9999
def next_id
  id = incr_last_id
  limit = id - 1 #This sets the modulo test to the id just before your start point
  id += 1 while (id_taken?(id) && (i % MaxId) != limit)
  raise NoAvailableIdsError if id_taken?(id)
  set_last_id(id)
  id
end