博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
简单的树遍历枚举器v0.2-挑战一个程序员到底能多懒- 添加广度优先遍历
阅读量:4327 次
发布时间:2019-06-06

本文共 14169 字,大约阅读时间需要 47 分钟。

前一阵在递归算法相关回贴的讨论中 和某lz抱怨 现在的同志们连用自己的栈加循环模拟递归都不会做了。
如果自己实现递归栈  又怎么会在线程栈中储存过多无关信息?数据全部都在堆里 又怎会stackoverflow?
当时就有想法自己实现一个,造福一下群众,但是被坏心眼的某lz 阻止了。“让他们自己写。” 他大概是这么说滴,“他们自己写了才算懂得了。”
可是 最近自己在工程中越来越多必须实现“数据库树型菜单表”“对游戏大厅内所有子孙房间进行检查” “对两个子目录中所有文件进行比较”这样的树型遍历操作
用递归 自己觉得恶心, 用栈每次重写麻烦。 
又想起用linq to xml 的 System.Xml.Linq.Extensions.Descendants<T>是多么畅快 为啥xpath可以 linq to xml可以  咱就不能用一个扩展方法把所有想遍历的树统统解决掉呢?
 于是花了一个下午写了一个枚举器, 用循环来代替栈,让溢出见鬼去吧!
-----------------------------------------------思路分割线--------------------------------------------------------------
问题:什么是树的遍历。
回答:树的遍历是树的一种重要的运算。所谓
遍历是指对树中所有结点的系统的访问,即依次对树中每个结点访问一次且仅访问一次。
 
问题:树的遍历一般过程?
回答:
朱德庸某绝对小孩2 中有一幅漫画:
心理辅导师对 顽皮:“你的问题很严重 我想找你的父母谈谈”
心理辅导师对 顽皮父母:“你们的问题很严重 我想找你们的父母谈谈”
心理辅导师对 顽皮祖父母:“你们的问题很严重 我想找你们的父母谈谈”
。。。。
顽皮-
  父亲
      父亲的父亲
            父亲的父亲的父亲
            父亲的父亲的母亲
      父亲的母亲
  母亲
      母亲的父亲
      母亲的母亲
。。。。
。。。
 不解决坟墓里面的曾祖父母们  是没办法解决祖父母们 和 父母的问题 也就没办法解决顽皮的问题。
1 访问本节点
2 有子节点则 
   对每一个子节点
            {
                  1 访问本节点 
                  2 有子节点则 
                     对每一个子节点
                        {
                              ....
                        }
            }
问题:为什么树的遍历要用到递归
回答: 递归调用“处理到一半 先搁置”的特性 非常符合树的遍历访问。
问题:为什么你做树的遍历不希望用递归
回答: 递归的时候 经常会把不需要的东西压到栈里( )。这个栈毕竟是有限的,我们可以自己把处理一半的东放在自己在堆实现的栈 而不是线程分配的那可怜的1MB。我们可以专注在我们需要的数据上
问题:为什么要用枚举器
回答: 好处多多,  结果直接支持linq查询, 支持lazy方式提高枚举效率 想变list就list  想变dictionary 就dictionary 
问题:不同树的相同点是什么
回答:  所有的节点都实现相同的接口,基本上是同质的  而且他们都有子节点的集合
· 顽皮家所有成员都是人生父母养的
· 所有的目录都可能有子目录和文件
· 所有的XML节点都可以具有标记名 属性 和子节点
问题:不同树的不同点是什么
回答:
如何取得子节点集合
· 联系一个人的父母要查通讯录
· 访问子目录要使用  io.directory.getdirectories()
· 访问XML子节点要访问childrennodes集合
-----------------------------------------------思路分割线--------------------------------------------------------------
理清思路 我们需要的是一个以根节点和从根节点选择子节点集合的方法为参数创建的范型的枚举器
    public class RecursionEnumerator<TItem> : System.Collections.Generic.IEnumerator<TItem>, System.Collections.IEnumerator ,IEnumerator <Stack<TItem >>
