【问题标题】:Is there an elegant LINQ solution for SomeButNotAll()?SomeButNotAll() 是否有优雅的 LINQ 解决方案?
【发布时间】:2015-07-03 14:29:01
【问题描述】:

这是我总体上想做的事情。需要明确的是,这不是家庭作业,也不是比赛或任何事情。希望我的措辞足够清楚:

问题

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

示例

为简单起见,假设字符串格式为\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. 虽然尺寸小到不关心性能,但每个withoutSuffix 元素通过withSuffix 一次似乎不优雅。

相关解决方案

你可以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 中还有什么比这更好的吗?

附:我愿意接受更好的方法来解决整体问题,而不是把它变成一个 XY 问题。优雅比性能更重要,但是(也许只是我)纯粹的浪费似乎仍然不优雅。

【问题讨论】:

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


    【解决方案1】:

    我不认为你真的需要正则表达式。以下是我的做法:

    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);
    

    【讨论】:

    • 好答案。不过,您可能希望在最后对结果进行重复数据删除。
    • @moarboilerplate 我不确定你的意思。集合具有独特的元素。
    • 这几乎让我希望有一个PartitionSelect 之类的。 var partitions = input.PartitionSelect(s =&gt; char.IsLower(s.Last()), s =&gt; s.Substring(0, s.Length - 1), s =&gt; s);,和partitions.TrueValues.Intersect(partitions.FalseValues);(虽然我不希望从中得到哈希集)。
    • @chris 它在某个地方的 Stack Overflow 答案中,我实际上经常使用它,但为了简洁起见,我不想在答案中添加扩展方法。但在许多情况下,一个简单的 foreach 循环更利于可读性和快速理解。
    • @Asad,哦,真的,这很好。起初我以为 C++ 有一个,但不,它缺乏转换方面。
    【解决方案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));
      });
      

      【讨论】:

      • 与 Asad 的回答类似,但感谢您对它的新看法。我没有意识到使用Aggregate 这么简单。
      • 哈!是的,我什至没有看到他的回答。我考虑过提供非 LINQ 解决方案,因为它更干净,但问题特别提到了 LINQ。
      【解决方案4】:

      .Where() 在每个元素上,对于每个元素,.Where() 在每个元素上再次,确保至少有一个符合原始元素的正则表达式模式加上任何一个小写字母。 p>

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

      【讨论】:

      • 你可以用.Any()代替.ToArray().Length != 0
      • 啊,我知道我做错了什么。好决定;我会编辑的。谢谢!
      【解决方案5】:

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

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

      【讨论】:

      • 有趣的是,你应该提到计数方法,因为我确实尝试了 grouping.Count(x =&gt; x == string.Empty) &lt; grouping.Count() 并意识到我仍然需要防止零空字符串(或者如果你反转它,则所有空字符串)。
      • @MarcinJuraszek grouped.Contains(string.Empty) &amp;&amp; !grouped.All(x =&gt; x == string.Empty)
      • 是的,但这并不能改变您必须具备两个条件的事实。我看不出这种变化有什么意义。
      • @MarcinJuraszek 写完之后你会感觉更好。
      猜你喜欢
      • 1970-01-01
      • 2023-03-30
      • 1970-01-01
      • 2018-04-28
      • 1970-01-01
      • 2011-03-31
      • 2011-02-13
      • 1970-01-01
      • 2023-03-19
      相关资源
      最近更新 更多