c++ 是否设置了N位的跳转值?

tzdcorbm  于 2023-05-24  发布在  其他
关注(0)|答案(2)|浏览(149)

我想要一个N位数的下一个+100,000值,因为结果值也必须有N位。
例如:30是0b11110
下一个值是39 × 1 m1n1x;
我读了that links,得到了一个用N位找到下一个值的方法。
现在,有没有一种方法可以一次“跳”10万个值?
10万在这里是任意的,它可以是-更高。
我能想到的唯一方法是从我的初始值进行跳跃,计算新值的位数,并在位级别将新值调整为我想要的N位。这是一个非常手动的过程,我想知道是否存在更有效的算法。但它不能保证跳变的值的数量。

epfja78i

epfja78i1#

下面是Maxim 1000的回答:
如果C(n,k)是设置了k位的n位值的数量,则C就是binomial coefficient。它可以计算为C(n,k) = C(n-1,k-1) * n / k,极限为C(n,0) = C(n,n) = 1(和0 <= k <= n)。
从那里,您可以通过从最高有效位到最低有效位(从左到右)迭代数字的位来计算数字的索引。如果数字总共有n位,并且设置了k位,那么当遇到位设置时,索引是C(bit_position, k) + subindex,其中bit_position是位的位置(0是最低有效位,n-1是最高有效位),如果您取消设置该位,则subindex是数字的索引。请注意,如果两个数字设置了不同数量的位,则它们可以具有相同的索引。
例如,有10个5位数,其中设置了3位。以下是它们,沿着它们的索引:

00111 - 0
01011 - 1
01101 - 2
01110 - 3
10011 - 4
10101 - 5
10110 - 6
11001 - 7
11010 - 8
11100 - 9

10110的索引为6:
--第一个比特集位于位置4,其中设置了3个比特,因此是C(4,3);

  • 第二位组在位置2,剩下2位组,所以C(2,2);
    --第三位设置在位置1,剩下1位设置,因此C(1,1)
    这就是C(4,3) + C(2,2) + C(1,1) = 4 + 1 + 1 = 6
    要从索引转到相应的数字,您只需要执行相反的操作:从值0开始,设置对应于C(p,k)的每个位,其中k是已知常数,pn-10
    然后,要向前跳转j值,您可以转换为数字的索引,将j添加到它,并转换回数字。
    下面是一个实现:
auto binomial(uint64_t n, uint64_t k) -> uint64_t {
    if (k > n) {
        return 0;
    }
    if (k == 0 || k == n) {
        return 1;
    }

    // use the GCD to prevent overflow from `binomial(n-1, k-1) * n`
    uint64_t gcd = std::gcd(n, k);
    return binomial(n-1, k-1) / (k / gcd) * (n / gcd);

    // if you have access to a 128-bit type, you can also use that:
    // return binomial(n-1, k-1) * static_cast<__int128>(n) / k;
}

auto indexOf(uint64_t n, uint64_t bitCount) -> uint64_t {
    auto index = uint64_t(0);
    for (auto bit = 0; bit < 64; ++bit) {
        auto bitmask = uint64_t(1) << (63-bit);
        if ((n & bitmask) != 0) {
            index += binomial(63-bit, bitCount);
            --bitCount; // underflow doesn't matter here
        }
    }
    return index;
}

auto fromIndex(uint64_t index, uint64_t bitCount) -> uint64_t {
    auto n = uint64_t(0);
    for (auto bit = 0; bit < 64; ++bit) {
        auto b = binomial(63-bit, bitCount);
        if (b <= index) {
            auto bitmask = uint64_t(1) << (63-bit);
            n |= bitmask;
            index -= b;
            --bitCount; // underflow doesn't matter here
        }
    }
    return n;
}

auto bitCount(uint64_t n) -> unsigned int {
    auto count = 0u;
    while (n != 0) {
        if (n & 1 != 0) {
            ++count;
        }
        n >>= 1;
    }
    return count;
}

auto jumpOptimized(uint64_t n, uint64_t jumpLength) -> uint64_t {
    auto b = bitCount(n);
    auto index = indexOf(n, b);
    return fromIndex(index + jumpLength, b);
}

它可能可以进一步优化,但希望这是写得足够清楚,给予你的想法。
下面是一个使用std::next_permutation的简单实现,以测试优化的实现:

auto jumpNaive(uint64_t n, uint64_t jumpLength) -> uint64_t {
    auto toBitVector = [](uint64_t n) -> std::vector<int> {
        auto v = std::vector<int>{};
        for (auto bit = 0; bit < 64; ++bit) {
            auto bitmask = uint64_t(1) << (63-bit);
            if ((n & bitmask) == 0) {
                v.push_back(0);
            } else {
                v.push_back(1);
            }
        }
        return v;
    };
    
    auto fromBitVector = [](std::vector<int> const& v) -> uint64_t {
        auto n = uint64_t(0);
        for (auto bit = 0; bit < 64; ++bit) {
            if (v[bit] == 1) {
                auto bitmask = uint64_t(1) << (63-bit);
                n |= bitmask;
            }
        }
        return n;
    };

    auto bitVector = toBitVector(n);
    for (auto i = uint64_t(0); i < jumpLength; ++i) {
        std::next_permutation(bitVector.begin(), bitVector.end());
    }
    return fromBitVector(bitVector);
}

您可以在this complete demo中尝试这两种方法。

czfnxgou

czfnxgou2#

我们可以尝试按照数值的顺序索引所有具有N 1的值。之后,我们可以计算起始数字的索引,将100000添加到它,然后将索引再次转换为数字。
我们称f(M,N)为具有N个1的M位值的数量(它是N!/M!/(N-M)!)。如果我们的数字在最高有效位中为0,我们可以将其视为(M-1)位值。如果它的MSB是1,我们可以计算100...00之前的N位数(即f(M-1,N)),并将100...00之后的N位数(即(N-1)1的(M-1)位数)相加。这允许我们将N1的数字转换为它的索引。
如果我没有错过任何东西,转换回来只是逆转每一步。
复杂度看起来像O(max-bit-count)
PS.听起来很复杂,希望有人能找到更简单的...

相关问题