Привет ! Меня зовут Ксавье Жувено, и вот третья часть длинной серии статей The Modern C++ Challenge. В этой статье я собираюсь объяснить, как я решил третью проблему на C++ и как я интегрировал это решение в проект Android.

Цель этой третьей задачи проста. Мы должны вычислить наименьший общий множитель для такого количества входных данных, которое пользователь хочет дать, и мы выводим его пользователю. Решение будет вычислено на C++, а интерфейс для получения пользовательского ввода и отображения результата будет обрабатываться с помощью Android Studio Framework.

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

Решение на С++

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

К счастью для нас, глядя на стандарт C++17, вы можете найти функцию std::lcm, которая вычисляет наименьший общий множитель двух чисел. Итак, все, что нам нужно сделать, это использовать этот метод для создания нашей собственной функции, способной принимать столько чисел, сколько нам нужно.

template <class InputIterator>
constexpr auto my_lcm (InputIterator first, InputIterator last) { 
  return std::accumulate(first, last, 1, [](const auto& first, const auto& second){
    return std::lcm(first, second);
  });
}

В этой функции мы используем std::accumulate, чтобы повторно использовать результат предыдущего вычисления std::lcm в лямбда-функции. Эта функция принимает два итератора в качестве параметров, чтобы иметь возможность иметь столько входных данных, сколько мы хотим. Вместо итераторов мы могли бы, например, передать std::vector.

Интерфейс пользовательского интерфейса в Android Studio

Создание функции, которая может принимать столько параметров, сколько мы хотим, на C++ — это одна вещь, которую можно легко сделать. Предоставление пользователю возможности делать это на своем телефоне требует немного подумать о пользовательском интерфейсе. Действительно, мы можем отобразить пользователю бесконечность EditText, чтобы удовлетворить наше требование. Мы должны использовать другое решение.

Решение, которое я придумал, требует одного EditText и двух кнопок: кнопки «Добавить» и кнопки «Очистить».

EditText позволяет пользователю указать свой ввод.

Кнопка «Добавить» позволяет ему интегрировать число, введенное в EditText, для расчета наименьшего общего множителя. Каждый раз, когда добавляется новое число, мы вычисляем наименьший общий множитель со всеми числами, добавленными до этого момента, и отображаем результат.

Кнопка «Очистить» позволяет пользователю сбросить наименьший общий множитель, удалив все сохраненные числа, добавленные в память до сих пор.

С помощью этого решения пользователь может указать столько входных данных, сколько захочет 🙂

Использование собственного кода C++

Для этой проблемы я решил сохранить пользовательский ввод в активности Java и отправить список пользовательского ввода в код C++ для вычисления результата. Я мог бы передать в C++ все входные данные в том виде, в каком они были введены пользователем, но я хотел поэкспериментировать с преобразованием массивов из Java в C++.

В Java я сохранил пользовательский ввод в List:

EditText et = findViewById(R.id.input_number);
if(et.getText().length() == 0) { return; }
userInputs.add(Integer.parseInt(et.getText().toString()));

Это была легкая часть. Действительно, если мы хотим получить массив целых чисел в коде C++, нам нужно преобразовать нашу List в структуру с именем jintArray. И после некоторого поиска в Google я нашел этот однострочный код, позволяющий мне сделать именно это:

userInputs.stream().mapToInt(i->i).toArray()

Эта строка выполняет много преобразований, чтобы использовать массив целых чисел. От userInputs, который является List, до Stream с [потоковым методом] (java Collection::stream document). Затем мы преобразуем Stream в IntStream из-за [метода mapToInt]( документация java Stream::mapToInt). И, наконец, из IntStream в массив int с помощью [метода toArray](документация java IntStream::toArray).

Но все это Java-сторона истории! Действительно, мы должны получить эти элементы в C++ сейчас, чтобы иметь возможность передать их нашей функции. А это выглядит примерно так:

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_themoderncppchallenge_ProblemStreamLcm(JNIEnv *env, jobject /* this */, jintArray arr) {
  jsize size = env->GetArrayLength( arr );
  std::vector<jint> input( size );
  env->GetIntArrayRegion( arr, jsize{0}, size, &input[0] );
  auto result = my_lcm(std::begin(input), std::end(input));
  return env->NewStringUTF(std::to_string(result).c_str());
}

В этой части программы мы получаем jintArray (то есть массив целых чисел, преобразованный из List) и преобразуем его в std::vector, чтобы передать его нашему методу. Для этого нам нужно использовать некоторые методы из объекта JNIEnv.

С помощью env-&gt;GetArrayLength( arr ) мы получаем размер массива, который мы сначала используем для создания std::vector с уже зарезервированным нужным размером. Затем идет инструкция env-&gt;GetIntArrayRegion( arr, jsize{0}, size, &amp;input[0] );, которая позволяет нам скопировать область массива в родной буфер, наш std::vector.

Все, что нам нужно сделать после этого, это вызвать наш метод и вернуть результат, чтобы отобразить его в нашем приложении 🙂

Вывод

Итак, у нас есть приложение, которое может решить первые три задачи The Modern C++ Challenge.

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

Спасибо всем за прочтение этой статьи, и до моей следующей статьи, хорошего дня 😉

Интересные ссылки

Первоначально опубликовано на http://10xlearner.com 8 апреля 2020 г.