Подтвердить что ты не робот

Как удалить некоторые специальные слова из строкового содержимого?

У меня есть некоторые строки, содержащие код для значков emoji, например :grinning:, :kissing_heart: или :bouquet:. Я хотел бы обработать их, чтобы удалить коды emoji.

Например, данный:

Привет: grinning:, как вы?: kissing_heart: Вы в порядке?: bouquet:

Я хочу получить следующее:

Привет, как дела? Вы в порядке?

Я знаю, что могу использовать этот код:

richTextBox2.Text = richTextBox1.Text.Replace(":kissing_heart:", "").Replace(":bouquet:", "").Replace(":grinning:", "").ToString();

Однако есть 856 различных значков emoji, которые я должен удалить (что, используя этот метод, займет 856 вызовов Replace()). Есть ли другой способ сделать это?

4b9b3361

Ответ 1

Вы можете использовать Regex для соответствия слову между :anything:. Используя Replace с функцией, вы можете выполнить другую проверку.

string pattern = @":(.*?):";
string input = "Hello:grinning: , how are you?:kissing_heart: Are you fine?:bouquet: Are you super fan, for example. :words not to replace:";
string output = Regex.Replace(input, pattern, (m) =>
{
    if (m.ToString().Split(' ').Count() > 1) // more than 1 word and other validations that will help preventing parsing the user text
    {
        return m.ToString();
    }
    return String.Empty;
}); // "Hello , how are you? Are you fine? Are you super fan, for example. :words not to replace:"

Если вы не хотите использовать Replace, который использует выражение лямбда, вы можете использовать \w, как указано в @yorye-nathan, чтобы соответствовать только словам.

string pattern = @":(\w*):";
string input = "Hello:grinning: , how are you?:kissing_heart: Are you fine?:bouquet: Are you super fan, for example. :words not to replace:";
string output = Regex.Replace(input, pattern, String.Empty); // "Hello , how are you? Are you fine? Are you super fan, for example. :words not to replace:"

Ответ 2

string Text = "Hello:grinning: , how are you?:kissing_heart: Are you fine?:bouquet:";

я решил бы это таким образом

List<string> Emoj = new List<string>() { ":kissing_heart:", ":bouquet:", ":grinning:" };
Emoj.ForEach(x => Text = Text.Replace(x, string.Empty));

UPDATE - ссылка на комментарий к деталям

Другой подход: заменить только существующие Emojs

List<string> Emoj = new List<string>() { ":kissing_heart:", ":bouquet:", ":grinning:" };
var Matches = Regex.Matches(Text, @":(\w*):").Cast<Match>().Select(x => x.Value);
Emoj.Intersect(Matches).ToList().ForEach(x => Text = Text.Replace(x, string.Empty));

Но я не уверен, что это большая разница для таких коротких чат-строк, и более важно иметь код, который легко читать/поддерживать. Вопрос OP заключался в уменьшении избыточности Text.Replace().Text.Replace(), а не в самом эффективном решении.

Ответ 3

Я бы использовал комбинацию некоторых из предложенных методов. Во-первых, я бы сохранил 800+ строк emoji в базе данных и затем загружал их во время выполнения. Используйте HashSet, чтобы сохранить их в памяти, чтобы у нас было время поиска O (1) (очень быстро). Используйте Regex, чтобы вытащить все возможные совпадения шаблонов из ввода, а затем сравнить их с нашими хэшированными эмози, удалив действительные и оставив любые шаблоны, не входящие в состав emoji, которые пользователь вошел...

public class Program
{
    //hashset for in memory representation of emoji,
    //lookups are O(1), so very fast
    private HashSet<string> _emoji = null;

    public Program(IEnumerable<string> emojiFromDb)
    {
        //load emoji from datastore (db/file,etc)
        //into memory at startup
        _emoji = new HashSet<string>(emojiFromDb);
    }

    public string RemoveEmoji(string input)
    {
        //pattern to search for
        string pattern = @":(\w*):";
        string output = input;

        //use regex to find all potential patterns in the input
        MatchCollection matches = Regex.Matches(input, pattern);

        //only do this if we actually find the 
        //pattern in the input string...
        if (matches.Count > 0)
        {
            //refine this to a distinct list of unique patterns 
            IEnumerable<string> distinct = 
                matches.Cast<Match>().Select(m => m.Value).Distinct();

            //then check each one against the hashset, only removing
            //registered emoji. This allows non-emoji versions 
            //of the pattern to survive...
            foreach (string match in distinct)
                if (_emoji.Contains(match))
                    output = output.Replace(match, string.Empty);
        }

        return output;
    }
}

