C# 用Linq解析文本数据文件
我有一个记录的大文本文件,每个记录用一个换行符分隔。每个记录都有一个两位数的前缀,用于指定其类型。下面是一个例子:C# 用Linq解析文本数据文件,c#,.net,linq,parsing,functional-programming,C#,.net,Linq,Parsing,Functional Programming,我有一个记录的大文本文件,每个记录用一个换行符分隔。每个记录都有一个两位数的前缀,用于指定其类型。下面是一个例子: .... 30AA ALUMINIUM ALLOY LMELMEUSD2.00 0.35 5101020100818 40AADFALUMINIUM ALLOY USD USD 100 1 0.20000 1.00 0 100 140003 50201008180.999993 0.00 0.00 120100818 6
....
30AA ALUMINIUM ALLOY LMELMEUSD2.00 0.35 5101020100818
40AADFALUMINIUM ALLOY USD USD 100 1 0.20000 1.00 0 100 140003
50201008180.999993 0.00 0.00 120100818
60 0F 1 222329 1.000000 0 0 -4667 -4667 4667 4667
50201008190.999986 0.00 0.00 120100819
60 0F 1 222300 1.000000 0 0 -4667 -4667 4667 4667
40AADOALUMINIUM ALLOY USD USD 100 1 0.20000 1.00 0 100 140001
50201009150.000000 0.17 0.17 120100915
60 1200C 1 101779 0.999800 0 0 -4666 -4666 4665 4665
60 1200P 1 0 0.000000 0 0 0 0 0 0
60 1225C 1 99279 0.999800 -1 -1 -4667 -4667 4665 4665
60 1225P 1 0 0.000000 0 0 0 0 0 0
60 1250C 1 96780 0.999800 0 0 -4666 -4666 4665 4665
60 1250P 1 0 0.000000 0 0 0 0 0 0
60 1275C 1 94280 0.999800 -1 -1 -4667 -4667 4665 4665
60 1275P 1 0 0.000000 0 0 0 0 0 0
60 1300C 1 91781 0.999800 0 0 -4666 -4666 4665 4665
60 1300P 1 0 0.000000
.......
该文件包含基于两位数前缀的层次关系。您可以将包含“40”行的“30”行视为子行;“40”行包含“50”,而“50”s包含“60”。解析后,这些行及其相关前缀显然将映射到clr类型、“30”映射到“ContractGroup”、“40”映射到“InstrumentTypeGroup”、“50”映射到“ExpirationGroup”等
我尝试采用函数式方法进行解析,并使用延迟加载方法减少内存消耗,因为这个文件非常大。我的第一步是创建一个生成器,一次生成一条线,如下所示:
public static IEnumerable<string> TextFileLineEnumerator()
{
using (StreamReader sr = new StreamReader("BigDataFile.txt"))
{
while (!sr.EndOfStream)
{
yield return sr.ReadLine();
}
}
}
public static IEnumerable<IEnumerable<string>> PartitionLines(
this IEnumerable<string> source,
Func<string, string> groupMarkerSelector,
string delimeter)
{
List<string> currentGroup = new List<string>();
foreach (string line in source)
{
var key = groupMarkerSelector(line);
if (delimeter == key && currentGroup.Count > 0)
{
yield return currentGroup;
currentGroup = new List<string>();
}
currentGroup.Add(line);
}
if (currentGroup.Count > 0)
yield return currentGroup;
}
var line30Groups =
TextFileLineEnumerator().
PartitionLines(l => l.Substring(0, 2), "30");
这给了我所有“30”的子行(但不幸的是忽略了“30”行本身)。此查询显然需要子查询(通过select)收集行并将其投影到适当的类型中,并使用适当的组合(包含InstrumentTypeGroup列表的ContractGroups等)
这个问题很可能归结为我缺乏函数式编程的经验,所以如果有人对这种解析有任何建议,那将是很有帮助的,谢谢-我还不完全清楚你到底想做什么,但我如何解决这个问题,首先要编写一个
PartitionLines
函数,如下所示:
public static IEnumerable<string> TextFileLineEnumerator()
{
using (StreamReader sr = new StreamReader("BigDataFile.txt"))
{
while (!sr.EndOfStream)
{
yield return sr.ReadLine();
}
}
}
public static IEnumerable<IEnumerable<string>> PartitionLines(
this IEnumerable<string> source,
Func<string, string> groupMarkerSelector,
string delimeter)
{
List<string> currentGroup = new List<string>();
foreach (string line in source)
{
var key = groupMarkerSelector(line);
if (delimeter == key && currentGroup.Count > 0)
{
yield return currentGroup;
currentGroup = new List<string>();
}
currentGroup.Add(line);
}
if (currentGroup.Count > 0)
yield return currentGroup;
}
var line30Groups =
TextFileLineEnumerator().
PartitionLines(l => l.Substring(0, 2), "30");
现在,您已经将行分组,每次看到“30”时都会有一组新的行。您可以进一步细分:
var line3040Groups =
TextFileLineEnumerator().
PartitionLines(l => l.Substring(0, 2), "30").Select(g =>
g.PartitionLines(l => l.Substring(0, 2), "40"));
现在您已经在“30”下得到了分组中的行,并且每个分组都是每个子级“40”下的可枚举组,依此类推
这是未经测试的,可能会更干净,但我希望你能理解。这样的文本文件通常在行中有一个分隔符。这似乎有一个制表符作为分隔符,您能确认或拒绝吗?如果您不确定,只需取一行并用
字符串拆分即可。拆分
,字符为'\t'
。LINQ不是为此而设计的,请改用正则表达式。@leppie:我觉得正则表达式更适合标记字符串。如何使用正则表达式将继承人的体系结构文件解析为组合数据结构?我想您应该返回currentGroup.ToArray()
或类似的内容,而不是currentGroup
本身,因为否则OP可能会调用分区行(s=>s.Substring(0,2),“30”)。ToList()
得到一大堆相同的列表的实例
对象只有一组元素。Dan Tao,我同意,我只是匆忙地把它搞砸了。我认为最干净的方法是currentGroup=newlist()
而不是清除它。我编辑了我的帖子。很好的解决方案。我确实遇到了Dan提到的关于重复列表实例的问题,但根据他的建议列出了该组,解决了该问题。谢谢你的努力!刚才看到的编辑,这也很有效,不会立即强制进行更可取的评估