ExpandedBlockStart.gifContractedBlock.gif    
{
        
public RecursionEnumerator(IEnumerable<TItem> rootObjects, Func<TItem, IEnumerable<TItem>>
 childrenSelector)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
        }
}
枚举器最重要的方法实现当然是MoveNext()了
整体工作流程和一般递归调用一致
 
   对每一个子节点
            {
                  (把当前子节点枚举器压入自定义Stack)
                  1 访问本节点 
                  2 有子节点则 
                     对每一个子节点
                        {
                              (把当前子节点枚举器压入自定义Stack)
                              ....
                              (把当前子节点枚举器从自定义Stack弹出)
                        }
                  (把当前子节点枚举器从自定义Stack弹出)
            }
这里提供了注释
     public bool
 MoveNext()
ExpandedBlockStart.gifContractedBlock.gif        
{
            
if (ColStack.Count > 0
)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                var cur 
=
 ColStack.Peek();
                
if (Started)  //已经开始
ExpandedSubBlockStart.gifContractedSubBlock.gif
                {
                    
//主动找下一个节点
                    var en = ChildrenSelector(cur.Current).GetEnumerator(); //先看有没有子节点
                    
if (en.MoveNext()) //有子节点  
ExpandedSubBlockStart.gifContractedSubBlock.gif
                    {
                        
// 把这个层加入堆栈 访问第一个节点
                        ColStack.Push(en);
                        
return true
;
                    }
                    
else //没有子节点 
ExpandedSubBlockStart.gifContractedSubBlock.gif
                    {
                        
//进入下一个同层节点
                        
while (ColStack.Count > 0)
ExpandedSubBlockStart.gifContractedSubBlock.gif                        
{
                            en 
=
 ColStack.Peek();
                            
if (en.MoveNext()) //有同层节点
ExpandedSubBlockStart.gifContractedSubBlock.gif
                            {
                                
return true;//本次访问这个节点;
                            }
                            
else//没有同层节点
ExpandedSubBlockStart.gifContractedSubBlock.gif
                            {
                                ColStack.Pop().Dispose();
// 取消本层堆栈  
                            }
                        }
                        
return false//完全没有子节点 也没有下一个节点  就无法继续了
                    }
                }
                
else //第一次访问 直接返回
ExpandedSubBlockStart.gifContractedSubBlock.gif
                {
                    Started 
= true
;
                    
return
 cur.MoveNext();
                }
            }
            
return false;
        }
稍微包装一下 也实现一个IEumeratable 和扩展方法
ContractedBlock.gifExpandedBlockStart.gif外包装
    
public class RecursionEnumeratable<TItem> : IEnumerable<TItem>,IEnumerable <Stack<TItem >>
    {
            IEnumerable 
<TItem> objs ;
            Func
<TItem, IEnumerable<TItem>>
   ChildrenSelector;
            
public RecursionEnumeratable(TItem rootObject, Func<TItem, IEnumerable<TItem>>
 childrenSelector)
        {
     
            ChildrenSelector 
=
 childrenSelector;
             objs 
= new
 TItem[] { rootObject };
        
        }
            
public RecursionEnumeratable(IEnumerable<TItem> rootObjects, Func<TItem, IEnumerable<TItem>>
 childrenSelector)
        {
            objs
=
rootObjects;
      
            ChildrenSelector 
=
 childrenSelector;
  
        }
        
        
#region IEnumerable<TItem> Members
        
public IEnumerator<TItem> GetEnumerator()
        {
            
return new RecursionEnumerator<TItem>
(objs ,ChildrenSelector );
        }
        
#endregion
        
#region IEnumerable Members
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            
return GetEnumerator();
        }
        
#endregion
        
#region IEnumerable<Stack<TItem>> Members
        IEnumerator
