LINQ方式更改我效率低下的程序

时间:2019-02-21 21:19:25

标签: linq

请考虑以下记录

enter image description here

我正在尝试按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();   
}

1 个答案:

答案 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进行了分离,并将此转换与分组进行了分离,因此可以轻松地分别测试类:

  • 您的CSV阅读器应该返回任何分成几行的文件,即使其中不包含FactoryProcesses
  • 您从读取行到FactoryProcess的转换应转换任何格式正确的字符串,无论是从文件中读取还是以其他任何方式收集
  • 您的分组应该对FactoryProcess的任何序列进行分组,无论它们是来自CSV文件还是来自数据库或List<FactoryProcess>,这都很方便,因为在测试中,创建测试列表更容易,而不是测试CSV文件。

如果将来您决定更改FactoryProcesses序列的源(例如,它来自数据库而不是CSV文件),则您的分组不会更改。或者,如果您决定支持在不同日期(多个日期值)进入和离开工厂,则只能更改转换。如果您决定以树状显示结果,或决定将组写入数据库,则您的读取,转换,分组等都不会改变:具有高度的可重用性!

分离您的关注点使您更容易理解如何解决分组问题,而无需麻烦地拆分读取行并将Date + LeftFactory转换为一个值。