ฉันจะค้นหาองค์ประกอบสุดท้ายในรายการ <> ได้อย่างไร


172

ต่อไปนี้เป็นสารสกัดจากรหัสของฉัน:

public class AllIntegerIDs 
{
    public AllIntegerIDs() 
    {            
        m_MessageID = 0;
        m_MessageType = 0;
        m_ClassID = 0;
        m_CategoryID = 0;
        m_MessageText = null;
    }

    ~AllIntegerIDs()
    {
    }

    public void SetIntegerValues (int messageID, int messagetype,
        int classID, int categoryID)
    {
        this.m_MessageID = messageID;
        this.m_MessageType = messagetype;
        this.m_ClassID = classID;
        this.m_CategoryID = categoryID;
    }

    public string m_MessageText;
    public int m_MessageID;
    public int m_MessageType;
    public int m_ClassID;
    public int m_CategoryID;
}

ฉันพยายามใช้สิ่งต่อไปนี้ในรหัสฟังก์ชัน main () ของฉัน:

List<AllIntegerIDs> integerList = new List<AllIntegerIDs>();

/* some code here that is ised for following assignments*/
{
   integerList.Add(new AllIntegerIDs());
   index++;
   integerList[index].m_MessageID = (int)IntegerIDsSubstring[IntOffset];
   integerList[index].m_MessageType = (int)IntegerIDsSubstring[IntOffset + 1];
   integerList[index].m_ClassID = (int)IntegerIDsSubstring[IntOffset + 2];
   integerList[index].m_CategoryID = (int)IntegerIDsSubstring[IntOffset + 3];
   integerList[index].m_MessageText = MessageTextSubstring;
}

ปัญหาอยู่ที่นี่: ฉันกำลังพยายามพิมพ์องค์ประกอบทั้งหมดในรายการของฉันโดยใช้การวนซ้ำสำหรับ:

for (int cnt3 = 0 ; cnt3 <= integerList.FindLastIndex ; cnt3++) //<----PROBLEM HERE
{
   Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\n", integerList[cnt3].m_MessageID,integerList[cnt3].m_MessageType,integerList[cnt3].m_ClassID,integerList[cnt3].m_CategoryID, integerList[cnt3].m_MessageText);
}

ฉันต้องการค้นหาองค์ประกอบสุดท้ายเพื่อให้ฉันเทียบ cnt3 ในลูป for for และพิมพ์รายการทั้งหมดในรายการ แต่ละองค์ประกอบในรายการเป็นวัตถุของคลาส AllIntegerIDs ดังกล่าวข้างต้นในตัวอย่างโค้ด ฉันจะค้นหารายการที่ถูกต้องล่าสุดในรายการได้อย่างไร