<Stack<TItem>> IEnumerable<Stack<TItem>>.GetEnumerator()
        {
            
return new RecursionEnumerator<TItem>
(objs, ChildrenSelector);
        }
        
#endregion
    }
    
public static class RecursionExtender
    {
        
static public RecursionEnumeratable<T> GetRecursionEnumeratable<T>(this T SingleRoot, Func<T, IEnumerable<T>>
 childrenSelector)
       {
           
return new RecursionEnumeratable<T>
(SingleRoot, childrenSelector);
       }
        
static public RecursionEnumeratable<T> GetRecursionEnumeratableAsRootCollection<T>(this IEnumerable<T> Roots, Func<T, IEnumerable<T>>
 childrenSelector)
        {
            
return new RecursionEnumeratable<T>
(Roots, childrenSelector);
            
        }
        
    }
就可以在任何你想要的对象上遍历了~
当然一个下午的工作一定会有很多纰漏 有什么意见不妨提出来 我修改修改

 

对于真正的懒人来说 以上内容全是乐色!
怎样名正言顺的偷懒才是重要的!
Sample  察看一个目录中所有子目录的文件列表
        static void Main(string
[] args)
ExpandedBlockStart.gifContractedBlock.gif        
{
            
string RootDir= @"J:\Emule"
;
            
            
foreach (string str in RootDir.GetRecursionEnumeratable ( path=>
System.IO.Directory.GetDirectories (path) ))
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                Console .WriteLine(str) ;
                System.IO.Directory.GetFiles (str).ToList().ForEach (s
=>
Console.WriteLine (s));
            
            }
            Console.ReadKey();
        }
或者干脆点
ContractedBlock.gifExpandedBlockStart.gifCode
            @"J:\Emule".GetRecursionEnumeratable(path => System.IO.Directory.GetDirectories(path))
        
        .ToList<string>
()
                .ForEach
                (
                    str =>
                    {
                        Console.WriteLine(str);
                        System.IO.Directory.GetFiles(str).ToList().ForEach(s 
=> Console.WriteLine(s));
                    }
                );
是不是比原来递归来递归去好一些呢?
有的时候  根节点不是一个对象 而是一个集合   比如treeview 的items
对于多个根节点  我也提供了支持
ExpandedBlockStart.gifContractedBlock.gif          (
new string[] @"J:\Emule" ,@"I:\cdimages"})
              
              .GetRecursionEnumeratableAsRootCollection (path 
=> System.IO.Directory.GetDirectories(path))
                .ToList
<string>()
                .ForEach
                (
                    str 
=>
ExpandedBlockStart.gifContractedBlock.gif                    
{
                        Console.WriteLine(str);
                        System.IO.Directory.GetFiles(str).ToList().ForEach(s 
=> Console.WriteLine(s));
                    }
                );
更复杂的状况  我们有时候不但要遍历节点  也要返回他们每一层的祖先列表   这里我同时实现了 IEnumeratable<Stack<TItem>>
Sample 2 从平面表中生成树
Table
ID  Name FatherID
            ItemEnt[] source
=
new
 ItemEnt[
0
]; 
//
 从orm中读取表
            var roots 
=
 source.Where(itm 
=>
 itm.FatherID 
==
 
0
);
            
//
多个根节点入口
            roots.GetRecursionEnumeratableAsRootCollection
                (
                
//
提供子节点方法
                    itm 
=>
 source.Where   
                        (
                            itmchild 
=>
 itmchild.FatherID 
==
 itm.ID
                        )
                )
                
//
返回每个节点及其祖先节点列表的方法
                .ToList 
<
Stack  
<
 ItemEnt
>
 
>
()
                .ForEach 
                (stack
=>
Console.WriteLine (  
string
.Join (
@"
\
"
 ,stack.Select (i
=>
i.Name ).ToArray ()  )));
                    
            Console.ReadKey();
