/* See LICENSE file for copyright and license details. */
#include <ctype.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(__GNUC__)
# define PURE __attribute__((__pure__))
#else
# define PURE
#endif
struct group {
char *key;
struct group *next;
struct group *prev;
char **elems;
size_t nelems;
size_t elems_size;
int numerical;
};
static const char *argv0 = "median";
static struct group groups_head;
static struct group groups_tail;
PURE static int
isnumerical(const char *s)
{
if (*s == '+' || *s == '-')
s++;
if (s[0] == '.' && !s[1])
return 0;
while (isdigit(*s))
s++;
if (*s == '.') {
s++;
while (isdigit(*s))
s++;
}
return *s ? 0 : 1;
}
PURE static int
cmp_num(const void *apv, const void *bpv)
{
const char *a = *(const char *const *)apv;
const char *b = *(const char *const *)bpv;
int mul = 1;
size_t an = 0, bn = 0, i;
if (*a == '-' || *b == '-') {
mul = -1;
a = &a[1];
b = &b[1];
} else if (*a == '-') {
return -1;
} else if (*b == '-') {
return +1;
} else {
a = &a[*a == '+'];
b = &b[*b == '+'];
}
while (*a == '0') a++;
while (*b == '0') b++;
while (a[an] && a[an] != '.') an++;
while (b[bn] && b[bn] != '.') bn++;
if (an != bn)
return an < bn ? -mul : mul;
for (i = 0; a[i] && a[i] == b[i]; i++);
a = &a[i];
b = &b[i];
if (i > an) {
if (!*a)
while (*b == '0') b++;
if (!*b)
while (*a == '0') a++;
}
if (!*a && !*b) {
return 0;
} else if (!*a) {
return -mul;
} else if (!*b) {
return mul;
} else if (*a < *b) {
return -mul;
} else {
return mul;
}
}
static int
cmp_str(const void *apv, const void *bpv)
{
const char *a = *(const char *const *)apv;
const char *b = *(const char *const *)bpv;
return strcmp(a, b);
}
static int
avg(char *a, const char *b)
{
size_t i;
int carry = 0, val;
for (i = 0; a[i]; i++) {
val = (a[i] & 15) + (b[i] & 15);
carry = val & 1;
a[i] = (char)((val >> 1) | '0');
}
return carry;
}
static int
subavg(char *a, const char *b)
{
size_t i;
int carry = 0, val;
for (i = 0; a[i]; i++) {
val = (a[i] & 15) - (b[i] & 15);
carry = val & 1;
a[i] = (char)((val >> 1) | '0');
}
return carry;
}
static void
median2(const char *low, const char *high, const char *key)
{
int low_plus = *low == '+', low_minus = *low == '-', low_dot;
int high_plus = *high == '+', high_minus = *high == '-', high_dot;
size_t low_int, low_frac = 0, high_int, high_frac = 0;
size_t max_int, max_frac, i;
char *low2, *high2, *tmp;
const char *prefix;
int carry;
for (low_int = 0; low[low_int] && low[low_int] != '.'; low_int++);
for (high_int = 0; high[high_int] && high[high_int] != '.'; high_int++);
low_dot = low[low_int] == '.';
high_dot = high[high_int] == '.';
low = &low[low_plus | low_minus];
high = &high[high_plus | high_minus];
if (low_dot)
low_frac = strlen(&low[low_int + 1]);
if (high_dot)
high_frac = strlen(&high[high_int + 1]);
max_int = low_int > high_int ? low_int : high_int;
max_frac = low_frac > high_frac ? low_frac : high_frac;
low2 = malloc(max_int + max_frac + 1);
high2 = malloc(max_int + max_frac + 1);
if (!low2 || !high2) {
perror(argv0);
exit(1);
}
low2[max_int + max_frac] = '\0';
high2[max_int + max_frac] = '\0';
memset(low2, '0', max_int - low_int);
memcpy(&low2[max_int - low_int], low, low_int);
memcpy(&low2[max_int], &low[low_int + 1], low_frac);
memset(&low2[max_int + low_frac], '0', max_frac - low_frac);
memset(high2, '0', max_int - high_int);
memcpy(&high2[max_int - high_int], high, high_int);
memcpy(&high2[max_int], &high[high_int + 1], high_frac);
memset(&high2[max_int + high_frac], '0', max_frac - high_frac);
if (low_minus && high_minus) {
prefix = "-";
carry = avg(high2, low2);
} else if (low_minus) {
for (i = 0; low2[i] && low2[i] == high2[i]; i++);
if (low2[i] <= high2[i]) {
prefix = "+";
carry = subavg(high2, low2);
} else {
prefix = "-";
carry = subavg(low2, high2);
tmp = low2, low2 = high2, high2 = tmp;
}
} else {
prefix = (low_plus || high_plus) ? "+" : "";
carry = avg(high2, low2);
}
printf("%s%.*s%s""%s%s%s\n",
prefix, (int)max_int, high2,
(carry || max_frac) ? "." : "", &high2[max_int], carry ? "5" : "", key);
free(low2);
free(high2);
}
int
main(int argc, char *argv[])
{
char *line = NULL, *key;
struct group *group = NULL;
size_t size = 0;
ssize_t len;
groups_head.next = &groups_tail;
groups_tail.prev = &groups_head;
if (argc) {
argv0 = *argv++, argc--;
if (argc && argv[0][0] == '-' && argv[0][1] == '-' && !argv[0][2])
argv++, argc--;
if (argc) {
fprintf(stderr, "usage: %s\n", argv0);
return 1;
}
}
while ((len = getdelim(&line, &size, '\n', stdin)) > 0) {
if (len && line[--len] == '\n')
line[len] = '\0';
for (key = line; *key && !isspace(*key); key++);
if (group && !strcmp(group->key, key))
goto found_group;
for (group = groups_head.next; group->key; group = group->next)
if (!strcmp(group->key, key))
goto found_group;
group = calloc(1, sizeof(*group));
if (!group) {
perror(argv0);
return 1;
}
group->key = strdup(key);
if (!group->key) {
perror(argv0);
return 1;
}
group->numerical = 1;
group->prev = groups_tail.prev;
group->next = &groups_tail;
groups_tail.prev->next = group;
groups_tail.prev = group;
found_group:
if (group->nelems == group->elems_size) {
if (group->elems_size > SIZE_MAX / 2 / sizeof(*group->elems)) {
errno = ENOMEM;
perror(argv0);
return 1;
}
group->elems_size = group->elems_size ? group->elems_size * 2 : 16;
group->elems = realloc(group->elems, group->elems_size * sizeof(*group->elems));
if (!group->elems) {
perror(argv0);
return 1;
}
}
*key = '\0';
group->elems[group->nelems] = strdup(line);
if (!group->elems[group->nelems]) {
perror(argv0);
return 1;
}
if (group->numerical)
if (!isnumerical(line))
group->numerical = 0;
group->nelems++;
}
if (ferror(stdin)) {
perror(argv0);
return 1;
}
free(line);
while ((group = groups_head.next)->key) {
qsort(group->elems, group->nelems, sizeof(*group->elems), group->numerical ? cmp_num : cmp_str);
if (group->nelems % 2 || !group->numerical)
printf("%s%s\n", group->elems[(group->nelems - 1) / 2], group->key);
else if (group->nelems)
median2(group->elems[group->nelems / 2 - 1], group->elems[group->nelems / 2 - 0], group->key);
groups_head.next = group->next;
while (group->nelems--)
free(group->elems[group->nelems]);
free(group->elems);
free(group->key);
free(group);
}
if (fflush(stdout) || ferror(stdout) || fclose(stdout)) {
perror(argv0);
return 1;
}
return 0;
}