Освой Arduino играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Библиотека cpplinq

avg - вычисляем среднее значение всех элементов
distinct - убираем дубликаты из массива
take - берём нужные элементы массива
skip - пропускаем элементы массива
Комбинируем take и skip
reverse - переворачиваем массив
first - находим первый элемент по условию - находим первое чётное число
last_or_default - находим последний элемент по условию
where - условия (чётные числа, больше 10 и т.д)
Цепочка операторов
min/max - находим наименьший и наибольший элемент в массиве
count - считаем число найденных элементов
all - проверяем все элементы на соответствие условию
any - хотя бы один элемент
pairwise - каждой твари по паре
concat - склеиваем два массива
union_with - объединение множеств
except - оставляем уникальные элементы
intersect_with - оставляем только одинаковые элементы

LINQ for C++ cpplinq является библиотекой для C++ 11. Предназначена для манипуляций с последовательностями. Идея была взята из популярной технологии Microsoft LINQ для C#. Удобно использовать на платах ESP32.

Исходники библиотеки можно посмотреть на Гитхабе.

Сначала установим библиотеку. Один из вариантов - скачать архив с сайта https://archive.codeplex.com/?p=cpplinq (кнопка "download archive") и найти в нём папку cpplinq.zip\sourceCode\cpplinq\CppLinq, который содержит единственный файл cpplinq.hpp. Либо можно скачать архив с нужной папкой с моего сайта. Распакованную папку с файлом поместить в папку libraries вашего дистрибутива Arduino. После этого библиотека будет готова к работе.

Для начала работы импортируем библиотеку и прописываем пространство имён.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);
}

void loop() {}

Минимальный код написан. Осталось вызвать функцию библиотеки для работы с последовательностями.

avg - вычисляем среднее значение всех элементов

Оператор avg позволяет вычислить среднее значение всех элементов.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int ints[] = {15, 8, 4, 16, 23, 42};

  int result = from_array(ints)
               >> avg();
 
  Serial.print("Average: ");
  Serial.println(result);

}

void loop() {}

// Результат 18

Усложним задачу, добавив дополнительное условие - считать только элементы, которые меньше 10.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int ints[] = {42, 8, 4, 16, 23, 15};

  int result = from_array(ints)
               >> where([](int i) {return i < 10;})
               >> avg();
 
  Serial.print("Average: ");
  Serial.println(result);
}

void loop() {}

// Результат
// 6

distinct - убираем дубликаты из массива

Оператор distinct позволяет избавиться от дубликатов.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int ints[] = {1, 1, 1, 2, 2, 3, 4, 5, 6, 7, 7, 7, 7, 8, 9, 9};

  auto result = from_array(ints)
                >> distinct()
                >> to_vector();

  for (int i = 0; i < result.size(); ++i) {
    Serial.print(result[i]);
    Serial.print("|");
  }
}

void loop() {}

// Результат
// 1|2|3|4|5|6|7|8|9|

take - берём нужные элементы массива

Если нам не нужны все элементы массива, а только несколько первых элементов, то вызываем оператор take с указанием числа элементов. Оставим пять элементов, остальные нас не интересуют.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int ints[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

  auto result = from_array(ints)
                >> take(5)
                >> to_vector();

  for (int i = 0; i < result.size(); ++i) {
    Serial.print(result[i]);
    Serial.print("|");
  }
}

void loop() {}

// Результат
// 1|2|3|4|5|

skip - пропускаем элементы массива

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


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int ints[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

  auto result = from_array(ints)
                >> skip(4) // пропустим первые четыре элемента
                >> to_vector();

  for (int i = 0; i < result.size(); ++i) {
    Serial.print(result[i]);
    Serial.print("|");
  }
}

void loop() {}

// Результат
// 5|6|7|8|9|

Комбинируем take и skip

Если нужно выбрать элементы из середины, то уместно выбрать сразу два оператора - skip и take. Сначала отсекаем первые три элемента массива, затем у оставшихся элементов берём три элемента, оставляя за бортом последние элементы.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int ints[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

  auto result = from_array(ints)
               >> skip(3)
               >> take(3)
               >> to_vector();

  for (int i = 0; i < result.size(); ++i) {
    Serial.print(result[i]);
    Serial.print("|");
  }
}

void loop() {}

// Результат
// 4|5|6|

reverse - переворачиваем массив

Перевернуть массив можно с помощью оператора reverse.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int ints[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

  auto result = from_array(ints)
                >> reverse()
                >> to_vector();

  for (int i = 0; i < result.size(); ++i) {
    Serial.print(result[i]);
    Serial.print("|");
  }
}

void loop() {}

// Результат
9|8|7|6|5|4|3|2|1|

first - находим первый элемент по условию - находим первое чётное число

Находим первое число массива при помощи оператора first. Создадим массив чисел {15, 8, 4, 16, 23, 42}. Воспользуемся функцией from_array(), которая создаст из массива последовательность, к которой можно применить запрос. После этого используем оператор >>. Затем указываем нужный запрос, в нашем случае, запрос first, получающий первый элемент коллекции.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int ints[] = {15, 8, 4, 16, 23, 42};

  int result = from_array (ints)
          >> first();

  Serial.println(result);
}

