LINQ:To Objects - 如何操作字符串

Posted 反骨仔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LINQ:To Objects - 如何操作字符串相关的知识,希望对你有一定的参考价值。

Linq To Objects - 如何操作字符串

  开篇语:  

  上次发布的 《LINQ:进阶 - LINQ 标准查询操作概述》(90+赞) 社会反响不错,但自己却始终觉得缺点什么!“纸上得来终觉浅,绝知此事要躬行”,没错,就是实战!这次让我们一起来看看一些操作字符串的技巧,也许能引我们从不同的角度思考问题,从而走出思维的死角!

  LINQ 可用于查询和转换字符串和字符串集合。它对文本文件中的半结构化数据尤其有用。LINQ 查询可与传统的字符串函数和正则表达式结合使用。

  例如,可以使用 Split 或 Split 方法来创建字符串数组,然后可以使用 LINQ 来查询或修改此数组;可以在 LINQ 查询的 where 子句中使用 IsMatch 方法;可以使用 LINQ 来查询或修改由正则表达式返回的 MatchCollection 结果......
 

查询文本块

  可以通过使用 Split 方法或 Split 方法将文本块拆分成可查询的较小字符串数组,来查询、分析和修改文本块。可以将源文本拆分成词语、句子、段落、页或任何其他条件,然后根据查询的需要,执行其他拆分。  
                    --演示如何使用 LINQ 对文本进行简单查询
                    --演示如何按任意边界拆分文本文件,以及如何对每个部分执行查询
                    --演示字符串是可查询类型
                    --演示如何在 LINQ 查询中使用正则表达式来对筛选的查询结果执行复杂模式匹配

查询文本格式的半结构化数据

   许多不同类型的文本文件都包含一系列行,通常具有类似的格式,例如制表符分隔或逗号分隔文件或固定长度的行。在将此类文本文件读取到内存中后,可以使用 LINQ 来查询和/或修改行。LINQ 查询还简化了组合多个源的数据的任务。  
                    --演示如何查找位于一个列表中但不在另一个列表中的所有字符串
                    --演示如何根据任意词语或字段排序文本行
                    --演示如何重新排列 .csv 文件中的某一行中的字段
                    --演示如何以各种方式组合字符串列表
                    --演示如何将多个文本文件用作数据源来创建对象集合
                    --演示如何将单个文件用作数据源来创建新文件
                    --演示如何使用匹配键将两个列表中的字符串组合为单个字符串
                    --演示如何对 .csv 文件中的文本数据执行数学运算
 

一、如何统计单词在字符串出现次数 

  请注意,若要执行计数,请先调用 Split 方法来创建词数组。Split 方法存在性能开销。如果对字符串执行的唯一操作是计数词,则应考虑改用 Matches 或 IndexOf 方法。
 1             const string text = @"Historically, the world of data and the world of objects" +
 2                                 @" have not been well integrated. Programmers work in C# or Visual Basic" +
 3                                 @" and also in SQL or XQuery. On the one side are concepts such as classes," +
 4                                 @" objects, fields, inheritance, and .NET Framework APIs. On the other side" +
 5                                 @" are tables, columns, rows, nodes, and separate languages for dealing with" +
 6                                 @" them. Data types often require translation between the two worlds; there are" +
 7                                 @" different standard functions. Because the object world has no notion of query, a" +
 8                                 @" query can only be represented as a string without compile-time type checking or" +
 9                                 @" IntelliSense support in the IDE. Transferring data from SQL tables or XML trees to" +
