Как сгенерировать уникальный идентификатор для структуры адреса?
У меня есть структура, которая описывает адрес, он выглядит так:
class Address
{
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string Zip { get; set; }
public string Country { get; set; }
}
Я ищу способ создания уникального идентификатора для этой структуры (я предполагаю, что он также должен быть типа string
), который зависит от всех свойств структуры (например, изменение AddressLine1
также приведет к изменению идентификатора структуры).
Я знаю, я мог бы просто объединить все свойства вместе, но это дает слишком длинный идентификатор. Я ищу что-то значительно короче этого.
Я также предполагаю, что количество разных адресов не должно превышать 100М.
Любые идеи о том, как этот идентификатор может быть сгенерирован?
Заранее спасибо.
Предыстория этого:
В базе данных есть несколько разных таблиц, которые содержат некоторую информацию + адресные данные. Данные хранятся в формате, аналогичном описанному выше.
К сожалению, перемещение данных адреса в отдельную таблицу сейчас очень дорого, но я надеюсь, что это будет сделано в будущем.
Мне нужно связать некоторые дополнительные свойства с адресными данными, и я собираюсь создать для этого отдельную таблицу. Вот почему мне нужно однозначно идентифицировать адрес данных.
3 ответа
Сериализуйте все поля в большое двоичное значение. Например, используя конкатенацию с правильным разделением домена.
Затем хэшируйте это значение с помощью криптографического хэша достаточной длины. Я предпочитаю 256 бит, но 128, вероятно, в порядке. Столкновения крайне редки с хорошими хешами, с 256-битным хешем, как SHA-256, они практически невозможны.
Вот возможный способ, о котором думает большинство людей:
- Нормализовать адрес
- Создать хэш из нормализованного адреса
- Сделанный…
Но настоящая проблема возникает, когда вам нужно нормализовать адрес. Например, эти улицы одинаковы:
- "Площадь Сен-Франсуа 14"
- "Площадь Сен-Франсуа 14"
- "Площадь Сен-Франсуа 14"
- "Площадь Сен-Франсуа 14"
- "14 Place SaintFrançois"
Вы можете попытаться нормализовать адрес в нижнем регистре текста, удалив акценты/седильи/тире и с ближайшим символом ASCII и проанализировав число, чтобы оставить его в стороне, но все равно будут непредвиденные исключения. И один другой символ создаст совершенно другой хэш.
Если все ваши адреса полностью не нормализованы, я бы посоветовал полагаться на внешний сервис, такой как here.com.
Есть 3 способа использования сервиса
- Либо используйте сервис, чтобы найти координаты вашего адреса (долгота, широта, высота), затем используйте их в качестве своего идентификатора (или 3 идентификатора)
- Или вы можете использовать сервис, чтобы найти адрес и сохранить свой идентификатор в вашей БД. Недостаток в том, что они не гарантируют, что их ID не изменится.
- Последний — использовать их сервис для поиска вашего адреса в их реестре, а затем использовать их запись (которая будет нормализована в соответствии с их стандартом) для создания хэша.
Мой фаворит - 1. так как мы все еще можем найти адрес обратно по координатам (в то время как это невозможно с хешем), более того, адрес может измениться (например, новое название улицы), а координаты - нет. И последнее, но не менее важное: у вас может быть 2 совершенно разных адреса для одного и того же местоположения, их легче согласовать с помощью координат.
Вот полный пример использования сериализации, хеширования sha256 и кодировки base64 (на основе ответа CodesInChaos):
using System;
using System.IO;
using System.Security.Cryptography;
using System.Runtime.Serialization.Formatters.Binary;
namespace Uniq
{
[Serializable]
class Address
{
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string Zip { get; set; }
public string Country { get; set; }
}
class MainClass
{
public static void Main (string[] args)
{
Address address1 = new Address(){AddressLine1 = "a1"};
Address address2 = new Address(){AddressLine1 = "a1"};
Address address3 = new Address(){AddressLine1 = "a2"};
string unique1 = GetUniqueIdentifier(address1);
string unique2 = GetUniqueIdentifier(address2);
string unique3 = GetUniqueIdentifier(address3);
Console.WriteLine(unique1);
Console.WriteLine(unique2);
Console.WriteLine(unique3);
}
public static string GetUniqueIdentifier(object obj){
if (obj == null) return "0";
SHA256 mySHA256 = SHA256Managed.Create ();
BinaryFormatter formatter = new BinaryFormatter ();
MemoryStream stream = new MemoryStream();
formatter.Serialize(stream, obj);
byte[] hash = mySHA256.ComputeHash(stream.GetArray());
string uniqId = Convert.ToBase64String(hash);
return uniqId;
}
}
}
Изменить: это версия без использования BinaryFormatter
, Вы можете заменить нулевое представление и разделитель полей на все, что вам подходит.
public static string GetUniqueIdentifier(object obj){
if (obj == null) return "0";
SHA256 mySHA256 = SHA256Managed.Create ();
StringBuilder stringRep = new StringBuilder();
obj.GetType().GetProperties()
.ToList().ForEach(p=>stringRep.Append(
p.GetValue(obj, null) ?? '¨'
).Append('^'));
Console.WriteLine(stringRep);
Console.WriteLine(stringRep.Length);
byte[] hash = mySHA256.ComputeHash(Encoding.Unicode.GetBytes(stringRep.ToString()));
string uniqId = Convert.ToBase64String(hash);
return uniqId;
}