2013-08-08 37 views
19

Tôi biết khái niệm String.Split đã được giải quyết trước với vô số các cách tiếp cận khác nhau, nhưng tôi đặc biệt quan tâm đến giải pháp LINQ cho câu hỏi này.Chia chuỗi thành danh sách các chuỗi có độ dài bằng cách sử dụng LINQ

Tôi đã cố gắng viết một lớp mở rộng để xử lý việc chia tách, nhưng cả hai lần thử đều có một số vấn đề lớn. Vì vậy, sau:

string s = "ABCDEFGHIJKLMNOPQRSTUVWX"; 
var results = s.SplitEvery(4); 

tôi muốn một danh sách như: { "ABCD", "EFGH", "IJKL", "MNOP", "QRST", "UVWX"}

đây là lớp mở rộng của tôi:

public static class Extensions 
{ 
    public static List<string> SplitEvery(this string s, int n) 
    { 
     List<string> list = new List<string>(); 

     var Attempt1 = s.Select((c, i) => i % n== 0 ? s.Substring(i, n) : "|").Where(x => x != "|").ToList(); 

     var Attempt2 = s.Where((c, i) => i % n== 0).Select((c, i) => s.Substring(i, n)).ToList(); 

     return list; 
    } 
} 

Cố gắng 1 chèn một chuỗi giả "|" mỗi khi điều kiện không được đáp ứng, sau đó loại bỏ tất cả các trường hợp của chuỗi giả để tạo danh sách cuối cùng. Nó hoạt động, nhưng việc tạo ra các chuỗi xấu có vẻ như một bước bổ sung không cần thiết. Hơn nữa, nỗ lực này thất bại nếu chuỗi không chia hết cho n. Cố gắng 2 là tôi đang cố gắng để lựa chọn chỉ nền mà chỉ số có thể chia hết cho N, nhưng giá trị 'i' trong câu lệnh Select không tương ứng với giá trị 'i' trong câu lệnh Where, vì vậy tôi nhận được kết quả như: {"ABCD", "BCDE", v.v ...}

Tôi cảm thấy mình gần gũi với một giải pháp tốt, nhưng có thể sử dụng hướng di chuyển hữu ích đúng hướng. Bất kỳ đề xuất?

[Chỉnh sửa]

tôi đã kết thúc đi với một sự kết hợp những gợi ý để xử lý tôi chuỗi-splitter. Nó có thể không phải là nhanh nhất, nhưng như là một newbie để LINQ, thực hiện này là gọn gàng nhất và dễ dàng cho tôi để hiểu.

public static List<string> SplitEvery(this string s, int size) 
{ 
    return s.Select((x, i) => i) 
     .Where(i => i % size == 0) 
     .Select(i => String.Concat(s.Skip(i).Take(size))).ToList(); 
} 

Cảm ơn tất cả các đề xuất tuyệt vời.

+0

Lưu ý phụ: sẽ rất hay khi chỉ định tiêu chí "tốt hơn" của bạn là gì. I E.trong trường hợp này nó có vẻ là "truy vấn có thể đọc được bởi người dùng LINQ mới làm quen với mô tả gần nhất có thể, thích phương pháp' Enumerable' hơn tất cả các cân nhắc về hiệu suất ". Trong 'Concat' lite lite này với 'Take' thực sự sẽ trông giống như một cách tiếp cận tốt nhất. –

+0

Lời xin lỗi của tôi, đó là một đánh giá công bằng. Tôi đã chủ yếu quan tâm đến một cách tiếp cận sạch sẽ, một lớp lót tương tự như những nỗ lực ban đầu của tôi ở trên. Trong trường hợp của tôi, khả năng đọc quan trọng hơn đối với tôi hơn là khả năng mở rộng. Hy vọng rằng không ai sẽ cố gắng đổ một tập tin văn bản khổng lồ vào chuỗi của tôi. :) – MadHenchbot

+1

(Nhận xét của tôi ở trên là đề xuất thuần túy - không có gì để xin lỗi). Một lưu ý ngẫu nhiên khác để xem ra trong LINQ - cách tiếp cận cuối cùng của bạn lặp lại chuỗi nhiều lần. Nó là tốt cho chuỗi, nhưng sẽ không làm việc cho "một thời gian" trình tự như kết quả của truy vấn SQL hoặc 'File.ReadAllLines'. Có một số câu trả lời (tức là với 'lợi nhuận') chứng minh các cách tiếp cận lặp lại việc thu thập một lần. –

