11

我需要一个多维数据结构,其中每个维度都是一个在设计时已知的小列表。

在我程序的不同位置,我希望能够以强类型的方式访问按不同维度“切片”的数据。

我在下面放了一些示例代码,它们适用于使用嵌套接口的 2D 示例,但我想它在 3D 或 4D 中会变得非常可怕。正如@kvb 所指出的,所需的样板代码将呈指数级增长。

有人有更好的建议吗?我的意思是,保持代码简单/简短/易于理解,同时仍然保留按照以下几行做事的能力:

Data a = new Data(...)
...
SomeMethodThatOnlyCaresAboutRedThings(a.Red) // takes a IBySize<T>
...
SomeMethodThatOnlyCaresAboutBigThings(a.Big) // takes a IByColour<T>
...

这避免了那些方法必须了解与它们无关的数据结构部分,从而使它们更容易测试。

我在这里仅使用颜色/尺寸作为示例,对于无意中误导任何人这些选择是有意义的,我深表歉意。T 可以是一个简单的数据项,如浮点数或其他一些简单的数据结构。

标记为 F# 和 C#,因为我对任何一个解决方案都很满意。

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);
    }
}

编辑:澄清我的意思是“更好”,这是我的解决方案所具有的理想属性,并解释我想如何使用它。

4

3 回答 3

5

这听起来像一个很好的老式的使用DataTable。然后,您可以使用 Linq 来根据需要进行切片和切块,并且由所选列的不同组合创建的任何唯一类型都由编译器自动生成。a 中的所有列DataTable都是强类型的,对它们的查询结果也是如此。此外,DataColumnsin aDataTable可以具有任何类型,包括复杂对象或您自己的枚举类型。

如果您想坚持使用更数学/不可变/ F# 的做事方式,您可以使用数组或Listof Tuple<Type1, Type2, .. TypeN>,这与 a 基本上是一样的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's 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);
        }
    }
}
于 2012-10-18T16:55:06.290 回答
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);
    }
}

对不起代码质量。这只是为了展示这个想法。

于 2012-10-18T19:11:21.137 回答
1

这是您可以在 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>。由于记录上的模式匹配,向记录类型添加更多属性不会改变查询。

于 2012-10-18T20:06:25.617 回答