【问题标题】:Rust: Create array from function at compilationRust:在编译时从函数创建数组
【发布时间】:2021-03-10 10:00:27
【问题描述】:

我正在寻找将以下 c++ 函数转换为 Rust 的解决方案

    uint32_t reverseBits(uint32_t n) {
        static constexpr array<uint8_t, 256> table{[]() constexpr{
                constexpr size_t SIZE = 256;
                array<uint8_t, SIZE> result{};

                for (size_t i = 0; i < SIZE; ++i)
                    result[i] = (i * 0x0202020202ULL & 0x010884422010ULL) % 0x3ff;
                return result;
        }()};

但是:

  • 我不能使用#![feature(const_fn)],因为#![feature] may not be used on the stable release channel
  • 我也不能使用 build.rs 和 cargo
  • 我考虑过使用macro_rules!但我找不到一个非常简单的示例来在循环中进行预计算,例如:
for (size_t i = 0; i < SIZE; ++i)
   result[i] = (i * 0x0202020202ULL & 0x010884422010ULL) % 0x3ff; // reverse bits

注意:我尝试将完整的 c++ 代码转换为 Rust,不幸的是,该表是在运行时计算的:

    pub fn reverse_bits(x: u32) -> u32 {
        let table: [u32; 256];

        for i in 0..10 {
            table[i] = ((i as u64 * 0x0202020202 as u64 & 0x010884422010 as u64) % 0x3ff) as u32;
        }

        return (table[x & 0xff] << 24) | (table[(x >> 8) & 0xff] << 16) |
            (table[(x >> 16) & 0xff] << 8) | (table[(x >> 24) & 0xff]);
    }

也遇到the type[u32] cannot be indexed by u32...

我任何人都知道如何在编译时构建一个具有 256 个 u32 值的数组,其中每个值都等于 (index * 0x0202020202ULL &amp; 0x010884422010ULL) % 0x3ff;,这将非常有帮助!

【问题讨论】:

    标签: rust


    【解决方案1】:

    我无法使用#![feature(const_fn)],因为#![feature] 可能无法在稳定发布频道上使用。

    好吧,我要告诉你一个好消息:const fn 已经成为稳定版 Rust 的一部分已经有一段时间了,所以你可以使用它。但这需要对代码进行一些调整。

    首先,您需要解决与const 无关的问题。正如错误消息告诉您的那样,您需要强制转换为 usize 来索引数组。您的数组应初始化为 0(C++ 代码实际上是这样做的),并且应为 mut

    然后,要使其成为const fn,您需要将for 循环(在const fn 中尚不允许)替换为尾递归,即更改:

    for i in 0..10 {
        table[i] = ...;
    }
    

    类似于:

    const fn set_table(mut table: [u32; 256], i: usize) -> [u32; 256] {
        if i == 10 {
            return table;
        }
        table[i] = ...;
        return set_table(table, i + 1);
    }
    table = set_table(table, 0);
    

    请注意,我们必须将表的所有权传递给尾递归函数并从中取回,因为const fn 不支持可变引用。

    最后,C++ 代码为SIZE 使用了一个不错的常量,我们也可以在 Rust 中这样做。最终结果如下所示,并在 playground 上使用稳定的 Rust 构建:

    pub const fn reverse_bits(x: u32) -> u32 {
        const SIZE: usize = 256;
        let mut table = [0u32; SIZE];
    
        const fn set_table(mut table: [u32; SIZE], i: usize) -> [u32; SIZE] {
            if i == SIZE {
                return table;
            }
            table[i] = ((i as u64 * 0x0202020202 & 0x010884422010) % 0x3ff) as u32;
            return set_table(table, i + 1);
        }
        table = set_table(table, 0);
    
        return (table[(x & 0xff) as usize] << 24)
            | (table[((x >> 8) & 0xff) as usize] << 16)
            | (table[((x >> 16) & 0xff) as usize] << 8)
            | (table[((x >> 24) & 0xff) as usize]);
    }
    

    【讨论】:

    • 非常感谢@user4815162342,非常感谢您的帮助,但它无法编译:if i == SIZE --> loops and conditional expressions are not stable in const fn,编译器建议使用 !feature(const_fn)
    • @AntoninGAVREL 你检查过playground link,我在哪里测试过它可以在稳定版上编译吗?也许您使用的是旧版本的 Rust?
    • Leetcode 显然使用了 Rust 1.40。它们有点过时了。
    • 另外,一个非常小的问题,但是当我想让字符串显示位反转的数字时,会跳过 MSB,play.rust-lang.org/… 有什么解决方案吗?
    【解决方案2】:

    你不应该使用的宏

    我考虑过使用宏规则!但我找不到一个非常简单的例子来循环预计算

    您不能只在声明性宏中从 0 循环到 256,因为宏只知道标记的存在或不存在,而不知道它们的值。但是还有其他方法可以解决这些问题。这是一个生成适合分配给[u32; 256] 的数组字面量的宏:

    macro_rules! table {
        () => { table!(@ (do re mi fa sol la ti do) 0u64) };
        (@ ($_:tt $($bin:tt)*) $($etc:expr),*) => {
            table!(@ ($($bin)*) $($etc*2, $etc*2 + 1),*)
        };
        (@ () $($i:expr),*) => {
            [$((($i * 0x0202020202 & 0x010884422010) % 0x3ff) as u32),*]
        };
    }
    

    此宏分阶段工作。当不带参数调用时(第一个分支),它调用自己(第二个分支),带有 8 个标记的括号序列和一个递增的整数序列,最初只包含一个值 0u64。 (括号中的标记无关紧要,只要它们正好有 8 个。)它为列表中的每个标记递归调用自身(仍在第二个分支中),每消耗一个标记,整数序列的长度就加倍。在 8 次加倍 (28 = 256) 之后,括号为空,当它再次调用自身时(第三个分支),它将每个整数 $i 包装在 (($i * 0x0202020202 &amp; 0x010884422010) % 0x3ff) as u32 中,并将整个内容放在方括号。

    Here it is on the Rust Playground. 可以通过扩展宏查看生成的代码。如果您识别整数的二进制编码,这......令人惊讶的是并不难理解。但我建议不要在实际代码中使用它,因为它并不明显。尽可能使用const 评估。

    更现实的基于宏观的答案

    使用您的编辑器或首选脚本语言生成简单、易于验证的部分,并将宏用于重复部分。

    macro_rules! table {
        ($($i:expr,)*) => {
            [$((($i as u64 * 0x0202020202 & 0x010884422010) % 0x3ff) as u32),*]
        };
    }
    const TABLE: [u32; 256] = table![
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
        0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
        0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
        0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
        0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
        0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
        0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
        0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
        0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
        0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
        0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
        0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
        0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
        0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
    ];
    

    【讨论】:

    • 非常好,它仍然无法在 leetcode 上运行,但感谢您的努力。也不要使用这样的点击诱饵标题,你知道当某些事情被禁止时,你会创造更多的吸引力;)
    • 嗯。我在Compiler Explorer and it seems fine 中使用 Rust 1.40 对其进行了测试。也许leetcode毕竟没有使用那个版本?如果您将$_ 替换为$_b,则相同的策略应该一直有效到1.2.0(不确定何时进行更改)
    【解决方案3】:

    仅适用于 leetcode(2019 年的旧编译器...)

    我必须分两步完成:

    在本地生成输出

    const SIZE: usize = 256;
    
    const fn set_table(mut table: [u32; SIZE], i: usize) -> [u32; SIZE] {
            if i == SIZE {
                return table;
            }
            table[i] = ((i as u64 * 0x0202020202 & 0x010884422010) % 0x3ff) as u32;
            return set_table(table, i + 1);
        }
    
    
    fn main() {
        let mut table = [0u32; SIZE];
        table = set_table(table, 0);
    
        for x in 0..table.len() {
             print!("{:#x}, ", table[x]);
             if x % 16 == 15 {
                println!("");
            }
        }
    }
    

    复制粘贴输出的原始值来构建我的数组

    impl Solution {
        pub const fn reverse_bits(x: u32) -> u32 {
        const table: [u32; 256] = [0x0, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, 
    0x8, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 
    0x4, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, 
    0xc, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, 
    0x2, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, 
    0xa, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, 
    0x6, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, 
    0xe, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 
    0x1, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, 
    0x9, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, 
    0x5, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, 
    0xd, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, 
    0x3, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, 
    0xb, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, 
    0x7, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, 
    0xf, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff];
    
        return (table[(x & 0xff) as usize] << 24)
            | (table[((x >> 8) & 0xff) as usize] << 16)
            | (table[((x >> 16) & 0xff) as usize] << 8)
            | (table[((x >> 24) & 0xff) as usize]);
        }
    }
    

    显然@trentcl 和@user4815162342 方法要好得多...如果编译器允许的话。

    注意:为了检查结果,我使用了以下内容(感谢 this link):

    let n:u32 = 43;
    println!("{:#034b}", n);
    let res:u32 = reverse_bits(n);
    println!("{:#034b}", res);
    

    【讨论】:

    • 如果你在本地生成输出,那么你不再需要函数是const fn,并且可以抛弃尾递归。也就是说,你可以直接在main()中写for i in 0..SIZE { table[i] = ... }
    • 我同意您的评论(总体思路),但在算法平台上,目标之一 - 与内存一起 - 是具有最低的运行时间,这就是为什么我在本地获取数组然后复制的原因- 粘贴到算法平台上。无论如何,非常感谢您的回答:)我真的很喜欢!
    猜你喜欢
    • 2018-11-07
    • 1970-01-01
    • 1970-01-01
    • 2021-11-09
    • 2020-02-22
    • 2014-12-17
    • 2012-10-24
    • 1970-01-01
    • 2011-01-29
    相关资源
    最近更新 更多