2

我有一个具有此实现的对象列表:

 class Locations
    {
        public string Origin { get; set; }
        public string Dest { get; set; }
        public int Total { get; set; }
    }

我需要以 CSV 格式输出一个表,该表将 Origins 作为行,将 Destinations 作为列,以及指定 Origins 和 Destinations 相遇的 Total。我的列表中可能有任意数量的 Location 对象。

鉴于我的列表中的以下记录:

Origin=A
Dest=B
Total=10

Origin=B
Dest=A
Total=20

我的输出看起来像这样(带有“-”字符意味着相同的起点/终点之间没有总计):

Origins/Destinations,A,B
A,-,10
B,20,-

到目前为止,我已经完成了以下工作:

1)遍历列表以输出目的地。

2)遍历List输出一个Origin。对于 Origin,遍历列表以查找与 Origin 相关的每个 Destination 的 Total。

3) 对下一个 Origin 重复 #2。

我的结果不太好,因为行/列数据并不总是匹配。有没有更简单的解决方案?

4

4 回答 4

2

更新

阅读您的更新后,似乎没有必要求和,因为只有一个具有给定Originand的对象Destination。在这种情况下,您可以保留收集的代码places,进行嵌套foreach并简单地获取总数

var item = locations.FirstOrDefault(l => l.Origin == origin && l.Dest == dest);
var total = item == null ? 0 : item.Total;

原始答案

您可以通过以下方式获取所有位置的列表

var locations = new List<Locations>(); // assume it's populated
var places = locations.Select(l => l.Origin)
                      .Concat(locations.Select(l => l.Dest))
                      .Distinct()
                      .OrderBy(s => s); // why not sort it as well

此时,您可以简单地迭代places行并对列进行另一个嵌套迭代:

foreach (var origin in places)
{
    foreach (var dest in places)
    {
        var total = locations.Where(l => l.Origin == origin && l.Dest == dest)
                             .Sum(l => l.Total);
    }
}

您可以立即看到如何轻松构建具有这种结构的表。这种方法的主要缺点是它所做的工作比绝对必要的要多得多。从理论上讲,我们当然可以只迭代一次locations,边走边收集信息。可以总结Total每一对OriginDest

var totals = locations.GroupBy(l => new { l.Origin, l.Dest })
             .ToDictionary(g => Tuple.Create(g.Key.Origin, g.Key.Dest),
                           g => g.Sum(r => r.Total));

此时我们可以从第一个解决方案中获取一个页面:

foreach (var origin in places)
{
    foreach (var dest in places)
    {
        var total = totals[Tuple.Create(origin, dest)]; // almost too easy :)
    }
}
于 2012-05-22T21:03:13.187 回答
1

我不确定我是否正确理解了您的要求,但这有帮助吗?

var destinationGroups = locations
    .GroupBy(l=> l.Dest)
    .Select(grp => new{
        SumTotal = grp.Sum(l => l.Total),
        Destination = grp.Key ,
        CountOrigins = grp.Count()
    });
于 2012-05-22T21:02:59.150 回答
1

您的问题似乎与二维矩阵结构(称为O/D 矩阵)有关,但重要的是不要限制大小。所以,我想建议你使用集合来构建矩阵结构,以允许不限制大小。我将代码片段如下所示以显示如何做。但是,此代码可能不是最佳的,但您可以得到一些亮点。

public class Location
{
    public string Origin { get; set; }
    public string Dest { get; set; }

    public bool Equals(Location other)
    {
        //...
    }

    public override bool Equals(object obj)
    {
        //...
    }

    public override int GetHashCode()
    {
        //...
    }
}

public class ODMatrix
{
    private readonly HashSet<string> _origins = new HashSet<string>();
    private readonly HashSet<string> _dests = new HashSet<string>();
    private readonly Dictionary<Location, int> _values = new Dictionary<Location, int>();

    public int this[Location location]
    {
        get
        {
            if (!_values.ContainsKey(location))
            {
                SetValue(location, 0);
            }
            return _values[location];
        }
        set { SetValue(location, value); }
    }

    private void SetValue(Location location, int value)
    {
        if (!_origins.Contains(location.Origin))
            _origins.Add(location.Origin);
        if (!_dests.Contains(location.Dest))
            _dests.Add(location.Dest);
        _values[location] = value;
    }

    public int this[string origin, string dest]
    {
        get { return this[new Location {Origin = origin, Dest = dest}]; }
        set { this[new Location {Origin = origin, Dest = dest}] = value; }
    }

    public override string ToString()
    {
        var content = new StringBuilder();
        //print dest lables
        content.AppendLine(_dests.Aggregate((x, y) => x + ", " + y));
        foreach (string origin in _origins)
        {
            //print origin lable
            content.Append(origin + ", ");
            foreach (string dest in _dests)
            {
                content.Append(this[origin, dest] + ", ");
            }
            content.Remove(content.Length - 2, 2);
            content.AppendLine();
        }
        return content.ToString();
    }
}

哦,从您更新的问题中,我检查出您要打印原产地和目的地的标签。我认为您可以修改 ToString 方法以显示内容。

[更新] 我更新了打印标签的代码。此代码未经测试,因此可能会或可能不会有问题。

于 2012-05-22T21:44:50.377 回答
0
// Group locations by origin
var table = 
    from location in list
    group location by location.Origin into originGroups
    select new {
        Origin = originGroups.Key
        Destinations = originGroups.ToDictionary(x => x.Dest)
    };

// Get list of destinations
var destinations = list.Select(location => location.Dest).Distinct();

// Output everything
foreach(var location in table)
    foreach(var dest in destinations)
        // output location.Destinations[dest].Total;
于 2012-05-22T21:27:00.500 回答