多简单~!
补充  鹤冲天同学提出了一个性能相关的很重要的问题  就是关于遍历的条件   老赵将其明确化 
“不过方法加predicate可以在中端就跳过一些节点,不用继续递归下去。”
如果仅仅对IEnumeratable 进行where 约束   一些本来不需要深入的子节点还是会继续深入下去的。 这样的确会带来性能的损失
这种where 约束分两部分 
1 是否选择本节点
2 是否对本节点的子节点今进行深入遍历
对于第一种  我在前面
为什么用枚举器中提到了  IEnumerable 的where 扩展是可以进行lazy filter的
对于第二中 我得补充下, 这个逻辑其实是 Children Selector 相关的。   可以在Children Selector中进行有效清晰的控制
ExpandedBlockStart.gif
ContractedBlock.gif
(
new
 
string
[] 
@"J:\Emule" ,@"I:\cdimages"}
             
.GetRecursionEnumeratableAsRootCollection (path 
=>
 (path.Length 
<
80
)
?
System.IO.Directory.GetDirectories(path):
new
 
string
[
0
] ) 
             
.ToList
<
string
>
() 
              
.ForEach 
              
                    
str 
=>
 
                   
ExpandedBlockStart.gifContractedBlock.gif
                       
Console.WriteLine(str); 
                      
System.IO.Directory.GetFiles(str).ToList().ForEach(s 
=> Console.WriteLine(s)); 
                  
}
 
               
);
//
如果路径太长 超过80 那么就不进行深层遍历了
特别感谢 鹤冲天同学和老赵同学的质疑精神   例子写得太少  Use Case做到覆盖  不好意思!
请大家继续提意见~~
更新广度优先(感谢装配脑袋提供的思路)
广度优先本身不存在访问栈 所以在枚举祖先列表的时候有一定消耗
于是在这里 分别写了两个枚举器
ContractedBlock.gifExpandedBlockStart.gif祖先枚举器
   public class RecursionEnumeratorBreadthWithAncestors<TItem> :  System.Collections.IEnumerator, IEnumerator<IEnumerable <TItem>>
    {
        Func
<TItem, IEnumerable<TItem>> ChildrenSelector;
        
/// <summary>
        
/// 任务。对于一个节点的1层子节点遍历 叫做一个任务
        
/// </summary>
        struct TaskItem
        {
            
/// <summary>
            
/// 本次任务要遍历的所有子节点
            
/// </summary>
            public IEnumerator<TItem> CurrentTasks;
            
/// <summary>
            
/// 本次任务要遍历的父亲节点。因为不涉及 Pop() 这里只用list就可以了
            
/// </summary>
            public List<TItem> ParentList;
        }
        
bool started = false;
        Queue
<TaskItem> TaskQueue;
        IEnumerable
<TItem> roots;
        
public RecursionEnumeratorBreadthWithAncestors(IEnumerable<TItem> rootObjects, Func<TItem, IEnumerable<TItem>> childrenSelector)
        {
            ChildrenSelector 
= childrenSelector;
            TaskQueue 
= new Queue<TaskItem>();
            var ti 
= new TaskItem() { CurrentTasks = rootObjects.GetEnumerator(), ParentList = new List<TItem>() };
            TaskQueue.Enqueue(ti);
            roots 
= rootObjects;
        }
        IEnumerator
<TItem> CurrentEnum
        {
            
get
            {
                
return TaskQueue.Peek().CurrentTasks;
            }
        }
        
#region IEnumerator<TItem> Members
        
public TItem Current
        {
            
get { return TaskQueue.Peek().CurrentTasks.Current; }
        }
        
#endregion
        
#region IDisposable Members
        
public void Dispose()
        {
            TaskQueue.ToList().ForEach(s 
=> s.CurrentTasks.Dispose());
            TaskQueue 
= null;
            roots 
= null;
            ChildrenSelector 
= null;
        }
        
#endregion
        
#region IEnumerator Members
        
object IEnumerator.Current
        {
            
get { return Current; }
        }
        
public bool MoveNext()
        {
            
if (TaskQueue.Count > 0)
            {
                
if (started)
                {
                    
while (TaskQueue.Count>0)
                    { 
                        
if (CurrentEnum.MoveNext())
                        {
                            GainChildrenTask();
                            
return true;
                        }
                        
else
                        {
                            var t
=  TaskQueue.Dequeue();
                            t.CurrentTasks.Dispose();
                            t.ParentList 
= null;                         
                        
                        }
                    }
                    
return false;
                }
                
else
                {
                    started 
= true;
                    
if (CurrentEnum.MoveNext())
                    {
                        GainChildrenTask();
                        
return true;
                    }
                    
return false;
                }
            }
            
return false;
        }
        
void GainChildrenTask()
        {
            var cur 
= Current;
            TaskItem ti 
= new TaskItem()
             {
                 CurrentTasks 
= ChildrenSelector(cur).GetEnumerator(),
                 ParentList 
= TaskQueue.Peek().ParentList.Concat (new TItem[] { cur }).ToList()
             };
            TaskQueue.Enqueue(ti);
        }
        
public void Reset()
        {
            var rootsbak 
= roots;
            var bakSelector 
= ChildrenSelector;
            Dispose();
            started 
= false;
            ChildrenSelector 
= bakSelector;
            TaskQueue 
= new Queue<TaskItem>();
            var ti 
= new TaskItem() { CurrentTasks = rootsbak.GetEnumerator(), ParentList = new List<TItem>() };
            TaskQueue.Enqueue(ti);
        }
        
#endregion
        
#region IEnumerator<IEnumerable<TItem>> Members
        IEnumerable
<TItem> IEnumerator<IEnumerable<TItem>>.Current
        {
            
get 
            { 
                
return TaskQueue.Peek().ParentList.Concat( new TItem[]{ Current }).ToList () ; 
           
            }
        }
        
#endregion
    }
  
