ลบอักขระออกจากสตริง C #


150

ฉันจะลบอักขระออกจากสตริงได้อย่างไร ตัวอย่างเช่น"My name @is ,Wan.;'; Wan".

ฉันต้องการลบอักขระ'@', ',', '.', ';', '\''จากสตริงนั้นเพื่อให้เป็น"My name is Wan Wan"

คำตอบ:


177
var str = "My name @is ,Wan.;'; Wan";
var charsToRemove = new string[] { "@", ",", ".", ";", "'" };
foreach (var c in charsToRemove)
{
    str = str.Replace(c, string.Empty);
}

แต่ฉันอาจแนะนำวิธีการอื่นหากคุณต้องการลบตัวอักษรที่ไม่ใช่ตัวอักษรทั้งหมด

var str = "My name @is ,Wan.;'; Wan";
str = new string((from c in str
                  where char.IsWhiteSpace(c) || char.IsLetterOrDigit(c)
                  select c
       ).ToArray());

12
สามารถทำได้เช่นนี้ str = สตริงใหม่ (str.Where (x => char.IsWhiteSpace (x) || char.IsLetterOrDigit (x)) ToArray ());
Adnan Bhatti

1
ฉันต้องค้นหาสิ่งนี้สตริงสตริงตัวน้อยไม่ได้สร้างสตริงสำหรับการเปรียบเทียบดังนั้นมันจึงมีประสิทธิภาพมากกว่า "" ( stackoverflow.com/questions/151472/… )
Tom Cerul

6
ฉันเป็นคนเดียวที่ได้รับ "อาร์กิวเมนต์ 2: ไม่สามารถแปลงจาก 'string' เป็น 'char'" om string ได้หรือไม่?
OddDev

2
@OddDev คุณควรได้รับข้อผิดพลาดนี้หากอาร์เรย์ที่คุณวนซ้ำเป็นรายชื่อตัวอักษร หากเป็นสตริงสิ่งนี้ควรใช้งานได้
Newteq Developer

3
นอกจากนี้โปรดทราบว่าเพื่อให้ฟังก์ชั่น "str.Replace" ทำงานอย่างถูกต้องพารามิเตอร์ตัวแรกจะต้องเป็น "string" หากคุณต้องการใช้ string.Empty เป็นพารามิเตอร์ตัวที่สอง หากคุณใช้ถ่าน (เช่น. 'a') เป็นพารามิเตอร์แรกคุณจะต้องใช้ถ่านเป็นพารามิเตอร์ที่สอง มิฉะนั้นคุณจะได้รับ "ข้อโต้แย้งที่ 2: ไม่สามารถแปลงจาก 'string' เป็น 'char'" ที่กล่าวถึงโดย @OddDev ด้านบน
Leo


64

ฟังดูเหมือนแอปพลิเคชั่นที่เหมาะสำหรับ RegEx - เอ็นจิ้นที่ออกแบบมาเพื่อการจัดการข้อความที่รวดเร็ว ในกรณีนี้:

Regex.Replace("He\"ll,o Wo'r.ld", "[@,\\.\";'\\\\]", string.Empty)

3
ดูเหมือนว่าสิ่งนี้จะมีประสิทธิภาพมากกว่าวิธีแบบวนซ้ำโดยเฉพาะถ้าคุณสามารถใช้ Regex ที่คอมไพล์ได้
Ade Miller

นี่ควรเป็นคำตอบที่ได้รับการยอมรับโดยเฉพาะอย่างยิ่งเนื่องจาก @AdeMiller กล่าวว่ามันจะมีประสิทธิภาพมากกว่า
Obsidian

