确定一组日期的事件重复模式

发布于 2024-10-01 18:37:10 字数 172 浏览 6 评论 0原文

我正在寻找一种模式、算法或库,它将采用一组日期并在其中一个退出时返回重复的描述,即集合 [11-01-2010, 11-08-2010, 11-15-2010 , 11-22-2010, 11-29-2010] 会产生类似“十一月的每个星期一”的结果。

有没有人以前见过这样的事情或者对实施它的最佳方法有任何建议?

I am looking for a pattern, algorithm, or library that will take a set of dates and return a description of the recurrence if one exits, i.e. the set [11-01-2010, 11-08-2010, 11-15-2010, 11-22-2010, 11-29-2010] would yield something like "Every Monday in November".

Has anyone seen anything like this before or have any suggestions on the best way to implement it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

无风消散 2024-10-08 18:37:10

语法进化 (GE) 适合此类问题,因为您正在寻找一个答案坚持某种语言。语法进化还用于程序生成、作曲、设计等。

我会这样处理这个任务:

用语法构建问题空间

构造一个可以表示所有所需重复模式的上下文无关语法。考虑如下的生产规则:

datepattern -> datepattern 'and' datepattern
datepattern -> frequency bounds
frequency -> 'every' ordinal weekday 'of the month'
frequency -> 'every' weekday
ordinal -> ordinal 'and' ordinal
ordinal -> 'first' | 'second' | 'third'
bounds -> 'in the year' year

由这些规则生成的模式的一个示例是:“2010 年每月的第二个和第三个星期三以及 2011 年的每个星期二”

实现此类的一种方法语法将通过类层次结构进行,稍后您将通过反射对其进行操作,就像我在下面的示例中所做的那样。

将此语言映射到一组日期

您应该创建一个函数,该函数从您的语言中获取一个子句,并递归地返回它所涵盖的所有日期的集合。这使您可以将您的答案与输入进行比较。

在语法的指导下,搜索潜在的解决方案

您可以使用遗传算法或模拟退火将日期与语法相匹配,使用动态编程试试运气,或者从简单的强力枚举所有可能的方法开始条款。

如果您使用遗传算法,您的突变概念应该包括根据您的生产规则之一的应用将一个表达式替换为另一个表达式。

请查看以下 GE 相关网站以获取代码和信息:
http://www.bangor.ac.uk/~eep201/jge/
http://nohejl.name/age/
http://www.genicprogramming.us/Home_Page.html

评估每个解决方案

函数可以考虑解决方案的文本长度、多次生成的日期数量、错过的日期数量以及生成的错误日期数量。

示例代码

