您如何将其转换为迭代函数,而不是通过嵌套循环进行递归?

时间:2019-03-29 13:12:49

标签: c# recursion iteration

以下函数可导致数百级递归。我想知道是否有人建议如何将其切换为循环功能。我相信在这种情况下执行的顺序确实很重要,但是我可能需要做更多的调查。我希望可以直接将其转换为迭代函数,而不是递归。

您可以看到,传递给每个递归级别的唯一参数是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;}
    }

2 个答案:

答案 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