14
นี่ไม่เร็วกว่าการวนซ้ำมันเป็นความเข้าใจผิดทั่วไปที่ regex นั้นเร็วกว่าลูป Regex ไม่ใช่เวทมนต์ แต่อย่างใดพวกเขาจะต้องวนซ้ำผ่านสายอักขระเพื่อดำเนินการของพวกเขาและพวกเขาอาจช้ากว่าค่าใช้จ่ายจาก regex เอง พวกมันยอดเยี่ยมจริง ๆ เมื่อพูดถึงการจัดการที่ซับซ้อนอย่างยิ่งซึ่งจะต้องมีโค้ดหลายสิบบรรทัดและหลายลูป การทดสอบเวอร์ชันที่รวบรวมของ regex นี้กับ loop ที่ไม่ได้เพิ่มประสิทธิภาพอย่างง่าย 50,000 ครั้ง regex นั้นจะช้าลง 6 เท่า
Tony Cheetham

แล้วประสิทธิภาพของหน่วยความจำล่ะ นิพจน์ทั่วไปจะมีประสิทธิภาพมากกว่าในแง่ของการจัดสรรสตริงใหม่หรือไม่
Marek

2
บางทีฉันผิดพลาดเมื่อฉันยืนยันว่า RegEx นั้นเร็ว ถ้าสิ่งนี้เป็นจุดศูนย์กลางของการวนซ้ำที่เข้มงวดมากดังนั้นการพิจารณาอื่น ๆ เช่นความสามารถในการอ่านและการบำรุงรักษามีแนวโน้มที่จะครอบงำประสิทธิภาพสำหรับการดำเนินการขนาดเล็กเช่นนี้
John Melville

21

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

string dirty = "My name @is ,Wan.;'; Wan";

// only space, capital A-Z, lowercase a-z, and digits 0-9 are allowed in the string
string clean = Regex.Replace(dirty, "[^A-Za-z0-9 ]", "");

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


19

การเปรียบเทียบข้อเสนอแนะต่างๆ (รวมถึงการเปรียบเทียบในบริบทของการแทนที่อักขระเดียวกับขนาดและตำแหน่งต่างๆของเป้าหมาย)

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

ผล

(ผลเต็มรูปแบบที่นี่ )

| Test                      | Compare | Elapsed                                                            |
|---------------------------|---------|--------------------------------------------------------------------|
| SplitJoin                 | 1.00x   | 29023 ticks elapsed (2.9023 ms) [in 10K reps, 0.00029023 ms per]   |
| Replace                   | 2.77x   | 80295 ticks elapsed (8.0295 ms) [in 10K reps, 0.00080295 ms per]   |
| RegexCompiled             | 5.27x   | 152869 ticks elapsed (15.2869 ms) [in 10K reps, 0.00152869 ms per] |
| LinqSplit                 | 5.43x   | 157580 ticks elapsed (15.758 ms) [in 10K reps, 0.0015758 ms per]   |
| Regex, Uncompiled         | 5.85x   | 169667 ticks elapsed (16.9667 ms) [in 10K reps, 0.00169667 ms per] |
| Regex                     | 6.81x   | 197551 ticks elapsed (19.7551 ms) [in 10K reps, 0.00197551 ms per] |
| RegexCompiled Insensitive | 7.33x   | 212789 ticks elapsed (21.2789 ms) [in 10K reps, 0.00212789 ms per] |
| Regex Insentive           | 7.52x   | 218164 ticks elapsed (21.8164 ms) [in 10K reps, 0.00218164 ms per] |

สายรัดทดสอบ (LinqPad)

(หมายเหตุ: PerfและVsเป็นส่วนขยายเวลาที่ฉันเขียน )