根据请求,并且因为这是一个非常有趣的挑战,我编写了该算法的基本实现来帮助您入门。虽然它可以工作,但它还没有完成,但设计绝对应该得到更多的思考,一旦您从这个示例中收集到基本要点,我建议您考虑使用我上面提到的库之一。

  /// <summary>
  ///  This is a very basic example implementation of a grammatical evolution algorithm for formulating a recurrence pattern in a set of dates.
  ///  It needs significant extensions and optimizations to be useful in a production setting.
  /// </summary>
  static class Program
  {

    #region "Class hierarchy that codifies the grammar"

    class DatePattern
    {

      public Frequency frequency;
      public Bounds bounds;

      public override string ToString() { return "" + frequency + " " + bounds; }

      public IEnumerable<DateTime> Dates()
      {
        return frequency == null ? new DateTime[] { } : frequency.FilterDates(bounds.GetDates());
      }

    }

    abstract class Bounds
    {
      public abstract IEnumerable<DateTime> GetDates();
    }

    class YearBounds : Bounds
    {

      /* in the year .. */
      public int year;

      public override string ToString() { return "in the year " + year; }

      public override IEnumerable<DateTime> GetDates()
      {
        var firstDayOfYear = new DateTime(year, 1, 1);
        return Enumerable.Range(0, new DateTime(year, 12, 31).DayOfYear)
          .Select(dayOfYear => firstDayOfYear.AddDays(dayOfYear));
      }
    }

    abstract class Frequency
    {
      public abstract IEnumerable<DateTime> FilterDates(IEnumerable<DateTime> Dates);
    }

    class WeeklyFrequency : Frequency
    {

      /* every .. */
      public DayOfWeek dayOfWeek;

      public override string ToString() { return "every " + dayOfWeek; }

      public override IEnumerable<DateTime> FilterDates(IEnumerable<DateTime> Dates)
      {
        return Dates.Where(date => (date.DayOfWeek == dayOfWeek));
      }

    }

    class MonthlyFrequency : Frequency
    {

      /* every .. */
      public Ordinal ordinal;
      public DayOfWeek dayOfWeek;
      /* .. of the month */

      public override string ToString() { return "every " + ordinal + " " + dayOfWeek + " of the month"; }

      public override IEnumerable<DateTime> FilterDates(IEnumerable<DateTime> Dates)
      {
        return Dates.Where(date => (date.DayOfWeek == dayOfWeek) && (int)ordinal == (date.Day - 1) / 7);
      }

    }

    enum Ordinal { First, Second, Third, Fourth, Fifth }

    #endregion

    static Random random = new Random();
    const double MUTATION_RATE = 0.3;
    static Dictionary<Type, Type[]> subtypes = new Dictionary<Type, Type[]>();

    static void Main()
    {

      // The input signifies the recurrence 'every first thursday of the month in 2010':
      var input = new DateTime[] {new DateTime(2010,12,2), new DateTime(2010,11,4),new DateTime(2010,10,7),new DateTime(2010,9,2),
                    new DateTime(2010,8,5),new DateTime(2010,7,1),new DateTime(2010,6,3),new DateTime(2010,5,6),
                    new DateTime(2010,4,1),new DateTime(2010,3,4),new DateTime(2010,2,4),new DateTime(2010,1,7) };


      for (int cTests = 0; cTests < 20; cTests++)
      {
        // Initialize with a random population
        int treesize = 0;
        var population = new DatePattern[] { (DatePattern)Generate(typeof(DatePattern), ref treesize), (DatePattern)Generate(typeof(DatePattern), ref treesize), (DatePattern)Generate(typeof(DatePattern), ref treesize) };
        Run(input, new List<DatePattern>(population));
      }
    }

    private static void Run(DateTime[] input, List<DatePattern> population)
    {
      var strongest = population[0];
      int strongestFitness = int.MinValue;
      int bestTry = int.MaxValue;
      for (int cGenerations = 0; cGenerations < 300 && strongestFitness < -100; cGenerations++)
      {
        // Select the best individuals to survive:
        var survivers = population
            .Select(individual => new { Fitness = Fitness(input, individual), individual })
            .OrderByDescending(pair => pair.Fitness)
            .Take(5)
            .Select(pair => pair.individual)
            .ToArray();
        population.Clear();

        // The survivers are the foundation for the next generation:
        foreach (var parent in survivers)
        {
          for (int cChildren = 0; cChildren < 3; cChildren++)
          {
            int treeSize = 1;
            DatePattern child = (DatePattern)Mutate(parent, ref treeSize); // NB: procreation may also be done through crossover.
            population.Add((DatePattern)child);

            var childFitness = Fitness(input, child);
            if (childFitness > strongestFitness)
            {
              bestTry = cGenerations;
              strongestFitness = childFitness;
              strongest = child;
            }

          }
        }
      }
      Trace.WriteLine("Found best match with fitness " + Fitness(input, strongest) + " after " + bestTry + " generations: " + strongest);

    }

    private static object Mutate(object original, ref int treeSize)
    {
      treeSize = 0;


      object replacement = Construct(original.GetType());
      foreach (var field in original.GetType().GetFields())
      {
        object newFieldValue = field.GetValue(original);
        int subtreeSize;
        if (field.FieldType.IsEnum)
        {
          subtreeSize = 1;
          if (random.NextDouble() <= MUTATION_RATE)
            newFieldValue = ConstructRandomEnumValue(field.FieldType);
        }
        else if (field.FieldType == typeof(int))
        {
          subtreeSize = 1;
          if (random.NextDouble() <= MUTATION_RATE)
            newFieldValue = (random.Next(2) == 0
            ? Math.Min(int.MaxValue - 1, (int)newFieldValue) + 1
            : Math.Max(int.MinValue + 1, (int)newFieldValue) - 1);
        }
        else
        {
          subtreeSize = 0;
          newFieldValue = Mutate(field.GetValue(original), ref subtreeSize); // mutate pre-maturely to find out subtreeSize

          if (random.NextDouble() <= MUTATION_RATE / subtreeSize) // makes high-level nodes mutate less.
          {
            subtreeSize = 0; // init so we can track the size of the subtree soon to be made.
            newFieldValue = Generate(field.FieldType, ref subtreeSize);
          }
        }
        field.SetValue(replacement, newFieldValue);
        treeSize += subtreeSize;
      }
      return replacement;

    }

    private static object ConstructRandomEnumValue(Type type)
    {
      var vals = type.GetEnumValues();
      return vals.GetValue(random.Next(vals.Length));
    }

    private static object Construct(Type type)
    {
      return type.GetConstructor(new Type[] { }).Invoke(new object[] { });
    }

    private static object Generate(Type type, ref int treesize)
    {
      if (type.IsEnum)
      {
        return ConstructRandomEnumValue(type);
      }
      else if (typeof(int) == type)
      {
        return random.Next(10) + 2005;
      }
      else
      {
        if (type.IsAbstract)
        {
          // pick one of the concrete subtypes:
          var subtypes = GetConcreteSubtypes(type);
          type = subtypes[random.Next(subtypes.Length)];
        }
        object newobj = Construct(type);

        foreach (var field in type.GetFields())
        {
          treesize++;
          field.SetValue(newobj, Generate(field.FieldType, ref treesize));
        }
        return newobj;
      }
    }


    private static int Fitness(DateTime[] input, DatePattern individual)
    {
      var output = individual.Dates().ToArray();
      var avgDateDiff = Math.Abs((output.Average(d => d.Ticks / (24.0 * 60 * 60 * 10000000)) - input.Average(d => d.Ticks / (24.0 * 60 * 60 * 10000000))));
      return
        -individual.ToString().Length // succinct patterns are preferred.
        - input.Except(output).Count() * 300 // Forgetting some of the dates is bad.
        - output.Except(input).Count() * 3000 // Spurious dates cause even more confusion to the user.
      - (int)(avgDateDiff) * 30000; // The difference in average date is the most important guide.
    }

    private static Type[] GetConcreteSubtypes(Type supertype)
    {
      if (subtypes.ContainsKey(supertype))
      {
        return subtypes[supertype];
      }
      else
      {

        var types = AppDomain.CurrentDomain.GetAssemblies().ToList()
            .SelectMany(s => s.GetTypes())
            .Where(p => supertype.IsAssignableFrom(p) && !p.IsAbstract).ToArray();
        subtypes.Add(supertype, types);
        return types;
      }
    }
  }

