以下函数可导致数百级递归。我想知道是否有人建议如何将其切换为循环功能。我相信在这种情况下执行的顺序确实很重要,但是我可能需要做更多的调查。我希望可以直接将其转换为迭代函数,而不是递归。
您可以看到,传递给每个递归级别的唯一参数是drPostDetails。因此,递归函数调用本身相对简单,但是调用周围的循环却使我失望。
我已经考虑过要排队,但是“更改”的条件是导致排队的原因。在将其添加到队列之前,我必须执行部分移位操作,但是在当前代码中,下一级别的递归将在此之后立即开始,因此我不能仅仅建立要处理和执行的项目队列他们按顺序。
我已经考虑过堆栈,但是我不确定如何实现它来代替递归。
private void Shift_Schedule_UP_Recursive(Calendar cal, DataRow dr, DaysOfWeek workdays, List<IBTWorkDayExceptions> jobWorkdayExceptions, int sourceShiftId)
{
int builderID = cal.BuilderID;
try
{
IBuilders curBuilder = CacheHandler.Builder.Get(builderID);
(bool emailLinkedAssignee, int emailDayspan) = InitializeVariablesForShift(cal, curBuilder);
foreach (DataRow drPost in dr.GetChildRows("schedulePostcessors")) // get all rows where this item is a parent and drPost = its child
{
DataRow drPostDetails;
drPostDetails = drPost.GetParentRow("schedulePredecessors"); // gets the related row for the child(from the schedules table) where scheduleid = scheduleid
bool changed = StartShift(cal, workdays, jobWorkdayExceptions, drPostDetails, emailLinkedAssignee, emailDayspan, curBuilder, sourceShiftId);
if (changed)
{
Shift_Schedule_UP_Recursive(cal, drPostDetails, workdays, jobWorkdayExceptions, sourceShiftId);
cal.SetRecursiveShiftCount(cal.RecursiveShiftCount - 1); // tracking recursive shifts where the method has not exited yet
}
}
}
catch (SqlException ex)
{
HandleSqlExceptionDuringShift(cal, builderID, ex);
throw;
}
catch (Exception ex)
{
HandleExceptionDuringShift(cal, builderID, ex);
throw;
}
}
我决定简化代码,因为这可能有点令人困惑。这是一个骨架(如果初始化变量,您可以实际运行它!)可能更像是“伪”,如
private void Shift_Schedule_UP_Recursive(ItemWithPredPostcessor dr)
{
InitializeVariablesForShift();
try
{
foreach (ItemWithPredPostcessor drPost in dr.Postcessors) // get all rows where this item is a parent and drPost = its child
{
var drPostDetails = drPost.Predecessor; // gets the related row for the child(from the schedules table) where scheduleid = scheduleid
bool changed = StartShift(drPostDetails);
if (changed)
{
Shift_Schedule_UP_Recursive(drPostDetails);
}
}
}
catch
{
// Assume relavant error handling at this level in the stack. This probably isn't important to maintain, but it'd be interested if we could.
throw;
}
}
public object InitializeVariablesForShift()
{
// Assume relavant work happens here.
return new object();
}
public bool StartShift(ItemWithPredPostcessor dr)
{
// Logic is that either of the dates or whatever change, so we need to shift. This does a bunch of comparisons on the current item.
return dr.NeedsShifted;
}
public class ItemWithPredPostcessor
{
public ItemWithPredPostcessor Predecessor {get;set;}
public bool NeedsShifted {get;set;}
public List<ItemWithPredPostcessor> Postcessors {get;set;}
}
答案 0 :(得分:0)
最后弄明白了,对此顿悟。事实证明,处理递归的最简单方法是使用IEnumerator进行循环。它要求您手动处理迭代(没有for循环),但是递归和循环的顺序将是相同的。
我将函数分为两部分,入口函数和与“递归”一起进行迭代的函数。本质上,这将确保所有子代都像第一个递归一样先完全完成,然后返回父代中正确的迭代点。从理论上讲,这应该对其中具有循环的任何递归函数都有效。
private void Shift_Schedule_UP_Recursive(ItemWithPredPostcessor dr)
{
try
{
DoRealShift(dr);
}
catch
{
// Assume relavant error handling at this level in the stack. This probably isn't important to maintain, but it'd be interested if we could.
throw;
}
}
private void DoRealShift(ItemWithPredPostcessor dr)
{
var state = new Stack<IEnumerator<ItemWithPredPostcessor>>();
state.Push(GetPostcessorsEnumerator(dr));
while (state.Count > 0)
{
InitializeVariablesForShift();
while (state.Peek().MoveNext())
{
ItemWithPredPostcessor drPost = state.Peek().Current;
ItemWithPredPostcessor drPostDetails = drPost.Predecessor;
bool needsShift = StartShift(drPostDetails);
if (needsShift)
{
ShiftEverything(drPostDetails);
// Shift all children first, prior to returning to shift the current set.
state.Push(GetPostcessorsEnumerator(drPostDetails));
}
}
state.Pop(); // Remove current from list, as we've completed.
}
}
答案 1 :(得分:-1)
内存中有一个称为stack的区域。函数调用存储在堆栈中。考虑到您的f函数调用了g函数,因此g函数被压入堆栈。当执行g时,它将从堆栈中弹出,并且其结果(如果有的话)将在f被中断以调用和执行g的位置返回给f。
此行为是自动的,但是如果您理解它-我建议您阅读一些有关它的文章-那么您将意识到需要模仿的行为。是的,您认为需要堆栈的想法是正确的。堆栈中的每个项目都必须存储状态,也就是说,您需要确保每当将新项目推送到堆栈中时,都不要忘记将新项目推送到之前被中断的项目的状态。堆栈。同样,当您从堆栈中弹出一个项目时,您需要确保其结果(在这种情况下您是空的,因此您不会关心结果,但我是在一般性地说)正确地返回给弹出后新的堆栈顶部。弹出后,您还需要准确地返回到正确的状态。因此,您需要仔细处理变量。
现在,让我们看看它应该如何工作。对于每个级别,您都有一个潜在的新级别队列。在我们的情况下,您将需要一个循环来处理每次迭代中的一个步骤。步骤可以是初始化,队列项目处理,弹出。该循环将一直进行到堆栈变空为止,因此请确保最终使堆栈变空以避免出现一些挫败感。要知道您需要做什么,您将需要为堆栈的每个级别都有一个状态,因此您的程序将始终知道给定项目是否已初始化,是否正在迭代其队列或是否要完成。队列是可能的子项目的集合。一个非常简化的伪代码来说明这个想法:
root.state <- preinitialized
stack.push(root);
while (not stack.empty()) do
item <- stack.top()
if (item.state = preinitialized) then
item.initializeQueue()
item.queueIterator.initialize()
item.state <- queue
else if (item.state = queue) then
if (item.queueIterator.hasNext()) then
next <- item.queueIterator.next()
if (someCondition) then
next.state <- preinitialized
stack.push(next)
end if
else
item.state <- finished
result <- stack.pop(item)
if (stack.top()) then
stack.top.apply(result)
else
finalResult <- result
end
end if
end if
end while