แยกสตริงออกเป็นชิ้นขนาดที่กำหนด


218

สมมติว่าฉันมีสตริง:

string str = "1111222233334444"; 

ฉันจะแบ่งสตริงนี้เป็นชิ้นขนาดได้อย่างไร

เช่นการแบ่งเป็น 4 ขนาดจะคืนค่าสตริง:

"1111"
"2222"
"3333"
"4444"

18
เหตุใดจึงต้องใช้ LINQ หรือ regexes เมื่อฟังก์ชั่นการจัดการสตริงมาตรฐานของ C # สามารถทำได้โดยใช้ความพยายามและความเร็วที่น้อยลง นอกจากนี้จะเกิดอะไรขึ้นถ้าสตริงมีจำนวนอักขระยาวเป็นเลขคี่
Ian Kemp

7
"ฉันต้องการหลีกเลี่ยงการวนซ้ำ" - ทำไม
Mitch Wheat

12
การใช้การวนซ้ำแบบเรียบง่ายเป็นสิ่งที่ให้ประสิทธิภาพดีที่สุดอย่างแน่นอน
Guffa

4
nichesoftware.co.nz/blog/200909/linq-vs-loop-performanceเป็นการเปรียบเทียบที่ดีระหว่าง linq และการวนลูปจริงในอาร์เรย์ ฉันสงสัยว่าคุณจะพบ linq เร็วกว่าโค้ดที่เขียนด้วยตนเองเพราะมันจะเรียกผู้ได้รับมอบหมายเวลาทำงานที่ยากที่จะเพิ่มประสิทธิภาพออกไป Linq คือความสนุกมากขึ้นแม้ว่า :)
Blindy

2
ไม่ว่าคุณจะใช้ LINQ หรือ regexes การวนซ้ำก็ยังคงอยู่
Anton Tykhyy

คำตอบ:


247
static IEnumerable<string> Split(string str, int chunkSize)
{
    return Enumerable.Range(0, str.Length / chunkSize)
        .Select(i => str.Substring(i * chunkSize, chunkSize));
}

โปรดทราบว่าอาจต้องใช้รหัสเพิ่มเติมเพื่อจัดการกับเคสขอบ ( nullหรือสตริงอินพุตว่างchunkSize == 0, ความยาวสตริงอินพุตที่ไม่สามารถหารด้วยchunkSizeฯลฯ ) คำถามดั้งเดิมไม่ได้ระบุข้อกำหนดใด ๆ สำหรับกรณีขอบเหล่านี้และในชีวิตจริงข้อกำหนดอาจแตกต่างกันไปดังนั้นจึงไม่อยู่ในขอบเขตของคำตอบนี้


3
@ แฮร์รี่ดีจับ! สิ่งนี้สามารถแก้ไขได้ด้วยนิพจน์ประกอบไปด้วยดรอปดาวน์ในพารามิเตอร์ count ของสตริงย่อย (i * chunkSize + chunkSize <= str.Length) ? chunkSize : str.Length - i * chunkSizeสิ่งที่ชอบ: ปัญหาเพิ่มเติมคือฟังก์ชั่นนี้ไม่ได้พิจารณาว่า str เป็นโมฆะ สิ่งนี้สามารถแก้ไขได้โดยการห่อคำสั่งการส่งคืนทั้งหมดไว้ในนิพจน์ประกอบ(str != null) ? ... : Enumerable.Empty<String>();ไปด้วย:
Drew Spickes

7
นี่ใกล้ แต่ไม่เหมือนผู้ upvoters 30 คนก่อนฉันต้องเปลี่ยนการ จำกัด จำนวนรอบของลูปจากstr.Length / chunkSizeเป็นdouble length = str.Length; double size = chunkSize; int count = (int)Math.Ceiling(length/size); return Enumerable.Range(0, count)...
ช่องว่าง

4
@ KonstantinSpirin ฉันเห็นด้วยถ้ารหัสทำงาน มันจัดการเฉพาะกรณีที่สตริงเป็นหลาย chunkSize ส่วนที่เหลือของสตริงจะหายไป กรุณาใส่กระสุน โปรดจำไว้ว่า LINQ และเวทมนตร์นั้นไม่ใช่เรื่องง่ายที่จะเข้าใจใครสักคนที่เพียงต้องการดูวิธีแก้ไขปัญหานี้ ตอนนี้บุคคลต้องเข้าใจว่าฟังก์ชัน Enumerable.Range () และ. Select () ทำอะไร ฉันจะไม่โต้แย้งว่าคุณควรมีความเข้าใจในการเขียนรหัส C # /. NET เนื่องจากฟังก์ชั่นเหล่านี้อยู่ใน BCL มาหลายปีแล้ว
CodeMonkeyKing

6
StringLength % 4 will always be 0เริ่มต้นกระทู้กล่าวว่าในความคิดเห็นว่า หากLinqไม่เข้าใจง่ายมีคำตอบอื่น ๆ ที่ใช้ลูปและอัตราผลตอบแทน ทุกคนสามารถเลือกวิธีการแก้ปัญหาที่เธอชอบที่สุดได้ฟรี คุณสามารถโพสต์รหัสของคุณเป็นคำตอบและผู้คนจะลงคะแนนอย่างมีความสุข
Konstantin Spirin

