这类似于我在这里问过的另一个问题,但它与我的不同之处在于,我还没有能够自己找到答案。我认为介绍我的问题的最好方法是用图片:
我有几个文本文件(本例中为4个),每个文件都有以下格式的数百万行数据:
TIME DATA
File #1
104500 4098
104501 34098
104502 1321
104502 3408
104503 4587
104503 1204
104503 49858
104504 1029
104505 4058
104506 7576
File #2
104500 23408
104500 2131
104501 5686
104502 6839
104502 21838
104503 86760
104503 20812
104503 85719
104504 4877
104505 2220
104506 4847
File #3
104500 23042
104501 12391
104501 5857
104501 6979
104502 2196
104502 21039
104503 9263
104503 50573
104503 18361
104504 17545
104505 67612
104506 21075
File #4
104500 1193
104501 8664
104502 1028
104502 68561
104503 69178
104503 1230
104503 12048
104504 8843
104505 9910
104506 53978
104506 13722
问题是一个文件中的给定时间可能比其他文件中的数据条目更多或更少。例如,在上图中,文件#1中只有一个条目为10:45:00,但文件#2中有两个条目为10:45:00。我希望每个文件的每个条目都有相同数量的行,所以在我的文件#1和#2的例子中,在第一个'104500 4098'行之后会添加一个'填充'行,并且这条填充线只是它上面一条线的精确副本(在这种情况下为104500 4098)。理想情况下,这些“填充”行将插入到正在读取的文本文件中,而不会写入新的文本文件。
到目前为止我想出的是我需要:
- 计算每个给定时间的行数
- 找出每个给定时间内具有最多行数的文件
- 在必要时在每个文件中插入“填充”行
不幸的是,我真的不知道怎么做。我有一些想法,但是在这一点上它们都很模糊,所以我真的不知道我应该读些什么。到目前为止我唯一能提出的真正的代码是我可以使用Directory.GetFiles将目录中的所有文件分配给一个数组,然后我可以循环遍历所有文件,但这不会让我很远。
这些数据行由程序生成,然后程序将行写入文本文件。我无法访问生成数据行的代码。
如果有人对如何实现这一点有任何想法,我们将非常感激提示。
答案 0 :(得分:3)
让我们将这种情况提炼到只有两个时间戳,我会提供答案。
下面我重新创建了三个文件。每个文件缓冲区的时间戳都是104500和104501,而第二个文件有两个501,表示为正在处理的问题。这意味着file1和file3只有一个501.然后我模拟从文件中解析数据并将项目解析为具有文件ID,数据和时间戳的类持有者。一旦为每个文件缓冲区获取了所有数据,我就会合并数据。利用一个IEnumerable列表中的数据,我按时间分组; 这是最终处理,分组的关键。
现在您要做的就是提取感兴趣的时间单位并对该集合进行计算,同时记住file1和file3的缺失数据。然后,您可以操作分组结果,为缺失添加更多时间戳,或者只弹出最后一个值。
答案:无论如何,请勿在文件中工作,将数据存入内存并在进行计算时调整丢失的数据。
以下是数据的样子,看看它是如何按照104500和104501的时间进行分组(密钥)。一个justs从文件1 - 3中的所有值中提取该分组的目标时间并进行计算
这是让它组织起来的代码(转储方法来自Linqpad,它显示了我在图片中显示的数据)
void Main()
{
string File1 = @"104500 1
104501 1
";
string File2 =
@"104500 2
104501 2
104501 4
";
string File3 =
@"104500 5
104501 5
";
var ds3 = ExtractData(File1, 1).Union( ExtractData(File2, 2) )
.Union( ExtractData(File3, 3))
.GroupBy (d => d.Time );
ds3.Dump();
}
public static IEnumerable<DataAndTime> ExtractData(string data, int fileID)
{
string pattern = @"^(?<Time>[^\s]+)(?:\s+)(?<Data>[^\s]+)";
return Regex.Matches(data, pattern, RegexOptions.Multiline)
.OfType<Match>()
.Select (m => new DataAndTime()
{
FileID = fileID,
Time = m.Groups["Time"].Value,
Data = int.Parse(m.Groups["Data"].Value)
}
);
}
// Define other methods and classes here
public class DataAndTime
{
public int FileID { get; set; }
public string Time { get; set; }
public int Data { get; set; }
}
更新:在Timeslice提取
以下是将索引值提取到目标时间的代码。我认为这是一个时间片。当一个人要求一个时间片时,代码必须足够智能,以便在索引(时间片)要求超出范围时将最后一个值标识为默认值。
例如,文件1有一个项目,如果我要求时间片索引2,它应该检索最后一个值,即第一个。如果我要求索引100,它也应该返回该值。
那么让我们看一下时间104501并获取该数据。然后我们将按文件ID分组
var ds3 = ExtractData(File1, 1).Union( ExtractData(File2, 2) )
.Union( ExtractData(File3, 3))
.GroupBy (d => d.Time )
.First (d => d.Key == "104501")
.GroupBy (d => d.FileID) ;
我们的数据对于ds3来说是这样的:
现在我们需要创建一个方法来处理时间片的提取并处理缺失的索引(切片)值。为此,我将使用DefaultIfEmpty指定如果我们要求太多,文件的最后一个值将是默认值。这是代码
public static int ValueAtSnapshotSlice(int slice, IEnumerable<DataAndTime> data)
{
var defaultData = data.Last();
return data.Take(slice)
.DefaultIfEmpty(defaultData)
.Last().Data;
}
然后,如果我们查看文件2并询问不存在的时间片1,2和3(甚至4+),我们期望2,4,4,4作为结果值。以下是对上面的ds3的调用
ValueAtSnapshotSlice(1, ds3.First (d => d.Key == 2)); // 2
ValueAtSnapshotSlice(2, ds3.First (d => d.Key == 2)); // 4
ValueAtSnapshotSlice(3, ds3.First (d => d.Key == 2)); // 4
ValueAtSnapshotSlice(4, ds3.First (d => d.Key == 2)); // 4
答案 1 :(得分:1)
这并不简单。对于初学者,您不能只在文本文件中插入一行。您需要将文件复制到新文件,插入流程中所需的行。然后,您可以删除旧文件并重命名新文件以取代它。
我也假设您在处理它们之前不知道哪个文件需要添加行。这意味着您需要将所有文件加载到内存中,在那里处理它们,然后写出结果,或者在每个文件上打开一个流,再为每个文件添加一个新文件,并将数据从旧流处理到新流对于每个文件,根据需要插入行。