void loop() {}

// Результат
// 15

Усложним задачу. Найдём среди элементов массива первое чётное число. При использовании оператора first, задаём условие деления на 2 без остатка (чётное число). Если в коллекции не будет найдено чётное число, то получим исключение. Чтобы избежать этой ситуации, можно вызвать другую функцию first_or_default, которая вернёт значение по умолчанию, если нужный элемент не будет найден.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int ints[] = {15, 8, 4, 16, 23, 42};

  int result = from_array (ints)
          >> first([](int i) { return i % 2 == 0; });

  Serial.println(result);
}

void loop() {}

// Результат
// 8

last_or_default - находим последний элемент по условию

Если нас интересует не первый, а последний элемент по заданному условию, то вызываем оператор last_or_default. Напишем два варианта - последнее чётное число и последнее число, большее чем 20.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int ints[] = {42, 8, 4, 16, 23, 15};

  int result1 = from_array(ints)
               >> last_or_default([](int i){return i%2 == 0;});
 
  int result2 = from_array(ints)
               >> last_or_default([](int i){return i > 20;});
                 
  Serial.println(result1);                  
  Serial.println(result2);  
}

void loop() {}

// Результат
// 16
// 23

where - условия (чётные числа, больше 10 и т.д)

С помощью оператора where можно задавать различные условия.

В одном из примеров мы получили первое чётное число из массива. Теперь рассмотрим задачу получения всех чётных чисел. Снова создаём последовательность из массива через from_array(). Затем вызываем where(), который перебирает все элементы последовательности и сравнивает их с условием деления без остатка. Полученный результат из массива конвертируем в объект C++ vector при помощи to_vector(). Теперь мы можем вывести результат на экран.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int ints[] = {15, 8, 4, 16, 23, 42};

  auto result = from_array(ints)
  >> where([](int i) {
    return i % 2 == 0;
  })
      >> to_vector();

  for (int i = 0; i < result.size(); ++i) {
    Serial.print(result[i]);
    Serial.print("|");
  }
}

void loop() {}

// Выводится
// 8|4|16|42|

Более сложный пример с применением класса. Допустим, у нас есть класс Cat и мы хотим получить список котов, чей возраст превышает 5 лет.


#include "cpplinq.hpp"

using namespace cpplinq;

class Cat {

  public:
    int id;
    int age;

    Cat (int t_id, int t_age) {
      id = t_id;
      age = t_age;
    }
};


void setup() {
  Serial.begin(115200);

  Cat cats[] = {
    Cat(1, 10),
    Cat(2, 14),
    Cat(3, 2),
    Cat(4, 3),
    Cat(4, 8),
    Cat(5, 5),
  };

  auto result = from_array(cats)
      >> where([](const Cat cat) { return cat.age > 5; })
      >> orderby([](const Cat cat) { return cat.age; })
      >> to_vector();

  for (int i = 0; i < result.size(); ++i) {
    Serial.printf("{id: %d age: %d}\n", result[i].id, result[i].age);
  }
}

void loop() {}

// Результат
/*
{id: 4 age: 8}
{id: 1 age: 10}
{id: 2 age: 14}
*/

Цепочка операторов

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


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int ints[] = {15, 8, 4, 16, 23, 42};

  int result = from_array(ints)
      >> where([](int i) { return i % 2 == 0; })
      >> where([](int i) { return i < 10; })
      >> max();

  Serial.println(result);
}

void loop() {}

// Результат
// 8

min/max - находим наименьший и наибольший элемент в массиве

Найти наименьший и наибольший элемент массива можно через готовые операторы min и max.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int ints[] = {15, 8, 4, 16, 23, 42};

  int maxVal = from_array(ints)
               >> max();

  int minVal = from_array(ints)
               >> min();

  Serial.println(minVal);
  Serial.println(maxVal);
}

void loop() {}

// Выводится
// 4
// 42

count - считаем число найденных элементов

С помощью оператора count мы можем посчитать число элементов, которые удовлетворяют заданному условию. Попробуем узнать число элементов, которые больше 10.

Мы можем решить задачу двумя способами. В первом случае воспользуемся помощью оператора where, во втором случае обойдёмся только одним оператором count.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int ints[] = {15, 8, 4, 16, 23, 42};

  int result = from_array(ints)
    >> where([](int i) {
    return i > 10;
  })
      >> count();

  int result2 = from_array(ints)
  >> count([](int i) {
    return i > 10;
  });

  Serial.print("Первый вариант: ");
  Serial.println(result);

  Serial.print("Второй вариант: ");
  Serial.println(result2);
}

void loop() {}

В обоих случаях выводится один результат - в нашем массиве 4 элемента, которые больше 10.

all - проверяем все элементы на соответствие условию