3
Enumerable.Range (0 (+ str.Length chunksize - 1) / chunksize) .Select (i => str.Substring (i * chunksize, Math.Min (str.Length - i * chunksize, chunksize)))
สเตน Petrov

135

ในการรวมกันของคำตอบของนกพิราบ + Konstatin ...

static IEnumerable<string> WholeChunks(string str, int chunkSize) {
    for (int i = 0; i < str.Length; i += chunkSize) 
        yield return str.Substring(i, chunkSize);
}

สิ่งนี้จะใช้ได้กับสตริงทั้งหมดที่สามารถแบ่งออกเป็นจำนวนเต็มทั้งหมดและจะมีข้อยกเว้นเป็นอย่างอื่น

หากคุณต้องการสนับสนุนสตริงที่มีความยาวเท่าใดก็ได้คุณสามารถใช้รหัสต่อไปนี้:

static IEnumerable<string> ChunksUpto(string str, int maxChunkSize) {
    for (int i = 0; i < str.Length; i += maxChunkSize) 
        yield return str.Substring(i, Math.Min(maxChunkSize, str.Length-i));
}

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


4
+1 พยักหน้า ค่อนข้างโดนเล็บบนหัว เขากำลังมองหา sytnax ที่กระชับและคุณยังให้ประสิทธิภาพที่ดีขึ้น (น่าจะ)
นกพิราบ

7
และถ้าคุณทำให้มัน "คงที่ ... ก้อน (สตริงนี้ str, int chunkSize) {" คุณยังมี "ใหม่" C # -Feature อีกหนึ่ง จากนั้นคุณสามารถเขียน "1111222233334444" .Chunk (4)
MartinStettner

1
@ MartinStettner: นั่นเป็นความคิดที่ดีถ้านี่เป็นการดำเนินการทั่วไป
Eamon Nerbonne

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

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

56

ทำไมไม่ลูป นี่คือสิ่งที่จะทำได้ค่อนข้างดี:

        string str = "111122223333444455";
        int chunkSize = 4;
        int stringLength = str.Length;
        for (int i = 0; i < stringLength ; i += chunkSize)
        {
            if (i + chunkSize > stringLength) chunkSize = stringLength  - i;
            Console.WriteLine(str.Substring(i, chunkSize));

        }
        Console.ReadLine();

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

หรือตามที่ระบุไว้ในความคิดเห็นคุณรู้ว่ามัน / 4 แล้ว

str = "1111222233334444";
for (int i = 0; i < stringLength; i += chunkSize) 
  {Console.WriteLine(str.Substring(i, chunkSize));} 

1
คุณสามารถดึงint chunkSize = 4ออกไปนอกวง มันจะถูกแก้ไขในรอบสุดท้ายเท่านั้น
John Feminella

+1 สำหรับวิธีแก้ปัญหาที่ง่ายและมีประสิทธิภาพ - นี่คือวิธีที่ฉันจะทำได้แม้ว่าฉันจะใช้i += chunkSizeแทน
Ian Kemp

อาจเล่นลิ้นเล็กน้อย แต่คุณควรดึงstr.Lengthออกจากวงและเป็นตัวแปรท้องถิ่น เครื่องมือเพิ่มประสิทธิภาพ C # อาจจะมีความยาวของแถวเรียงแบบอินไลน์ แต่ฉันคิดว่าโค้ดตามที่เขียนจะทำการเรียกเมธอดบนทุกลูปซึ่งไม่มีประสิทธิภาพเนื่องจากขนาดstrไม่เคยเปลี่ยนแปลง
Daniel Pryden

@ แดเนียลใส่ความคิดของคุณในนั้น แต่ผมไม่แน่ใจว่าเรื่องนี้จะไม่ได้รับการคำนวณที่รันไทม์ แต่ที่คำถามอื่น;)
นกพิราบ

@Daniel กลับมาที่นี่ค่อนข้างแน่ใจว่าการเพิ่มประสิทธิภาพนี้จะถูกสกัดโดยคอมไพเลอร์
นกพิราบ

41

ใช้นิพจน์ทั่วไปและLinq :

List<string> groups = (from Match m in Regex.Matches(str, @"\d{4}")
                       select m.Value).ToList();

ฉันพบว่าสิ่งนี้สามารถอ่านได้มากขึ้น แต่เป็นเพียงความเห็นส่วนตัว นอกจากนี้ยังอาจเป็นหนึ่งซับ:)


7
เปลี่ยนรูปแบบเป็น @ "\ d {1,4}" และใช้ได้กับความยาวสตริงใด ๆ :)
Guffa

