Подтвердить что ты не робот

Использование LINQ для поиска массива байтов для всех подмассивов, которые запускаются/останавливаются с помощью определенного байта

Я имею дело с приложением COM-порта, и у нас есть определенная структура пакетной длины переменной длины, с которой я разговариваю с микроконтроллером. Пакет имеет разделители для байтов запуска и остановки. Проблема в том, что иногда буфер чтения может содержать посторонние символы. Кажется, что я всегда получаю весь пакет, просто дополнительную болтовню до/после фактических данных. Поэтому у меня есть буфер, в который я добавляю данные, когда новые данные получаются из COM-порта. Каков наилучший способ поиска этого буфера для любых возможных случаев моего пакета? Например:

Скажите, что мой разделитель пакетов 0xFF, и у меня есть массив как таковой

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

Как создать функцию /LINQ -statment, которая возвращает все подмассивы, которые начинаются и заканчиваются разделителем (почти как скользящий коррелятор с подстановочными знаками)?

Образец вернет следующие 3 массива:

{0xFF, 0x02, 0xDA, 0xFF}, {0xFF, 0x55, 0xFF}, and
{0xFF, 0x02, 0xDA, 0xFF, 0x55, 0xFF}
4b9b3361

Ответ 1

Здесь вы можете сделать это с помощью 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));

Или используя индексы:

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

Ответ 2

В то время как ответ Trystan технически корректен, он делает много копий исходного массива сразу. Если стартовый массив большой и имеет кучу разделителей, это становится очень быстрым. Такой подход позволяет избежать массового потребления памяти, используя только исходный массив и массив для текущего оцениваемого сегмента.

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

Может использоваться как таковой:

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

Если вы действительно хотите использовать LINQ, это должно работать довольно быстро (даже если это не так быстро, как хороший старый цикл):

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

Ответ 4

Я бы не попытался сделать это с помощью linq, поэтому здесь обычный метод, который возвращает тот же результат, что и вы.

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

Если это должно быть выражение на лямбданом на месте, просто измените первую строку на (byte[] array, byte delimeter) => (без модификаторов и имени метода) и назовите ее таким образом.

Ответ 5

Вы можете сделать это, используя агрегатор Linq, но он гораздо менее прямолинейный, чем другие предлагаемые здесь решения, также должен был добавить специальный случай, чтобы охватить уже завершенные массивы, как вы сказали выше.

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;

Ответ 6

Хотя структура разделителя кажется немного расплывчатой, я бы не использовал linq и не делал что-то вроде ниже (без обширных тестов). Он вернет все подмножества (байтов , окруженных разделителем), без включения разделителя (это все равно, зачем его включать?). Он также не возвращает объединение результатов, но это всегда можно собрать вручную.

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