void test(string title, string sample, string target, string replacement) {
    var targets = target.ToCharArray();

    var tox = "[" + target + "]";
    var x = new Regex(tox);
    var xc = new Regex(tox, RegexOptions.Compiled);
    var xci = new Regex(tox, RegexOptions.Compiled | RegexOptions.IgnoreCase);

    // no, don't dump the results
    var p = new Perf/*<string>*/();
        p.Add(string.Join(" ", title, "Replace"), n => targets.Aggregate(sample, (res, curr) => res.Replace(new string(curr, 1), replacement)));
        p.Add(string.Join(" ", title, "SplitJoin"), n => String.Join(replacement, sample.Split(targets)));
        p.Add(string.Join(" ", title, "LinqSplit"), n => String.Concat(sample.Select(c => targets.Contains(c) ? replacement : new string(c, 1))));
        p.Add(string.Join(" ", title, "Regex"), n => Regex.Replace(sample, tox, replacement));
        p.Add(string.Join(" ", title, "Regex Insentive"), n => Regex.Replace(sample, tox, replacement, RegexOptions.IgnoreCase));
        p.Add(string.Join(" ", title, "Regex, Uncompiled"), n => x.Replace(sample, replacement));
        p.Add(string.Join(" ", title, "RegexCompiled"), n => xc.Replace(sample, replacement));
        p.Add(string.Join(" ", title, "RegexCompiled Insensitive"), n => xci.Replace(sample, replacement));

    var trunc = 40;
    var header = sample.Length > trunc ? sample.Substring(0, trunc) + "..." : sample;

    p.Vs(header);
}

void Main()
{
    // also see /programming/7411438/remove-characters-from-c-sharp-string

    "Control".Perf(n => { var s = "*"; });


    var text = "My name @is ,Wan.;'; Wan";
    var clean = new[] { '@', ',', '.', ';', '\'' };

    test("stackoverflow", text, string.Concat(clean), string.Empty);


    var target = "o";
    var f = "x";
    var replacement = "1";

    var fillers = new Dictionary<string, string> {
        { "short", new String(f[0], 10) },
        { "med", new String(f[0], 300) },
        { "long", new String(f[0], 1000) },
        { "huge", new String(f[0], 10000) }
    };

    var formats = new Dictionary<string, string> {
        { "start", "{0}{1}{1}" },
        { "middle", "{1}{0}{1}" },
        { "end", "{1}{1}{0}" }
    };

    foreach(var filler in fillers)
    foreach(var format in formats) {
        var title = string.Join("-", filler.Key, format.Key);
        var sample = string.Format(format.Value, target, filler.Value);

        test(title, sample, target, replacement);
    }
}

1
ในที่สุดก็มีบางตัวเลข! เก่งมาก @drzaus!
Marek



6

อีกวิธีง่ายๆ:

var forbiddenChars = @"@,.;'".ToCharArray();
var dirty = "My name @is ,Wan.;'; Wan";
var clean = new string(dirty.Where(c => !forbiddenChars.Contains(c)).ToArray());


4

สตริงเป็นเพียงอาร์เรย์อักขระดังนั้นให้ใช้ Linq เพื่อทำการแทนที่ (คล้ายกับ Albin ด้านบนยกเว้นการใช้ linq บรรจุคำสั่งเพื่อทำการแทนที่):

var resultString = new string(
        (from ch in "My name @is ,Wan.;'; Wan"
         where ! @"@,.;\'".Contains(ch)
         select ch).ToArray());

สตริงแรกคือสตริงที่จะแทนที่ตัวอักษรและที่สองคือสตริงที่เรียบง่ายที่มีตัวอักษร


โซลูชัน Linq ของ Albin น่าจะดีกว่าเว้นแต่จะมีตัวอักษรเพิ่มเติมที่คุณต้องการกรองออก (ไม่ครอบคลุมด้วยช่องว่างและตัวอักษรและตัวเลข)
ลิสแตร์

3

ฉันอาจจะโยนนี่ออกไป

สร้างส่วนขยายเพื่อลบตัวอักษรออกจากสตริง:

public static string RemoveChars(this string input, params char[] chars)
{
    var sb = new StringBuilder();
    for (int i = 0; i < input.Length; i++)
    {
        if (!chars.Contains(input[i]))
            sb.Append(input[i]);
    }
    return sb.ToString();
}

และมันใช้งานได้เช่นนี้:

string str = "My name @is ,Wan.;'; Wan";
string cleanedUpString = str.RemoveChars('@', ',', '.', ';', '\'');

หรือเช่นนี้

string str = "My name @is ,Wan.;'; Wan".RemoveChars('@', ',', '.', ';', '\'');

