复制PHP的动态多维数组创建

时间:2017-12-01 11:36:51

标签: c# php data-structures

所以,我正在将项目从PHP转换为C#。作为查询的结果获得了通用数据列表

//C# code
public class TermsCommodityModel
{
    public int terms_id { get; set; }
    public int commodity_id { get; set; }
    public int custom { get; set; }
    public int calculated { get; set; }
    public string name { get; set; }
    public string formula { get; set; }
    public int division_id { get; set; }
}

我能够将其填充到termsTable List<TermsCommodityModel>。然后PHP代码开始循环termsTable。(C#和PHP代码使用相同的变量进行简单转换)。第一行彻底改变了我的数据结构

//PHP code
if (!isset($termsTable[$name]))
    $termsTable[$name] = array();

我想,奇怪但可行。然后第二个条件创建了另一个子数组,然后继续。现在PHP代码看起来如此,

//PHP Code
if (!isset($termsTable[$name][$t->commodity_id]))
    $termsTable[$name][$t->commodity_id] = array();
//.Omitted for brevity
//....
$year = date("Y") + 5;
for ($y = 2008; $y<= $year; $y++) {
    $termsTable[$name][$t->commodity_id][$y] = array();
    for ($i=1; $i<=12; $i++)
        $termsTable[$name][$t->commodity_id][$y][$i] = 0;
}   

这是最终的数据结构

//PHP Code
$termsTable[$name]
$termsTable[$name][$t->commodity_id]
$termsTable[$name][$t->commodity_id][$y]
$termsTable[$name][$t->commodity_id][$y][$i]

这实际上是动态地创建了一个对象数组数组的数组。问题是PHP是一种动态类型语言。它不需要指定类型

C#中的哪个数据结构可能会这样做?不能使用tuple,因为它们是分层的,对吧?

哪种方法可以解决这个问题?任何指针都非常有用,因为这有点重要。

3 个答案:

答案 0 :(得分:1)

我对PHP知之甚少,但看起来我可以效仿。您在问题中演示的代码基于关联数组。在.NET中,关联数组通常通过Dictionary<TKey, TValue>数据结构实现。

您从平面List<TermsCommodityModel>开始,然后您可以按如下方式构建基于字典的分层结构:

// a flat list of TermsCommodityModel, filled with data elsewhere
List<TermsCommodityModel> list = new List<TermsCommodityModel>(); 

Dictionary<string, Dictionary<int, Dictionary<int, Dictionary<int, int>>>> termsTable = list
    .GroupBy(tcm => tcm.name)
    .ToDictionary(
        tcmGroup => tcmGroup.Key,
        tcmGroup => tcmGroup.ToDictionary(
            tcm => tcm.commodity_id, 
            tcm => CreateYearMonthTable()));

还有一个辅助函数:

static Dictionary<int, Dictionary<int, int>> CreateYearMonthTable()
{
    var year = DateTime.Now.Year + 5;

    return Enumerable
        .Range(2008, year - 2008 + 1)
        .ToDictionary(
            y => y,
            y => Enumerable.Range(1, 12).ToDictionary(i => i, i => 0));
}

以下是您如何访问此数据结构中的叶值的示例:

string name = "ABC";
int commodityId = 12345;
int year = 2010;
int month = 10;

int value = termsTable[name][commodityId][year][month];

修改

解决问题的更好方法是我的第二个答案: https://stackoverflow.com/a/47593724/4544845

答案 1 :(得分:1)

我不确定TermsCommodityModel与php代码有什么关系,因为据我所知,它并没有显示在任何地方。无论如何,你可以通过(ab)使用dynamicDynamicObject来实现类似于php的语法。首先创建这样的类:

public class DynamicDictionary : DynamicObject {
    private readonly Dictionary<object, object> _dictionary;

    public DynamicDictionary() {
        _dictionary = new Dictionary<object, object>();
    }

    public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) {
        // this will be called when you do myDict[index] (but not myDict[index] = something)
        if (indexes.Length != 1)
            throw new Exception("Only 1-dimensional indexer is supported");
        var index = indexes[0];
        // if our internal dictionary does not contain this key
        // - add new DynamicDictionary for that key and return that
        if (_dictionary.ContainsKey(index)) {
            _dictionary.Add(index, new DynamicDictionary());
        }
        result = _dictionary[index];
        return true;
    }

    public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) {
        // this will be called when you do myDict[index] = value
        if (indexes.Length != 1)
            throw new Exception("Only 1-dimensional indexer is supported");
        var index = indexes[0];
        // just set value
        _dictionary[index] = value;
        return true;
    }
}

