วิธีที่มีประสิทธิภาพที่สุดในการลบอักขระพิเศษออกจากสตริง


266

ฉันต้องการลบอักขระพิเศษทั้งหมดออกจากสตริง อักขระที่อนุญาตคือ AZ (ตัวพิมพ์ใหญ่หรือตัวพิมพ์เล็ก), ตัวเลข (0-9), ขีดล่าง (_) หรือเครื่องหมายจุด (.)

ฉันมีสิ่งต่อไปนี้ใช้งานได้ แต่ฉันสงสัย (ฉันรู้!) มันไม่ได้มีประสิทธิภาพมาก:

    public static string RemoveSpecialCharacters(string str)
    {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < str.Length; i++)
        {
            if ((str[i] >= '0' && str[i] <= '9')
                || (str[i] >= 'A' && str[i] <= 'z'
                    || (str[i] == '.' || str[i] == '_')))
                {
                    sb.Append(str[i]);
                }
        }

        return sb.ToString();
    }

วิธีที่มีประสิทธิภาพที่สุดในการทำเช่นนี้คืออะไร? นิพจน์ทั่วไปจะมีลักษณะอย่างไรและเปรียบเทียบกับการจัดการสตริงปกติอย่างไร

สตริงที่จะทำความสะอาดจะค่อนข้างสั้นโดยปกติจะอยู่ระหว่าง 10 ถึง 30 อักขระ


5
ฉันจะไม่ตอบคำถามนี้เพราะจะไม่มีประสิทธิภาพมากขึ้น แต่มีวิธีการแบบคงที่จำนวนมากเช่น char.IsLetterOrDigit () ที่คุณสามารถใช้ในคำสั่ง if เพื่อให้อ่านง่ายขึ้นอย่างน้อย
มาร์ตินแฮร์ริส

5
ฉันไม่แน่ใจว่าการตรวจสอบสำหรับ A ถึง z นั้นปลอดภัยซึ่งทำให้มีตัวอักษร 6 ตัวที่ไม่เรียงตามตัวอักษรมีเพียงตัวเดียวที่ต้องการ (underbar)
สตีเว่น Sudit

4
มุ่งเน้นที่การทำให้โค้ดของคุณอ่านง่ายขึ้น ถ้าคุณไม่ทำเช่นนี้ในวงวน 500 ครั้งต่อวินาทีประสิทธิภาพไม่ใช่เรื่องใหญ่อะไร ใช้ regexp และมันจะง่ายมากที่จะ read.l
ไบรอนวิทล็อค

4
ไบรอนคุณอาจถูกต้องเกี่ยวกับการเน้นการอ่าน อย่างไรก็ตามฉันสงสัยเกี่ยวกับการอ่าน regexp :-)
Steven Sudit

2
นิพจน์ทั่วไปที่สามารถอ่านได้หรือไม่นั้นเป็นเหมือนคนเยอรมันที่สามารถอ่านได้หรือไม่ มันขึ้นอยู่กับว่าคุณรู้หรือไม่ (แม้ว่าในทั้งสองกรณีที่คุณจะได้ทุกขณะนี้แล้วเจอกฎไวยากรณ์ที่ทำให้รู้สึกไม่;)
Blixt

คำตอบ:


325

ทำไมคุณคิดว่าวิธีการของคุณไม่มีประสิทธิภาพ จริงๆแล้วมันเป็นวิธีที่มีประสิทธิภาพที่สุดที่คุณสามารถทำได้

แน่นอนคุณควรอ่านตัวอักษรเป็นตัวแปรท้องถิ่นหรือใช้ตัวแจงนับเพื่อลดจำนวนการเข้าถึงอาร์เรย์:

public static string RemoveSpecialCharacters(this string str) {
   StringBuilder sb = new StringBuilder();
   foreach (char c in str) {
      if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '.' || c == '_') {
         sb.Append(c);
      }
   }
   return sb.ToString();
}

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

แก้ไข:
ฉันทำการทดสอบประสิทธิภาพอย่างรวดเร็วโดยเรียกใช้แต่ละฟังก์ชันหนึ่งล้านครั้งด้วยสตริงอักขระ 24 ตัว ผลลัพธ์เหล่านี้คือ:

ฟังก์ชั่นดั้งเดิม: 54.5 ms
การเปลี่ยนแปลงที่แนะนำของฉัน: 47.1 มิลลิวินาที
ขุดด้วยการตั้งค่าความจุ StringBuilder: 43.3 ms
นิพจน์ทั่วไป: 294.4 ms

แก้ไข 2: ฉันเพิ่มความแตกต่างระหว่าง AZ และ az ในรหัสข้างต้น (ฉันทำการทดสอบประสิทธิภาพอีกครั้งและไม่มีความแตกต่างที่น่าสังเกต)

แก้ไข 3:
ฉันทดสอบโซลูชันการค้นหา + char [] และมันทำงานในเวลาประมาณ 13 ms

ราคาที่ต้องจ่ายคือแน่นอนว่าการเริ่มต้นของตารางการค้นหาขนาดใหญ่และเก็บไว้ในหน่วยความจำ มันไม่ได้เป็นข้อมูลที่มากนัก แต่มันมีประโยชน์มากสำหรับฟังก์ชั่นที่น่าสนใจเช่นนี้ ...

private static bool[] _lookup;

static Program() {
   _lookup = new bool[65536];
   for (char c = '0'; c <= '9'; c++) _lookup[c] = true;
   for (char c = 'A'; c <= 'Z'; c++) _lookup[c] = true;
   for (char c = 'a'; c <= 'z'; c++) _lookup[c] = true;
   _lookup['.'] = true;
   _lookup['_'] = true;
}

public static string RemoveSpecialCharacters(string str) {
   char[] buffer = new char[str.Length];
   int index = 0;
   foreach (char c in str) {
      if (_lookup[c]) {
         buffer[index] = c;
         index++;
      }
   }
   return new string(buffer, 0, index);
}

4
ฉันเห็นด้วย. การเปลี่ยนแปลงอื่น ๆ ที่ฉันจะทำคือการเพิ่มอาร์กิวเมนต์ความจุเริ่มต้นให้กับตัวสร้าง StringBuilder, "= new StringBuilder (str.Length)"
David

2
คำตอบของฉันใช้char[]บัฟเฟอร์แทนStringBuilderมีขอบเล็กน้อยในอันนี้ตามการทดสอบของฉัน (เหมืองแม้ว่าอ่านได้น้อยลงดังนั้นผลประโยชน์เล็ก ๆ อาจจะไม่คุ้มค่า.)
LukeH

1
@ สตีเว่น: นั่นอาจเป็นกรณี แต่เกณฑ์มาตรฐานพูดเพื่อตัวเอง! ในการทดสอบของฉันการใช้char[]บัฟเฟอร์มีประสิทธิภาพมากกว่า (เล็กน้อย) ถึงStringBuilderแม้จะปรับขนาดเป็นสตริงที่มีความยาวหลายหมื่นอักขระ
LukeH

10
@downvoter: ทำไมต้อง downvote หากคุณไม่อธิบายสิ่งที่คุณคิดว่าผิดมันไม่สามารถปรับปรุงคำตอบได้
Guffa

2
@SILENT: ไม่ไม่ได้ แต่คุณควรทำเพียงครั้งเดียว หากคุณจัดสรรอาเรย์ที่มีขนาดใหญ่ในแต่ละครั้งที่คุณเรียกใช้เมธอด (และถ้าคุณเรียกใช้เมธอดบ่อยครั้ง) เมธอดจะกลายเป็นช้าที่สุดและจะทำให้งานเก็บขยะเป็นจำนวนมาก
Guffa

195

ดีเว้นเสียแต่ว่าคุณต้องการบีบประสิทธิภาพออกจากฟังก์ชันของคุณจริงๆให้ไปกับสิ่งที่ง่ายที่สุดในการบำรุงรักษาและทำความเข้าใจ การแสดงออกปกติจะมีลักษณะเช่นนี้:

เพื่อประสิทธิภาพเพิ่มเติมคุณสามารถรวบรวมล่วงหน้าหรือเพียงแค่บอกให้คอมไพล์ในการโทรครั้งแรก (การโทรครั้งต่อไปจะเร็วขึ้น)