希望这能让你走上正轨。请务必在某处分享您的实际解决方案;我认为它在很多场景下都会非常有用。

Grammatical Evolution (GE) is suitable for this kind of problem, because you are searching for an answer that adheres to a certain language. Grammatical Evolution is also used for program generation, composing music, designing, etcetera.

I'd approach the task like this:

Structure the problem space with a grammar.

Construct a Context-free Grammar that can represent all desired recurrence patterns. Consider production rules like these:

datepattern -> datepattern 'and' datepattern
datepattern -> frequency bounds
frequency -> 'every' ordinal weekday 'of the month'
frequency -> 'every' weekday
ordinal -> ordinal 'and' ordinal
ordinal -> 'first' | 'second' | 'third'
bounds -> 'in the year' year

An example of a pattern generated by these rules is: 'every second and third wednesday of the month in the year 2010 and every tuesday in the year 2011'

One way to implement such a grammar would be through a class hierarchy that you will later operate on through reflection, as I've done in the example below.

Map this language to a set of dates

You should create a function that takes a clause from your language and recursively returns the set of all dates covered by it. This allows you to compare your answers to the input.

Guided by the grammar, search for potential solutions

You could use a Genetic algorithm or Simulated Annealing to match the dates to the grammar, try your luck with Dynamic Programming or start simple with a brute force enumeration of all possible clauses.

Should you go with a Genetic Algorithm, your mutation concept should consist of substituting an expression for another one based on the application of one of your production rules.

Have a look at the following GE-related sites for code and information:
http://www.bangor.ac.uk/~eep201/jge/
http://nohejl.name/age/
http://www.geneticprogramming.us/Home_Page.html

Evaluate each solution

The fitness function could take into account the textual length of the solution, the number of dates generated more than once, the number of dates missed, as well as the number of wrong dates generated.

Example code

