Mam plik tekstowy zawierający informacje o nowych liniach takich jak poniżej. Te dane zawiera około 500 osób.

Person
name: abc
age: 40
.
Person
name: xyx
age: 18
.
Person
name: uke
age: 27
.

Informacje o osobie są między "osobą" a "." postacie. Stworzyłem klasę Person, aby uzyskać informacje: Ten tekst

public class Person {
   public string Name { get; set;}
   public string Age { get; set;}
}

Chcę przeczytać ten plik tekstowy i przeanalizuj go do klasy Person. Mogę przeczytać plik:

string path = @"c:\temp\person.txt";
string readText = File.ReadAllText(path);

Ale nie mogłem analizować każdej osoby między "osobą" a "." postacie. Nie chcę używać warunków if else. Czy istnieje praktyczne rozwiązanie przy użyciu regex lub {x1}} lub linq?

Chcę uzyskać listę w ten sposób po przeczytaniu:

var person = List<Person> {
   new Person { Name = "abc", Age = 40 },
   new Person { Name = "xyx", Age = 18 },
   new Person { Name = "uke", Age = 27 }
   ....
   ...
   ..
}
2
barteloma 3 czerwiec 2018, 12:44

4 odpowiedzi

Najlepsza odpowiedź

Możesz podzielić tekst przez kropki ., co daje informacje dla każdej osoby, a następnie podzielą każde przez nowe linie \n, \r, lub \r\n w zależności od tego, co linia Korzystanie z używania plików. Następnie możesz uzyskać nazwę z drugiej linii (z indeksem 1), a wiek z trzeciej linii (z indeksem 2). Wreszcie, podziel nazwę i wiek przez Colon i Space {X4}} i zdobądź drugi ciąg (z indeksem 1). Zakłada to, że struktura plików jest stała i nigdy się nie zmienia, w przeciwnym razie będziesz potrzebować nazwy i wieku na podstawie warunków, które powiedziałeś, że chcesz uniknąć:

var persons = new List<Person>();
var personInfo = readText.Split(new char[]{'.'}, StringSplitOptions.RemoveEmptyEntries);
foreach (var i in personInfo)
{
    var person = new Person();
    var lines = i.Split(new char[]{'\n'}, StringSplitOptions.RemoveEmptyEntries);
    person.Name = lines[1].Split(new string[]{": "}, StringSplitOptions.None)[1];
    person.Age = lines[2].Split(new string[]{": "}, StringSplitOptions.None)[1];
    persons.Add(person);
}

Alternatywnie możesz użyć LINQ:

var persons = readText.Split(new char[]{'.'}, StringSplitOptions.RemoveEmptyEntries)
                      .Select(i => i.Split(new char[]{'\n'}, StringSplitOptions.RemoveEmptyEntries))
                      .Select(l => new Person{
                          Name = l[1].Split(new string[]{": "}, StringSplitOptions.None)[1],
                          Age = l[2].Split(new string[]{": "}, StringSplitOptions.None)[1]
                      });
1
Racil Hilan 3 czerwiec 2018, 10:58

Jeśli używasz File.ReadAllLines() zamiast .ReadAllText(), możesz użyć funkcji boksu. Napisałem jedną z tych samego siebie, więc jeśli chcesz czegoś, co działa z pudełka (nie ma słów), możesz zainstalować Anaximander.linq i użyj tego tak:

var readText = File.ReadAllLines(path);

var people = readText
    .BoxWhile((a, b) => b != ".")
    .Select(x => x
            .Where(t => t != "." && t != "Person")
            .Select(t => t.Split(':').Last().Trim())
        .ToList())
    .Where(x => x.Any())
    .Select(x => new Person
    {
        Name = x[0],
        Age = x[1]
    });

Jeśli chcesz wiedzieć, jak to działa wewnętrznie, a następnie Kod źródłowy jest dostępny na GitHub Może spojrzeć na to, jak zaimplementowany jest BoxedeneMereable. Używa przesuwnego okna z operatorem porównawczym; Uruchamia nowe "pudełko" przy każdym porównaniu między bieżącym elementem a następnym zwrotem FALSE. Twoje pytanie jest w rzeczywistości jednym z kilku przypadków, które ostatnio widziałem, gdzie porównanie nie musi patrzeć na bieżący, a następnie, tylko następny, więc wkrótce dodam dodatkową metodę.

1
anaximander 3 czerwiec 2018, 10:08

Łatwiejsza alternatywa może być podzielona przez "osobę" lub "Nazwa:"

List<Person> people = File.ReadAllText(@"c:\temp\person.txt")
    .Split(new[] { "name:" }, StringSplitOptions.None)
    .Skip(1)
    .Select(p => p.Split('\n', ':'))
    .Select(a => new Person { 
         Name = a[0].Trim(), 
         Age = a[2].Trim() 
    })
    .ToList();
0
Slai 3 czerwiec 2018, 11:29

Nie jest prosty, ale bardzo potężny roztwór przy użyciu parsera monadycznego Sprache:

Możesz łączyć parsery do analizowania bardzo złożonych struktur.

splittetsample.csproj

<PropertyGroup>
  <OutputType>Exe</OutputType>
  <TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
  <PackageReference Include="Sprache" Version="2.1.2" />
</ItemGroup>

Program.cs

using System;
using System.Collections.Generic;
using Sprache;

namespace SplitTextSample
{
    class Program
    {
        public static string Text =
@"Person
name: abc
age: 40
.
Person
name: xyx
age: 18
.
Person
name: uke
age: 27
.";

        public class Person
        {
            public string Name { get; set; }
            public string Age { get; set; }
        }

        static void Main(string[] args)
        {
            // Parses: Person\r\n
            var personTagParser = Parse.String("Person").Then(_=>Parse.LineEnd);

            // Parses: name: {name}\r\n
            Parser<string> nameParser = from tag in Parse.String("name:").Token()
                from name in Parse.AnyChar.Except(Parse.LineEnd).AtLeastOnce().Text()
                from lineEnd in Parse.LineEnd
                select name;

            // Parses: age: {age}\r\n.[\r\n]
            Parser<string> ageParser = from tag in Parse.String("age:").Token()
                from age in Parse.AnyChar.Except(Parse.LineEnd).AtLeastOnce().Text()
                from delimeter in Parse.LineEnd.Then(_ => Parse.Char('.')).Then(_=> Parse.LineEnd.Optional())
                select age;

            // Parses: Person\r\nname: {name}\r\nage: {age}\r\n.[\r\n]
            Parser<Person> personParser =
                from personTag in personTagParser
                from name in nameParser
                from age in ageParser
                select new Person{Name = name, Age = age};

            // Parses: Many persons
            var personsParser = personParser.Many();

            // Final parse returns IEnumerable<Person>
            var persons = personsParser.Parse(Text);

            foreach (var person in persons)
            {
                Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
            }

            Console.ReadLine();
        }

}

Wynik:

Name: abc, Age: 40
Name: xyx, Age: 18
Name: uke, Age: 27
0
Alexey.Petriashev 3 czerwiec 2018, 19:29