นี่เป็นทางออกที่ดีที่สุดเนื่องจากทำให้การจัดสรรหน่วยความจำมีจำนวนน้อยที่สุด ฉันจะตั้งค่าความยาวของสตริงเดิมเป็นความจุเริ่มต้นของตัวสร้างสตริงเช่น: ใหม่ StringBuilder (input.Length) เพื่อจุดประสงค์ในการจัดสรรหน่วยความจำให้น้อยที่สุด
Treaschf

3

ดูเหมือนว่าวิธีที่สั้นที่สุดคือการรวม LINQ และstring.Concat:

var input = @"My name @is ,Wan.;'; Wan";
var chrs = new[] {'@', ',', '.', ';', '\''};
var result = string.Concat(input.Where(c => !chrs.Contains(c)));
// => result = "My name is Wan Wan" 

ดูC # สาธิต ทราบว่าstring.Concatเป็นทางลัดไปstring.Join("", ...)เป็นทางลัดไปยัง

โปรดทราบว่าการใช้ regex เพื่อลบตัวอักษรที่รู้จักแต่ละตัวยังคงเป็นไปได้ที่จะสร้างแบบไดนามิกแม้ว่าจะเชื่อว่า regex ช้าลง อย่างไรก็ตามนี่คือวิธีในการสร้าง regex แบบไดนามิก (ซึ่งคุณต้องมีเพียงคลาสอักขระ):

var pattern = $"[{Regex.Escape(new string(chrs))}]+";
var result = Regex.Replace(input, pattern, string.Empty);

ดูการสาธิต C #อื่น Regex จะดูเหมือน[@,\.;']+ (หนึ่งในการจับคู่หรือมากกว่า ( +) เกิดขึ้นติดต่อกัน@, ,, ., ;หรือ'ตัวอักษร) ที่จุดไม่ได้ที่จะหนีออกมา แต่Regex.Escapeจะมีความจำเป็นที่จะหลบหนีตัวอักษรอื่น ๆ ที่จะต้องหนีเช่น\, ^, ]หรือ-มีตำแหน่ง ภายในคลาสตัวละครที่คุณไม่สามารถคาดเดาได้


วิธี LINQ คือช้าน่ากลัวในบางกรณี
drzaus

3

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

