วิธีแทนที่รายการด้วยวิธีที่ดีที่สุด


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

ฉันได้แทนที่เหมือนข้างบน มีวิธีอื่นใดที่จะเปรียบเทียบได้ดีกว่าทางนี้

คำตอบ:


119

ใช้ Lambda เพื่อค้นหาดัชนีในรายการและใช้ดัชนีนี้เพื่อแทนที่รายการ

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

16
ตรวจสอบ -1! ในกรณีที่ไม่มีสินค้าอยู่ในคอลเลกชัน
Surender Singh Malik

3
บวกหนึ่งสำหรับการใช้ FindIndex
Aaron Barker

2
นี่เป็นคำตอบสากลที่ดีที่สุด IMHO เนื่องจากสามารถใช้เปรียบเทียบวัตถุได้เช่นกัน
Simcha Khabinsky

ดูการเพิ่มประสิทธิภาพ Fej ว่าการตรวจสอบสำหรับ -1 แต่สำหรับง่ายๆEqualsทดสอบเก่าดีIndexOfทำงานได้เป็นอย่างดีและเป็นที่กระชับมากขึ้น - ในขณะที่คำตอบของทิม
ToolmakerSteve

110

คุณสามารถทำให้อ่านง่ายขึ้นและมีประสิทธิภาพมากขึ้น:

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

สิ่งนี้ถามเพียงครั้งเดียวสำหรับดัชนี แนวทางของคุณใช้Containsก่อนซึ่งจำเป็นต้องวนซ้ำรายการทั้งหมด (ในกรณีที่เลวร้ายที่สุด) จากนั้นคุณกำลังใช้IndexOfสิ่งที่ต้องระบุรายการอีกครั้ง


2
นี่คือคำตอบที่ถูกต้องสำหรับการค้นหาตัวอักษร - ints สตริง แต่ไม่ดีมากสำหรับการค้นหาวัตถุ อย่างไรก็ตามฉันชอบคำตอบของ rokkuchan ดีกว่าเนื่องจากเป็นสากล
Simcha Khabinsky

1
@SimchaKhabinsky: ใช้งานได้กับประเภทการอ้างอิงประเภทเพียงต้องการแทนที่Equalsหรือคุณจะพบวัตถุก็ต่อเมื่อเป็นการอ้างอิงเดียวกัน โปรดทราบว่าstringเป็นวัตถุ (ชนิดการอ้างอิง)
Tim Schmelter

ใช่คุณถูก. อย่างไรก็ตามฉันเคยเห็นนักพัฒนาหลายคนไม่จำที่จะนำไปใช้Equals และคุณต้องจำไว้ว่าบางครั้งในเวลาเดียวกันคุณต้องใช้GetHashCode
Simcha Khabinsky

1
@SimchaKhabinsky: ใช่คุณควรแทนที่GetHashCodeถ้าคุณแทนที่Equalsแต่GetHashCodeจะใช้เฉพาะในกรณีที่วัตถุที่ถูกเก็บไว้ในชุด (fe DictionaryหรือHashSet) ดังนั้นจึงไม่ได้นำมาใช้กับIndexOfหรือเพียงContains Equals
Tim Schmelter

ทิมฉันมีคำถามเกี่ยวกับเรื่องนี้กับโรคุจัง ผมอ่านในเอกสารที่ใช้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 loop mate กี่ครั้ง?
Pap

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}
  • ไม่มีการทำซ้ำรหัส
  • ไม่จำเป็นต้องพิมพ์นิพจน์ 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);
        }
    }
}

มีการเพิ่มสองวิธีแรกเพื่อเปลี่ยนอ็อบเจ็กต์ของประเภทการอ้างอิงในตำแหน่ง แน่นอนคุณสามารถใช้เพียงวิธีที่สามสำหรับทุกประเภท

ปล. ขอบคุณการสังเกตของไมค์ฉันได้เพิ่มเมธอด ReplaceAll


1
Re "เปลี่ยนวัตถุของประเภทการอ้างอิงในตำแหน่ง" - ไม่ว่าTจะเป็นประเภทการอ้างอิงหรือไม่ไม่เกี่ยวข้อง สิ่งที่สำคัญคือคุณต้องการเปลี่ยนแปลง (แก้ไข) รายการหรือส่งคืนรายการใหม่ วิธีที่สามของหลักสูตรจะไม่เปลี่ยนแปลงรายการเดิมดังนั้นคุณจึงไม่สามารถ ใช้เพียงวิธีที่สาม ... วิธีแรกคือวิธีที่ตอบคำถามเฉพาะที่ถาม รหัสที่ยอดเยี่ยม - เพียงแค่แก้ไขคำอธิบายของคุณว่าวิธีการทำอะไร :)
ToolmakerSteve

8

ตามคำตอบของ 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

หรือสร้างตามคำแนะนำของ 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

คุณสามารถใช้แลมบ์ดานิพจน์เช่นนี้

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);
    
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.