使用 LINQ 检查字符串是否包含字符串或字符列表

Using LINQ to check if a string contains a list of strings or characters

我看到这个问题以不同的方式被问到(检查列表是否包含特定字符串或字符串是否包含任何给定字符)但我需要其他东西。

我正在开发的程序与扑克相关,并以 [rank][suit] 格式显示扑克牌列表,例如AhKd5h3c,在多个DataGridViews中。

现在,我有这个基本的文本框过滤器,工作正常。

for (int i = 0; i < allFilteredRows.Count; i++)
{
     allFilteredRows[i] = new BindingList<CSVModel>(allRows[i].Where
    (x => x.Combo.Contains(txtHandFilter.Text)).ToList());          
} 

allFilteredRows 是我的 DataGridView 的数据源。 allRows 是来自 SQL 数据库的未过滤手牌列表。 Combo 是个人扑克牌。

不过,这只会过滤文本框中字符的确切顺序。我想要的是分别过滤每个级别和套装。因此,如果用户键入 'AK',则应显示包含(至少)一张 A 和一张 K 的所有组合。如果输入是 'sss',它应该过滤所有至少有三张黑桃的牌。字符的顺序无关紧要('KA' 等于 'AK')但每个字符都需要包括在内并且可以组合等级和花色,例如AKh 应该过滤所有至少有一张 A 和红心 K 的牌。

这超出了我对 LINQ 的了解,因此我将不胜感激。

  1. 将您的过滤器字符串转换为卡片列表(据我所知,如果第二个字符较小,则每个项目的长度为 2,否则长度为 1)。

  2. 使用 GroupBy 来计算每张卡片的数量,因此您应该使用 struct { "K", 3 } 而不是 "KKK"。 https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.groupby?view=net-5.0

  3. 在每只手上重复(我假设,每只手都是列表,而不仅仅是“AhKd5h3c”字符串)

  4. 遍历手动检查它是否包含 p2 输出中的所有项目(我假设 { "K", 3 } 应该 return 也有 4 个 "K" 的组合:

    public bool Match(object GroupedCombo, object filter)
    {
        foreach (var item in filter)
        {
            if (!GroupedCombo.Contains(x => x.Card==filter.Card && x.Count>=filter.Count))
                return false;
        }
        return true;
    }
    

听起来您想为过滤器评估多个条件...

您需要评估的条件看起来需要您解析字符串并推断卡片组合的表示形式。这里的问题是它没有任何分隔符。

根据我的建议,您将需要它们:

for (int i = 0; i < allFilteredRows.Count; i++)
{
     allFilteredRows[i] = new BindingList<CSVModel>(allRows[i].Where
    (x => txtHandFilter.Text.Split(" ").All(y => x.Combo.Contains(y)).ToList());          
} 

它是文本框,你应该用空格分隔卡片组合。

如果您愿意,您可以找到一些其他方法来分隔 txtHandFilter,但我认为这回答了您的基本问题。

编辑:我只能想到两个选项来从您想要的文本框中得出字符串数组:

1:定界。

2:逐个字符地解析它以从字典中找到与卡片类型表示相匹配的字符串。(对我来说这似乎更不切实际)

至于如何计算出现次数,我认为 Michał Woliński 的想法是正确的。

using System;
using System.Linq;

var cards = from card in txtHandFilter.Text.Split(" ")
     group card by card
     into g
     select new { Card = g.Key, Count = g.Count() };

for (int i = 0; i < allFilteredRows.Count; i++)
{
     allFilteredRows[i] = new BindingList<CSVModel>(allRows[i]
          .Where(x => cards
          .All(y => x.Combo.Contains(String.Concat(Enumerable.Repeat(y.Card, y.Count)))
          .ToList());          
} 

在我看来,您需要执行两个拆分操作。首先,您需要拆分您的过滤器 string,以便它由其中的单个卡片过滤器组成 - 等级、花色或特定卡片。您可以使用正则表达式来做到这一点。

首先,创建代表可能等级和可能花色的字符集:

var ranks = "[23456789TJQKA]";
var suits = "[hsdc]";

然后,创建一个正则表达式来提取各个卡片过滤器:

var aCardFilter = $"{ranks}{suits}";

最后,使用 returns 来自正则表达式(或内联)的匹配值的扩展方法,您可以拆分过滤器,然后将相似的过滤器分组。您最终会得到两个过滤器,单独的卡片过滤器和 rank/suite 个过滤器:

var cardFilters = txtHandFilter.Text.Matches(aCardFilter).ToList();
var suitRankFilters = txtHandFilter.Text.GroupBy(filterCh => filterCh).ToList();

现在您需要将手牌 string 拆分成一组牌。由于每张卡片都是两个字符,因此您可以在每个第二个位置拆分子字符串。我将其包装在本地函数中以使代码更清晰:

IEnumerable<string> handToCards(string hand) => Enumerable.Range(0, hand.Length / 2).Select(p => hand.Substring(2 * p, 2));

现在您可以通过检查每张牌是否出现在手牌中来测试一手牌是否匹配牌过滤器,并通过检查每张牌在手牌中出现的频率至少与过滤器:

bool handMatchesCardFilters(string hand, List<string> filters)
    => filters.All(filter => handToCards(hand).Contains(filter));
bool handMatchesFilters(string hand, List<IGrouping<char, char>> filters)
    => filters.All(fg => handToCards(hand).Count(card => card.Contains(fg.Key)) >= fg.Count());

终于可以过滤行了:

for (int i = 0; i < allFilteredRows.Count; ++i)
    allFilteredRows[i] = new BindingList<CSVModel>(
        allRows[i].Where(row => handMatchesCardFilters(row.Combo, cardFilters) &&
                               handMatchesFilters(row.Combo, suitRankFilters))
                  .ToList());

需要的扩展方法是

public static class StringExt {
    public static IEnumerable<string> Matches(this string s, string sre) => Regex.Matches(s, sre).Cast<Match>().Select(m => m.Value);
}