ในตัวอย่างของ OP เขาเพียงต้องการเก็บตัวอักษรและช่องว่าง นี่คือสิ่งที่การเรียกร้องให้วิธีการของฉันดูเหมือน ( C # demo ):

var str = "My name @is ,Wan.;'; Wan";

// "My name is Wan Wan"
var result = RemoveExcept(str, alphas: true, spaces: true);

นี่คือวิธีการของฉัน:

/// <summary>
/// Returns a copy of the original string containing only the set of whitelisted characters.
/// </summary>
/// <param name="value">The string that will be copied and scrubbed.</param>
/// <param name="alphas">If true, all alphabetical characters (a-zA-Z) will be preserved; otherwise, they will be removed.</param>
/// <param name="numerics">If true, all alphabetical characters (a-zA-Z) will be preserved; otherwise, they will be removed.</param>
/// <param name="dashes">If true, all alphabetical characters (a-zA-Z) will be preserved; otherwise, they will be removed.</param>
/// <param name="underlines">If true, all alphabetical characters (a-zA-Z) will be preserved; otherwise, they will be removed.</param>
/// <param name="spaces">If true, all alphabetical characters (a-zA-Z) will be preserved; otherwise, they will be removed.</param>
/// <param name="periods">If true, all decimal characters (".") will be preserved; otherwise, they will be removed.</param>
public static string RemoveExcept(string value, bool alphas = false, bool numerics = false, bool dashes = false, bool underlines = false, bool spaces = false, bool periods = false) {
    if (string.IsNullOrWhiteSpace(value)) return value;
    if (new[] { alphas, numerics, dashes, underlines, spaces, periods }.All(x => x == false)) return value;

    var whitelistChars = new HashSet<char>(string.Concat(
        alphas ? "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" : "",
        numerics ? "0123456789" : "",
        dashes ? "-" : "",
        underlines ? "_" : "",
        periods ? "." : "",
        spaces ? " " : ""
    ).ToCharArray());

    var scrubbedValue = value.Aggregate(new StringBuilder(), (sb, @char) => {
        if (whitelistChars.Contains(@char)) sb.Append(@char);
        return sb;
    }).ToString();

    return scrubbedValue;
}

คำตอบที่ยอดเยี่ยม!
edtheprogrammerguy

ดีมาก! สตริงตัวเลขมี 0 สองครั้ง
John Kurtz

@ JohnKurtz Nice catch - มันหายไปแล้ว
Mass Dot Net

2

คำตอบที่ดีมากมายที่นี่นี่คือการเพิ่มของฉันพร้อมกับการทดสอบหน่วยต่างๆที่สามารถใช้เพื่อช่วยทดสอบความถูกต้องโซลูชันของฉันคล้ายกับ @ Rianne's ด้านบน แต่ใช้ ISet เพื่อให้เวลาค้นหา O (1) สำหรับตัวอักษรทดแทน (และ คล้ายกับโซลูชัน Linq ของ @Albin Sunnanbo)

    using System;
    using System.Collections.Generic;
    using System.Linq;

    /// <summary>
    /// Returns a string with the specified characters removed.
    /// </summary>
    /// <param name="source">The string to filter.</param>
    /// <param name="removeCharacters">The characters to remove.</param>
    /// <returns>A new <see cref="System.String"/> with the specified characters removed.</returns>
    public static string Remove(this string source, IEnumerable<char> removeCharacters)
    {
        if (source == null)
        {
            throw new  ArgumentNullException("source");
        }

        if (removeCharacters == null)
        {
            throw new ArgumentNullException("removeCharacters");
        }

        // First see if we were given a collection that supports ISet
        ISet<char> replaceChars = removeCharacters as ISet<char>;

        if (replaceChars == null)
        {
            replaceChars = new HashSet<char>(removeCharacters);
        }

        IEnumerable<char> filtered = source.Where(currentChar => !replaceChars.Contains(currentChar));

        return new string(filtered.ToArray());
    }

การทดสอบ NUnit (2.6+) ที่นี่

using System;
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;

[TestFixture]
public class StringExtensionMethodsTests
{
    [TestCaseSource(typeof(StringExtensionMethodsTests_Remove_Tests))]
    public void Remove(string targetString, IEnumerable<char> removeCharacters, string expected)
    {
        string actual = StringExtensionMethods.Remove(targetString, removeCharacters);

        Assert.That(actual, Is.EqualTo(expected));
    }

    [TestCaseSource(typeof(StringExtensionMethodsTests_Remove_ParameterValidation_Tests))]
    public void Remove_ParameterValidation(string targetString, IEnumerable<char> removeCharacters)
    {
        Assert.Throws<ArgumentNullException>(() => StringExtensionMethods.Remove(targetString, removeCharacters));
    }
}

internal class StringExtensionMethodsTests_Remove_Tests : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        yield return new TestCaseData("My name @is ,Wan.;'; Wan", new char[] { '@', ',', '.', ';', '\'' }, "My name is Wan Wan").SetName("StringUsingCharArray");
        yield return new TestCaseData("My name @is ,Wan.;'; Wan", new HashSet<char> { '@', ',', '.', ';', '\'' }, "My name is Wan Wan").SetName("StringUsingISetCollection");
        yield return new TestCaseData(string.Empty, new char[1], string.Empty).SetName("EmptyStringNoReplacementCharactersYieldsEmptyString");
        yield return new TestCaseData(string.Empty, new char[] { 'A', 'B', 'C' }, string.Empty).SetName("EmptyStringReplacementCharsYieldsEmptyString");
        yield return new TestCaseData("No replacement characters", new char[1], "No replacement characters").SetName("StringNoReplacementCharactersYieldsString");
        yield return new TestCaseData("No characters will be replaced", new char[] { 'Z' }, "No characters will be replaced").SetName("StringNonExistantReplacementCharactersYieldsString");
        yield return new TestCaseData("AaBbCc", new char[] { 'a', 'C' }, "ABbc").SetName("CaseSensitivityReplacements");
        yield return new TestCaseData("ABC", new char[] { 'A', 'B', 'C' }, string.Empty).SetName("AllCharactersRemoved");
        yield return new TestCaseData("AABBBBBBCC", new char[] { 'A', 'B', 'C' }, string.Empty).SetName("AllCharactersRemovedMultiple");
        yield return new TestCaseData("Test That They Didn't Attempt To Use .Except() which returns distinct characters", new char[] { '(', ')' }, "Test That They Didn't Attempt To Use .Except which returns distinct characters").SetName("ValidateTheStringIsNotJustDistinctCharacters");
    }
}