Trả lời

8

Dưới đây là một giải pháp:

var result = s.Select((x, i) => i) 
       .Where(i => i % 4 == 0) 
       .Select(i => s.Substring(i, s.Length - i >= 4 ? 4 : s.Length - i)); 
+0

CÓ. Đó là chính xác những gì tôi đã cố gắng để có được ra khỏi đó. Đối với tôi, đây là lựa chọn dễ hiểu và dễ đọc nhất, vì kiến ​​thức về LINQ của tôi khá hạn chế. Cám ơn rất nhiều! – MadHenchbot

24
string s = "ABCDEFGHIJKLMNOPQRSTUVWX"; 
var results = s.Select((c, i) => new { c, i }) 
      .GroupBy(x => x.i/4) 
      .Select(g => String.Join("",g.Select(y=>y.c))) 
      .ToList(); 

Bạn cũng có thể sử dụng morelinq's batch

var res = s.Batch(4).Select(x => String.Join("", x)).ToList(); 

Nếu bạn không nhớ sử dụng tác dụng phụ, điều này có thể quá

var res2 = s.SplitEvery(4).ToList(); 

public static IEnumerable<string> SplitEvery(this string s, int n) 
{ 
    int index = 0; 
    return s.GroupBy(_=> index++/n).Select(g => new string(g.ToArray())); 
} 

Và Dĩ nhiên mỗi chuỗi hoạt động câu hỏi xứng đáng là một câu trả lời Regex :)

var res3 = Regex.Split(s, @"(?<=\G.{4})"); 
+2

+1 cho regex. ;-p – Macke

+0

Tuyệt vời! Cảm ơn rất nhiều ví dụ hay. :) – MadHenchbot

+0

Hãy nghĩ rằng giải pháp regex nên ở trên cùng của câu trả lời này, vì nó nhanh hơn (từ các bài kiểm tra của tôi) và ngắn hơn thì các giải pháp khác ở đây. – Kamarey

3

Điều này dường như làm việc:

public static IEnumerable<string> SplitEvery(this string s, int n) { 
    var enumerators = Enumerable.Repeat(s.GetEnumerator(), n); 
    while (true) { 
     var chunk = string.Concat(enumerators 
      .Where(e => e.MoveNext()) 
      .Select(e => e.Current)); 
     if (chunk == "") yield break; 
     yield return chunk; 
    } 
} 
1

Dưới đây là một vài LINQy cách để làm điều đó:

public static IEnumerable<string> SplitEvery(this IEnumerable<char> s , int n) 
{ 
    StringBuilder sb = new StringBuilder(n) ; 
    foreach (char c in s) 
    { 
    if (sb.Length == n) 
    { 
     yield return sb.ToString() ; 
     sb.Length = 0 ; 
    } 
    sb.Append(c) ; 
    } 
} 

Hoặc

public static IEnumerable<string> SplitEvery(this string s , int n) 
{ 
    int limit = s.Length - (s.Length % n) ; 
    int i = 0 ; 

    while (i < limit) 
    { 
    yield return s.Substring(i,n) ; 
    i+=n ; 
    } 

    if (i < s.Length) 
    { 
    yield return s.Substring(i) ; 
    } 

} 
+5

Tò mò như thế nào họ đang "LINQy"? –

+2

Để được LINQy, bạn nên sử dụng LINQ. – recursive

+1

