БлогNot. C++: алгоритмы одной строкой

C++: алгоритмы одной строкой

Конечно, (почти) любая аналитическая формула может быть представлена подобной однострочной функцией, поэтому суммы элементов прогрессий, площади и объёмы, выражения для расчёта физических величин и т.п. нас интересовать не будут, а только такие однострочники, которые либо показывают синтаксические особенности языка, либо демонстрируют характерные для него нетривиальные способы обращения с данными.

В отношении C++, это будут, по сути, рекурсия, побитовые операции, тернарная операция и шаблоны.

Здесь публикуются законченные консольные программки, которые проверялись в актуальной версии Visual Studio 2019. Все примеры следует расценивать больше как спорт и упражнения в красоте программирования, чем как скучные практические рекомендации. Хотя, даже при развлечении нужно помнить о том, что:

  • для больших числовых значений рекурсия - не лучший подход, потому что чревата переполнением стека во время выполнения программы;
  • расчёты с накоплением рассчитываемых величин в целых числах быстро дают переполнение значения, а в вещественных - погрешность результатов;
  • при операциях с битами всегда следует иметь в виду sizeof (количество занимаемой памяти) для наших типов данных.

Если знаете ещё интересные функции-однострочники - пишите, я их с удовольствием опубликую.

1. Целые числа, рекурсия

1.1. Наибольший общий делитель, он же НОД или GCD.

#include <iostream>

int GCD (int a, int b) {  return b ? GCD(b, a % b) : a; }

int main() {
 std::cout << GCD(36,48); //12
 return 0;
}

1.2. Наименьшее общее кратное, оно же НОК, LCM.

#include <iostream>

int GCD (int a, int b) {  return b ? GCD(b, a % b) : a; }
int LCM (int a, int b) { return a / GCD(a, b) * b; }

int main() {
 std::cout << LCM(36,48); //144
 return 0;
}

Строго говоря, это не однострочник, так как LCM считается через CGD, но можно сделать и так:

#include <iostream>

int LCM (int a, int b) {
 return (b < 1 ? (b ? LCM(b, a % b) : a) : (a / -LCM(-b, -a % b) * b));
}

int main() {
 std::cout << LCM(36,48); //144
 return 0;
}

1.3. Факториал натурального числа.

#include <iostream>

long long unsigned fact (unsigned n) {
 return n ? n * fact(n - 1) : 1;
}

int main() {
 std::cout << fact(20); //2432902008176640000
 //на платформе x64 уже 21 даст значение больше максимально возможного 
 //для long long unsigned (18446744073709551615) и "мусор" в выводе
 return 0;
}

1.4. Сумма цифр целого числа.

#include <iostream>

int sumD (int a) {
 return (!a) ? 0 : (a % 10 + sumD(a / 10));
}

int main() {
 std::cout << sumD(12345); //15
 return 0;
}

Для отрицательного числа ответ выведется со знаком "-".

1.5. Число Фибоначчи с номером n (n == 1 для первой единицы).

#include <iostream>

int fibN (int n) {
 return (n <= 2) ? 1 : (fibN(n - 1) + fibN(n - 2));
}

int main() {
 std::cout << fibN (7); //15
 return 0;
}

1.6. Следующее число Фибоначчи (для вычисления в цикле).

#include <iostream>

int fibNext (int& n1, int& n2) {
 return n1 = (n2 += n1) - n1;
}

int main() {
 int n1 = 0, n2 = 1;
 for (int n = 0; n < 10; n++)
  std::cout << fibNext(n1,n2) << ' '; 
 //1 1 2 3 5 8 13 21 34 55
 return 0;
}

1.7. Проверка, является ли натуральное n числом Мерсенна.

#include <iostream>

bool Mersenne (unsigned  n) {
 return !(n & (n + 1));
}

int main() {
 std::cout << Mersenne(7); //1
 return 0;
}

