เรียงลำดับตามธรรมชาติใน C #


129

ใครมีแหล่งข้อมูลที่ดีหรือให้ตัวอย่างการเรียงลำดับธรรมชาติใน C # สำหรับFileInfoอาร์เรย์ ฉันกำลังใช้งานIComparerอินเทอร์เฟซในประเภทของฉัน

คำตอบ:


149

สิ่งที่ง่ายที่สุดที่จะทำคือเพียง P / เรียกใช้ฟังก์ชันในตัวใน Windows และใช้เป็นฟังก์ชันเปรียบเทียบในIComparer:

[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
private static extern int StrCmpLogicalW(string psz1, string psz2);

Michael Kaplan มีตัวอย่างบางส่วนเกี่ยวกับการทำงานของฟังก์ชันนี้ที่นี่และการเปลี่ยนแปลงที่เกิดขึ้นกับ Vista เพื่อให้ใช้งานได้ง่ายขึ้น ข้อดีของฟังก์ชั่นนี้คือมันจะมีลักษณะการทำงานเหมือนกับ Windows เวอร์ชันที่ทำงานอยู่ แต่หมายความว่ามันแตกต่างกันระหว่างเวอร์ชันของ Windows ดังนั้นคุณต้องพิจารณาว่านี่เป็นปัญหาสำหรับคุณหรือไม่

ดังนั้นการใช้งานที่สมบูรณ์จะเป็นดังนี้:

[SuppressUnmanagedCodeSecurity]
internal static class SafeNativeMethods
{
    [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
    public static extern int StrCmpLogicalW(string psz1, string psz2);
}

public sealed class NaturalStringComparer : IComparer<string>
{
    public int Compare(string a, string b)
    {
        return SafeNativeMethods.StrCmpLogicalW(a, b);
    }
}

public sealed class NaturalFileInfoNameComparer : IComparer<FileInfo>
{
    public int Compare(FileInfo a, FileInfo b)
    {
        return SafeNativeMethods.StrCmpLogicalW(a.Name, b.Name);
    }
}

8
คำตอบที่ดี ข้อแม้: วิธีนี้ใช้ไม่ได้กับ Win2000 สำหรับคนไม่กี่คนที่ยังคงใช้งานสิ่งต่างๆบนระบบปฏิบัติการนั้น ในทางกลับกันมีคำแนะนำเพียงพอระหว่างบล็อกของ Kaplan และเอกสาร MSDN เพื่อสร้างฟังก์ชันที่คล้ายกัน
Chris Charabaruk

9
นี่ไม่ใช่แบบพกพาใช้ได้เฉพาะใน Win32 แต่ใช้ไม่ได้ใน Linux / MacOS / Silverlight / Windows Phone / Metro
linquize

20
@linquize - เขาบอกว่า. NET ไม่ใช่ Mono ดังนั้น Linux / OSX จึงไม่น่ากังวล Windows Phone / Metro ไม่มีในปี 2008 เมื่อโพสต์คำตอบนี้ และคุณใช้งานไฟล์ใน Silverlight บ่อยแค่ไหน? ดังนั้นสำหรับ OP และคนอื่น ๆ ส่วนใหญ่มันเป็นคำตอบที่เหมาะสม ไม่ว่าในกรณีใดคุณมีอิสระที่จะให้คำตอบที่ดีกว่า นั่นคือวิธีการทำงานของไซต์นี้
Greg Beech

6
นี่ไม่ได้หมายความว่าคำตอบเดิมผิด ฉันเพิ่งเพิ่มข้อมูลเพิ่มเติมพร้อมข้อมูลล่าสุด
linquize

2
FYI หากคุณรับช่วงจากComparer<T>แทนที่จะใช้งานIComparer<T>คุณจะได้รับIComparerอินเทอร์เฟซ (ไม่ใช่ทั่วไป) ในตัวที่เรียกใช้วิธีการทั่วไปของคุณเพื่อใช้ใน API ที่ใช้แทน มันเป็นพื้นอิสระที่จะทำเกินไป: เพียงแค่ลบ "ฉัน" และการเปลี่ยนแปลงไปpublic int Compare(...) public override int Compare(...)เหมือนกันสำหรับIEqualityComparer<T>และEqualityComparer<T>.
Joe Amenta

75

แค่คิดว่าฉันจะเพิ่มสิ่งนี้ (ด้วยวิธีแก้ปัญหาที่รัดกุมที่สุดที่ฉันหาได้):

public static IOrderedEnumerable<T> OrderByAlphaNumeric<T>(this IEnumerable<T> source, Func<T, string> selector)
{
    int max = source
        .SelectMany(i => Regex.Matches(selector(i), @"\d+").Cast<Match>().Select(m => (int?)m.Value.Length))
        .Max() ?? 0;

    return source.OrderBy(i => Regex.Replace(selector(i), @"\d+", m => m.Value.PadLeft(max, '0')));
}

ด้านบนจะกำหนดตัวเลขใด ๆ ในสตริงให้มีความยาวสูงสุดของตัวเลขทั้งหมดในสตริงทั้งหมดและใช้สตริงผลลัพธ์ในการจัดเรียง

โยนไปที่ ( int?) คือการอนุญาตให้สำหรับคอลเลกชันของสตริงโดยไม่ต้องตัวเลขใด ๆ ( .Max()ในที่ว่างเปล่านับพ่นInvalidOperationException)


1
+1 ไม่เพียง แต่จะกระชับที่สุดเท่านั้น แต่ยังเร็วที่สุดเท่าที่ฉันเคยเห็น ยกเว้นคำตอบที่ยอมรับ แต่ฉันไม่สามารถใช้คำตอบนั้นได้เนื่องจากการพึ่งพาเครื่อง เรียงลำดับมากกว่า 4 ล้านค่าในเวลาประมาณ 35 วินาที
Gene S

4
นี่ทั้งสวยงามและอ่านไม่ออก ฉันคิดว่าประโยชน์ของ Linq จะหมายถึง (อย่างน้อย) ค่าเฉลี่ยที่ดีที่สุดและประสิทธิภาพกรณีที่ดีที่สุดดังนั้นฉันคิดว่าฉันจะไปกับมัน แม้จะไม่มีความชัดเจน ขอบคุณมาก @Matthew Horsley
Ian Grainger

1
นี่เป็นสิ่งที่ดีมาก แต่มีข้อผิดพลาดสำหรับตัวเลขทศนิยมบางตัวตัวอย่างของฉันคือการเรียงลำดับ k8.11 เทียบกับ k8.2 ในการแก้ไขปัญหานี้ฉันใช้ regex ต่อไปนี้: \ d + ([\.,] \ d)?
devzero

2
คุณต้องคำนึงถึงความยาวของกลุ่มที่สอง (จุดทศนิยม + ทศนิยม) เมื่อคุณใส่รหัสนี้ m.Value.PadLeft (สูงสุด '0')
devzero

3
ฉันคิดว่าคุณสามารถใช้แทนการหล่อไป.DefaultIfEmpty().Max() int?นอกจากนี้ยังควรทำsource.ToList()เพื่อหลีกเลี่ยงการแจกแจงซ้ำอีกครั้ง
Teejay

30

ไม่มีการนำไปใช้งานใดที่ดูดีมากดังนั้นฉันจึงเขียนของตัวเอง ผลลัพธ์เกือบจะเหมือนกับการเรียงลำดับที่ใช้โดย Windows Explorer รุ่นใหม่ (Windows 7/8) ความแตกต่างเพียงอย่างเดียวที่ฉันเห็นคือ 1) แม้ว่า Windows จะใช้ (เช่น XP) จัดการกับตัวเลขที่มีความยาวเท่าใดก็ได้ แต่ตอนนี้ จำกัด ไว้ที่ 19 หลัก - ของฉันไม่ จำกัด 2) Windows ให้ผลลัพธ์ที่ไม่สอดคล้องกันกับชุดตัวเลข Unicode บางชุด - งานของฉัน ดี (แม้ว่าจะไม่ได้เปรียบเทียบตัวเลขจากคู่ตัวแทนหรือ Windows) และ 3) ของฉันไม่สามารถแยกแยะน้ำหนักการเรียงลำดับที่ไม่ใช่หลักประเภทต่างๆได้หากเกิดขึ้นในส่วนต่างๆ (เช่น "e-1é" vs " é1e- "- ส่วนก่อนและหลังตัวเลขมีความแตกต่างของน้ำหนักตัวกำกับเสียงและเครื่องหมายวรรคตอน)