并像这样使用它:

dynamic termsTable = new DynamicDictionary();
var name = "name";
int commodityId = 123;
var year = DateTime.Now.Year + 5;
for (int y = 2008; y <= year; y++) {
    for (int i = 1; i < 12; i++) {
        // that's fine
        termsTable[name][commodityId][y][i] = 0;
    }
}

// let's see what we've got:
for (int y = 2008; y <= year; y++) {
    for (int i = 1; i < 12; i++) {
        // that's fine
        Console.WriteLine(termsTable[name][commodityId][y][i]);
    }
}

要更多地镜像您的PHP代码,请更改TryGetIndex,如下所示:

public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) {
    // this will be called when you do myDict[index] (but not myDict[index] = something)
    if (indexes.Length != 1)
        throw new Exception("Only 1-dimensional indexer is supported");
    var index = indexes[0];
    // if our internal dictionary does not contain this key
    // return null
    if (!_dictionary.ContainsKey(index)) {
        result = null;
    }
    else {
        result = _dictionary[index];
    }
    return true;
}

然后你需要检查这个密钥是否已经存在(这在我看来有点好):

dynamic termsTable = new DynamicDictionary();
var name = "name";
int commodityId = 123;
var year = DateTime.Now.Year + 5;
// need to check if such key exists
// like isset in php
if (termsTable[name] == null)
    termsTable[name] = new DynamicDictionary();
if (termsTable[name][commodityId] == null)
    termsTable[name][commodityId] = new DynamicDictionary();
for (int y = 2008; y <= year; y++) {
    if (termsTable[name][commodityId][y] == null)
        termsTable[name][commodityId][y] = new DynamicDictionary();
    for (int i = 1; i < 12; i++) {
        // that's fine
        termsTable[name][commodityId][y][i] = 0;
    }
}

当然,通过这样做可以将类型安全抛到窗外,但如果你对此感觉不错 - 为什么不呢。

答案 2 :(得分:1)

虽然我的第一个答案中的代码再现了用PHP编写的原始逻辑,但它缺少一些非常重要的特性。这不是不言自明的,而且难以阅读。

具体来说,像Dictionary<string, Dictionary<int, Dictionary<int, Dictionary<int, int>>>>这样的东西是一个巨大的反模式。没有人知道这个怪物数据结构的键和值的预期。这太容易出错了。

对代码进行分解的更好方法如下:

public class TermsTable 
{
    private readonly Dictionary<string, IndexByCommodityId> _index;

    public TermsTable(IEnumerable<TermsCommodityModel> list)
    {
        _index = list
            .GroupBy(tcm => tcm.name)
            .ToDictionary(
                tcmGroup => tcmGroup.Key,
                tcmGroup => new IndexByCommodityId(tcmGroup));
    }

    public IndexByCommodityId this[string name] => _index[name];
}

public class IndexByCommodityId
{
    private readonly Dictionary<int, IndexByYear> _index;

    public IndexByCommodityId(IEnumerable<TermsCommodityModel> list)
    {
        _index = list.ToDictionary(
            keySelector: tcm => tcm.commodity_id,
            elementSelector: tcm => new IndexByYear());
    }

    public IndexByYear this[int commodityId] => _index[commodityId];
}

public class IndexByYear
{
    private static readonly int _nowYear = DateTime.Now.Year;
    private readonly Dictionary<int, IndexByMonth> _index;

    public IndexByYear()
    {
        _index = Enumerable
            .Range(2008, _nowYear - 2008 + 1)
            .ToDictionary(
                keySelector: year => year,
                elementSelector: year => new IndexByMonth());
    }

    public IndexByMonth this[int year] => _index[year];
}

public class IndexByMonth
{
    private readonly Dictionary<int, int> _index;

    public IndexByMonth()
    {
        _index = Enumerable.Range(1, 12).ToDictionary(month => month, month => 0);
    }

    public int this[int month]
    {
        get => _index[month];
        set => _index[month] = value;
    }
}

使用新数据结构的代码如下所示:

// a flat list of TermsCommodityModel, filled with data elsewhere
List<TermsCommodityModel> list = new List<TermsCommodityModel>(); 

// create our hierarchical index from the above list
TermsTable aBetterTermsTable = new TermsTable(list);

string name = "ABC";
int commodityId = 12345;
int year = 2010;
int month = 10;
int value = aBetterTermsTable[name][commodityId][year][month];

是的,编写的代码要多得多,但值得。它更容易阅读,更不容易出错。例如,其中一个好处是IntelliSense:

enter image description here