3
+1 แม้ว่าจะช้ากว่าโซลูชันอื่น ๆ แต่ก็สามารถอ่านได้อย่างแน่นอน ยังไม่ชัดเจนสำหรับฉันว่า OP ต้องการตัวเลขหรือตัวอักษรใด ๆ ก็ตาม มันอาจจะฉลาดที่จะมาแทนที่\dตัวละครคลาสด้วยและเพื่อระบุ. RegexOptions.Singleline
Eamon Nerbonne

2
หรือเพียงแค่ Regex.Matches (s, @ "\ d {1,4}") เลือก (m => m.Value) .ToList (); ฉันไม่เคยได้จุดของไวยากรณ์ทางเลือกนี้ที่ให้บริการเท่านั้นที่ทำให้งงงวยว่าเรากำลังใช้วิธีการขยาย
Dag

38

สิ่งนี้ขึ้นอยู่กับโซลูชัน @doveแต่นำมาใช้เป็นวิธีส่วนขยาย

ประโยชน์ที่ได้รับ:

  • วิธีการขยาย
  • ครอบคลุมกรณีมุม
  • แยกสตริงด้วยตัวอักษรใด ๆ : ตัวเลขตัวอักษรสัญลักษณ์อื่น ๆ

รหัส

public static class EnumerableEx
{    
    public static IEnumerable<string> SplitBy(this string str, int chunkLength)
    {
        if (String.IsNullOrEmpty(str)) throw new ArgumentException();
        if (chunkLength < 1) throw new ArgumentException();

        for (int i = 0; i < str.Length; i += chunkLength)
        {
            if (chunkLength + i > str.Length)
                chunkLength = str.Length - i;

            yield return str.Substring(i, chunkLength);
        }
    }
}

การใช้

var result = "bobjoecat".SplitBy(3); // bob, joe, cat

การทดสอบหน่วยถอดออกมาเพื่อความกะทัดรัด (ดูการแก้ไขก่อนหน้านี้ )


วิธีการแก้ปัญหาที่น่าสนใจ แต่เพื่อประโยชน์ในการหลีกเลี่ยงการตรวจสอบเกิน null กับการป้อนข้อมูลที่ดูเหมือนว่าตรรกะมากขึ้นเพื่อช่วยให้สตริงที่ว่างเปล่าที่จะเพียงแค่กลับมาเป็นส่วนหนึ่งที่ว่างเปล่าสตริงเดียว:if (str.Length == 0) yield return String.Empty; else { for... }
Nyerguds

ฉันหมายความว่านั่นเป็นวิธีปกติของ String.Split จัดการกับสตริงว่าง; มันจะส่งกลับรายการหนึ่งสตริงที่ว่างเปล่า
Nyerguds

หมายเหตุด้านข้าง: ตัวอย่างการใช้งานของคุณผิด คุณไม่สามารถส่งIEnumerableไปยังแถวลำดับโดยเฉพาะอย่างยิ่งไม่ใช่โดยปริยาย
Nyerguds

โดยส่วนตัวผมชอบเรียกวิธีการนั้นChunkify.. ไม่ใช่ของฉันฉันจำไม่ได้ว่าฉันเห็นชื่อนั้นที่ไหน แต่รู้สึกดีกับฉันมาก
quetzalcoatl

20

วิธีนี้สำหรับซับหนึ่งหรือไม่

List<string> result = new List<string>(Regex.Split(target, @"(?<=\G.{4})", RegexOptions.Singleline));

ด้วย regex นี้มันไม่สำคัญว่าอันสุดท้ายจะน้อยกว่าสี่ตัวอักษรเพราะมันจะมองที่ตัวอักษรด้านหลังเท่านั้น

ฉันแน่ใจว่านี่ไม่ใช่วิธีที่มีประสิทธิภาพที่สุด แต่ฉันต้องโยนมันทิ้งไป


ในกรณีtarget.Lenght % ChunckSize == 0ที่ส่งคืนแถวว่างเพิ่มเติมเช่นList<string> result = new List<string>(Regex.Split("fooo", @"(?<=\G.{4})", RegexOptions.Singleline));
fubo

9

มันไม่สวยและไม่เร็ว แต่ใช้งานได้มันเป็นแบบเส้นเดียวและเป็น LINQy:

List<string> a = text.Select((c, i) => new { Char = c, Index = i }).GroupBy(o => o.Index / 4).Select(g => new String(g.Select(o => o.Char).ToArray())).ToList();

มันรับประกันได้หรือไม่ว่า GroupBy รักษาลำดับขององค์ประกอบไว้?
Konstantin Spirin

ToCharArrayไม่จำเป็นตั้งแต่มีstring IEnumerable<char>
juharr

8

เมื่อเร็ว ๆ นี้ฉันต้องเขียนบางสิ่งบางอย่างที่ประสบความสำเร็จในที่ทำงานดังนั้นฉันคิดว่าฉันจะโพสต์วิธีแก้ไขปัญหานี้ ในฐานะโบนัสเพิ่มเติมฟังก์ชันการทำงานของโซลูชันนี้มีวิธีการแยกสตริงในทิศทางตรงกันข้ามและจะจัดการอักขระ unicode อย่างถูกต้องตามที่ Marvin Pinto กล่าวไว้ข้างต้น ดังนั้นนี่คือ:

