【问题标题】:Replace items in a list -- in place替换列表中的项目——就地
【发布时间】:2018-03-02 15:16:00
【问题描述】:

我遇到了一些简单的 C# 代码问题,我可以在 C/C++ 中轻松修复。 我想我错过了一些东西。

我想做以下事情(修改列表中的项目——就地):

//pseudocode
void modify<T>(List<T> a) {
  foreach(var item in a) {
    if(condition(item)) {
      item = somethingElse;
    }
  }
}

我知道 foreach 循环在一个被视为不可变的集合上,所以上面的代码不能工作。

因此我尝试了以下方法:

void modify<T>(List<T> a) {
  using (var sequenceEnum = a.GetEnumerator())
  {
    while (sequenceEnum.MoveNext())
    {
      var m = sequenceEnum.Current;
      if(condition(m)) {
         sequenceEnum.Current = somethingElse;
      }
    }
  }
}

天真地认为 Enumerator 是某种指向我的元素的指针。显然枚举数也是不可变的。

在 C++ 中我会这样写:

template<typename T>
struct Node {
    T* value;
    Node* next;
}

然后能够修改 *value 而不触及 Node 中的任何内容,因此在父集合中:

Node<T>* current = a->head;
while(current != nullptr) {
    if(condition(current->value)) 
        current->value = ...
    }
    current = current->next;
}

我真的需要不安全的代码吗?

还是我在循环中陷入了调用下标的可怕问题?

【问题讨论】:

  • “任何一个 C# 都有严重的问题” - 你知道有多少人使用 C#?你真的认为如果这种语言存在严重问题,它会是目前最受欢迎的语言之一吗?退后一步考虑一下。问题更可能出在您身上,而不是语言上。
  • 好的,我从我的问题中删除了我的幽默
  • 使用for 循环有什么问题?
  • 和 C,因为我添加了模板
  • (properties) modified & set default(null in the case of reference types)是一个非常不同的东西。提出正确的问题,您将得到准确的答案。

标签: c# list enumerator


【解决方案1】:

您也可以使用简单的for 循环。

void modify<T>(List<T> a)
{
    for (int i = 0; i < a.Count; i++)
    {
        if (condition(a[i]))
        {
            a[i] = default(T);
        }
    }
}

【讨论】:

    【解决方案2】:

    简而言之 - 不要修改列表。您可以使用

    达到预期的效果
    a = a.Select(x => <some logic> ? x : default(T)).ToList()
    

    通常,C# 中的列表在迭代期间是不可变的。您可以使用 .RemoveAll 或类似方法。

    【讨论】:

    • 我明确要求在原地进行修改。
    【解决方案3】:

    如文档here 中所述,System.Collections.Generic.List&lt;T&gt;System.Collections.ArrayList 的通用实现,它在索引访问器中具有 O(1) 复杂度。非常像 C++ std::vector&lt;&gt;,插入/添加元素的复杂性是不可预测的,但访问是时间常数(复杂性,关于缓存等)。

    相当于您的 C++ 代码 sn-p 将是 LinkedList

    关于你的集合在迭代过程中的不变性,在GetEnumerator方法here的文档中有明确的说明。事实上,在枚举期间(在foreach 内,或直接使用IEnumerator.MoveNext):

    枚举器可用于读取集合中的数据,但它们 不能用于修改底层集合。

    而且,修改列表会使枚举器失效,通常会抛出异常:

    只要集合仍然存在,枚举器就保持有效 不变。如果对集合进行了更改,例如添加, 修改或删除元素,枚举数不可恢复 无效且其行为未定义。

    我相信各种类型集合之间的接口契约一致性会导致您的误解:可以实现可变列表,但接口契约不需要。

    假设您想实现一个在枚举期间可变的列表。枚举器会持有对条目或条目本身的引用(或检索方式)吗?条目本身会使其不可变,例如在链表中插入元素时引用将无效。

    如果你想使用标准的 Collections 库,@Igor 提出的简单 for 循环似乎是最好的方法。否则,您可能需要自己重新实现它。

    【讨论】:

    • 解释为什么 List 上的下标是可以的,这是我所期望的。我天真地认为 List 是 std::list 的 c# 实现,其中 add 是免费的,但 [] 具有 O(n) 复杂性。谢谢
    【解决方案4】:

    使用这样的东西:

    List<T> GetModified<T>(List<T> list, Func<T, bool> condition, Func<T> replacement)
    {
       return list.Select(m => if (condition(m)) 
                            { return m; }
                           else
                            { return replacement(); }).ToList();
    }
    

    用法:

    originalList = GetModified(originalList, i => i.IsAwesome(), null);
    

    但这也可能让您陷入跨线程操作的麻烦。尽可能尝试使用不可变实例,尤其是使用 IEnumerable。

    如果真的要修改list的实例:

    //if you ever want to also remove items, this is magic (why I iterate backwards)
    for (int i = list.Count - 1; i >= 0; i--)
    {
       if (condition)
       {
         list[i] = whateverYouWant;
       }
    }
    

    【讨论】:

    • 没错,你得到了一个新版本的列表。啊等等,我会更新一下我的答案。
    猜你喜欢
    • 1970-01-01
    • 2019-12-02
    • 2012-11-11
    • 2021-03-06
    • 1970-01-01
    • 1970-01-01
    • 2017-04-11
    • 2010-09-15
    • 2019-02-21
    相关资源
    最近更新 更多