2011-01-06 9 views
11

Estoy tratando con una aplicación de puerto COM y tenemos una estructura de paquetes de longitud variable definida que estoy hablando con un microcontrolador con . El paquete tiene delimitadores para los bytes de inicio y detención. El problema es que a veces el buffer de lectura puede contener caracteres extraños. Parece que siempre recibiré todo el paquete, solo un parloteo adicional antes/después de los datos reales. Así que tengo un búfer al que anexo datos cada vez que se reciben datos nuevos del puerto COM. ¿Cuál es la mejor manera de buscar en este búfer las posibles apariciones de mi paquete? Por ejemplo:Usando LINQ para buscar una matriz de bytes para todas las subcapas que se inician/detienen con cierto byte

Di mi paquete delimitador es 0xFF y tengo una matriz como tales

{ 0x00, 0xFF, 0x02, 0xDA, 0xFF, 0x55, 0xFF, 0x04 } 

¿Cómo puedo crear una función/LINQ DECLARACIÓN que devuelve todos los subconjuntos que empiezan y terminan con el delimitador (casi como un correlador deslizante con comodines)?

La muestra devolvería los siguientes matrices 3:

{0xFF, 0x02, 0xDA, 0xFF}, {0xFF, 0x55, 0xFF}, and 
{0xFF, 0x02, 0xDA, 0xFF, 0x55, 0xFF} 
+1

me hubiera esperado que las matrices volvió a ser {} {0x00 0x02, 0xDA} y {} 0x55, pero aparte de eso, si esto representa el tamaño de la matriz real aproximado, y el delimitador es sólo una byte long, ¿por qué no ir por un simple bucle? Probablemente superará a linq. –

+0

@Willem: Sí, estoy de acuerdo en todos los puntos. – Amy

+1

@Willem van Rumpt: redacté el título de la pregunta con LINQ porque generalmente recibe visitas en SO, pero en la pregunta especifiqué "function/LINQ-statement" porque era consciente de la posibilidad muy real de que una solución LINQ elegante fuera más probablemente tome más tiempo que un ciclo. Estoy abierto a cualquiera, solo quiero que todo el que viene detrás de mí en un año sepa lo que hice además del paquete "// Decode aquí" en el código. –

Respuesta

5

Así es como se puede hacer esto utilizando LINQ ...

int[] list = new int[] { 0x00, 0xFF, 0x02, 0xDA, 0xFF, 0x55, 0xFF, 0x04 }; 
int MAXLENGTH = 10; 

var windows = list.Select((element, i) => list.Skip(i).Take(MAXLENGTH)); 
var matched = windows.Where(w => w.First() == 0xFF); 
var allcombinations = matched.SelectMany(m => Enumerable.Range(1, m.Count()) 
      .Select(i => m.Take(i)).Where(x => x.Count() > 2 && x.Last() == 0xFF)); 

o el uso de índices:

int length = list.Count(); 
var indexes = Enumerable.Range(0, length) 
       .SelectMany(i => Enumerable.Range(3, Math.Min(length-i, MAXLENGTH)) 
       .Select(count => new {i, count})); 
var results = indexes.Select(index => list.Skip(index.i).Take(index.count)) 
       .Where(x => x.First() == 0xFF && x.Last() == 0xFF); 
+0

Muy agradable. Me gusta. – Amy

+0

de sus dos variantes, la primera (no usando índices) se realizó más rápido en mis pruebas –

0

no intentaría hacer esto con LINQ lo que aquí es un método regular que devuelve el mismo resultado que quería.

public List<byte[]> GetSubArrays(byte[] array, byte delimeter) 
{ 
    if (array == null) throw new ArgumentNullException("array"); 

    List<byte[]> subArrays = new List<byte[]>(); 

    for (int i = 0; i < array.Length; i++) 
    { 
    if (array[i] == delimeter && i != array.Length - 1) 
    { 
     List<byte> subList = new List<byte>() { delimeter }; 

     for (int j = i+1; j < array.Length; j++) 
     { 
     subList.Add(array[j]); 
     if (array[j] == delimeter) 
     { 
      subArrays.Add(subList.ToArray()); 
     } 
     } 
    } 
    } 

    return subArrays; 
} 

Si tiene que ser una expresión lambda en el lugar, a continuación, sólo cambiar la primera línea de (byte[] array, byte delimeter) => (sin los modificadores de método y nombre) y lo llaman de esa manera.

+0

El problema con esto es que el consumo de memoria puede ser muy grande si la matriz original es grande y hay muchos delimitadores. Ver mi respuesta, que usa un método de extensión. – Amy

15

Si bien la respuesta de Trystan es técnicamente correcta, está haciendo muchas copias de la matriz original de una vez. Si la matriz de inicio es grande y tiene un grupo de delimitadores, eso se hace enorme rápidamente. Este enfoque evita el consumo masivo de memoria al usar solo la matriz original y una matriz para el segmento actual que se evalúa.

public static List<ArraySegment<byte>> GetSubArrays(this byte[] array, byte delimeter) 
{ 
    if (array == null) throw new ArgumentNullException("array"); 

    List<ArraySegment<byte>> retval = new List<ArraySegment<byte>>(); 

    for (int i = 0; i < array.Length; i++) 
    { 
     if (array[i] == delimeter) 
     { 
      for (int j = i + 1; j < array.Length; j++) 
      { 
       if (array[j] == delimeter) 
       { 
        retval.Add(new ArraySegment<byte>(array, i + 1, j - i - 1)); 
       } 
      } 
     } 
    } 

    return retval; 
} 

Puede ser utilizado como tal:

