2012-03-21 52 views
4

我想用LINQ解決以下問題,我有以下集合:如何從IEnumerable巧妙地創建一個匿名類型<T>?

List<byte> byteList = new List<byte() { 0x01, 0x00, 0x01, 0x02, 0x01, 0x00, 0x3, 0x4, 0x02 }; 

的數據在這個例子中遵循以下圖案:

byteList [0] =地址(1, 2,3,... N)

byteList [1] =舊狀態,這基本上是代表一個枚舉

byteList [2] =新的狀態的,與上述相同的

我與嵌入式設備連接,這是我可以查看輸入更改的方式。

爲了清理代碼並使維護程序員更容易遵循我的邏輯,我想抽象出一些涉及到的細節和螺栓,並將每個三字節數據集提取爲匿名類型在函數內部使用來執行一些額外的處理。我寫了一個快速實現,但我相信它可以大大簡化。我試圖清理代碼,而不是渾水!必須有做一個簡單的方法如下:

List<byte> byteList = new List<byte>() 
{ 
    0x01, 0x09, 0x01, 0x02, 0x08, 0x02, 0x03, 0x07, 0x03 
}; 
var addresses = byteList 
    .Where((b, i) => i % 3 == 0) 
    .ToList(); 
var oldValues = byteList 
    .Where((b, i) => i % 3 == 1) 
    .ToList(); 
var newValues = byteList 
    .Where((b, i) => i % 3 == 2) 
    .ToList(); 

var completeObjects = addresses 
    .Select((address, index) => new 
    { 
     Address = address, 
     OldValue = oldValues[index], 
     NewValue = newValues[index] 
    }) 
    .ToList(); 
foreach (var anonType in completeObjects) 
{ 
    Console.WriteLine("Address: {0}\nOld Value: {1}\nNew Value: {2}\n", 
     anonType.Address, anonType.OldValue, anonType.NewValue); 
} 
+0

對於非負I,'第(i + 2)%3 == 0'相當於'則i%3 == 1' 。 – phoog 2012-03-21 22:02:34

+0

我不確定LINQ在這裏有多大的幫助。 MoreLINQ包含'Batch()'方法,這是將枚舉分塊爲大小桶的好方法。或者,一個簡單的'for'循環可能會非常簡單明瞭。 – dlev 2012-03-21 22:05:30

+0

'選擇(b => b)'是一個不適用 – 2012-03-21 22:07:38

回答

5

您可以使用Enumerable.Range和一些數學:

List<byte> byteList = new List<byte>() 
{ 
    0x01, 0x09, 0x01, 0x02, 0x08, 0x02, 0x03, 0x07, 0x03 
}; 
var completeObjects = Enumerable.Range(0, byteList.Count/3).Select(index => 
    new 
    { 
     Address = byteList[index * 3], 
     OldValue = byteList[index * 3 + 1], 
     NewValue = byteList[index * 3 + 2], 
    }); 

如果字節數是不是3的倍數,額外的一個或兩個字節將被忽略。

+0

這似乎是使用LINQ解決我的最簡單的方法問題。我知道LINQ可能不是解決這個問題的最好方法,但是這個問題確實指出要使用LINQ。這個問題是作爲一個學習工具,我的生產代碼可能會使用phoog解決方案的變體。 – Nate 2012-03-22 00:00:33

3

爲了簡化起見,我想創建一個記錄類型和使用for循環:

class RecordType 
{ 
    //constructor to set the properties omitted 
    public byte Address { get; private set; } 
    public byte OldValue { get; private set; } 
    public byte NewValue { get; private set; } 
} 

IEnumerable<RecordType> Transform(List<byte> bytes) 
{ 
    //validation that bytes.Count is divisible by 3 omitted 

    for (int index = 0; index < bytes.Count; index += 3) 
     yield return new RecordType(bytes[index], bytes[index + 1], bytes[index + 2]); 
} 

另外,如果你肯定需要一個匿名類型,你可以做,沒有LINQ:

for (int index = 0; index < bytes.Count; index += 3) 
{ 
    var anon = new { Address = bytes[index], OldValue = bytes[index + 1], NewValue = bytes[index + 3] }; 
    //... do something with anon 
} 

的LINQ是非常有用的,但它是在這個任務尷尬,因爲序列項具有取決於其locati有不同的含義在序列中。

+0