public class MainClass
{
    static void Main(string[] args)
    {
        var program = new Program(new string[] { ":grinning:", ":kissing_heart:", ":bouquet:" });
        string output = program.RemoveEmoji("Hello:grinning: :imadethis:, how are you?:kissing_heart: Are you fine?:bouquet: This is:a:strange:thing :to type:, but valid :nonetheless:");
        Console.WriteLine(output);
    }
}

Результат:

Привет: imadethis: как ты? Вы в порядке? Это: a: странно: вещь: для ввода:,  но действительно: тем не менее:

Ответ 4

Вам не нужно заменять все 856 emoji. Вам нужно заменить только те, которые появляются в строке. Так что посмотрите:

Поиск подстроки с использованием С# с твистом

В основном вы извлекаете все токены, т.е. строки между: и: и затем заменяете их на string.Empty()

Если вы обеспокоены тем, что поиск вернет строки, которые не являются emojis, такими как: некоторый другой текст: тогда вы можете иметь поиск в хэш-таблице, чтобы убедиться, что замена указанного найденного токена подходит.

Ответ 5

Наконец-то собрался что-то написать. Я объединяя пару ранее упомянутых идей, с тем фактом, что мы должны только цикл над строкой один раз. Исходя из этих требований, это звучит как идеальная работа для Linq.

Вероятно, вы должны кэшировать HashSet. Помимо этого, это имеет производительность O (n) и только один раз перебирает список. Было бы интересно провести сравнительный анализ, но это могло бы быть самым эффективным решением.

Подход довольно прямо вперед.

  • Сначала загрузите все Emoij в HashSet, чтобы мы могли быстро просмотреть их.
  • Разделите строку с помощью input.Split(':') на :.
  • Решите, сохраняем ли текущий элемент.
    • Если последний элемент был совпадением, сохраните текущий элемент.
    • Если последний элемент не соответствует, проверьте соответствие текущего элемента.
      • Если это так, игнорируйте его. (Это эффективно удаляет подстроку с выхода).
      • Если это не так, добавьте : назад и сохраните его.
  • Перестройте нашу строку с помощью StringBuilder.

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

namespace ConsoleApplication1
{
    static class Program
    {
        static void Main(string[] args)
        {
            ISet<string> emojiList = new HashSet<string>(new[] { "kissing_heart", "bouquet", "grinning" });

            Console.WriteLine("Hello:grinning: , ho:w: a::re you?:kissing_heart:kissing_heart: Are you fine?:bouquet:".RemoveEmoji(':', emojiList));
            Console.ReadLine();
        }

        public static string RemoveEmoji(this string input, char delimiter, ISet<string> emojiList)
        {
            StringBuilder sb = new StringBuilder();
            input.Split(delimiter).Aggregate(true, (prev, curr) =>
            {
                if (prev)
                {
                    sb.Append(curr);
                    return false;
                }
                if (emojiList.Contains(curr))
                {
                    return true;
                }
                sb.Append(delimiter);
                sb.Append(curr);
                return false;
            });
            return sb.ToString();
        }
    }
}

Изменить: я сделал что-то классное, используя Rx-библиотеку, но затем реализованный Aggregate является IEnumerable -компонентом Scan в Rx, тем самым упрощая код еще больше.

Ответ 6

Если эффективность является проблемой и не обрабатывать "ложные срабатывания", рассмотрите возможность перезаписи строки с помощью StringBuilder при пропуске специальных токенов-эмуляторов:

static HashSet<string> emojis = new HashSet<string>()
{
    "grinning",
    "kissing_heart",
    "bouquet"
};

