I saw examples where I can sort a list in dart using one property in flutter(dart).
But how can I do the functionality which an SQL query does like for example:
order by points desc, time asc
You can sort the list then sort it again..
Here is a sample I made from dartpad.dev
void main() {
Object x = Object(name: 'Helloabc', i: 1);
Object y = Object(name: 'Othello', i: 3);
Object z = Object(name: 'Avatar', i: 2);
List<Object> _objects = [
x, y, z
];
_objects.sort((a, b) => a.name.length.compareTo(b.name.length));
/// second sorting
// _objects.sort((a, b) => a.i.compareTo(b.i));
for (Object a in _objects) {
print(a.name);
}
}
class Object {
final String name;
final int i;
Object({this.name, this.i});
}
I was able to find an answer for this. Thanks to #pskink and the url
https://www.woolha.com/tutorials/dart-sorting-list-with-comparator-and-comparable.
I implemented the Comparable to sort by the two properties.
class Sample implements Comparable<Sample> {
final int points;
final int timeInSeconds;
Sample(
{
this.points,
this.timeInSeconds});
#override
int compareTo(Sample other) {
int pointDifference = points- other.points;
return pointDifference != 0
? pointDifference
: other.timeInSeconds.compareTo(this.timeInSeconds);
}
}
sampleList.sort();
I need to check how long does a function need to run. I have the following functions which address the same task:
mixAnimalsA
fun mixAnimalsA(a1: Animal, a2: Animal) =
when (setOf(a1, a2)) {
setOf(Animal.OWL, Animal.Leopard) -> Beast.OWLPARD
setOf(Animal.ELEPHANT, Animal.BUTTERFLY) -> Beast.BUTTERPHANT
else -> throw Exception("Not possible combination")
}
mixAnimalsB
fun mixAnimalsB(a1: Animal, a2: Animal) =
when (setOf(a1, a2)) {
(c1 == Animal.OWL && c2 == Animal.Leopard) ||
(c2 == Animal.OWL && c1 == Animal.Leopard) -> Beast.OWLPARD
(c1 == Animal.ELEPHANT && c2 == Animal.BUTTERFLY) ||
(c2 == Animal.ELEPHANT && c1 == Animal.BUTTERFLY)-> Beast.BUTTERPHANT
else -> throw Exception("Not possible combination")
}
Animal and Beast are enumerations. How can I measure how long each function takes to run?
If you're looking for an in-code solution, you can use measureTimeMillis and measureNanoTime, like this:
val time = measureTimeMillis {
// call your function here
}
They return the measured time in milliseconds and nanoseconds, respectively.
Measure execution time and also keep the result
Standard Library
The standard library function measureTimedValue may be used to measure execution time and at the same time capture the result. This tuple of values is being exposed as a TimedValue(value: T, duration: Duration):
#ExperimentalTime
fun main() {
val (result: String, duration: Duration) = measureTimedValue {
operation()
}
print("Got $result after ${duration.inMilliseconds} ms.")
}
Note that this API is experimental and requires explicit opt-in.
Obsolete custom implementation
(This used to be my answer before the standard lib was extended)
If you want to measure the execution time and also access the measured function's return value afterward, here's a custom solution:
inline fun <R> executeAndMeasureTimeMillis(block: () -> R): Pair<R, Long> {
val start = System.currentTimeMillis()
val result = block()
return result to (System.currentTimeMillis() - start)
}
You can call it like this:
val (response, duration) = executeAndMeasureTimeMillis {
restTemplate.getForObject<AnyObject>(uri)
}
If it's enough to get the time as output on the console:
fun <T> timeIt(message: String = "", block: () -> T): T {
val start = System.currentTimeMillis()
val r = block()
val end = System.currentTimeMillis()
println("$message: ${end - start} ms")
return r
}
Usage:
val result = timeIt("note about the code") {
// do something...
}
Output (example):
note about the code: 1ms
For the benchmark of some code block and getting the result a same time, i do some refactor of the standard method in TimingKt class
to give us output generic result and at the same time display a given log.
Here is my example :
/**
* Executes the given block and show the elapsed time in milliseconds in a given message.
*
* #param block which will be bench marked
* #param logMessage log message to be displayed
*
* #return a generic result
*
*/
private fun <T> measureTime(block: () -> T, logMessage: String): T {
val start = System.currentTimeMillis()
val t = block()
val consumedTime = System.currentTimeMillis() - start
Log.d(TAG, "Calculation of $logMessage time :$consumedTime milliseconds")
return t
}
And how it will be used :
return measureTime({
// given block with return result
}, "message to be displayed typically the name of method which will be calculated")
This is my simple time test code.
class TimeCounter(val name: String) {
var totalTime: Long = 0
private set
var count: Int = 0
private set
var minTime: Long = Long.MAX_VALUE
private set
var maxTime: Long = Long.MIN_VALUE
private set
fun addTime(time: Long) {
this.count++
this.totalTime += time
if (this.minTime > time) {
this.minTime = time
}
if (this.maxTime < time) {
this.maxTime = time
}
}
val averageTime: Double
get() = this.totalTime / this.count.toDouble()
fun printTime() {
println("(time about : '$name'), totalTime : $totalTime, count : $count, " +
"average : $averageTime, minTime : $minTime, maxTime : $maxTime")
}
fun <T> runWithTimeCount(run: () -> T): T {
val startTime = System.currentTimeMillis()
return run().also {
this.addTime(System.currentTimeMillis() - startTime)
}
}
}
you can use 'TimeCounter' like this, (example)
var sum = 0
val testTimeCounter = TimeCounter("logic1")
for(i in 0 until 100){
sum += testTimeCounter.runWithTimeCount {
logic1(i) // your logic
}
}
println(sum)
testTimeCounter.printTime() // totalTime, average, minTime, maxTime
Execute function, measure its performance and logs performance - in same call
this solution will help folks who want to measure and log performance, execute function at same time, also is a cleaner approach when there is multiple performance measurement involved of different functions
Create functions as such:
//the inline performance measurement method
private inline fun <T> measurePerformanceInMS(
logger: (Long) -> Unit,
function: () -> T)
: T {
val startTime = System.currentTimeMillis()
val result: T = function.invoke()
val endTime = System.currentTimeMillis()
logger.invoke( endTime - startTime)
return result
}
//the logger function
fun logPerf(time: Long){
Log.d("TAG","PERFORMANCE IN MS: $time ms ")
}
//the function whose performance needs to be checked
fun longRunningFunction() : Int{
var x = 0
for (i in 1..20000) x++
return x
}
This way you can keep logging, performance computation and function execution under a single function call and no code replication needed.
If you require nano second measurement then use System.nanoTime()
USAGE :
val endResult = measurePerformanceInMS({time -> logPerf(time)}){
longRunningFunction()
}
NOTE : here the 'endResult' will carry the result of function whose performance was being measured.
I am working on an algorithm and it seems to be working fine, apart from one thing.
Let me first show you the code and then I will explain what the code does and what the problem is.
public Triple<List<ROUTE>, Integer, List<Customer>> LocalSearch()
{
int noImprLS = 0;
boolean initialization = false;
List<ROUTE> bestRoutes = startRoutes;
int bestProfit = profit;
List<Customer> bestU = u;
List<ROUTE> tempBestRoutes = startRoutes;
int tempBestProfit = profit;
List<Customer> tempBestU = u;
int tempBestDistance = totalDistance(tempBestRoutes);
ELIMINATOR e = new ELIMINATOR(bestU, bestRoutes, bestProfit, initialization, name, rnd);
while (noImprLS <= noImprUB)
{
System.out.print(noImprLS);
boolean improvement = false;
long starttime = System.nanoTime();
double timeE = 0;
for (int i = 1; i <= N; i++)
{
long starttimeE = System.nanoTime();
e = new ELIMINATOR(bestU, bestRoutes, bestProfit, initialization, name, rnd);
timeE = timeE + (System.nanoTime()-starttimeE)/1000000000.0;
POSTPROCEDURE pp = new POSTPROCEDURE(e.getRoutes(), profitRoutes(e.getRoutes()), e.getU(), name);
for (int p = 0; p < pp.getBestSolution().size(); p++)
{
ROUTE r = pp.getBestSolution().get(p);
addToPOOL(r);
}
int tempprofit = pp.getTP();
int tempdistance = pp.getTD();
if (tempprofit > tempBestProfit)
{
tempBestRoutes = pp.getBestSolution();
tempBestProfit = tempprofit;
tempBestU = pp.getU();
tempBestDistance = tempdistance;
}
else if (tempprofit == tempBestProfit)
{
if (tempdistance < tempBestDistance)
{
tempBestRoutes = pp.getBestSolution();
tempBestProfit = tempprofit;
tempBestU = pp.getU();
tempBestDistance = tempdistance;
}
}
}
if (tempBestProfit > bestProfit)
{
// Move to better neighbor
bestRoutes = tempBestRoutes;
bestProfit = tempBestProfit;
bestU = tempBestU;
noImprLS = 0;
improvement = true;
System.out.print(" total profit: " + bestProfit);
}
else if (tempBestProfit == bestProfit)
{
if (totalDistance(tempBestRoutes) < totalDistance(bestRoutes))
{
// Move to better neighbor
bestRoutes = tempBestRoutes;
bestProfit = tempBestProfit;
bestU = tempBestU;
noImprLS = 0;
improvement = true;
System.out.print(" total profit: " + bestProfit + " total distance: " + totalDistance(bestRoutes));
}
}
if (improvement == false)
{
noImprLS++;
}
long endtime = System.nanoTime();
double duration = (endtime - starttime)/1000000000.0;
System.out.print(" duration: " + duration + " timeE: " + timeE + "\n");
}
Explanation
I know that the code is quite lengthy, but it is all quite important. In this code, I am writing an algorithm for the Team Orienteering Problem with Time Windows (extensive case of the Vehicle Routing Problems). My aim is to find a good set of routes with maximum profit. In the example below, bestRoutes and tempBestRoutes consist of 4 different routes, profit (bestProfit/tempBestProfit) is equal to the total profit of these routes respectively, and (temp)bestU is a list of customers that are not included in my route yet.
The problem now is with ELIMINATOR. This method removes and adds some customers. The output of this class is used for PostProcedure that also changes some facts in the routes.
I hope it is kind of clear now what my code is doing. I am considering N neighbourhoods and I will choose the best one. If the best one is not better than my starting solution, I increase noImprLS with one. I keep on considering new nieghbours until my upperbound on the number of consecutive iterations without improvement is met.
Problem
The problem now is that if I have not found a better solution, and hence I keep on inserting the same routes and profit in ELIMINATOR, my computation time increases.
A few examples where duration indicates how long an iteration within the while loop takes, and timeE indicates what the total time of ELIMINATOR in the for loop is. It is clear that ELIMINATOR causees the duration to increase.
0 total profit: 800 duration: 0.486570471 timeE: 0.16644330999999998
0 total profit: 900 duration: 0.431213528 timeE: 0.11342619799999998
0 total profit: 950 duration: 0.444671005 timeE: 0.12090608200000001
0 total profit: 960 duration: 0.519406695 timeE: 0.16836757300000005
0 duration: 0.460473438 timeE: 0.137813155
1 duration: 0.572109775 timeE: 0.30774360900000003
2 duration: 0.698965292 timeE: 0.471859029
3 duration: 0.918376211 timeE: 0.686916669
4 duration: 1.165481175 timeE: 0.92621492
5 duration: 1.326080436 timeE: 1.0874366910000002
6 duration: 2.006102605 timeE: 1.674879135
7 duration: 2.787172112 timeE: 2.4276636639999993
8 duration: 2.042213493 timeE: 1.7967797849999998
9 duration: 2.652985618 timeE: 2.3503671230000003
10 duration: 2.422183993 timeE: 2.1859969810000006
The ELIMINATOR CODE:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
public class ELIMINATOR extends BASIS
{
private List<Customer> u;
private List<ROUTE> routes;
private int totalprofit;
private Random prob;
public ELIMINATOR(List<Customer> u, List<ROUTE> routes, int profit, boolean initialization, String name, Random rnd)
{
super(name);
this.u = u;
this.routes = routes;
this.totalprofit = profit;
this.prob = rnd;
if (initialization == true)
{
addCustomers();
for (ROUTE route : routes)
{
checkCorrectNess(route, "after adding procedure in eliminator");
}
}
else
{
removeCustomers();
for (ROUTE route : routes)
{
checkCorrectNess(route, "after removing procedure in eliminator");
}
addCustomers();
for (ROUTE route : routes)
{
checkCorrectNess(route, "after removing and adding procedure in eliminator");
}
}
}
public void removeCustomers()
{
double Ph = 0.1;
double Pl = 0.3;
double total_profit = totalprofit;
int num_customers = 0;
// Calculate the total profit and total number of customers in the routes
for(ROUTE route : routes)
{
num_customers = num_customers + (route.getLocations().size()-2);
}
// Calculate average profit
double average_profit = total_profit/num_customers;
// For each customer on each route, determine whether he/she will be removed
for(ROUTE r : routes)
{
List<RouteNode> route = r.getLocations();
int routesize = route.size();
int j = 1;
while (j < routesize-1)
{
boolean removed = false;
RouteNode node = route.get(j);
if (node.customer.getProfit() >= average_profit)
{
if (prob.nextDouble() < Ph)
{
removed = true;
RouteNode node_toberemoved = node;
int index_node = route.indexOf(node);
route.remove(index_node);
u.add(node.customer);
route = removal(route, node_toberemoved, index_node);
r.setLocations(route);
r.setDistance(distanceOneRoute(route));
r.setProfit(profitOneRoute(route));
checkCorrectNess(r, "remove customers eliminator");
}
}
else
{
if (prob.nextDouble() < Pl)
{
removed = true;
RouteNode node_toberemoved = node;
int index_node = route.indexOf(node);
route.remove(index_node);
u.add(node.customer);
route = removal(route, node_toberemoved, index_node);
r.setLocations(route);
r.setDistance(distanceOneRoute(route));
r.setProfit(profitOneRoute(route));
checkCorrectNess(r, "remove customers eliminator");
}
}
if (removed == false)
{
j++;
}
else
{
routesize = route.size();
total_profit = total_profit-node.customer.getProfit();
average_profit = total_profit/num_customers;
}
}
}
totalprofit = profitRoutes(routes);
}
public void addCustomers()
{
List<Customer> u_copy = new ArrayList<Customer>(u);
List<Customer> u_temp = new ArrayList<Customer>(u);
for (Customer c : u_temp)
{
boolean added = false;
for (ROUTE r : routes)
{
checkCorrectNess(r, "add customers eliminator");
if (added == true)
{
break;
}
Customer customer = c;
u_copy.remove(c);
List<RouteNode> route = r.getLocations();
for (int i = 0; i < route.size()-1; i++)
{
RouteNode possibleNode = new RouteNode();
possibleNode.customer = customer;
List<Integer> distances = calculateDistances(route.get(i), possibleNode, route.get(i+1));
// Calculate shift for customer under consideration
int arrivalTime = route.get(i).timeStartService+ route.get(i).customer.getService() + distances.get(0);
int wait = Math.max(0, customer.getOpeningTW()-arrivalTime);
int serviceDuration = customer.getService();
int shift = distances.get(0) + wait + serviceDuration + distances.get(2) - distances.get(1);
// Determine Start Service
int startServiceTime = Math.max(customer.getOpeningTW(), arrivalTime);
// Obtain waiting time of next customer
int waiting_next = route.get(i+1).wait;
// Obtain MaxShift of next customer
int maxShift = route.get(i+1).maxShift;
if (shift <= (waiting_next + maxShift) & startServiceTime <= customer.getClosingTW() )
{
// Customer can be inserted
added = true;
RouteNode newNode = new RouteNode();
newNode.customer = customer;
newNode.arrivalTime = arrivalTime;
newNode.timeStartService = startServiceTime;
newNode.shift = shift;
newNode.wait = wait;
int pos_insertion = i + 1;
route = ADD(route, newNode, pos_insertion);
r.setLocations(route);
r.setDistance(distanceOneRoute(route));
r.setProfit(profitOneRoute(route));
checkCorrectNess(r, "add customers eliminator");
// exit the last for loop
break;
}
}
}
if (added == false)
{
u_copy.add(c);
}
}
u = u_copy;
totalprofit = profitRoutes(routes);
}
/**
* Returns list of unvisited customers
* #return
*/
public List<Customer> getU()
{
return u;
}
/**
* Returns list of routes
* #return
*/
public List<ROUTE> getRoutes()
{
return routes;
}
}
How can I write the following method in Java 8 streams? I couldn't find a way to do it. This is my code:
public static List<ObjectB> getFilteredList(List<ObjectA> list, LocalTime startTime, LocalTime endTime, int quantity) {
List<ObjectA> objectAList = new LinkedList<>();
List<ObjectB> objectBList = new LinkedList<>();
for (ObjectA object : list) {
if (object.getDateTime().toLocalTime().isAfter(startTime) && object.getDateTime().toLocalTime().isBefore(endTime)) {
objectAList.add(object);
}
}
for (ObjectA objectA : objectAList) {
int total = 0;
for (ObjectA object : list) {
if (object.getDateTime().toLocalDate().equals(objectA.getDateTime().toLocalDate())) {
total += object.getQuantity();
}
}
if (total > quantity) {
objectBList.add(new ObjectB(objectA.getDateTime(), objectA.getDescription(), objectA.getQuantity(), true));
} else {
objectBList.add(new ObjectB(objectA.getDateTime(), objectA.getDescription(), objectA.getQuantity(), false));
}
}
return objectBList;}
I have a list of objects with two fields: date and quantity. I need to return a list with one object for each date, but with one more feild - boolean, which should be true if the total sum of all quantites per day is more than 16, and false if it's not.
Let's do this step-by-step.
for (ObjectA object : list) {: a for loop is usually replaced with stream(), so start with list.stream().
if (...) {: condition is usually replaced with filter(), so continue with .filter(object -> object.getDateTime()...)
objectAList.add(object);: adding the results to the container is usually replaced with collect(). You are using LinkedList(), but any other List would be fine here, so we will simply use collect(Collectors.toList()).
So here's first loop:
List<ObjectA> objectAList = list.stream()
.filter(object -> object.getDateTime().toLocalTime().isAfter(startTime) &&
object.getDateTime().toLocalTime().isBefore(endTime))
.collect(Collectors.toList());
Now let's look into the inner loop which calculates the total:
int total = 0;
for (ObjectA object : list) {
if (object.getDateTime().toLocalDate().equals(objectA.getDateTime().toLocalDate())) {
total += object.getQuantity();
}
}
It's also stream-filter-collect sequence, but here you want to collect the sum. So you may use IntStream here which already has the sum() method:
int total = list.stream()
.filter(object -> object.getDateTime().toLocalDate().equals(
objectA.getDateTime().toLocalDate())
.mapToInt(ObjectA::getQuantity).sum();
To make your code less crowded I would extract this to the separate method:
private static int getQuantityByDate(List<ObjectA> list, LocalDate date) {
return list.stream().filter(object -> object.getDateTime().toLocalDate().equals(date))
.mapToInt(ObjectA::getQuantity).sum();
}
Now the next if statement. It just changes the last boolean argument, so I would rewrite it (even without Stream API):
objectBList.add(new ObjectB(objectA.getDateTime(), objectA.getDescription(),
objectA.getQuantity(), total > quantity));
So now we see that the outer loop becomes stream-map-collect chain and could be rewritten this way:
List<ObjectB> objectBList = objectAList.stream()
.map(objectA ->
new ObjectB(objectA.getDateTime(), objectA.getDescription(), objectA.getQuantity(),
getQuantityByDate(list, objectA.getDateTime().toLocalDate()) > quantity))
.collect(Collectors.toList());
Now you can notice that collecting into objectAList is unnecessary as we just use it to create another stream. So we can merge both loops into single pipeline, resulting in the following final code:
private static int getQuantityByDate(List<ObjectA> list, LocalDate date) {
return list.stream().filter(object -> object.getDateTime().toLocalDate().equals(date))
.mapToInt(ObjectA::getQuantity).sum();
}
public static List<ObjectB> getFilteredList(
List<ObjectA> list, LocalTime startTime, LocalTime endTime, int quantity) {
return list.stream()
.filter(object -> object.getDateTime().toLocalTime().isAfter(startTime) &&
object.getDateTime().toLocalTime().isBefore(endTime))
.map(objectA -> new ObjectB(
objectA.getDateTime(), objectA.getDescription(), objectA.getQuantity(),
getQuantityByDate(list, objectA.getDateTime().toLocalDate()) > quantity))
.collect(Collectors.toList());
}
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?
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.
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.
What I would do:
Create samples of the data
Use a clustering algorithm
Generate samples using the algorithm
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.
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.
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.
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
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());
}
}
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.
Have a look at your favourite calendar program. See what patterns of event recurrence it can generate. Reverse engineer them.