【问题标题】:Traverse list to add elements using .NET遍历列表以使用 .NET 添加元素
【发布时间】:2010-03-23 19:50:59
【问题描述】:

我有一个对象列表。每个对象都有一个整数数量和一个包含月份和年份值的 DateTime 变量。我想遍历列表并通过添加缺失的月份(数量为 0)来填充列表,以便在列表中表示所有连续的月份。实现这一目标的最佳方法是什么?

示例: 原名单

{ Jan10, 3 }, { Feb10, 4 }, { Apr10, 2 }, { May10, 2 }, { Aug10, 3 }, { Sep10, -3 }, { Oct10, 6 }, { Nov10, 3 }, { 10 月 10 日, 7 }, { 2 月 11 日, 3 }

新列表

{ Jan10, 3 }, { Feb10, 4 }, {Mar10, 0}, { Apr10, 2 }, { May10, 2 }, { Jun10, 0 }, { Jul10, 0 } { Aug10, 3 }, { Sep10, -3 }, { Oct10, 6 }, { Nov10, 3 }, { Dec10, 7 }, { Jan11, 0 },{ 2011 年 2 月 3 日}

【问题讨论】:

  • 你如何拥有一个包含月份和年份的 DateTime 变量?该类有一个日期类型的字段/属性,它设法不持有这一天?或者,它是一个字符串(“Apr10”)字段/属性吗?
  • 这个列表应该是一天吗?
  • @Addie 如果您使用 Jan2010 而不是 Jan10,您的示例可能对人们来说更清楚,看起来很多人认为这意味着 the 10th of Janurary,尽管您指定了 @ 987654324@ 在您的文本中。
  • 这个列表是动态的。 DateTime 对象的唯一重要部分是月份和年份。该列表通常包括多个年份,例如 Jan10、Feb10、Jan11 等。我之前也按日期对列表进行了排序。 @Patrick DateTime 对象将保存月份和年份以外的值,但我不关心它们,也不会访问这些值。我将 Apr10 写为代表 DateTime 对象的伪代码,只有月份和年份很重要
  • @JaredPar 我不明白你的问题。你能澄清一下吗?该列表是月/年 DateTime 对象以及表示数量的整数值的集合。

标签: c# linq list linq-to-objects


【解决方案1】:

一种可能的算法是跟踪前几个月和当前月份。如果上一个和当前之间的差异是 1 个月,则将当前附加到结果中。如果差值超过一个月,则先添加缺失的月份,然后复制当前月份。

Foo prev = months.First();
List<Foo> result = new List<Foo> { prev };
foreach (Foo foo in months.Skip(1))
{
    DateTime month = prev.Month;
    while (true)
    {
        month = month.AddMonths(1);
        if (month >= foo.Month)
        {
            break;
        }
        result.Add(new Foo { Month = month, Count = 0 });
    }
    result.Add(foo);
    prev = foo;
}

结果:

01-01-2010 00:00:00: 3
01-02-2010 00:00:00: 4
01-03-2010 00:00:00: 0
01-04-2010 00:00:00: 2
01-05-2010 00:00:00: 2
01-06-2010 00:00:00: 0
01-07-2010 00:00:00: 0
01-08-2010 00:00:00: 3
01-09-2010 00:00:00: -3
01-10-2010 00:00:00: 6
01-11-2010 00:00:00: 3
01-12-2010 00:00:00: 7
01-01-2011 00:00:00: 0
01-02-2011 00:00:00: 3

编译所需的其他代码:

class Foo
{
    public DateTime Month { get; set; }
    public int Count { get; set; }
}

List<Foo> months = new List<Foo>
{
    new Foo{ Month = new DateTime(2010, 1, 1), Count = 3 },
    new Foo{ Month = new DateTime(2010, 2, 1), Count = 4 },
    new Foo{ Month = new DateTime(2010, 4, 1), Count = 2 },
    new Foo{ Month = new DateTime(2010, 5, 1), Count = 2 },
    new Foo{ Month = new DateTime(2010, 8, 1), Count = 3 },
    new Foo{ Month = new DateTime(2010, 9, 1), Count = -3 },
    new Foo{ Month = new DateTime(2010, 10, 1), Count = 6 },
    new Foo{ Month = new DateTime(2010, 11, 1), Count = 3 },
    new Foo{ Month = new DateTime(2010, 12, 1), Count = 7 },
    new Foo{ Month = new DateTime(2011, 2, 1), Count = 3 }
};

注意:为简单起见,我没有处理原始列表为空的情况,但您应该在生产代码中执行此操作。

【讨论】:

  • 这符合规范,非常简单,而且性能很好。
  • 实际上,即使它符合规范,也不需要创建新的对象列表,因为您可以使用 yield 指令遍历有序列表并仅返回新的缺失对象(详情如下)
【解决方案2】:

假设结构被保存为List&lt;Tuple&lt;DateTime,int&gt;&gt;

var oldList = GetTheStartList();
var map = oldList.ToDictionary(x => x.Item1.Month);

// Create an entry with 0 for every month 1-12 in this year 
// and reduce it to just the months which don't already 
// exist 
var missing = 
  Enumerable.Range(1,12)
  .Where(x => !map.ContainsKey(x))
  .Select(x => Tuple.Create(new DateTime(2010, x,0),0))

// Combine the missing list with the original list, sort by
// month 
var all = 
  oldList
  .Concat(missing)
  .OrderBy(x => x.Item1.Month)
  .ToList();

