C# 优化平面文件中存储的数据的处理

C# 优化平面文件中存储的数据的处理,c#,C#,在我的办公室,我们使用一个旧的第三方工具来处理一些数据处理和导出工作。不幸的是,该工具的输出格式非常笨拙,因此,为了将其转换为有意义的形式并使用它,我们必须在原始数据导出和我们对其采取进一步行动的能力之间有一个中间处理步骤 这个问题是我不久前用Python和itertools非常简洁地解决的,但出于某些原因,我需要将这项工作重新定位到现有的C#应用程序中 我已经对我在这里发布的示例数据(以及相应的代码)进行了超级概括和简化,但它代表了真实数据的设置方式。该工具输出的原始数据如下所示,还有一些警告

在我的办公室,我们使用一个旧的第三方工具来处理一些数据处理和导出工作。不幸的是,该工具的输出格式非常笨拙,因此,为了将其转换为有意义的形式并使用它,我们必须在原始数据导出和我们对其采取进一步行动的能力之间有一个中间处理步骤

这个问题是我不久前用Python和itertools非常简洁地解决的,但出于某些原因,我需要将这项工作重新定位到现有的C#应用程序中

我已经对我在这里发布的示例数据(以及相应的代码)进行了超级概括和简化,但它代表了真实数据的设置方式。该工具输出的原始数据如下所示,还有一些警告(我将解释):

记录之间没有唯一的分隔符。他们只是一个接一个地被列出。有效且可操作的记录包含所有五项(“邮政编码”、“名”、“姓”、“ID”、“电话号码”)

我们只需要姓名、身份证和电话号码。每个独特的记录总是以邮政编码开头,但由于底层流程和第三方工具中的一些怪癖,我有一些事情需要说明:

  • 缺少电话号码的记录无效,并将在“电话号码”行中显示“(n/a)”值。在这种情况下,我们需要忽略整个记录
  • 如果在处理之前未正确输入记录,记录(很少)可能会缺少一行(如“姓氏”)。我们也忽略了这些案例
  • 如果与基础数据的某些链接信息出错,则记录将包含以“error”开头的行。它在记录中的确切位置各不相同。如果记录包含错误,我们将忽略它
我在C#中解决这个问题的方法是从第一行开始,检查它是否以“邮政编码”开头。如果是这样的话,我将进入一个进一步的循环,在那里我构建了一个键和值的字典(在第一行拆分“:”),直到我到达下一个“邮政编码”行。然后,当
当前行<(行计数-5)
时,它再次重复并滚动该过程