using System;
using Extensions;

namespace TestCSharp
{
    class Program
    {
        static void Main(string[] args)
        {    
            string asciiStr = "This is a string.";
            string unicodeStr = "これは文字列です。";

            string[] array1 = asciiStr.Split(4);
            string[] array2 = asciiStr.Split(-4);

            string[] array3 = asciiStr.Split(7);
            string[] array4 = asciiStr.Split(-7);

            string[] array5 = unicodeStr.Split(5);
            string[] array6 = unicodeStr.Split(-5);
        }
    }
}

namespace Extensions
{
    public static class StringExtensions
    {
        /// <summary>Returns a string array that contains the substrings in this string that are seperated a given fixed length.</summary>
        /// <param name="s">This string object.</param>
        /// <param name="length">Size of each substring.
        ///     <para>CASE: length &gt; 0 , RESULT: String is split from left to right.</para>
        ///     <para>CASE: length == 0 , RESULT: String is returned as the only entry in the array.</para>
        ///     <para>CASE: length &lt; 0 , RESULT: String is split from right to left.</para>
        /// </param>
        /// <returns>String array that has been split into substrings of equal length.</returns>
        /// <example>
        ///     <code>
        ///         string s = "1234567890";
        ///         string[] a = s.Split(4); // a == { "1234", "5678", "90" }
        ///     </code>
        /// </example>            
        public static string[] Split(this string s, int length)
        {
            System.Globalization.StringInfo str = new System.Globalization.StringInfo(s);

            int lengthAbs = Math.Abs(length);

            if (str == null || str.LengthInTextElements == 0 || lengthAbs == 0 || str.LengthInTextElements <= lengthAbs)
                return new string[] { str.ToString() };

            string[] array = new string[(str.LengthInTextElements % lengthAbs == 0 ? str.LengthInTextElements / lengthAbs: (str.LengthInTextElements / lengthAbs) + 1)];

            if (length > 0)
                for (int iStr = 0, iArray = 0; iStr < str.LengthInTextElements && iArray < array.Length; iStr += lengthAbs, iArray++)
                    array[iArray] = str.SubstringByTextElements(iStr, (str.LengthInTextElements - iStr < lengthAbs ? str.LengthInTextElements - iStr : lengthAbs));
            else // if (length < 0)
                for (int iStr = str.LengthInTextElements - 1, iArray = array.Length - 1; iStr >= 0 && iArray >= 0; iStr -= lengthAbs, iArray--)
                    array[iArray] = str.SubstringByTextElements((iStr - lengthAbs < 0 ? 0 : iStr - lengthAbs + 1), (iStr - lengthAbs < 0 ? iStr + 1 : lengthAbs));

            return array;
        }
    }
}

และนี่คือลิงค์รูปภาพไปยังผลลัพธ์ของการเรียกใช้รหัสนี้: http://i.imgur.com/16Iih.png


1
ฉันสังเกตเห็นปัญหากับรหัสนี้ คุณมี{str.ToString()}คำสั่ง IF สุดท้ายของคุณ คุณแน่ใจว่าคุณไม่ได้หมายความว่าstr.String? ฉันมีปัญหากับรหัสข้างต้นทำการเปลี่ยนแปลงนั้นและทุกอย่างทำงานได้
gunr2171

@ gunr2171 ดูเหมือนว่าถ้า str == null บรรทัดนั้นจะให้ NullReferenceException
John Zabroski

5

นี่ควรจะเร็วกว่าและมีประสิทธิภาพมากกว่าการใช้ LINQ หรือวิธีการอื่น ๆ ที่ใช้ในที่นี้

public static IEnumerable<string> Splice(this string s, int spliceLength)
{
    if (s == null)
        throw new ArgumentNullException("s");
    if (spliceLength < 1)
        throw new ArgumentOutOfRangeException("spliceLength");

    if (s.Length == 0)
        yield break;
    var start = 0;
    for (var end = spliceLength; end < s.Length; end += spliceLength)
    {
        yield return s.Substring(start, spliceLength);
        start = end;
    }
    yield return s.Substring(start);
}

นี้รูปลักษณ์ที่ชอบมันไม่ตรวจสอบก่อน แต่มันไม่ได้ คุณจะไม่ได้รับข้อผิดพลาดจนกว่าคุณจะเริ่มต้นการแจกแจง คุณต้องแยกฟังก์ชันของคุณออกเป็นสองส่วนโดยที่ส่วนแรกทำการตรวจสอบอาร์กิวเมนต์จากนั้นส่งคืนผลลัพธ์ของส่วนที่สองส่วนตัวที่จะทำการแจงนับ
ErikE

4
public static IEnumerable<IEnumerable<T>> SplitEvery<T>(this IEnumerable<T> values, int n)
{
    var ls = values.Take(n);
    var rs = values.Skip(n);
    return ls.Any() ?
        Cons(ls, SplitEvery(rs, n)) : 
        Enumerable.Empty<IEnumerable<T>>();
}

