C # 4.0 - многомерный ассоциативный массив (или способ имитировать один?) - PullRequest
6 голосов
/ 23 февраля 2011

Я опытный разработчик PHP, переходящий на C #.В настоящее время я работаю над приложением Windows Forms.

В своих поисках я обнаружил, что C # не поддерживает ассоциативные массивы так же свободно, как PHP.Я нашел информацию о словаре и кое-что о «структурах», которые кажутся объектами классов.

Проблема, с которой я сталкиваюсь, заключается в том, чтобы свести в голову не только ассоциативный массив, но и многомерный, который я хочуиспользуется для хранения нескольких счетчиков в серии циклов.

Приложение читает текстовый файл журнала, ищет предопределенную строку, вытаскивает дату в этой строке, когда строка найдена, и увеличивает счетчик дляэто совпадение строк на эту дату.

В PHP это было бы так просто:

// 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;
    }
}

Кажется, что он немного больше вовлечен в C # (что неплохо)вещь).Я попытался использовать предложение, которое я нашел по другому подобному вопросу, но я действительно не следую, как увеличивать или итерировать / выводить содержимое:

// 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);
    }
}

Я даже на правильном пути?Или у кого-то есть лучший / более чистый метод имитации приведенного выше PHP-кода?

ОБНОВЛЕНИЕ

При использовании приведенного выше кода C # я действительно получаю сообщение об ошибке "// если строка найдена "строка.Ошибка: «Ссылка на объект не установлена ​​для экземпляра объекта».Я предполагаю, что это потому, что у меня есть строка в ссылочной секции, а не объект Dictionary.Так что сейчас я не уверен, как увеличить.

ОБНОВЛЕНИЕ 2

Спасибо всем за ваше время.Текущий код теперь работает благодаря пониманию работы словаря.Однако все советы относительно использования классов и объектов для этой ситуации также не потеряны.Я могу рефакторинг для удовлетворения.

Ответы [ 6 ]

4 голосов
/ 23 февраля 2011

Сам код выглядит здравым, единственное, что я вижу пропущенным, - нет никаких проверок, чтобы увидеть, существуют ли значения, прежде чем увеличивать их.

Прежде чем вы позвоните

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

ВыВам нужно будет сделать:

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);
}

Прежде чем пытаться увеличивать что-либо.

Вам нужно инициализировать записи в словаре, вызвав .Add или ["key"] = value.Вызов ++ для неинициализированной словарной записи не будет работать.В зависимости от того, что именно вы пытаетесь достичь, хотя было бы неплохо использовать класс.

2 голосов
/ 23 февраля 2011

Вы можете использовать кортеж для создания многомерного ключа для использования в Словаре.

Dictionary<Tuple<TKey1,TKey2>,TValue>

Или словарь словаря:

Dictionary<TKey1,Dictionart<TKey2,Tvalue>>

Второй вариант более раздражает в работе, но имеет преимущество, которое вы можете индексировать в него только первым ключом, а затем получить все пары ключ-значение, связанные с этим ключом.

Но, возможно, вы можете использовать некоторые linq, но ваш код для этого немного неполон.

1 голос
/ 23 февраля 2011

А как насчет создания класса для этого?

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; } }
}

У вас все еще есть словарь, вероятно, DateTime, LogEntry? Не совсем уверен, что именно вам нужно / что это за ключ.

В любом случае, создание класса кажется «правильным» способом, поскольку вы можете выразить свое намерение более четко.

0 голосов
/ 23 февраля 2011

Вы должны начать думать в терминах ОО. В рабочем коде я бы дал классам некоторое назначение для печати, вместо того, чтобы идти прямо в Консоль, и, возможно, использовал бы стратегию или подобное для форматирования текста, но, по сути, это ОО-способ мышления о проблеме.

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 голосов
/ 23 февраля 2011

Одна вещь, которую вы должны убедиться, что Dictionary не является ValueType и не инициализируется автоматически.

Следовательно, когда вы говорите count_array = null, это означает, что вы сбрасываете ссылку на нулевое местоположение. Просто удалите строку.

Ваш код должен выглядеть так:

    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);
        }
    }

Вы также можете использовать ?? оператор, чтобы гарантировать, что когда словарь имеет значение null, вы назначаете новую ссылку

count_array [dt] = count_array [dt] ?? новый словарь ();

Надеюсь, это поможет вам, даже если вы правильно закодируете это.

0 голосов
/ 23 февраля 2011

Ваш подход может работать, однако вы должны понимать, что Dictionary - это ссылочный тип , что означает, что он должен быть создан перед использованием.Вы создаете словарь «верхнего уровня», но также необходимо создать словари «второго уровня».Но в

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

вы рассчитываете на count_array[string_date.ToShortDateString()], который уже создан (так что его можно запросить).И еще одна проблема заключается в том, что поведение Dictionary<Key, Value> заключается в том, что попытка получить доступ к элементу, который не существует, приводит к исключению (KeyNotFoundException).Существует более мягкий метод TryGetValue, который вы можете использовать.В совокупности вам нужно сделать что-то вроде:

// 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);
    }
}
...