By request, and because it's such an interesting challenge, I've written a rudimentary implementation of the algorithm to get you started. Although it works it is by no means finished, the design should definitively get some more thought, and once you have gleaned the fundamental take-aways from this example I recommend you consider using one the libraries I've mentioned above.

  /// <summary>
  ///  This is a very basic example implementation of a grammatical evolution algorithm for formulating a recurrence pattern in a set of dates.
  ///  It needs significant extensions and optimizations to be useful in a production setting.
  /// </summary>
  static class Program
  {

    #region "Class hierarchy that codifies the grammar"

    class DatePattern
    {

      public Frequency frequency;
      public Bounds bounds;

      public override string ToString() { return "" + frequency + " " + bounds; }

      public IEnumerable<DateTime> Dates()
      {
        return frequency == null ? new DateTime[] { } : frequency.FilterDates(bounds.GetDates());
      }

    }

    abstract class Bounds
    {
      public abstract IEnumerable<DateTime> GetDates();
    }

    class YearBounds : Bounds
    {

      /* in the year .. */
      public int year;

      public override string ToString() { return "in the year " + year; }

      public override IEnumerable<DateTime> GetDates()
      {
        var firstDayOfYear = new DateTime(year, 1, 1);
        return Enumerable.Range(0, new DateTime(year, 12, 31).DayOfYear)
          .Select(dayOfYear => firstDayOfYear.AddDays(dayOfYear));
      }
    }

    abstract class Frequency
    {
      public abstract IEnumerable<DateTime> FilterDates(IEnumerable<DateTime> Dates);
    }

    class WeeklyFrequency : Frequency
    {

      /* every .. */
      public DayOfWeek dayOfWeek;

      public override string ToString() { return "every " + dayOfWeek; }

      public override IEnumerable<DateTime> FilterDates(IEnumerable<DateTime> Dates)
      {
        return Dates.Where(date => (date.DayOfWeek == dayOfWeek));
      }

    }

    class MonthlyFrequency : Frequency
    {

      /* every .. */
      public Ordinal ordinal;
      public DayOfWeek dayOfWeek;
      /* .. of the month */

      public override string ToString() { return "every " + ordinal + " " + dayOfWeek + " of the month"; }

      public override IEnumerable<DateTime> FilterDates(IEnumerable<DateTime> Dates)
      {
        return Dates.Where(date => (date.DayOfWeek == dayOfWeek) && (int)ordinal == (date.Day - 1) / 7);
      }

    }

    enum Ordinal { First, Second, Third, Fourth, Fifth }

    #endregion

    static Random random = new Random();
    const double MUTATION_RATE = 0.3;
    static Dictionary<Type, Type[]> subtypes = new Dictionary<Type, Type[]>();

    static void Main()
    {

      // The input signifies the recurrence 'every first thursday of the month in 2010':
      var input = new DateTime[] {new DateTime(2010,12,2), new DateTime(2010,11,4),new DateTime(2010,10,7),new DateTime(2010,9,2),
                    new DateTime(2010,8,5),new DateTime(2010,7,1),new DateTime(2010,6,3),new DateTime(2010,5,6),
                    new DateTime(2010,4,1),new DateTime(2010,3,4),new DateTime(2010,2,4),new DateTime(2010,1,7) };


      for (int cTests = 0; cTests < 20; cTests++)
      {
        // Initialize with a random population
        int treesize = 0;
        var population = new DatePattern[] { (DatePattern)Generate(typeof(DatePattern), ref treesize), (DatePattern)Generate(typeof(DatePattern), ref treesize), (DatePattern)Generate(typeof(DatePattern), ref treesize) };
        Run(input, new List<DatePattern>(population));
      }
    }

    private static void Run(DateTime[] input, List<DatePattern> population)
    {
      var strongest = population[0];
      int strongestFitness = int.MinValue;
      int bestTry = int.MaxValue;
      for (int cGenerations = 0; cGenerations < 300 && strongestFitness < -100; cGenerations++)
      {
        // Select the best individuals to survive:
        var survivers = population
            .Select(individual => new { Fitness = Fitness(input, individual), individual })
            .OrderByDescending(pair => pair.Fitness)
            .Take(5)
            .Select(pair => pair.individual)
            .ToArray();
        population.Clear();

        // The survivers are the foundation for the next generation:
        foreach (var parent in survivers)
        {
          for (int cChildren = 0; cChildren < 3; cChildren++)
          {
            int treeSize = 1;
            DatePattern child = (DatePattern)Mutate(parent, ref treeSize); // NB: procreation may also be done through crossover.
            population.Add((DatePattern)child);

            var childFitness = Fitness(input, child);
            if (childFitness > strongestFitness)
            {
              bestTry = cGenerations;
              strongestFitness = childFitness;
              strongest = child;
            }

          }
        }
      }
      Trace.WriteLine("Found best match with fitness " + Fitness(input, strongest) + " after " + bestTry + " generations: " + strongest);

    }

    private static object Mutate(object original, ref int treeSize)
    {
      treeSize = 0;


      object replacement = Construct(original.GetType());
      foreach (var field in original.GetType().GetFields())
      {
        object newFieldValue = field.GetValue(original);
        int subtreeSize;
        if (field.FieldType.IsEnum)
        {
          subtreeSize = 1;
          if (random.NextDouble() <= MUTATION_RATE)
            newFieldValue = ConstructRandomEnumValue(field.FieldType);
        }
        else if (field.FieldType == typeof(int))
        {
          subtreeSize = 1;
          if (random.NextDouble() <= MUTATION_RATE)
            newFieldValue = (random.Next(2) == 0
            ? Math.Min(int.MaxValue - 1, (int)newFieldValue) + 1
            : Math.Max(int.MinValue + 1, (int)newFieldValue) - 1);
        }
        else
        {
          subtreeSize = 0;
          newFieldValue = Mutate(field.GetValue(original), ref subtreeSize); // mutate pre-maturely to find out subtreeSize

          if (random.NextDouble() <= MUTATION_RATE / subtreeSize) // makes high-level nodes mutate less.
          {
            subtreeSize = 0; // init so we can track the size of the subtree soon to be made.
            newFieldValue = Generate(field.FieldType, ref subtreeSize);
          }
        }
        field.SetValue(replacement, newFieldValue);
        treeSize += subtreeSize;
      }
      return replacement;

    }

    private static object ConstructRandomEnumValue(Type type)
    {
      var vals = type.GetEnumValues();
      return vals.GetValue(random.Next(vals.Length));
    }

    private static object Construct(Type type)
    {
      return type.GetConstructor(new Type[] { }).Invoke(new object[] { });
    }

    private static object Generate(Type type, ref int treesize)
    {
      if (type.IsEnum)
      {
        return ConstructRandomEnumValue(type);
      }
      else if (typeof(int) == type)
      {
        return random.Next(10) + 2005;
      }
      else
      {
        if (type.IsAbstract)
        {
          // pick one of the concrete subtypes:
          var subtypes = GetConcreteSubtypes(type);
          type = subtypes[random.Next(subtypes.Length)];
        }
        object newobj = Construct(type);

        foreach (var field in type.GetFields())
        {
          treesize++;
          field.SetValue(newobj, Generate(field.FieldType, ref treesize));
        }
        return newobj;
      }
    }


    private static int Fitness(DateTime[] input, DatePattern individual)
    {
      var output = individual.Dates().ToArray();
      var avgDateDiff = Math.Abs((output.Average(d => d.Ticks / (24.0 * 60 * 60 * 10000000)) - input.Average(d => d.Ticks / (24.0 * 60 * 60 * 10000000))));
      return
        -individual.ToString().Length // succinct patterns are preferred.
        - input.Except(output).Count() * 300 // Forgetting some of the dates is bad.
        - output.Except(input).Count() * 3000 // Spurious dates cause even more confusion to the user.
      - (int)(avgDateDiff) * 30000; // The difference in average date is the most important guide.
    }

    private static Type[] GetConcreteSubtypes(Type supertype)
    {
      if (subtypes.ContainsKey(supertype))
      {
        return subtypes[supertype];
      }
      else
      {

        var types = AppDomain.CurrentDomain.GetAssemblies().ToList()
            .SelectMany(s => s.GetTypes())
            .Where(p => supertype.IsAssignableFrom(p) && !p.IsAbstract).ToArray();
        subtypes.Add(supertype, types);
        return types;
      }
    }
  }

Hope this gets you on track. Be sure to share your actual solution somewhere; I think it will be quite useful in lots of scenarios.

