ฉันค้นพบวิธีการเรียนรู้สนิมของตัวเองแล้ว หลังจากกำแพงที่ 'เป็นไปไม่ได้' ในตอนแรก จะเกิดปัญหาอื่นอีก เมื่อคุณติดอยู่ตรงกลางของนิพจน์และไม่สามารถสร้างค่าให้เป็นประเภทที่เหมาะสมได้
นี่เป็นหนึ่งในกรณีดังกล่าว และฉันจะซ้อมสิ่งที่ฉันได้เรียนรู้
งาน
มีสตริงอินพุต และฟังก์ชันจำเป็นต้องสร้างสตริงเอาต์พุต โดยที่ .
(จุด) ทุกจุดจะถูกแทนที่ด้วย [.]
(จุดในวงเล็บเหลี่ยม) โค้ดจะต้องเป็นตัววนซ้ำเหนืออินพุตต่อยด้วย .collect()
ที่ส่วนท้ายที่สร้างสตริงเอาต์พุต ไม่ String.replace
หรือการโกงอื่น ๆ !
ลายเซ็น:
fn myfunc(input: String) -> String; assert_eq!("abc".to_string(), myfunc("abc".to_string()); assert_eq!("ab[.]c".to_string(), myfunc("ab.c".to_string()); assert_eq!("ab[.][.]c".to_string(), myfunc("ab..c".to_string()); assert_eq!("[.]".to_string(), myfunc(".".to_string());
โครงกระดูกของฟังก์ชัน:
intput.chars().map??(|c|{ match c{ '.' => {?something with "[.]"}, c => {?just a value of c} } }).collect()
เราไม่รู้ว่าฟังก์ชัน 'map??' คืออะไร และ {?}
เป็นปริศนาทั้งหมด
สิ่งที่เป็นไปไม่ได้
แขนทั้งสองของ match
ต้องส่งคืนประเภทเดียวกัน (หรือประเภทด้านล่างซึ่งไม่เหมาะสมที่นี่) ปัญหาแรกคือ c
คือ Char
และ “บางสิ่งที่มี [.]” ไม่สามารถเป็นอักขระได้ แต่เป็นลำดับของตัวอักษร ไม่มี 'ทั่วไป' ที่นี่
ปัญหาในการส่งคืน “[.]
” เป็นเรื่องพื้นฐานที่นี่ เนื่องจากมันเป็นลำดับไม่ว่าคุณจะบิดอย่างไร ดังนั้น เพื่อทำให้แขนที่สอง (ที่มีตัว 'c') จับคู่กันเป็นประเภท เราจำเป็นต้องสร้างมันตามลำดับด้วย
แบบแผน: ฉันเบื่อที่จะพิมพ์ [.]
และ c
ดังนั้นฉันจะเรียกพวกเขาว่า 'dot-arm' และ 'c-arm' อดทนกับฉัน
เราสามารถใช้ std::iter::once
ได้ไหม? มันสร้างตัววนซ้ำด้วยค่าเดียว... หากเราทำเช่นนั้น เราจำเป็นต้องแปลงดอทอาร์มเป็นตัววนซ้ำบางประเภทด้วย
เอาล่ะ มาทำกัน:
'.' => "[.]".chars(), c => std::iter::once(c)
แต่แน่นอนว่ามันจะใช้งานไม่ได้ ตัววนซ้ำของอักขระส่งคืนประเภท Chars
และเมื่อตัววนซ้ำส่งคืน Once
แม้ว่าทั้งคู่จะเป็นตัววนซ้ำและมีการใช้คุณลักษณะ Iterator
แต่ก็มีความแตกต่างกันอย่างชัดเจน และ Rust จะไม่อนุญาตให้แขนที่แตกต่างกันของการแข่งขันมีความแตกต่างกัน แม้ว่าเราจะใส่ตัววนซ้ำเพิ่มเติมในตอนท้าย (เช่น .take(3)
) ประเภทการส่งคืนของแต่ละรายการจะแตกต่างกัน
นอกจากนี้ หากเราใช้ตัววนซ้ำเป็นอาวุธ เราจำเป็นต้องใช้ฟังก์ชัน flat_map
แทนที่จะเป็นเพียง map
เรื่อง “อาจจะเป็นไปได้”
ในทางเทคนิคแล้ว เราสามารถสร้างสตริงในแขนทั้งสองข้างแล้วส่งคืนได้ วิธีนี้จะได้ผล แต่เราจะดำเนินการจัดสรรจำนวนมหาศาล อักขระแต่ละตัว (c-arm) จะถูกรวมไว้ใน String
ซึ่งหมายถึงการจัดสรรใหม่สำหรับอักขระแต่ละตัว จากนั้นในช่วง flat_map
จะยุบกลับเป็นสตริงเดียว ไม่ โปรดอย่าใช้ไมโครสตริง
แล้วตัววนซ้ำล่ะ?
intput.chars().flat_map(|c|{ match c{ '.' => ['[','.',']'].iter(), c => [c].iter() } }).collect()
ฟังดูสมเหตุสมผลใช่ไหม? แต่ละแขนเป็นตัววนซ้ำบนอาร์เรย์ และส่งคืนตัววนซ้ำบนตัวมันเอง ปัญหาคือเราสร้างอาร์เรย์โดยใช้อักขระ c
และอาร์เรย์นี้ไม่ได้เป็นของสิ่งใดๆ ดังนั้นจึงถูกจัดสรรคืนทันที และเราไม่สามารถส่งตัววนซ้ำทับมันได้ สนิมจะไม่ยอมให้เป็นอย่างนั้น
จะเกิดอะไรขึ้นถ้าเราส่งคืนอาร์เรย์ 'ตามที่เป็น'? (ลืมเรื่อง collect
ไปซะ) เราทำไม่ได้ เนื่องจาก [char;1]
และ [char;3]
เป็นประเภทที่แตกต่างกัน
คิดหนักขึ้น
เราต้องการส่งคืนอักขระอย่างน้อยหนึ่งตัว เราสามารถพลั่วมันเป็น Vec
ได้ แต่ Vec
แทบจะไม่มีประสิทธิภาพเท่ากับ String
เลย การจัดสรรมากมาย
แต่อาจจะใช้สไลซ์ได้ วิธีทำชิ้นจาก str? … เศษอะไร?
นี่คือจุดของการคิด str
ไม่ใช่อาร์เรย์ของ Char
เราควรใช้ประเภทใด? ดูเหมือนว่า Char
s เพราะเราวนซ้ำด้วยตัวอักษร (.chars
ตัววนซ้ำ!) เดี๋ยวก่อน ทำไมเราถึงใช้ chars
? เราสามารถทำซ้ำมากกว่า str
โดยกำหนดให้ str
มีอักขระตัวเดียวในแต่ละครั้งได้หรือไม่
ฉันมองหาบางอย่างและไม่มีฟังก์ชันเฉพาะสำหรับตัววนซ้ำดังกล่าว ฉันถามใน Reddit และได้รับคำตอบ:
str.split_inclusive(|_| true)
ซึ่งให้ผลเป็นชิ้นของ str โดยที่ทุกชิ้นจะมีอักขระเพียงตัวเดียวเท่านั้น มันเป็นการหักมุมที่ไม่คาดคิด แต่ก็ได้ผล
ด้วยตัววนซ้ำนี้ เราสามารถเขียนวิธีแก้ปัญหาการทำงานแรกได้:
fn myfunc(input: String) -> String { input .split_inclusive(|_| true) .map(|c| match c { "." => "[.]", c => c, }) .collect() }
เรากำลังให้ผล &str
ที่มีความยาวต่างกัน และรวบรวมเป็น String
วิธีอื่น
ผู้คนในวาทกรรมชี้ไปที่วิธีแก้ปัญหาที่เป็นไปได้อื่นๆ ดังนั้นเรามาดูกันดีกว่า:
pub fn myfunc(input: &str) -> String {
let bytes: Vec<u8> = input
.as_bytes()
.into_iter()
.flat_map(|c| match c {
b'.' => b"[.]",
c => std::slice::from_ref(c),
})
.copied()
.collect();
String::from_utf8(bytes).unwrap()
}
สตริงได้รับการแยกโครงสร้างเป็นไบต์และวนซ้ำ สำหรับอักขระแต่ละตัว จะเป็น str
หรือฟังก์ชันที่ฉันไม่เคยได้ยินมาก่อน: std::slice::from_ref
ซึ่งฉันเชื่อว่ามีประโยชน์มาก เนื่องจากสามารถแปลงการอ้างอิงใดๆ ให้เป็นการแบ่งส่วนได้
หมายเหตุเพิ่มเติม:
- ฉันต้องให้ความสำคัญกับประเภทสไลซ์ให้มากขึ้น ฉันคิดว่ามันสำคัญมากกว่าที่เห็น
- ฉันพลาดสัญลักษณ์ b อนุญาตให้สร้างประเภท
u8
(x=b’.’
) หรืออาร์เรย์u8
(b"hello"
เหมือนกับ[b'h', b'e', b'l', b'o']
)
นอกจากนี้ยังมีข้อความที่ยอดเยี่ยมเกี่ยวกับไลบรารีการแบ่งส่วน Unicode ซึ่งชี้ไปที่บทความนี้: https://manishearth.github.io/blog/2017/01/14/stop-ascribing-meaning-to-unicode-code-points/ … ฉันพบว่า Unicode นั้นบิดเบี้ยวมากกว่าที่ฉันคิดไว้… (0xFDFD, omg)…
บทสรุป
- ใช้
String.replace
และไม่ทำให้เกิดการกวน
split_inclusive
เป็นสิ่งที่ฉันไม่คาดคิดว่าจะใช้ แต่น่าสนใจมาก และสามารถใช้สำหรับการแยกแบบไม่สูญเสียข้อมูลได้หลายประเภท std::slice::from_ref
เป็นวิธีที่สะดวกมากในการตักสิ่งต่าง ๆ ลงในตัววนซ้ำ b
สัญกรณ์เยี่ยมมาก!
ที่สำคัญกว่านั้น ฉันมีพื้นฐานที่มั่นคงเกี่ยวกับ 'ไม่มีเวทย์มนตร์ประเภทใน match
' คุณต้องมีแขนทั้งสองข้างเพื่อให้มีประเภทเดียวกัน และไม่มีทางแก้ไขได้ นอกจากนี้ collect::<String>()
ที่มี flat_map
บนสไลซ์เป็นวิธีที่น่าสนใจในการประมวลผลสตริง...