randomfox (randomfox) wrote,
randomfox
randomfox

Calculate bills and hits needed to add 1 to the George Score (C# version)

Same as the Perl version of the program, but rewritten in C#.


// gs - George Score calculator and predictor
// Given the number of bills entered, the total hits, and the number of
// days of inactivity, calculates the George Score. Also calculates the
// number of bills and hits needed to raise the George Score by 1.
//
// Author: Po Shan Cheah
// Last updated: August 19, 2003
// 
// Compile with: csc /doc:gs.xml gs.cs

namespace GeorgeScore {

using System;

/// <summary>Holds the bill and hit stats. Calculates the George Score from
/// the stats.</summary>
class UserStats {
    int billsEntered;
    int totalHits;
    int daysInactive;

    public UserStats(int billsEntered, int totalHits, int daysInactive) {
	this.billsEntered = billsEntered;
	this.totalHits = totalHits;
	this.daysInactive = daysInactive;
    }

    public int BillsEntered {
	get { return billsEntered; }
    }
    public int TotalHits {
	get { return totalHits; }
    }
    public int DaysInactive {
	get { return daysInactive; }
    }

    /// <summary>George Score formula</summary>
    static double __CalcGS(double billsEntered, double totalHits, 
			   double daysInactive) {
	return 100 * 
	    (Math.Sqrt(Math.Log(billsEntered)) + Math.Log(totalHits + 1)) * 
	    (1 - daysInactive / 90.0);
    }

    /// <summary>Calculate the George Score.</summary>
    public double CalcGS() {
	return __CalcGS(billsEntered, totalHits, daysInactive);
    }

    /// <summary>Calculate the George Score with <paramref name="x"/> more
    /// hits.</summary>
    public double IncrHits(double x) {
	return __CalcGS(billsEntered, totalHits + x, daysInactive);
    }
    
    /// <summary>Calculate the George Score with <paramref name="x"/> more
    /// bills entered.</summary>
    public double IncrBills(double x) {
	return __CalcGS(billsEntered + x, totalHits, daysInactive);
    }
} // class UserStats

class GeorgeScore {
    delegate double FuncX(double x);

    /// <summary>Does linear interpolation to find out how large the
    /// <paramref name="fx"/>'s argument must be to raise its value by
    /// 1.</summary>
    /// <param name="fx">The function that is to be solved</param>
    double Solve1GS(FuncX fx) {

	double currentGS = fx(0);

	// Get a crude upper bound for the solution. Keep doubling the
	// increment variable until the George Score is raised by more than
	// one.
	double upper;
	for (upper = 1; upper < 1e9; upper *= 2) {
	    if (fx(upper) - currentGS >= 1)
		break;
	}

	double a = 0;
	double b = upper;
	int itercount = 0;
	double xold = a;

	const double TOL = 1e-10;
	const int ITERLIMIT = 200;

	// This part is the linear interpolation solver. Keep iterating until
	// the solution stops changing to within a tolerance factor of TOL.
	// Then we have the answer. The number of iterations is capped at
	// ITERLIMIT.
	while (true) {
	    double fa = fx(a) - currentGS - 1;
	    double fb = fx(b) - currentGS - 1;

	    double newx = a - fa * (b - a) / (fb - fa);
	    double newfx = fx(newx) - currentGS - 1;

	    ++itercount;
	    Console.WriteLine("{0,5}: {1,25} {2,25}", itercount, newx, newfx);

	    if (Math.Abs(newx - xold) < TOL * Math.Abs(newx))
		return newx;
	    
	    if (itercount >= ITERLIMIT) {
		Console.WriteLine(
		    "Not converging. Returning the last approximation.");
		return newx;
	    }

	    xold = newx;

	    if (fa * newfx > 0)
		a = newx;
	    else
		b = newx;
	}
    } // Solve1GS

    /// <summary>Main output function. Run the projections. Show the
    /// results.</summary>
    void DoProjections(UserStats userstats) {
	double hitPred;
	double billPred;

	Console.WriteLine("Bills Entered            = {0}", 
			  userstats.BillsEntered);
	Console.WriteLine("Total Hits               = {0}", 
			  userstats.TotalHits);
	Console.WriteLine("Days Inactive            = {0}", 
			  userstats.DaysInactive);
	Console.WriteLine("George Score             = {0:f3}", 
			  userstats.CalcGS());
	Console.WriteLine();

	// One way to approximate the number of hits or bills entered needed to
	// raise the GS by 1 is to calculate the reciprocal of the partial
	// derivative.
	hitPred = (userstats.TotalHits + 1) / 100.0 / 
	    (1 - userstats.DaysInactive / 90.0);
	billPred = userstats.BillsEntered * 
	    Math.Sqrt(Math.Log(userstats.BillsEntered)) / 50 / 
	    (1 - userstats.DaysInactive / 90.0);

	Console.WriteLine("Using partial derivatives:");
	Console.WriteLine("    Hits needed for 1GS  = {0:f2}", hitPred);
	Console.WriteLine("    Bills needed for 1GS = {0:f2}", billPred);
	Console.WriteLine("    Bill/Hit ratio       = {0:f3}", 
			  billPred / hitPred);
	Console.WriteLine();

	// A more accurate but slower way to do that is to solve for it.
	hitPred = Solve1GS(new FuncX(userstats.IncrHits));
	billPred = Solve1GS(new FuncX(userstats.IncrBills));
	Console.WriteLine();

	Console.WriteLine("Using solver:");
	Console.WriteLine("    Hits needed for 1GS  = {0:f2}", hitPred);
	Console.WriteLine("    Bills needed for 1GS = {0:f2}", billPred);
	Console.WriteLine("    Bill/Hit ratio       = {0:f3}", 
			  billPred / hitPred);
    } // DoProjections

    const string USAGE = "Usage: gs bills-entered total-hits [days-inactive]";

    static int Main(string[] args) {
	if (args.Length < 2) {
	    Console.Error.WriteLine(USAGE);
	    return 1;
	}

	int billsEntered = 0;
	int totalHits = 0;
	int daysInactive = 0;

	try {
	    billsEntered = int.Parse(args[0]);
	    totalHits = int.Parse(args[1]);

	    if (args.Length > 2)
		daysInactive = int.Parse(args[2]);
	}
	catch (FormatException) {
	    Console.Error.WriteLine(USAGE);
	    return 1;
	}

	if (billsEntered < 1)
	    billsEntered = 1;

	if (totalHits < 0)
	    totalHits = 0;

	// Cap the days of inactivity at 89. Solver does not work at 90 and
	// above.
	if (daysInactive > 89)
	    daysInactive = 89;
	if (daysInactive < 0)
	    daysInactive = 0;

	new GeorgeScore().
	    DoProjections(new UserStats(billsEntered, 
					totalHits, daysInactive));
	return 0;
    }
} // class GeorgeScore

}
// The End


Example:
C:\cs>gs 93500 26526
Bills Entered            = 93500
Total Hits               = 26526
Days Inactive            = 0
George Score             = 1356.907

Using partial derivatives:
    Hits needed for 1GS  = 265.27
    Bills needed for 1GS = 6326.49
    Bill/Hit ratio       = 23.849

    1:          267.821843383639       0.00455718262378468
    2:          266.606867201049      2.27104924306332E-05
    3:          266.600812565313      1.13174792204518E-07
    4:          266.600782392825      5.63886715099216E-10
    5:          266.600782242493      2.95585778076202E-12
    6:          266.600782241705                         0
    1:          6611.84774072976       0.00830433443115908
    2:          6557.39295662145      0.000287001647393481
    3:          6555.51151401742       9.9170479188615E-06
    4:          6555.44650334032      3.42671228281688E-07
    5:          6555.44425697818      1.18407115223818E-08
    6:          6555.44417935706      4.09045242122374E-10
    7:          6555.44417667559      1.40971678774804E-11
    8:          6555.44417658317      4.54747350886464E-13

Using solver:
    Hits needed for 1GS  = 266.60
    Bills needed for 1GS = 6555.44
    Bill/Hit ratio       = 24.589

Subscribe
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 0 comments