10                                 @" objects in memory is often tedious and error-prone.";
11 
12             const string searchTerm = "data";
13 
14             //字符串转换成数组
15             var source = text.Split(new[] { \'.\', \'?\', \'!\', \' \', \';\', \':\', \',\' }, StringSplitOptions.RemoveEmptyEntries);
16 
17             //创建查询,并忽略大小写比较
18             var matchQuery = from word in source
19                              where string.Equals(word, searchTerm, StringComparison.InvariantCultureIgnoreCase)
20                              select word;
21 
22             //统计匹配数量
23             var wordCount = matchQuery.Count();
24             Console.WriteLine($"{wordCount} occurrences(s) of the search term \\"{searchTerm}\\" were found.");

 

二、如何查询包含指定的一组单词的句子

  此示例演示如何查找文本文件中包含指定的一组单词中每个单词的匹配项的句子。虽然在此示例中搜索条件数组是硬编码的,但也可以在运行时动态填充此数组。
 1             const string text = @"Historically, the world of data and the world of objects " +
 2                                 @"have not been well integrated. Programmers work in C# or Visual Basic " +
 3                                 @"and also in SQL or XQuery. On the one side are concepts such as classes, " +
 4                                 @"objects, fields, inheritance, and .NET Framework APIs. On the other side " +
 5                                 @"are tables, columns, rows, nodes, and separate languages for dealing with " +
 6                                 @"them. Data types often require translation between the two worlds; there are " +
 7                                 @"different standard functions. Because the object world has no notion of query, a " +
 8                                 @"query can only be represented as a string without compile-time type checking or " +
 9                                 @"IntelliSense support in the IDE. Transferring data from SQL tables or XML trees to " +
10                                 @"objects in memory is often tedious and error-prone.";
11 
12             //将文本块切割成数组
13             var sentences = text.Split(\'.\', \'?\', \'!\');
14 
15             //定义搜索条件,此列表可以运行时动态添加
16             string[] wordsToMatch = { "Historically", "data", "integrated" };
17 
18             var match = from sentence in sentences
19                         let t =
20                             sentence.Split(new char[] { \'.\', \'?\', \'!\', \' \', \';\', \':\', \',\' }, StringSplitOptions.RemoveEmptyEntries)
21                         where t.Distinct().Intersect(wordsToMatch).Count() == wordsToMatch.Length   //去重,取交集后的数量对比
22                         select sentence;
23 
24             foreach (var s in match)
25             {
26                 Console.WriteLine(s);
27             }

  

  查询运行时首先将文本拆分成句子,然后将句子拆分成包含每个单词的字符串数组。对于每个这样的数组,Distinct<TSource> 方法移除所有重复的单词,然后查询对单词数组和 wordstoMatch 数组执行 Intersect<TSource> 操作。如果交集的计数与 wordsToMatch 数组的计数相同,则在单词中找到了所有的单词,且返回原始句子。

  在对 Split 的调用中,使用标点符号作为分隔符,以从字符串中移除标点符号。如果您没有这样做,则假如您有一个字符串“Historically,”,该字符串不会与 wordsToMatch 数组中的“Historically”相匹配。根据源文本中标点的类型,您可能必须使用其他分隔符。
 

三、如何在字符串中查询字符

  因为 String 类实现泛型 IEnumerable<T> 接口,所以可以将任何字符串作为字符序列进行查询。但是,这不是 LINQ 的常见用法。若要执行复杂的模式匹配操作,请使用 Regex 类。  

  下面的示例查询一个字符串以确定它包含的数字的数目。

 1             const string aString = "ABCDE99F-J74-12-89A";
 2 
 3             //只选择数字的字符 
 4             var digits = from ch in aString
 5                          where char.IsDigit(ch)
 6                          select ch;
 7 
 8             Console.Write("digit: ");
 9 
10             foreach (var n in digits)
11             {
12                 Console.Write($"{n} ");
13             }
14 
15             Console.WriteLine();
16 
17             //选择第一个“-”之前的所有字符
18             var query = aString.TakeWhile(x => x != \'-\');
19 
20             foreach (var ch in query)
21             {
22                 Console.Write(ch);
23             }

 

四、如何用正则表达式结合 LINQ 查询

  此示例演示如何使用 Regex 类创建正则表达式以便在文本字符串中进行更复杂的匹配。使用 LINQ 查询可以方便地对您要用正则表达式搜索的文件进行准确筛选,以及对结果进行加工。  

 1             //根据不同版本的 vs 修改路径
 2             const string floder = @"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\";
 3             var infoes = GetFiles(floder);
 4             //创建正则表达式来寻找所有的"Visual"
 5             var searchTerm = new Regex(@"Visual (Basic|C#|C\\+\\+|J#|SourceSafe|Studio)");
 6 
 7             //搜索每一个“.html”文件
 8             //通过 where 找到匹配项
 9             //【注意】select 中的变量要求显示声明其类型,因为 MatchCollection 不是泛型 IEnumerable 集合
10             var query = from fileInfo in infoes
11                         where fileInfo.Extension == ".html"
12                         let text = File.ReadAllText(fileInfo.FullName)
13                         let matches = searchTerm.Matches(text)
14                         where matches.Count > 0
15                         select new
16                         {
17                             name = fileInfo.FullName,
18                             matchValue = from Match match in matches select match.Value
19                         };
20 
21             Console.WriteLine($"The term \\"{searchTerm}\\" was found in:");
22 
23             foreach (var q in query)
24             {
25                 //修剪匹配找到的文件中的路径
26                 Console.WriteLine($"{q.name.Substring(floder.Length - 1)}");
27 
28                 //输出找到的匹配值
29                 foreach (var v in q.matchValue)
30                 {
31                     Console.WriteLine(v);
32                 }
33             }
1         private static IList<FileInfo> GetFiles(string path)
2         {
3             var files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);
4 
5             return files.Select(file => new FileInfo(file)).ToList();
6         }

  

  您还可以查询由 RegEx 搜索返回的 MatchCollection 对象。在此示例中,结果中仅生成每个匹配项的值。但也可使用 LINQ 对该集合执行各种筛选、排序和分组操作。

  【注意】由于 MatchCollection 是非泛型 IEnumerable 集合,因此必须显式声明查询中的范围变量的类型。

 

五、如何查找两个集合间的差异

  此示例演示如何使用 LINQ 对两个字符串列表进行比较,并输出那些位于 names1.txt 中但不在 names2.txt 中的行。
Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra
names1.txt
Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi
names2.txt
 1             //创建数据源
 2             var names1Text = File.ReadAllLines(@"names1.txt");
 3             var names2Text = File.ReadAllLines(@"names2.txt");
 4 
 5             //创建查询,这里必须使用方法语法
 6             var query = names1Text.Except(names2Text);
 7 
 8             //执行查询
 9             Console.WriteLine("The following lines are in names1.txt but not names2.txt");
10             foreach (var name in query)
11             {
12                 Console.WriteLine(name);
13             }    

 

  【注意】某些类型的查询操作(如 Except<TSource>Distinct<TSource>Union<TSource> 和 Concat<TSource>)只能用基于方法的语法表示。
 

六、如何排序或过滤任意单词或字段的文本数据

  下面的示例演示如何按结构化文本(如逗号分隔值)行中的任意字段对该文本行进行排序。可在运行时动态指定该字段。假定 scores.csv 中的字段表示学生的 ID 号,后面跟四个测验分数。  
111, 97, 92, 81, 60
112, 75, 84, 91, 39
113, 88, 94, 65, 91
114, 97, 89, 85, 82
115, 35, 72, 91, 70
116, 99, 86, 90, 94
117, 93, 92, 80, 87
118, 92, 90, 83, 78
119, 68, 79, 88, 92
120, 99, 82, 81, 79
121, 96, 85, 91, 60
122, 94, 92, 91, 91
scores.csv
 1             //创建数据源
 2             var scores = File.ReadAllLines(@"scores.csv");
 3             //可以改为 0~4 的任意值
 4             const int sortField = 1;
 5 
 6             //演示从方法返回查询
 7             //返回查询变量,非查询结果
 8             //这里执行查询
 9             foreach (var score in RunQuery(scores, sortField))
10             {
11                 Console.WriteLine(score);
12             }
 1         private static IEnumerable<string> RunQuery(IEnumerable<string> score, int num)
 2         {
 3             //分割字符串来排序
 4             var query = from line in score
 5                         let fields = line.Split(\',\')
 6                         orderby fields[num] descending
 7                         select line;
 8 
 9             return query;
10         }

  

  此示例还演示如何从方法返回查询变量。

 

七、如何对一个分割的文件的字段重新排序

  逗号分隔值 (CSV) 文件是一种文本文件,通常用于存储电子表格数据或其他由行和列表示的表格数据。通过使用 Split 方法分隔字段,可以非常轻松地使用 LINQ 来查询和操作 CSV 文件。事实上,可以使用此技术来重新排列任何结构化文本行部分;此技术不局限于 CSV 文件。  

  在下面的示例中,假定有三列分别代表学生的“姓氏”、“名字”和“ID”。这些字段基于学生的姓氏按字母顺序排列。查询生成一个新序列,其中首先出现的是 ID 列,后面的第二列组合了学生的名字和姓氏。根据 ID 字段重新排列各行。结果保存到新文件,但不修改原始数据。  
Adams,Terry,120
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Cesar,114
Garcia,Debra,115
Garcia,Hugo,118
Mortensen,Sven,113
O\'Donnell,Claire,112
Omelchenko,Svetlana,111
Tucker,Lance,119
Tucker,Michael,122
Zabokritski,Eugene,121
spreadsheet1.csv
 1             //数据源
 2             var lines = File.ReadAllLines(@"spreadsheet1.csv");
 3             //将旧数据的第2列的字段放到第一位,逆向结合第0列和第1列的字段
 4             var query = from line in lines
 5                         let t = line.Split(\',\')
 6                         orderby t[2]
 7                         select $"{t[2]}, {t[1]} {t[0]}";
 8 
 9             foreach (var q in query)
10             {
11                 Console.WriteLine(q);
12             }
13 
14             //写入文件
15             File.WriteAllLines("spreadsheet2.csv", query);

 

八、如何组合和比较字符串集合

  此示例演示如何合并包含文本行的文件,然后排序结果。具体来说,此示例演示如何对两组文本行执行简单的串联、联合和交集。  

Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra
names1.txt
Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi
names2.txt
 1             var names1Text = File.ReadAllLines(@"names1.txt");
 2             var names2Text = File.ReadAllLines(@"names2.txt");
 3 
 4             //简单连接,并排序。重复保存。 
 5             var concatQuery = names1Text.Concat(names2Text).OrderBy(x => x);
 6             OutputQueryResult(concatQuery, "Simple concatenate and sort. Duplicates are preserved:");
 7 
 8             //基于默认字符串比较器连接,并删除重名。 
 9             var unionQuery = names1Text.Union(names2Text).OrderBy(x => x);
10             OutputQueryResult(unionQuery, "Union removes duplicate names:");
11 
12             //查找在两个文件中出现的名称 
13             var intersectQuery = names1Text.Intersect(names2Text).OrderBy(x => x);
14             OutputQueryResult(intersectQuery, "Merge based on intersect:");
15 
16             //在每个列表中找到匹配的字段。使用 concat 将两个结果合并,然后使用默认的字符串比较器进行排序
17             const string nameMatch = "Garcia";
18             var matchQuery1 = from name in names1Text
19                               let t = name.Split(\',\')
20                               where t[0] == nameMatch
21                               select name;
22             var matchQuery2 = from name in names2Text
23                               let t = name.Split(\',\')
24                               where t[0] == nameMatch
25                               select name;
26 
27             var temp = matchQuery1.Concat(matchQuery2).OrderBy(x => x);
28             OutputQueryResult(temp, $"Concat based on partial name match \\"{nameMatch}\\":");