ContractedBlock.gifExpandedBlockStart.gif不取祖先的枚举器
 public class RecursionEnumeratorBreadth<TItem> : System.Collections.Generic.IEnumerator<TItem>, System.Collections.IEnumerator
    {
        Func
<TItem, IEnumerable<TItem>> ChildrenSelector;
        
bool started = false;
        Queue
<IEnumerator <TItem >> TaskQueue;
        IEnumerable
<TItem> roots;
        
public RecursionEnumeratorBreadth(IEnumerable<TItem> rootObjects, Func<TItem, IEnumerable<TItem>> childrenSelector  )
        {
            ChildrenSelector 
= childrenSelector;
            TaskQueue 
= new Queue<IEnumerator <TItem >>();
            var ti 
= rootObjects.GetEnumerator();
            TaskQueue.Enqueue(ti);
            roots 
= rootObjects;
        }
        IEnumerator
<TItem> CurrentEnum
        {
            
get
            {
                
return TaskQueue.Peek();
            }
        }
        
#region IEnumerator<TItem> Members
        
public TItem Current
        {
            
get { return TaskQueue.Peek().Current; }
        }
        
#endregion
        
#region IDisposable Members
        
public void Dispose()
        {
            TaskQueue.ToList().ForEach(s 
=> s.Dispose());
            TaskQueue 
= null;
            roots 
= null;
            ChildrenSelector 
= null;
        }
        
#endregion
        
#region IEnumerator Members
        
object IEnumerator.Current
        {
            
get { return Current; }
        }
        
public bool MoveNext()
        {
            
if (TaskQueue.Count > 0)
            {
                
if (started)
                {
                    
while (TaskQueue.Count>0)
                    { 
                        
if (CurrentEnum.MoveNext())
                        {
                            GainChildrenTask();
                            
return true;
                        }
                        
else
                        {
                            var t
=  TaskQueue.Dequeue();
                            t.Dispose();
                             
                        
                        }
                    }
                    
return false;
                }
                
else
                {
                    started 
= true;
                    
if (CurrentEnum.MoveNext())
                    {
                        GainChildrenTask();
                        
return true;
                    }
                    
return false;
                }
            }
            
return false;
        }
        
void GainChildrenTask()
        {
            var cur 
= Current;
            var ti 
=  ChildrenSelector(cur).GetEnumerator();
             
            TaskQueue.Enqueue(ti);
        }
        
public void Reset()
        {
            var rootsbak 
= roots;
            var bakSelector 
= ChildrenSelector;
            Dispose();
            started 
= false;
            ChildrenSelector 
= bakSelector;
            TaskQueue 
= new Queue<IEnumerator<TItem>>();
            var ti 
= rootsbak.GetEnumerator();
            TaskQueue.Enqueue(ti);
        }
        
#endregion
    }
