Подтвердить что ты не робот

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

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

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

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

4b9b3361

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