您将如何通过多个键实现快速查找?

时间:2016-08-04 18:23:43

标签: c# .net dictionary

我希望用可选键创建快速查找(字典?),例如,让我说我有3个键:" first_name"," last_name" ,"邮政编码"

所以我希望能够做到以下(伪代码):

GetValue(first_name) -- would return a list of everyone with that first name
GetValue(first_name, last_name) -- would return a list of everyone with that first name & last name
GetValue(zipcode, first_name) -- would return a list of everyone with that first_name in the specified zipcode

我应该能够查询这些键的所有排列。您将使用什么数据结构?你会如何实现这个?

4 个答案:

答案 0 :(得分:5)

您仍然可以使用常规词典,其中键可以是这样的自定义类型:

public class CompositeKey
{
     public CompositeKey(string firstName, string lastName, string zipCode)
     {
           FirstName = firstName;
           LastName = lastName;
           ZipCode = zipCode;
     }

     public string FirstName { get; }
     public string LastName { get; }
     public string ZipCode { get; }
}

现在,我将覆盖Equals上的GetHashCodeCompositeKey,以提供使复合键唯一的内容,以便Dictionary<TKey, TValue>能够存储唯一的复合键。

最后,我可以这样查询字典:

var value = dict[new CompositeKey(firstName: "Matías", lastName: "Fidemraizer" )];
OP在一些评论中提到了这个问题:

  

我考虑过这种方法,但你会如何查询字典?   for&#34; FirstName =&#34; Matias&#34;仅?

由于您要覆盖EqualsGetHashCode,因此您可以将所有组合添加为整个词典中的键,并且它们可以共存于那里:

Person person = new Person { /* Set members here */ }

// Note that I'll add many keys that have the same value
dict.Add(new CompositeKey(name: "Matías"), person);
dict.Add(new CompositeKey(lastName: "Fidemraizer"), person);
dict.Add(new CompositeKey(firstName: "Matías", lastName: "Fidemraizer"), person);

每个密钥都会产生不同的哈希代码,因此它们可以共存于同一个字典中,并且它们将提供一个强大的工具来查询许多标准和标准组合。

另一种方法

其他方法可能是使用多个字典,其中键是使用某些约定的整个值的连接,值是整个类的实例:

Dictionary<string, Person> names = new Dictionary<string, Person>();
names.Add("matias", new Person { /* Set members here */ });

Dictionary<string, Person> names = new Dictionary<string, Person>();
names.Add("matias:fidemraizer", new Person { /* Set members here */ });

// And so on, for every criteria you want to search...

稍后,您将实现一个代理,以根据给定的条件确定要查询的字典。

Redis

怎么样?

实际上你应该看看Redis,它是一个具有复杂数据结构的键值存储,如散列,集合,排序集等等。也就是说,您可以集中缓存并使用Redis进行分发,并且许多应用程序都可以使用缓存。

使用和安装非常简单(它的可执行程序小于10MB ......)。

@Paparazzi提出了字典方法的问题

他说:

  

具有相同名字的第二个人呢?

如果OP需要考虑这种情况(是的,它不是一个例外情况,所以它值得努力考虑它!),似乎OP需要将数据存储在字典,其中键是整个复合键,值应为List<Person>HashSet<Person>或甚至LinkedList<Person>

