Есть ли лучший способ создать многомерную строго типизированную структуру данных?

Мне нужна многомерная структура данных, где каждое измерение представляет собой небольшой список, который известен во время разработки.

В разных местах моей программы я хотел бы иметь доступ к данным, "нарезанным" разными измерениями, строго типизированным способом.

Я привел пример кода ниже, который работает для 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>. Добавление большего количества свойств в тип записи не приведет к существенному изменению запросов благодаря сопоставлению шаблонов в записях.