夜访吸血鬼 2024-10-08 18:37:10

如果您的目的是生成人类可读的模式描述(如“十一月的每个星期一”),那么您可能希望从枚举可能的描述开始。描述可以分为频率和界限,例如,

频率:

  • 每天...
  • 每隔/第三/第四天...
  • 工作日/周末...
  • 每周一...
  • 隔周星期一...
  • 第一/第二/上周一……
  • 范围

  • ……1月
  • ……3月25日至10月25日
  • ……

每种不会那么多,可以一一查。

If your purpose is to generate human-readable descriptions of the pattern, as in your "Every Monday in November", then you probably want to start by enumerating the possible descriptions. Descriptions can be broken down into frequency and bounds, for example,

Frequency:

  • Every day ...
  • Every other/third/fourth day ...
  • Weekdays/weekends ...
  • Every Monday ...
  • Alternate Mondays ...
  • The first/second/last Monday ...
  • ...

Bounds:

  • ... in January
  • ... between 25 March and 25 October
  • ...

There won't be all that many of each, and you can check for them one by one.

记忆消瘦 2024-10-08 18:37:10

我会做什么:

  1. 创建数据样本
  2. 使用聚类算法
  3. 使用算法生成样本
  4. 创建适应度函数来衡量它与完整数据集的关联程度。聚类算法将提出 0 或 1 个建议,您可以根据它与整个集合的适合程度来衡量它。
  5. 将出现的事件与已找到的集合元素化/合并,然后重新运行该算法。

考虑到这一点,您可能想要使用模拟退火或遗传算法。此外,如果您有描述,您可能需要比较描述以生成示例。

What I would do:

  1. Create samples of the data
  2. Use a clustering algorithm
  3. Generate samples using the algorithm
  4. Creating a fitness function to measure how well it correlates to the full data set. The clustering algorithm will come up with either 0 or 1 suggestions and you can meassure it against how well it fits in with the full set.
  5. Elementate/merge the occurrence with the already found sets and rerun this algorithm.

Looking at that you may want to use either Simulated Annealing, or an Genetic Algorithm. Also, if you have the descriptions, you may want to compare the descriptions to generate a sample.

北恋 2024-10-08 18:37:10

您可以访问系统日期或系统日期和时间,并根据调用或函数结果返回的日期和星期几在内存中构造粗略的日历点。然后使用相关月份中的天数对其进行求和,并添加输入中日期变量的天数和/或访问从星期日或星期一开始的相关周的日历点,并计算或将索引向前增加到正确的值天。使用固定字符构造文本字符串,并根据需要插入相关变量,例如星期几的全名。可能需要多次遍历才能获得要显示或计数其出现次数的所有事件。

You could access the system date or system dateandtime and construct crude calendar points in memory based on the date and the day of the week as returned by the call or function result. Then use the number of days in relevant months to sum them and add on the number of days of the day variable in the input and/or access the calendar point for the relevant week starting sunday or monday and calculate or increment index forward to the correct day. Construct text string using fixed characters and insert the relevant variable such as the full name of the day of the week as required. There may be multiple traversals needed to obtain all the events of which the occurrences are to be displayed or counted.

梦毁影碎の 2024-10-08 18:37:10

首先,找一个序列,如果存在的话:

step = {day,month,year}
period=0
for d = 1 to dates.count-1
    interval(d,step)=datedifference(s,date(d),date(d+1))
next
' Find frequency with largest interval
for s = year downto day
    found=true
    for d = 1 to dates.count-2
        if interval(d,s)=interval(d+1,s) then
            found=false
            exit for
        end if
    next
    if found then
        period=s
        frequency=interval(1,s)
        exit for
    end if
next

if period>0
    Select case period
      case day
        if frequency mod 7 = 0 then
          say "every" dayname(date(1))
        else
          say "every" frequency "days"
        end if
      case month
        say "every" frequency "months on day" daynumber(date(1))
      case years
        say "every" frequency "years on" daynumber(date(1)) monthname(date(1))
    end select
end if

最后,处理“11月”、“2007年到2010年”等,应该是显而易见的。

华泰

First, find a sequence, if it exists:

step = {day,month,year}
period=0
for d = 1 to dates.count-1
    interval(d,step)=datedifference(s,date(d),date(d+1))
next
' Find frequency with largest interval
for s = year downto day
    found=true
    for d = 1 to dates.count-2
        if interval(d,s)=interval(d+1,s) then
            found=false
            exit for
        end if
    next
    if found then
        period=s
        frequency=interval(1,s)
        exit for
    end if
next

if period>0
    Select case period
      case day
        if frequency mod 7 = 0 then
          say "every" dayname(date(1))
        else
          say "every" frequency "days"
        end if
      case month
        say "every" frequency "months on day" daynumber(date(1))
      case years
        say "every" frequency "years on" daynumber(date(1)) monthname(date(1))
    end select
end if

Finally, deal with "in November", "from 2007 to 2010" etc., should be obvious.

HTH

多情出卖 2024-10-08 18:37:10

我喜欢@arjen 的回答,但我认为不需要复杂的算法。这就是这么简单。如果有模式,就有模式……因此一个简单的算法就可以了。首先,我们需要考虑我们正在寻找的模式类型:每日、每周、每月和每年。

如何识别?

每日:每天都有记录
每周:每周有一条记录
每月:每月有一条记录
每年:每年都有记录