【讨论】:

  • 他在同一个列表中有 Jan10 和 Jan11。
  • @Tanzelax,好像打错了,我加个评论确认一下
  • 不,他希望最终列表中有 14 个元素,从 2010 年 1 月到 2011 年 2 月
  • 有趣的是,几乎所有发布回复的人都在他的示例中忽略了这一点。 :p
  • @Tanzelax。正确,我特别需要它来考虑多年。
【解决方案3】:
var months = new [] { "Jan", "Feb", "Mar", ... };
var yourList = ...;
var result = months.Select(x => {
  var yourEntry = yourList.SingleOrDefault(y => y.Month = x);
  if (yourEntry != null) {
    return yourEntry;
  } else {
    return new ...;
  }
});

【讨论】:

    【解决方案4】:

    如果我对“日期时间”月份的理解正确:

        for (int i = 0; i < 12; i++)
            if (!original.Any(n => n.DateTimePropery.Month == i))
                original.Add(new MyClass {DateTimePropery = new DateTime(2010, i, 1), IntQuantity = 0});
        var sorted = original.OrderBy(n => n.DateTimePropery.Month);
    

    【讨论】:

      【解决方案5】:

      一种方法是实现对象的 IEqualityComparer,然后您可以使用“Except”扩展方法创建“填充”对象列表以添加到现有列表中。有点像下面

      public class MyClass
      {
          public DateTime MonthYear { get; set; }
          public int Quantity { get; set; }
      }
      
      public class MyClassEqualityComparer : IEqualityComparer<MyClass>
      {
          #region IEqualityComparer<MyClass> Members
      
          public bool Equals(MyClass x, MyClass y)
          {
              return x.MonthYear == y.MonthYear;
          }
      
          public int GetHashCode(MyClass obj)
          {
              return obj.MonthYear.GetHashCode();
          }
      
          #endregion
      }
      

      然后你可以做这样的事情

      // let this be your real list of objects    
      List<MyClass> myClasses = new List<MyClass>() 
      {
          new MyClass () { MonthYear = new DateTime (2010,1,1), Quantity = 3},
          new MyClass() { MonthYear = new DateTime (2010,12,1), Quantity = 2}
      };
      
      List<MyClass> fillerClasses = new List<MyClass>();
      for (int i = 1; i < 12; i++)
      {
          MyClass filler = new MyClass() { Quantity = 0, MonthYear = new DateTime(2010, i, 1) };
          fillerClasses.Add(filler);
      }
      
      myClasses.AddRange(fillerClasses.Except(myClasses, new MyClassEqualityComparer()));
      

      【讨论】:

      • 哦,和我的解决方案类似:)
      【解决方案6】:

      考虑到年份、速度和可扩展性,它可以作为可枚举的扩展来完成(甚至可能使用通用属性选择器)。 如果日期已经被截断为月份,并且在 FillMissing 执行之前对列表进行了排序,请考虑这种方法:

      public static class Extensions
      {
          public static IEnumerable<Tuple<DateTime, int>> FillMissing(this IEnumerable<Tuple<DateTime, int>> list)
          {
              if(list.Count() == 0)
                  yield break;
              DateTime lastDate = list.First().Item1;
              foreach(var tuple in list)
              {
                  lastDate = lastDate.AddMonths(1);
                  while(lastDate < tuple.Item1)
                  {
                      yield return new Tuple<DateTime, int>(lastDate, 0);
                      lastDate = lastDate.AddMonths(1);
                  }
                  yield return tuple;
                  lastDate = tuple.Item1;
              }
          }
      }
      

      并以示例形式:

          private List<Tuple<DateTime, int>> items = new List<Tuple<DateTime, int>>()
          {
              new Tuple<DateTime, int>(new DateTime(2010, 1, 1), 3),
              new Tuple<DateTime, int>(new DateTime(2010, 2, 1), 4),
              new Tuple<DateTime, int>(new DateTime(2010, 4, 1), 2),
              new Tuple<DateTime, int>(new DateTime(2010, 5, 1), 2),
              new Tuple<DateTime, int>(new DateTime(2010, 8, 1), 3),
              new Tuple<DateTime, int>(new DateTime(2010, 9, 1), -3),
              new Tuple<DateTime, int>(new DateTime(2010, 10, 1), 6),
              new Tuple<DateTime, int>(new DateTime(2010, 11, 1), 3),
              new Tuple<DateTime, int>(new DateTime(2010, 12, 1), 7),
              new Tuple<DateTime, int>(new DateTime(2011, 2, 1), 3)
          };
      
          public Form1()
          {
              InitializeComponent();
              var list = items.FillMissing();
              foreach(var element in list)
              {
                  textBox1.Text += Environment.NewLine + element.Item1.ToString() + " - " + element.Item2.ToString();
              }
          }
      

      这将导致文本框包含:

      2010-01-01 00:00:00 - 3
      2010-02-01 00:00:00 - 4
      2010-03-01 00:00:00 - 0
      2010-04-01 00:00:00 - 2
      2010-05-01 00:00:00 - 2
      2010-06-01 00:00:00 - 0
      2010-07-01 00:00:00 - 0
      2010-08-01 00:00:00 - 3
      2010-09-01 00:00:00 - -3
      2010-10-01 00:00:00 - 6
      2010-11-01 00:00:00 - 3
      2010-12-01 00:00:00 - 7
      2011-01-01 00:00:00 - 0
      2011-02-01 00:00:00 - 3
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-11-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多