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

Есть ли элегантное решение LINQ для SomeButNotAll()?

Вот что я пытаюсь сделать в целом. Чтобы быть ясным, это не домашнее задание или конкурс или что-то еще. Надеюсь, я достаточно четко сформулировал формулировку:

Проблема

Учитывая набор строк в том же формате, но там, где какой-то конец в строчной букве, а некоторые нет, верните набор из одной строки, которая не заканчивается строчной буквой, но имеет по крайней мере одну идентичную строка, заканчивающаяся строчной буквой.

Пример

Чтобы это было просто, пусть говорят, что формат строки \d+[a-z]?, где общая часть - это число. Учитывая {1, 4, 3a, 1b, 3, 6c}, я должен получить перестановку {1, 3}, потому что 1 и 3 имеют как элемент с буквой в нижнем регистре, так и без него.

Альтернативное решение

Вы можете посмотреть это решение здесь.

Один из способов, которым я думал сделать это, - разбить набор на элементы с суффиксом букв в нижнем регистре ({1, 4, 3} и {3a, 1b, 6c}) и затем вернуться withoutSuffix.Where(x => withSuffix.Any(y => y.StartsWith(x))).

У меня есть две проблемы:

  • Я не вижу хорошего способа разбиения на два набора с предикатом Regex.IsMatch(input, "[a-z]$"). Я думал, что это две одинаково определенные переменные, каждая из которых использует предложение Where и выполняет регулярное выражение, совпадающее дважды на элемент, или преобразование набора для хранения результатов соответствия регулярному выражению, а затем формирование двух переменных из этого. group...by, похоже, не очень хорошо играет, когда вам нужно получить доступ к обоим наборам, как это, но я мог ошибаться.

  • Хотя размер достаточно мал, чтобы не заботиться о производительности, переход через withSuffix один раз за элемент withoutSuffix кажется неэлегантным.

Релевантное решение

Вы можете посмотреть это решение здесь.

Другим способом, который пришел на ум, было захватить общий префикс и дополнительный суффикс: {1 => {"", b}, 3 => {a, ""}, 4 => {""}, 6 => {c}}. Это легко осуществить, захватив префикс и суффикс с помощью регулярного выражения ((\d+)([a-z])?) и группируя суффикс с помощью префикса в grouped.

Отсюда было бы здорово:

where grouped.SomeButNotAll(x => x == string.Empty)
select grouped.Key

Или даже:

where grouped.ContainsSomeButNotAll(string.Empty)
select grouped.Key

Я мог бы создать любой из них, но, к сожалению, лучшее, что я вижу в LINQ, это:

where grouped.Contains(string.Empty) && grouped.Any(x => x != string.Empty)
select grouped.Key

Это просто супер избыточно. В LINQ уже есть что-то лучше этого?

P.S. Я открыт для лучших подходов к решению общей проблемы вместо того, чтобы сделать это проблемой XY. Элегантность желательна гораздо больше, чем производительность, но (возможно, это только я), являясь простой расточительностью, все еще кажется неэлегантной.

4b9b3361

Ответ 1

Я не думаю, что вам действительно нужно regexen для этого. Вот как я это сделаю:

var withEndings = new HashSet<string>();
var withoutEndings = new HashSet<string>();

foreach (var s in input)
    if(char.IsLower(s[s.Length - 1])) 
        withEndings.Add(s.Substring(0, s.Length - 1));
    else
        withoutEndings.Add(s);

var result = withEndings.Intersect(withoutEndings);

Ответ 2

Вы можете добавить еще одну группу string.IsNullOrEmpty и убедиться, что она имеет 2 группы (одна для false и одна для true):

return
    from str in strs
    let match = Regex.Match(str, STR_FORMAT)
    group match.Groups[2].Value by match.Groups[1].Value into parts
    where (parts.GroupBy(string.IsNullOrEmpty).Count() == 2)
    select parts.Key;

Ответ 3

В этом случае я считаю, что наиболее эффективное решение не обязательно очень элегантно в LINQ. Я думаю, что это должно делать то, что вы хотите, и делать это время O (N).

values
.Aggregate(
new { HashSet1 = new HashSet<string>(), HashSet2 = new HashSet<string>() },
(a, x) =>
{
    // If the last character is a lowercase letter then put the string
    // (minus the last character) in HashSet1, otherwise, put the string
    // in HashSet2
    if(Char.IsLower(x, x.Length - 1))
    {
        a.HashSet1.Add(x.Substring(0, x.Length - 1));
    }
    else
    {
        a.HashSet2.Add(x);
    }
    return a;
},
a => 
{
    // Return all the strings that are present in both hash sets.
    return 
    a
    .HashSet1
    .Where(x => a.HashSet2.Contains(x));
});

Ответ 4

.Where() для каждого элемента и для каждого из них .Where() для каждого элемента снова, убедившись, что хотя бы один соответствует шаблону регулярного выражения исходного элемента плюс любая строчная буква.

var input = new List<string>() { "1", "4", "3a", "1b", "3", "6c" };
var output = input.Where(
    x => input.Where(
        y => Regex.Match(y, "^" + Regex.Escape(x) + "[a-z]$").Success
    ).Any()
);

output содержит { "1", "3" }.

Ответ 5

Вы можете изменить .Any() на !All().

Я бы предпочел использовать перегрузку Count с предикатом и сравнить с общим количеством. Это, вероятно, будет самым чистым, и вам не придется беспокоиться об исключениях из пустых коллекций.