识别字符串集合中的相似项并将其分组

本文关键字:相似 字符串 集合 识别 | 更新日期: 2023-09-27 18:01:34

我有一个字符串集合,如下所示:

List<string> codes = new List<string>
{
    "44.01", "44.02", "44.03", "44.04", "44.05", "44.06", "44.07", "44.08", "46", "47.10"
};

每个字符串由两个用句号分隔的组件组成——一个前缀代码和一个子代码。有些字符串没有子代码。

我希望能够组合前缀相同的字符串,并将它们与其他代码一起输出如下:

44(01,02,03,04,05,06,07,08(,46,47.10

我遇到了第一个障碍,那就是如何识别前缀值相同的代码并将其分组,这样我就可以将它们组合成一个字符串,正如您在上面看到的那样。

识别字符串集合中的相似项并将其分组

你可以做:

var query = codes.Select(c => 
    new
    {
        SplitArray = c.Split('.'),  //to avoid multiple split
        Value = c
    })
    .Select(c => new
    {
        Prefix = c.SplitArray.First(), //you can avoid multiple split if you split first and use it later
        PostFix = c.SplitArray.Last(),
        Value = c.Value,
    })
    .GroupBy(r => r.Prefix)
    .Select(grp => new
    {
        Key = grp.Key,
        Items = grp.Count() > 1 ? String.Join(",", grp.Select(t => t.PostFix)) : "",
        Value = grp.First().Value,
    });

这就是它的工作原理:

  • 在分隔符上拆分列表中的每个项目,并使用PrefixPostfix和原始value填充匿名类型
  • Prefix上的后期组
  • 然后使用string.Join选择值和后期修复值

输出:

foreach (var item in query)
{
    if(String.IsNullOrWhiteSpace(item.Items))
        Console.WriteLine(item.Value);
    else
        Console.WriteLine("{0}({1})", item.Key, item.Items);
}

输出为:

44(01,02,03,04,05,06,07,08)
46
47.10

试试这个:-

 var result = codes.Select(x => new { SplitArr = x.Split('.'), OriginalValue = x })
                   .GroupBy(x => x.SplitArr[0])
                   .Select(x => new 
                    {
                       Prefix= x.Key,
                       subCode = x.Count() > 1 ? 
                             String.Join(",", x.Select(z => z.SplitArray[1])) : "",
                       OriginalValue = x.First().OriginalValue
                    });

你可以这样打印你想要的输出:-

foreach (var item in result)
{
     Console.Write("{0}({1}),",item.Prefix,item.subCode);
}

工作Fiddle

概述的想法:

  • 使用Dictionary<string, List<string>>收集结果

  • 在列表的循环中,使用string.split()。。第一个元素将是您的Dictionary关键字。。。如果密钥还不存在,则在那里创建一个新的List<string>

  • 如果拆分的结果有第二个元素,则将其附加到List

  • 使用第二个循环将Dictionary格式化为输出字符串

当然,linq也是可能的,例如

List<string> codes = new List<string>() {
    "44.01", "44.05", "47", "42.02", "44.03" };
var result = string.Join(",",
    codes.OrderBy(x => x)
    .Select(x => x.Split('.'))
    .GroupBy(x => x[0])
    .Select((x) =>
    {
        if (x.Count() == 0) return x.Key;
        else if (x.Count() == 1) return string.Join(".", x.First());
        else return x.Key + "(" + string.Join(",", x.Select(e => e[1]).ToArray()) + ")";
    }).ToArray());

一定要爱上林克。。。哈哈。。。我觉得这是个怪物。

您可以在一个聪明的LINQ:中完成所有操作

var grouped = codes.Select(x => x.Split('.'))
                   .Select(x => new
                   {
                       Prefix = int.Parse(x[0]),
                       Subcode = x.Length > 1 ? int.Parse(x[1]) : (int?)null
                   })
                   .GroupBy(k => k.Prefix)
                   .Select(g => new
                   {
                       Prefix = g.Key,
                       Subcodes = g.Where(s => s.Subcode.HasValue).Select(s => s.Subcode)
                   })
                   .Select(x =>
                       x.Prefix +
                       (x.Subcodes.Count() == 1 ? string.Format(".{0}", x.Subcodes.First()) :
                        x.Subcodes.Count() > 1 ? string.Format("({0})", string.Join(",", x.Subcodes))
                                                : string.Empty)
                   ).ToArray();
  1. 首先通过CodeSubcode进行拆分
  2. 按您的Code分组,并将所有Subcode作为一个集合
  3. 以适当的格式选择

考虑到这个问题,我认为您应该在最后一个Select之前停止,让数据表示在应用程序的另一个部分/方法中完成。

老式方式:

List<string> codes = new List<string>() {"44.01", "44.05", "47", "42.02", "44.03" };
string output="" 
for (int i=0;i<list.count;i++)
{
  string [] items= (codes[i]+"..").split('.') ;
  int pos1=output.IndexOf(","+items[0]+"(") ;
  if (pos1<0) output+=","+items[0]+"("+items[1]+")" ; // first occurence of code : add it
  else
  { // Code already inserted : find the insert point
    int pos2=output.Substring(pos1).IndexOf(')') ;   
    output=output.Substring(0,pos2)+","+items[1]+output.Substring(pos2) ;
  }
}
if (output.Length>0) output=output.Substring(1).replace("()","") ;

这将起作用,包括无子代码、单个子代码和多个子代码的正确格式。它也不假设前缀或子代码是数字,所以它保留了前导零。你的问题没有说明在没有子代码的前缀和有子代码的相同前缀的情况下该怎么办,所以它可能在边缘情况下不起作用(44,44.01(。

List<string> codes = new List<string>
{
    "44.01", "44.02", "44.03", "44.04", "44.05", "44.06", "44.07", "44.08", "46", "47.10"
};
var result=codes.Select(x => (x+".").Split('.'))
                   .Select(x => new
                   {
                       Prefix = x[0],
                       Subcode = x[1]
                   })
                   .GroupBy(k => k.Prefix)
                   .Select(g => new
                   {
                       Prefix = g.Key,
                       Subcodes = g.Where(s => s.Subcode!="").Select(s => s.Subcode)
                   })
                   .Select(x =>
                       x.Prefix +
                       (x.Subcodes.Count() == 0 ? string.Empty :
                        string.Format(x.Subcodes.Count()>1?"({0})":".{0}",
                         string.Join(",", x.Subcodes)))
                   ).ToArray();

一般概念,但我相信用Regex替换Substring调用也会更好

List<string> newCodes = new List<string>()
foreach (string sub1 in codes.Select(item => item.Substring(0,2)).Distinct)
{
    StringBuilder code = new StringBuilder();
    code.Append("sub1(");
    foreach (string sub2 in codes.Where(item => item.Substring(0,2) == sub1).Select(item => item.Substring(2))
        code.Append(sub2 + ",");
    code.Append(")");
    newCodes.Add(code.ToString());
}

你可以走几条路。。。我可以看到你制作了一个Dictionary<string,List<string>>,这样你就可以将"44"映射到{".01"、".02"、".03"等}的列表。这需要你在将代码添加到此列表之前处理代码(即,将代码的两个部分分离出来,并处理只有一个部分的情况(。

或者,您可以将它们放入SortedSet中,并提供自己的Comparator,它知道这些是代码以及如何对它们进行排序(至少这比按字母顺序对它们进行分组更可靠(。不过,在这个SortedSet上迭代仍然需要特殊的逻辑,所以上面的Dictionary to List选项可能仍然更可取。

在任何一种情况下,您仍然需要处理代码中没有第二个元素的特殊情况"46"。在字典的例子中,你会插入一个字符串吗。清空列表?不确定如果你得到一个列表{"46","46.1"}-你会显示为"46(null,1("还是。。。"46(0,1("。。。还是"46(,1("或"46(1("?