外包装也作了小小的变化
ContractedBlock.gifExpandedBlockStart.gif外包装
public  class EmptyEnumerable<T>: IEnumerable<T> 
{
    IEnumerator
<T> Enumerator;
    
public EmptyEnumerable(IEnumerator<T> enumerator)
    {
        Enumerator 
= enumerator;
    
    }
    
#region IEnumerable<T> Members
    
public IEnumerator<T> GetEnumerator()
    {
        
return Enumerator;
    }
    
#endregion
    
#region IEnumerable Members
    IEnumerator IEnumerable.GetEnumerator()
    {
        
return Enumerator;
    }
    
#endregion
}
    
public static class RecursionExtender
    {
        
public enum RecursionType
        { 
            DeepFirst
=0,
            Breadth
=1
        
        }
        
static public IEnumerable <T> GetRecursionEnumerable<T>(this T SingleRoot, Func<T, IEnumerable<T>> childrenSelector , RecursionType type)
        {
            
            
switch (type)
            { 
                
case RecursionType.DeepFirst :
                    
return new EmptyEnumerable <T>(new RecursionEnumeratorDepthFirst<T>(new T[] { SingleRoot }, childrenSelector));
                
case RecursionType.Breadth :
                    
return new EmptyEnumerable<T>(new RecursionEnumeratorBreadth <T>(new T[] { SingleRoot }, childrenSelector));
            }
            
return null;
        }
        
static public IEnumerable <IEnumerable <T> >GetRecursionEnumerableWithAncestors<T>(this T SingleRoot, Func<T, IEnumerable<T>> childrenSelector , RecursionType type)
        {
            
            
switch (type)
            { 
                
case RecursionType.DeepFirst :
                    
return new EmptyEnumerable <IEnumerable<T>>(new RecursionEnumeratorDepthFirst<T>(new T[] { SingleRoot }, childrenSelector));
                
case RecursionType.Breadth :
                    
return new EmptyEnumerable<IEnumerable<T>>(new RecursionEnumeratorBreadthWithAncestors <T>(new T[] { SingleRoot }, childrenSelector));
            }
            
return null;
        }
        
static public IEnumerable<T> GetRecursionEnumerableAsRootCollection<T>(this IEnumerable<T> Roots, Func<T, IEnumerable<T>> childrenSelector, RecursionType type)
        {
            
switch (type)
            {
                
case RecursionType.DeepFirst:
                    
return new EmptyEnumerable<T>(new RecursionEnumeratorDepthFirst<T>(Roots, childrenSelector));
                
case RecursionType.Breadth:
                    
return new EmptyEnumerable<T>(new RecursionEnumeratorBreadth<T>(Roots, childrenSelector));
            }
            
return null;
        }
        
static public IEnumerable<IEnumerable<T>> GetRecursionEnumerableAsRootCollectionWithAncestors<T>(this IEnumerable<T> Roots, Func<T, IEnumerable<T>> childrenSelector, RecursionType type)
        {
            
switch (type)
            {
                
case RecursionType.DeepFirst:
                    
return new EmptyEnumerable<IEnumerable<T>>(new RecursionEnumeratorDepthFirst<T>(Roots, childrenSelector));
                
case RecursionType.Breadth:
                    
return new EmptyEnumerable<IEnumerable<T>>(new RecursionEnumeratorBreadthWithAncestors<T>(Roots, childrenSelector));
            }
            
return null;
        }
    }