ฉันควรใช้บางอย่างเช่น integerList.Find (integerList []. m_MessageText == null;

ถ้าฉันใช้มันจะต้องมีดัชนีที่จะอยู่ในช่วงจาก 0 ถึงสูงสุดใด ๆ หมายถึงฉันจะต้องใช้อันอื่นสำหรับลูปซึ่งฉันไม่ต้องการใช้ มีวิธีที่สั้นกว่า / ดีกว่าหรือไม่?

ขอบคุณ Viren


@Viren: ฉันเยื้องรหัสเพื่อให้แสดงอย่างถูกต้อง หากคุณทำการแก้ไขภายใต้ฉันคุณสามารถตรวจสอบให้แน่ใจว่าฉันไม่ได้ทำการยกเลิกหรือไม่
Sam Harwell

8
ไม่เกี่ยวข้องกับคำถามของคุณ แต่คุณไม่ควรใช้เครื่องมือปรับแต่งขั้นสุดท้ายหากจำเป็นจริงๆ
Brian Rasmussen

ไม่เกี่ยวข้องกับคำถาม แต่สำหรับการอ่านและการบำรุงรักษาผมขอแนะนำให้คุณทำAllIntegerIDs newItem = new AllIntegerID();, การใช้งานที่จะกำหนดให้ทุกสาขาและแล้วintegerList.Add(newItem)โทร หรือใช้คุณสมบัติแทนฟิลด์และใช้ไวยากรณ์ตัวกำหนดค่าเริ่มต้นของวัตถุ C # 3.0
Thorarin

คำตอบ:


208

หากคุณต้องการเข้าถึงรายการสุดท้ายในรายการที่คุณสามารถทำได้

if(integerList.Count>0)
{
   var item = integerList[integerList.Count - 1];
}

เพื่อรับจำนวนทั้งหมดของรายการในรายการคุณสามารถใช้คุณสมบัติ Count

var itemCount = integerList.Count;

17
@ Jared ฉันคิดว่าคุณลืมที่จะเพิ่มบรรทัดนี้ "if (integerList.Count! = 0)" ก่อนที่บรรทัดแรก
prabhakaran

21
IMHO สิ่งนี้ไม่สมควรที่จะเป็นคำตอบอันดับต้นมันอ่านได้อย่างน่ากลัวและปล่อยให้โอกาสเกิดข้อผิดพลาดหากการนับเป็นศูนย์ วิธีCleanCode ™จะใช้Last/ LastOrDefaultตามที่กล่าวไว้ด้านล่าง
พริก

2
ตามที่ระบุไว้ก่อนหน้านี้คำตอบนี้ไม่ได้คำนึงถึงสถานการณ์เมื่อรายการว่างเปล่าและไม่ควรใช้ IMHO
merrr

2
@chillitom @merrr การใช้วิธีการขยาย LINQ ไม่ได้ช่วย Enumerable.Lastจะโยนข้อยกเว้นหากรายการว่างเปล่า หากคุณโทรEnumerable.LastOrDefaultและส่งรายการประเภทค่าค่าเริ่มต้นจะถูกส่งกลับหากรายการว่างเปล่า ดังนั้นถ้าคุณได้ 0 กลับมาจาก a List<int>คุณจะไม่รู้ว่ารายการนั้นว่างเปล่าหรือค่าสุดท้ายคือ 0 ในระยะสั้นคุณจะต้องตรวจสอบCountกลไกการดึงข้อมูลที่คุณตัดสินใจใช้
0b101010

4
@ chillitom แต่ละคนเพื่อตนเอง ในกรณีที่คุณรู้ว่ารายการนั้นมีประชากรฉันคิดว่าvar element = list[list.Count - 1]รวบรัดและอ่านง่าย ไม่จำเป็นต้องเรียกใช้วิธีการขยาย
0b101010

276

ในการรับไอเท็มสุดท้ายของคอลเล็กชันให้ใช้วิธีการนามสกุลLastOrDefault ()และLast ()

var lastItem = integerList.LastOrDefault();

หรือ

var lastItem = integerList.Last();

อย่าลืมเพิ่มusing System.Linq;มิฉะนั้นจะไม่สามารถใช้วิธีนี้ได้


17
ใช่นี่เป็นวิธีที่ดีที่สุด Last และ LastOrDefault ได้รับการปรับให้เหมาะกับรายการ <> s
chillitom

2
@Gusdor ฉันไม่เห็นมันเป็นเอกสาร แต่ฉันมักจะหันไปหาแหล่งที่มา (หรือใช้ disassembler เช่น Resharper, dotPeek หรือ ILSpy) สำหรับสิ่งเหล่านี้โดยตรง จากนั้นฉันจะเห็นว่าFirst, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, ElementAtและElementAtOrDefaultจะเหมาะสำหรับIList<TSource>, CountและContainsเหมาะสำหรับICollection<TSource>และเป็นที่เหมาะสำหรับCast<TResult> IEnumerable<TResult>
พริก

8
ตรวจสอบให้แน่ใจเพื่อเพิ่มusing System.Linq;
ไฮบริด

4
@chillitom วิธีการขยายที่System.Linq.Enumerableไม่ได้รับการปรับให้เหมาะสม นี่คือรหัสสำหรับEnumerable.Lastวิธีการ
0b101010

4
@ chillitom หลังจากอ่านแหล่งที่มาของSystem.Linq.Enumerable.Lastฉันเห็นด้วยกับ 0b101010 - Last()รหัสไม่ได้ "เหมาะสำหรับList<>s" - Last()เป็นเพียงเสื้อคลุมน่าเกลียดซึ่งเป็นค่าเริ่มต้นreturn list[list.Count-1]ในกรณีที่อาร์กิวเมนต์เป็นIListและซ้ำผ่านรายการจนถึงจุดสิ้นสุดในกรณี มันไม่ใช่ ... ทำให้เป็นโซลูชั่นที่แย่มากหากIListเป็นLinkedListเพราะตัวทำดัชนีจะผ่านรายการทั้งหมดโดยไม่จำเป็น (ฉันไม่พบการแทนที่การวนซ้ำย้อนหลังItem[]ด้วยดัชนี> Count / 2 ในแหล่ง c # YMMV )

20

ให้ได้ที่รากของคำถามวิธีการที่อยู่องค์ประกอบสุดท้ายของรายการได้อย่างปลอดภัย ...

ทะลึ่ง

List<string> myList = new List<string>();

แล้วก็

//NOT safe on an empty list!
string myString = myList[myList.Count -1];

//equivalent to the above line when Count is 0, bad index
string otherString = myList[-1];

"count-1" เป็นนิสัยที่ไม่ดีเว้นแต่คุณจะรับประกันว่ารายการนั้นไม่ว่างเปล่า

ไม่มีวิธีที่สะดวกในการตรวจสอบรายการว่างเปล่ายกเว้นการทำรายการ

วิธีที่สั้นที่สุดที่ฉันคิดได้คือ

string myString = (myList.Count != 0) ? myList [ myList.Count-1 ] : "";

คุณสามารถออกไปข้างนอกและสร้างผู้แทนที่ส่งกลับค่าจริงเสมอและส่งต่อไปยัง FindLast ซึ่งจะส่งคืนค่าสุดท้าย (หรือค่าเริ่มต้นที่สร้าง valye หากรายการว่างเปล่า) ฟังก์ชั่นนี้เริ่มต้นในตอนท้ายของรายการดังนั้นจะเป็น Big O (1) หรือเวลาคงที่แม้จะใช้วิธีปกติเป็น O (n)

//somewhere in your codebase, a strange delegate is defined
private static bool alwaysTrue(string in)
{
    return true;
}

//Wherever you are working with the list
string myString = myList.FindLast(alwaysTrue);

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


2
ตัวแทนสามารถถูกแทนที่ด้วยการแสดงออกแลมบ์ดา: myList.FindLast(_unused_variable_name => true);สิ่งนี้จะทำงานโดยไม่คำนึงถึงประเภท รุ่นที่สั้นกว่าคือmyList.FindLast(_ => true);แต่ฉันพบว่าขีดเส้นใต้ (หรือตัวระบุอักขระอื่น ๆ ) อาจทำให้สับสนเล็กน้อยในบางครั้ง
Bob


5

เปลี่ยนแปลง

for (int cnt3 = 0 ; cnt3 <= integerList.FindLastIndex ; cnt3++)

ถึง

for (int cnt3 = 0 ; cnt3 < integerList.Count; cnt3++)

foreach มักจะใช้งานได้สะดวกกว่า แต่ช้ากว่าเล็กน้อย
Eric J.

หากใช้ Count ... ให้ทำ -1 หรือคุณจะได้รับข้อผิดพลาดของดัชนี สำหรับ (int cnt3 = 0; cnt3 <integerList.Count - 1; cnt3 ++)
RiddlerDev

4
นั่นเป็นเหตุผลที่ฉันเปลี่ยน <= เป็น < รหัสที่ถูกต้องโพสต์ :-)
เอริคเจ

@ เอริค: มันช้ากว่า แต่ก็เป็นเรื่องเล็กน้อยที่จะเข้ามาใน JIT ดังนั้นฉันจะแปลกใจถ้าพวกเขาไม่ได้อยู่ในตอนนี้ : dunno:
Sam Harwell

1
@IPX Ares: ดูเหมือนว่าจะยังคงมีปัญหาขึ้นอยู่กับชนิดของข้อมูลที่คุณกำลังทำซ้ำ: stackoverflow.com/questions/365615/…
Eric J.


2

คุณสามารถค้นหาได้โดยนับจำนวนองค์ประกอบในรายการเช่น

int count = list.Count();

จากนั้นคุณสามารถทำดัชนีจำนวน - 1 เพื่อรับองค์ประกอบสุดท้ายในรายการเช่น

int lastNumber = list[count - 1];

2
กรุณาอย่าโพสต์คำตอบที่ซ้ำกัน
Ian Mercer



0

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

AllIntegerIDs ids = new AllIntegerIDs();
ids.m_MessageID = (int)IntegerIDsSubstring[IntOffset];
ids.m_MessageType = (int)IntegerIDsSubstring[IntOffset + 1];
ids.m_ClassID = (int)IntegerIDsSubstring[IntOffset + 2];
ids.m_CategoryID = (int)IntegerIDsSubstring[IntOffset + 3];
ids.m_MessageText = MessageTextSubstring;
integerList.Add(ids);

และในforวงของคุณ:

for (int cnt3 = 0 ; cnt3 < integerList.Count ; cnt3++) //<----PROBLEM HERE
{
   AllIntegerIDs ids = integerList[cnt3];
   Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\n",
      ids.m_MessageID,ids.m_MessageType,ids.m_ClassID,ids.m_CategoryID, ids.m_MessageText);
}

-1

ฉันจะต้องเห็นด้วย foreach จะเป็นอะไรที่ง่ายกว่ามากเช่น

foreach(AllIntegerIDs allIntegerIDs in integerList)
{
Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\n", allIntegerIDs.m_MessageID,
allIntegerIDs.m_MessageType,
allIntegerIDs.m_ClassID,
allIntegerIDs.m_CategoryID,
allIntegerIDs.m_MessageText);
}

นอกจากนี้ฉันขอแนะนำให้คุณเพิ่มคุณสมบัติในการเข้าถึงข้อมูลของคุณแทนเขตข้อมูลสาธารณะทั้งนี้ขึ้นอยู่กับรุ่น. net ของคุณคุณสามารถเพิ่มได้เช่นpublic int MessageType {get; set;}และกำจัดm_ออกจากเขตข้อมูลสาธารณะคุณสมบัติอื่น ๆ ตามที่มันไม่ควรจะมี


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