public static string RemoveSpecialCharacters(string str)
{
    return Regex.Replace(str, "[^a-zA-Z0-9_.]+", "", RegexOptions.Compiled);
}

1
ฉันเดาว่านี่อาจเป็นคำถามที่ซับซ้อนพอที่จะเร็วกว่าวิธีของ OP โดยเฉพาะถ้ารวบรวมไว้ล่วงหน้า อย่างไรก็ตามฉันไม่มีหลักฐานที่จะสนับสนุนสิ่งนั้น มันควรจะทดสอบ ฉันจะเลือกวิธีนี้โดยไม่คำนึงว่าจะช้าลงอย่างมากเพราะอ่านและบำรุงรักษาได้ง่ายกว่า +1
rmeador

6
มันเป็น regex ที่ง่ายมาก (ไม่มีการย้อนรอยหรือสิ่งที่ซับซ้อนอยู่ในนั้น) ดังนั้นมันควรจะค่อนข้างเร็วมาก

9
@ rmeador: โดยไม่ต้องรวบรวมมันเป็นเรื่องช้าลงประมาณ 5x รวบรวมมันเป็น 3x ช้ากว่าวิธีการของเขา แม้ว่าจะยังง่ายกว่าเดิมถึง 10 เท่า :-D
user7116

6
การแสดงออกปกติไม่ใช่ค้อนวิเศษและไม่เร็วกว่าโค้ดที่ปรับให้เหมาะกับมือ
Christian Klauser

2
สำหรับผู้ที่จำคำพูดที่โด่งดังของ Knuth เกี่ยวกับการปรับให้เหมาะสมนี่คือจุดเริ่มต้น จากนั้นหากคุณพบว่าคุณต้องการประสิทธิภาพที่เพิ่มขึ้นหนึ่งในพันของมิลลิวินาทีให้ใช้เทคนิคอื่นอย่างใดอย่างหนึ่ง
จอห์น

15

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

แก้ไข

นอกจากนี้สำหรับความเร็วคุณจะต้องเริ่มต้นความจุของ StringBuilder ของคุณตามความยาวของสตริงอินพุตของคุณ สิ่งนี้จะหลีกเลี่ยงการจัดสรรใหม่ ทั้งสองวิธีร่วมกันจะให้ทั้งความเร็วและความยืดหยุ่น

การแก้ไขอื่น

ฉันคิดว่าคอมไพเลอร์อาจปรับให้เหมาะสม แต่ตามสไตล์และประสิทธิภาพฉันขอแนะนำ foreach แทน


สำหรับอาร์เรย์forและforeachสร้างรหัสที่คล้ายกัน ฉันไม่รู้เกี่ยวกับสตริงแม้ว่า ฉันสงสัยว่า JIT รู้เกี่ยวกับลักษณะคล้ายอาร์เรย์ของ String
Christian Klauser

1
ฉันพนันได้ว่า JIT รู้มากขึ้นเกี่ยวกับลักษณะของสตริงที่เหมือน [การลบโจ๊ก] ของคุณ Anders etal ทำงานหลายอย่างเพื่อเพิ่มประสิทธิภาพทุกอย่างเกี่ยวกับสตริงใน. net

ฉันทำสิ่งนี้โดยใช้ HashSet <char> และช้ากว่าวิธีของเขาประมาณ 2x การใช้บูล [] นั้นเร็วกว่า (0.0469ms / iter v. 0.0559ms / iter) น้อยกว่ารุ่นที่เขามีใน OP ... โดยมีปัญหาว่าอ่านไม่ได้
user7116

1
ฉันไม่เห็นความแตกต่างด้านประสิทธิภาพใด ๆ ระหว่างการใช้อาร์เรย์ bool กับอาร์เรย์ int ฉันจะใช้อาเรย์บูลเนื่องจากมันทำให้ตารางการค้นหาลดลงจาก 256 kb เป็น 64 kb แต่ก็ยังมีข้อมูลจำนวนมากสำหรับฟังก์ชั่นที่น่าสนใจ ... และเร็วขึ้นเพียง 30% เท่านั้น
Guffa

