Есть ли лучший способ создать многомерную строго типизированную структуру данных?
Мне нужна многомерная структура данных, где каждое измерение представляет собой небольшой список, который известен во время разработки.
В разных местах моей программы я хотел бы иметь доступ к данным, "нарезанным" разными измерениями, строго типизированным способом.
Я привел пример кода ниже, который работает для 2D-примера, используя вложенные интерфейсы, но я предполагаю, что это будет ужасно ужасно в 3D или 4D. Как определено в @kvb, требуемый код шаблона должен экспоненциально расти.
Есть ли у кого-нибудь лучшее предложение? Под этим я подразумеваю, что код должен быть простым/коротким/легким для понимания, сохраняя при этом способность делать вещи в следующих строках:
Data a = new Data(...)
...
SomeMethodThatOnlyCaresAboutRedThings(a.Red) // takes a IBySize<T>
...
SomeMethodThatOnlyCaresAboutBigThings(a.Big) // takes a IByColour<T>
...
Это позволяет избежать того, что эти методы должны знать о части структуры данных, которые не имеют к ним отношения, что делает их более легко проверяемыми.
Я использовал цвета/размеры здесь только как пример, извинения за непреднамеренное вводящее в заблуждение любого, что эти выборы были значимыми. T может быть простым элементом данных, таким как float или какая-либо другая простая структура данных.
Отмечено как F # и С#, так как я был бы счастлив с решением в.
public interface IByColour<T>
{
T Green { get; }
T Red { get; }
T Blue { get; }
}
public interface IBySize<T>
{
T Small { get; }
T Big { get; }
}
internal class ByColour<T> : IByColour<T>
{
public T Green { get; private set; }
public T Red { get; private set; }
public T Blue { get; private set; }
internal ByColour(T green, T red, T blue)
{
Green = green;
Red = red;
Blue = blue;
}
}
internal class BySize<T> : IBySize<T>
{
public T Small { get; private set; }
public T Big { get; private set; }
internal BySize(T small, T big)
{
Small = small;
Big = big;
}
}
public class Data<T> : IByColour<IBySize<T>>, IBySize<IByColour<T>>
{
public IBySize<T> Green { get; private set; }
public IBySize<T> Red { get; private set; }
public IBySize<T> Blue { get; private set; }
public IByColour<T> Small { get; private set; }
public IByColour<T> Big { get; private set; }
public Data(IBySize<T> green, IBySize<T> red, IBySize<T> blue)
{
Green = green;
Red = red;
Blue = blue;
Small = new ByColour<T>(Green.Small, Red.Small, Blue.Small);
Big = new ByColour<T>(Green.Big, Red.Big, Blue.Big);
}
}
EDIT: уточнить, что я имею в виду под "лучшим", желательным свойством, которое имеет мое решение, и объяснить, как я хотел бы его использовать.
Ответы
Ответ 1
Это звучит как хорошее использование хорошего старомодного DataTable
. Затем вы можете использовать Linq для среза и кости, но вы хотите, и любые уникальные типы, созданные разными комбинациями выбранных столбцов, автоматически генерируются компилятором. Все столбцы в DataTable
строго типизированы, как и результаты запросов к ним. Кроме того, DataColumns
в DataTable
может иметь любой тип вообще, включая сложные объекты или собственные типы перечисления.
Если вы хотите придерживаться более mathy/immutable/F # способа делать что-то, вы можете использовать массив или List
of Tuple<Type1, Type2, .. TypeN>
, который в любом случае похож на DataTable
.
Если вы дадите немного больше информации о том, что вы моделируете, я мог бы привести пример. Я не уверен, должен ли отображаемый вами код представлять одежду, изображения (цветовое пространство RGB) или что-то совершенно другое.
[Через час] Ну, никаких обновлений из OP, поэтому я приступаю к примеру, когда я использую List<Tuple<x, y, ..n>>
и предполагаю, что объекты - предметы одежды.
// Some enums
public enum Size { Small, Medium, Large }
public enum Color { Red, Green, Blue, Purple, Brown }
public enum Segment { Men, Women, Boys, Girls, Infants }
// Fetches the actual list of items, where the object
// item is the actual shirt, sock, shoe or whatever object
static List<Tuple<Size, Color, Segment, object>> GetAllItems() {
return new List<Tuple<Size, Color, Segment, object>> {
Tuple.Create(Size.Small, Color.Red, Segment.Boys, (object)new { Name="I'm a sock! Just one sock." }),
Tuple.Create(Size.Large, Color.Blue, Segment.Infants, (object)new { Name="Baby hat, so cute." }),
Tuple.Create(Size.Large, Color.Green, Segment.Women, (object)new { Name="High heels. In GREEN." }),
};
}
static void test() {
var allItems = GetAllItems();
// Lazy (non-materialized) definition of a "slice" of everything that Small
var smallQuery = allItems.Where(x => x.Item1 == Size.Small);
// Lazy map where the key is the size and the value is
// an IEnumerable of all items that are of that size
var sizeLookup = allItems.ToLookup(x => x.Item1, x => x);
// Materialize the map as a dictionary the key is the size and the
// value is a list of all items that are of that size
var sizeMap = sizeLookup.ToDictionary(x => x.Key, x => x.ToList());
// Proof:
foreach (var size in sizeMap.Keys) {
var list = sizeMap[size];
Console.WriteLine("Size {0}:", size);
foreach (var item in list) {
Console.WriteLine(" Item: {{ Size={0}, Color={1}, Segment={2}, value={3} }}",
item.Item1, item.Item2, item.Item3, item.Item4);
}
}
}
Ответ 2
Вы рассматривали такой подход:
public enum ElementSize
{
Small,
Big
}
public enum ElementColor
{
Green,
Red,
Blue
}
public enum Temperature
{
Hot,
Cold
}
public class Element<T>
{
public T Value { get; set; }
public ElementColor Color { get; set; }
public Temperature Temperature { get; set; }
public ElementSize Size { get; set; }
}
public class Data<T>
{
private readonly IList<Element<T>> list = new List<Element<T>>();
public T Value
{
get
{
if ( list.Count == 1 )
return list[0].Value;
else
throw new Exception("Throw a proper exception or consider not implementing this property at all");
}
}
public Data<T> Green
{
get { return FilterByColor(ElementColor.Green); }
}
public Data<T> Red
{
get { return FilterByColor(ElementColor.Red); }
}
private Data<T> FilterByColor(ElementColor color)
{
return new Data<T>(from x in list where x.Color == color select x);
}
//etc...
public Data<T> Small
{
get { return new Data<T>(from x in list where x.Size == ElementSize.Small select x); }
}
public Data<T> Cold
{
get { return new Data<T>(from x in list where x.Temperature == Temperature.Cold select x); }
}
public void Add(Element<T> element)
{
list.Add(element);
}
public Data(IEnumerable<Element<T>> list)
{
this.list = new List<Element<T>>(list);
}
}
Извините за качество кода. Это просто показать эту идею.
Ответ 3
Это то, что вы могли бы сделать в F #:
/// Use discriminated unions which are safer than enums
type Size = Smal | Big
type Color = Red | Green | Blue
/// Use 'T to demonstrate parameterized records
type Element<'T> = {Value: 'T; Size: Size; Color: Color}
/// Query on a list of elements using pattern matching on records
let getElementsByColor color elements =
List.filter (fun {Color = c} -> c = color) elements
let getElementsBySize size elements =
List.filter (fun {Size = s} -> s = size) elements
По существу, каждое свойство объявляется как свойство в типе записи Element<'T>
. Добавление большего количества свойств в тип записи не приведет к существенному изменению запросов благодаря сопоставлению шаблонов в записях.