有没有办法使用连接的字符串键访问嵌套字典? [复制]

Posted

技术标签:

【中文标题】有没有办法使用连接的字符串键访问嵌套字典? [复制]【英文标题】:Is there a way to access a nested dictionary with a concatenated string key? [duplicate] 【发布时间】:2019-12-21 08:35:40 【问题描述】:

我想要一种方法来访问嵌套字典中的值,使用该值的完整路径作为连接的字符串键。

public Dictionary<string, object> info =
    new Dictionary<string, object>
    
        
            "Gen",
            new Dictionary<string, string>
            
                "name", "Genesis",
                "chapters", "50",
                "before", "",
                "after", "Exod"
            
        ,
        
            "Exod",
            new Dictionary<string, string>
            
                "name", "Exodus",
                "chapters", "40",
                "before", "Gen",
                "after", "Lev"
            
        
    ;

string some1 = info["gen"]["name"]; // to get "Genesis"
string some2 = info["gen, name"]; //not work, but i would need some similar

目前,我可以访问嵌套字典并抛出很多括号,例如在some1 上,但是由于我可以将完整路径作为字符串获取(并且很容易作为数组获取)我真的想要一种方法作为some2,让我的代码更加动态。

【问题讨论】:

您是否考虑过制作一种方法来接收您的字典结构和键? GetFoo(info, ["gen", "name"]) 您是否有任何理由使用嵌套字典而不是自定义类? @RufusL 因为我的结构可能要复杂得多并且总是可变的,所以我几乎不会有两个格式相同的结构。 @schil227 在我的示例中,我正在获取数据,但我还需要进行归因,例如 info["gen, name"] = "Genebra" 一般的回答是,“不,你不能那样做”。 【参考方案1】:

我不在方括号内,但如果有任何兴趣,我确实有一些扩展方法。

他们可以无休止地遍历并获取一个对象或在字典键链的末尾设置一个通用值。 Get() 返回一个对象,但如果需要,它很容易修复为泛型类型。

由于键是基于参数的,如果您有一个非常动态的环境,您可以放入一个动态的键数组。

像这样工作:

    var exists1 = info.Exists("Gen", "name", "One key too much"); // false
    var exists2 = info.Exists("Gen", "chapters"); // true

    var value1 = info.Get("Gen", "name", "One key too much"); // null
    var value2 = info.Get("Gen", "chapters"); // "50"

    var valueToSet = "myNewValue";

    var successSet1 = info.TrySet(valueToSet, "Gen", "before", "One key too much"); // false
    var successSet2 = info.TrySet(valueToSet, "Gen", "after"); // true | "Gen" -> "after" set to "myNewValue"

    var dictionary1 = info.FindSubDictionary("Gen", "name", "One key too much"); // null
    var dictionary2 = info.FindSubDictionary("Gen", "chapters"); // "Gen" sub dictionary

我没有对它进行太多测试,也没有考虑过它应该在什么时候返回,但是如果你觉得它有用,它可能是开始修补的东西。

4 种扩展方法主要协同工作,提供 Exists、Get、TrySet 和 (应该是什么) 一个内部方法来解析字典树。

TrySet:

/// <summary>
/// Tries to set a value to any dictionary found at the end of the params keys, or returns false
/// </summary>
public static bool TrySet<T>(this System.Collections.IDictionary dictionary, T value, params string[] keys)

    // Get the deepest sub dictionary, set if available
    var subDictionary = dictionary.FindSubDictionary(keys);
    if (subDictionary == null) return false;

    subDictionary[keys.Last()] = value;
    return true;

获取:

/// <summary>
/// Returns a value from the last key, assuming there is a dictionary available for every key but last
/// </summary>
public static object Get(this System.Collections.IDictionary dictionary, params string[] keys)

    var subDictionary = dictionary.FindSubDictionary(keys);
    if (subDictionary == null) return null; // Or throw

    return subDictionary[keys.Last()];

存在:

/// <summary>
/// Returns whether the param list of keys has dictionaries all the way down to the final key
/// </summary>
public static bool Exists(this System.Collections.IDictionary dictionary, params string[] keys)

    // If we have no keys, we have traversed all the keys, and should have dictionaries all the way down.
    // (needs a fix for initial empty key params though)
    if (keys.Count() == 0) return true;

    // If the dictionary contains the first key in the param list, and the value is another dictionary, 
    // return that dictionary with first key removed (recursing down)
    if (dictionary.Contains(keys.First()) && dictionary[keys.First()] is System.Collections.IDictionary subDictionary)
        return subDictionary.Exists(keys.Skip(1).ToArray());

    // If we didn't have a dictionary, but we have multiple keys left, there are not enough dictionaries for all keys
    if (keys.Count() > 1) return false; 

    // If we get here, we have 1 key, and we have a dictionary, we simply check whether the last value exists,
    // thus completing our recursion
    return dictionary.Contains(keys.First());

查找子字典:

/// <summary>
/// Returns the possible dictionary that exists for all keys but last. (should eventually be set to private)
/// </summary>
public static System.Collections.IDictionary FindSubDictionary(this System.Collections.IDictionary dictionary, params string[] keys)

    // If it doesn't exist, don't bother
    if (!dictionary.Exists(keys)) return null; // Or throw

    // If we reached end of keys, or got 0 keys, return
    if (keys.Count() == 0) return null; // Or throw

    // Look in the current dictionary if the first key is another dictionary.
    return dictionary[keys.First()] is System.Collections.IDictionary subDictionary
        ? subDictionary.FindSubDictionary(keys.Skip(1).ToArray()) // If it is, follow the subdictionary down after removing the key just used
        : keys.Count() == 1 // If we only have one key remaining, the last key should be for a value in the current dictionary. 
            ? dictionary // Return the current dictionary as it's the proper last one
            : null; // (or throw). If we didn't find a dictionary and we have remaining keys, the dictionary tree is invalid

编辑: 注意到您需要 CSV 密钥。从上面构建应该很容易,但我使用单个 csv-key 和一个额外的可选自定义分隔符做了一个简单的示例。

    var csvResult1 = info.Get("Gen, chapters, One key too much"); // null
    var csvResult2 = info.Get("Gen, chapters"); // "50"

    // With custom separator
    var csvResult3 = info.Get("Gen;chapters;One key too much", separator: ";"); 
    var csvResult4 = info.Get("Gen; chapters", separator: ";"); 

代码:

/// <summary>
/// Returns a value from the last key of a csv string with keys, assuming there is a dictionary available for every key but the last.
/// </summary>
public static object Get(this System.Collections.IDictionary dictionary, string csvKeys, string separator = ",")

    if (String.IsNullOrEmpty(csvKeys)) throw new ArgumentNullException("Csv key input parameters is not allowed to be null or empty", nameof(csvKeys));

    var keys = csvKeys.Split(separator).Select(k => k.Trim());
    var subDictionary = dictionary.FindSubDictionary(keys.ToArray());
    if (subDictionary == null) return null; // Or throw

    return subDictionary[keys.Last()];

不过,你最好测试最后一个,我没有运行它。不过应该可以正常运行...我只是不能保证它会:)

【讨论】:

我希望有更简单的东西,但这必须解决。非常感谢! 确实有点乱,其中三个方法真的只是帮助说明用法。它实际上只是归结为字典递归,可以简化为:if (!keys.Any()) return current; return current.Contains(keys[0]) &amp;&amp; current[keys[0]] is IDictionary child ? child.FindDictionary(keys.Skip(1).ToArray()) : null;。几乎只是一个偏好问题,最终如何使用它:) 我确实建议对其进行更改以更好地满足您的确切需求。

以上是关于有没有办法使用连接的字符串键访问嵌套字典? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如何仅使用子键访问嵌套字典的值?

如何识别嵌套字典中的最高值键? [复制]

嵌套字典 copy() 还是 deepcopy()? [复制]

如何将嵌套字典列表与它们的值中的公共键相加? [复制]

python字典的简单操作(修改删除嵌套遍历复制)

python3嵌套字典解包格式字符串