3

I have a foreach statement where I go through several lines from a text file, where I have trimmed down and sorted out the lines I need. What I want to do is count up on how many times an identical string is present. How do I do this?

Here is my code. It's the second if statement where I am stuck:

        foreach (string line in lines.Where(l => l.Length >= 5))
        {
            string a = line.Remove(0, 11);

            if ((a.Contains(mobName) && a.Contains("dies")))
            {

                mobDeathCount++;
            }
            if (a.Contains(mobName) && a.Contains("drops"))
            {
                string lastpart = a.Substring(a.LastIndexOf("drops"));
                string modifiedLastpart = lastpart.Remove(0, 6);

            }

Heres what some of the lines look like:

a bag of coins

a siog brandy

a bag of coins

a bag of coins

the Cath Shield

a tattered scroll

So what im trying to do is counting up there are 3 lines with bag of coins. But i need to make it so that it can be everything, theres a drop lists thats huge. So cant add all of em, would take too long

EDIT

    private static void Main()
    {
        int mobDeathCount = 1;
        int lootCheckCount = 1;

        string[] lines =
            System.IO.File.ReadAllLines(@"C:\Users\Michael\Documents\Electronic Arts\Dark Age of Camelot\chat.log");
        Console.WriteLine(
            "Enter which mob you want to see, remember to include the, for an example; The siog seeker, remember to start with a capital T");
        string mobName = Console.ReadLine();


        foreach (string line in lines.Where(l => l.Length >= 5))
        {




            string a = line.Remove(0, 11);

            if ((a.Contains(mobName) && a.Contains("dies")))
            {

                mobDeathCount++;
            }
            if (a.Contains(mobName) && a.Contains("drops"))
            {
                string lastpart = a.Substring(a.LastIndexOf("drops"));
                string modifiedLastpart = lastpart.Remove(0, 6);

               var lineCountDict = modifiedLastpart.GroupBy(x => x).Where(x => x.Count() > 1).ToDictionary(x => x.Key, x => x.Count());
               foreach (var val in lineCountDict)
               {
                   Console.WriteLine(val.Key + " - " + val.Value);
               }

new lines;

[01:09:55] The siog seeker drops a bag of coins.

[01:09:55] The siog seeker drops a siog brandy.

[01:09:55] The siog seeker dies!

[01:09:55] You get 3,687,564 experience points.(1,638,917 camp bonus)

[01:10:31] You cast a Lesser Disenchanting Eruption spell!

[01:10:31] You hit the siog seeker for 424 (+18) damage!

[01:10:31] The siog seeker drops a bag of coins.

[01:10:31] You pick up 18 silver and 88 copper pieces.

[01:10:31] The siog seeker dies

Winkz
  • 329
  • 2
  • 5
  • 19
  • Do you mean how many times it's present in a _single line_? – DonBoitnott Jun 27 '13 at 19:08
  • 1
    What is "stuck" in the 2nd statement? From the description, `mobDropCount++` would be sufficient - but I suspect you are interested in counting *what* dropped, no? For this, consider a [Dictionary](http://msdn.microsoft.com/en-us/library/xfhwa508.aspx) where the name of the thing dropped is the key while the number of times is the value. Also search for "C# frequency map" or "C# histogram" on SO, as many approaches will use a Dictionary. – user2246674 Jun 27 '13 at 19:09
  • No I mean if there are 3 lines which are exactly the same, i can count them up. Hope im making my self clear enough :) – Winkz Jun 27 '13 at 19:09
  • What is the type of `lines`? Is it a List? – keyboardP Jun 27 '13 at 19:09
  • Yes different drops, but there are identical drops as well and i want to count up how many times a specific drop has been dropped – Winkz Jun 27 '13 at 19:10
  • How are you obtaining lines, are you reading the file line by line into an string array? – Bearcat9425 Jun 27 '13 at 19:17
  • Yes Bearcat, its line by line – Winkz Jun 30 '13 at 13:00
  • Where is the problem with using the dictionary? – Marguth Jul 01 '13 at 09:49

5 Answers5

11

You can use LINQ to get the number of duplicate lines. This will create a dictionary that contains the string as the key and the number of times that string appears as the value.

var lineCountDict = lines.GroupBy(x => x).ToDictionary(x => x.Key, x => x.Count());

To read out the values, simply iterate through the dictionary So, using your example list

List<String> lines = new List<string>()
     { 
         "a bag of coins",
         "a siog brandy",
         "a bag of coins",
         "a bag of coins",
         "the Cath Shield",
         "a tattered scroll"
     };

var lineCountDict = lines.GroupBy(x => x).ToDictionary(x => x.Key, x => x.Count());

foreach (var val in lineCountDict)
{
     Console.WriteLine(val.Key + " - " + val.Value);
}

This will output each string and how many times it appeared, including those strings that only appear once. If you only want those that are duplicates, you can modify the LINQ query by adding a Whereclause

var lineCountDict = lines.GroupBy(x => x).Where(x => x.Count() > 1).ToDictionary(x => x.Key, x => x.Count());

The dictionary will then have only one item from the list in your example (a bag of coins) and the key would be a bag of coins and the value would be 3 since it appears 3 times.

update based on comments

This should work in your case

List<string> modifiedList = new List<string>();
int numberOfDrops = 0;

foreach (string line in lines.Where(l => l.Length >= 5))
{
     string ad = line.Remove(0, 11);

     if ((ad.Contains(mobName) && ad.Contains("dies")))
     {
        mobDeathCount++;
     }
     if (ad.Contains(mobName) && ad.Contains("drops"))
     {
         string lastpart = ad.Substring(ad.LastIndexOf("drops"));
         string modifiedLastpart = lastpart.Remove(0, 6);
         modifiedList.Add(modifiedLastpart);
         numberOfDrops++;
     }

}

double deathDropRatio = (double)mobDeathCount / (double)numberOfDrops;

var lineCountDict = modifiedList.GroupBy(x => x).Where(x => x.Count() > 1).ToDictionary(x => x.Key, x => x.Count());

foreach (var val in lineCountDict)
{
   Console.WriteLine(val.Key + " - " + val.Value);
}
keyboardP
  • 68,824
  • 13
  • 156
  • 205
  • Cant really get this to work, a bit more help with it would appriciated :D – Winkz Jun 27 '13 at 21:27
  • 1
    Yea i get this when i try and print lineCountDict out System.Collection.Generic.Dictionary`2[System.String,System.Int32] – Winkz Jun 28 '13 at 11:19
  • 2
    You can't just do `lineCountDict.ToString();`, it's a dictionary structure. You can access it by it's key, such as `lineCountDict["string that might be duplicate"]` or you can loop through the entire dictionary. Read up on dictionaries [here](http://www.dotnetperls.com/dictionary). What you're getting is not an error message, the code is fine. – keyboardP Jun 28 '13 at 11:32
  • @Winkz - I've further updated my answer to show how you can read the values with the List you've posted in your edit. – keyboardP Jun 30 '13 at 23:11
  • Thank you :) But then I have to write all the drops? Theres alot of them :/ – Winkz Jul 05 '13 at 12:08
  • I thought you were reading them from the text file already? The one in my example was just to create a working demo so the list was manually added. – keyboardP Jul 05 '13 at 12:19
  • I am, but getting some really weird output atm, gonna try something real quick. – Winkz Jul 05 '13 at 12:20
  • When i print it out now, i get a somewhat weird looking list like this; a - 2, - 3, 0 - 2, a - 2 etc. gonna edit my post and show you what my code looks like. – Winkz Jul 05 '13 at 12:27
  • 1
    That's because of this line `Console.WriteLine(val.Key + " - " + val.Value);` I just added it to show you how you can access each key and each value. You can format it how you like (or you don't even have to print it out) – keyboardP Jul 05 '13 at 12:34
  • Still seems like get too many values though, can I ask how you would set it up if i would want something like Bag of coins - 10. – Winkz Jul 05 '13 at 12:39
  • 1
    Since the name of the item is the key, you simply do `Console.WriteLine(lineCountDict["a bag of coins"]);`, or if you wanted the number of `the Cath Shield` strings, you'd do `Console.WriteLine(lineCountDict["the Cath Shield"]);` – keyboardP Jul 05 '13 at 12:43
  • Cant it be done automaticly? And i also get an error with that, cannot convert from string to char – Winkz Jul 05 '13 at 12:46
  • Meant like, so I dont have to type for an example a bag of coins and so on.. hmm weird i get this error then. – Winkz Jul 05 '13 at 12:49
  • Im trying to list up on how many of each item there are. like 10 drops of bag of coins, 5 cath shields and so on. – Winkz Jul 05 '13 at 12:52
  • If you copy/paste my code in a new console project, it does exactly that. – keyboardP Jul 05 '13 at 12:53
  • It might help if you paste a few lines from the log file so I can see the format you're working with. – keyboardP Jul 05 '13 at 12:57
  • a bag of coins\n a siog brandy\n a bag of coins.. or do you mean how it looks like before i trim and so? – Winkz Jul 05 '13 at 12:58
  • New lines added up in my question – Winkz Jul 05 '13 at 13:01
  • So in your new edit, the only duplicate should be `The siog seeker drops a bag of coins.`? – keyboardP Jul 05 '13 at 13:03
  • No theres like 7 lines where the line is: a bag of coins.. and 2-3 lines with the cath shield and so on with some others. The log file is waaay longer.. but what i have trimmed down to when i search for The siog, theres like 15-20 lines of drops.. most of them identical, like the bag of coins and cath shield and so on. – Winkz Jul 05 '13 at 13:07
  • Oh no, that's what I meant. In that small list you posted, there's only one that should be considered duplicate? – keyboardP Jul 05 '13 at 13:12
  • I've updated my answer. I think it does what you want. You need to keep a separate list of the modified lines and then convert that to the dictionary. – keyboardP Jul 05 '13 at 13:16
  • Yea in that list, but theres alot of more drops which are the same – Winkz Jul 05 '13 at 13:17
  • You are awesome :D Thank you, it works now. Now i can finally go on doing a bit more stuff :D Just a quick question. How can I divide my mobdeathcount with the number of drops? if you dont mind ;D – Winkz Jul 05 '13 at 13:21
  • You're welcome! I've updated the code with how to calculate the ratio :) – keyboardP Jul 05 '13 at 13:24
  • Awesome, i just cant do a; Console.WriteLine(val.Key + " - " + mobDeathCount/val.Value);? – Winkz Jul 05 '13 at 13:26
  • You could do. If you don't want whole numbers then cast it as a double `Console.WriteLine(val.Key + " - " + ((double)mobDeathCount / (double)val.Value));` but your value won't be the same mine because yours only takes into account duplicates (but that might be what you want). – keyboardP Jul 05 '13 at 13:29
  • Yea i want to see what the chance is for how many times a mob drops a certain item. I got it working now, Thank you so many times :D It's a bit over what i could do my self yet :) – Winkz Jul 05 '13 at 13:38
  • No problem. We all have to start somewhere, just keep practicing! I suggest the next thing you try is to figure out how to avoid having to type capital letters each time to match the strings. (Hint: http://stackoverflow.com/questions/631233/is-there-a-c-sharp-case-insensitive-equals-operator) :) – keyboardP Jul 05 '13 at 13:41
  • @Winkz - Hehe, not ToLower. Either ToUpper or, preferably, the `String.Equals` method (in the link). Reason for not doing ToLower is because it fails the [Turkey Test](http://www.moserware.com/2008/02/does-your-code-pass-turkey-test.html) but that's another story :) – keyboardP Jul 05 '13 at 14:07
3

I like to use a Dictionary for this.

Dictionary<string, int> dict = new Dictionary<string, int>();
foreach (string s in yourStringList) {
    if (dict.ContainsKey(s)) {
        dict[s] = ++dict[s];
    } else {
        dict[s] = 1;
    }
}

Your strings are the keys of the dictionary, and the count of the number of times each appears is the value.

(Disclaimer: didn't test code; may require minor tweaks.)

James Cronen
  • 5,715
  • 2
  • 32
  • 52
  • Tried what you wrote, but i get an error with the string s in the foreach loop, it says i cannot convert element type 'char' to iterator type 'string'.. not sure what that means – Winkz Jun 27 '13 at 21:10
  • This happens at run time or compile time? – James Cronen Jun 27 '13 at 21:27
  • I can't get the exact error you did, but my guess is that whatever collection you're replacing `yourStringList` with, it's returning single characters rather than strings. Make sure that list really is composed of strings. – James Cronen Jun 27 '13 at 21:34
  • Tried kinda everything, when i print it out i get this as output; System.Collection.Generic.Dictionary`2[System.String,System.Int32] – Winkz Jun 27 '13 at 21:38
  • `yourStringList` shouldn't be the dictionary... that should be a list of the strings that you want to count for uniqueness. I didn't include that part in the code snippet since it seemed like that already worked. If you want to only process one string at a time as you find them, rather than all of them as a List, you can take the `if (dict.ContainsKey(s)) { ... } else { ... }` part and run that after each string to be processed. – James Cronen Jun 27 '13 at 21:51
1

I think this is what you want:

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

foreach (string line in lines.Where(l => l.Length >= 5))
{
     string a = line.Remove(0, 11);

     if ((a.Contains(mobName) && a.Contains("dies")))
     {
         mobDeathCount++;
     }

     if (a.Contains(mobName) && a.Contains("drops"))
     {
         string lastpart = a.Substring(a.LastIndexOf("drops"));
         string modifiedLastpart = lastpart.Remove(0, 6);

         if (dropsDict.ContainsKey(modifiedLastpart)) 
         {
             dropsDict[modifiedLastpart] = dropsDict[modifiedLastpart]++;
         } 
         else 
         {
             dropsDict[modifiedLastpart] = 1;
         }
     }
}
Dodecapus
  • 391
  • 3
  • 10
  • Thanks for your effort, though im not 100 % sure how your code works, i entered it, but i cant seem to make it work like i want to. You know by finding out how many of each similar line there is. – Winkz Jun 27 '13 at 21:02
  • after that code is executed you can check inside the dictionary to find the number of drops for each item. so.. if one of the drops is "Big Dagger" you could check dropsDict["Big Dagger"] and that would give you the number of Big Daggers that were dropped. However, I'm sure there's more to modifiedLastpart than just the name of the item, so it could be more complicated. – Dodecapus Jun 27 '13 at 21:06
  • I havde made so that the lines i work with either start with a or the and then the name of the item.. like a dagger or the sword of something etc.. – Winkz Jun 27 '13 at 21:13
0

If you are trying to find how many strings match in all of the lines array(i mean like - "string one" appears 2 times - and - "string two" appears 4 times),create a dictionary outside the foreach and first thing inside foreach place this:

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

foreach (string line in lines)
{
      if (same.ContainsKey(line))
          ++same[line];
      else
          same.Add(line, 1);

      //......
      //do your other stuff
}

Each string that repeats will be updated in the value of dictionary(inside the dictionary will be recorded all strings and how many times they appeared),with that you can check how many times a certain string appeared.

terrybozzio
  • 4,424
  • 1
  • 19
  • 25
0

Maybe this can help you, it's a pice of code thats counts all the duplicate strings in a collection. You have to modify it to suit your needs, but hope you the point.

   var allStrings = new  List<string>{"stringOne", "stringOne", "stringTwo", "stringOne", "stringThree", "stringTwo"};
   var allStringsGrouped = allStrings.GroupBy(i => i);
   foreach (var group in allStringsGrouped)
   {
       System.Diagnostics.Debug.WriteLine(group.Key +" occured " + group.Count() + " times");
   }

Output is as following:

stringOne occured 3 times
stringTwo occured 2 times
stringThree occured 1 times
Jeroen1984
  • 1,616
  • 1
  • 19
  • 32
  • Thanks, but the thing is, for me to enter all the different kind of lines there are would take a massive amount of time. – Winkz Jul 05 '13 at 10:21