public static IEnumerable<T> Cons<T>(T x, IEnumerable<T> xs)
{
    yield return x;
    foreach (var xi in xs)
        yield return xi;
}

4

คุณสามารถใช้morelinqโดย Jon Skeet ใช้แบทช์ที่ชอบ:

string str = "1111222233334444";
int chunkSize = 4;
var chunks = str.Batch(chunkSize).Select(r => new String(r.ToArray()));

นี้จะกลับ 4 "1111222233334444"ชิ้นสำหรับสตริง หากความยาวสตริงน้อยกว่าหรือเท่ากับขนาดก้อนBatchจะส่งคืนสตริงเป็นองค์ประกอบเดียวของIEnumerable<string>

สำหรับเอาท์พุท:

foreach (var chunk in chunks)
{
    Console.WriteLine(chunk);
}

และมันจะให้:

1111
2222
3333
4444

ในหมู่ผู้เขียนของ MoreLINQ ที่ฉันเห็นโจนาธานเป้าแต่ไม่มีจอนสกีต ดังนั้นคุณจึงไม่ได้หมายความว่าจอนสกีตหรืออะไร? ;-)
Sнаđошƒаӽ

3
static IEnumerable<string> Split(string str, double chunkSize)
{
    return Enumerable.Range(0, (int) Math.Ceiling(str.Length/chunkSize))
       .Select(i => new string(str
           .Skip(i * (int)chunkSize)
           .Take((int)chunkSize)
           .ToArray()));
}

และวิธีการอื่น:

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {

        var x = "Hello World";
        foreach(var i in x.ChunkString(2)) Console.WriteLine(i);
    }
}

public static class Ext{
    public static IEnumerable<string> ChunkString(this string val, int chunkSize){
        return val.Select((x,i) => new {Index = i, Value = x})
                  .GroupBy(x => x.Index/chunkSize, x => x.Value)
                  .Select(x => string.Join("",x));
    }
}

3

หกปีต่อมา o_O

เพียงเพราะว่า

    public static IEnumerable<string> Split(this string str, int chunkSize, bool remainingInFront)
    {
        var count = (int) Math.Ceiling(str.Length/(double) chunkSize);
        Func<int, int> start = index => remainingInFront ? str.Length - (count - index)*chunkSize : index*chunkSize;
        Func<int, int> end = index => Math.Min(str.Length - Math.Max(start(index), 0), Math.Min(start(index) + chunkSize - Math.Max(start(index), 0), chunkSize));
        return Enumerable.Range(0, count).Select(i => str.Substring(Math.Max(start(i), 0),end(i)));
    }

หรือ

    private static Func<bool, int, int, int, int, int> start = (remainingInFront, length, count, index, size) =>
        remainingInFront ? length - (count - index) * size : index * size;

    private static Func<bool, int, int, int, int, int, int> end = (remainingInFront, length, count, index, size, start) =>
        Math.Min(length - Math.Max(start, 0), Math.Min(start + size - Math.Max(start, 0), size));

    public static IEnumerable<string> Split(this string str, int chunkSize, bool remainingInFront)
    {
        var count = (int)Math.Ceiling(str.Length / (double)chunkSize);
        return Enumerable.Range(0, count).Select(i => str.Substring(
            Math.Max(start(remainingInFront, str.Length, count, i, chunkSize), 0),
            end(remainingInFront, str.Length, count, i, chunkSize, start(remainingInFront, str.Length, count, i, chunkSize))
        ));
    }

AFAIK เคสแบบขอบทั้งหมดได้รับการจัดการ

Console.WriteLine(string.Join(" ", "abc".Split(2, false))); // ab c
Console.WriteLine(string.Join(" ", "abc".Split(2, true))); // a bc
Console.WriteLine(string.Join(" ", "a".Split(2, true))); // a
Console.WriteLine(string.Join(" ", "a".Split(2, false))); // a

กรณีขอบ "อินพุตเป็นสตริงว่างเปล่า"? ฉันคาดหวังว่าเหมือนกับ Split เพื่อส่งคืน IEnumerable ด้วยสตริงว่างเดียวที่มีรายการ
Nyerguds


3
static IEnumerable<string> Split(string str, int chunkSize)
{
   IEnumerable<string> retVal = Enumerable.Range(0, str.Length / chunkSize)
        .Select(i => str.Substring(i * chunkSize, chunkSize))

   if (str.Length % chunkSize > 0)
        retVal = retVal.Append(str.Substring(str.Length / chunkSize * chunkSize, str.Length % chunkSize));

   return retVal;
}

มันจัดการอย่างถูกต้องความยาวสตริงที่ไม่หารหารด้วย chunkSize

โปรดทราบว่าอาจต้องใช้รหัสเพิ่มเติมเพื่อจัดการกับขอบเคส (สตริงอินพุตว่างหรือสตริงว่าง chunkSize == 0)


2

เคล็ดลับสำคัญหากสตริงที่กำลังถูก chunked จำเป็นต้องสนับสนุนอักขระ Unicode ทั้งหมด

