我正在使用 SplHeap保存具有从叶子遍历到根的有向边的树的图节点。为此,我预先计算了节点的“扇入”并将它们放入堆中,以便我始终可以从中检索具有最小扇入 (0) 的节点。
访问一个节点后,我将其后继者的扇入减少 1。显然,堆需要重新计算,因为后继者现在在错误的位置。我已经尝试过 recoverFromCorruption(),但它没有做任何事情并且保持堆的顺序错误(具有较大 fanIn 的节点位于较小的 fanIn 之前)。
作为解决方法,我现在在每次访问后创建一个新堆,每次总计为一个完整的 O(N*log(N)) 排序。
然而,应该可以对更改的堆条目进行堆上操作,直到它在 O(log(N)) 中的正确位置。
SplHeap 的 API 没有提及上堆(或删除任意元素 - 然后可以重新添加)。我能否以某种方式从 SplHeap 派生一个类来执行此操作,还是我必须从头开始创建一个纯 PHP 堆?
编辑:代码示例:
class VoteGraph {
private $nodes = array();
private function calculateFanIn() { /* ... */ }
// ...
private function calculateWeights() {
$this->calculateFanIn();
$fnodes = new GraphNodeHeap(); // heap by fan-in ascending (leaves are first)
foreach($this->nodes as $n) {
// omitted: filter loops
$fnodes->insert($n);
}
// traversal from leaves to root
while($fnodes->valid()) {
$node = $fnodes->extract(); // fetch a leaf from the heap
$successor = $this->nodes[$node->successor];
// omitted: actual job of traversal
$successor->fanIn--; // will need to fix heap (sift up successor) because of this
//$fnodes->recoverFromCorruption(); // doesn't work for what I want
// workaround: rebuild $fnodes from scratch
$fixedHeap = new GraphNodeHeap();
foreach($fnodes as $e)
$fixedHeap->insert($e);
$fnodes = $fixedHeap;
}
}
}
class GraphNodeHeap extends SplHeap {
public function compare($a, $b) {
if($a->fanIn === $b->fanIn)
return 0;
else
return $a->fanIn < $b->fanIn ? 1 : -1;
}
}
完整代码也可用:https://github.com/md2k7/civicracy/blob/master/civi-php/protected/components/VoteGraph.php#L73
编辑 2:
$this->putNode(new GraphNode(4));
$this->putNode(new GraphNode(1, 2));
$this->putNode(new GraphNode(3, 2));
$this->putNode(new GraphNode(2, 4));
这意味着用户 1 和用户 3 正在为 用户 2 投票,而用户 2 正在为 用户 2 投票用户 4,通过了 3 票(收到 2 票 + 他/她自己的票)。这称为委托(delegate)投票:我的算法“从底部”(叶子)传递选票,我已经知道每个用户有多少权重(责任/代表/你喜欢它......)。
最佳答案
我最近在解决非常相似的问题,似乎 SPL 不支持更新。所以
我必须自己编写堆。
它不是特别高效,但它可以满足我的需要,而且它比重复排序数组快得多...不过 SPL 堆仍然快得多...
这里是...
class heap
{
public $members=array();
// these two are just for statistics
private $swaps=0;
private $recurs=array('lups'=>0, 'ldowns'=>0);
public function insert($val){
if(is_array($val) && empty($this->members)){ // because heapify is (in theory) more efficient
foreach($val as $v){
$this->members[]=$v;
}
$this->heapify();
}else{
$emptyPosition=count($this->members); // count(members) gets index of first empty position, not last key
$this->members[]=$val; // puts $val in
$this->ladderup($emptyPosition);
}
}
public function heapify(){
/* in case all the heap is broken, we can always use this to repair it.
It should be more efficient to fill $members randomly and "repair" it with heapify after,
than inserting things one by one*/
$start=max(0, floor( (count($this->members)-1)/2)); // find last parent
for($i=$start;$i>=0;$i--){
$this->ladderdown($i);
}
}
private function ladderdown($index){
// recursively sifts down $index
$this->recurs['ldowns']++;
/*
indexes of children
they are stored at parent_position*2 and parent_position*2+1
but becouse php uses null-based array indexing, we have to modify it a little
*/
$iA=$index*2+1;
$iB=$index*2+2;
if($iA<count($this->members)){ // check if children exist
if($iB<count($this->members)){
if($this->compare($iA, $iB)>=0) $bigger=$iA; // if both exist, compare them, cause we want to swap with the bigger one ; I'm using ">=" here, that means if they're equal, left child is used
else $bigger=$iB;
}else{
$bigger=$iA; // if only one children exists, use it
}
if($this->compare($bigger, $index)>0){ // not using ">=" here, there's no reason to swap them if they're same
$this->swap($bigger, $index);
$this->ladderdown($bigger); // continue with $bigger because that's the position, where the bigger member was before we swap()ped it
}
}
}
private function ladderup($index){
// sift-up,
$this->recurs['lups']++;
$parent=max(0, floor( ($index-1)/2)); // find parent index; this way it actualy swaps one too many times: at the end of sift-up-ing swaps the root with itself
if($this->compare($index, $parent)>0){
$this->swap($index, $parent);
$this->ladderup($parent);
}
}
public function root(){
if(count($this->members)){
return $this->members[0];
}
return false;
}
public function extract(){
// removes and returns root member
if(!count($this->members)) return false;
$this->swap(0,count($this->members)-1); // swaps root with last member
$result=array_pop($this->members); // removes last member (now root)
$this->ladderdown(0); // root is on wrong position, sifts it down
return $result;
}
public function update($index, $value){
if($index<count($this->members)){
$this->members[$index]=$value;
$this->ladderup($index);
$this->ladderdown($index);
}
}
public function delete($index){
// removes index from heap the same way as root is extracted
$this->swap(count($this->members)-1, $index); // swaps index with last one
array_pop($this->members);
$this->ladderup($index);
$this->ladderdown($index);
}
private function swap($iA, $iB){
// swaps two members
$this->swaps++;
$swap=$this->members[$iA];
$this->members[$iA]=$this->members[$iB];
$this->members[$iB]=$swap;
}
private function compare($iA, $iB){
$result=$this->members[$iA] - $this->members[$iB];
return $result;
}
public function stats($text=""){
// prints and resets statistics
echo "STATS: $text... Sift-ups: ".$this->recurs['lups']." Sift-downs: ".$this->recurs['ldowns']." Swaps: ".$this->swaps." <br>";
$this->recurs=array('lups'=>0, 'ldowns'=>0);
$this->swaps=0;
}
}
//here's how to use it...
$h=new heap;
for($i=0; $i<10000; $i++){
$h->insert(rand(1,1000));
}
$h->stats("after inserting one-by-one");
while($biggest=$h->extract()); // note that $h->extract might return FALSE, but might return zero as well, if there was zero in the heap
$h->stats("after extracting all roots (like in heapsort)");
echo "Now, heap is empty. Let's try whole array at once <br>";
for($i=0; $i<10000; $i++){
$a[]=rand(1,1000);
}
$h->insert($a); // inserting whole array here, so heap will use more efficient heapify()
$h->stats("after heapify");
echo "let's update two indexes<br>";
$h->update(1234,44444);// sure on top
$h->stats("after update");
$h->update(8888,40000);// second place
$h->stats("after update");
echo "extract biggest three indexes<br>";
echo $h->extract()." - this should be 44444<br>";
echo $h->extract()." - this should be 40000<br>";
echo $h->extract()." - this should be biggest number given by rand(1,1000)<br>";
$h->stats("after three extracts");
while($h->extract());
$h->stats("after extracting the rest");
结果是:
STATS: after inserting one-by-one... Sift-ups: 22651 Sift-downs: 0 Swaps: 12651
STATS: after extracting all roots (like in heapsort)... Sift-ups: 0 Sift-downs: 116737 Swaps: 116737
Now, heap is empty. Let's try whole array at once
STATS: after heapify... Sift-ups: 0 Sift-downs: 12396 Swaps: 7396
let's update two indexes
STATS: after update... Sift-ups: 11 Sift-downs: 1 Swaps: 10
STATS: after update... Sift-ups: 13 Sift-downs: 1 Swaps: 12
extract biggest three indexes
44444 - this should be 44444
40000 - this should be 40000
1000 - this should be biggest number given by rand(1,1000)
STATS: after three extracts... Sift-ups: 0 Sift-downs: 42 Swaps: 42
STATS: after extracting the rest... Sift-ups: 0 Sift-downs: 116652 Swaps: 116652
您将不得不对其进行一些修改,但无论如何,希望它对您有所帮助..
关于php - 有没有办法让 PHP 的 SplHeap 重新计算? (又名 : add up-heap to SplHeap? ),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13305532/
我在Rails应用程序中使用CarrierWave/Fog将视频上传到AmazonS3。有没有办法判断上传的进度,让我可以显示上传进度如何? 最佳答案 CarrierWave和Fog本身没有这种功能;你需要一个前端uploader来显示进度。当我不得不解决这个问题时,我使用了jQueryfileupload因为我的堆栈中已经有jQuery。甚至还有apostonCarrierWaveintegration因此您只需按照那里的说明操作即可获得适用于您的应用的进度条。 关于ruby-on-r
我想从then子句中访问case语句表达式,即food="cheese"casefoodwhen"dip"then"carrotsticks"when"cheese"then"#{expr}crackers"else"mayo"end在这种情况下,expr是食物的当前值(value)。在这种情况下,我知道,我可以简单地访问变量food,但是在某些情况下,该值可能无法再访问(array.shift等)。除了将expr移出到局部变量然后访问它之外,是否有直接访问caseexpr值的方法?罗亚附注我知道这个具体示例很简单,只是一个示例场景。 最佳答案
我最喜欢的Google文档功能之一是它会在我工作时不断自动保存我的文档版本。这意味着即使我在进行关键更改之前忘记在某个点进行保存,也很有可能会自动创建一个保存点。至少,我可以将文档恢复到错误更改之前的状态,并从该点继续工作。对于在MacOS(或UNIX)上运行的Ruby编码器,是否有具有等效功能的工具?例如,一个工具会每隔几分钟自动将Gitcheckin我的本地存储库以获取我正在处理的文件。也许我有点偏执,但这点小保险可以让我在日常工作中安心。 最佳答案 虚拟机有些人可能讨厌我对此的回应,但我在编码时经常使用VIM,它具有自动保存功
我在Ruby中遇到了一个有趣的表达式:a||="new"表示如果没有定义a,则将"new"值赋给a;否则,a将保持原样。在进行一些数据库查询时很有用。如果设置了该值,我不想触发另一个数据库查询。所以我在Python中尝试了类似的思路:a=aifaisnotNoneelse"new"失败了。我认为这是因为如果未定义a,则无法在Python中执行“a=a”。所以我能得出的解决方案是检查locals()和globals(),或者使用try...except表达式:myVar=myVarif'myVar'inlocals()and'myVar'inglobals()else"new"或try:
我写了很多initialize代码,将attrs设置为参数,类似于:classSiteClientattr_reader:login,:password,:domaindefinitialize(login,password,domain='somedefaultsite.com')@login=login@password=password@domain=domainendend有没有更像Ruby的方式来做到这一点?我觉得我在一遍又一遍地编写相同的样板设置代码。 最佳答案 您可以使用rubyStruct:classMyClass或
如果我想使用“create”构建策略创建和实例,然后想使用“attributes_for”构建策略进行验证,是否可以这样做?如果我在工厂中使用序列?在Machinistgem中有可能吗? 最佳答案 不太确定我是否完全理解。而且我不是机械师的用户。但听起来您只是想做这样的事情。@attributes=FactoryGirl.attributes_for(:my_object)my_object=MyObject.create(@attributes)my_object.some_property.should==@attributes
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我来自C、php和bash背景,很容易学习,因为它们都有相同的C结构,我可以将其与我已经知道的联系起来。然后2年前我学了Python并且学得很好,Python对我来说比Ruby更容易学。然后从去年开始,我一直在尝试学习Ruby,然后是Rails,我承认,直到现在我还是学不会,讽刺的是那些打着简单易学的烙印,但是对于我这样一个老练的程序员来说,我只是无法将它
假设我在我的Rails应用中某处定义了一个名为bla的函数。在ruby或rails中有没有一种方法可以动态/以编程方式打印用于定义该函数的代码?例如:defblaputs"HiThere"end然后如果我调用一个函数,例如get_definition:putsget_definition(:bla)这会打印出来"puts\"HiThere\""有规范的方法吗?我以前实际上不需要这样做,而且我知道这在Rails中并不是很常见的做法。我也不想使用元(反射)编程定义我的方法,然后存储用于定义我的方法的字符串。感谢您的帮助! 最佳答案
我知道Ruby是动态和强类型的,但据我所知,由于每个参数缺少显式类型表示法(或契约),当前语法不允许在编译时检查参数类型。如果我想执行编译时类型检查,我有哪些(实际成熟的)选项?更新我的意思是类型检查类似于典型的静态类型语言。比如C。例如,C函数表示每个参数的类型,编译器检查传入的参数是否正确。voidfunc1(structAAAaaa){structBBBbbb;func1(bbb);//Wrongtype.Compiletimeerror.}作为另一个例子,Objective-C通过放置显式类型信息来做到这一点。-(id)method1:(AAA*)aaa{BBB*bbb=[[A
从Ruby2.4.0开始,对于使用某些已弃用的功能,会出现弃用警告。例如,Bignum、Fixnum、TRUE和FALSE都会触发弃用警告。当我修复我的代码时,有相当多的代码我希望它保持沉默,尤其是在Rails中。我该怎么做? 最佳答案 moduleKerneldefsuppress_warningsoriginal_verbosity=$VERBOSE$VERBOSE=nilresult=yield$VERBOSE=original_verbosityreturnresultendend>>X=:foo=>:foo>>X=:bar