Как удалить некоторые специальные слова из строкового содержимого?
У меня есть некоторые строки, содержащие код для значков 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()
). Есть ли другой способ сделать это?
Ответы
Ответ 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;
}
});