如何以最佳方式替换列表项

155
if (listofelements.Contains(valueFieldValue.ToString()))
{
    listofelements[listofelements.IndexOf(valueFieldValue.ToString())] = value.ToString();
}

我已经像上面那样替换了。是否有其他更好的方法来放置比较?

12个回答

225
使用Lambda来查找列表中的索引,并使用该索引替换列表项。
List<string> listOfStrings = new List<string> { "abc", "123", "ghi" };

int index = listOfStrings.FindIndex(s => s == "123");

if (index != -1)
    listOfStrings[index] =  "def";

25
检查是否为-1!如果该项不在集合中。 - Surender Singh Malik
3
使用FindIndex函数加1。 - Aaron Barker
2
在我看来,这是最好的通用答案,因为它也可以用于比较对象。 - Simcha Khabinsky
请参见Fej的增强功能,检查-1。虽然对于简单的Equals测试,好老的IndexOf同样适用,并且更加简洁 - 就像Tim的答案一样。 - ToolmakerSteve
你知道一种同时在多个值上实现这个的方法吗? - undefined

129

你可以让它更易读且更高效:

string oldValue = valueFieldValue.ToString();
string newValue = value.ToString();
int index = listofelements.IndexOf(oldValue);
if(index != -1)
    listofelements[index] = newValue;

这只询问索引一次。你的方法首先使用Contains,需要遍历所有项(在最坏情况下),然后再使用IndexOf,需要再次枚举项。


3
这是查找字面量 - 整数,字符串的正确答案,但不适用于查找对象。然而,我更喜欢rokkuchan的答案,因为它是通用的。 - Simcha Khabinsky
1
@SimchaKhabinsky:也适用于引用类型,只需要重写Equals方法即可,否则只有在引用相同时才能找到该对象。请注意,string也是一个对象(引用类型)。 - Tim Schmelter
是的,你说得对。然而,我见过很多开发人员忘记实现“Equals”,而且你还要记住有时候同时需要实现“GetHashCode”。 - Simcha Khabinsky
1
@SimchaKhabinsky:是的,如果你重写了Equals方法,那么你应该总是重写GetHashCode方法。但是,GetHashCode方法只有在对象存储在集合中(例如DictionaryHashSet)时才会被使用,因此它不会与IndexOfContains一起使用,只会与Equals一起使用。 - Tim Schmelter
Tim,我有一个关于this和rokkuchan的问题。我在文档中读到IndexOf使用EqualityComparer<T>.Default。你是说这最终会为列表中的每个项调用item.Equals(target),因此具有与rokkuchan答案完全相同的行为吗? - ToolmakerSteve

23

为什么不使用扩展方法?

考虑以下代码:

        var intArray = new int[] { 0, 1, 1, 2, 3, 4 };
        // Replaces the first occurance and returns the index
        var index = intArray.Replace(1, 0);
        // {0, 0, 1, 2, 3, 4}; index=1

        var stringList = new List<string> { "a", "a", "c", "d"};
        stringList.ReplaceAll("a", "b");
        // {"b", "b", "c", "d"};

        var intEnum = intArray.Select(x => x);
        intEnum = intEnum.Replace(0, 1);
        // {0, 0, 1, 2, 3, 4} => {1, 1, 1, 2, 3, 4}
  • 没有重复的代码
  • 不需要输入长的Linq表达式
  • 不需要额外使用

源代码:

namespace System.Collections.Generic
{
    public static class Extensions
    {
        public static int Replace<T>(this IList<T> source, T oldValue, T newValue)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));

            var index = source.IndexOf(oldValue);
            if (index != -1)
                source[index] = newValue;
            return index;
        }

        public static void ReplaceAll<T>(this IList<T> source, T oldValue, T newValue)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));

            int index = -1;
            do
            {
                index = source.IndexOf(oldValue);
                if (index != -1)
                    source[index] = newValue;
            } while (index != -1);
        }


        public static IEnumerable<T> Replace<T>(this IEnumerable<T> source, T oldValue, T newValue)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));

            return source.Select(x => EqualityComparer<T>.Default.Equals(x, oldValue) ? newValue : x);
        }
    }
}

前两种方法已添加以在原地更改引用类型的对象。当然,您可以对所有类型使用第三种方法。

P.S. 感谢迈克的观察,我已添加了ReplaceAll方法。


1
重新“就地更改引用类型的对象”-无论T是否为引用类型都不重要。重要的是您是否想要突变(更改)列表或返回新列表。第三种方法当然不会改变原始列表,因此您不能仅使用第三种方法...。第一种方法是回答特定问题的方法。优秀的代码-只是纠正了您对方法功能的描述 :) - ToolmakerSteve

17

您正在两次访问列表以替换一个元素。我认为简单的for循环就足够了:

var key = valueFieldValue.ToString();
for (int i = 0; i < listofelements.Count; i++)
{
    if (listofelements[i] == key)
    {
        listofelements[i] = value.ToString();
        break;
    }
}

1
@gzaxx。"您正在访问列表两次以替换一个元素。我认为简单的for循环就足够了。" 那么在您的for循环中,您会访问列表多少次呢? - Pap
7
抱歉,我之前表述不够清晰。他正在对列表进行两次迭代(第一次是为了检查项目是否在列表中,第二次是为了获取项目索引)。 - gzaxx

10

根据rokkuchan的答案,稍微进行了一些升级:

List<string> listOfStrings = new List<string> {"abc", "123", "ghi"};

int index = listOfStrings.FindIndex(ind => ind.Equals("123"));
if (index > -1)
    listOfStrings[index] =  "def";

6

您可以使用基于谓词条件的以下扩展:

    /// <summary>
    /// Find an index of a first element that satisfies <paramref name="match"/>
    /// </summary>
    /// <typeparam name="T">Type of elements in the source collection</typeparam>
    /// <param name="this">This</param>
    /// <param name="match">Match predicate</param>
    /// <returns>Zero based index of an element. -1 if there is not such matches</returns>
    public static int IndexOf<T>(this IList<T> @this, Predicate<T> match)
    {
        @this.ThrowIfArgumentIsNull();
        match.ThrowIfArgumentIsNull();

        for (int i = 0; i < @this.Count; ++i)
            if (match(@this[i]))
                return i;

        return -1;
    }

    /// <summary>
    /// Replace the first occurance of an oldValue which satisfies the <paramref name="removeByCondition"/> by a newValue
    /// </summary>
    /// <typeparam name="T">Type of elements of a target list</typeparam>
    /// <param name="this">Source collection</param>
    /// <param name="removeByCondition">A condition which decides is a value should be replaced or not</param>
    /// <param name="newValue">A new value instead of replaced</param>
    /// <returns>This</returns>
    public static IList<T> Replace<T>(this IList<T> @this, Predicate<T> replaceByCondition, T newValue)
    {
        @this.ThrowIfArgumentIsNull();
        removeByCondition.ThrowIfArgumentIsNull();

        int index = @this.IndexOf(replaceByCondition);
        if (index != -1)
            @this[index] = newValue;

        return @this;
    }

    /// <summary>
    /// Replace all occurance of values which satisfy the <paramref name="removeByCondition"/> by a newValue
    /// </summary>
    /// <typeparam name="T">Type of elements of a target list</typeparam>
    /// <param name="this">Source collection</param>
    /// <param name="removeByCondition">A condition which decides is a value should be replaced or not</param>
    /// <param name="newValue">A new value instead of replaced</param>
    /// <returns>This</returns>
    public static IList<T> ReplaceAll<T>(this IList<T> @this, Predicate<T> replaceByCondition, T newValue)
    {
        @this.ThrowIfArgumentIsNull();
        removeByCondition.ThrowIfArgumentIsNull();

        for (int i = 0; i < @this.Count; ++i)
            if (replaceByCondition(@this[i]))
                @this[i] = newValue;

        return @this;
    }

注: - 您可以使用一般方法而不是ThrowIfArgumentIsNull扩展进行操作,例如:
if (argName == null) throw new ArgumentNullException(nameof(argName));

所以,您使用这些扩展时遇到的问题可以通过以下方式解决:
string targetString = valueFieldValue.ToString();
listofelements.Replace(x => x.Equals(targetString), value.ToString());

这是一个有用的示例。 - Mike Finch

4

使用FindIndex和lambda表达式查找并替换您的值:

int j = listofelements.FindIndex(i => i.Contains(valueFieldValue.ToString())); //Finds the item index

lstString[j] = lstString[j].Replace(valueFieldValue.ToString(), value.ToString()); //Replaces the item by new value

它将替换第一次出现的字符串部分,并将结果设置为列表项... - Adam Silenko

4
您可以像这样使用lambda表达式。
int index = listOfElements.FindIndex(item => item.Id == id);  
if (index != -1) 
{
    listOfElements[index] = newValue;
}

1

或者,基于Rusian L.的建议,如果你正在搜索的项目可以在列表中出现多次:

[Extension()]
public void ReplaceAll<T>(List<T> input, T search, T replace)
{
    int i = 0;
    do {
        i = input.FindIndex(i, s => EqualityComparer<T>.Default.Equals(s, search));

        if (i > -1) {
            FileSystem.input(i) = replace;
            continue;
        }

        break;  
    } while (true);
}

1
我不知道它是否最好,但你也可以使用它。
List<string> data = new List<string>
(new string[]   { "Computer", "A", "B", "Computer", "B", "A" });
int[] indexes = Enumerable.Range(0, data.Count).Where
                 (i => data[i] == "Computer").ToArray();
Array.ForEach(indexes, i => data[i] = "Calculator");

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接