2013-06-13 3 views
3

Ok, donc le titre n'est pas vraiment le meilleur. Mais voici mon problème:Comment lire plusieurs lignes sur la même ligne?

J'ai créé un petit programme qui écrit quelques noms et un nombre entier dans un document .txt lorsque certains événements se produisent à partir d'un programme externe. Le fait est qu'un nom peut apparaître sur plusieurs lignes dans le document, donc je veux résumer les entiers pour chaque personne spécifique afin que j'obtienne le nombre total de points pour lui/elle et ensuite le trier.

Par exemple:

La ligne originale:

Aaaa Aaa 5 
Bbbb Bbb 7 
Cccc Ccc 2 
Aaaa Aaa 4 
Cccc Ccc 4 
Bbbb Bbb 1 
Dddd Ddd 1 

La sortie Je veux:

1. Aaaa Aaa 9 
2. Bbbb Bbb 8 
3. Cccc Ccc 6 
4. Dddd Ddd 1 

Est-il possible de le faire en C#? J'ai essayé de lire dans chaque ligne du fichier et de rechercher le nom d'une personne. Mais cela n'aide pas vraiment et je ne sais pas comment résoudre ça. Un conseil?

+1

Peut-être avez-vous besoin d'une vraie base de données à la place d'un fichier. – Oscar

+0

Mais si je n'ai pas de base de données? –

+0

Et comment puis-je le faire? –

Répondre

2

Cette requête Linq renvoie le résultat souhaité comme IEnumerable<string>:

IEnumerable<string> lineGroups = File.ReadLines(path) 
.Select((l, i) => new { Line = l, Parts = l.Split() }) 
.Select(x => new 
{ 
    Number = x.Parts.ElementAtOrDefault(2).TryGetInt() ?? 1, 
    Col1 = x.Parts.ElementAtOrDefault(0), 
    Col2 = x.Parts.ElementAtOrDefault(1), 
    x.Line, 
    x.Parts 
}) 
.GroupBy(x =>new { x.Col1, x.Col2 }) 
.Select((g, groupIndex) => 
    string.Format("{0}. {1} {2} {3}", 
    groupIndex + 1, g.Key.Col1, g.Key.Col2, g.Sum(x => x.Number))); 

sortie:

foreach (var grp in lineGroups) 
    Console.WriteLine(grp); 

C'est la sortie:

1. Aaaa Aaa 9 
2. Bbbb Bbb 8 
3. Cccc Ccc 2 // different than your desired ouput but seems to be correct 
4. Dddd Ddd 1 

Ce sont mes méthodes d'extension que j'utilise dans les requêtes LINQ to Try-Parse un string type de valeur commune comme int (comme ci-dessus).Il retourne un type nullable si elle n'a pas été analysable:

public static class NumericExtensions 
{ 
    public static bool IsBetween(this int value, int fromInclusive, int toInclusive) 
    { 
     return value >= fromInclusive && value <= toInclusive; 
    } 

    public static Decimal? TryGetDecimal(this string item) 
    { 
     Decimal d; 
     bool success = Decimal.TryParse(item, out d); 
     return success ? (Decimal?)d : (Decimal?)null; 
    } 

    public static int? TryGetInt(this string item) 
    { 
     int i; 
     bool success = int.TryParse(item, out i); 
     return success ? (int?)i : (int?)null; 
    } 

    public static bool TryGetBool(this string item) 
    { 
     bool b = false; 
     Boolean.TryParse(item, out b); 
     return b; ; 
    } 

    public static Version TryGetVersion(this string item) 
    { 
     Version v; 
     bool success = Version.TryParse(item, out v); 
     return v; 
    } 
} 
+1

@OP: Notez que quelqu'un a modifié votre question. Maintenant, mes données d'échantillon sont différentes et mon commentaire sur la sortie désirée/réelle est obsolète. –

+0

J'ai cependant trouvé un problème, si les noms varient en fonction de la longueur, cela ne fonctionnera pas. –

+0

