Як замінити елемент списку найкращим чином


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

Я замінив, як вище. Чи є інший найкращий спосіб порівняння, крім цього?

Відповіді:


109

Використовуйте лямбду, щоб знайти індекс у списку, і використовуйте цей індекс для заміни елемента списку.

List<string> listOfStrings = new List<string> {"abc", "123", "ghi"};
listOfStrings[listOfStrings.FindIndex(ind=>ind.Equals("123"))] =  "def";

15
Перевірте на -1! Якщо предмета немає в колекціях
Surender Singh Malik,

3
плюс один за використання FindIndex
Аарон Баркер

2
Це найкраща універсальна відповідь IMHO, оскільки це також може бути використано для порівняння об'єктів.
Сімча Хабінський

Див. Вдосконалення Fej, яке перевіряє на -1 . Хоча для простого Equalsтесту старий добрий IndexOfпрацює так само добре, і є більш стислим - як у відповіді Тіма .
ToolmakerSteve

109

Ви можете зробити його більш читабельним та більш ефективним:

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

Це запитує індекс лише один раз. Ваш підхід використовує Containsспочатку, який повинен цикувати всі елементи (у гіршому випадку), потім ви використовуєте, IndexOfякий повинен перерахувати елементи знову.


2
Це правильна відповідь для пошуку літералів - ints, рядків, але не дуже хороша для пошуку об’єктів. Однак мені більше подобається відповідь rokkuchan, оскільки вона універсальна.
Сімча Хабінський

1
@SimchaKhabinsky: працює також із посилальними типами, тип просто потрібно замінити, Equalsінакше ви знайдете об'єкт, лише якщо це однакові посилання. Зверніть увагу, що stringце також об'єкт (тип посилання).
Тім Шмельтер

Так, ти маєш рацію. Однак я бачив, що багато розробників не пам’ятають про реалізацію, Equals і вам також доводиться пам’ятати, що іноді одночасно вам доводиться реалізовуватиGetHashCode
Сімча Хабінський

1
@SimchaKhabinsky: так, ви завжди повинні перевизначати, GetHashCodeякщо ви перевизначаєте, Equalsале GetHashCodeвикористовується лише в тому випадку, якщо об'єкт зберігається у наборі (fe Dictionaryабо HashSet), тому він не використовується з IndexOfабо Contains, лише Equals.
Тім Шмельтер

Тім, у мене питання про це проти рокучан. Я читав у документах, де IndexOfвикористовується EqualityComparer<T>.Default. Ви хочете сказати, що врешті-решт буде викликати item.Equals(target)кожен елемент у списку, а отже, має точно таку ж поведінку, як відповідь rokkuchan?
ToolmakerSteve

16

Ви переходите до свого списку двічі, щоб замінити один елемент. Я думаю, що простого 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?
Пап

5
@Pap вибачте, я був недостатньо чітким. Він двічі повторює свій список (перший, щоб перевірити, чи є елемент у списку, другий, щоб отримати індекс товару).
gzaxx

14

Чому б не використовувати методи розширення?

Розглянемо такий код:

        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}
  • Немає дублювання коду
  • Немає необхідності вводити вирази long 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("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("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("source");

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

Перші два методи були додані для зміни об’єктів посилальних типів на місці. Звичайно, ви можете використовувати лише третій метод для всіх типів.

PS Завдяки спостереженню Майка , я додав метод ReplaceAll.


1
Повторно "змінити об’єкти посилальних типів на місці" - незалежно від того, Tє посилальний тип чи ні. Важливо те, чи хочете ви змінити (змінити) список або повернути новий. Третій метод, звичайно , не змінюватиме початковий список, так що ви не можете використовувати тільки третій спосіб ... . Перший метод - це той, який відповідає на конкретне запитання. Відмінно - просто коригуючи ваше опис того , що роблять методи :)
ToolmakerSteve

7

Після відповіді rokkuchan, лише невелике оновлення:

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

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

5

Використовуйте FindIndexта лямбда, щоб знайти та замінити ваші значення:

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

3

Ви можете використовувати наступні розширення, які базуються на умові предиката:

    /// <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());

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");

1

Або, спираючись на пропозицію Русіяна Л., якщо предмет, який ви шукаєте, може бути в списку більше одного разу:

[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

Ви можете використовувати такий лямбда-вираз.

int index = listOfElements.FindIndex(item => item.Id == id);  
if (index != -1) 
{
    listOfElements[index] = newValue;
}

0

Я вважаю, що найкраще це зробити швидко і просто

  1. знайти ваш елемент у списку - -

    var d = Details.Where(x => x.ProductID == selectedProduct.ID).SingleOrDefault();
  2. зробити клон з поточного - -

    OrderDetail dd = d;
  3. Оновіть ваш клон

    dd.Quantity++;
  4. знайти індекс у списку

    int idx = Details.IndexOf(d);
  5. видалити заснований предмет у (1)

      Details.Remove(d);
  6. вставити

     if (idx > -1)
          Details.Insert(idx, dd);
      else
          Details.Insert(Details.Count, dd);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.