请考虑以下记录
我正在尝试按Robot Name
然后按date
+ Left Factory
时间对数据进行扁平化和分组,然后对该日期和时间进行地址分组。请注意,某些Left Factory
时间是相同的。
我写了下面的代码,它可以工作。它给了我想要的输出。我是Perl开发人员,因此您从下面的思路中看到的是。我相信在LINQ中有更好的方法。请一点帮助。
static void Main(string[] args)
{
if (args.Length < 0){
Console.WriteLine("Input file name is required");
return;
}
List<string> rawlst = File.ReadAllLines(args[0]).ToList<string>();
Dictionary<string, Dictionary<DateTime, List<string>>> dicDriver = new Dictionary<string, Dictionary<DateTime, List<string>>>();
foreach (string ln in rawlst)
{
try
{
List<string> parts = new List<string>();
parts = ln.Split(',').ToList<string>();
string[] dtparts = parts[1].Split('/');
string[] dttime = parts[15].Split(':');
DateTime dtrow = new DateTime(
int.Parse(dtparts[2]), int.Parse(dtparts[0]), int.Parse(dtparts[1]),
int.Parse(dttime[0]), int.Parse(dttime[1]), int.Parse(dttime[2]));
string rowAddress = parts[7] + " " + parts[9] + " " + parts[10] + " " + parts[11];
if (!dicDriver.Keys.Contains(parts[3]))
{
Dictionary<DateTime, List<string>> thisRec = new Dictionary<DateTime, List<string>>();
thisRec.Add(dtrow, new List<string>() { rowAddress });
dicDriver.Add(parts[3], thisRec);
}
else
{
Dictionary<DateTime, List<string>> thisDriver = new Dictionary<DateTime, List<string>>();
thisDriver = dicDriver[parts[3]];
if (!thisDriver.Keys.Contains(dtrow))
{
dicDriver[parts[3]].Add(dtrow, new List<string>() { rowAddress });
}
else
{
dicDriver[parts[3]][dtrow].Add(rowAddress);
}
}
}
catch (Exception e)
{
Console.WriteLine("ERROR:" + ln);
}
}
//output
string filename = DateTime.Now.Ticks.ToString() + ".out";
foreach (var name in dicDriver.Keys)
{
foreach (var dd in dicDriver[name])
{
Console.Write(name + "," + dd.Key + ",");
File.AppendAllText(filename, name + "," + dd.Key + Environment.NewLine);
foreach (var addr in dd.Value)
{
Console.Write("\t\t" + addr + Environment.NewLine);
File.AppendAllText(filename, "\t" + addr + Environment.NewLine);
}
}
Console.Write(Environment.NewLine);
File.AppendAllText(filename, Environment.NewLine);
}
Console.ReadLine();
}
答案 0 :(得分:1)
您应该分离您的关注点:将输入与处理以及输出分离。
例如:假设您必须从数据库而不是CSV文件读取输入?这会严重改变您处理获取数据的方式吗?在设计中,获取数据与处理过程混合在一起:尽管您知道要处理的数据包含类似FactoryProcesses
的内容,但是您还是决定将每个FactoryProcess
呈现为字符串。 FactoryProcess
不是字符串。它描述了在工厂中如何,何时以及由谁处理某些东西。那不是字符串,是吗?但是,它可能在内部用字符串表示,但外界不应该知道这一点。这样,如果您将FactoryProcess
从CSV文件读取更改为数据库提供的内容,则FactoryProcess
的用户将看不到任何区别。
关注点的分离使您的代码更易于理解,更易于测试,更易于更改和更好地重用。
所以让我们分开吧!
IEnumerable<FactoryProcess> ReadFactoryProcesses(string fileName)
{
// TODO: check fileName not null, file exists
using (var fileReader = new StreamReader(fileName))
{
// read the file Line by Line and split each line into one FactoryProcess object
string line = fileReader.ReadLine();
while (line != null)
{
// one line read, convert to FactoryProcess and yield return:
FactoryProcess factoryProcess = this.ToFactoryProcess(line);
yield return factoryProcess;
// read next line:
line = fileReader.ReadLine();
}
}
}
我将把读取行到FactoryProcess
的转换留给您。提示:如果您行中的项目用逗号或类似字符分隔,请考虑使用Nuget Package CSVHelper。将文件转换为序列FactoryProcesses
的过程更加容易。
我想按“机器人名称”对数据进行分组,然后按日期+“左工厂时间”对数据进行分组,然后按该日期和时间对地址进行分组。
首先:确保FactoryProcess类具有您实际需要的属性。将此表示形式与文件中的表示形式分开。显然,您希望将日期+离开工厂作为一项代表其离开工厂的日期和时间的项目。因此,我们为此创建一个DateTime属性。
class FactoryProcess
{
public int Id {get; set}
public int PartNo {get; set;}
public string RobotName {get; set;} // or if desired: use a unique RobotId
...
// DateTimes: ArrivalTime, OutOfFactoryTime, LeftFactoryTime
public DateTime ArrivalTime {get; set;}
public DateTime OutOfFactoryTime {get; set;}
public DateTime LeftFactoryTime {get; set;}
}
我将“日期和时间”放入一个DateTime的原因是,如果某项在第二天的23:55到达并在第二天的00:05离开,它将解决问题。
将读取的CSV行转换为FactoryProcess的过程应将您的日期和时间解释为字符串,然后转换为FactoryProcess。您可以为此创建一个构造器,或者创建一个特殊的Factory类
public FactoryProcess InterpretReadLine(string line)
{
// TODO: separate the parts, such that you've got the strings dateTxt, arrivalTimeTxt, ...
DateTime date = DateTime.Parse(dateTxt);
TimeSpan arrivalTime = TimeSpan.Parse(arrivalTimeTxt);
TimeSpan outOfFactoryTime = TimeSpan.Parse(outOfFactoryTimeTxt);
TimeSpan leftFactoryTime = TimeSpan.Parse(leftFactoryTimeTxt);
return new FactoryProces
{
Id = ...
PartNo = ..
RobotName = ...
// The DateTimes:
ArrivalTime = date + arrivalTime,
OutOfFactoryTime = date + outOfFactoryTime,
LeftFactoryTime = date + leftFactoryTime,
};
}
现在您已经创建了一种将CSV文件转换为FactoryProcesses
序列的正确方法,让我们对其进行处理
我想按“机器人名称”对数据进行分组,然后按日期+“左工厂时间”对数据进行分组,然后按该日期和时间对地址进行分组。
var result = fetchedFactoryProcesses.GroupBy(
// parameter KeySelector: make groups of FactoryProcesses with same RobotName:
factoryProcess => factoryProcess.RobotName,
// parameter ResultSelector: from every group of FactoryProcesses with this RobotName
// make one new Object:
(robotName, processesWithThisRobotName) => new
{
RobotName = robotName,
// Group all processes with this RobotName into groups with same LeftFactoryTime:
LeftFactory = processesWithThisRobotName.GroupBy(
// KeySelector: make groups with same LeftFactoryTime
process => process.LeftFactoryTime,
// ResultSelector: from each group of factory processes with the same LeftFactoryTime
(leftFactoryTime, processesWithThisLeftFactoryTime) => new
{
LeftFactoryTime = leftFactoryTime,
FactoryProcesses = processesWithThisLeftFactoryTime,
// or even better: select only the properties you actually plan to use
FactoryProcesses = processesWithThisLeftFactoryTime.Select(process => new
{
Id = process.Id,
PartNo = process.PartNo,
...
// not needed: you know the value, because it is in this group
// RobotName = process.RobotName,
// LeftFactoryTime = process.LeftFactoryTime,
}),
})
});
出于完整性考虑:将代码分组在一起:
void ProcessData(string fileName)
{
var fetchedFactoryProcesses = ReadFactoryProcess(fileName); // fetch the data
var groups = fetchFactoryProcesses.ToGroups(); // put into groups
this.Display(groups); // output result;
}
由于我将输入从字符串转换为FactoryProcesses进行了分离,并将此转换与分组进行了分离,因此可以轻松地分别测试类:
List<FactoryProcess>
,这都很方便,因为在测试中,创建测试列表更容易,而不是测试CSV文件。如果将来您决定更改FactoryProcesses序列的源(例如,它来自数据库而不是CSV文件),则您的分组不会更改。或者,如果您决定支持在不同日期(多个日期值)进入和离开工厂,则只能更改转换。如果您决定以树状显示结果,或决定将组写入数据库,则您的读取,转换,分组等都不会改变:具有高度的可重用性!
分离您的关注点使您更容易理解如何解决分组问题,而无需麻烦地拆分读取行并将Date + LeftFactory转换为一个值。