难吗?不会。只需数一下有多少次重复,然后进行分类即可。

这是我的实现

RecurrencePatternAnalyser.java

public class RecurrencePatternAnalyser {

    // Local copy of calendars by add() method 
    private ArrayList<Calendar> mCalendars = new ArrayList<Calendar>();

    // Used to count the uniqueness of each year/month/day 
    private HashMap<Integer, Integer> year_count = new HashMap<Integer,Integer>();
    private HashMap<Integer, Integer> month_count = new HashMap<Integer,Integer>();
    private HashMap<Integer, Integer> day_count = new HashMap<Integer,Integer>();
    private HashMap<Integer, Integer> busday_count = new HashMap<Integer,Integer>();

    // Used for counting payments before due date on weekends
    private int day_goodpayer_ocurrences = 0;
    private int day_goodPayer = 0;

    // Add a new calendar to the analysis
    public void add(Calendar date)
    {
        mCalendars.add(date);
        addYear( date.get(Calendar.YEAR) );
        addMonth( date.get(Calendar.MONTH) );
        addDay( date.get(Calendar.DAY_OF_MONTH) );
        addWeekendDays( date );
    }

    public void printCounts()
    {
        System.out.println("Year: " +  getYearCount() + 
                " month: " +  getMonthCount() + " day: " +  getDayCount());
    }

    public RecurrencePattern getPattern()
    {
        int records = mCalendars.size();
        if (records==1)
            return null;

        RecurrencePattern rp = null;

        if (getYearCount()==records)
        {
            rp = new RecurrencePatternYearly();
            if (records>=3)
                rp.setConfidence(1);
            else if (records==2)
                rp.setConfidence(0.9f);
        }
        else if (getMonthCount()==records)
        {
            rp = new RecurrencePatternMonthly();
            if (records>=12)
                rp.setConfidence(1);
            else
                rp.setConfidence(1-(-0.0168f * records + 0.2f));
        } 
        else 
        {
            calcDaysRepetitionWithWeekends();
            if (day_goodpayer_ocurrences==records)
            {
                rp = new RecurrencePatternMonthly();
                rp.setPattern(RecurrencePattern.PatternType.MONTHLY_GOOD_PAYER);
                if (records>=12)
                    rp.setConfidence(0.95f);
                else
                    rp.setConfidence(1-(-0.0168f * records + 0.25f));
            }
        }

        return rp;
    }

    // Increment one more year/month/day on each count variable
    private void addYear(int key_year)  { incrementHash(year_count, key_year); }
    private void addMonth(int key_month)    { incrementHash(month_count, key_month); }
    private void addDay(int key_day)    { incrementHash(day_count, key_day); }

    // Retrieve number of unique entries for the records
    private int getYearCount() { return year_count.size(); }
    private int getMonthCount() { return month_count.size(); }
    private int getDayCount() { return day_count.size(); }

    // Generic function to increment the hash by 1  
    private void incrementHash(HashMap<Integer, Integer> var, Integer key)
    {
        Integer oldCount = var.get(key);
        Integer newCount = 0;
        if ( oldCount != null ) {
            newCount = oldCount;
        }
        newCount++;
        var.put(key, newCount);
    }

    // As Bank are closed during weekends, some dates might be anticipated
    // to Fridays. These will be false positives for the recurrence pattern.
    // This function adds Saturdays and Sundays to the count when a date is 
    // Friday.
    private void addWeekendDays(Calendar c)
    {
        int key_day = c.get(Calendar.DAY_OF_MONTH);
        incrementHash(busday_count, key_day);
        if (c.get(Calendar.DAY_OF_WEEK) == Calendar.FRIDAY)
        {
            // Adds Saturday
            c.add(Calendar.DATE, 1);
            key_day = c.get(Calendar.DAY_OF_MONTH);
            incrementHash(busday_count, key_day);
            // Adds Sunday
            c.add(Calendar.DATE, 1);
            key_day = c.get(Calendar.DAY_OF_MONTH);
            incrementHash(busday_count, key_day);
        }
    }

    private void calcDaysRepetitionWithWeekends()
    {               
        Iterator<Entry<Integer, Integer>> it =
                busday_count.entrySet().iterator();
        while (it.hasNext()) {
            @SuppressWarnings("rawtypes")
            Map.Entry pair = (Map.Entry)it.next();
            if ((int)pair.getValue() > day_goodpayer_ocurrences)
            {
                day_goodpayer_ocurrences = (int) pair.getValue();
                day_goodPayer = (int) pair.getKey();
            }
            //it.remove(); // avoids a ConcurrentModificationException
        }
    }
}

RecurrencePattern.java

public abstract class RecurrencePattern {

    public enum PatternType {
        YEARLY, MONTHLY, WEEKLY, DAILY, MONTHLY_GOOD_PAYER 
    }   
    public enum OrdinalType {
        FIRST, SECOND, THIRD, FOURTH, FIFTH 
    }

    protected PatternType pattern;
    private float confidence;
    private int frequency;

    public PatternType getPattern() {
        return pattern;
    }

    public void setPattern(PatternType pattern) {
        this.pattern = pattern;
    }

    public float getConfidence() {
        return confidence;
    }
    public void setConfidence(float confidence) {
        this.confidence = confidence;
    }
    public int getFrequency() {
        return frequency;
    }
    public void setFrequency(int frequency) {
        this.frequency = frequency;
    }   
}

