Open quinnwencn opened 1 month ago
Cpp中沒有切片的概念,在python等語言中則較為常見。我們假設s是一個數組(字符串數組也可以),以s為例子說明在Rust中切片的定義方式:
s[..]: 獲取s到所有元素 對於需要包含右邊的元素,可以採用=號,如s[n1..=n2], 此時切片的元素區間為:[n1..n2]; 需要注意的一點是,Rust中的切片為str,String無法通過下標索引,因此無法像數組一樣,採用下標定義。 在Rust中,切片是原始數據類型的引用,因此無法通過以下方式定義切片:
fn main() {
let arr = [1, 2, 3, 4, 5];
let n: usize = 3;
let slice = arr[0..n];
}
正確的用法為:
fn main() {
let mut arr = [1, 2, 3, 4, 5];
let slice1 = &arr[0..3];
println!("{:?}", slice1);
let slice2 = &mut arr[0..3];
slice2[0] = 6;
println!("{:?}", slice2);
}
這裏說明下切片和數組的區別:
Rust中,切片有很多方法支持,但是我這裡只列舉常用的幾個,後續用到的再補充:
#[derive(Debug, PartialEq, Eq)]
pub enum Comparison {
Equal,
Sublist,
Superlist,
Unequal,
}
pub fn sublist
題解中,通過長度較長的切片使用windows方法,以短的切片長度為輸入,生成多個windows切片,然後通過any裡匹配,是否有其中一個切片window和短的切片匹配。
普通的數組或者vec的切片,我們都可以用切片來訪問切片中的元素。但是String類型的切片,我們只能用chars()去訪問,但是有一個例外,如果一個String的所有元素都是ASCII碼,那麼我們可以將這個切片轉換成bytes,然後就可以用下標的方式去訪問了。 我們來看Exercism上的這一道掃雷題目: Minesweeper,如果不使用切片去實現,那麼我們就要先拷貝一份切片, 存儲成vec,然後再分別掃描每一個位置是否是雷,並計算地雷的數目,這也是我的解法:
pub fn annotate(strings: &[&str]) -> Vec<String> {
let mut result = Vec::new();
let rows = strings.len();
if rows == 0 {
return result;
}
let cols = strings[0].len();
if cols == 0 {
result.push("".to_string());
return result;
}
let grid: Vec<Vec<char>> = strings
.iter()
.map(|s| s.chars().collect())
.collect();
for i in 0..rows {
let mut line = String::new();
for j in 0..cols {
if grid[i][j] == '*' {
line.push('*');
continue;
}
let mut count = 0;
for di in -1..=1 {
for dj in -1..=1 {
let (x, y) = (i as isize + di, j as isize + dj);
if x >= 0 && x < rows as isize && y >=0 && y < cols as isize {
if grid[x as usize][y as usize] == '*' {
count += 1;
}
}
}
}
line.push(if count == 0 {' '} else {char::from_digit(count, 10).unwrap()});
}
result.push(line);
}
result
}
[package]
edition = "2021"
name = "minesweeper"
version = "1.1.0"
但是,社區中,其他人的優秀解法都是使用了切片轉bytes的方式,並結合match機制非常優雅地解決了這個問題,我們看其中一個:
static NEIGBOURHOOD_OFFSETS: &'static [(i32, i32)] = &[
(-1, -1), (0, -1), (1, -1),
(-1, 0), (1, 0),
(-1, 1), (0, 1), (1, 1),
];
pub fn annotate(field: &[&str]) -> Vec<String> {
let height = field.len() as i32;
(0..height).map(|y| {
let width = field[y as usize].len() as i32;
(0..width).map(|x| {
if field[y as usize].as_bytes()[x as usize] == b'*' {
'*'
} else {
match NEIGBOURHOOD_OFFSETS.iter()
.map(|&(ox, oy)| (x + ox, y + oy))
.filter(|&(x, y)| (0 <= x && x < width) && (0 <= y && y < height))
.filter(|&(x, y)| field[y as usize].as_bytes()[x as usize] == b'*')
.count() {
0 => ' ',
n => (n as u8 + '0' as u8) as char
}
}
}).collect()
}).collect()
}
這可以說是match的完美理解了!Solution by kstep
當然,這個優秀的解法也有一些需要注意的地方:
slice还有一个有用的方法:first,first方法返回一個Option,如果是空則是None,否則就是Some(list),這對於使用match來處理切片非常方便。比如Exercism上的這道Proverb,一開始我使用for循環來冩,其實還是C++的味道:
pub fn build_proverb(list: &[&str]) -> String {
let mut proverb = String::new();
if list.len() == 0 {
return proverb;
}
let tail = list.len() - 1;
for i in 0..list.len() {
match i {
t if t == tail => proverb.push_str(&format!("And all for the want of a {}.", list[0])),
_ => proverb.push_str(&format!("For want of a {} the {} was lost.\n", list[i], list[i + 1])),
}
}
proverb
}
[package]
edition = "2021"
name = "proverb"
version = "1.1.0"
[dependencies]
但是如果使用first和match來完成,就可以使用迭代器來完成:
use std::iter::once;
pub fn build_proverb(list: &[&str]) -> String {
match list.first() {
None => String::new(),
Some(word) => list.windows(2)
.map(|w| format!("For want of a {} the {} was lost.\n", w[0], w[1]))
.chain(once(format!("And all for the want of a {}.", word)))
.collect(),
}
}
這裡首先match list.first(),如果切片爲空,則直接返回空String,否則則將切片劃分windows,由於打印是打印兩個,因此我們劃分爲兩個一組的windows,然後將這些輸出使用chain將兩個迭代器組合,也就是將String拼接。
windws这个迭代器方法,虽然可以直接用于slice,但是并不能用于&str上,原因应该是&str不能按下标索引,不过&str可以转化为bytes,如果确认都是ascii的话,然后再使用windows。 可以参考Exercism的这道题Series,我的解法如下:
pub fn series(digits: &str, len: usize) -> Vec<String> {
if len > digits.len() {
return vec![];
}
digits.as_bytes()
.windows(len)
.map(|w| std::str::from_utf8(w).unwrap().to_string())
.collect()
}
可以看到,&str转为bytes后,如果结果仍然是&str,那么可以使用std::str::from_utf8将bytes转为&str,题中结果是String,因此还需要再使用str的to_string进行一次转换。 需要注意的是,as_bytes以及std::str::from_utf8都不会申请内存,都是在原有的slice的地址进行转换和检查,但是to_string则会申请一次内存。
Slice是如何定義的,又該如何使用?Slice有哪些常用的方法?