Разобрать каждое подмножество k-элементов n-элементного вектора

Моя цель — выполнить операцию над каждым подмножеством k-элементов из n элементов, хранящихся в vec.

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

pub fn set_next_bit_permutation(v: &mut u128) {
    let t: u128 = *v | (*v-1);
    *v = (t + 1) | (((!t & (!t).wrapping_neg()) - 1) >> (v.trailing_zeros() + 1))
}

Затем я использую позиции установленных битов в качестве индексов, извлекая каждый индекс следующим образом. Допустим, я использую perm: u128 для хранения текущей перестановки. Этот код будет внутри цикла, который будет выполняться до тех пор, пока я не проверю каждую перестановку:

perm_copy = perm;
while perm_copy > 0 {
  cur_index = perm.trailing_zeros();
  // do something with cur_index
  perm_copy ^= 1 << cur_index;
}
set_next_bit_permutation(perm);

Хотя это работает и довольно быстро (важно, потому что мы повторяем это много раз), мы ограничены размером 128. Кроме того, это похоже на хак.

Вопросы:

  • Есть ли в Rust что-нибудь встроенное для обработки подмножеств Vecs фиксированного размера? Или я не женат на Vecs, если с этим справится какой-то другой контейнер.
  • Если нет, есть ли лучший и более идиоматический способ добиться этого?
  • Или как я могу изменить это, чтобы обрабатывать длину более 128? Сколь угодно большой?

@ChayimFriedman Скорость жизненно важна, но мне нужно обрабатывать больше элементов.

Dave 21.07.2024 00:25

Это хороший хак. Это работает, это быстро, и вы можете это ясно объяснить, даже если с первого взгляда не будет понятно, как это работает. Конечно, это может быть переусложнено, если вам действительно не нужна скорость. Но если да, то знаете ли вы n (или хотя бы верхнюю границу) во время компиляции, а оно всего > 128, или вам нужна динамика n?

Chayim Friedman 21.07.2024 00:25

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

Dave 21.07.2024 00:30

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

Chayim Friedman 21.07.2024 00:34

Извините, я всегда путаю эти термины. Для этого существует термин «комбинации». В itertools это тоже есть (и тоже будет медленно).

Chayim Friedman 21.07.2024 00:39

Хм... Это будет сложнее, чем кажется на первый взгляд, поскольку вы создадите много временных объектов. Если вы хотите сделать это эффективно, вам нужно будет найти способ их избежать. В качестве альтернативы вы можете выделить несколько запасных bigints, но это будет менее эффективно.

Chayim Friedman 21.07.2024 00:43
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
88
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вам просто нужен bigint. В Rust есть различные библиотеки bigint, самая быстрая из них — коврик. Однако он связан с GMP, что делает его лицензированным LGPL и не работает на Windows-MSVC.

Вот как с ним может выглядеть ваш код (не проверялось):

use rug::{Assign, Integer};

pub struct Combinations {
    result: Integer,
    temp1: Integer,
    temp2: Integer,
    temp3: Integer,
}

impl Combinations {
    pub fn new(upper_bound_length: usize) -> Self {
        Self {
            result: Integer::with_capacity(upper_bound_length),
            temp1: Integer::with_capacity(upper_bound_length),
            temp2: Integer::with_capacity(upper_bound_length),
            temp3: Integer::with_capacity(upper_bound_length),
        }
    }
}

/// This is an operation `rug` doesn't natively provide. It can be accelerated more if it's a hotspot,
/// via SIMD and such.
fn trailing_zeros(v: &Integer) -> u32 {
    let mut result = 0;
    for &limb in v.as_limbs().iter().rev() {
        let trailing_zeros = limb.trailing_zeros();
        result += trailing_zeros;
        if trailing_zeros < gmp_mpfr_sys::gmp::limb_t::BITS {
            break;
        }
    }
    result
}

pub fn set_next_bit_permutation(v: &mut Combinations) {
    v.temp1.assign(&v.result - 1);
    v.temp1 |= &v.result;
    v.temp2.assign(!&v.temp1);
    v.temp1 += 1;
    v.temp3.assign(-&v.temp2);
    v.temp2 &= &v.temp3;
    v.temp2 -= 1;
    v.temp2 >>= trailing_zeros(&v.result) + 1;
    v.result.assign(&v.temp1 | &v.temp2);
}

Зависимости:

gmp-mpfr-sys = "1.6.4"
rug = { version = "1.25.0", default-features = false, features = ["integer"] }

Помимо оптимизированных алгоритмов и реализации rug, у него есть еще одна потрясающая особенность: возможность присваивать результат вычисления непосредственно другой переменной без выделения (что происходит с незавершенными операциями и assign()). Альтернативой для таких библиотек, как num-bigint, будет сначала назначить левую переменную цели, а затем использовать op=.

Если вы готовы перекомпилировать для каждого размера, вы можете работать еще быстрее, используя целые числа произвольного размера во время компиляции, что позволит компилятору полностью развернуть циклы. Крейт bnum — это пример крейта, который реализует целые числа произвольного размера, хотя я не знаю, будет ли он таким же быстрым, как rug, поскольку его реализация не так оптимизирована, как GMP.

Я не получаю здесь правильного ответа. Возможно, проблема не в этом, но что в set_next_bit_permutation() эквивалентно wrapping_neg()?

Dave 22.07.2024 15:06

@Dave Извините, я допустил опечатку. Починил это.

Chayim Friedman 23.07.2024 15:08
Ответ принят как подходящий

На самом деле это легко и эффективно сделать с помощью отсортированного массива из k индексов.

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

Поэтому мы инициализируем массив наименьшими индексами: A=[0, 1, ... k-1]

Затем, чтобы привести в порядок следующий массив индексов подмножества, просто:

  1. Найдите наименьший индекс i такой, что i == k-1 или A[i+i] > A[i]+1.
  2. Приращение A[i]
  3. Установите для предыдущих элементов значение [0, 1,... i-1]

Когда вы увеличите A[k-1] до n, все готово.

Обычно вы касаетесь только A[0]. это амортизированное постоянное время, если n немного больше k. Скорее всего, это будет намного быстрее, чем то, что вы собираетесь делать с этими индексами. Если вы измените подмножество векторов на месте, вам обычно нужно изменить только первый элемент.

Matt Timmermans 21.07.2024 06:00

Оба подхода быстрые. Я принимаю это, потому что это также просто.

Dave 24.07.2024 21:19

Другие вопросы по теме