internal class StringExtensionMethodsTests_Remove_ParameterValidation_Tests : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        yield return new TestCaseData(null, null);
        yield return new TestCaseData("valid string", null);
        yield return new TestCaseData(null, new char[1]);
    }
}

2

เป็นวิธีที่มีประสิทธิภาพฉันมักจะใช้ในกรณีเดียวกัน:

private string Normalize(string text)
{
        return string.Join("",
            from ch in text
            where char.IsLetterOrDigit(ch) || char.IsWhiteSpace(ch)
            select ch);
}

สนุก...


1

โรงเรียนเก่าในสถานที่คัดลอก / กระทืบ:

  private static string RemoveDirtyCharsFromString(string in_string)
     {
        int index = 0;
        int removed = 0;

        byte[] in_array = Encoding.UTF8.GetBytes(in_string);

        foreach (byte element in in_array)
        {
           if ((element == ' ') ||
               (element == '-') ||
               (element == ':'))
           {
              removed++;
           }
           else
           {
              in_array[index] = element;
              index++;
           }
        }

        Array.Resize<byte>(ref in_array, (in_array.Length - removed));
        return(System.Text.Encoding.UTF8.GetString(in_array, 0, in_array.Length));
     }

ไม่แน่ใจเกี่ยวกับประสิทธิภาพของวิธีอื่น ๆ (เช่นค่าใช้จ่ายของการเรียกใช้ฟังก์ชันและการสร้างอินสแตนซ์ที่เกิดขึ้นเป็นผลข้างเคียงในการดำเนินการ C #)


1

ฉันทำให้มันเป็นวิธีการขยายและกับสตริงอาเรย์ฉันคิดว่าstring[]มีประโยชน์มากกว่าchar[]เพราะถ่านสามารถเป็นสตริง:

public static class Helper
{
    public static string RemoverStrs(this string str, string[] removeStrs)
    {
        foreach (var removeStr in removeStrs)
            str = str.Replace(removeStr, "");
        return str;
    }
}

จากนั้นคุณสามารถใช้งานได้ทุกที่:

string myname = "My name @is ,Wan.;'; Wan";
string result = myname.RemoveStrs(new[]{ "@", ",", ".", ";", "\\"});

1

ฉันต้องการลบอักขระพิเศษจากไฟล์ XML นี่เป็นวิธีที่ฉันทำ char.ToString () เป็นฮีโร่ในรหัสนี้

string item = "<item type="line" />"
char DC4 = (char)0x14;
string fixed = item.Replace(DC4.ToString(), string.Empty);


1

จดตัวเลขประสิทธิภาพจาก @drzaus นี่เป็นวิธีส่วนขยายที่ใช้อัลกอริทึมที่เร็วที่สุด

public static class StringEx
{
    public static string RemoveCharacters(this string s, params char[] unwantedCharacters) 
        => s == null ? null : string.Join(string.Empty, s.Split(unwantedCharacters));
}

การใช้

var name = "edward woodward!";
var removeDs = name.RemoveCharacters('d', '!');
Assert.Equal("ewar woowar", removeDs); // old joke
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.