Chúng là phương pháp mở rộng LINQ. Bạn có thể muốn đọc lên trên [cách mở rộng LINQ] (http://msdn.microsoft.com/en-us/library/cc981895.aspx) –

4

Substring sẽ ổn nếu chọn các phần gồm 4 ký tự của chuỗi. Bạn chỉ cần phải cẩn thận với phần cuối cùng:

new Func<string, int, IEnumerable<string>>(
     (string s, int n) => 
      Enumerable.Range(0, (s.Length + n-1)/n) 
      .Select(i => s.Substring(i*n, Math.Min(n, s.Length - i*n)))) 
("ABCDEFGHIJKLMNOPQRSTUVWX", 4) 

Lưu ý: nếu câu trả lời này được chuyển thành hoạt động trên generic đếm được nó sẽ phải lặp bộ sưu tập nhiều lần (Count()Substring chuyển đổi sang Skip(i*n).Take(n)).

6
public static IEnumerable<string> SplitEvery(this string s, int length) 
{ 
    return s.Where((c, index) => index % length == 0) 
      .Select((c, index) => String.Concat(
       s.Skip(index * length).Take(length) 
      ) 
      ); 
} 

Ban giám khảo sẽ ra liệu new String(chars.ToArray()) sẽ nhanh hơn hoặc chậm hơn cho điều này là String.Concat(chars).

Bạn có thể khóa thêm .ToList() để trả lại Danh sách thay vì IEnumerable.

+0

Tôi đã lo lắng cuối cùng .Take (chiều dài) sẽ ném ra khỏi lỗi chỉ mục, nhưng có vẻ như tất cả được xử lý bên trong phương thức. Giải pháp tuyệt vời! – MadHenchbot

+1

Vâng, có một chút thủ thuật dễ bị tổn thương có thể đọc được để cho thấy rằng đã muộn khi tôi viết điều này ... Tức là, kết quả của cuộc gọi 'Where' (=' char' tại mỗi chỉ số chia nhỏ) không bao giờ được sử dụng trực tiếp - chỉ có ở đó để giới hạn số lượng kết quả mà 'Select' sau sẽ trả về. Ngoại lệ duy nhất 'Take' nên ném là, theo như tôi nhớ lại, nếu nguồn bạn gọi nó là' null'. Phần còn lại của thời gian, nó là The Sensible Thing. – JimmiTh

+0

... nói cách khác, 's.Where' có thể được thay thế bằng' Enumerable.Range (0, x) ', trong đó' x' sẽ là số chỉ số được chia. Xem ví dụ Câu trả lời của @ AlexeiLevenkov. Điều đó sẽ truyền đạt rõ ràng hơn ý định. – JimmiTh

9

Bạn có thể sử dụng phương pháp mở rộng này, mà thực hiện với chuỗi đơn giản nhận được (Tôi tin rằng nó là nhanh hơn, hơn liệt kê trên nhân vật và tham gia chúng thành chuỗi):

public static IEnumerable<string> SplitEvery(this string s, int length) 
{ 
    int index = 0; 
    while (index + length < s.Length) 
    { 
     yield return s.Substring(index, length); 
     index += length;     
    } 

    if (index < s.Length) 
     yield return s.Substring(index, s.Length - index); 
} 
1

cũng này hoạt động, nhưng đòi hỏi 'unwrapping' một IGrouping<x,y>:

public static IEnumerable<String> Split(this String me,int SIZE) { 
    //Works by mapping the character index to a 'modulo Staircase' 
    //and then grouping by that 'stair step' value 
    return me.Select((c, i) => new { 
    step = i - i % SIZE, 
    letter = c.ToString() 
    }) 
    .GroupBy(kvp => kvp.step) 
    .Select(grouping => grouping 
    .Select(g => g.letter) 
    .Aggregate((a, b) => a + b) 
); 
} 

EDIT: Sử dụng cơ chế thẩm định lười biếng LINQ của (yield return) bạn cũng có thể đạt được điều này sử dụng đệ quy

public static IEnumerable<String> Split(this String me, int SIZE) {  
    if (me.Length > SIZE) { 
    var head = me.Substring(0,SIZE); 
    var tail = me.Substring(SIZE,me.Length-SIZE); 
    yield return head;   
    foreach (var item in tail.Split(SIZE)) { 
     yield return item; 
    } 
    } else { 
    yield return me; 
    } 
} 

Mặc dù, cá nhân tôi, tôi tránh xa Substring vì nó khuyến khích mã nhà nước-ful (quầy, chỉ số, vv trong cha mẹ hoặc phạm vi toàn cầu).

+0

Đọc câu trả lời phương pháp này gần giống với câu trả lời đầu tiên từ @ I4V, ngoại trừ không có số nguyên sàn-số nguyên hoặc tham số trống-chuỗi. – theoski

Các vấn đề liên quan