此外,这意味着一个(插槽)可以存储许多人,而像这样的查询会获得名字 {{1}的人总会返回"Matías"(list,hash,linkedlist ...)的实现,其中整个返回的集合将是找到的人

IEnumerable<Person>

此外,这种增强的方法还有另一个问题。当您使用KeyValuePair<CompositeKey, ISet<Person>> result; if(dictionary.TryGetValue(new CompositeKey(firstName: "Matías"), out result)) { // I've got either one or many results and I'll decide what to do in // that case! } 之类的复合键进行查询时,整个字典存储可能存储的人名不仅仅是new CompositeKey(firstName: "Matías")名字的人,那么您将获得"Matías",{{1 }或ISet<Person>

获得一个或多个结果的第一次搜索具有复杂度IList<Person>(常量时间),因为整个复合键基于其哈希码存储,但返回第一次搜索的结果不再是字典,任何针对它们的搜索都将是O(N)(您获得的项目越多,查找结果的时间就越多)< /强>

顺便说一句,如果您正试图以其名字找一个人它,因为您知道您可以获得的不仅仅是结果,而且您​​无法期望找到除非只有一个具有全名的人被存储在字典中

因此,如果结果大于LinkedList<Person>,您似乎需要消除结果的歧义,并且可以通过提供超过{$ 1}}的复合键来执行另一个O(1)搜索名字或某些人类用户(或人工智能......)需要手动选择其中一个结果。

总结:

  • 如果您通过提供一个组件来寻找一个人,那么您将承担获得更多结果的风险。然后,如果它是具有UI或某种人工智能的应用程序,则根本不会发生搜索,而是直接从结果中选择项目(这是具有1复杂度的操作):< / LI>
    
O(1)
  • 如果您通过提供一个组件来寻找一个人,并且一旦您的应用程序意识到它得到的结果超过了它可以自动提供另一个组件,您不会对结果执行搜索,但是您将提供一个新的复合键,为主词典提供更多组件,幸运的是,您将获得单一结果。
O(1)

答案 1 :(得分:3)

您可以使用3 Lookup s:

var FirstNamesLookup = data.ToLookup(x => Tuple.Create(x.FirstName), x => x);
var FirstAndLastLookup = data.ToLookup(x => Tuple.Create(x.FirstName, x.LastName), x => x);
var FirstAndZipLookup = data.ToLookup(x => Tuple.Create(x.FirstName, x.zipCode), x => x);

具有特定FirstName的所有记录:

var matches = FirstNamesLookup[Tuple.Create("SomeName")].ToList();

具有特定FirstName和LastName的所有记录:

var matches = FirstAndLastLookup[Tuple.Create("SomeName", "SomeLastName")].ToList();

第三种情况也一样。

答案 2 :(得分:2)

您应该只使用任何泛型集合类型,并使用LINQ进行查找:

var addresses = Enumerable.Empty<Address>();

// would return a list of everyone with that first name
addresses.Where(x => x.FirstName == "firstname");

// would return a list of everyone with that first name & last name
addresses.Where(x => x.FirstName == "firstname" && x.LastName == "lastname");

// would return a list of everyone with that first_name in the specified zipcode
addresses.Where(x => x.FirstName == "firstname" && x.ZipCode == "zipcode");

答案 3 :(得分:0)

基于这里的所有想法以及我被限制使用.NET 2.0的事实,我这样做了。包括它只是为了完整性,以防有人再次遇到这个问题。 [不会将此标记为答案,因为它基于上述@Matias的许多想法,因此将其作为对最终解决方案贡献最大的答案]:

public class PersonKey {
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public int Zipcode { get; private set; }

    public PersonKey() {
        FirstName = null;
        LastName = null;
        Zipcode = int.MinValue;
    }

    public PersonKey(int Zipcode, string FirstName) : this() {
        this.FirstName = FirstName;
        this.Zipcode = Zipcode;
    }

    public PersonKey(string LastName, string FirstName) : this() {
        this.FirstName = FirstName;
        this.LastName = LastName;
    }

    public PersonKey(int Zipcode, string LastName, string FirstName) {
        this.Zipcode = Zipcode;
        this.LastName = LastName;
        this.FirstName = FirstName;
    }

    public List<string> KeyList {
        get {
            var keyLst = new List<string>();
            if (!String.IsNullOrEmpty(FirstName))
                keyLst.Add("FirstName:" + FirstName);

            if (!String.IsNullOrEmpty(LastName))
                keyLst.Add("LastName:" + LastName);

            if (Zipcode != int.MinValue)
                keyLst.Add("Zipcode:" + Zipcode);

            return keyLst;
        }
    }

    public string Key {
        get {
            return MakeKey(KeyList.ToArray());
        }
    }

    public List<string[]> AllPossibleKeys {
        get {
            return CreateSubsets(KeyList.ToArray());
        }
    }

    List<T[]> CreateSubsets<T>(T[] originalArray) {
        List<T[]> subsets = new List<T[]>();

        for (int i = 0; i < originalArray.Length; i++) {
            int subsetCount = subsets.Count;
            subsets.Add(new T[] { originalArray[i] });

            for (int j = 0; j < subsetCount; j++) {
                T[] newSubset = new T[subsets[j].Length + 1];
                subsets[j].CopyTo(newSubset, 0);
                newSubset[newSubset.Length - 1] = originalArray[i];
                subsets.Add(newSubset);
            }
        }

        return subsets;
    }

    internal string MakeKey(string[] possKey) {
        return String.Join(",", possKey);
    }

然后我创建一个这样的缓存:

//declare the cache
        private Dictionary<string, List<Person>> _lookup = new Dictionary<string, List<Person>>();

当我从数据库中读取记录时,我将其存储在缓存中,如下所示:

  var key = new PersonKey(person.ZipCode, person.LastName, person.FirstName);
  List<Person> lst;
  foreach (var possKey in key.AllPossibleKeys) {
      var k = key.MakeKey(possKey);
      if (!_lookup.TryGetValue(k, out lst)) {
           lst = new List<Person>();
           _lookup.Add(k, lst);
      }
      lst.Add(person);
   }

从缓存中查找非常简单:

  List<Person> lst;
  var key = new PersonKey(lastName, firstName);  //more constructors can be added in PersonKey
   _lookup.TryGetValue(key.Key, out lst);