หากสตริงคือการสนับสนุนอักขระสากลเช่น𠀋แล้วแยกสตริงโดยใช้คลาส System.Globalization.StringInfo ใช้ StringInfo คุณสามารถแยกสตริงตามจำนวนขององค์ประกอบข้อความ

string internationalString = '𠀋';

สตริงด้านบนมีความยาว 2 เนื่องจากString.Lengthคุณสมบัติส่งคืนจำนวนวัตถุ Char ในอินสแตนซ์นี้ไม่ใช่จำนวนอักขระ Unicode


2

คำตอบที่ดีที่สุดง่ายที่สุดและทั่วไป :)

    string originalString = "1111222233334444";
    List<string> test = new List<string>();
    int chunkSize = 4; // change 4 with the size of strings you want.
    for (int i = 0; i < originalString.Length; i = i + chunkSize)
    {
        if (originalString.Length - i >= chunkSize)
            test.Add(originalString.Substring(i, chunkSize));
        else
            test.Add(originalString.Substring(i,((originalString.Length - i))));
    }

การคำนวณระยะเวลาในบรรทัดสุดท้ายจะซ้ำซ้อนเพียงใช้เกินที่ไม่ต้องใช้พารามิเตอร์ยาวSubstring originalString.Substring(i)นอกจากนี้คุณสามารถใช้>แทน>=ในการตรวจสอบของคุณ
Racil Hilan

@RacilHilan ฉันจะทดสอบการเปลี่ยนแปลงรหัสด้วยคำแนะนำของคุณและอัปเดตคำตอบ ฉันดีใจที่มีคนที่มีชื่อเสียงดีถึงเวลาที่จะทบทวนรหัสของฉัน :) ขอบคุณ Sandeep
Sandeep Kushwah

2

โดยส่วนตัวแล้วฉันชอบโซลูชันของฉัน :-)

มันจัดการ:

  • ความยาวสตริงที่มีขนาดหลายเท่า
  • ความยาวสตริงที่ไม่ใช่ขนาดของก้อนข้อมูลที่หลากหลาย
  • ความยาวสตริงที่เล็กกว่าขนาดก้อน
  • สตริง NULL และสตริงว่างเปล่า (ส่งข้อยกเว้น)
  • ขนาดของก้อนเล็กกว่า 1 (ส่งข้อยกเว้น)

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

    public static string[] Split(this string value, int chunkSize)
    {
        if (string.IsNullOrEmpty(value)) throw new ArgumentException("The string cannot be null.");
        if (chunkSize < 1) throw new ArgumentException("The chunk size should be equal or greater than one.");

        int remainder;
        int divResult = Math.DivRem(value.Length, chunkSize, out remainder);

        int numberOfChunks = remainder > 0 ? divResult + 1 : divResult;
        var result = new string[numberOfChunks];

        int i = 0;
        while (i < numberOfChunks - 1)
        {
            result[i] = value.Substring(i * chunkSize, chunkSize);
            i++;
        }

        int lastChunkSize = remainder > 0 ? remainder : chunkSize;
        result[i] = value.Substring(i * chunkSize, lastChunkSize);

        return result;
    }

2
List<string> SplitString(int chunk, string input)
{
    List<string> list = new List<string>();
    int cycles = input.Length / chunk;

    if (input.Length % chunk != 0)
        cycles++;

    for (int i = 0; i < cycles; i++)
    {
        try
        {
            list.Add(input.Substring(i * chunk, chunk));
        }
        catch
        {
            list.Add(input.Substring(i * chunk));
        }
    }
    return list;
}

1
ฉันชอบคำตอบนี้มาก แต่บางทีคุณควรใช้ถ้า ((i + 1) * chunk> = input.Length) แทนที่จะลอง / จับตามข้อยกเว้นสำหรับกรณีพิเศษ
nelsontruran

2

ฉันคิดว่านี่เป็นคำตอบที่ตรงไปตรงมา:

public static IEnumerable<string> Split(this string str, int chunkSize)
    {
        if(string.IsNullOrEmpty(str) || chunkSize<1)
            throw new ArgumentException("String can not be null or empty and chunk size should be greater than zero.");
        var chunkCount = str.Length / chunkSize + (str.Length % chunkSize != 0 ? 1 : 0);
        for (var i = 0; i < chunkCount; i++)
        {
            var startIndex = i * chunkSize;
            if (startIndex + chunkSize >= str.Length)
                yield return str.Substring(startIndex);
            else
                yield return str.Substring(startIndex, chunkSize);
        }
    }

และครอบคลุมกรณีขอบ


2

ฉันรู้ว่าคำถามมีอายุหลายปี แต่นี่คือการใช้ Rx มันจัดการlength % chunkSize != 0ปัญหาออกจากกล่อง:

   public static IEnumerable<string> Chunkify(this string input, int size)
        {
            if(size < 1)
                throw new ArgumentException("size must be greater than 0");

            return input.ToCharArray()
                .ToObservable()
                .Buffer(size)            
                .Select(x => new string(x.ToArray()))
                .ToEnumerable();
        }

1