RecurrencePatternMonthly.java

public class RecurrencePatternMonthly extends RecurrencePattern {
    private boolean isDayFixed;
    private boolean isDayOrdinal;
    private OrdinalType ordinaltype;

    public RecurrencePatternMonthly()
    {
        this.pattern = PatternType.MONTHLY;
    }
}

RecurrencePatternYearly.java

public class RecurrencePatternYearly extends RecurrencePattern {
    private boolean isDayFixed;
    private boolean isMonthFixed;
    private boolean isDayOrdinal;
    private OrdinalType ordinaltype;

    public RecurrencePatternYearly()
    {
        this.pattern = PatternType.YEARLY;
    }
}   

Main.java

public class Algofin {

    static Connection c = null;

    public static void main(String[] args) {
        //openConnection();
        //readSqlFile();

        RecurrencePatternAnalyser r = new RecurrencePatternAnalyser();

        //System.out.println(new GregorianCalendar(2015,1,30).get(Calendar.MONTH));
        r.add(new GregorianCalendar(2015,0,1));
        r.add(new GregorianCalendar(2015,0,30));
        r.add(new GregorianCalendar(2015,1,27));
        r.add(new GregorianCalendar(2015,3,1));
        r.add(new GregorianCalendar(2015,4,1));

        r.printCounts();

        RecurrencePattern rp;
        rp=r.getPattern();
        System.out.println("Pattern: " + rp.getPattern() + " confidence: " + rp.getConfidence());
    }
}

I like @arjen answer but I don't think there is any need for complex algorithm. This is so so simple. If there is a pattern, there is a pattern... therefore a simple algorithm would work. First we need to think of the types of patterns we are looking for: daily, weekly, monthly and yearly.

How to recognize?

Daily: there is a record every day
Weekly: there is a record every week
Monthly: there is a record every month
Yearly: there is a record every year

Difficult? No. Just count how many repetitions you have and then classify.

Here is my implementation

RecurrencePatternAnalyser.java

public class RecurrencePatternAnalyser {

    // Local copy of calendars by add() method 
    private ArrayList<Calendar> mCalendars = new ArrayList<Calendar>();

    // Used to count the uniqueness of each year/month/day 
    private HashMap<Integer, Integer> year_count = new HashMap<Integer,Integer>();
    private HashMap<Integer, Integer> month_count = new HashMap<Integer,Integer>();
    private HashMap<Integer, Integer> day_count = new HashMap<Integer,Integer>();
    private HashMap<Integer, Integer> busday_count = new HashMap<Integer,Integer>();

    // Used for counting payments before due date on weekends
    private int day_goodpayer_ocurrences = 0;
    private int day_goodPayer = 0;

    // Add a new calendar to the analysis
    public void add(Calendar date)
    {
        mCalendars.add(date);
        addYear( date.get(Calendar.YEAR) );
        addMonth( date.get(Calendar.MONTH) );
        addDay( date.get(Calendar.DAY_OF_MONTH) );
        addWeekendDays( date );
    }

    public void printCounts()
    {
        System.out.println("Year: " +  getYearCount() + 
                " month: " +  getMonthCount() + " day: " +  getDayCount());
    }

    public RecurrencePattern getPattern()
    {
        int records = mCalendars.size();
        if (records==1)
            return null;

        RecurrencePattern rp = null;

        if (getYearCount()==records)
        {
            rp = new RecurrencePatternYearly();
            if (records>=3)
                rp.setConfidence(1);
            else if (records==2)
                rp.setConfidence(0.9f);
        }
        else if (getMonthCount()==records)
        {
            rp = new RecurrencePatternMonthly();
            if (records>=12)
                rp.setConfidence(1);
            else
                rp.setConfidence(1-(-0.0168f * records + 0.2f));
        } 
        else 
        {
            calcDaysRepetitionWithWeekends();
            if (day_goodpayer_ocurrences==records)
            {
                rp = new RecurrencePatternMonthly();
                rp.setPattern(RecurrencePattern.PatternType.MONTHLY_GOOD_PAYER);
                if (records>=12)
                    rp.setConfidence(0.95f);
                else
                    rp.setConfidence(1-(-0.0168f * records + 0.25f));
            }
        }

        return rp;
    }

    // Increment one more year/month/day on each count variable
    private void addYear(int key_year)  { incrementHash(year_count, key_year); }
    private void addMonth(int key_month)    { incrementHash(month_count, key_month); }
    private void addDay(int key_day)    { incrementHash(day_count, key_day); }

    // Retrieve number of unique entries for the records
    private int getYearCount() { return year_count.size(); }
    private int getMonthCount() { return month_count.size(); }
    private int getDayCount() { return day_count.size(); }

    // Generic function to increment the hash by 1  
    private void incrementHash(HashMap<Integer, Integer> var, Integer key)
    {
        Integer oldCount = var.get(key);
        Integer newCount = 0;
        if ( oldCount != null ) {
            newCount = oldCount;
        }
        newCount++;
        var.put(key, newCount);
    }