用法
ContractedBlock.gifExpandedBlockStart.gif用法
        static public IEnumerable<string> Rtest()
ExpandedBlockStart.gifContractedBlock.gif        
{
            
string dir = @"d:\webcast";
            var xx 
= dir.GetRecursionEnumeratable(s => System.IO.Directory.GetDirectories(s),RecursionExtender.RecursionType.DeepFirst );
            
return xx.ToList<string>();
        }
        
static public IEnumerable<IEnumerable <string>> Rtest2()
ExpandedBlockStart.gifContractedBlock.gif        
{
            
string dir = @"d:\webcast";
            var xx 
= dir.GetRecursionEnumeratableWithAncestors(s => System.IO.Directory.GetDirectories(s)
, RecursionExtender.RecursionType.DeepFirst);
            
return xx.ToList < IEnumerable<string>>();
        }
        
static public IEnumerable<string> R1test()
ExpandedBlockStart.gifContractedBlock.gif        
{
            
string dir = @"d:\webcast";
            var xx 
= dir.GetRecursionEnumeratable(s => System.IO.Directory.GetDirectories(s) ,RecursionExtender.RecursionType.Breadth );
            
return xx.ToList<string>();
        }
        
static public IEnumerable<IEnumerable<string>> R1test2()
ExpandedBlockStart.gifContractedBlock.gif        
{
            
string dir = @"d:\webcast";
            var xx 
= dir.GetRecursionEnumeratableWithAncestors(s => System.IO.Directory.GetDirectories(s), RecursionExtender.RecursionType.Breadth);
            
return xx.ToList<IEnumerable<string>>();
        }
    }
这个版本估计近期不会动了 :D

 

代码地址
RecursionEnumerator

转载于:https://www.cnblogs.com/waynebaby/archive/2009/08/16/1546980.html

你可能感兴趣的文章
如何使用mysql
查看>>
linux下wc命令详解
查看>>
敏捷开发中软件测试团队的职责和产出是什么?
查看>>
在mvc3中使用ffmpeg对上传视频进行截图和转换格式
查看>>
python的字符串内建函数
查看>>
Spring - DI
查看>>
微软自己的官网介绍 SSL 参数相关
查看>>
Composite UI Application Block (CAB) 概念和术语
查看>>
ajax跨域,携带cookie
查看>>
阶段3 2.Spring_01.Spring框架简介_03.spring概述
查看>>
阶段3 2.Spring_02.程序间耦合_1 编写jdbc的工程代码用于分析程序的耦合
查看>>
阶段3 2.Spring_01.Spring框架简介_04.spring发展历程
查看>>
阶段3 2.Spring_02.程序间耦合_3 程序的耦合和解耦的思路分析1
查看>>
阶段3 2.Spring_02.程序间耦合_5 编写工厂类和配置文件
查看>>
阶段3 2.Spring_01.Spring框架简介_05.spring的优势
查看>>
阶段3 2.Spring_02.程序间耦合_7 分析工厂模式中的问题并改造
查看>>
阶段3 2.Spring_02.程序间耦合_4 曾经代码中的问题分析
查看>>
阶段3 2.Spring_03.Spring的 IOC 和 DI_2 spring中的Ioc前期准备
查看>>
阶段3 2.Spring_03.Spring的 IOC 和 DI_4 ApplicationContext的三个实现类
查看>>
阶段3 2.Spring_02.程序间耦合_8 工厂模式解耦的升级版
查看>>