static void Main(string[] args) 
{ 
    byte[] arr = new byte[] { 0x00, 0xFF, 0x02, 0xDA, 0xFF, 0x55, 0xFF, 0x04 }; 
    List<ArraySegment<byte>> retval = GetSubArrays(arr, 0xFF); 

    // this also works (looks like LINQ): 
    //List<ArraySegment<byte>> retval = arr.GetSubArrays(0xFF); 

    byte[] buffer = new byte[retval.Select(x => x.Count).Max()]; 
    foreach (var x in retval) 
    { 
     Buffer.BlockCopy(x.Array, x.Offset, buffer, 0, x.Count); 
     Console.WriteLine(String.Join(", ", buffer.Take(x.Count).Select(b => b.ToString("X2")).ToArray())); 
    } 


    Console.ReadLine(); 
} 
+3

No sabía sobre el ArraySegment. Muy genial. –

+4

Buen uso del poco conocido ArraySegment. –

+0

Creo que 'if (array [j] == delimeter)' causaría un error fuera de límites si la matriz termina con el delímetro. –

0

Usted puede hacer esto mediante un agregador de LINQ, pero es mucho menos evidente que las otras soluciones sugeridas aquí, también tuvo que añadir un caso especial para cubrir extendiendo arreglos ya completados como sugirió arriba.

byte[] myArray = new byte[] { 0x00, 0xFF, 0x02, 0xDA, 0xFF, 0x55, 0xFF, 0x04 }; 
var arrayList = myArray.Aggregate(
       new { completedLists = new List<List<byte>>(), 
         activeList = new List<byte>() }, 
       (seed, s) => 
       { 
        if (s == 0xFF) 
        { 
         if (seed.activeList.Count == 0) 
         { 
          seed.activeList.Add(s); 
         } 
         else 
         { 
          seed.activeList.Add(s); 
          var combinedLists = new List<List<byte>>(); 

          foreach (var l in seed.completedLists) 
          { 
           var combinedList = new List<byte>(l); 
           combinedList.AddRange(seed.activeList.Skip(1)); 
           combinedLists.Add(combinedList); 
          } 
          seed.completedLists.AddRange(combinedLists); 
          seed.completedLists.Add(new List<byte>(seed.activeList)); 
          seed.activeList.Clear(); 
          seed.activeList.Add(s); 
         } 
        } 
        else 
        { 
         if (seed.activeList.Count > 0) 
          seed.activeList.Add(s); 
        } 
        return seed; 
       }).completedLists; 
+0

No se compila en esta línea: seed.completedLists.Add (new List (seed.activeList)); – Amy

+0

Lo cambié de la lista a la lista y olvidé esa línea. Apenas una razón para votar abajo, especialmente porque es tan obvio. De todos modos, arreglado ahora. – BrokenGlass

2

Si realmente desea utilizar LINQ, esto debería funcionar bastante rápido (incluso si no es tan rápido como un bucle for old-old):

public static IEnumerable<T[]> GetPackets<T>(this IList<T> buffer, T delimiter) 
{ 
    // gets delimiters' indexes 
    var delimiterIdxs = Enumerable.Range(0, buffer.Count()) 
            .Where(i => buffer[i].Equals(delimiter)) 
            .ToArray(); 

    // creates a list of delimiters' indexes pair (startIdx,endIdx) 
    var dlmtrIndexesPairs = delimiterIdxs.Take(delimiterIdxs.Count() - 1) 
             .SelectMany(
                (startIdx, idx) => 
                delimiterIdxs.Skip(idx + 1) 
                    .Select(endIdx => new { startIdx, endIdx }) 
                ); 
    // creates array of packets 
    var packets = dlmtrIndexesPairs.Select(p => buffer.Skip(p.startIdx) 
                 .Take(p.endIdx - p.startIdx + 1) 
                 .ToArray()) 
            .ToArray(); 

    return packets; 
} 
0

Aunque la estructura del delimitador parece un poco vaga, no usaría linq y haría algo como a continuación (no se realizaron pruebas exhaustivas). Devolverá todos los subconjuntos (de bytes rodeados por el delimitador), sin incluir el delimitador (se da de todos modos, ¿por qué incluirlo?). Tampoco devuelve la unión de los resultados, pero eso siempre se puede ensamblar manualmente.

public IEnumerable<byte[]> GetArrays(byte[] data, byte delimiter) 
{ 
    List<byte[]> arrays = new List<byte[]>(); 
    int start = 0; 
    while (start >= 0 && (start = Array.IndexOf<byte>(data, delimiter, start)) >= 0) 
    { 
     start++; 
     if (start >= data.Length - 1) 
     { 
      break; 
     } 

     int end = Array.IndexOf<byte>(data, delimiter, start); 
     if (end < 0) 
     { 
      break; 
     } 

     byte[] sub = new byte[end - start]; 
     Array.Copy(data, start, sub, 0, end - start); 
     arrays.Add(sub); 
     start = end; 
    } 

    return arrays; 
} 
+0

Esto no devuelve el conjunto de resultados esperado, que puede contener segmentos anidados (tercer resultado en la pregunta). – Amy

+0

@yodaj: Aaaaaaaah! Pensé que el tercer resultado era la unión de cada resultado individual. Me preguntaba por qué querría devolverlo todo :) Es por eso que escribí "Tampoco devuelve la unión de los resultados, pero eso siempre se puede ensamblar manualmente". –

+0

Sí. Las uniones podrían ensamblarse bastante fácilmente a partir de sus resultados utilizando la recursión. – Amy

Cuestiones relacionadas