开发者

C# 4.0 - Multidimensional Associative Array (or way to mimic one?)

开发者 https://www.devze.com 2023-02-13 04:44 出处:网络
I\'m an experienced PHP developer transitioning to C#. At present I am working on a Windows Forms application.

I'm an experienced PHP developer transitioning to C#. At present I am working on a Windows Forms application.

I found in my searches that C# doesn't support associative arrays in the same loose fashion PHP does. I have found info on Dictionary and something about "structs" which seem to be class objects.

The trouble I am having is getting my head around not only an Associative array, but a multi dimensional one that I want to use for keeping multiple counts in a series of loops.

The application is reading a text log file, searching for a predefined string, pulling out the date on that line when the string is found, and incrementing a count for that string match on that date.

In PHP, it would be as easy as this:

// Initialize
$count_array[$string_date][$string_keyword] = 0;

...

// if string is found
$count_array[$string_date][$string_keyword] += 1;

...

// To ouput contents of array
foreach($count_array as $date -> $keyword_count_array) {
    echo $date; // output date

    foreach($keyword_count_array as $keyword -> $count) {
        echo $keyword . ": " . $count;
    }
}

It seems to be a little more involved in C# (which isn't a bad thing). I have tried using an suggestion I found on another similar question but I don't really follow how to either increment or iterate/output the contents:

// Initialize
var count_array = new Dictionary<string, Dictionary<string, int>>();
count_array = null;

...

// if string is found - I think the second reference is supposed to be a Dictionary object??
count_array[string_date.ToShortDateString()][string_keyword]++;

...

// To ouput contents of "array"
foreach (KeyValuePair<string, Dictionary<string, int>> kvp in exportArray)
{
    foreach(KeyValuePair<string, int> kvp2 in kvp.Value) 
    {
        MessageBox.Show(kvp.Key + " - " + kvp2.Key + " = " + kvp2.Value);
    }
}

Am I even on the right track? Or does someone have a better/cleaner method of mimicing the PHP code above?

UPDATE

With the above C# code, I actually get an error at the "// if string is found " line. The error is "Object reference is not set to an instance of an object". I am a开发者_如何学Gossuming that it is because I have a string in the secound reference, not a Dictionary object. So right now, I am unsure how to increment.

UPDATE 2

Thanks everyone for your time. Current code is now functional thanks to understanding how Dictionary's work. However all advice regarding the use of classes and objects for this situation is not lost either. I may refactor to suit.


The code itself looks sound, the only thing I see missing is there are no checks to see if the values exist before incrementing them.

Before you call

count_array[string_date.ToShortDateString()][string_keyword]++;

You'll need to do:

string shortDate = string_date.ToShortDateString();
if (!count_array.ContainsKey(shortDate))
{
    count_array.Add(shortDate, new Dictionary<string, int>());
}

if (!count_array[shortDate].ContainsKey(string_keyword))
{
    count_array[shortDate].Add(string_keyword, 0);
}

Before you try incrementing anything.

You need to initialize your dictionary entries by calling .Add or ["key"] = value. Calling ++ on an uninitialized dictionary entry won't work. Depending on what exactly it is you're trying to accomplish though it might be a good idea to use a class.


You can use a tuple to create a multi-dimensional key for use in a Dictionary.

Dictionary<Tuple<TKey1,TKey2>,TValue>

Or a Dictionary of Dictionary:

Dictionary<TKey1,Dictionart<TKey2,Tvalue>>

The second one is more annoying to work with, but has the upside that you can index into it with just the first key and then get all key-value pairs associated with that key.

But perhaps you can use some linq, but your code is a bit incomplete for that.


What about creating a class for this?

public class LogEntry 
{
   private List<int> _lines = new List<int>();
   public string LogContent { get;set; }
   public DateTime Time { get;set; }
   public List<int> Lines { get { return _lines; } }
}

You'd still have a dictionary of probably DateTime, LogEntry? Not entirely sure what exactly you need / what the key is.

Anyways, creating a class seems to be the "correct" way as you can express your intend more clearly.


Your approach can work, however, you need to understand Dictionary is a reference type, which means it has to be created prior to use. You create the “top-level” Dictionary, but the “second-level” dictionaries need to be created as well. But in

count_array[string_date.ToShortDateString()][string_keyword]++;

you count on count_array[string_date.ToShortDateString()] being already created (so that it can be queried). And another problem is that Dictionary<Key, Value> behavior is that an attempt to access an item which does not exist results in an exception (KeyNotFoundException). There is a more lenient TryGetValue method for you to use. Combined, you need to do something along the lines of:

// Initialize
var count_array = new Dictionary<string, Dictionary<string, int>>();

// if string is found - I think the second reference is supposed to be a Dictionary object??
Dictionary<string, int> perDateDict;
var dateKey = string_date.ToShortDateString();
if (!count_array.TryGetValue(dateKey, out perDateDict)
{
    perDateDict = new Dictionary<string, int>();
    count_array.Add(adteKey, perDateDict);
}
int prevValue;
// note that when not found, prevValue will be zero, which is what we need
perDateDict.TryGetValue(string_keyword, out prevValue);
perDateDict[string_keyword] = prevValue+1;

// To ouput contents of "array"
foreach (KeyValuePair<string, Dictionary<string, int>> kvp in exportArray)
{
    foreach(KeyValuePair<string, int> kvp2 in kvp.Value) 
    {
        MessageBox.Show(kvp.Key + " - " + kvp2.Key + " = " + kvp2.Value);
    }
}


One thing you must be make sure Dictionary is not a ValueType and is not auto initialized.

Hence when you say count_array = null it means you are resetting the reference to null location. Just remove the line.

Your code should look like :

    var count_array = new Dictionary<string, Dictionary<string, int>>();

    // if string is found - I think the second reference is supposed to be a Dictionary object??
    string dt = DateTime.Now.ToShortDateString();
    count_array[dt] = new Dictionary<string, int>(); //It is important as you should always give appropriate refernece before doing a fetch.

    count_array[dt]["key"] = 0; //Value types are defaults to 0 so it is not explicitely required.

    //Now you can do 
    count_array[dt]["key"]++;

    // To ouput contents of "array"
    foreach (KeyValuePair<string, Dictionary<string, int>> kvp in count_array)
    {
        foreach (KeyValuePair<string, int> kvp2 in kvp.Value)
        {
            Console.WriteLine(kvp.Key + " - " + kvp2.Key + " = " + kvp2.Value);
        }
    }

You can also use ?? operator to ensure that when Dictionary is null, you assign a new reference.

count_array[dt] = count_array[dt] ?? new Dictionary();

I hope this will help you even you should recode this properly.


You need to start thinking in OO terms. In production code I would give the classes some destination to print to, instead of going directly to Console, and maybe use strategy or similar to format the text, but essentially this is the OO way of thinking about the problem.

class Log {
    Dictionary<DateTime, List<LogEntry>} Entries { get; private set; }

    public void PrintLogs()
    {
        foreach (var date in Entries.Keys)
        {
            Console.WriteLine(date);

            foreach (var entry in Entries[date])
            {
                entry.PrintEntry();
            }
        }
    }
}

class LogEntry {
    public List<string> EntryLines { get; set; }
    public DateTime Date { get; set; }

    public void PrintEntry()
    {
        foreach (var line in EntryLines)
            Console.WriteLine(line);
        }
}
0

精彩评论

暂无评论...
验证码 换一张
取 消