【问题标题】:Why does Stack<T> class in C# allow ElementAt(index) while it is an ADT?为什么 C# 中的 Stack<T> 类允许 ElementAt(index) 而它是 ADT?
【发布时间】:2018-11-02 02:30:57
【问题描述】:

堆栈是一种抽象数据类型 (ADT),它应该有一个密封的操作列表,如 Push()、Pop()、Peek() 等,以强制执行后进先出 (LIFO ) 原则。

但它有 ElementAt(index) 允许我访问堆栈中的任何元素。据我了解,Stack 对不在表面的元素的可见性应该较低。不是吗?

【问题讨论】:

  • Stack&lt;T&gt; 没有拥有 ElementAt() 方法。它实现了IEnumerable&lt;T&gt;,为此有linq 扩展ElementAt()。但这不能更改堆栈(你可以更改元素的属性,但不能更改堆栈中的引用),所以我在这里没有看到问题。

标签: c# data-structures stack adt


【解决方案1】:

ElementAt() 是通过 Linq 的 Enumerable.ElementAt() 提供的,而不是通过 Stack&lt;T&gt; 接口提供的。

这是可行的,因为 Stack&lt;T&gt; 实现了 IEnumerable&lt;T&gt;,这是实现 ElementAt() 所需的全部,因为它所做的只是遍历通过 IEnumerable&lt;T&gt; 提供的所有元素,直到访问了其中 N 个元素。

对于Stack&lt;T&gt;,这是一个 O(N) 操作。如果您将ElementAt()List&lt;T&gt; 一起使用,那么内部优化会将其转换为O(1) 操作。

至于为什么Stack&lt;T&gt; 实现IEnumerable&lt;T&gt; - 只有一位设计师才能真正回答这个问题。由于它是一个非变异操作,因此它并没有真正违反堆栈的任何基本原则。我会提供它是为了方便。

正如 /u/Damien_The_Unbeliever 在他的回答中指出的那样,代码可以通过将 N 个元素弹出到另一个堆栈然后将它们全部推回以使原始堆栈保持不变来确定没有 IEnumerable 接口的第 N 个元素。

美中不足的是微软没有记录 Stack 的IEnumerable 返回其元素的顺序。您可以检查源代码以查看它确实以 LIFO 顺序返回元素 - 但这根本没有记录。

this question 的答案中对此进行了讨论。

无论如何,为您所说的抽象堆栈定义的 ADT 接口在哪里?我不认为有一个明确的答案。严格来说,你可以说一个堆栈只有Push()Pop()。然而大多数实现也提供Count

作为the article about Stack on Wikipedia states:

在许多实现中,堆栈比“push”和“pop”有更多的操作。一个例子是“栈顶”或“peek”,它观察最顶层的元素而不将其从栈中移除。

因为这可以通过具有相同数据的“pop”和“push”来完成,所以这不是必需的。如果堆栈为空,则“堆栈顶部”操作中可能会出现下溢条件,与“弹出”相同。此外,实现通常有一个函数,它只返回堆栈是否为空。

所以基本上你的问题的答案是:

库设计者决定添加一些非变异的便捷方法,而不仅仅是添加变异方法Push()Pop()

【讨论】:

  • 那么 OP 可能会问为什么堆栈实现 IEnumerable,因为它也不是理论堆栈结构所暗示的合同的一部分。
【解决方案2】:

堆栈是一种抽象数据类型 (ADT)

Stack 的一般概念确实如此,但System.Collections.Generic.Stack&lt;T&gt; 从不承诺是(只是)一个 ADT。

它必须至少提供 ADT 功能(名副其实),但可以免费提供更多功能。

所以它不会尝试隐藏 Contains()、Count、TryPeek() 等。

【讨论】:

    【解决方案3】:

    在伪代码中,这是一个ElementAt,它位于Stack 的外部,并且只使用了ADT 操作:

    T ElementAt<T>(Stack<T> items, int index)
    {
        var tmp = new Stack<T>();
        while(!items.Empty && index > 0)
        {
           tmp.Push(items.Peek());
           items.Pop();
           index --
        }
        try {
            return items.Peek(); //Presumed to throw an appropriate exception if empty
        }
        finally {
           while(!tmp.Empty)
           {
              items.Push(tmp.Peek());
              tmp.Pop();
           }
        }
    }
    

    (以上假设改变Stacks - 一个稍微不同的实现将适用于不可变堆栈并且不需要笨拙的撤消操作)

    当然,事实上,.NET 中的Stack 类型是为实际问题解决 而构建的,而不是为了某种抽象的纯粹性,并且(通过Stack提供允许枚举),实际上可以在此处获得更有效的实现。而且我们不会强迫人们将这些方法复制到“utils”库中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-02-24
      • 1970-01-01
      • 2017-03-19
      • 2022-12-10
      • 2011-02-25
      • 1970-01-01
      • 2010-11-01
      相关资源
      最近更新 更多