Здравствуйте. Не так давно выполнил (во всяком случае так считал) тестовое задание. Описание задачи: Спроектируйте и реализуйте класс-коллекцию для хранения элементов, имеющих уникальный составной ключ [Id, Name] (Id и Name – компоненты ключа, могу быть произвольного типа). Вы можете использовать любую встроенную коллекцию .NET Framework.
Предоставьте необходимые с вашей точки зрения методы для этой коллекции, а так же методы для эффективного по скорости получения элементов по их Id ИЛИ Name.
Программа должна содержать пример использования коллекции с идентификатором ключа в виде пользовательского типа (т.е. Id в ключе – UserType, объекты UserType должны сравниваться по значению).
Дополнительно: Создайте потокобезопасную реализацию этой коллекции (не использовать коллекции из пространства имён System.Collections.Concurrent).
То, что я предложил в качестве решения:
Элемент и UserType:
/// <summary>
/// Элемент коллекции
/// </summary>public class MyElement
{
private UserType id;
private String name;
public String Name
{
get { return name; }
set { name = value; }
}
public UserType Id
{
get { return id; }
set { id = value; }
}
public override int GetHashCode()
{
return Name.GetHashCode() ^ Id.GetHashCode();
}
public override bool Equals(object obj)
{
if (!(obj is MyElement))
{
MyElement el = (MyElement)obj;
return Id == el.Id && Name == el.Name;
}
else
{
return false;
}
}
public override string ToString()
{
return String.Format("Id:{0}, Name:{1}",id.Content.ToString(), Name);
}
}
/// <summary>
/// Тип для поля Id
/// </summary>public struct UserType
{
private int content;
public int Content
{
get { return content; }
set { content = value; }
}
public override int GetHashCode()
{
return Content^Content;
}
public override bool Equals(object obj)
{
if (!(obj is UserType))
{
UserType ut = (UserType)obj;
return Content == ut.Content;
}
else
{
return false;
}
}
public static Boolean operator == (UserType ut1, UserType ut2)
{
return ut1.Content == ut2.Content;
}
public static Boolean operator != (UserType ut1, UserType ut2)
{
return ut1.Content != ut2.Content;
}
public override string ToString()
{
return Content.ToString();
}
}
Индекс:
/// <summary>
/// Индекс для коллекции
/// </summary>public struct MyId : IEquatable<MyId>
{
private readonly UserType id;
private readonly String name;
public UserType Id
{
get
{
return id;
}
}
public String Name
{
get
{
return name;
}
}
//public MyId(UserType _id, String _name)
//{
// id = _id;
// name = _name;
//}public MyId(MyElement element)
{
id = element.Id;
name = element.Name;
}
public override int GetHashCode()
{
return id.GetHashCode() ^ name.GetHashCode();
}
public bool Equals(MyId other)
{
if ((object)other == null)
{
return false;
}
else
{
return id == other.id && name == other.name;
}
}
public override bool Equals(object obj)
{
return Equals((MyId)obj);
}
public static Boolean operator == (MyId u1, MyId u2)
{
return (u1.id == u2.id) && (u1.name == u2.name);
}
public static Boolean operator != (MyId u1, MyId u2)
{
return (u1.id == u2.id) || (u1.name == u2.name);
}
public override string ToString()
{
return String.Format("Key(id={0}, name={1})", id.ToString(), name);
}
}
Собственно, коллекция:
/// <summary>
/// Класс-коллекция. Потокобезопасность реализована для метода Add.
/// Т.к. коллекция наследует от Dictionary, она поддерживает необходимые базовые методы
/// </summary>
/// <typeparam name="TKey">В качестве ключа используется MyId</typeparam>
/// <typeparam name="TValue">Элемент коллекции</typeparam>public class MyCollection<TKey, TValue> : Dictionary<MyId, TValue>
{
private readonly object syncRoot = new object();
private Dictionary<MyId, TValue> _myCollection = new Dictionary<MyId, TValue>();
/// <summary>
/// Перекрываем метод вставки в коллекцию для реализации потокобезопасности.
/// Примечание: при попытка вставить элемент с повторяющимся индексом просто игнорируем вставку.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>public new void Add(MyId key, TValue value)
{
lock (syncRoot)
{
if (!this.ContainsKey(key))
{
base.Add(key, value);
}
}
}
#region Реализация методов поиска указанных в задании
public MyElement GetElementById(UserType _id)
{
dynamic result;
lock (syncRoot)
{
result = this.Where(x => x.Key.Id == _id).SingleOrDefault().Value;
}
if (result is MyElement)
{
return result as MyElement;
}
else
{
return null;
}
}
public MyElement GetElementByName(String _name)
{
dynamic result;
lock (syncRoot)
{
result = this.Where(x => x.Key.Name == _name).SingleOrDefault().Value;
}
if (result is MyElement)
{
return result as MyElement;
}
else
{
return null;
}
}
public MyElement GetElementByIdOrName(object o)
{
if (o is String)
{
return GetElementByName((string)o);
}
else if (o is UserType)
{
return GetElementById((UserType)o);
}
else
{
return null;
}
}
#endregion
}
Этот вариант решения, как видно, не понравился проверявшему.
Буду признателен, если "ткнете носом" в ошибки. Понимаю, что, возможно подошел к решению довольно формально, но по требованиям вроде все реализовал. Или чего-то не так понял.
12/5/2013 6:32 PM, mig84 пишет:
> Этот вариант решения, как видно, не понравился проверявшему. > Буду признателен, если "ткнете носом" в ошибки. Понимаю, что, возможно > подошел к решению довольно формально, но по требованиям вроде все > реализовал. Или чего-то не так понял.
Сколько раз уже здесь срачи на эту тему были. Возможно нафиг им никто не
нужен, возможно кто-то сильно меньше тебя попросил, возможно знакомый
пришел и т.д.
З.Ы. Для тебе это сложно было? Ты много времени потратил? Тебе так важно
устроиться в гадюшник, где даже не соизволили тебе высказать свое мнение
о том, что ты написал? Потому, как это хамство дать человеку делать
тестовое задание и не отвечать и в работе там 100% такой же хамский
подход принят.
Для быстрого поиска под id или name лучше бы иметь две хэш-таблицы. Вообще непонятно причем тут составной ключ, если получать данные предпологается только по отдельным частям ключа.
V>З.Ы. Для тебе это сложно было? Ты много времени потратил? Тебе так важно V>устроиться в гадюшник, где даже не соизволили тебе высказать свое мнение V>о том, что ты написал? Потому, как это хамство дать человеку делать V>тестовое задание и не отвечать и в работе там 100% такой же хамский V>подход принят.
Да, собственно я все это понимаю.
Времени потратил на это совсем немного, но просто любопытны соображения проверяющих.
Здравствуйте, mig84, Вы писали:
Х>>UserType.getHashCode() будет всегда давать 0. Ты число само с собой суммируешь.
M>+1 действительно пропустил. Спасибо. M>Модифицируем до:
Здравствуйте, mig84, Вы писали:
M>Здравствуйте. Не так давно выполнил (во всяком случае так считал) тестовое задание. Описание задачи: M>Этот вариант решения, как видно, не понравился проверявшему. M>Буду признателен, если "ткнете носом" в ошибки. Понимаю, что, возможно подошел к решению довольно формально, но по требованиям вроде все реализовал. Или чего-то не так понял.
Был бы я проверяющим — тоже бы не понравилось. Слишком сложно сделано, много ляпов, да и задание не выполнено. Код джуниора.
1. Id и Name – компоненты ключа, могу быть произвольного типа — этого нет. У вас Id и Name типа string
2. public new void Add(MyId key, TValue value) — зачем вам new? так следует делать только в экстренных случаях
3. lock вообще используется в случайных местах и неоптимально
4. Вот это вообще жесть какая-то.
if (result is MyElement)
{
return result as MyElement;
}
else
{
return null;
}
M>1. Id и Name – компоненты ключа, могу быть произвольного типа — этого нет. У вас Id и Name типа string M>2. public new void Add(MyId key, TValue value) — зачем вам new? так следует делать только в экстренных случаях M>3. lock вообще используется в случайных местах и неоптимально M>4. Вот это вообще жесть какая-то. M>
M> if (result is MyElement)
M> {
M> return result as MyElement;
M> }
M> else
M> {
M> return null;
M> }
M>
Спасибо за комментарий!
1. "Произвольного типа" можно трактовать по разному. Скорее всего тут Вы правы, действительно имелось ввиду generic.
2. Каким образом иначе можно реализовать потокобезопасность при вставке? Можно было бы обойтись Concurent коллекцией, но по заданию — нельзя.
3. lock оправдан в методе Add (на мой взгляд). В GetElementById — лишний.
4. Да, не оптимально. Можно было одной строкой обойтись.
Здравствуйте, mig84, Вы писали:
M>Спасибо за комментарий! M>1. "Произвольного типа" можно трактовать по разному. Скорее всего тут Вы правы, действительно имелось ввиду generic.
Имелось в виду generic. M>4. Да, не оптимально. Можно было одной строкой обойтись.
Дело не в строках, дело в подходе.
От вас наверняка ждали что-то вроде
class KeyValueHolder<TId, TName, T>
{
private int _count;
private readonly Dictionary<TId, Dictionary<TName, T>> _dicById;
private readonly Dictionary<TName, Dictionary<TId, T>> _dicByName;
public KeyValueHolder()
{
_dicById = new Dictionary<TId, Dictionary<TName, T>>();
_dicByName = new Dictionary<TName, Dictionary<TId, T>>();
}
/// <summary>
/// Получить элементы, имеющие заданный Id
/// </summary>public IList<T> GetById(TId id)
{
Dictionary<TName, T> dicName;
if (!_dicById.TryGetValue(id, out dicName)) return new List<T>();
return dicName.Values.ToList();
}
/// <summary>
/// Получить элементы, имеющие заданный Name
/// </summary>public IList<T> GetByName(TName name)
{
Dictionary<TId, T> dicId;
if (!_dicByName.TryGetValue(name, out dicId)) return new List<T>();
return dicId.Values.ToList();
}
/// <summary>
/// Получить элемент по ключу
/// </summary>public T Get(TId id, TName name)
{
Dictionary<TName, T> dicName;
if (!_dicById.TryGetValue(id, out dicName)) return default(T);
T value;
if (dicName.TryGetValue(name, out value)) return value;
return default(T);
}
/// <summary>
/// Добавить элемент
/// </summary>public void Add(TId id, TName name, T value)
{
Dictionary<TName, T> dicName;
if (!_dicById.TryGetValue(id, out dicName))
{
dicName = new Dictionary<TName, T>();
_dicById.Add(id, dicName);
}
if (dicName.ContainsKey(name))
{
throw new ArgumentOutOfRangeException("name", "Element with given (Id,Name) already exists");
}
Dictionary<TId, T> dicId;
if (!_dicByName.TryGetValue(name, out dicId))
{
dicId = new Dictionary<TId, T>();
_dicByName.Add(name, dicId);
}
if (dicId.ContainsKey(id))
{
throw new ArgumentOutOfRangeException("name", "Element with given (Id,Name) already exists");
}
dicId.Add(id, value);
dicName.Add(name, value);
_count++;
}
/// <summary>
/// Удалить элемент
/// </summary>public void Remove(TId id, TName name)
{
Dictionary<TName, T> dicName;
if (!_dicById.TryGetValue(id, out dicName)) return;
Dictionary<TId, T> dicId;
if (!_dicByName.TryGetValue(name, out dicId)) return;
dicId.Remove(id);
dicName.Remove(name);
_count--;
}
public int Count
{
get
{
return _count;
}
}
}
class Program
{
static void Main(string[] args)
{
var holder = new KeyValueHolder<int, string, string>();
holder.Add(5, "Петя", "Значение0");
holder.Add(5, "Вася", "Значение1");
holder.Add(6, "Вася", "Значение2");
holder.Add(7, "Вася", "Значение3");
Debug.Assert(holder.Count == 4);
var elementsByName = holder.GetByName("Вася");
var elementsById = holder.GetById(5);
holder.Remove(7, "Вася");
Debug.Assert(holder.Count == 3);
}
}
Довести до потокобезопасности предлагаю в качестве домашнего задания.
12/5/2013 7:12 PM, mig84 пишет:
> Времени потратил на это совсем немного, но просто любопытны соображения > проверяющих.
Если Хэлкар не тот проверяющий, то соображения у них могут быть любыми.
Но, то что тебе не ответили — это хамство. А хамы обычно всегда хамы.