static string RemoveEmojis(string input)
{
    StringBuilder sb = new StringBuilder();

    int length = input.Length;
    int startIndex = 0;
    int colonIndex = input.IndexOf(':');

    while (colonIndex >= 0 && startIndex < length)
    {
        //Keep normal text
        int substringLength = colonIndex - startIndex;
        if (substringLength > 0)
            sb.Append(input.Substring(startIndex, substringLength));

        //Advance the feed and get the next colon
        startIndex = colonIndex + 1;
        colonIndex = input.IndexOf(':', startIndex);

        if (colonIndex < 0) //No more colons, so no more emojis
        {
            //Don't forget that first colon we found
            sb.Append(':');
            //Add the rest of the text
            sb.Append(input.Substring(startIndex));
            break;
        }
        else //Possible emoji, let check
        {
            string token = input.Substring(startIndex, colonIndex - startIndex);

            if (emojis.Contains(token)) //It a match, so we skip this text
            {
                //Advance the feed
                startIndex = colonIndex + 1;
                colonIndex = input.IndexOf(':', startIndex);
            }
            else //No match, so we keep the normal text
            {
                //Don't forget the colon
                sb.Append(':');

                //Instead of doing another substring next loop, let just use the one we already have
                sb.Append(token);
                startIndex = colonIndex;
            }
        }
    }

    return sb.ToString();
}

static void Main(string[] args)
{
    List<string> inputs = new List<string>()
    {
        "Hello:grinning: , how are you?:kissing_heart: Are you fine?:bouquet:",
        "Tricky test:123:grinning:",
        "Hello:grinning: :imadethis:, how are you?:kissing_heart: Are you fine?:bouquet: This is:a:strange:thing :to type:, but valid :nonetheless:"
    };

    foreach (string input in inputs)
    {
        Console.WriteLine("In  <- " + input);
        Console.WriteLine("Out -> " + RemoveEmojis(input));
        Console.WriteLine();
    }

    Console.WriteLine("\r\n\r\nPress enter to exit...");
    Console.ReadLine();
}

Выходы:

In  <- Hello:grinning: , how are you?:kissing_heart: Are you fine?:bouquet:
Out -> Hello , how are you? Are you fine?

In  <- Tricky test:123:grinning:
Out -> Tricky test:123

In  <- Hello:grinning: :imadethis:, how are you?:kissing_heart: Are you fine?:bouquet: This is:a:strange:thing :to type:, but valid :nonetheless:
Out -> Hello :imadethis:, how are you? Are you fine? This is:a:strange:thing :to type:, but valid :nonetheless:

Ответ 7

Используйте этот код, который я ставлю ниже. Думаю, используя эту функцию, ваша проблема будет решена.

        string s = "Hello:grinning: , how are you?:kissing_heart: Are you fine?:bouquet:";

        string rmv = ""; string remove = "";
        int i = 0; int k = 0;
    A:
        rmv = "";
        for (i = k; i < s.Length; i++)
        {
            if (Convert.ToString(s[i]) == ":")
            {
                for (int j = i + 1; j < s.Length; j++)
                {
                    if (Convert.ToString(s[j]) != ":")
                    {
                        rmv += s[j];
                    }
                    else
                    {
                        remove += rmv + ",";
                        i = j;
                        k = j + 1;
                        goto A;
                    }
                }
            }
        }

        string[] str = remove.Split(',');
        for (int x = 0; x < str.Length-1; x++)
        {
            s = s.Replace(Convert.ToString(":" + str[x] + ":"), "");
        }
        Console.WriteLine(s);
        Console.ReadKey();

Ответ 8

Я бы использовал метод расширения следующим образом:

public static class Helper
{
   public static string MyReplace(this string dirty, char separator)
    {
        string newText = "";
        bool replace = false;

        for (int i = 0; i < dirty.Length; i++)
        {
            if(dirty[i] == separator) { replace = !replace ; continue;}
            if(replace ) continue;
            newText += dirty[i];
        }
        return newText;
    }

}

Использование:

richTextBox2.Text = richTextBox2.Text.MyReplace(':');

Этот метод будет лучше с точки зрения производительности по сравнению с одним с Regex

Ответ 9

Я бы разделил текст на ":", а затем построил строку, исключая найденные имена emoji.

        const char marker = ':';
        var textSections = text.Split(marker);

        var emojiRemovedText = string.Empty;

        var notMatchedCount = 0;
        textSections.ToList().ForEach(section =>
        {
            if (emojiNames.Contains(section))
            {
                notMatchedCount = 0;
            }
            else
            {
                if (notMatchedCount++ > 0)
                {
                    emojiRemovedText += marker.ToString();

                }
                emojiRemovedText += section;
            }
        });