2011-09-30 57 views
5

Tôi có đối tượng Tin nhắn bao bọc định dạng tin nhắn mà tôi không có quyền kiểm soát. Định dạng là danh sách các cặp khóa/giá trị đơn giản. Tôi muốn trích xuất một danh sách Người dùng từ một Tin nhắn đã cho. Ví dụ: được cung cấp thông báo sau ...Thay thế vòng lặp chuyển mạch bằng truy vấn LINQ

1. 200->.... 
2. 300->.... 
3. .... 
4. 405->.... 
5. 001->first_user_name 
6. 002->first_user_phone 
7. 003->first_user_fax 
8. 001->second_user_name 
9. 001->third_user_name 
10. 002->third_user_phone 
11. 003->third_user_fax 
12. 004->third_user_address 
13. ..... 
14. 001->last_user_name 
15. 003->last_user_fax 

Tôi muốn trích xuất bốn Người dùng bằng tập hợp thuộc tính được cung cấp. Các phím ban đầu 200/300 .... 405 đại diện cho các trường mà tôi không cần và có thể bỏ qua để chuyển đến dữ liệu Người dùng.

Mỗi dữ liệu người dùng ở các trường liên tiếp nhưng số trường khác nhau tùy thuộc vào lượng thông tin được biết về người dùng. Phương pháp sau đây thực hiện những gì tôi đang tìm kiếm. Nó sử dụng một liệt kê các kiểu khóa có thể và một phương thức để tìm chỉ mục của trường đầu tiên với dữ liệu người dùng.

private List<User> ParseUsers(Message message) 
{ 
    List<User> users = new List<User>(); 
    User user = null; String val = String.Empty; 

    for(Int32 i = message.IndexOfFirst(Keys.Name); i < message.Count; i++) 
    { 
     val = message[ i ].Val; 

     switch(message[ i ].Key) 
     { 
      case Keys.Name: 
       user = new User(val); 
       users.Add(user); 
       break; 
      case Keys.Phone: 
       user.Phone = val; 
       break; 
      case Keys.Fax: 
       user.Fax = val; 
       break; 
      case Keys.Address: 
       user.Address = val; 
       break; 
      default: 
       break; 
     } 
    } 

    return users; 
} 

Tôi tự hỏi nếu có thể thay thế phương thức bằng truy vấn LINQ. Tôi gặp sự cố khi yêu cầu Linq chọn một người dùng mới và điền vào các trường của nó với tất cả dữ liệu phù hợp cho đến khi bạn tìm thấy bắt đầu của mục nhập người dùng tiếp theo.

Lưu ý: Số khóa tương đối là ngẫu nhiên (không phải 1,2,3,4) theo định dạng thư thực.

+0

bạn đang sử dụng Resharper? nó khá tốt ở các vòng tái cấu trúc cho các biểu thức LINQ. –

+2

Lợi ích của việc biến điều này thành truy vấn LINQ là gì? Mã của bạn trông rất tuyệt với tôi. – dtb

+0

@Marian: chỉ sau 5.x IIRC – sehe

Trả lời

5

Tôi không thấy lợi ích trong việc thay đổi mã của bạn để truy vấn LINQ, nhưng nó chắc chắn có thể:

private List<User> ParseUsers(Message message) 
{ 
    return Enumerable 
     .Range(0, message.Count) 
     .Select(i => message[i]) 
     .SkipWhile(x => x.Key != Keys.Name) 
     .GroupAdjacent((g, x) => x.Key != Keys.Name) 
     .Select(g => g.ToDictionary(x => x.Key, x => x.Val)) 
     .Select(d => new User(d[Keys.Name]) 
     { 
      Phone = d.ContainsKey(Keys.Phone) ? d[Keys.Phone] : null, 
      Fax  = d.ContainsKey(Keys.Fax)  ? d[Keys.Fax]  : null, 
      Address = d.ContainsKey(Keys.Address) ? d[Keys.Address] : null, 
     }) 
     .ToList(); 
} 

sử dụng

static IEnumerable<IEnumerable<T>> GroupAdjacent<T>(
    this IEnumerable<T> source, Func<IEnumerable<T>, T, bool> adjacent) 
{ 
    var g = new List<T>(); 
    foreach (var x in source) 
    { 
     if (g.Count != 0 && !adjacent(g, x)) 
     { 
      yield return g; 
      g = new List<T>(); 
     } 
     g.Add(x); 
    } 
    yield return g; 
} 
+1

+1: câu trả lời câu hỏi của OP và là * khá thuyết phục * nói với anh ta để lại mã số tiền phạt của mình như nó được. – ANeves

+1

@dtb .. yup phiên bản chỉnh sửa hoạt động một điều trị .. haha ​​.. không hoàn toàn chắc chắn tôi hiểu chính xác như thế nào được nêu ra nhưng cảm ơn một lần nữa .. tuyệt vời của nó để xem luôn luôn có một cách để làm điều gì đó .. và tôi đang tìm hiểu thêm về LINQ bằng cách đi qua mã số của bạn – Chris

