#c# #.net #regex #optimization #dictionary
#c# #.net #регулярное выражение #оптимизация #словарь
Вопрос:
У меня есть строка в формате "$0Option one$1$Option two$2$Option three"
(etc), которую я хочу преобразовать в словарь, где каждое число соответствует параметру. В настоящее время у меня есть рабочее решение для этой проблемы, но поскольку этот метод вызывается для каждой импортируемой записи (несколько тысяч) Я хочу, чтобы это было максимально оптимизировано.
public Dictionary<string, int> GetSelValsDictBySelValsString(string selectableValuesString)
{
// Get all numbers in the string.
var correspondingNumbersArray = Regex.Split(selectableValuesString, @"[^d] ").Where(x => (!String.IsNullOrWhiteSpace(x))).ToArray();
List<int> correspondingNumbers = new List<int>();
int number;
foreach (string s in correspondingNumbersArray)
{
Int32.TryParse(s, out number);
correspondingNumbers.Add(number);
}
selectableValuesString = selectableValuesString.Replace("$", "");
var selectableStringValuesArray = Regex.Split(selectableValuesString, @"[d] ").Where(x => (!String.IsNullOrWhiteSpace(x))).ToArray();
var selectableValues = new Dictionary<string, int>();
for (int i = 0; i < selectableStringValuesArray.Count(); i )
{
selectableValues.Add(selectableStringValuesArray.ElementAt(i), correspondingNumbers.ElementAt(i));
}
return selectableValues;
}
Комментарии:
1. Есть ли
$
знак между каждым ключом и каждым значением? В вашем примере один$
отсутствует, намеренно или по ошибке?2. Намеренно. $ 0Option $ 1 $Option $ 2 $ Option$3$Option$4$Opt….. и т.д. Я понимаю, что здесь также должны быть целые числа (если строки равны).
Ответ №1:
Первое, что привлекло мое внимание в вашем коде, это то, что он обрабатывает входную строку три раза: дважды с помощью Split()
и один раз с помощью Replace()
. Matches()
Метод — гораздо лучший инструмент, чем Split()
для этой работы. С его помощью вы можете извлечь все, что вам нужно, за один проход. Это также значительно упрощает чтение кода.
Второе, что я заметил, это все эти циклы и промежуточные объекты. Вы уже используете LINQ; действительно используйте его, и вы сможете устранить весь этот беспорядок и повысить производительность. Проверьте это:
public static Dictionary<int, string> GetSelectValuesDictionary(string inputString)
{
return Regex.Matches(inputString, @"(?<key>[0-9] )$*(?<value>[^$] )")
.Cast<Match>()
.ToDictionary(
m => int.Parse(m.Groups["key"].Value),
m => m.Groups["value"].Value);
}
Примечания:
Cast<Match>()
необходимо, потому чтоMatchCollection
только рекламирует себя какIEnumerable
, а нам нужно, чтобы это былоIEnumerable<Match>
.- Я использовал
[0-9]
вместоd
на тот случай, если ваши значения могут содержать цифры из нелатинских систем записи; в .NETd
соответствует им всем. - Такие методы статических регулярных выражений, как
Matches()
, автоматически кэшируют объекты регулярных выражений, но если этот метод будет вызываться часто (особенно если вы используете много других регулярных выражений), возможно, вам все равно захочется создать статический объект регулярных выражений. Если производительность действительно критична, вы можете указатьCompiled
параметр, пока работаете над этим. - Мой код, как и ваш, не предпринимает попыток справиться с искаженным вводом. В частности, мой выдаст исключение, если число окажется слишком большим, в то время как ваш просто преобразует его в ноль. Вероятно, это не имеет отношения к вашему реальному коду, но я был вынужден выразить свое беспокойство, увидев, что вы вызываете
TryParse()
без проверки возвращаемого значения. :/ - Вы также не убедитесь, что ваши ключи уникальны. Как и @Gabe, я перевернул его, используя числовые значения в качестве ключей, потому что они оказались уникальными, а строковые значения — нет. Я надеюсь, что это тоже не проблема с вашими реальными данными. 😉
Комментарии:
1. Вау, это намного проще, чем мой исходный код. Спасибо. Думаю, теперь я буду играть со спичками (хотя моя мама говорила мне не делать этого) 😉
2. Да, я никогда по-настоящему не программировал ни с одним. СЕТЕВЫЕ языки, так что многое из этого меня тоже удивило. Я мог бы привыкнуть к этому!
Ответ №2:
Ваш selectableStringValuesArray
на самом деле не является массивом! Это означает, что каждый раз, когда вы индексируете в него (с помощью ElementAt
или подсчитываете с помощью Count
), он должен повторно запускать регулярное выражение и просматривать список результатов в поисках непробельных. Вместо этого вам нужно что-то вроде этого:
var selectableStringValuesArray = Regex.Split(selectableValuesString, @"[d] ").Where(x => (!String.IsNullOrWhiteSpace(x))).ToArray();
Вы также должны исправить свой correspondingNumbersString
, потому что у него та же проблема.
Я вижу, что вы используете C # 4, поэтому вы можете использовать Zip
для объединения списков, и тогда вам не придется создавать массив или использовать какие-либо циклы. Вы могли бы создать свой словарь следующим образом:
return correspondingNumbersString.Zip(selectableStringValuesArray,
(number, str) => new KeyValuePair<int, string>(int.Parse(number), str))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Комментарии:
1. Хороший ответ с zip-решением. И производительность должна быть лучше при использовании Zip? Я бы повысил ваш рейтинг, если бы у меня были кредиты.
2. @Simon: Да, использование
zip
должно быть немного быстрее, чем использование массивов, потому что это позволяет избежать фактического этапа создания массива.