private void crapilyHandleExportLines(列出RawExportLines)
{
int lineNumber=0;
而(行号<(RawExportLines.Count-5))
{
//线组dict将表示我们当前正在处理的记录
字典线组=新字典();
//如果当前行以“Zip Code”开头,这意味着我们已到达另一条要处理的记录
if(RawExportLines[lineNumber++].StartsWith(“邮政编码”))
{
//如果行不是以“邮政编码”开头,我们假设它是记录的另一部分
//正在研究。
而(!RawExportLines[lineNumber].StartsWith(“邮政编码”))
{
//将除“错误”行以外的所有内容追加到我们正在处理的记录中,如存储在lineGroup中的那样
如果(!RawExportLines[lineNumber].StartsWith(“错误”)
{
string[]splitLine=RawExportLines[lineNumber]。拆分(新[]{:“},2,StringSplitOptions.None);
线组[splitLine[0].Trim()]=splitLine[1].Trim();
}
lineNumber++;
}
}
//继续之前验证记录。verifyAllKeys只是一种检查密钥列表的方法
//根据预期键列表,使用Except确保我们需要的所有项都存在。
如果(验证所有键(新列表(lineGroup.Keys))| |(lineGroup[“电话号码”!=“(n/a)”)
{
//记录不错!现在我们可以用它做点什么了:
工作流程记录(线组);
}
}
}
这是可行的(至少从我最初的测试来看)。问题是我真的不喜欢这段代码。我知道有更好的方法可以做到这一点,但我在C#方面不如我所希望的那么强,所以我认为我错过了一些让我更优雅、更安全地获得所需结果的方法


有谁能帮我指出如何实施更好的解决方案的正确方向吗?谢谢!

这可能会对您有所帮助,我们的想法是通过字典根据条目的id对条目进行分组,然后您可以使用适当的条件验证Enitry:

static void Main(string[] args)
{
    string path = @"t.txt";
    var text = File.ReadAllLines(path, Encoding.UTF8);
    var dict = new Dictionary<string, Dictionary<string, string>>();
    var id = "";
    var rows = text
        .Select(l => new { prop = l.Split(':')[0], val = l.Split(':')[1].Trim() })
        .ToList();

    foreach (var row in rows)
    {
        if (row.prop == "ID")
        {
            id = row.val;
        }
        else if (dict.ContainsKey(id))
        {
            dict[id].Add(row.prop, row.val);
        }
        else
        {
            dict[id] = new Dictionary<string, string>();
            dict[id].Add(row.prop, row.val);
        }
    }

   //get valid entries
   var validEntries = dict.Where(e =>e.Value.Keys.Intersect(new List<string> { "Zip Code", "First Name", "Last Name", "Phone Number" }).Count()==4 && e.Value["Phone Number"] != "(n/a)").ToDictionary(x=>x.Key, x => x.Value);
}

我将尝试使用工厂模式以更面向对象的方式解决这个问题

//Define a class to hold all people we get, which might be empty or have problems in them.
public class PersonText
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string PhoneNumber { get; set; }
    public string ID { get; set; }
    public string ZipCode { get; set; }

    public bool Error { get; set; }
    public bool Anything { get; set; }
}

//A class to hold a key ("First Name"), and a way to set the respective item on the PersonText class correctly.
public class PersonItemGetSets
{
    public string Key { get; }
    public Func<PersonText, string> Getter { get; }
    public Action<PersonText, string> Setter { get; }

    public PersonItemGetSets(string key, Action<PersonText, string> setter, Func<PersonText, string> getter)
    {
        Getter = getter;
        Key = key;
        Setter = setter;
    }
}

//This will get people from the lines of text
public static IEnumerable<PersonText> GetPeople(IEnumerable<string> lines)
{
    var itemGetSets = new List<PersonItemGetSets>()
    {
        new PersonItemGetSets("First Name", (p, s) =>  p.FirstName = s, p => p.FirstName),
        new PersonItemGetSets("Last Name", (p, s) =>  p.LastName = s, p => p.LastName),
        new PersonItemGetSets("Phone Number", (p, s) =>  p.PhoneNumber = s, p => p.PhoneNumber),
        new PersonItemGetSets("ID", (p, s) =>  p.ID = s, p => p.ID),
        new PersonItemGetSets("Zip Code", (p, s) =>  p.ZipCode = s, p => p.ZipCode),
    };

    foreach (var person in GetRawPeople(lines, itemGetSets, "Error"))
    {
        if (IsValidPerson(person, itemGetSets))
            yield return person;
    }
}

//Used to determine if a PersonText is valid and if it is worth processing.
private static bool IsValidPerson(PersonText p, IReadOnlyList<PersonItemGetSets> itemGetSets)
{
    if (itemGetSets.Any(x => x.Getter(p) == null))
        return false;
    if (p.Error)
        return false;
    if (!p.Anything)
        return false;
    if (p.PhoneNumber.Length != 12) // "555-555-5555".Length = 12
        return false;

    return true;
}

//Read through each line, and return all potential people, but don't validate whether they're correct at this time.
private static IEnumerable<PersonText> GetRawPeople(IEnumerable<string> lines, IReadOnlyList<PersonItemGetSets> itemGetSets, string errorToken)
{
    var person = new PersonText();


    foreach (var line in lines)
    {
        var parts = line.Split(':');
        bool valid = false;

        if (parts.Length == 2)
        {
            var left = parts[0];
            var right = parts[1].Trim();

            foreach (var igs in itemGetSets)
            {
                if (left.Equals(igs.Key, StringComparison.OrdinalIgnoreCase))
                {
                    valid = true;
                    person.Anything = true;
                    if (igs.Getter(person) != null)
                    {
                        yield return person;
                        person = new PersonText();
                    }
                    igs.Setter(person, right);
                }
            }
        }
        else if (parts.Length == 1)
        {
            if (parts[0].Trim().Equals(errorToken, StringComparison.OrdinalIgnoreCase))
            {
                person.Error = true;
            }
        }
        if (!valid)
        {
            if (person.Anything)
            {
                yield return person;
                person = new PersonText();
            }
            continue;
        }
    }
    if (person.Anything)
        yield return person;
}
//定义一个类来容纳我们得到的所有人,这些人可能是空的或有问题。
公共类PersonText
{
公共字符串名{get;set;}
公共字符串LastName{get;set;}
公共字符串PhoneNumber{get;set;}
公共字符串ID{get;set;}
公共字符串ZipCode{get;set;}
公共布尔错误{get;set;}
公共bool Anything{get;set;}
}
//一个用来保存键(“名字”)的类,以及一种在PersonText类上正确设置相应项的方法。
公共类PersonitGetSets
{
公共字符串密钥{get;}
公共函数Getter{get;}
公共操作设置程序{get;}
公共PersonitGetSets(字符串键、动作设置器、函数获取器)
{
吸气剂=吸气剂;
钥匙=钥匙;
Setter=Setter;
}
}
//这将使人们从一行行文字中获得信息
公共静态IEnumerable GetPeople(IEnumerable行)
{
var itemGetSets=新列表()
{
新PersonitGetSets(“First Name”,(p,s)=>p.FirstName=s,p=>p.FirstName),
新PersonitGetSets(“姓氏”,(p,s)=>p.LastName=s,p=>p.LastName),
新PersonitGetSets(“电话号码”(p,s)=>p.PhoneNumber=s,p=>p.PhoneNumber),
新PersonitGetSets(“ID”,(p,s)=>p.ID=s,p=>p.ID),
新PersonitGetSets(“邮政编码”(p,s)=>p.ZipCode=s,p=>p.ZipCode),
};
foreach(变量)GetRawPeople中的person(行,itemGetSe
static void Main(string[] args)
{
    string path = @"t.txt";
    var text = File.ReadAllLines(path, Encoding.UTF8);
    var dict = new Dictionary<string, Dictionary<string, string>>();
    var id = "";
    var rows = text
        .Select(l => new { prop = l.Split(':')[0], val = l.Split(':')[1].Trim() })
        .ToList();

    foreach (var row in rows)
    {
        if (row.prop == "ID")
        {
            id = row.val;
        }
        else if (dict.ContainsKey(id))
        {
            dict[id].Add(row.prop, row.val);
        }
        else
        {
            dict[id] = new Dictionary<string, string>();
            dict[id].Add(row.prop, row.val);
        }
    }

   //get valid entries
   var validEntries = dict.Where(e =>e.Value.Keys.Intersect(new List<string> { "Zip Code", "First Name", "Last Name", "Phone Number" }).Count()==4 && e.Value["Phone Number"] != "(n/a)").ToDictionary(x=>x.Key, x => x.Value);
}
if (row.prop == "ID")
{
        var values=dict[id];
        dict.Remove(id);
        dict.Add(row.val,values);
        id = "";
}
//Define a class to hold all people we get, which might be empty or have problems in them.
public class PersonText
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string PhoneNumber { get; set; }
    public string ID { get; set; }
    public string ZipCode { get; set; }

    public bool Error { get; set; }
    public bool Anything { get; set; }
}

//A class to hold a key ("First Name"), and a way to set the respective item on the PersonText class correctly.
public class PersonItemGetSets
{
    public string Key { get; }
    public Func<PersonText, string> Getter { get; }
    public Action<PersonText, string> Setter { get; }

    public PersonItemGetSets(string key, Action<PersonText, string> setter, Func<PersonText, string> getter)
    {
        Getter = getter;
        Key = key;
        Setter = setter;
    }
}

//This will get people from the lines of text
public static IEnumerable<PersonText> GetPeople(IEnumerable<string> lines)
{
    var itemGetSets = new List<PersonItemGetSets>()
    {
        new PersonItemGetSets("First Name", (p, s) =>  p.FirstName = s, p => p.FirstName),
        new PersonItemGetSets("Last Name", (p, s) =>  p.LastName = s, p => p.LastName),
        new PersonItemGetSets("Phone Number", (p, s) =>  p.PhoneNumber = s, p => p.PhoneNumber),
        new PersonItemGetSets("ID", (p, s) =>  p.ID = s, p => p.ID),
        new PersonItemGetSets("Zip Code", (p, s) =>  p.ZipCode = s, p => p.ZipCode),
    };

    foreach (var person in GetRawPeople(lines, itemGetSets, "Error"))
    {
        if (IsValidPerson(person, itemGetSets))
            yield return person;
    }
}

//Used to determine if a PersonText is valid and if it is worth processing.
private static bool IsValidPerson(PersonText p, IReadOnlyList<PersonItemGetSets> itemGetSets)
{
    if (itemGetSets.Any(x => x.Getter(p) == null))
        return false;
    if (p.Error)
        return false;
    if (!p.Anything)
        return false;
    if (p.PhoneNumber.Length != 12) // "555-555-5555".Length = 12
        return false;

    return true;
}

//Read through each line, and return all potential people, but don't validate whether they're correct at this time.
private static IEnumerable<PersonText> GetRawPeople(IEnumerable<string> lines, IReadOnlyList<PersonItemGetSets> itemGetSets, string errorToken)
{
    var person = new PersonText();


    foreach (var line in lines)
    {
        var parts = line.Split(':');
        bool valid = false;

        if (parts.Length == 2)
        {
            var left = parts[0];
            var right = parts[1].Trim();

            foreach (var igs in itemGetSets)
            {
                if (left.Equals(igs.Key, StringComparison.OrdinalIgnoreCase))
                {
                    valid = true;
                    person.Anything = true;
                    if (igs.Getter(person) != null)
                    {
                        yield return person;
                        person = new PersonText();
                    }
                    igs.Setter(person, right);
                }
            }
        }
        else if (parts.Length == 1)
        {
            if (parts[0].Trim().Equals(errorToken, StringComparison.OrdinalIgnoreCase))
            {
                person.Error = true;
            }
        }
        if (!valid)
        {
            if (person.Anything)
            {
                yield return person;
                person = new PersonText();
            }
            continue;
        }
    }
    if (person.Anything)
        yield return person;
}