我承認這是我收到的最務實和最簡單的答案。你的答案的一個變種可能是我爲我的產品代碼選擇的路線,但是由於指定LINQ的問題,我需要公平地考慮那些始終牢記我的問題框架的人。謝謝你的回答。 – Nate 2012-03-22 00:03:01

+0

@Nate夠公平;當我讀完這個問題並開始制定我的答案時,我不知何故失去了「我想使用LINQ ...」這句話的軌道:-) – phoog 2012-03-22 02:49:49

0

我不確定這是否是一個聰明的解決方案,但我使用該示例嘗試在不創建單獨列表的情況下完成此操作。

var completeObjects = byteList 
    // This is required to access the index, and use integer 
    // division (to ignore any reminders) to group them into 
    // sets by three bytes in each. 
    .Select((value, idx) => new { group = idx/3, value }) 
    .GroupBy(x => x.group, x => x.value) 

    // This is just to be able to access them using indices. 
    .Select(x => x.ToArray()) 

    // This is a superfluous comment. 
    .Select(x => new { 
     Address = x[0], 
     OldValue = x[1], 
     NewValue = x[2] 
    }) 

    .ToList(); 
0

如果你必須使用LINQ(不知道這是一個很好的計劃),那麼一個選項是:

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

static class LinqExtensions 
{ 
    public static IEnumerable<T> EveryNth<T>(this IEnumerable<T> e, int start, int n) 
    { 
     int index = 0; 
     foreach(T t in e) 
     { 
      if((index - start) % n == 0) 
      { 
       yield return t; 
      } 
      ++index; 
     } 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     List<byte> byteList = new List<byte>() 
     { 
      0x01, 0x09, 0x01, 0x02, 0x08, 0x02, 0x03, 0x07, 0x03 
     }; 

     var completeObjects = 
      byteList.EveryNth(0, 3).Zip 
      (
       byteList.EveryNth(1, 3).Zip 
       (
        byteList.EveryNth(2, 3), 
        Tuple.Create 
       ), 
       (f,t) => new { Address = f, OldValue = t.Item1, NewValue = t.Item2 } 
      ); 

     foreach (var anonType in completeObjects) 
     { 
      Console.WriteLine("Address: {0}\nOld Value: {1}\nNew Value: {2}\n", anonType.Address, anonType.OldValue, anonType.NewValue); 
     } 
    } 
} 
0

這個怎麼樣?

var addresses = 
    from i in Enumerable.Range(0, byteList.Count/3) 
    let startIndex = i * 3 
    select new 
    { 
     Address = byteList[startIndex], 
     OldValue = byteList[startIndex + 1], 
     NewValue = byteList[startIndex + 2] 
    }; 

注:我公司開發的獨立邁克爾·劉的回答這一點,而他幾乎是一樣我要去,因爲它看起來更漂亮些我離開這裏這個答案。 :-)

+0

這是一個很好,簡潔的答案,正是我期待的類型。不幸的是,當使用LINQ而不是查詢語法時,我更喜歡使用方法語法。也許我對Python的使用體驗正在顯現,但感覺更自然。感謝您給我一種替代方式來完成工作! – Nate 2012-03-21 23:58:50

0

這裏試圖用擴展方法ChunkToList來做到這一點,它將IEnumerable<T>分成IList<T>的塊。

用法:

 var compObjs = byteList.ChunkToList(3) 
           .Select(arr => new { 
             Address = arr[0], 
             OldValue = arr[1], 
             NewValue = arr[2] 
           }); 

實現:

static class LinqExtensions 
{ 
    public static IEnumerable<IList<T>> ChunkToList<T>(this IEnumerable<T> list, int size) 
    { 
     Debug.Assert(list.Count() % size == 0); 

     int index = 0; 
     while (index < list.Count()) 
     { 
      yield return list.Skip(index).Take(size).ToList(); 
      index += size; 
     } 
    } 
} 
+0

嗯,'SplitIntoListsOf'應該是一個更好的名字不會吧:'byteList.SplitIntoListsOf(3).Select' ... – markmuetz 2012-03-21 23:08:34

+0

我真的很喜歡你的解決方案適用於Enumerable 並且相當乾淨。非常有趣的方式來解決這個問題,謝謝你向我展示了另一種完成工作的方式。 – Nate 2012-03-22 00:09:31

相關問題