从哈希码生成唯一键

时间:2018-05-07 08:24:58

标签: c# .net gethashcode

我正在上课

class Group
{
    public Collection<int> UserIds { get; set; }
    public int CreateByUserId { get; set; }
    public int HashKey { get; set; }
}

我想基于UsersIds[]CreateByUserId生成一些唯一的哈希键,并将其存储到mongo并在其上进行搜索。

条件:

  1. 每次对于相同的UsersIds[]CreateByUserId
  2. ,我应该使用相同的哈希键 当UsersIds[] 中的用户数增加时,
  3. hashkey应该不同

    为此我倾向于GetHashCode()功能:

    public override int GetHashCode()
    {
        unchecked
        {
            var hash = (int)2166136261;
            const int fnvPrime = 16777619;
    
            List<int> users = new List<int>() { CreateByUserId };
            UserIds.ToList().ForEach(x => users.Add(x));
            users.Sort();
    
            users.ForEach(x => hash = (hash * fnvPrime) ^ x.GetHashCode());
            return hash;
        }
    }
    

    这是一个更好的解决方案还是建议一些更好的解决方案。

3 个答案:

答案 0 :(得分:1)

所以如果打算在数据库中保存哈希值,不要在对象上覆盖GetHashCode,那就是与HashTables(Dictionary,HashSet ..)一起使用,而不是Equals而不是足够独特,适合您的目的。而是使用已建立的哈希函数,例如SHA1。

public string Hash(IEnumerable<int> values)
{
   using (var hasher = new SHA1Managed())
   {
    var hash = hasher.ComputeHash(Encoding.UTF8.GetBytes(string.Join("-", values)));
    return BitConverter.ToString(hash).Replace("-", "");
   }
}

用法:

var hashKey = Hash(UsersIds.Concat(new[]{ CreateByUserId });

如果需要,请排序UsersIds

答案 1 :(得分:0)

HashKey是一个值,用于检查Equals()的来电是否可能产生true的结果。 如果元素可能是正确的元素,或者如果它确定为假元素,则使用该哈希键进行快速决策。

首先,将HashKey替换为Unique Id

如果您想要一个唯一的ID,我建议您使用带有Id列的数据库,如果您将其存储在那里,然后使用其他数据获取Id。 +在mongo DB中,每个条目也都有自己的ID: 见here

  

mongo中的每个对象都有一个id,它们可以排序   插入顺序。获取用户集合有什么问题   对象,迭代它并将其用作递增的ID?[...]

这样:使用数据库获取唯一ID,并使用简单的廉价数学计算HashKey(如果您不再需要它),例如添加用户ID。

以编程方式进行编写: 如果要以编程方式检查它并忽略数据库中的ID,则需要实现给定对象的GetHashKey() - Function和Equals() - Function。

class Group
{
    public Collection<int> UserIds { get; set; }
    public int CreateByUserId { get; set; }

    public override bool Equals(object obj)
    {
        Group objectToCompare = (Group)obj;

        if (this.UserIds.Count != objectToCompare.UserIds.Count)
            return false;

        if (this.CreateByUserId != objectToCompare.CreateByUserId)
            return false;

        foreach (int ownUserId in this.UserIds)
            if (!objectToCompare.UserIds.Contains(ownUserId))
                return false;
        //some elements might be double, i.e. 1: 1,2,2 vs 2: 1,2,3 => not equal. cross check to avoid this error
        foreach (int foreignUserId in objectToCompare.UserIds)
            if (!this.UserIds.Contains(foreignUserId))
                return false;

        return true;
    }

    public override int GetHashCode()
    {
        int sum = CreateByUserId;
        foreach (int userId in UserIds)
            sum += userId;

        return sum;
    }
}

用法:

Group group1 = new Group() { UserIds = ..., CreateByUserId = ...};
Group group2 = new Group() { UserIds = ..., CreateByUserId = ...};
group1.Equals(group2);

Here is the answer to "Why do we need the GetHashCode-Function when we use Equals?"

注意:这肯定不是Equals() - 方法中性能最佳的解决方案。根据需要进行调整。

答案 2 :(得分:0)

通常,如果没有关于数据的一些额外信息,则无法从一大堆其他整数中创建唯一的整数。如果对其允许值范围没有约束,则即使从单个long值也无法创建唯一的int键。

GetHashCode函数不保证您为每个可能的Group对象获取唯一的整数哈希键。但是,良好的散列函数会尝试最小化冲突 - 例如,为不同的对象生成相同的哈希码。在这个SO答案中有很好的哈希函数示例: What is the best algorithm for an overridden System.Object.GetHashCode?

通常,您需要GetHashCode将对象存储为字典和散列集中的键。与之前的回答一样,您需要为该情况重写Equals方法,因为字典和散列集之类的散列表通过在名为bucket的列表中存储具有相同散列码的项来解决冲突。他们使用Equals方法来识别存储桶中的项目。建议您在覆盖GetHashCode时覆盖Equals作为预防措施。

没有具体说明你期望从什么类型的平等&#39; Group&#39;对象。想象一下具有相同CreateByUserID和以下UserIds的两个对象:{1,2}和{2,1}。他们是平等的吗?或者订单很重要?

允许从任何地方更改群组字段不是一个好主意。我会用只读字段实现它:

class Group : IEquatable<Group>
{
    private readonly Collection<int> userIds;

    public ReadOnlyCollection<int> UserIds { get; }
    public int CreateByUserId { get; }
    public int HashKey { get; }

    public Group(int createByUserId, IList<int> createdByUserIDs)
    {
        CreateByUserId = createByUserId;
        userIds = createdByUserIDs != null 
           ? new Collection<int>(createdByUserIDs)
           : new Collection<int>();
        UserIds = new ReadOnlyCollection<int>(userIds);

        HashKey = GetHashCode();
    }

    public void AddUserID(int userID)
    {
        userIds.Add(userID);
        HashKey = GetHashCode();
    }

    //IEquatable<T> implementation is generally a good practice in such cases, especially for value types
    public override bool Equals(object obj) => Equals(obj as Group);

     public bool Equals(Group objectToCompare)
     {
        if (objectToCompare == null)
            return false;

        if (ReferenceEquals(this, objectToCompare))
            return true;

        if (UserIds.Count != objectToCompare.UserIds.Count || CreateByUserId != objectToCompare.CreateByUserId)
            return false;

        //If you need equality when order matters - use this
        //return UserIds.SequenceEqual(objectToCompare.UserIds);


        //This is for set equality. If this is your case and you don't allow duplicates then I would suggest to use HashSet<int> or ISet<int> instead of Collection<int>
        //and use their methods for more concise and effective comparison
        return UserIds.All(id => objectToCompare.UserIds.Contains(id)) && objectToCompare.UserIds.All(id => UserIds.Contains(id));
    }

    public override int GetHashCode()
    {
        unchecked // to suppress overflow exceptions
        {
            int hash = 17;          
            hash = hash * 23 + CreateByUserId.GetHashCode();

            foreach (int userId in UserIds)
                hash = hash * 23 + userId.GetHashCode();

            return hash;
        }
    }
}