public static int CompareNatural(string strA, string strB) {
    return CompareNatural(strA, strB, CultureInfo.CurrentCulture, CompareOptions.IgnoreCase);
}

public static int CompareNatural(string strA, string strB, CultureInfo culture, CompareOptions options) {
    CompareInfo cmp = culture.CompareInfo;
    int iA = 0;
    int iB = 0;
    int softResult = 0;
    int softResultWeight = 0;
    while (iA < strA.Length && iB < strB.Length) {
        bool isDigitA = Char.IsDigit(strA[iA]);
        bool isDigitB = Char.IsDigit(strB[iB]);
        if (isDigitA != isDigitB) {
            return cmp.Compare(strA, iA, strB, iB, options);
        }
        else if (!isDigitA && !isDigitB) {
            int jA = iA + 1;
            int jB = iB + 1;
            while (jA < strA.Length && !Char.IsDigit(strA[jA])) jA++;
            while (jB < strB.Length && !Char.IsDigit(strB[jB])) jB++;
            int cmpResult = cmp.Compare(strA, iA, jA - iA, strB, iB, jB - iB, options);
            if (cmpResult != 0) {
                // Certain strings may be considered different due to "soft" differences that are
                // ignored if more significant differences follow, e.g. a hyphen only affects the
                // comparison if no other differences follow
                string sectionA = strA.Substring(iA, jA - iA);
                string sectionB = strB.Substring(iB, jB - iB);
                if (cmp.Compare(sectionA + "1", sectionB + "2", options) ==
                    cmp.Compare(sectionA + "2", sectionB + "1", options))
                {
                    return cmp.Compare(strA, iA, strB, iB, options);
                }
                else if (softResultWeight < 1) {
                    softResult = cmpResult;
                    softResultWeight = 1;
                }
            }
            iA = jA;
            iB = jB;
        }
        else {
            char zeroA = (char)(strA[iA] - (int)Char.GetNumericValue(strA[iA]));
            char zeroB = (char)(strB[iB] - (int)Char.GetNumericValue(strB[iB]));
            int jA = iA;
            int jB = iB;
            while (jA < strA.Length && strA[jA] == zeroA) jA++;
            while (jB < strB.Length && strB[jB] == zeroB) jB++;
            int resultIfSameLength = 0;
            do {
                isDigitA = jA < strA.Length && Char.IsDigit(strA[jA]);
                isDigitB = jB < strB.Length && Char.IsDigit(strB[jB]);
                int numA = isDigitA ? (int)Char.GetNumericValue(strA[jA]) : 0;
                int numB = isDigitB ? (int)Char.GetNumericValue(strB[jB]) : 0;
                if (isDigitA && (char)(strA[jA] - numA) != zeroA) isDigitA = false;
                if (isDigitB && (char)(strB[jB] - numB) != zeroB) isDigitB = false;
                if (isDigitA && isDigitB) {
                    if (numA != numB && resultIfSameLength == 0) {
                        resultIfSameLength = numA < numB ? -1 : 1;
                    }
                    jA++;
                    jB++;
                }
            }
            while (isDigitA && isDigitB);
            if (isDigitA != isDigitB) {
                // One number has more digits than the other (ignoring leading zeros) - the longer
                // number must be larger
                return isDigitA ? 1 : -1;
            }
            else if (resultIfSameLength != 0) {
                // Both numbers are the same length (ignoring leading zeros) and at least one of
                // the digits differed - the first difference determines the result
                return resultIfSameLength;
            }
            int lA = jA - iA;
            int lB = jB - iB;
            if (lA != lB) {
                // Both numbers are equivalent but one has more leading zeros
                return lA > lB ? -1 : 1;
            }
            else if (zeroA != zeroB && softResultWeight < 2) {
                softResult = cmp.Compare(strA, iA, 1, strB, iB, 1, options);
                softResultWeight = 2;
            }
            iA = jA;
            iB = jB;
        }
    }
    if (iA < strA.Length || iB < strB.Length) {
        return iA < strA.Length ? 1 : -1;
    }
    else if (softResult != 0) {
        return softResult;
    }
    return 0;
}

ลายเซ็นตรงกับComparison<string>ผู้รับมอบสิทธิ์:

string[] files = Directory.GetFiles(@"C:\");
Array.Sort(files, CompareNatural);

นี่คือคลาส Wrapper สำหรับใช้เป็นIComparer<string>:

public class CustomComparer<T> : IComparer<T> {
    private Comparison<T> _comparison;

    public CustomComparer(Comparison<T> comparison) {
        _comparison = comparison;
    }

    public int Compare(T x, T y) {
        return _comparison(x, y);
    }
}

ตัวอย่าง:

string[] files = Directory.EnumerateFiles(@"C:\")
    .OrderBy(f => f, new CustomComparer<string>(CompareNatural))
    .ToArray();

นี่คือชุดชื่อไฟล์ที่ดีที่ฉันใช้ในการทดสอบ:

Func<string, string> expand = (s) => { int o; while ((o = s.IndexOf('\\')) != -1) { int p = o + 1;
    int z = 1; while (s[p] == '0') { z++; p++; } int c = Int32.Parse(s.Substring(p, z));
    s = s.Substring(0, o) + new string(s[o - 1], c) + s.Substring(p + z); } return s; };
string encodedFileNames =
    "KDEqLW4xMiotbjEzKjAwMDFcMDY2KjAwMlwwMTcqMDA5XDAxNyowMlwwMTcqMDlcMDE3KjEhKjEtISox" +
    "LWEqMS4yNT8xLjI1KjEuNT8xLjUqMSoxXDAxNyoxXDAxOCoxXDAxOSoxXDA2NioxXDA2NyoxYSoyXDAx" +
    "NyoyXDAxOCo5XDAxNyo5XDAxOCo5XDA2Nio9MSphMDAxdGVzdDAxKmEwMDF0ZXN0aW5nYTBcMzEqYTAw" +
    "Mj9hMDAyIGE/YTAwMiBhKmEwMDIqYTAwMmE/YTAwMmEqYTAxdGVzdGluZ2EwMDEqYTAxdnNmcyphMSph" +
    "MWEqYTF6KmEyKmIwMDAzcTYqYjAwM3E0KmIwM3E1KmMtZSpjZCpjZipmIDEqZipnP2cgMT9oLW4qaG8t" +
    "bipJKmljZS1jcmVhbT9pY2VjcmVhbT9pY2VjcmVhbS0/ajBcNDE/ajAwMWE/ajAxP2shKmsnKmstKmsx" +
    "KmthKmxpc3QqbTAwMDNhMDA1YSptMDAzYTAwMDVhKm0wMDNhMDA1Km0wMDNhMDA1YSpuMTIqbjEzKm8t" +
    "bjAxMypvLW4xMipvLW40P28tbjQhP28tbjR6P28tbjlhLWI1Km8tbjlhYjUqb24wMTMqb24xMipvbjQ/" +
    "b240IT9vbjR6P29uOWEtYjUqb245YWI1Km/CrW4wMTMqb8KtbjEyKnAwMCpwMDEqcDAxwr0hKnAwMcK9" +
    "KnAwMcK9YSpwMDHCvcK+KnAwMipwMMK9KnEtbjAxMypxLW4xMipxbjAxMypxbjEyKnItMDAhKnItMDAh" +
    "NSpyLTAwIe+8lSpyLTAwYSpyLe+8kFwxIS01KnIt77yQXDEhLe+8lSpyLe+8kFwxISpyLe+8kFwxITUq" +
    "ci3vvJBcMSHvvJUqci3vvJBcMWEqci3vvJBcMyE1KnIwMCEqcjAwLTUqcjAwLjUqcjAwNSpyMDBhKnIw" +
    "NSpyMDYqcjQqcjUqctmg2aYqctmkKnLZpSpy27Dbtipy27Qqctu1KnLfgN+GKnLfhCpy34UqcuClpuCl" +
    "rCpy4KWqKnLgpasqcuCnpuCnrCpy4KeqKnLgp6sqcuCppuCprCpy4KmqKnLgqasqcuCrpuCrrCpy4Kuq" +
    "KnLgq6sqcuCtpuCtrCpy4K2qKnLgrasqcuCvpuCvrCpy4K+qKnLgr6sqcuCxpuCxrCpy4LGqKnLgsasq" +
    "cuCzpuCzrCpy4LOqKnLgs6sqcuC1puC1rCpy4LWqKnLgtasqcuC5kOC5lipy4LmUKnLguZUqcuC7kOC7" +
    "lipy4LuUKnLgu5UqcuC8oOC8pipy4LykKnLgvKUqcuGBgOGBhipy4YGEKnLhgYUqcuGCkOGClipy4YKU" +
    "KnLhgpUqcuGfoOGfpipy4Z+kKnLhn6UqcuGgkOGglipy4aCUKnLhoJUqcuGlhuGljCpy4aWKKnLhpYsq" +
    "cuGnkOGnlipy4aeUKnLhp5UqcuGtkOGtlipy4a2UKnLhrZUqcuGusOGutipy4a60KnLhrrUqcuGxgOGx" +
    "hipy4bGEKnLhsYUqcuGxkOGxlipy4bGUKnLhsZUqcuqYoFwx6pilKnLqmKDqmKUqcuqYoOqYpipy6pik" +
    "KnLqmKUqcuqjkOqjlipy6qOUKnLqo5UqcuqkgOqkhipy6qSEKnLqpIUqcuqpkOqplipy6qmUKnLqqZUq" +
    "cvCQkqAqcvCQkqUqcvCdn5gqcvCdn50qcu+8kFwxISpy77yQXDEt77yVKnLvvJBcMS7vvJUqcu+8kFwx" +
    "YSpy77yQXDHqmKUqcu+8kFwx77yO77yVKnLvvJBcMe+8lSpy77yQ77yVKnLvvJDvvJYqcu+8lCpy77yV" +
    "KnNpKnPEsSp0ZXN02aIqdGVzdNmi2aAqdGVzdNmjKnVBZS0qdWFlKnViZS0qdUJlKnVjZS0xw6kqdWNl" +
    "McOpLSp1Y2Uxw6kqdWPDqS0xZSp1Y8OpMWUtKnVjw6kxZSp3ZWlhMSp3ZWlhMip3ZWlzczEqd2Vpc3My" +
    "KndlaXoxKndlaXoyKndlacOfMSp3ZWnDnzIqeSBhMyp5IGE0KnknYTMqeSdhNCp5K2EzKnkrYTQqeS1h" +
    "Myp5LWE0KnlhMyp5YTQqej96IDA1MD96IDIxP3ohMjE/ejIwP3oyMj96YTIxP3rCqTIxP1sxKl8xKsKt" +
    "bjEyKsKtbjEzKsSwKg==";
string[] fileNames = Encoding.UTF8.GetString(Convert.FromBase64String(encodedFileNames))
    .Replace("*", ".txt?").Split(new[] { "?" }, StringSplitOptions.RemoveEmptyEntries)
    .Select(n => expand(n)).ToArray();

ส่วนหลักจะต้องมีการเปรียบเทียบส่วนที่ชาญฉลาดกล่าวคือ 'abc12b' ควรน้อยกว่า 'abc123'
SOUser

คุณสามารถลองใช้ข้อมูลต่อไปนี้: สตริงสาธารณะ [] filenames = {"-abc12.txt", " abc12.txt", "1abc_2.txt", "a0000012.txt", "a0000012c.txt", "a000012.txt" , "a000012b.txt", "a012.txt", "a0000102.txt", "abc1_2.txt", "abc12 .txt", "abc12b.txt", "abc123.txt", "abccde.txt", " b0000.txt "," b00001.txt "," b0001.txt "," b001.txt "," c0000.txt "," c0000c.txt "," c00001.txt "," c000b.txt "," d0. 20.2b.txt "," d0.1000c.txt "," d0.2000y.txt "," d0.20000.2b.txt ","
SOUser

@ XichenLi ขอบคุณสำหรับกรณีทดสอบที่ดี หากคุณปล่อยให้ Windows Explorer จัดเรียงไฟล์เหล่านั้นคุณจะได้ผลลัพธ์ที่แตกต่างกันขึ้นอยู่กับเวอร์ชันของ Windows ที่คุณใช้ รหัสของฉันจะเรียงลำดับชื่อเหล่านั้นเหมือนกับ Server 2003 (และน่าจะเป็น XP) แต่แตกต่างจาก Windows 8 ถ้าฉันมีโอกาสฉันจะพยายามหาว่า Windows 8 ทำงานอย่างไรและอัปเดตรหัสของฉัน
JD

3
มีบั๊ก ดัชนีอยู่นอกช่วง
linquize

3
ทางออกที่ดี! เมื่อฉันเปรียบเทียบในสถานการณ์ปกติที่มีไฟล์ประมาณ 10,000 ไฟล์มันเร็วกว่าตัวอย่าง regex ของ Matthew และประสิทธิภาพเดียวกันกับ StrCmpLogicalW () มีข้อบกพร่องเล็กน้อยในโค้ดด้านบน: the "while (strA [jA] == zeroA) jA ++;" และ "while (strB [jB] == zeroB) jB ++;" ควรเป็น "while (jA <strA.Length && strA [jA] == zeroA) jA ++;" และ "while (jB <strB.Length && strB [jB] == zeroB) jB ++;" มิฉะนั้นสตริงที่มีเพียงศูนย์จะทำให้เกิดข้อยกเว้น
kuroki

22

โซลูชัน Pure C # สำหรับ linq orderby:

http://zootfroot.blogspot.com/2009/09/natural-sort-compare-with-linq-orderby.html

public class NaturalSortComparer<T> : IComparer<string>, IDisposable
{
    private bool isAscending;

    public NaturalSortComparer(bool inAscendingOrder = true)
    {
        this.isAscending = inAscendingOrder;
    }

    #region IComparer<string> Members

    public int Compare(string x, string y)
    {
        throw new NotImplementedException();
    }

    #endregion

    #region IComparer<string> Members

    int IComparer<string>.Compare(string x, string y)
    {
        if (x == y)
            return 0;

        string[] x1, y1;

        if (!table.TryGetValue(x, out x1))
        {
            x1 = Regex.Split(x.Replace(" ", ""), "([0-9]+)");
            table.Add(x, x1);
        }

        if (!table.TryGetValue(y, out y1))
        {
            y1 = Regex.Split(y.Replace(" ", ""), "([0-9]+)");
            table.Add(y, y1);
        }

        int returnVal;

        for (int i = 0; i < x1.Length && i < y1.Length; i++)
        {
            if (x1[i] != y1[i])
            {
                returnVal = PartCompare(x1[i], y1[i]);
                return isAscending ? returnVal : -returnVal;
            }
        }

        if (y1.Length > x1.Length)
        {
            returnVal = 1;
        }
        else if (x1.Length > y1.Length)
        { 
            returnVal = -1; 
        }
        else
        {
            returnVal = 0;
        }

        return isAscending ? returnVal : -returnVal;
    }

    private static int PartCompare(string left, string right)
    {
        int x, y;
        if (!int.TryParse(left, out x))
            return left.CompareTo(right);

        if (!int.TryParse(right, out y))
            return left.CompareTo(right);

        return x.CompareTo(y);
    }

    #endregion

    private Dictionary<string, string[]> table = new Dictionary<string, string[]>();

    public void Dispose()
    {
        table.Clear();
        table = null;
    }
}

2
ในที่สุดโค้ดนั้นมาจากcodeproject.com/KB/recipes/NaturalComparer.aspx (ซึ่งไม่ใช่ LINQ-oriented)
mhenry1384

2
บล็อกโพสต์ให้เครดิต Justin Jones ( codeproject.com/KB/string/NaturalSortComparer.aspx ) สำหรับ IComparer ไม่ใช่ Pascal Ganaye
James McCormack

1
หมายเหตุเล็กน้อยโซลูชันนี้จะละเว้นช่องว่างซึ่งไม่เหมือนกับที่หน้าต่างทำและไม่ดีเท่าโค้ดของ Matthew Horsley ด้านล่าง ดังนั้นคุณอาจได้รับ 'string01' 'string 01' 'string 02' 'string02' เช่น (ซึ่งดูน่าเกลียด) หากคุณลบการขีดฆ่าช่องว่างมันจะสั่งให้สตริงถอยหลังเช่น "string01" มาก่อน "สตริง 01" ซึ่งอาจเป็นที่ยอมรับหรือไม่ก็ได้
Michael Parker

วิธีนี้ใช้ได้กับที่อยู่เช่น "1 Smith Rd", "10 Smith Rd", "2 Smith Rd" ฯลฯ - เรียงตามธรรมชาติ ใช่ ทำได้ดีนี่!
Piotr Kula

อย่างไรก็ตามฉันสังเกตเห็น (และความคิดเห็นในหน้าที่เชื่อมโยงนั้นดูเหมือนจะระบุด้วย) ว่าอาร์กิวเมนต์ Type <T> นั้นไม่จำเป็นอย่างสมบูรณ์
jv-dev

18

คำตอบของ Matthews Horsleys เป็นวิธีที่เร็วที่สุดซึ่งจะไม่เปลี่ยนพฤติกรรมขึ้นอยู่กับว่าโปรแกรมของคุณกำลังทำงานบน windows รุ่นใด อย่างไรก็ตามสามารถทำได้เร็วยิ่งขึ้นโดยการสร้าง regex เพียงครั้งเดียวและใช้ RegexOptions.Compiled ฉันยังเพิ่มตัวเลือกในการแทรกตัวเปรียบเทียบสตริงเพื่อให้คุณสามารถละเว้นตัวพิมพ์เล็กและใหญ่ได้หากจำเป็นและปรับปรุงการอ่านให้ดีขึ้น

    public static IEnumerable<T> OrderByNatural<T>(this IEnumerable<T> items, Func<T, string> selector, StringComparer stringComparer = null)
    {
        var regex = new Regex(@"\d+", RegexOptions.Compiled);

        int maxDigits = items
                      .SelectMany(i => regex.Matches(selector(i)).Cast<Match>().Select(digitChunk => (int?)digitChunk.Value.Length))
                      .Max() ?? 0;

        return items.OrderBy(i => regex.Replace(selector(i), match => match.Value.PadLeft(maxDigits, '0')), stringComparer ?? StringComparer.CurrentCulture);
    }

ใช้โดย

var sortedEmployees = employees.OrderByNatural(emp => emp.Name);

สิ่งนี้ใช้เวลา 450ms ในการเรียง 100,000 สตริงเทียบกับ 300ms สำหรับการเปรียบเทียบสตริง. net เริ่มต้น - ค่อนข้างเร็ว!


2
สิ่งนี้ควรค่าแก่การอ่าน WRT ข้างต้น - การรวบรวมและใช้ซ้ำในนิพจน์ทั่วไป
mungflesh

16

วิธีแก้ปัญหาของฉัน:

void Main()
{
    new[] {"a4","a3","a2","a10","b5","b4","b400","1","C1d","c1d2"}.OrderBy(x => x, new NaturalStringComparer()).Dump();
}

public class NaturalStringComparer : IComparer<string>
{
    private static readonly Regex _re = new Regex(@"(?<=\D)(?=\d)|(?<=\d)(?=\D)", RegexOptions.Compiled);

    public int Compare(string x, string y)
    {
        x = x.ToLower();
        y = y.ToLower();
        if(string.Compare(x, 0, y, 0, Math.Min(x.Length, y.Length)) == 0)
        {
            if(x.Length == y.Length) return 0;
            return x.Length < y.Length ? -1 : 1;
        }
        var a = _re.Split(x);
        var b = _re.Split(y);
        int i = 0;
        while(true)
        {
            int r = PartCompare(a[i], b[i]);
            if(r != 0) return r;
            ++i;
        }
    }

    private static int PartCompare(string x, string y)
    {
        int a, b;
        if(int.TryParse(x, out a) && int.TryParse(y, out b))
            return a.CompareTo(b);
        return x.CompareTo(y);
    }
}

ผล:

1
a2
a3
a4
a10
b4
b5
b400
C1d
c1d2

ฉันชอบมัน. เข้าใจง่ายและไม่ต้องใช้ Linq

11

คุณต้องระวัง - ฉันจำได้คร่าวๆว่าการอ่าน StrCmpLogicalW หรืออะไรทำนองนั้นไม่ใช่สกรรมกริยาอย่างเคร่งครัดและฉันสังเกตเห็นวิธีการเรียงลำดับของ. NET บางครั้งติดอยู่ในลูปที่ไม่มีที่สิ้นสุดหากฟังก์ชันการเปรียบเทียบทำลายกฎนั้น

การเปรียบเทียบสกรรมกริยาจะรายงานเสมอว่า a <c if a <b and b <c. มีฟังก์ชันที่ทำการเปรียบเทียบลำดับการเรียงตามธรรมชาติที่ไม่ตรงตามเกณฑ์นั้นเสมอไป แต่ฉันจำไม่ได้ว่าเป็น StrCmpLogicalW หรืออย่างอื่น


คุณมีหลักฐานยืนยันคำพูดนี้หรือไม่? หลังจาก Googling ไปรอบ ๆ ฉันไม่พบสิ่งที่บ่งชี้ว่าเป็นเรื่องจริง
mhenry1384

1
ฉันเคยสัมผัสกับลูปที่ไม่มีที่สิ้นสุดกับ StrCmpLogicalW
THD


รายการคำติชม Visual Studio 236900 ไม่มีอยู่อีกต่อไป แต่นี่เป็นรายการที่ทันสมัยกว่าที่ยืนยันปัญหา: connect.microsoft.com/VisualStudio/feedback/details/774540/… นอกจากนี้ยังให้วิธีแก้ปัญหา : CultureInfoมีคุณสมบัติCompareInfoและวัตถุที่ส่งกลับมาสามารถจัดหาSortKeyวัตถุให้คุณได้ ในทางกลับกันสิ่งเหล่านี้สามารถเปรียบเทียบและรับประกันการขนส่งได้
Jonathan Gilbert

9

นี่คือรหัสของฉันในการจัดเรียงสตริงที่มีทั้งอักขระอัลฟาและตัวเลข

ขั้นแรกวิธีการขยายนี้:

public static IEnumerable<string> AlphanumericSort(this IEnumerable<string> me)
{
    return me.OrderBy(x => Regex.Replace(x, @"\d+", m => m.Value.PadLeft(50, '0')));
}

จากนั้นใช้ที่ใดก็ได้ในโค้ดของคุณดังนี้:

List<string> test = new List<string>() { "The 1st", "The 12th", "The 2nd" };
test = test.AlphanumericSort();

มันทำงานอย่างไร? โดยแทนที่ด้วยศูนย์:

  Original  | Regex Replace |      The      |   Returned
    List    | Apply PadLeft |    Sorting    |     List
            |               |               |
 "The 1st"  |  "The 001st"  |  "The 001st"  |  "The 1st"
 "The 12th" |  "The 012th"  |  "The 002nd"  |  "The 2nd"
 "The 2nd"  |  "The 002nd"  |  "The 012th"  |  "The 12th"

ทำงานร่วมกับตัวเลขทวีคูณ:

 Alphabetical Sorting | Alphanumeric Sorting
                      |
 "Page 21, Line 42"   | "Page 3, Line 7"
 "Page 21, Line 5"    | "Page 3, Line 32"
 "Page 3, Line 32"    | "Page 21, Line 5"
 "Page 3, Line 7"     | "Page 21, Line 42"

หวังว่าจะช่วยได้


6

การเพิ่มคำตอบที่เกร็กบีชของ (เพราะฉันได้รับเพียงแค่การค้นหานั้น) ถ้าคุณต้องการที่จะใช้นี้จาก Linq คุณสามารถใช้ว่าจะใช้เวลาOrderBy IComparerเช่น:

var items = new List<MyItem>();

// fill items

var sorted = items.OrderBy(item => item.Name, new NaturalStringComparer());

2

นี่คือตัวอย่างที่ค่อนข้างง่ายที่ไม่ใช้ P / Invoke และหลีกเลี่ยงการจัดสรรใด ๆ ในระหว่างการดำเนินการ

internal sealed class NumericStringComparer : IComparer<string>
{
    public static NumericStringComparer Instance { get; } = new NumericStringComparer();

    public int Compare(string x, string y)
    {
        // sort nulls to the start
        if (x == null)
            return y == null ? 0 : -1;
        if (y == null)
            return 1;

        var ix = 0;
        var iy = 0;

        while (true)
        {
            // sort shorter strings to the start
            if (ix >= x.Length)
                return iy >= y.Length ? 0 : -1;
            if (iy >= y.Length)
                return 1;

            var cx = x[ix];
            var cy = y[iy];

            int result;
            if (char.IsDigit(cx) && char.IsDigit(cy))
                result = CompareInteger(x, y, ref ix, ref iy);
            else
                result = cx.CompareTo(y[iy]);

            if (result != 0)
                return result;

            ix++;
            iy++;
        }
    }

    private static int CompareInteger(string x, string y, ref int ix, ref int iy)
    {
        var lx = GetNumLength(x, ix);
        var ly = GetNumLength(y, iy);

        // shorter number first (note, doesn't handle leading zeroes)
        if (lx != ly)
            return lx.CompareTo(ly);

        for (var i = 0; i < lx; i++)
        {
            var result = x[ix++].CompareTo(y[iy++]);
            if (result != 0)
                return result;
        }

        return 0;
    }

    private static int GetNumLength(string s, int i)
    {
        var length = 0;
        while (i < s.Length && char.IsDigit(s[i++]))
            length++;
        return length;
    }
}

มันไม่ได้ละเลย zeroes ชั้นนำเพื่อมาหลังจาก012

การทดสอบหน่วยที่เกี่ยวข้อง:

public class NumericStringComparerTests
{
    [Fact]
    public void OrdersCorrectly()
    {
        AssertEqual("", "");
        AssertEqual(null, null);
        AssertEqual("Hello", "Hello");
        AssertEqual("Hello123", "Hello123");
        AssertEqual("123", "123");
        AssertEqual("123Hello", "123Hello");

        AssertOrdered("", "Hello");
        AssertOrdered(null, "Hello");
        AssertOrdered("Hello", "Hello1");
        AssertOrdered("Hello123", "Hello124");
        AssertOrdered("Hello123", "Hello133");
        AssertOrdered("Hello123", "Hello223");
        AssertOrdered("123", "124");
        AssertOrdered("123", "133");
        AssertOrdered("123", "223");
        AssertOrdered("123", "1234");
        AssertOrdered("123", "2345");
        AssertOrdered("0", "1");
        AssertOrdered("123Hello", "124Hello");
        AssertOrdered("123Hello", "133Hello");
        AssertOrdered("123Hello", "223Hello");
        AssertOrdered("123Hello", "1234Hello");
    }

    private static void AssertEqual(string x, string y)
    {
        Assert.Equal(0, NumericStringComparer.Instance.Compare(x, y));
        Assert.Equal(0, NumericStringComparer.Instance.Compare(y, x));
    }

    private static void AssertOrdered(string x, string y)
    {
        Assert.Equal(-1, NumericStringComparer.Instance.Compare(x, y));
        Assert.Equal( 1, NumericStringComparer.Instance.Compare(y, x));
    }
}

2

ฉันได้ใช้มันเป็นวิธีการขยายStringComparerเพื่อให้คุณสามารถทำได้เช่น:

  • StringComparer.CurrentCulture.WithNaturalSort() หรือ
  • StringComparer.OrdinalIgnoreCase.WithNaturalSort().

ส่งผลให้IComparer<string>สามารถนำมาใช้ในสถานที่ที่ทุกคนชอบOrderBy, OrderByDescending, ThenBy, ThenByDescending, SortedSet<string>ฯลฯ และคุณสามารถไวกรณีที่ยังคงปรับแต่งได้อย่างง่ายดายวัฒนธรรม ฯลฯ

การนำไปใช้นั้นค่อนข้างไม่สำคัญและควรทำงานได้ค่อนข้างดีแม้ในลำดับใหญ่ ๆ


ฉันยังเผยแพร่เป็นแพ็คเกจ NuGetขนาดเล็กดังนั้นคุณสามารถทำได้:

Install-Package NaturalSort.Extension

รหัสรวมถึงความคิดเห็นเอกสาร XML และชุดของการทดสอบที่มีอยู่ในNaturalSort.Extensionพื้นที่เก็บข้อมูล GitHub


รหัสทั้งหมดคือสิ่งนี้ (หากคุณยังไม่สามารถใช้ C # 7 ได้เพียงแค่ติดตั้งแพ็คเกจ NuGet):

public static class StringComparerNaturalSortExtension
{
    public static IComparer<string> WithNaturalSort(this StringComparer stringComparer) => new NaturalSortComparer(stringComparer);

    private class NaturalSortComparer : IComparer<string>
    {
        public NaturalSortComparer(StringComparer stringComparer)
        {
            _stringComparer = stringComparer;
        }

        private readonly StringComparer _stringComparer;
        private static readonly Regex NumberSequenceRegex = new Regex(@"(\d+)", RegexOptions.Compiled | RegexOptions.CultureInvariant);
        private static string[] Tokenize(string s) => s == null ? new string[] { } : NumberSequenceRegex.Split(s);
        private static ulong ParseNumberOrZero(string s) => ulong.TryParse(s, NumberStyles.None, CultureInfo.InvariantCulture, out var result) ? result : 0;

        public int Compare(string s1, string s2)
        {
            var tokens1 = Tokenize(s1);
            var tokens2 = Tokenize(s2);

            var zipCompare = tokens1.Zip(tokens2, TokenCompare).FirstOrDefault(x => x != 0);
            if (zipCompare != 0)
                return zipCompare;

            var lengthCompare = tokens1.Length.CompareTo(tokens2.Length);
            return lengthCompare;
        }
        
        private int TokenCompare(string token1, string token2)
        {
            var number1 = ParseNumberOrZero(token1);
            var number2 = ParseNumberOrZero(token2);

            var numberCompare = number1.CompareTo(number2);
            if (numberCompare != 0)
                return numberCompare;

            var stringCompare = _stringComparer.Compare(token1, token2);
            return stringCompare;
        }
    }
}

2

นี่คือวิธี LINQ แบบ regex-less แบบไร้เดียงสาบรรทัดเดียว (ยืมมาจาก python):

var alphaStrings = new List<string>() { "10","2","3","4","50","11","100","a12","b12" };
var orderedString = alphaStrings.OrderBy(g => new Tuple<int, string>(g.ToCharArray().All(char.IsDigit)? int.Parse(g) : int.MaxValue, g));
// Order Now: ["2","3","4","10","11","50","100","a12","b12"]

ลบ Dump () และกำหนดให้กับ var และสิ่งนี้ได้ผลอย่างมีเสน่ห์!
Arne S

@ArneS: มันถูกเขียนใน LinQPad; และฉันลืมลบไฟล์Dump(). ขอบคุณที่ชี้ให้เห็น
mshsayem

1

เมื่อขยายคำตอบก่อนหน้านี้สองสามคำและการใช้วิธีการขยายฉันได้มาพร้อมกับคำตอบต่อไปนี้ที่ไม่มีข้อแม้ของการแจงนับที่อาจเกิดขึ้นได้หลายค่าหรือปัญหาด้านประสิทธิภาพที่เกี่ยวข้องกับการใช้วัตถุ regex หลายรายการหรือการเรียก regex โดยไม่จำเป็นว่า กล่าวคือใช้ ToList () ซึ่งสามารถลบล้างผลประโยชน์ในคอลเลกชันขนาดใหญ่ได้

ตัวเลือกรองรับการพิมพ์ทั่วไปเพื่ออนุญาตให้มอบหมายตัวแทนใด ๆ องค์ประกอบในคอลเลกชันที่มาจะถูกกลายพันธุ์โดยตัวเลือกจากนั้นแปลงเป็นสตริงด้วย ToString ()

    private static readonly Regex _NaturalOrderExpr = new Regex(@"\d+", RegexOptions.Compiled);

    public static IEnumerable<TSource> OrderByNatural<TSource, TKey>(
        this IEnumerable<TSource> source, Func<TSource, TKey> selector)
    {
        int max = 0;

        var selection = source.Select(
            o =>
            {
                var v = selector(o);
                var s = v != null ? v.ToString() : String.Empty;

                if (!String.IsNullOrWhiteSpace(s))
                {
                    var mc = _NaturalOrderExpr.Matches(s);

                    if (mc.Count > 0)
                    {
                        max = Math.Max(max, mc.Cast<Match>().Max(m => m.Value.Length));
                    }
                }

                return new
                {
                    Key = o,
                    Value = s
                };
            }).ToList();

        return
            selection.OrderBy(
                o =>
                String.IsNullOrWhiteSpace(o.Value) ? o.Value : _NaturalOrderExpr.Replace(o.Value, m => m.Value.PadLeft(max, '0')))
                     .Select(o => o.Key);
    }

    public static IEnumerable<TSource> OrderByDescendingNatural<TSource, TKey>(
        this IEnumerable<TSource> source, Func<TSource, TKey> selector)
    {
        int max = 0;

        var selection = source.Select(
            o =>
            {
                var v = selector(o);
                var s = v != null ? v.ToString() : String.Empty;

                if (!String.IsNullOrWhiteSpace(s))
                {
                    var mc = _NaturalOrderExpr.Matches(s);

                    if (mc.Count > 0)
                    {
                        max = Math.Max(max, mc.Cast<Match>().Max(m => m.Value.Length));
                    }
                }

                return new
                {
                    Key = o,
                    Value = s
                };
            }).ToList();

        return
            selection.OrderByDescending(
                o =>
                String.IsNullOrWhiteSpace(o.Value) ? o.Value : _NaturalOrderExpr.Replace(o.Value, m => m.Value.PadLeft(max, '0')))
                     .Select(o => o.Key);
    }

1

ด้วยแรงบันดาลใจจากโซลูชันของ Michael Parker นี่คือการIComparerใช้งานที่คุณสามารถเลือกวิธีการสั่งซื้อ linq:

private class NaturalStringComparer : IComparer<string>
{
    public int Compare(string left, string right)
    {
        int max = new[] { left, right }
            .SelectMany(x => Regex.Matches(x, @"\d+").Cast<Match>().Select(y => (int?)y.Value.Length))
            .Max() ?? 0;

        var leftPadded = Regex.Replace(left, @"\d+", m => m.Value.PadLeft(max, '0'));
        var rightPadded = Regex.Replace(right, @"\d+", m => m.Value.PadLeft(max, '0'));

        return string.Compare(leftPadded, rightPadded);
    }
}

0

เราต้องการการจัดเรียงที่เป็นธรรมชาติเพื่อจัดการกับข้อความด้วยรูปแบบต่อไปนี้:

"Test 1-1-1 something"
"Test 1-2-3 something"
...

ด้วยเหตุผลบางประการเมื่อฉันดู SO ครั้งแรกฉันไม่พบโพสต์นี้และนำไปใช้งานของเราเอง เมื่อเทียบกับโซลูชันบางอย่างที่นำเสนอที่นี่ในขณะที่มีแนวคิดคล้ายกัน แต่ก็อาจมีประโยชน์ที่อาจจะง่ายกว่าและเข้าใจง่ายกว่า อย่างไรก็ตามในขณะที่ฉันพยายามดูปัญหาคอขวดด้านประสิทธิภาพ แต่ก็ยังใช้งานได้ช้ากว่าค่าเริ่มต้นOrderBy()มาก

นี่คือวิธีการขยายที่ฉันใช้:

public static class EnumerableExtensions
{
    // set up the regex parser once and for all
    private static readonly Regex Regex = new Regex(@"\d+|\D+", RegexOptions.Compiled | RegexOptions.Singleline);

    // stateless comparer can be built once
    private static readonly AggregateComparer Comparer = new AggregateComparer();

    public static IEnumerable<T> OrderByNatural<T>(this IEnumerable<T> source, Func<T, string> selector)
    {
        // first extract string from object using selector
        // then extract digit and non-digit groups
        Func<T, IEnumerable<IComparable>> splitter =
            s => Regex.Matches(selector(s))
                      .Cast<Match>()
                      .Select(m => Char.IsDigit(m.Value[0]) ? (IComparable) int.Parse(m.Value) : m.Value);
        return source.OrderBy(splitter, Comparer);
    }

    /// <summary>
    /// This comparer will compare two lists of objects against each other
    /// </summary>
    /// <remarks>Objects in each list are compare to their corresponding elements in the other
    /// list until a difference is found.</remarks>
    private class AggregateComparer : IComparer<IEnumerable<IComparable>>
    {
        public int Compare(IEnumerable<IComparable> x, IEnumerable<IComparable> y)
        {
            return
                x.Zip(y, (a, b) => new {a, b})              // walk both lists
                 .Select(pair => pair.a.CompareTo(pair.b))  // compare each object
                 .FirstOrDefault(result => result != 0);    // until a difference is found
        }
    }
}

แนวคิดคือการแยกสตริงเดิมออกเป็นบล็อกของตัวเลขและไม่ใช่ตัวเลข ( "\d+|\D+") เนื่องจากอาจเป็นงานที่มีราคาแพงจึงทำเพียงครั้งเดียวต่อรายการ จากนั้นเราใช้ตัวเปรียบเทียบของวัตถุที่เทียบเคียงได้ (ขออภัยฉันไม่พบวิธีที่เหมาะสมกว่านี้ในการพูด) จะเปรียบเทียบแต่ละบล็อกกับบล็อกที่เกี่ยวข้องในสตริงอื่น

ฉันต้องการความคิดเห็นเกี่ยวกับวิธีการปรับปรุงและข้อบกพร่องที่สำคัญคืออะไร โปรดทราบว่าความสามารถในการบำรุงรักษาเป็นสิ่งสำคัญสำหรับเราในตอนนี้และเราไม่ได้ใช้สิ่งนี้ในชุดข้อมูลขนาดใหญ่มาก


1
สิ่งนี้จะขัดข้องเมื่อพยายามเปรียบเทียบสตริงที่มีโครงสร้างแตกต่างกันเช่นการเปรียบเทียบ "a-1" กับ "a-2" ทำงานได้ดี แต่การเปรียบเทียบ "a" กับ "1" นั้นไม่ใช่เพราะ "a" .CompareTo (1) โยนข้อยกเว้น
jimrandomh

@jimrandomh คุณถูกต้อง แนวทางนี้เฉพาะสำหรับรูปแบบของเรา
Eric Liprandi

0

เวอร์ชันที่อ่าน / ดูแลรักษาง่ายกว่า

public class NaturalStringComparer : IComparer<string>
{
    public static NaturalStringComparer Instance { get; } = new NaturalStringComparer();

    public int Compare(string x, string y) {
        const int LeftIsSmaller = -1;
        const int RightIsSmaller = 1;
        const int Equal = 0;

        var leftString = x;
        var rightString = y;

        var stringComparer = CultureInfo.CurrentCulture.CompareInfo;

        int rightIndex;
        int leftIndex;

        for (leftIndex = 0, rightIndex = 0;
             leftIndex < leftString.Length && rightIndex < rightString.Length;
             leftIndex++, rightIndex++) {
            var leftChar = leftString[leftIndex];
            var rightChar = rightString[leftIndex];

            var leftIsNumber = char.IsNumber(leftChar);
            var rightIsNumber = char.IsNumber(rightChar);

            if (!leftIsNumber && !rightIsNumber) {
                var result = stringComparer.Compare(leftString, leftIndex, 1, rightString, leftIndex, 1);
                if (result != 0) return result;
            } else if (leftIsNumber && !rightIsNumber) {
                return LeftIsSmaller;
            } else if (!leftIsNumber && rightIsNumber) {
                return RightIsSmaller;
            } else {
                var leftNumberLength = NumberLength(leftString, leftIndex, out var leftNumber);
                var rightNumberLength = NumberLength(rightString, rightIndex, out var rightNumber);

                if (leftNumberLength < rightNumberLength) {
                    return LeftIsSmaller;
                } else if (leftNumberLength > rightNumberLength) {
                    return RightIsSmaller;
                } else {
                    if(leftNumber < rightNumber) {
                        return LeftIsSmaller;
                    } else if(leftNumber > rightNumber) {
                        return RightIsSmaller;
                    }
                }
            }
        }

        if (leftString.Length < rightString.Length) {
            return LeftIsSmaller;
        } else if(leftString.Length > rightString.Length) {
            return RightIsSmaller;
        }

        return Equal;
    }

    public int NumberLength(string str, int offset, out int number) {
        if (string.IsNullOrWhiteSpace(str)) throw new ArgumentNullException(nameof(str));
        if (offset >= str.Length) throw new ArgumentOutOfRangeException(nameof(offset), offset, "Offset must be less than the length of the string.");

        var currentOffset = offset;

        var curChar = str[currentOffset];

        if (!char.IsNumber(curChar))
            throw new ArgumentException($"'{curChar}' is not a number.", nameof(offset));

        int length = 1;

        var numberString = string.Empty;

        for (currentOffset = offset + 1;
            currentOffset < str.Length;
            currentOffset++, length++) {

            curChar = str[currentOffset];
            numberString += curChar;

            if (!char.IsNumber(curChar)) {
                number = int.Parse(numberString);

                return length;
            }
        }

        number = int.Parse(numberString);

        return length;
    }
}

-2

ให้ฉันอธิบายปัญหาของฉันและฉันจะแก้ไขได้อย่างไร

ปัญหา: - เรียงไฟล์ตาม FileName จากอ็อบเจ็กต์ FileInfo ซึ่งดึงมาจากไดเร็กทอรี

วิธีแก้ไข: - ฉันเลือกชื่อไฟล์จาก FileInfo และตัดส่วน ".png" ของชื่อไฟล์ ตอนนี้เพียงแค่ทำ List.Sort () ซึ่งจะจัดเรียงชื่อไฟล์ตามลำดับการเรียงลำดับตามธรรมชาติ จากการทดสอบของฉันฉันพบว่าการมี. png ทำให้ลำดับการจัดเรียงยุ่งเหยิง ดูรหัสด้านล่าง

var imageNameList = new DirectoryInfo(@"C:\Temp\Images").GetFiles("*.png").Select(x =>x.Name.Substring(0, x.Name.Length - 4)).ToList();
imageNameList.Sort();

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