SomeButNotAll()是否有优雅的LINQ解决方案?

时间:2015-04-24 20:18:43

标签: c# linq .net-4.5 c#-5.0

以下是我要整体尝试的内容。需要明确的是,这不是作业或竞赛或其他任何事情。希望我的措辞足够清晰:

问题

  

给定一组相同格式的字符串,但是其中一些字符串以小写字母结尾而另一些不以字母结尾,则返回每个字符串中的一个字符串,该字符串不以小写字母结尾,但至少有一个相同的字符串字符串以小写字母结尾。

实施例

为了简单起见,我们假设字符串格式是\d+[a-z]?,其中公共部分是数字。鉴于{1, 4, 3a, 1b, 3, 6c},我应该收到{1, 3}的排列,因为1和3在结尾处都有一个带或不带小写字母的元素。

替代解决方案

你可以view this solution here

我想到这样做的一种方法是将集合划分为包含和不包含小写字母后缀({1, 4, 3}{3a, 1b, 6c})的元素,然后返回withoutSuffix.Where(x => withSuffix.Any(y => y.StartsWith(x)))

我有两个问题:

  1. 我没有看到使用谓词Regex.IsMatch(input, "[a-z]$")分区到两个集合的好方法。我想到的两个是两个相似定义的变量,每个变量使用Where子句并对每个元素执行两次正则表达式匹配,或者转换集合以存储正则表达式匹配结果,然后从中形成两个变量。当你需要像这样访问两个集合时,group...by似乎不能很好地发挥作用,但我可能在那里错了。

  2. 虽然尺寸足够小而不关心性能,但每个withSuffix元素经过withoutSuffix一次似乎不太优雅。

  3. 相关解决方案

    你可以view this solution here

    另一种想到的方法是获取公共前缀和可选后缀:{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问题。优雅不仅仅是表现,而且(也许只是我)明显浪费仍然显得不那么优雅。

5 个答案:

答案 0 :(得分:4)

我认为你真的不需要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);

答案 1 :(得分:3)

您可以按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;

答案 2 :(得分:1)

在这种情况下,我认为最高性能的解决方案在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));
});

答案 3 :(得分:1)

.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" }

答案 4 :(得分:-1)

您可以将.Any()更改为!All()

我更喜欢将Count重载与谓词一起使用并与总计数进行比较。这可能是最干净的,你不必担心空集合产生的异常。