ฉันสร้างโซลูชันของJoãoขึ้นมาเล็กน้อย สิ่งที่ฉันทำแตกต่างกันคือในวิธีการของฉันคุณสามารถระบุได้ว่าคุณต้องการคืนค่าอาเรย์ด้วยอักขระที่เหลือหรือว่าคุณต้องการที่จะตัดทอนถ้าอักขระสุดท้ายไม่ตรงกับความยาวของชิ้นที่ต้องการฉันคิดว่ามันค่อนข้างยืดหยุ่นและ รหัสค่อนข้างตรงไปตรงมา:

using System;
using System.Linq;
using System.Text.RegularExpressions;

namespace SplitFunction
{
    class Program
    {
        static void Main(string[] args)
        {
            string text = "hello, how are you doing today?";
            string[] chunks = SplitIntoChunks(text, 3,false);
            if (chunks != null)
            {
                chunks.ToList().ForEach(e => Console.WriteLine(e));
            }

            Console.ReadKey();
        }

        private static string[] SplitIntoChunks(string text, int chunkSize, bool truncateRemaining)
        {
            string chunk = chunkSize.ToString(); 
            string pattern = truncateRemaining ? ".{" + chunk + "}" : ".{1," + chunk + "}";

            string[] chunks = null;
            if (chunkSize > 0 && !String.IsNullOrEmpty(text))
                chunks = (from Match m in Regex.Matches(text,pattern)select m.Value).ToArray(); 

            return chunks;
        }     
    }
}

1
    public static List<string> SplitByMaxLength(this string str)
    {
        List<string> splitString = new List<string>();

        for (int index = 0; index < str.Length; index += MaxLength)
        {
            splitString.Add(str.Substring(index, Math.Min(MaxLength, str.Length - index)));
        }

        return splitString;
    }

คุณใช่ลืมพารามิเตอร์ MaxLength
Nyerguds

1

เปลี่ยนเล็กน้อยเพื่อส่งคืนชิ้นส่วนที่มีขนาดไม่เท่ากับก้อนขนาด

public static IEnumerable<string> Split(this string str, int chunkSize)
    {
        var splits = new List<string>();
        if (str.Length < chunkSize) { chunkSize = str.Length; }
        splits.AddRange(Enumerable.Range(0, str.Length / chunkSize).Select(i => str.Substring(i * chunkSize, chunkSize)));
        splits.Add(str.Length % chunkSize > 0 ? str.Substring((str.Length / chunkSize) * chunkSize, str.Length - ((str.Length / chunkSize) * chunkSize)) : string.Empty);
        return (IEnumerable<string>)splits;
    }

ไม่แน่ใจว่าฉันเห็นการใช้งาน back-casting นั้นListเพื่อIEnumerable; สิ่งที่ทำคือซ่อนฟังก์ชันเฉพาะรายการที่คุณอาจต้องการใช้ ไม่มีข้อเสียคือสิ่งใด ๆ Listที่จะเพียงแค่กลับมา
Nyerguds

1

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

List<string> Divided = Source3.Chunk(24).Select(Piece => string.Concat<char>(Piece)).ToList();

รหัสส่วนขยายจะมีลักษณะเช่นนี้ ...

#region Chunk Logic
private class ChunkedEnumerable<T> : IEnumerable<T>
{
    class ChildEnumerator : IEnumerator<T>
    {
        ChunkedEnumerable<T> parent;
        int position;
        bool done = false;
        T current;


        public ChildEnumerator(ChunkedEnumerable<T> parent)
        {
            this.parent = parent;
            position = -1;
            parent.wrapper.AddRef();
        }

        public T Current
        {
            get
            {
                if (position == -1 || done)
                {
                    throw new InvalidOperationException();
                }
                return current;

            }
        }

        public void Dispose()
        {
            if (!done)
            {
                done = true;
                parent.wrapper.RemoveRef();
            }
        }

        object System.Collections.IEnumerator.Current
        {
            get { return Current; }
        }

        public bool MoveNext()
        {
            position++;

            if (position + 1 > parent.chunkSize)
            {
                done = true;
            }

            if (!done)
            {
                done = !parent.wrapper.Get(position + parent.start, out current);
            }

            return !done;

        }

        public void Reset()
        {
            // per http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.reset.aspx
            throw new NotSupportedException();
        }
    }

    EnumeratorWrapper<T> wrapper;
    int chunkSize;
    int start;

    public ChunkedEnumerable(EnumeratorWrapper<T> wrapper, int chunkSize, int start)
    {
        this.wrapper = wrapper;
        this.chunkSize = chunkSize;
        this.start = start;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return new ChildEnumerator(this);
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

}
private class EnumeratorWrapper<T>
{
    public EnumeratorWrapper(IEnumerable<T> source)
    {
        SourceEumerable = source;
    }
    IEnumerable<T> SourceEumerable { get; set; }

    Enumeration currentEnumeration;

    class Enumeration
    {
        public IEnumerator<T> Source { get; set; }
        public int Position { get; set; }
        public bool AtEnd { get; set; }
    }

