带过滤器的加权镐

时间:2010-11-13 01:04:42

标签: algorithm random high-load

我有一个带权重的元素列表:

{ id1, weight1 },
{ id2, weight2 },
...
{ idN, weightN }

权重是小整数(例如,小于1000,通常小于50)。列表中的ID总数也小于1000。 (每个id仅列出一次。)

对于每个查询,我必须返回"足够随机"列表中的元素。如果我执行E次查询,其中E与所有权重之和成比例,则每个元素元素的次数必须相同完全比例 weight值。请注意,这应该适用于E的较小值(例如,最多50 *权重之和)。另见问题末尾的注释。

到目前为止,我通过将元素ID放入循环列表,将重复次数复制到其中,然后对列表进行混洗来解决此任务。每个查询返回列表的头部,然后递增头部位置。

但在这种情况下,我还有一个额外的条件:

我有查询的附加参数:过滤器。过滤器是id => is_enabled的地图。如果给定is_enabled的{​​{1}}为false,则应从结果中排除id。上述限制中的id值仅针对已启用的元素计算。也就是说,要从查询中排除禁用的元素权重。

过滤器是"唯一的"对于每个查询,并在列表中包含每个E的条目。 (请注意,这意味着2 ^ 1000个潜在的过滤器值。)

有没有办法有效地解决这个问题?我需要算法在多服务器集群上有效。

注1:我想强调的是,我认为,完全随机选择元素(如其中一个答案所示),而不存储任何状态,将无效。它将仅在无限数量的查询上给出完全成比例的元素数。随机数生成器有权在很长一段时间内返回不公平的值。

注2:此任务对随机性的质量没有任何限制。考虑一下,甚至没有必要在上面的简单案例解决方案中洗牌。良好的随机性更好,但根本没有必要。

注3:请注意,2 ^ 1000个潜在过滤器值确实意味着我无法存储与过滤器值相关联的任何内容 - 它将需要太多内存。我可以存储最新(或经常使用)过滤器的内容,但我不能存储项目列表偏移等内容,因为我无法承受丢失数据。

注4:我们无法通过查询返回元信息,让客户为我们存储状态(无论如何,好主意,谢谢,Diacleticus)。我们无法理解,因为两个客户端可能会意外使用相同的过滤器(某些过滤器比其他过滤器更受欢迎)。在这种情况下,我们必须对两个查询使用相同的状态。事实上,客户端执行多个查询是一个相对罕见的事件。

3 个答案:

答案 0 :(得分:0)

在我看来,你必须为每个不同的过滤器保留一个轨道。这意味着每次引入新过滤器或者所有元素都用于旧过滤器时,您必须构建一个新的混洗列表。

编辑:现在我们使用比例值,我们可以完全删除混洗列表,并让统计数据为我们洗牌。对于每个查询,将一个计数器设置为随机(0..sum_of_all_enabled_weights_for_the_query)。从列表的开头开始,如果为查询启用了元素,则从此计数器中减去所有权重,如果禁用该元素,则忽略它。如果计数器变为负数,那么你就会发现自己是一个元素。

答案 1 :(得分:0)

让我们看看我是否理解你的问题。

我将逐步在Mathematica中发布代码,并且注释输出可以轻松地跟随它。

这个答案提供了确定性和有序的输出(即非改组)。如果您确实需要随机排列,则可以使用相同的算法预先生成完整的过滤序列,对其进行随机排列,并逐个使用这些值。

该计划

拳头我们定义了两个常数:

n = 10; (* nbr of ids *)
m = 3;  (* max weight - 1 *) 

我保持数字很小,所以我们可以逐步检查输出。

现在我们定义一个随机{id,weight}表来使用。我们使用素数作为ids:

weights = Table[{Prime@k, RandomInteger[m] + 1}, {k, n}]

输出:

{{2, 3}, {3, 2}, {5, 3}, {7, 1}, {11, 1}, 
{13, 3}, {17, 1}, {19,4}, {23, 1}, {29, 2}}  

接下来,我们累积权重值

accumulator = Accumulate[Table[k[[2]], {k, weights}]]

输出:

{3, 5, 8, 9, 10, 13, 14, 18, 19, 21}  

我们合并两个表以将累加器放入id表中:

weightsAcc = MapThread[Append, {weights, accumulator}]

输出:

{{2, 3, 3}, {3, 2, 5}, {5, 3, 8}, {7, 1, 9}, {11, 1, 10}, 
 {13, 3, 13}, {17, 1, 14}, {19, 4, 18}, {23, 1, 19}, {29, 2, 21}}

现在我们使用您的默认值(true或false)初始化过滤器。我使用了True:

filter = Table[{k[[1]], True}, {k, weights}]

输出:

{{2, True}, {3, True}, {5, True}, {7, True}, {11, True}, {13, True}, 
 {17, True}, {19, True}, {23, True}, {29, True}}  

诀窍是保持过滤器与ids向量同步,因此我们定义了一个以这种方式更新过滤器的函数:

updateFilter[filter_, newValuePair_] :=Return@
         ReplaceAll[filter, {newValuePair[[1]], x_} -> newValuePair];  

并使用它来改变两个值:

filter = updateFilter[filter, {2, False}];
filter = updateFilter[filter, {5, False}];
Print@filter

输出:

{{2,False},{3,True},{5,False},{7,True},{11,True},{13,True},
 {17,True},{19,True},{23,True},{29,True}}  

现在我们定义我们的查询。我们将使用两个全局变量(agrhhhh!)和两个函数来使事物同步:

i = 1; j = 0; (* GLOBAL state variables *)

