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

Posted

技术标签:

【中文标题】使用 LINQ 检查字符串是不是包含字符串或字符列表【英文标题】:Using LINQ to check if a string contains a list of strings or characters使用 LINQ 检查字符串是否包含字符串或字符列表 【发布时间】:2021-11-26 02:43:59 【问题描述】:

我见过以不同方式提出的这个问题(检查列​​表是否包含某个字符串或字符串是否包含任何给定字符),但我需要其他内容。

我正在开发的程序与扑克有关,并以 [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 是我的 DataGridViews 的数据源。 allRows 是来自 SQL 数据库的未经过滤的手牌列表。 Combo 是个人扑克手。

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

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

【问题讨论】:

Enumerable.All 是你的朋友。 Where(r =&gt; txtHandFilter.Text.All(cardCh =&gt; r.Combo.Contains(cardCh))) 所以我成功地完成了这样的工作。然而,问题是这并不能解释同一字符的多次出现。因此,“AA98”显示所有 A98* 手牌,“ssss”显示所有至少包含一把黑桃的手牌。 我认为这需要更多代码来处理,例如5h5 正确。看我的回答。另外,5h5 应该匹配什么? h5h5 应该匹配什么? 另外,重复的卡片应该被忽略还是失败?例如5h5h 需要匹配两个 5h 还是只匹配一个? 5h5 应该显示所有包含红桃 5 和任何花色 5 的手牌。 h5h5 理想地过滤了 5 颗心,再加上至少一颗额外的 5 和一颗额外的心,例如啊5h5s3d。 5hh 将是 5 颗红心加上至少一颗额外的红心。 5h5h 是不可能的,因为甲板上只有一个 5h,但我想可以忽略它。如果您有兴趣,这里是其他工具实现的附加语法:propokertools.com/simulations/generic_syntax 但这种方式超出了我目前所需要的。谢谢你的帮助!我现在看看你的答案。 【参考方案1】:

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

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

    在每只手上重复(我假设每只手都是 List 而不仅仅是“AhKd5h3c”字符串)

    遍历手检查它是否包含来自 p2 输出的所有项目(我假设 "K", 3 也应该返回具有 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;
    
    

【讨论】:

我有点卡在第 4 步。“包含”不能与对象类型一起使用,但我传递给 Match() 的参数是匿名类型。我怎样才能做到这一点? @GasPanic 对象不应该是匿名的,而是已知的类。他们使用匿名,因为在 group by 中不需要它。但是,您应该能够使用类构造函数:new MyGroup() 而不仅仅是 new 【参考方案2】:

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

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

对于我的建议,您将需要它们:

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

【讨论】:

正如您所说,此解决方案的问题在于,您需要一个不太实用的分隔符,而且它不会过滤搜索字符串中多次出现的字符。所以“AA”并不显示所有至少有两个 A 的牌,而是所有至少有一个 A 的牌。【参考方案3】:

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

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

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

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

var aCardFilter = $"rankssuits";

最后,使用从正则表达式(或内联)返回匹配值的扩展方法,您可以拆分过滤器,然后对相似的过滤器进行分组。您最终会得到两个过滤器,单个卡片过滤器和等级/套件过滤器:

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

【讨论】:

完美运行!我需要一段时间才能完全理解所有内容,但这是将来实现其他过滤器/语法的一个很好的起点。非常感谢!

以上是关于使用 LINQ 检查字符串是不是包含字符串或字符列表的主要内容,如果未能解决你的问题,请参考以下文章

检查PostgreSQL jsonb列是不是包含某些字符串的快速方法

检查列是不是包含类型字符串(对象)

检查包含 json 字符串的列是不是具有特定值

Oracle:检查数字列是不是包含格式化数字字符串中的值

如果单列数据表没有主键,如何检查它是不是包含某个字符串?

检查文本是否包含字符串c#linq列表中的任何字符串项