# HG changeset patch # User jbe # Date 1381489023 -7200 # Node ID 326dc0c3b859fcc2d38584d46ca387fbf80c7984 # Parent 25fbce923cf227abec3f40286ad75965ff942a32 Calculation of "order_in_admission_state" diff -r 25fbce923cf2 -r 326dc0c3b859 Makefile --- a/Makefile Thu Oct 10 11:37:50 2013 +0200 +++ b/Makefile Fri Oct 11 12:57:03 2013 +0200 @@ -1,4 +1,4 @@ -all:: lf_update lf_update_suggestion_order +all:: lf_update lf_update_issue_order lf_update_suggestion_order lf_update: lf_update.c cc -Wall -O2 \ @@ -6,6 +6,12 @@ -L "`pg_config --libdir`" \ -o lf_update lf_update.c -lpq +lf_update_issue_order: lf_update_issue_order.c + cc -Wall -O2 \ + -I "`pg_config --includedir`" \ + -L "`pg_config --libdir`" \ + -o lf_update_issue_order lf_update_issue_order.c -lpq + lf_update_suggestion_order: lf_update_suggestion_order.c cc -Wall -O2 \ -I "`pg_config --includedir`" \ @@ -13,4 +19,4 @@ -o lf_update_suggestion_order lf_update_suggestion_order.c -lpq clean:: - rm -f lf_update lf_update_suggestion_order + rm -f lf_update lf_update_issue_order lf_update_suggestion_order diff -r 25fbce923cf2 -r 326dc0c3b859 lf_update_issue_order.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lf_update_issue_order.c Fri Oct 11 12:57:03 2013 +0200 @@ -0,0 +1,507 @@ +#include +#include +#include +#include +#include + +static int logging = 0; + +static char *escapeLiteral(PGconn *conn, const char *str, size_t len) { + // provides compatibility for PostgreSQL versions prior 9.0 + // in future: return PQescapeLiteral(conn, str, len); + char *res; + size_t res_len; + res = malloc(2*len+3); + if (!res) return NULL; + res[0] = '\''; + res_len = PQescapeStringConn(conn, res+1, str, len, NULL); + res[res_len+1] = '\''; + res[res_len+2] = 0; + return res; +} + +static void freemem(void *ptr) { + // to be used for "escapeLiteral" function + // provides compatibility for PostgreSQL versions prior 9.0 + // in future: PQfreemem(ptr); + free(ptr); +} + +// column numbers when querying "issue_supporter_in_admission_state" view in function main(): +#define COL_MEMBER_ID 0 +#define COL_WEIGHT 1 +#define COL_ISSUE_ID 2 + +// data structure for a candidate (in this case a suggestion) to the proportional runoff system: +struct candidate { + char *key; // identifier of the candidate, which is the "suggestion_id" string + double score_per_step; // added score per step + double score; // current score of candidate; a score of 1.0 is needed to survive a round + int seat; // equals 0 for unseated candidates, or contains rank number +}; + +// compare two integers stored as strings (invocation like strcmp): +static int compare_id(char *id1, char *id2) { + int ldiff; + ldiff = strlen(id1) - strlen(id2); + if (ldiff) return ldiff; + else return strcmp(id1, id2); +} + +// compare two candidates by their key (invocation like strcmp): +static int compare_candidate(struct candidate *c1, struct candidate *c2) { + return compare_id(c1->key, c2->key); +} + +// candidates are stored as global variables due to the constrained twalk() interface: +static int candidate_count; +static struct candidate *candidates; + +// function to be passed to twalk() to store candidates ordered in candidates[] array: +static void register_candidate(char **candidate_key, VISIT visit, int level) { + if (visit == postorder || visit == leaf) { + struct candidate *candidate; + candidate = candidates + (candidate_count++); + candidate->key = *candidate_key; + candidate->seat = 0; + if (logging) printf("Candidate #%i is suggestion #%s.\n", candidate_count, candidate->key); + } +} + +// performs a binary search in candidates[] array to lookup a candidate by its key (which is the suggestion_id): +static struct candidate *candidate_by_key(char *candidate_key) { + struct candidate *candidate; + struct candidate compare; + compare.key = candidate_key; + candidate = bsearch(&compare, candidates, candidate_count, sizeof(struct candidate), (void *)compare_candidate); + if (!candidate) { + fprintf(stderr, "Candidate not found (should not happen).\n"); + abort(); + } + return candidate; +} + +// ballot of the proportional runoff system: +struct ballot { + int weight; // if weight is greater than 1, then the ballot is counted multiple times + int count; + struct candidate **candidates; +}; + +// determine candidate, which is assigned the next seat (starting with the worst rank): +static struct candidate *loser(int round_number, struct ballot *ballots, int ballot_count) { + int i, j; // index variables for loops + int remaining; // remaining candidates to be seated + // reset scores of all candidates: + for (i=0; i 1) { + if (logging) printf("There are %i remaining candidates.\n", remaining); + double scale; // factor to be later multiplied with score_per_step: + // reset score_per_step for all candidates: + for (i=0; iscore < 1.0 && !candidate->seat) matches++; + } + if (matches) { + double score_inc; + score_inc = (double)ballots[i].weight / (double)matches; + for (j=0; jscore < 1.0 && !candidate->seat) { + candidate->score_per_step += score_inc; + } + } + } + } + // calculate scale factor: + scale = (double)0.0; // 0.0 is used to indicate that there is no value yet + for (i=0; i 0.0) { + max_scale = (1.0-candidates[i].score) / candidates[i].score_per_step; + if (scale == 0.0 || max_scale <= scale) { + scale = max_scale; + } + } + } + // add scale*score_per_step to each candidates score: + for (i=0; i 0.0) { + double max_scale; + max_scale = (1.0-candidates[i].score) / candidates[i].score_per_step; + if (max_scale == scale) { + // score of 1.0 should be reached, so we set score directly to avoid floating point errors: + candidates[i].score = 1.0; + remaining--; + } else { + candidates[i].score += scale * candidates[i].score_per_step; + if (candidates[i].score >= 1.0) remaining--; + } + } + if (log_candidate) { + if (candidates[i].score >= 1.0) printf("=1\n"); + else printf("=%.4f\n", candidates[i].score); + } + // when there is only one candidate remaining, then break inner (and thus outer) loop: + if (remaining <= 1) { + break; + } + } + } + // return remaining candidate: + for (i=0; iweight = weight; + ballot->count++; + old_member_id = member_id; + } + // allocate memory for ballot sections: + for (i=0; icandidates[candidates_in_ballot++] = candidate_by_key(issue_id); + old_member_id = member_id; + } + // print ballots, if logging is enabled: + if (logging) { + for (i=0; ikey); + } + // if (!j) printf("empty"); // should not happen + printf(".\n"); + } + } + } + + // calculate ranks based on constructed data structures: + for (i=0; iseat = candidate_count - i; + if (logging) printf("Assigning rank #%i to issue #%s.\n", candidate_count-i, candidate->key); + } + + // free ballots[] array: + for (i=0; i\n", argv[0]); + fprintf(out, "\n"); + fprintf(out, " is specified by PostgreSQL's libpq,\n"); + fprintf(out, "see http://www.postgresql.org/docs/9.1/static/libpq-connect.html\n"); + fprintf(out, "\n"); + fprintf(out, "Example: %s dbname=liquid_feedback\n", argv[0]); + fprintf(out, "\n"); + return argc == 1 ? 1 : 0; + } + { + size_t len = 0; + int argb = 1; + if ( + argc >= 2 && + (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--verbose")) + ) { + argb = 2; + logging = 1; + } + for (i=argb; iargb) strcat(conninfo, " "); + strcat(conninfo, argv[i]); + } + } + + // connect to database: + db = PQconnectdb(conninfo); + if (!db) { + fprintf(stderr, "Error: Could not create database handle.\n"); + return 1; + } + if (PQstatus(db) != CONNECTION_OK) { + fprintf(stderr, "Could not open connection:\n%s", PQerrorMessage(db)); + return 1; + } + + // go through areas: + res = PQexec(db, "SELECT \"id\" FROM \"area\""); + if (!res) { + fprintf(stderr, "Error in pqlib while sending SQL command selecting areas to process.\n"); + err = 1; + } else if (PQresultStatus(res) != PGRES_TUPLES_OK) { + fprintf(stderr, "Error while executing SQL command selecting areas to process:\n%s", PQresultErrorMessage(res)); + err = 1; + PQclear(res); + } else if (PQnfields(res) < 1) { + fprintf(stderr, "Too few columns returned by SQL command selecting areas to process.\n"); + err = 1; + PQclear(res); + } else { + count = PQntuples(res); + if (logging) printf("Number of areas to process: %i\n", count); + for (i=0; i