Мы можем задать условие и проверить, соответствуют ли ему ВСЕ элементы. Если соответствуют, то возвращается 1 (true), если хотя бы один элемент не проходит по условию, то вернётся 0 (false).

Все элементы массива меньше 100? Проверим.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int ints[] = {15, 8, 4, 16, 23, 42};

  bool result = from_array(ints)
      >> all([](int i) { return i < 100; });

  Serial.print("Все элементы меньше 100?: ");
  Serial.println(result);

}

void loop() {}

// Результат
// Все элементы меньше 100?: 1

any - хотя бы один элемент

Оператор any позволяет узнать, есть ли хотя бы один элемент, который удовлетворяет заданному условию. Проверим, есть ли в нашем массиве число, которое больше 100.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int ints[] = {42, 8, 4, 16, 23, 15};

  bool result = from_array(ints)
      >> any([](int i) { return i > 100; });

  Serial.println(result);
}

void loop() {}

// Результат
// 0 (т.е. false)

pairwise - каждой твари по паре

Оператор pairwise позволяет сгруппировать элементы массива в пары. Каждый элемент массива получает в пару следующий за ним элемент. Так как последний элемент не может получить себе пару, то мы ограничим цикл на одну единицу меньше.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int array[] = {1, 2, 3, 4};

  auto result = from_array(array)
                >> pairwise()
                >> to_vector();

  for (int i = 0; i < result.size(); i++) {
    Serial.print("(");
    Serial.print(result[i].first);
    Serial.print(",");
    Serial.print(result[i].second);
    Serial.print(")");
  }
}

void loop() {}

// Результат
(1,2)(2,3)(3,4)

Операторы для двух массивов

Рассмотрим примеры, где используются два массива.

concat - склеиваем два массива

"Кошачий" оператор concat позволяет склеить два массива и получить новый массив со всеми элементами двух массивов.

Как и в предыдущих примерах, сначала получим последовательности из массивов. Затем к первой последовательности присоединяем вторую последовательность при помощи оператора concat. Полученный результат конвертируем в vector при помощи to_vector - это позволит нам пройтись по элементам в цикле


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int array1[] = {15, 8, 4};
  int array2[] = {16, 23, 42};

  auto result = from_array(array1)
                >> concat(from_array(array2))
                >> to_vector();

  for (int i = 0; i < result.size(); ++i) {
    Serial.print(result[i]);
    Serial.print("|");
  }
}

void loop() {}

// Результат
// 15|8|4|16|23|42|

union_with - объединение множеств

Оператор union_with объединяет два массива, оставляя только уникальные элементы. Результат зависит от порядка объединения.

Создадим два массива и объединим сначала второй массив с первым, а потом первый со вторым.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int array1[] = {1, 2, 3, 4};
  int array2[] = {3, 4, 5, 6};

  auto result = from_array(array1)
                >> union_with(from_array(array2))
                >> to_vector();

  Serial.println("Result 1:");
  for (int i = 0; i < result.size(); ++i) {
    Serial.print(result[i]);
    Serial.print("|");
  }

  auto result2 = from_array(array2)
                 >> union_with(from_array(array1))
                 >> to_vector();

  Serial.println("\nResult 2:");
  for (int i = 0; i < result2.size(); ++i) {
    Serial.print(result2[i]);
    Serial.print("|");
  }
}

void loop() {}

В первом случае получим 1|2|3|4|5|6|, во втором - 3|4|5|6|1|2|.

except - оставляем уникальные элементы

Оператор except оставляет из первого массива только те элементы, которые не встречаются во втором массиве.

Для примера создадим два массива, в котором некоторые элементы совпадают. Сначала применим оператор к первому массиву, проверяя второй, а затем наоборот.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int array1[] = {1, 2, 3, 4};
  int array2[] = {3, 4, 5, 6};

  auto result = from_array(array1)
                >> except(from_array(array2))
                >> to_vector();

  Serial.println("Result 1:");
  for (int i = 0; i < result.size(); ++i) {
    Serial.print(result[i]);
    Serial.print("|");
  }

  auto result2 = from_array(array2)
                 >> except(from_array(array1))
                 >> to_vector();

  Serial.println("\nResult 2:");
  for (int i = 0; i < result2.size(); ++i) {
    Serial.print(result2[i]);
    Serial.print("|");
  }
}

void loop() {}

В первом случае останутся только числа 1 и 2. Во втором случае - 5 и 6.

intersect_with - оставляем только одинаковые элементы

С помощью оператора intersect_with оставляем только те элементы, которые встречаются в двух массивах.


#include "cpplinq.hpp"

using namespace cpplinq;

void setup() {
  Serial.begin(115200);

  int array1[] = {1, 2, 3, 4};
  int array2[] = {3, 4, 5, 6};

  auto result = from_array(array1)
                >> intersect_with(from_array(array2))
                >> to_vector();

  for (int i = 0; i < result.size(); ++i) {
    Serial.print(result[i]);
    Serial.print("|");
  }

}

void loop() {}

Получим два элемента 3|4|.

Дополнительные материалы

Реклама