    // As Bank are closed during weekends, some dates might be anticipated
    // to Fridays. These will be false positives for the recurrence pattern.
    // This function adds Saturdays and Sundays to the count when a date is 
    // Friday.
    private void addWeekendDays(Calendar c)
    {
        int key_day = c.get(Calendar.DAY_OF_MONTH);
        incrementHash(busday_count, key_day);
        if (c.get(Calendar.DAY_OF_WEEK) == Calendar.FRIDAY)
        {
            // Adds Saturday
            c.add(Calendar.DATE, 1);
            key_day = c.get(Calendar.DAY_OF_MONTH);
            incrementHash(busday_count, key_day);
            // Adds Sunday
            c.add(Calendar.DATE, 1);
            key_day = c.get(Calendar.DAY_OF_MONTH);
            incrementHash(busday_count, key_day);
        }
    }

    private void calcDaysRepetitionWithWeekends()
    {               
        Iterator<Entry<Integer, Integer>> it =
                busday_count.entrySet().iterator();
        while (it.hasNext()) {
            @SuppressWarnings("rawtypes")
            Map.Entry pair = (Map.Entry)it.next();
            if ((int)pair.getValue() > day_goodpayer_ocurrences)
            {
                day_goodpayer_ocurrences = (int) pair.getValue();
                day_goodPayer = (int) pair.getKey();
            }
            //it.remove(); // avoids a ConcurrentModificationException
        }
    }
}

RecurrencePattern.java

public abstract class RecurrencePattern {

    public enum PatternType {
        YEARLY, MONTHLY, WEEKLY, DAILY, MONTHLY_GOOD_PAYER 
    }   
    public enum OrdinalType {
        FIRST, SECOND, THIRD, FOURTH, FIFTH 
    }

    protected PatternType pattern;
    private float confidence;
    private int frequency;

    public PatternType getPattern() {
        return pattern;
    }

    public void setPattern(PatternType pattern) {
        this.pattern = pattern;
    }

    public float getConfidence() {
        return confidence;
    }
    public void setConfidence(float confidence) {
        this.confidence = confidence;
    }
    public int getFrequency() {
        return frequency;
    }
    public void setFrequency(int frequency) {
        this.frequency = frequency;
    }   
}

RecurrencePatternMonthly.java

public class RecurrencePatternMonthly extends RecurrencePattern {
    private boolean isDayFixed;
    private boolean isDayOrdinal;
    private OrdinalType ordinaltype;

    public RecurrencePatternMonthly()
    {
        this.pattern = PatternType.MONTHLY;
    }
}

RecurrencePatternYearly.java

public class RecurrencePatternYearly extends RecurrencePattern {
    private boolean isDayFixed;
    private boolean isMonthFixed;
    private boolean isDayOrdinal;
    private OrdinalType ordinaltype;

    public RecurrencePatternYearly()
    {
        this.pattern = PatternType.YEARLY;
    }
}   

Main.java

public class Algofin {

    static Connection c = null;

    public static void main(String[] args) {
        //openConnection();
        //readSqlFile();

        RecurrencePatternAnalyser r = new RecurrencePatternAnalyser();

        //System.out.println(new GregorianCalendar(2015,1,30).get(Calendar.MONTH));
        r.add(new GregorianCalendar(2015,0,1));
        r.add(new GregorianCalendar(2015,0,30));
        r.add(new GregorianCalendar(2015,1,27));
        r.add(new GregorianCalendar(2015,3,1));
        r.add(new GregorianCalendar(2015,4,1));

        r.printCounts();

        RecurrencePattern rp;
        rp=r.getPattern();
        System.out.println("Pattern: " + rp.getPattern() + " confidence: " + rp.getConfidence());
    }
}
梦回梦里 2024-10-08 18:37:10

我认为你必须建造它,而且我认为这将是一个细节问题的项目。首先获得更彻底的要求。您想识别哪些日期模式?列出您希望算法成功识别的示例列表。编写你的算法来满足你的例子。将您的示例放入测试套件中,这样当您稍后收到不同的需求时,您可以确保没有破坏旧的需求。

我预测你会写 200 个 if-then-else 语句。

好吧,我确实有一个想法。熟悉集合覆盖交集等概念。列出您要搜索的简短模式,例如“十月的每一天”、“十一月的每一天”和“十二月的每一天”。如果这些短模式包含在日期集中,则定义一个 Union 函数,该函数可以以智能方式组合较短的模式。例如,假设您匹配了我上面提到的三种模式。如果将它们联合在一起,您会得到“十月到十二月的每一天”。您的目标可能是返回最简洁的联合覆盖您的日期集或类似内容。

I think you'll have to build it, and I think it will be a devil in the details kind of project. Start by getting much more thorough requirements. Which date patterns do you want to recognize? Come up with a list of examples that you want your algorithm to successfully identify. Write your algorithm to meet your examples. Put your examples in a test suite so when you get different requirements later you can make sure you didn't break the old ones.

I predict you will write 200 if-then-else statements.

OK, I do have one idea. Get familiar with the concepts of sets, unions, coverage, intersection and so on. Have a list of short patterns that you search for, say, "Every day in October", "Every day in November", and "Every day in December." If these short patterns are contained within the set of dates, then define a union function that can combine shorter patterns in intelligent ways. For example, let's say you matched the three patterns I mention above. If you Union them together you get, "Every day in October through December." You could aim to return the most succinct set of unions that cover your set of dates or something like that.

悟红尘 2024-10-08 18:37:10

看看您最喜欢的日历程序。查看它可以生成哪些事件重复模式。对它们进行逆向工程。

Have a look at your favourite calendar program. See what patterns of event recurrence it can generate. Reverse engineer them.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文