【问题标题】:Iterating over nested lists with a Next() function, without a generator使用 Next() 函数迭代嵌套列表,无需生成器
【发布时间】:2009-10-08 14:31:35
【问题描述】:

虽然我很想在 python 中解决这个问题,但我被困在 Delphi 中。我有嵌套列表(实际上是具有嵌套列表作为属性的对象,但没关系),我想以生成器的方式迭代它们。也就是说,我想编写一个 Next 函数,它为我提供嵌套列表所描述的树的叶子中的下一项。

例如,假设我有

 [[1,2,3],[4,5],[],[6],[7,8]]

我希望连续 8 次调用 Next() 以返回 1..8。

如何在没有 yield 和生成器的语言中做到这一点?

请注意,嵌套的深度是固定的(本例中为 2,现实生活中为 4),但欢迎解决深度可变的更一般情况的答案。

编辑:对不起,我应该提到,这是 Delphi 2007。

【问题讨论】:

    标签: delphi iteration yield


    【解决方案1】:

    如果您正在使用任何具有内置枚举器的 Delphi 列表,则只需一点递归即可轻松完成。您的基本列表是一个数字列表,例如 TList<integer>。然后你有嵌套列表实现为TList<TList<integer>>。 (我假设你有 Delphi 2009 或 2010。如果没有,它会变得有点棘手。)

    您需要创建自己的从TList<T> 继承的专用列表类,并为其添加一个虚拟 Next() 函数,并为您的列表添加一个枚举器字段。当您设置 for..in 循环时,编译器在内部使用枚举器,但您可以手动运行它们。 Next() 函数创建枚举器(如果尚未分配)并将其放入字段中,然后对其调用 MoveNext()。如果成功,请致电 GetCurrent 并获取您的号码。否则,你就完了。 FreeAndNil 枚举器,并向调用函数发出信号,表明您没有任何可返回的内容。可能最简单的方法是在 Next() 中放置一个 var 布尔参数,该参数返回其对 MoveNext 的调用结果,并让调用函数检查其值。

    对于较高的列表,它稍微复杂一些,但并不多。从您刚刚设置的泛型类下降,并覆盖 Next()。这将获得一个枚举器,它枚举它所拥有的列表,并返回 FEnumerator.GetCurrent.Next() 的值,直到该子列表用尽,然后在其枚举器上调用 MoveNext。

    这应该适用于任何深度的嵌套列表。只是不要尝试创建一个同时包含数字和列表的列表。

    【讨论】:

    • 如果你被 Delphi 2007 困住了,你仍然可以这样做。从 TList 下降,并重写 Items 属性以及 Get 和 Put 方法,以将所有内容强制转换为正确的类型。如果没有泛型,它会变得有点难看,但可以做到。
    【解决方案2】:

    方法1:“分层列表”是一棵树,使用(或编写)树/树节点类,并对其值进行简单的深度优先搜索。

    方法 2:为您的分层列表类型显式编写深度优先搜索:

    TEnumerator = RECORD
      private
        FStack : Stack of TNestedList;  //a stack, holding which level of the tree you are exploring
        FIndexStack : Stack of Integer; //a twin stack, holding the index of the child you are exploring at each layer
    
      public
        Init( AList : TNestedList );
        Next( var AVal : Integer ) : boolean;
    end;
    
    
    procedure TEnumerator.Init( AList );
    begin
      SetLength(FStack, 1);
      FStack[0] := AList;
      SetLength( FIndexStack, 1 );
      FIndexStack[0] := 0;
    
      _GoToNextValue();
    end;
    
    procedure TEnumerator._GoToNextValue();
    begin
      while FStack.notEmpty()
        and FIndexStack.last > FStack.last.length do
         begin
         //we have finished exploring FStack.last, we need to explore its next sibling :
         //pop FStack.last :
           FIndexStack.pop;
           FStack.pop;
         //go to next sibling :
           FIndexStack.last := FIndexStack.last + 1;
         end;
    
      //nothing left to explore :
      if FStack.empty then Exit;
    
      //else :
      //  dig through the layers of list until you reach a value
      while FStack.last.isAList() do
      begin
        FStack.push( FStack.last[ FIndexStack.last ] );
        FIndexStack.push( 0 );    
      end;
    end;
    
    function Next( var AVal : Integer ) : boolean;
    begin
      _GoToNextValue();
    
      Result := FStack.notEmpty();
    
      if Result then
      begin
        AVal := FStack.last[ FIndexStack.last ];
        FIndexSatck.last := FIndexSatck.last + 1;
      end;
    end;
    

    你基本上明确地做了“yield”会做的事情(即:保存调用堆栈的“冻结副本”,返回当前值)

    用法:

    LEnum : TEnumerator;
    
    LEnum.Init( myList );
    while LEnum.Next( LVal ) do
    begin
      <do something>
    end;
    

    【讨论】:

      【解决方案3】:

      为了解决这个问题,我最终制作了一个简单的索引列表并记住了我的位置:(在 pythonesque 中,因为它更短)

      class Nested:
          nestedList = [[1,2,3],[4,5],[],[6],[7,8]]
          positions = []
          curr = 0
      
          def Setup:
              for a in nestedList:
                  for b in a:
                     positions.add((a,b))
      
          def Next:
              p = positions[curr]
              curr += 1
              return nestedList[p[0]][p[1]]
      

      显然我的列表在迭代过程中没有改变,或者这可能行不通......

      【讨论】:

        猜你喜欢
        • 2020-07-11
        • 1970-01-01
        • 2013-05-11
        • 2018-06-04
        • 2011-12-17
        • 2020-06-06
        • 2014-06-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多