Освой Arduino играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
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 позволяет вычислить среднее значение всех элементов.
#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 позволяет избавиться от дубликатов.
#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 с указанием числа элементов. Оставим пять элементов, остальные нас не интересуют.
#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|
В противовес оператору 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|
Если нужно выбрать элементы из середины, то уместно выбрать сразу два оператора - 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.
#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. Создадим массив чисел {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. Напишем два варианта - последнее чётное число и последнее число, большее чем 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 можно задавать различные условия.
В одном из примеров мы получили первое чётное число из массива. Теперь рассмотрим задачу получения всех чётных чисел. Снова создаём последовательность из массива через 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.
#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 мы можем посчитать число элементов, которые удовлетворяют заданному условию. Попробуем узнать число элементов, которые больше 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.
Мы можем задать условие и проверить, соответствуют ли ему ВСЕ элементы. Если соответствуют, то возвращается 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 позволяет узнать, есть ли хотя бы один элемент, который удовлетворяет заданному условию. Проверим, есть ли в нашем массиве число, которое больше 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 позволяет сгруппировать элементы массива в пары. Каждый элемент массива получает в пару следующий за ним элемент. Так как последний элемент не может получить себе пару, то мы ограничим цикл на одну единицу меньше.
#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. Полученный результат конвертируем в 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 объединяет два массива, оставляя только уникальные элементы. Результат зависит от порядка объединения.
Создадим два массива и объединим сначала второй массив с первым, а потом первый со вторым.
#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 оставляет из первого массива только те элементы, которые не встречаются во втором массиве.
Для примера создадим два массива, в котором некоторые элементы совпадают. Сначала применим оператор к первому массиву, проверяя второй, а затем наоборот.
#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 оставляем только те элементы, которые встречаются в двух массивах.
#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|.