2011-09-30 65 views
5

我有一個消息對象,它封裝了我無法控制的消息格式。格式是鍵/值對的簡單列表。我想從給定的消息中提取用戶列表。例如,給出以下消息...使用Linq查詢替換for-switch循環

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 

我想提取四個用戶提供的屬性集。初始密鑰200/300 .... 405表示我不需要的字段,可以跳過以獲取用戶數據。

每個用戶的數據都在連續的字段中,但字段的數量取決於有多少用戶知道的信息。以下方法做我正在尋找的東西。它使用枚舉可能的鍵類型和方法來查找包含用戶數據的第一個字段的索引。

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; 
} 

我想知道是否有可能用Linq查詢替換方法。我無法告訴Linq選擇一個新用戶並填充其所有匹配數據的字段,直到找到下一個用戶條目的開始。

注意:相對密鑰號碼是真實消息格式的隨機數(不是1,2,3,4)。

+0

您使用的是Resharper嗎?它非常適合重構LINQ表達式的循環。 –

+2

將此轉換爲LINQ查詢有什麼好處?你的代碼對我來說看起來不錯。 – dtb

+0

@Marian:只有5.x IIRC – sehe

回答

5

我沒有看到改變你的代碼LINQ查詢的利益,但它絕對有可能:

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(); 
} 

使用

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:回答OP的問題,*相當令人信服*告訴他保持原樣。 – ANeves

+1

@dtb .. yup編輯的版本工程治療..哈哈..不完全確定我明白如何只是但再次感謝..它的偉大,看到總有辦法做某事..我正在學習更多Linq通過您的代碼 – Chris

1

如何將郵件拆分爲List<List<KeyValuePait<int, string>>>,其中每個List<KeyValuePair<int, string>>代表單個用戶。然後,你可以這樣做:

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

隨着

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

@joey ..嗨Joey ..感謝您的帖子..一直襬弄着dtb的代碼..它現在工作的一種享受(謝謝dtb)..我必須重新實現我的用戶類與ConstructUser一起工作,並且通過這種方式不止一次地閱讀消息..但看到不同的方法很有趣!再次感謝 – Chris

+0

沒問題。我認爲最好的解決方案仍然是你開始的。雖然也許嘗試將交換機重構爲User或某個UserBuilder對象。 – Joey

+0

+1 .. that這是一個很好的建議..通過它可分割的字段可分割爲你建議/通過GroupAdjacent或類似的,讓它照顧自己的創作,用戶格式不太可能改變(Ahem!)...所以我認爲我會使用User而不是UserFactory/UserBuilder,但即使如此,將創建代碼放在User中也會比在另一個位置的交換機中更好。謝謝Joey。 – Chris

1

否,其原因是,在一般情況下,大多數LINQ功能,以同樣的方式作爲SQL查詢,處理無序數據,即他們不會對傳入數據的順序做出假設。這使得他們可以靈活地進行並行化處理等。您的數據具有固有的順序,因此不符合查詢模型。

+0

@tim ..是的,我在想,當我寫循環,但每次我跳過它時,我都有這種n feeling的感覺,這是可能的。 – Chris

1

我不認爲有任何性能優勢,但在我看來,它提高了可讀性。

一個可能的解決辦法是這樣的:

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

真正神奇正在發生的事情背後GroupByOrder,這是一個擴展方法。

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; 
} 

(免責聲明:從托馬斯Petricek被盜想法)

你的樣本數據得出以下組,現在只需要被解析成你的用戶對象。

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

嗨fjdumont ..感謝張貼..我仍然偏愛dtb的答案,因爲它是完整的即。接收消息輸入並返回用戶列表。此外,當非用戶數據存在時,您的解決方案似乎會中斷..ie原始示例似乎只能用於刪除200/300/405字段 – Chris

+0

@fjdumont ..您有鏈接到Tomas Petricek對此的討論嗎? – Chris

+0

我不得不穀歌它自己,但它在這裏:http://tomasp.net/blog/custom-linq-grouping.aspx – fjdumont