Adjustij[w_] := (                      (* parm w is weightsAcc *)
   j++;                                (* increment accumulator comparator*)
   If[j == w[[i, 3]], i++];            (* if current id exhausted, get next*)
   If[i == Length@w, i = 1; j = 0];    (* wraparound table when exhausted*)
);   

query[w_, filter_] :=                  (* parm w is weightsAcc *)
 (
  Adjustij[w];
  While[Not@filter[[i, 2]], Adjustij[w]];       (* get non filtered ids only *)
  Return[w[[i, 1]]];
  )

当然,while循环可以加速,只是使用过滤器False跳过id,但我认为这样的意图更清晰。

现在我们执行30次查询:

 Table[query[weightsAcc, filter], {30}]

并获得:

{3, 3, 7, 11, 13, 13, 13, 17, 19, 19, 19, 19, 23, 3, 3, 7, 11, 13, \
 13, 13, 17, 19, 19, 19, 19, 23, 3, 3, 7, 11}  

我们的列表(循环地)具有适当的权重,除了那些过滤器为FALSE的值。

HTH!

编辑:分割为回答评论的服务器和客户端代码

它可以处理具有不同过滤器的并发查询

过滤器状态存储在客户端。

服务器实现的功能和代码:

Clear["Global`*"];

(*Server Implemented  Functions follows*)

AdjustFilterState[fs_] := Module[{i, j}, (    (*fs = filterstate, i,j localvars*)
     i = fs[[1]]; (*local vars*)              (*w  = weights with accs*)
     j = fs[[2]];
     j++;                                     (* increment accumulator comparator*)
     If[j == weightsAcc[[i, 3]], i++];        (* if current id exhausted, get next*)
     If[i == Length@weightsAcc, i = 1; j = 0];(* wraparound table when exhausted*)
     Return[{i, j}];);
   ];


query[filter_, fs_] := Module[{fsTemp},       (*fs = filterstate*)
   (
    fsTemp = AdjustFilterState[fs];           (* local var *)

    While[Not@filter[[fsTemp[[1]], 2]],       (* get non filtered ids only *)
       fsTemp = AdjustFilterState[fsTemp]
    ];

    Return[{weightsAcc[[fsTemp[[1]], 1]], fsTemp}]; (*return[value,{filterState}]*)
   )
   ];

initFilter[] := masterFilter; (*Init filters to your defult vallue*)

(*The trick is to get the filter coordinated with the list value*)
updateFilter[f_, newValuePair_] :=
 Return@ReplaceAll[f, {newValuePair[[1]], x_} -> newValuePair];

(*Server Code - Just initialize the whole thing
   The SERVER stores ONLY the weights vectors and a master filter initialized*)

n = 10; (* nbr of ids *)                                (*init vars*)
m = 3;  (*max weight - 1 *)

weights = Table[{Prime@k, RandomInteger[m] + 1}, {k, n}]; (*random weights to test*)
accumulator = Accumulate[Table[k[[2]], {k, weights}]];    
weightsAcc = MapThread[Append, {weights, accumulator}];   (*add acummulator to list*)
masterFilter= Table[{k[[1]],True}, {k,weights}]; (* only ONE virgin filter in server*)

客户代码:

(* Client Code 
   The CLIENT stores only the filter and the filterState*)
(* Set up filter and filterstate *)

filter = initFilter[];
filter = updateFilter[filter, {2, False}];  (*specify particular values*)
filter = updateFilter[filter, {5, False}];

filterState = {1,0}; (* these replace the previous GLOBAL state variables *)

ValuesList = {};  (*for storing results *)

Do[
 q1 = query[filter, filterState]; (* do the query *)
 AppendTo[ValuesList, q1[[1]]];   (* first element of return is the value *)
 filterState = q1[[2]];           (* second element is updated filter state *)
 , {30}  (*do 30 times*)
 ];
Print@ValuesList                 (* print results vector *)

答案 2 :(得分:-1)

也许我找到了解决方案:

  1. 存储id->number_of_queries_left,其中number_of_queries_left的初始值为weight * 10(因此列表不会经常刷新 - 完全比例要求将被保留。
  2. 在每个查询上:
    1. 从过滤器中选择一个随机id,其中is_enabledtrue
    2. number_of_queries_left减少id
    3. 如果结果小于或等于零,请将id标记为已使用并选择另一个。
    4. 如果使用了所有值且未找到任何值,请对过滤器中启用的所有ID重新初始化id->number_of_queries_left(“充值”)。
  3. 看起来它应该有效。你觉得怎么样?

    更新1:

    我担心看起来我必须为每个过滤器值保持id->number_of_queries_left值。由于内存限制,我负担不起(有2 ^ 1000个潜在的过滤器值)。我是对的吗?

    有人可以帮助我更好地了解共享number_of_queries_left计数器的后果吗?

    更新2:

    这个想法的功劳归到Diacleticus(参见对这个答案的评论)。

    如果我们不为过滤器中的所有已启用项重置id->number_of_queries_left,而是按相应的权重递增它们,该怎么办?我认为这应该确定比例。 (或者它应该吗?)

    唯一的问题是,使用此算法,每个number_of_queries_left计数器都可能非常消极。 (见上文,我们每次想要查看其值时都会减少它。)

    因此,在一个悲观的情况下,即使通过递增所有计数器,我们也不会将它们中的任何一个置于零之上。这可能没问题,因为我们将有效地运行增量循环,直到任何值都变为正值。

    更新3:

    不,我们不能只运行增量循环,直到任何值都变为正值。

    这将衡量权重:负面部分没有“物理意义” - 它不代表从查询返回的值。

    因此,采用混合方式:

    进行“充值”时,将每个计数器递增weight + -min(0, current_counter_value)。这应该以原子方式完成,但看起来可行。

    但是,在这种情况下,我不确定体重处理是否公平。

    评论