    public bool Get(int pos, out T item)
    {

        if (currentEnumeration != null && currentEnumeration.Position > pos)
        {
            currentEnumeration.Source.Dispose();
            currentEnumeration = null;
        }

        if (currentEnumeration == null)
        {
            currentEnumeration = new Enumeration { Position = -1, Source = SourceEumerable.GetEnumerator(), AtEnd = false };
        }

        item = default(T);
        if (currentEnumeration.AtEnd)
        {
            return false;
        }

        while (currentEnumeration.Position < pos)
        {
            currentEnumeration.AtEnd = !currentEnumeration.Source.MoveNext();
            currentEnumeration.Position++;

            if (currentEnumeration.AtEnd)
            {
                return false;
            }

        }

        item = currentEnumeration.Source.Current;

        return true;
    }

    int refs = 0;

    // needed for dispose semantics 
    public void AddRef()
    {
        refs++;
    }

    public void RemoveRef()
    {
        refs--;
        if (refs == 0 && currentEnumeration != null)
        {
            var copy = currentEnumeration;
            currentEnumeration = null;
            copy.Source.Dispose();
        }
    }
}
/// <summary>Speed Checked.  Works Great!</summary>
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
    if (chunksize < 1) throw new InvalidOperationException();

    var wrapper = new EnumeratorWrapper<T>(source);

    int currentPos = 0;
    T ignore;
    try
    {
        wrapper.AddRef();
        while (wrapper.Get(currentPos, out ignore))
        {
            yield return new ChunkedEnumerable<T>(wrapper, chunksize, currentPos);
            currentPos += chunksize;
        }
    }
    finally
    {
        wrapper.RemoveRef();
    }
}
#endregion

1
class StringHelper
{
    static void Main(string[] args)
    {
        string str = "Hi my name is vikas bansal and my email id is bansal.vks@gmail.com";
        int offSet = 10;

        List<string> chunks = chunkMyStr(str, offSet);

        Console.Read();
    }

    static List<string> chunkMyStr(string str, int offSet)
    {


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

        for (int i = 0; i < str.Length; i += offSet)
        {
            string temp = str.Substring(i, (str.Length - i) > offSet ? offSet : (str.Length - i));
            Console.WriteLine(temp);
            resultChunks.Add(temp);


        }

        return resultChunks;
    }
}

คุณสามารถปรับปรุงรหัสของคุณได้เล็กน้อย: เปลี่ยนนิพจน์ที่เพิ่มขึ้นi += offSetเป็นforนิพจน์ของคุณ
JimiLoe

1

แก้ไข (ตอนนี้ก็ยอมรับใด ๆ null ไม่ใช่stringและใด ๆในเชิงบวกchunkSize) คอนสแตนติ Spirinแก้ปัญหา 's:

public static IEnumerable<String> Split(String value, int chunkSize) {
  if (null == value)
    throw new ArgumentNullException("value");
  else if (chunkSize <= 0)
    throw new ArgumentOutOfRangeException("chunkSize", "Chunk size should be positive");

  return Enumerable
    .Range(0, value.Length / chunkSize + ((value.Length % chunkSize) == 0 ? 0 : 1))
    .Select(index => (index + 1) * chunkSize < value.Length 
      ? value.Substring(index * chunkSize, chunkSize)
      : value.Substring(index * chunkSize));
}

แบบทดสอบ:

  String source = @"ABCDEF";

  // "ABCD,EF"
  String test1 = String.Join(",", Split(source, 4));
  // "AB,CD,EF"
  String test2 = String.Join(",", Split(source, 2));
  // "ABCDEF"
  String test3 = String.Join(",", Split(source, 123));

1
static List<string> GetChunks(string value, int chunkLength)
{
    var res = new List<string>();
    int count = (value.Length / chunkLength) + (value.Length % chunkLength > 0 ? 1 : 0);
    Enumerable.Range(0, count).ToList().ForEach(f => res.Add(value.Skip(f * chunkLength).Take(chunkLength).Select(z => z.ToString()).Aggregate((a,b) => a+b)));
    return res;
}

การสาธิต


อันนี้เก็บส่วนที่เหลือของสตริง (การแยกโพสต์) แม้ว่ามันจะสั้นกว่า "chunkLenght" ขอบคุณ
Jason Loki Smith

0

ตามคำตอบโปสเตอร์อื่น ๆ พร้อมกับตัวอย่างการใช้งาน:

public static string FormatSortCode(string sortCode)
{
    return ChunkString(sortCode, 2, "-");
}
public static string FormatIBAN(string iban)
{
    return ChunkString(iban, 4, "&nbsp;&nbsp;");
}

private static string ChunkString(string str, int chunkSize, string separator)
{
    var b = new StringBuilder();
    var stringLength = str.Length;
    for (var i = 0; i < stringLength; i += chunkSize)
    {
        if (i + chunkSize > stringLength) chunkSize = stringLength - i;
        b.Append(str.Substring(i, chunkSize));
        if (i+chunkSize != stringLength)
            b.Append(separator);
    }
    return b.ToString();
}

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