1.8. Натуральная степень целого числа. Считает, что a0 == 1.

#include <iostream>

int pow (int a, unsigned n) { return (!n) ? 1 : a * pow(a, n - 1); }

int main() {
 std::cout << pow(-3,5); //-243
 return 0;
}

1.9. Быстрый алгоритм возведения целого числа в натуральную степень.

#include <iostream>

int pow (int a, unsigned n) {
 return (!n) ? 1 : ((n & 1) ? a : 1) * pow(a * a, n / 2);
}

int main() {
 std::cout << pow(-3, 5); //-243
 return 0;
}

В отличие от предыдущего алгоритма линейной сложности, имеет логарфмическую сложность.

2. Тернарная операция

2.1. Максимум и минимум из двух значений (шаблоны функций).

#include <iostream>

template <typename T> T max (T a, T b) {
 return (a > b) ? a : b;
}

template <typename T> T min (T a, T b) {
 return (a > b) ? b : a;
}

int main() {
 std::cout << max(-7,-3) << ' ' << min(0,4); //-3 0
 std::cout << std::endl << max(-2.5, -2.51); //-2.5
 return 0;
}

2.2. Шаблон для функции сравнения двух чисел (если a<b, вернёт отрицательное значение, если a и b равны, то ноль, иначе положительное значение.

#include <iostream>

template <typename T> int cmp (const T a, const T b) {
 return (a - b);
}

int main() {
 std::cout << cmp(-7,-3) << ' ' << cmp(1,1); //-4 0
 return 0;
}

2.3. Знак числа со значением -1, 0 или 1, шаблон функции.

#include <iostream>

template<typename T> int sign (const T& a) {
 return (a < 0 ? -1 : a > 0 ? 1 : 0);
}

int main() {
 std::cout << sign(-24) << ' ' << sign(1.5) << ' ' << sign(0.0); //-1 1 0
 return 0;
}

2.4. Найти бит с номером n (нумерация с нуля слева направо) в целочисленном массиве a. Пользуемся тем, что массив должен располагаться в последовательных адресах памяти.

#include <iostream>

int nBit (int* a, int n) {
 return (a[n >> 5] >> (n & 31)) & 1;
}

int main() {
 int a[] = {1,2,3,4,5};
 for (int i = 0; i < 32 * 5; i++) {
  std::cout << nBit(a, i);
  if (i%8 == 7) std::cout << ' ';
 }
 return 0;
}

Работает в предположении, что sizeof(int) == 4 (32 бита).

3. Операции с битами

3.1. Количество ненулевых бит в натуральном числе.

#include <iostream>

int NBit (unsigned int x) { return x == 0 ? 0 : (x & 1) + NBit(x >> 1); }

int main() {
 std::cout << NBit(12345); //6
 return 0;
}

3.2. Проверка того, является ли натуральное число степенью двойки.

#include <iostream>

bool isPow2 (unsigned int a) { return !(a & (a - 1)); }

int main() {
 std::cout << isPow2(64); //1
 return 0;
}

3.3. Обмен значений переменных a и b.

#include <iostream>

void swap (int* a, int* b) { *a ^= (*b ^= (*a ^= *b)); }

int main() {
 int a = 5, b = -3;
 swap (&a, &b);
 std::cout << a << ' ' << b; //-3 5
 return 0;
}

В реальности этот метод своппинга небезопасен даже для простых типов данных, например, не сработает swap(a,a).

Ну а для составных типов он и работать не будет, в таких случаях нужно применять std::swap.

3.4. Возведение двойки в натуральную степень n.

#include <iostream>

long long unsigned pow2 (unsigned n) {
 return 1llu << n;
}

int main() {
 std::cout << pow2(63); //9223372036854775808
  //степень 64 даст переполнение на платформе x64
 return 0;
}

3.5. Максимальная степень числа 2, на которую делится без остатка целое число n.

#include <iostream>

long long int maxDivPow2 (long long int n) {
 return n & -n;
}

int main() {
 std::cout << maxDivPow2(-24); //8
 return 0;
}

То есть, выводится именно само число (два в кубе равно восьми), а не степень двойки.

3.6. Установка в едиинцу n-ого бита числа, нумерация битов справа налево начиная с нуля, шаблон функции.

#include <iostream>

template <typename T> T setBit (T x, unsigned n) {
 return x | ((T)1 << n);
}

int main() {
 long long unsigned n = 0xfffffff0llu;
 std::cout << std::hex << setBit(n,3); //fffffff8
 return 0;
}

3.7. Обнуление n-ого бита числа, нумерация битов справа налево начиная с нуля, шаблон функции.

#include <iostream>

template <typename T> T clearBit (T x, unsigned n) {
 return x & ~((T)1 << n);
}

int main() {
 long long unsigned n = 0xffffffffllu;
 std::cout << std::hex << clearBit(n,3); //fffffff7
 return 0;
}

3.8. Переключение n-ого бита с нуля на единицу или обратно, нумерация битов справа налево начиная с нуля, шаблон функции.

#include <iostream>

template <typename T> T switchBit(T x, unsigned n) {
 return x ^ ((T)1 << n);
}

int main() {
 long long unsigned n = 0xffffffffllu;
 n = switchBit(n, 3);
 std::cout << std::hex << n << std::endl; //fffffff7
 std::cout << std::hex << switchBit(n, 3); //ffffffff
 return 0;
}

3.9. Проверка n-ого бита числа, нумерация битов справа налево начиная с нуля, шаблон функции.

#include <iostream>

template <typename T> bool checkBit(T x, unsigned n) {
 return x & ((T)1 << n);
}

int main() {
 long long unsigned n = 0x08llu;
 std::cout << std::boolalpha << checkBit(n, 3) << std::endl; //true
 std::cout << std::boolalpha << checkBit(n, 4); //false
 return 0;
}

3.10. Вернуть число, в котором установлена только самая правая единица исходного числа, нумерация битов справа налево начиная с нуля, шаблон функции.

#include <iostream>

template <typename T> T right1(T x) {
 return x & (-x);
}

int main() {
 long long n = 0xf8llu;
 std::cout << std::hex << right1(n); //8
 return 0;
}

3.11. Вернуть число, в котором установлена только единица исходного числа, соответствующая самому правому нулю, нумерация битов справа налево начиная с нуля, шаблон функции.

#include <iostream>

template <typename T> T right0(T x) {
 return ~x & (x + 1);
}

int main() {
 long long unsigned n = 0xff0fllu;
 std::cout << std::hex << right0(n); //10
 return 0;
}

3.12. Сменить самый правый нулевой бит на единичный, нумерация битов справа налево начиная с нуля, шаблон функции.

#include <iostream>

template <typename T> T setRight0(T x) {
 return x | (x + 1);
}

int main() {
 long long unsigned n = 0xff0fllu;
 std::cout << std::hex << setRight0(n); //ff1f
 return 0;
}

4. Прочее

4.1. Копирование строки с стиле Си.

Без этого однострочника не обходится нигде :) Предполагается, что строка src завершается нуль-терминатором (байтом с кодом 0x00), причём, сам он не копируется.

#include <iostream>

void copy (char* a, const char* b) {
 while (*a++ = *b++);
}

int main() {
 const char *src = "Hello";
 char dest[6] = { '\0'};
 copy (dest, src);
 std::cout << dest; //Hello
 return 0;
}

См также по теме: книгу Уоррена, можно скачать с этой страницы вместе с кодами программ из неё.

 Bit Twiddling Hacks - фундаментальное баловство с битами от Eron Anderson

12.12.2021, 16:01 [693 просмотра]


теги: программирование c++ числа алгоритм

К этой статье пока нет комментариев, Ваш будет первым