开篇语: html
上次发布的 《LINQ:进阶 - LINQ 标准查询操做概述》(90+赞) 社会反响不错,但本身却始终以为缺点什么!“纸上得来终觉浅,绝知此事要躬行”,没错,就是实战!此次让咱们一块儿来看看一些操做字符串的技巧,也许能引咱们从不一样的角度思考问题,从而走出思惟的死角!node
LINQ 可用于查询和转换字符串和字符串集合。它对文本文件中的半结构化数据尤为有用。LINQ 查询可与传统的字符串函数和正则表达式结合使用。git
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 数组的计数相同,则在单词中找到了全部的单词,且返回原始句子。正则表达式
由于 String 类实现泛型 IEnumerable<T> 接口,因此能够将任何字符串做为字符序列进行查询。可是,这不是 LINQ 的常见用法。若要执行复杂的模式匹配操做,请使用 Regex 类。 数据库
下面的示例查询一个字符串以肯定它包含的数字的数目。express
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 }
此示例演示如何使用 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 集合,所以必须显式声明查询中的范围变量的类型。缓存
Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra
Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi
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 }
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
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 }
此示例还演示如何从方法返回查询变量。ide
逗号分隔值 (CSV) 文件是一种文本文件,一般用于存储电子表格数据或其余由行和列表示的表格数据。经过使用 Split 方法分隔字段,能够很是轻松地使用 LINQ 来查询和操做 CSV 文件。事实上,可使用此技术来从新排列任何结构化文本行部分;此技术不局限于 CSV 文件。
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
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
Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi
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}\":");
1 private static void OutputQueryResult(IEnumerable<string> querys, string title) 2 { 3 Console.WriteLine(Environment.NewLine + title); 4 foreach (var query in querys) 5 { 6 Console.WriteLine(query); 7 } 8 9 Console.WriteLine($"{querys.Count()} total names in list"); 10 }
1 //每行 names.csv 包含姓氏,名字,和身份证号,以逗号分隔。例如,Omelchenko,Svetlana,111 2 var names = File.ReadAllLines(@"names.csv"); 3 //每行 scores.csv 包括身份证号码和四个测试评分,以逗号分隔。例如,111,97,92,81,60 4 var scores = File.ReadAllLines(@"scores.csv"); 5 6 //使用一个匿名的类型合并数据源。 7 //【注意】动态建立一个 int 的考试成绩成员列表。 8 //跳过度割字符串中的第一项,由于它是学生的身份证,不是一个考试成绩 9 var students = from name in names 10 let t = name.Split(',') 11 from score in scores 12 13 let t2 = score.Split(',') 14 where t[2] == t2[0] 15 select new 16 { 17 FirstName = t[0], 18 LastName = t[1], 19 ID = Convert.ToInt32(t[2]), 20 ExamScores = (from scoreAsText in t2.Skip(1) 21 select Convert.ToInt32(scoreAsText)).ToList() 22 }; 23 24 foreach (var student in students) 25 { 26 Console.WriteLine( 27 $"The average score of {student.FirstName} {student.LastName} is {student.ExamScores.Average()}."); 28 }
Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra
Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi
1 var fileA = File.ReadAllLines(@"names1.txt"); 2 var fileB = File.ReadAllLines(@"names2.txt"); 3 4 //并集:链接并删除重复的名字 5 var mergeQuery = fileA.Union(fileB); 6 //根据姓氏的首字母对姓名进行分组 7 var query = from name in mergeQuery 8 let t = name.Split(',') 9 group name by t[0][0] into g 10 orderby g.Key 11 select g; 12 13 //注意嵌套的 foreach 循环 14 foreach (var g in query) 15 { 16 var fileName = @"testFile_" + g.Key + ".txt"; 17 Console.WriteLine(g.Key + ":"); 18 19 //写入文件 20 using (var sw = new StreamWriter(fileName)) 21 { 22 foreach (var name in g) 23 { 24 sw.WriteLine(name); 25 Console.WriteLine(" " + name); 26 } 27 } 28 }
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
Omelchenko,Svetlana,111 O'Donnell,Claire,112 Mortensen,Sven,113 Garcia,Cesar,114 Garcia,Debra,115 Fakhouri,Fadi,116 Feng,Hanying,117 Garcia,Hugo,118 Tucker,Lance,119 Adams,Terry,120 Zabokritski,Eugene,121 Tucker,Michael,122
scores.csv:此文件表示电子表格数据。第 1 列是学生的 ID,第 2 至 5 列是测验分数。
names.csv:此文件表示一个电子表格。该电子表格包含学生的姓氏、名字和学生 ID。
1 var names = File.ReadAllLines(@"names.csv"); 2 var scores = File.ReadAllLines(@"scores.csv"); 3 4 //Name: Last[0], First[1], ID[2] 5 // Omelchenko, Svetlana, 11 6 //Score: StudentID[0], Exam1[1] Exam2[2], Exam3[3], Exam4[4] 7 // 111, 97, 92, 81, 60 8 9 //该查询基于 id 链接两个不一样的电子表格 10 var query = from name in names 11 let t1 = name.Split(',') 12 from score in scores 13 let t2 = score.Split(',') 14 where t1[2] == t2[0] 15 orderby t1[0] 16 select $"{t1[0]},{t2[1]},{t2[2]},{t2[3]},{t2[4]}"; 17 18 //输出 19 OutputQueryResult(query, "Merge two spreadsheets:");
1 private static void OutputQueryResult(IEnumerable<string> querys, string title) 2 { 3 Console.WriteLine(Environment.NewLine + title); 4 foreach (var query in querys) 5 { 6 Console.WriteLine(query); 7 } 8 9 Console.WriteLine($"{querys.Count()} total names in list"); 10 }
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:假定第一列表示学员 ID,后面几列表示四次考试的分数。
1 var scores = File.ReadAllLines(@"scores.csv"); 2 3 //指定要计算的列 4 const int examNum = 3; 5 6 //scores.csv 格式: 7 //Student ID Exam#1 Exam#2 Exam#3 Exam#4 8 //111, 97, 92, 81, 60 9 10 //+1 表示跳过第一列 11 //计算但一列 12 SingleColumn(scores, examNum+1); 13 14 Console.WriteLine(); 15 16 //计算多列 17 MultiColumns(scores);
1 private static void SingleColumn(IEnumerable<string> strs, int examNum) 2 { 3 Console.WriteLine("Single Column Query:"); 4 5 //查询分两步: 6 // 1.分割字符串 7 // 2.对要计算的列的值转换为 int 8 var query = from str in strs 9 let t = str.Split(',') 10 select Convert.ToInt32(t[examNum]); 11 12 //对指定的列进行统计 13 var average = query.Average(); 14 var max = query.Max(); 15 var min = query.Min(); 16 17 Console.WriteLine($"Exam #{examNum}: Average:{average:##.##} High Score:{max} Low Score:{min}"); 18 } 19 20 private static void MultiColumns(IEnumerable<string> strs) 21 { 22 Console.WriteLine("Multi Column Query:"); 23 24 //查询步骤: 25 // 1.分割字符串 26 // 2.跳过 id 列(第一列) 27 // 3.将当前行的每一个评分都转换成 int,并选择整个序列做为一行结果。 28 var query = from str in strs 29 let t1 = str.Split(',') 30 let t2 = t1.Skip(1) 31 select (from t in t2 32 select Convert.ToInt32(t)); 33 34 //执行查询并缓存结果以提升性能 35 var results = query.ToList(); 36 //找出结果的列数 37 var count = results[0].Count(); 38 39 //执行统计 40 //为每一列分数的循环执行一次循环 41 for (var i = 0; i < count; i++) 42 { 43 var query2 = from result in results 44 select result.ElementAt(i); 45 46 var average = query2.Average(); 47 var max = query2.Max(); 48 var min = query2.Min(); 49 50 //+1 由于 #1 表示第一次考试 51 Console.WriteLine($"Exam #{i + 1} Average: {average:##.##} High Score: {max} Low Score: {min}"); 52 } 53 54 }
查询的工做原理是使用 Split 方法将每一行文本转换为数组。每一个数组元素表示一列。最后,每一列中的文本都转换为其数字表示形式。若是文件是制表符分隔文件,只需将 Split 方法中的参数更新为 \t。
================================================== 传送门分割线 ==================================================
LINQ 其它随笔 - 《开始使用 LINQ》
================================================== 传送门分割线 ==================================================
【首联】http://www.cnblogs.com/liqingwen/p/5814204.html
【参考】https://msdn.microsoft.com/zh-cn/library/bb397915(v=vs.100).aspx