1
@Guffa 2) เนื่องจากเรากำลังเก็บตัวอักษรและตัวเลขและตัวอักษรละตินพื้นฐานบางตัวเท่านั้นเราจึงต้องการตารางสำหรับไบต์ต่ำดังนั้นขนาดจึงไม่ใช่ปัญหาจริงๆ หากเราต้องการเป็นจุดประสงค์ทั่วไปเทคนิค Unicode มาตรฐานคือ double -irection กล่าวอีกนัยหนึ่งตาราง 256 ตารางอ้างอิงหลายแห่งซึ่งชี้ไปที่ตารางว่างเปล่าเดียวกัน
สตีเว่น Sudit

12
public static string RemoveSpecialCharacters(string str)
{
    char[] buffer = new char[str.Length];
    int idx = 0;

    foreach (char c in str)
    {
        if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')
            || (c >= 'a' && c <= 'z') || (c == '.') || (c == '_'))
        {
            buffer[idx] = c;
            idx++;
        }
    }

    return new string(buffer, 0, idx);
}

1
+1 ทดสอบแล้วและเร็วกว่า StringBuilder ประมาณ 40% 0.0294ms / string v. 0.0399ms / string
user7116

เพียงเพื่อให้แน่ใจว่าคุณหมายถึง StringBuilder มีหรือไม่มีการจัดสรรล่วงหน้า?
สตีเว่น Sudit

ด้วยการจัดสรรล่วงหน้าจะยังช้ากว่าการจัดสรรถ่าน [] และสตริงใหม่ 40%
user7116

2
ฉันชอบสิ่งนี้. ฉัน tweaked วิธีนี้foreach (char c in input.Where(c => char.IsLetterOrDigit(c) || allowedSpecialCharacters.Any(x => x == c))) buffer[idx++] = c;
Chris Marisic

11

นิพจน์ทั่วไปจะมีลักษณะดังนี้:

public string RemoveSpecialChars(string input)
{
    return Regex.Replace(input, @"[^0-9a-zA-Z\._]", string.Empty);
}

แต่ถ้าประสิทธิภาพมีความสำคัญอย่างยิ่งฉันขอแนะนำให้คุณทำเกณฑ์มาตรฐานก่อนเลือก "เส้นทาง regex" ...


11

หากคุณกำลังใช้รายการอักขระแบบไดนามิก LINQ อาจเสนอวิธีแก้ปัญหาที่รวดเร็วและสง่างามมากขึ้น:

public static string RemoveSpecialCharacters(string value, char[] specialCharacters)
{
    return new String(value.Except(specialCharacters).ToArray());
}

ฉันเปรียบเทียบวิธีนี้กับสองวิธี "เร็ว" ก่อนหน้า (การคอมไพล์รุ่น):

  • วิธีแก้ปัญหา Char array โดย LukeH - 427 ms
  • วิธีการแก้ปัญหา StringBuilder - 429 ms
  • LINQ (คำตอบนี้) - 98 ms

