昨晚我开始学习Rust。鉴于其并行化功能,我认为一个很好的起点是将2048的C ++ AI移植到Rust。

这是实现从++逐字移植2048板的一行(从高索引到低索引)转移的功能。我应该对行中的数字进行移位,然后返回该举动得出的分数。对于不使用迭代器而不得不在各处使用可变变量感到特别不舒服。

我还发现这些显式强制转换非常麻烦,它们真的有必要吗?

评论

SCORE_MERGE_FACTOR在哪里,它是什么类型? f32?

我在缺少的声明中进行了编辑(静态SCORE_MERGE_FACTOR:f32 = 1.2f32;)。用于AI计算中的加权。

#1 楼

既有得分又有合并

糟糕!我确实忽略了合并。这是我的错误;我不是通过可变参数返回值的忠实拥护者,所以我认为我的大脑处于关闭状态。幸运的是,在处理合并逻辑时,我使计分逻辑更短。

// Comment #1
fn compress_zeroes(line: [u8, ..4]) -> (u8, u8, u8, u8) {
    let mut nums: Vec<_> = line.iter().map(|x| *x).filter(|x| *x != 0).collect();
    nums.resize(4, 0);
    (nums[0], nums[1], nums[2], nums[3])
}

// Comment #2
fn score_line(line: [u8, ..4]) -> u64 {
    let line = compress_zeroes(line);
    let line = (line.0 as u64, line.1 as u64, line.2 as u64, line.3 as u64);

    let r = match line {
        (a, b, c, d) if a == b && c == d && a != 0 && c != 0 => a+1 + c+1,
        (a, b, _, _) if a == b && a != 0                     => a+1,
        (_, a, b, _) if a == b && a != 0                     => a+1,
        (_, _, a, b) if a == b && a != 0                     => a+1,
        _                                                    => 0,
    };

    (r as f32 * SCORE_MERGE_FACTOR).round() as u64
}

fn squish_line(line: [u8, ..4]) -> [u8, ..4] {
    let line = compress_zeroes(line);

    // Comment #3
    match line {
        (a, b, c, d) if a == b && c == d && a != 0 && c != 0 => [a+1, c+1, 0,   0],
        (a, b, c, d) if a == b && a != 0                     => [a+1, c,   d,   0],
        (a, b, c, d) if b == c && b != 0                     => [a,   b+1, d,   0],
        (a, b, c, d) if c == d && c != 0                     => [a,   b,   c+1, 0],
        (a, b, c, d)                                         => [a,   b,   c,   d],
    }
}



这可能是最好的增强。由于我们不在乎零值,因此我们可以简单地删除所有零,然后将它们添加回数组的末尾。这会从每个match语句中删除3个情况!
从早期的解决方案看,概念上保持不变,由于能够忽略零而变小了。 (且非零)。在这种情况下,我们需要跟踪所有值,因此我稍微改变了命名方式,以使每个字母都位于同一位置。

我绝对喜欢在这里使用两种不同的方法。最初,该方法称为score_line,但实际上应将其称为score_line。我认为这增加了我最初的困惑。将被合并。我利用Rust的模式匹配来表达给定行中存在的少量有趣案例。

fn score_line(line: [u8, ..4]) -> u64 {
    // Comment #1
    let a0 = line[0] as u64;
    let a1 = line[1] as u64;
    let a2 = line[2] as u64;
    let a3 = line[3] as u64;

    // Comment #2
    let r = match (a0, a1, a2, a3) {
        (a, b, c, d) if a == b && c == d && a != 0 && c != 0 => a+1 + c+1,
        (a, b, _, _) if a == b && a != 0 => a+1,
        (_, a, b, _) if a == b && a != 0 => a+1,
        (_, _, a, b) if a == b && a != 0 => a+1,
        (a, 0, b, _) if a == b && a != 0 => a+1, // Comment #3
        (a, 0, 0, b) if a == b && a != 0 => a+1,
        (_, a, 0, b) if a == b && a != 0 => a+1,
        _ => 0, // Comment #4
    };

    (r as f32 * SCORE_MERGE_FACTOR).round() as u64
}



我们首先将每个8位数字并将其转换为更大的类型。当我们将两个正方形加在一起时,可能需要超过8位。一个score_and_merge_line在这里就足够了,但是原始方法返回了一个u16,所以我们会坚持下去。我们创建一个元组,将它们全部塞满并匹配。
所有的模式匹配线都是相似的,但是这一类型最多。 u64a将绑定到元组中各个位置的数字值。 b表示我们不在乎该位置的值,而_需要该位置的文字值0。我们还使用模式保护(0 ...)将模式匹配进一步限制为彼此相等的数字对。如果选择了该特定分支,则if a == b之后的表达式将是=>的结果。 >我认为此解决方案比原始解决方案有所改进,原因是:


删除了不需要的可变性。创建变量后,无需更改变量,这有助于程序员更好地理解代码。
删除了未使用的功能。该函数旨在返回得分,它不需要实际将数字移到将来的位置。这与缺乏可变性有关。
模式匹配用更简洁,更直观的内容代替了可能是一系列复杂的if-else检查。模式匹配看起来像是一行数字。
没有循环或嵌套循环。再次,这有助于我作为程序员看到更清晰的逻辑。 />

使用match类型或类型别名。
使用新类型或类型别名表示Line将被解释为2的幂。缺少值,而不是零。
删除1.2倍系数,因为我目前看不到乘以每个值的好处。如果所有物体的重量相同,则不会影响任何物体。

另外,我相信当两个相邻单元格具有u8时,原始代码中存在错误。我找到了这个原因是因为我将每个可能的值都与原始代码进行了比较。这只花了几分钟,所以我相信我的代码对于简单的案例来说已经足够了。

评论


\ $ \ begingroup \ $
嗨。欢迎使用代码审查!我们倾向于在评论中提供更多解释。例如,您的四个a变量替换了什么?为什么? (a,0,b,_)表示法如何工作?它替代什么?为什么会更好?
\ $ \ endgroup \ $
– Brythan
2014年12月26日下午6:53

\ $ \ begingroup \ $
感谢您的评论。但实际上,该功能(如我的问题所述)应该改变界限,而不仅仅是计算分数。但是,使用模式匹配来创建结果格式的想法非常聪明,我想知道这是否可以应用于原始问题。
\ $ \ endgroup \ $
–乔纳斯·谢弗(JonasSchäfer)
2014-12-27 16:18



\ $ \ begingroup \ $
@JonasWielicki感谢您指出这一点。我已经更新了(IMO更好)的解决方案!
\ $ \ endgroup \ $
– Shepmaster
2014年12月29日在22:28

\ $ \ begingroup \ $
非常好!压缩零的技巧很聪明,我没有考虑过。在AI中具有用于计算分数的单独功能也可能有用。
\ $ \ endgroup \ $
–乔纳斯·谢弗(JonasSchäfer)
2014年12月31日12:11