Jak najlepiej zamienić pozycję na liście


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

Wymieniłem jak wyżej. Czy jest jakieś inne najlepsze miejsce do porównania niż to?

Odpowiedzi:


112

Użyj Lambda, aby znaleźć indeks na liście i użyj tego indeksu do zastąpienia elementu listy.

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

15
Sprawdź -1! O ile przedmiot nie jest obecny w kolekcjach
Surender Singh Malik

3
plus jeden do korzystania z FindIndex
Aaron Barker

2
To najlepsza uniwersalna odpowiedź IMHO, ponieważ może być również używana do porównywania obiektów.
Simcha Khabinsky

Zobacz ulepszenie Feja, które sprawdza -1 . Chociaż dla prostego Equalstestu, stare dobre IndexOfdziała równie dobrze i jest bardziej zwięzłe - jak w odpowiedzi Tima .
ToolmakerSteve

109

Możesz uczynić go bardziej czytelnym i wydajniejszym:

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

To pyta tylko raz o indeks. Twoje podejście wykorzystuje Containsnajpierw, który musi zapętlić wszystkie elementy (w najgorszym przypadku), a następnie używasz, IndexOfktóry wymaga ponownego wyliczenia elementów.


2
To jest poprawna odpowiedź do znajdowania literałów - int, stringów, ale niezbyt dobra do znajdowania obiektów. Jednak bardziej podoba mi się odpowiedź rokkuchana, ponieważ jest uniwersalna.
Simcha Khabinsky

1
@SimchaKhabinsky: działa również z typami referencyjnymi, typ musi po prostu przesłonić Equalslub znajdziesz obiekt tylko wtedy, gdy jest to to samo odniesienie. Zauważ, że stringjest to również obiekt (typ referencyjny).
Tim Schmelter,

Tak masz rację. Jednak widziałem wielu programistów, którzy nie pamiętali o wdrożeniu Equals i trzeba też pamiętać, że czasami w tym samym czasie trzeba to zaimplementowaćGetHashCode
Simcha Khabinsky.

1
@SimchaKhabinsky: tak, zawsze należy zastąpić GetHashCodejeśli przesłonić Equals, ale GetHashCodejest stosowany tylko wtedy, gdy obiekt jest przechowywany w zbiorze (Fe Dictionarylub HashSet), więc to nie jest stosowany z IndexOflub Containstylko Equals.
Tim Schmelter,

Tim, mam pytanie odnośnie tego vs rokkuchan. Czytałem w dokumentach, które IndexOfużywają EqualityComparer<T>.Default. Czy chcesz powiedzieć, że w końcu wywoła item.Equals(target)każdą pozycję z listy, a zatem zachowuje się dokładnie tak samo, jak odpowiedź rokkuchana?
ToolmakerSteve

16

Uzyskujesz dostęp do listy dwukrotnie, aby zamienić jeden element. Myślę, że prosta forpętla powinna wystarczyć:

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

1
@gzaxx. „Uzyskujesz dostęp do swojej listy dwukrotnie, aby zastąpić jeden element. Myślę, że prosta pętla for powinna wystarczyć”. I ile razy uzyskujesz dostęp do listy w swoim koleżance for?
Pap

5
@Pap przepraszam, nie wyraziłem się wystarczająco jasno. Powtarza swoją listę dwukrotnie (najpierw, aby sprawdzić, czy pozycja znajduje się na liście, a następnie, aby uzyskać indeks pozycji).
gzaxx,

14

Dlaczego nie skorzystać z metod rozszerzających?

Rozważ następujący kod:

        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}
  • Brak powielania kodu
  • Nie ma potrzeby wpisywania długich wyrażeń linq
  • Nie ma potrzeby dodatkowych zastosowań

Kod źródłowy:

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

Dwie pierwsze metody zostały dodane w celu zmiany obiektów typów referencyjnych w miejscu. Oczywiście możesz użyć tylko trzeciej metody dla wszystkich typów.

PS Dzięki obserwacji Mike'a dodałem metodę ReplaceAll.


1
Re „zmień w miejscu obiekty typów referencyjnych” - Tnie ma znaczenia, czy jest to typ referencyjny, czy nie. Liczy się to, czy chcesz zmutować (zmienić) listę, czy zwrócić nową listę. Trzecia metoda oczywiście nie zmienia pierwotną listę, więc nie można używać tylko z trzeciej metody ... . Pierwsza metoda to ta, która odpowiada na określone pytanie. Doskonały kod - wystarczy poprawić opis tego, co robią metody :)
ToolmakerSteve

7

Zgodnie z odpowiedzią rokkuchana, po prostu niewielka aktualizacja:

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

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

5

Użyj FindIndexi lambda, aby znaleźć i zamienić wartości:

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

Możesz użyć kolejnych rozszerzeń, które są oparte na warunku predykatu:

    /// <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;
    }

Uwagi: - Zamiast rozszerzenia ThrowIfArgumentIsNull można zastosować ogólne podejście, takie jak:

if (argName == null) throw new ArgumentNullException(nameof(argName));

Więc twój przypadek z tymi rozszerzeniami można rozwiązać jako:

string targetString = valueFieldValue.ToString();
listofelements.Replace(x => x.Equals(targetString), value.ToString());

1

Nie wiem, czy jest to najlepsze, czy nie, ale możesz go również użyć

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

Lub, opierając się na sugestii Rusian L., jeśli szukany przedmiot może znajdować się na liście więcej niż raz:

[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

Możesz użyć wyrażenia lambda w ten sposób.

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

0

uważam, że najlepiej zrobić to szybko i prosto

  1. znajdź swój przedmiot na liście

    var d = Details.Where(x => x.ProductID == selectedProduct.ID).SingleOrDefault();
    
  2. zrobić klon z bieżącego

    OrderDetail dd = d;
    
  3. Zaktualizuj klon ur

    dd.Quantity++;
    
  4. znajdź indeks na liście

    int idx = Details.IndexOf(d);
    
  5. usuń znalezioną pozycję w (1)

      Details.Remove(d);
    
  6. wstawić

     if (idx > -1)
          Details.Insert(idx, dd);
      else
          Details.Insert(Details.Count, dd);
    
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.