โปรดทราบว่าอัลกอริทึมนั้นได้รับการปรับเปลี่ยนเล็กน้อย - ตัวละครจะถูกส่งผ่านในรูปแบบของอาร์เรย์มากกว่าฮาร์ดโค้ดซึ่งอาจส่งผลกระทบต่อสิ่งต่าง ๆ เล็กน้อย (เช่น / วิธีแก้ปัญหาอื่น ๆ

ถ้าฉันสลับไปใช้โซลูชันที่เขียนโค้ดยากโดยใช้ LINQ โดยที่ส่วนคำสั่งผลลัพธ์คือ:

  • วิธีแก้ปัญหา Char array - 7ms
  • วิธีการแก้ปัญหา StringBuilder - 22ms
  • LINQ - 60 ms

อาจเป็นสิ่งที่ควรค่าแก่การดู LINQ หรือแนวทางที่ได้รับการแก้ไขหากคุณวางแผนที่จะเขียนคำตอบทั่วไปมากกว่าการเข้ารหัสรายการอักขระอย่างหนัก LINQ ช่วยให้คุณอ่านรหัสได้อย่างกระชับและรัดกุมกว่า Regex


3
วิธีนี้ดูดี แต่ใช้งานไม่ได้ - ยกเว้น () เป็นการดำเนินการที่กำหนดไว้ดังนั้นคุณจะปรากฏตัวครั้งแรกของอักขระที่ไม่ซ้ำกันแต่ละตัวในสตริง
McKenzieG1

5

ฉันไม่มั่นใจว่าอัลกอริทึมของคุณคืออะไร แต่มีประสิทธิภาพ มันคือ O (n) และดูที่ตัวละครแต่ละตัวเพียงครั้งเดียว คุณจะไม่ได้อะไรที่ดีไปกว่านี้เว้นแต่คุณจะรู้ค่าอย่างน่าอัศจรรย์ก่อนที่จะตรวจสอบ

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

หมายเหตุด้านข้าง: การตรวจสอบA- zไม่ปลอดภัย คุณกำลังรวมทั้ง[, \, ], ^, _, และ ` ...

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

if (str[i] >= '0' && str[i] <= 'z' && 
    (str[i] >= 'a' || str[i] <= '9' ||  (str[i] >= 'A' && str[i] <= 'Z') || 
    str[i] == '_') || str[i] == '.')

หมายเหตุด้านข้าง 3: หากด้วยเหตุผลใดก็ตามที่คุณต้องการให้สิ่งนี้รวดเร็วการเปลี่ยนคำสั่งอาจเร็วขึ้น คอมไพเลอร์ควรสร้างตารางกระโดดสำหรับคุณส่งผลให้มีการเปรียบเทียบเพียงครั้งเดียว:

switch (str[i])
{
    case '0':
    case '1':
    .
    .
    .
    case '.':
        sb.Append(str[i]);
        break;
}

1
ฉันยอมรับว่าคุณไม่สามารถเอาชนะ O (n) กับอันนี้ได้ อย่างไรก็ตามมีต้นทุนต่อการเปรียบเทียบซึ่งสามารถลดลงได้ การค้นหาตารางมีค่าใช้จ่ายต่ำและคงที่ในขณะที่ชุดการเปรียบเทียบจะเพิ่มค่าใช้จ่ายเมื่อคุณเพิ่มข้อยกเว้นเพิ่มเติม
สตีเว่น Sudit

เกี่ยวกับบันทึกย่อ 3 คุณคิดว่าตารางการกระโดดจะเร็วกว่าการค้นหาตารางหรือไม่
สตีเว่น Sudit

ฉันรันการทดสอบประสิทธิภาพอย่างรวดเร็วในโซลูชันสวิตช์และทำงานเหมือนกับการเปรียบเทียบ
Guffa

@ Steven Sudit - ฉันจะเสี่ยงพวกเขาเกี่ยวกับสิ่งเดียวกัน สนใจที่จะทำการทดสอบหรือไม่?
lc

7
สัญกรณ์ O (n) บางครั้งทำให้ฉันโกรธ ผู้คนจะตั้งสมมติฐานโง่ ๆ ตามความจริงที่ว่าอัลกอริทึมนั้นมีอยู่แล้ว O (n) หากเราเปลี่ยนกิจวัตรนี้เพื่อแทนที่การโทร str [i] ด้วยฟังก์ชั่นที่ดึงค่าการเปรียบเทียบโดยสร้างการเชื่อมต่อ SSL แบบครั้งเดียวกับเซิร์ฟเวอร์ที่อยู่ฝั่งตรงข้ามของโลก ... คุณแน่ใจว่าจะเห็นประสิทธิภาพสูง ความแตกต่างและอัลกอริทึมคือ STILL O (n) ค่าใช้จ่ายของ O (1) สำหรับแต่ละอัลกอริทึมนั้นสำคัญและไม่เท่ากัน!
darron



3

มันดูดีสำหรับฉัน การปรับปรุงเดียวที่ฉันจะทำคือการเริ่มต้นStringBuilderด้วยความยาวของสตริง

StringBuilder sb = new StringBuilder(str.Length);

3

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

string test = "abc@#$123";
test.RemoveSpecialCharacters();

ขอบคุณ Guffa สำหรับการทดสอบของคุณ

public static class MethodExtensionHelper
    {
    public static string RemoveSpecialCharacters(this string str)
        {
            StringBuilder sb = new StringBuilder();
            foreach (char c in str)
            {
                if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
                {
                    sb.Append(c);
                }
            }
            return sb.ToString();
        }
}

2

ฉันจะใช้การแทนที่สตริงด้วยนิพจน์ทั่วไปค้นหา "อักขระพิเศษ" แทนที่อักขระทั้งหมดที่พบด้วยสตริงว่าง


+1 รหัสน้อยลงอย่างแน่นอนและสามารถอ่านได้มากกว่าอ่าน Regex ครั้งเดียว
kenny

1
@kenny - ฉันเห็นด้วย คำถามเดิมยังระบุว่าสายสั้น - 10-30 ตัวอักษร แต่เห็นได้ชัดว่าผู้คนจำนวนมากยังคงคิดว่าเราเวลาขาย CPU กำลังโดยที่สอง ...
ทอมเชล

Reguler expressin ทำงานอย่างขี้เกียจดังนั้นจึงไม่ควรใช้งานเสมอ
RockOnGom

2

ฉันต้องทำสิ่งที่คล้ายกันสำหรับการทำงาน แต่ในกรณีของฉันฉันต้องกรองทั้งหมดที่ไม่ใช่ตัวอักษรหมายเลขหรือช่องว่าง (แต่คุณสามารถปรับเปลี่ยนได้ตามความต้องการของคุณ) การกรองเสร็จสิ้นฝั่งไคลเอ็นต์ใน JavaScript แต่ด้วยเหตุผลด้านความปลอดภัยฉันกำลังทำการกรองฝั่งเซิร์ฟเวอร์ เนื่องจากฉันสามารถคาดหวังได้ว่าสตริงส่วนใหญ่นั้นสะอาดฉันต้องการหลีกเลี่ยงการคัดลอกสตริงยกเว้นว่าฉันต้องการจริงๆ นี่เป็นการดำเนินการของฉันด้านล่างซึ่งควรทำงานได้ดีขึ้นสำหรับทั้งสตริงที่สะอาดและสกปรก

public static string EnsureOnlyLetterDigitOrWhiteSpace(string input)
{
    StringBuilder cleanedInput = null;
    for (var i = 0; i < input.Length; ++i)
    {
        var currentChar = input[i];
        var charIsValid = char.IsLetterOrDigit(currentChar) || char.IsWhiteSpace(currentChar);

        if (charIsValid)
        {
            if(cleanedInput != null)
                cleanedInput.Append(currentChar);
        }
        else
        {
            if (cleanedInput != null) continue;
            cleanedInput = new StringBuilder();
            if (i > 0)
                cleanedInput.Append(input.Substring(0, i));
        }
    }

    return cleanedInput == null ? input : cleanedInput.ToString();
}

1

สำหรับ S&G วิธี Linq-ified:

var original = "(*^%foo)(@)&^@#><>?:\":';=-+_";
var valid = new char[] { 
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 
    'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 
    'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 
    'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', 
    '9', '0', '.', '_' };
var result = string.Join("",
    (from x in original.ToCharArray() 
     where valid.Contains(x) select x.ToString())
        .ToArray());

ฉันไม่คิดว่านี่จะเป็นวิธีที่มีประสิทธิภาพมากที่สุด


2
ไม่ใช่เพราะเป็นการค้นหาเชิงเส้น
สตีเว่น Sudit

1
public string RemoveSpecial(string evalstr)
{
StringBuilder finalstr = new StringBuilder();
            foreach(char c in evalstr){
            int charassci = Convert.ToInt16(c);
            if (!(charassci >= 33 && charassci <= 47))// special char ???
             finalstr.append(c);
            }
return finalstr.ToString();
}

1

ใช้:

s.erase(std::remove_if(s.begin(), s.end(), my_predicate), s.end());

bool my_predicate(char c)
{
 return !(isalpha(c) || c=='_' || c==' '); // depending on you definition of special characters
}

sและคุณจะได้รับสตริงสะอาด

erase()จะตัดมันของตัวละครพิเศษทั้งหมดและสามารถปรับแต่งได้อย่างมากด้วยmy_predicate()ฟังก์ชั่น


1

HashSet เป็น O (1)
ไม่แน่ใจว่าเร็วกว่าการเปรียบเทียบที่มีอยู่หรือไม่

private static HashSet<char> ValidChars = new HashSet<char>() { 'a', 'b', 'c', 'A', 'B', 'C', '1', '2', '3', '_' };
public static string RemoveSpecialCharacters(string str)
{
    StringBuilder sb = new StringBuilder(str.Length / 2);
    foreach (char c in str)
    {
        if (ValidChars.Contains(c)) sb.Append(c);
    }
    return sb.ToString();
}

ฉันทดสอบและไม่เร็วกว่าคำตอบที่ยอมรับ
ฉันจะปล่อยให้มันเป็นถ้าคุณต้องการชุดของตัวละครที่กำหนดค่าได้ซึ่งจะเป็นทางออกที่ดี


ทำไมคุณคิดว่าการเปรียบเทียบไม่ใช่ O (1)
Guffa

@Guffa ฉันไม่แน่ใจว่ามันไม่ได้และฉันได้ลบความคิดเห็นของฉัน และ +1 ฉันควรทำการทดสอบเพิ่มเติมก่อนที่จะแสดงความคิดเห็น
paparazzo

1

ฉันสงสัยว่าการแทนที่ตาม Regex (อาจจะรวบรวม) จะเร็วขึ้น จะต้องทดสอบว่ามีคนพบว่านี่ช้ากว่า ~ 5 เท่า

นอกเหนือจากนั้นคุณควรเริ่มต้น StringBuilder ด้วยความยาวที่คาดไว้เพื่อไม่ให้คัดลอกสตริงกลางขณะที่มันโตขึ้น

ตัวเลขที่ดีคือความยาวของสตริงต้นฉบับหรือบางอย่างที่ต่ำกว่าเล็กน้อย (ขึ้นอยู่กับลักษณะของฟังก์ชั่นอินพุต)

สุดท้ายคุณสามารถใช้ตารางการค้นหา (ในช่วง 0..127) เพื่อค้นหาว่าตัวละครนั้นได้รับการยอมรับหรือไม่


นิพจน์ทั่วไปได้รับการทดสอบแล้วและช้ากว่าประมาณห้าเท่า ด้วยตารางการค้นหาในช่วง 0..127 คุณยังต้องตรวจสอบช่วงรหัสอักขระก่อนใช้ตารางการค้นหาเนื่องจากอักขระเป็นค่า 16 บิตไม่ใช่ค่า 7 บิต
Guffa

@Guffa เอ่อ ... ใช่มั้ย ;)
Christian Klauser

1

รหัสต่อไปนี้มีผลลัพธ์ต่อไปนี้ (ข้อสรุปคือเรายังสามารถบันทึกทรัพยากรหน่วยความจำบางส่วนที่จัดสรรขนาดเล็กกว่าอาเรย์):

lookup = new bool[123];

for (var c = '0'; c <= '9'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

for (var c = 'A'; c <= 'Z'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

for (var c = 'a'; c <= 'z'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

48: 0  
49: 1  
50: 2  
51: 3  
52: 4  
53: 5  
54: 6  
55: 7  
56: 8  
57: 9  
65: A  
66: B  
67: C  
68: D  
69: E  
70: F  
71: G  
72: H  
73: I  
74: J  
75: K  
76: L  
77: M  
78: N  
79: O  
80: P  
81: Q  
82: R  
83: S  
84: T  
85: U  
86: V  
87: W  
88: X  
89: Y  
90: Z  
97: a  
98: b  
99: c  
100: d  
101: e  
102: f  
103: g  
104: h  
105: i  
106: j  
107: k  
108: l  
109: m  
110: n  
111: o  
112: p  
113: q  
114: r  
115: s  
116: t  
117: u  
118: v  
119: w  
120: x  
121: y  
122: z  

คุณยังสามารถเพิ่มบรรทัดโค้ดต่อไปนี้เพื่อรองรับโลแคลรัสเซีย (ขนาดอาร์เรย์จะเป็น 1104):

for (var c = 'А'; c <= 'Я'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

for (var c = 'а'; c <= 'я'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

1

ฉันไม่แน่ใจว่ามันเป็นวิธีที่มีประสิทธิภาพที่สุด แต่ก็ใช้ได้กับฉัน

 Public Function RemoverTildes(stIn As String) As String
    Dim stFormD As String = stIn.Normalize(NormalizationForm.FormD)
    Dim sb As New StringBuilder()

    For ich As Integer = 0 To stFormD.Length - 1
        Dim uc As UnicodeCategory = CharUnicodeInfo.GetUnicodeCategory(stFormD(ich))
        If uc <> UnicodeCategory.NonSpacingMark Then
            sb.Append(stFormD(ich))
        End If
    Next
    Return (sb.ToString().Normalize(NormalizationForm.FormC))
End Function

คำตอบที่ไม่ทำงาน แต่คำถามคือสำหรับC # (PS: ฉันรู้ว่านี่เป็นจริงเมื่อห้าปีที่แล้ว แต่ยัง .. ) ฉันใช้ Telerik VB เพื่อ C # Converter (และในทางกลับกัน) และรหัสทำงานได้ดี - ไม่แน่ใจว่าคนอื่น (อีกอย่างคือconverter.telerik.com )
Momoro

1

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

string stringToclean = "This is a test.  Do not try this at home; you might get hurt. Don't believe it?";

var validPunctuation = new HashSet<char>(". -");

var cleanedVersion = new String(stringToclean.Where(x => (x >= 'A' && x <= 'Z') || (x >= 'a' && x <= 'z') || validPunctuation.Contains(x)).ToArray());

var cleanedLowercaseVersion = new String(stringToclean.ToLower().Where(x => (x >= 'a' && x <= 'z') || validPunctuation.Contains(x)).ToArray());

-1
public static string RemoveSpecialCharacters(string str){
    return str.replaceAll("[^A-Za-z0-9_\\\\.]", "");
}

1
ฉันกลัวreplaceAllไม่ใช่ฟังก์ชัน C # String แต่ Java หรือ JavaScript
Csaba Toth

-1
public static string RemoveAllSpecialCharacters(this string text) {
  if (string.IsNullOrEmpty(text))
    return text;

  string result = Regex.Replace(text, "[:!@#$%^&*()}{|\":?><\\[\\]\\;'/.,~]", " ");
  return result;
}

คำตอบนั้นผิด หากคุณจะใช้ regex มันควรจะรวมไม่ได้เป็นพิเศษเพราะคุณพลาดตัวละครบางตัวในขณะนี้ ที่จริงแล้วมีคำตอบกับ regex และจะเต็ม - regex เป็นช้าแล้วโดยตรงเปรียบเทียบตัวอักษรฟังก์ชั่น
TPAKTOPA

-3

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

static void Main(string[] args)
{
    string str = "string!$%with^&*invalid!!characters";
    Console.WriteLine( str ); //print original string
    FixMyString( str, ' ' );
    Console.WriteLine( str ); //print string again to verify that it has been modified
    Console.ReadLine(); //pause to leave command prompt open
}


public static unsafe void FixMyString( string str, char replacement_char )
{
    fixed (char* p_str = str)
    {
        char* c = p_str; //temp pointer, since p_str is read-only
        for (int i = 0; i < str.Length; i++, c++) //loop through each character in string, advancing the character pointer as well
            if (!IsValidChar(*c)) //check whether the current character is invalid
                (*c) = replacement_char; //overwrite character in existing string with replacement character
    }
}

public static bool IsValidChar( char c )
{
    return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '.' || c == '_');
    //return char.IsLetterOrDigit( c ) || c == '.' || c == '_'; //this may work as well
}

14
noooooooooo! การเปลี่ยนสตริงใน. NET คือ BAAAAAAAAAAAAD! ทุกอย่างในกรอบอาศัยอยู่กับกฎที่สายจะไม่เปลี่ยนรูปและถ้าคุณทำลายที่คุณจะได้รับผลข้างเคียงที่น่าแปลกใจมาก ...
Guffa
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.