@JimmyLindberg: Avez-vous un exemple? Si les noms sont différents (si la longueur d'un nom est différente, le nom lui-même est différent), ils doivent appartenir à des groupes différents. –

3

De toute évidence, le texte est quelque peu une clé que vous utilisez pour résumer les nombres, alors pourquoi ne pas utiliser un Dictionary<string, int> pour résumer d'abord et écrire plus tard?

Exemple:

Dictionary<string, int> sums = new Dictionary<string, int>(); 

... 

if (sums.ContainsKey(theNewString)) 
    sums[theNewString] += theNewNumber; 
else 
    sums[theNewString] = theNewNumber; 

Et quand vous savez que vous avez terminé, écrire le fichier. Vous pouvez également réécrire le fichier après chaque mise à jour du dictionnaire, mais n'oubliez pas que le dictionnaire va grandir et grandir si vous ne le purgez pas.

Également: Cela ne fonctionnera pas si le programme est redémarré, sauf si vous créez un nouveau fichier chaque fois que le programme démarre. Sinon, vous devrez lire un fichier existant dans le dictionnaire lorsque le programme commencera à se résumer.

+0

C'est juste que le premier fichier est déjà écrit, donc je dois lire le fichier et écrire dans un autre fichier après avoir trié et sumarazied les valeurs. –

+0

Vous pouvez également écrire dans le même fichier. Vous pouvez également - quand une nouvelle valeur arrive - lire le fichier dans un dictionnaire, changer le dictionnaire (en résumant peut-être), puis réécrire le dictionnaire dans le fichier. –

+0

Ok, c'est juste que je ne sais pas comment lire en double quand j'utilise les fonctions ReadLine ou ReadToEnd. Existe-t-il un type de FindDuplicates implémenté en C#? –

2

Créez un dictionnaire avec les clés comme noms. En tant que valeur de chaque élément du dictionnaire, utilisez l'entier et ajoutez-le à (la valeur de) une clé déjà existante (ou non).

+0

C'est juste que les noms sont aléatoires et que le fichier est déjà écrit à partir d'un autre programme. –

+0

Vous pouvez lire le fichier et conserver le dictionnaire en mémoire ... si vous avez besoin d'un nouveau fichier à sortir, vous devez écrire du code pour écrire les informations du dictionnaire dans un fichier (différent ou non). –

1
var results = File.ReadAllLines("filename.txt") 
    .Select(x => x.Split()) 
    .GroupBy(y => new { y1 = y[0], y2 = y[1] }) 
    .Select(g => new { g.Key.y1, g.Key.y2, Sum = g.Sum(v => int.Parse(v[2])) }) 
    .OrderByDescending(p => p.Sum) 
    .Select(m => m.y1 + " " + m.y2 + " " + m.Sum).ToList(); 
1

quelque chose comme ça (pas élégant, mais ne dépend pas du nombre d'espaces)

var lines = File.ReadAllLines(@"<pathToFile>"); 

var result = lines 
       .Select(m => m.Split(' ')) 
       .Select(x => new { 
        text = string.Join(" ", x.Where(z => z != x.Last())), 
        val = Convert.ToInt32(x.Last()) 
       }) 
       .GroupBy(x => x.text) 
       .Select(g => new { 
        text =g.Key, 
        sum = g.Sum(z => z.val) 
       }).ToList(); 
2
lines.GroupBy(line => string.Join(" ", line.Split().Take(2))) 
    .Select((g, index) => 
      string.Format("{0}. {1} {2}", 
      index, 
      g.Key, 
      g.Sum(line => int.Parse(line.Split().Last())))); 
1

Juste une autre solution avec LINQ et des expressions régulières (pour vérifier le format de ligne et d'obtenir les noms et les valeurs de celle-ci):

Regex regex = new Regex(@"^(?<name>.*)\s(?<value>\d+)$"); 

var query = from line in File.ReadLines(file_name) 
      let match = regex.Match(line) 
      where match.Success 
      select new { 
       Name = match.Groups["name"].Value, 
       Value = match.Groups["value"].Value 
      } into item 
      group item by item.Name into g 
      orderby g.Key 
      select new { 
       Name = g.Key, 
       Total = g.Sum(x => Int32.Parse(x.Value)) 
      }; 

dépassement de la valeur est pas vérifié ici. S'il est possible que certaines valeurs soient supérieures à Int32.MaxValue, changez le calcul de la somme en

g.Sum(x => { int value; return Int32.TryParse(x.Value, out value) ? value : 0; }) 
Questions connexes