1

Cách chia tin nhắn thành List<List<KeyValuePait<int, string>>> trong đó mỗi List<KeyValuePair<int, string>> đại diện cho một người dùng. Sau đó bạn có thể làm điều gì đó như:

// SplitToUserLists would need a sensible implementation. 
List<List<KeyValuePair<int,string>>> splitMessage = message.SplitToUserLists(); 
IEnumerable<User> users = splitMessage.Select(ConstructUser); 

Với

private User ConstructUser(List<KeyValuePair<int, string>> userList) 
{ 
    return userList.Aggregate(new User(), (user, keyValuePair) => user[keyValuePair.Key] = keyValuePair.Val); 
} 
+0

@joey .. hi Joey .. cảm ơn cho bài viết của bạn .. được fiddling với mã của dtb .. mà làm việc một điều trị bây giờ (cảm ơn dtb) .. Tôi phải thực hiện lại người dùng của tôi lớp để làm việc với ConstructUser như là và Id được đọc qua tin nhắn nhiều hơn một lần theo cách này .. nhưng thật thú vị khi thấy các cách tiếp cận khác nhau! cảm ơn một lần nữa – Chris

+0

Không sao cả. Tôi nghĩ giải pháp tốt nhất vẫn là giải pháp bạn bắt đầu. Mặc dù có lẽ cố gắng refactor chuyển đổi thành User hoặc một số đối tượng UserBuilder. – Joey

+0

+1 .. yup thats một gợi ý tốt .. vượt qua nó một enumerable của các lĩnh vực chia như u đề nghị/thông qua GroupAdjacent hoặc tương tự và để cho nó chăm sóc sáng tạo của riêng mình, định dạng người dùng là không thay đổi (Ahem!) ... Vì vậy, tôi nghĩ rằng tôi muốn đi với người dùng hơn là UserFactory/UserBuilder nhưng ngay cả như vậy đặt mã tạo trong người dùng sẽ đẹp hơn có nó bên trong chuyển đổi tại một vị trí khác. Cảm ơn Joey. – Chris

1

Không, và lý do hạnh phúc, nói chung, hầu hết các chức năng LINQ, trong cùng một cách như các truy vấn SQL, đối phó với các dữ liệu có thứ tự, ví dụ: họ không đưa ra giả định về thứ tự của dữ liệu đến. Điều đó mang lại cho họ sự linh hoạt để được song song, vv Dữ liệu của bạn có thứ tự nội tại, do đó, không phù hợp với mô hình truy vấn.

+0

@tim .. yup Tôi đã nghĩ rằng khi tôi viết vòng lặp nhưng mỗi khi tôi bỏ qua nó tôi có cảm giác niggling rằng nó đã có thể .. – Chris

1

Tôi không nghĩ có bất kỳ lợi ích hiệu suất , nhưng nó làm tăng khả năng đọc rất nhiều trong quan điểm của tôi.

Một giải pháp khả thi có thể trông như thế này:

var data = File.ReadAllLines("data.txt") 
      .Select(line => line.Split(new[] {"->"}, StringSplitOptions.RemoveEmptyEntries)) 
      .GroupByOrder(ele => ele[0]); 

Sự kỳ diệu thực đang xảy ra đằng sau GroupByOrder, mà là một phương pháp khuyến nông.

public static IEnumerable<IEnumerable<T>> GroupByOrder<T, K>(this IEnumerable<T> source, Func<T, K> keySelector) where K : IComparable { 
    var prevKey = keySelector(source.First()); 
    var captured = new List<T>(); 
    foreach (var curr in source) { 
    if (keySelector(curr).CompareTo(prevKey) <= 0) { 
     yield return captured; 
     captured = new List<T>(); 
    } 
    captured.Add(curr); 
    } 
    yield return captured; 
} 

(Disclaimer: ý tưởng bị đánh cắp từ Tomas Petricek)

dữ liệu mẫu của bạn mang lại các nhóm sau đây, mà bây giờ chỉ cần phải được phân tích vào đối tượng người dùng.

User: 
    first_user_name 
    first_user_phone 
    first_user_fax 
User: 
    second_user_name 
User: 
    third_user_name 
    third_user_phone 
    third_user_fax 
    third_user_address 
User: 
    last_user_name 
    last_user_fax 
+0

hi fjdumont .. cảm ơn bạn đã đăng bài .. Tôi vẫn ủng hộ câu trả lời của dtb vì nó hoàn thành tức là. nhận một thông điệp đầu vào và trả về một danh sách người dùng. Ngoài ra, giải pháp của bạn dường như vỡ khi không có dữ liệu người dùng .. ví dụ ban đầu dường như chỉ hoạt động với 200/300/405 trường đã bị xóa – Chris

+0

@fjdumont .. bạn có liên kết đến cuộc thảo luận của Tomas Petricek về điều này không? – Chris

+0

Tôi đã phải google nó bản thân mình, nhưng ở đây nó là: http://tomasp.net/blog/custom-linq-grouping.aspx – fjdumont

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