ฉันค้นพบวิธีการเรียนรู้สนิมของตัวเองแล้ว หลังจากกำแพงที่ 'เป็นไปไม่ได้' ในตอนแรก จะเกิดปัญหาอื่นอีก เมื่อคุณติดอยู่ตรงกลางของนิพจน์และไม่สามารถสร้างค่าให้เป็นประเภทที่เหมาะสมได้

นี่เป็นหนึ่งในกรณีดังกล่าว และฉันจะซ้อมสิ่งที่ฉันได้เรียนรู้

งาน

มีสตริงอินพุต และฟังก์ชันจำเป็นต้องสร้างสตริงเอาต์พุต โดยที่ . (จุด) ทุกจุดจะถูกแทนที่ด้วย [.] (จุดในวงเล็บเหลี่ยม) โค้ดจะต้องเป็นตัววนซ้ำเหนืออินพุตต่อยด้วย .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 เราควรใช้ประเภทใด? ดูเหมือนว่า Chars เพราะเราวนซ้ำด้วยตัวอักษร (.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

ซึ่งฉันเชื่อว่ามีประโยชน์มาก เนื่องจากสามารถแปลงการอ้างอิงใดๆ ให้เป็นการแบ่งส่วนได้

หมายเหตุเพิ่มเติม:

  1. ฉันต้องให้ความสำคัญกับประเภทสไลซ์ให้มากขึ้น ฉันคิดว่ามันสำคัญมากกว่าที่เห็น
  2. ฉันพลาดสัญลักษณ์ 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)…

บทสรุป

  1. ใช้ String.replace และไม่ทำให้เกิดการกวน

split_inclusive เป็นสิ่งที่ฉันไม่คาดคิดว่าจะใช้ แต่น่าสนใจมาก และสามารถใช้สำหรับการแยกแบบไม่สูญเสียข้อมูลได้หลายประเภท std::slice::from_ref เป็นวิธีที่สะดวกมากในการตักสิ่งต่าง ๆ ลงในตัววนซ้ำ b สัญกรณ์เยี่ยมมาก!

ที่สำคัญกว่านั้น ฉันมีพื้นฐานที่มั่นคงเกี่ยวกับ 'ไม่มีเวทย์มนตร์ประเภทใน match' คุณต้องมีแขนทั้งสองข้างเพื่อให้มีประเภทเดียวกัน และไม่มีทางแก้ไขได้ นอกจากนี้ collect::<String>() ที่มี flat_map บนสไลซ์เป็นวิธีที่น่าสนใจในการประมวลผลสตริง...