Доброго времени суток всем читающим! Дали мне в одной фирме тестовое задание:
Есть файл, который может содержать 32768 целых, неповторяющихся чисел в диапазоне от 1 до 32768. Числа в файле расположены случайным образом. Написать программу, осуществляющую сортировку данного файла, результатом которой будет файл с отсортированными по возрастанию числами. Для осуществления сортировки разрешено использовать максимально 4Кб оперативной памяти.
...и все это должно быть написано на С (не путать с C++) и работать под Linux. В общем, переизобрести GNU "sort -n". Ладно, я, покурив, сочинил овнокод, отослал, никакого ответа, ессно — как модно нынче стало выражаться, "слив засчитан". Но все равно интересно, чтобы коллеги посмотрели со стороны и высказали замечания — на будущее.
Собственно, вот оно:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
/*
Merge-sort algorithm implementation.
Application read data from stdin, sort it using merge-sort
with RAM usage limitation and write data to stdout.
Data values are integers in range 1..32768
(NB! Zero value is excluded).
*/
/* RAM buffer size (in bytes)*/#define BUF_RAW_SIZE 4096
/* Input values total number limit */#define NUM_VALUES_MAX 32768
/* Value type */typedef unsigned VAL_T;
/* Iterator type */typedef unsigned ITER_T;
/* Size of one input value (in bytes) */#define VAL_SIZE sizeof(VAL_T)
/* RAM buffer size (in input values) */#define BUF_SIZE (BUF_RAW_SIZE / VAL_SIZE)
/* Temporary files quantity limit */#define NUM_TMP_FILES_MAX (NUM_VALUES_MAX / BUF_SIZE + 1)
/* Single data value format string, is used by printf/scanf */#define VAL_FMT "%u\n"/* Cast some arbitrary pointer to (VAL_T*) and dereference it */#define VAL_DEREF(Z) (*((const VAL_T*)Z))
/* Struct represents data stored in the temporary file */typedef struct {
/*
Temporary file associated with data,
NULL if file is not created or was exhausted
*/
FILE* pfile;
/*
Stack top value, is 0 if no data is present;
NB! We rely, that each data value is >= 1
*/
VAL_T top;
} OnDiskStack;
/* Size of one OnDiskStack instance */#define ODS_SIZE (sizeof(OnDiskStack))
/* Get the top value of OnDiskStack instance given by pointer to. */#define ODSREF_TOP(Z) (((const OnDiskStack*)Z)->top)
/*
Predicate, compares two VAL_T values,
is needed by std library qsort algorithm.
See qsort info for detailes.
*/int compare(const void* a, const void* b) {
return (VAL_DEREF(a) > VAL_DEREF(b)) - (VAL_DEREF(a) < VAL_DEREF(b));
}
/*
Predicate, compares two OnDiskStack instances by
top values. Is used by std library qsort algorithm
and also at stacks resorting (see main(), resort() functions).
Truth table:
a->top | b->top | compare_s(a, b)
------------+-----------+-----------------
0 | 0 | 0
------------+-----------+-----------------
S!=0 | 0 | -1
------------+-----------+-----------------
0 | S!=0 | 1
------------+-----------+-----------------
S1 | S2 | compare(S1, S2)
*/int compare_s(const void* a, const void* b) {
return
compare(&ODSREF_TOP(a), &ODSREF_TOP(b)) *
((ODSREF_TOP(a) && ODSREF_TOP(b)) ? 1: -1);
}
/*
Sort data in ascending order by std library qsort
and place to OnDiskStack (i.e. to temporary file);
Arguments:
buf - pointer to data buffer;
size - buffer size;
stack - pointer to destination stack (
stack s.b. empty, with no temporary file associated,
stack data would be lost otherwise
).
*/void sortnflush (
VAL_T* buf, ITER_T size,
OnDiskStack* stack
) {
stack->pfile = NULL;
stack->top = 0;
switch (size) {
case 1:
stack->top = buf[0];
case 0:
break;
default:
if (!(stack->pfile = tmpfile())) {
fprintf(stderr, "Temporary file creation failed\n");
exit(EXIT_FAILURE);
};
qsort(buf, size, VAL_SIZE, compare);
stack->top = buf[0];
/*
HACK! Write operation may be interrupted
in some cicrcuimstances, but we treat
such situation as an error
*/if (fwrite(&buf[1], VAL_SIZE, size - 1, stack->pfile) != (size - 1)) {
fprintf(stderr, "Temporary file write failed\n");
exit(EXIT_FAILURE);
};
rewind(stack->pfile);
break;
};
}
/*
Stacks resorting.
Stacks [1..(num_stacks - 1)] are assumed to be sorted.
Find the place to insert the first stack, and do insert.
*/void resort(OnDiskStack* stacks, ITER_T num_stacks) {
OnDiskStack buf_stack;
switch (num_stacks) {
case 2:
if (compare_s(&stacks[0], &stacks[1]) == 1) {
buf_stack = stacks[0];
stacks[0] = stacks[1];
stacks[1] = buf_stack;
};
case 0:
;
case 1:
break;
default:
if (compare_s(&stacks[0], &stacks[1]) == 1) {
ITER_T beg = 1;
ITER_T end = num_stacks;
ITER_T mid;
buf_stack = stacks[0];
/*
Search the place to insert stacks[0] in
range [beg, end)
*/do {
mid = (end + beg) / 2;
switch (compare_s(&buf_stack, &stacks[mid])) {
case 0:
beg = end = mid;
break;
case 1:
beg = mid;
break;
case -1:
end = mid;
break;
default:
/* We should never go here */
assert(0);
break;
};
} while ((end - beg) > 1);
memmove(stacks, &stacks[1], beg * ODS_SIZE);
stacks[beg] = buf_stack;
};
break;
};
}
/* Application entry point */int main(int argc, char* argv[]) {
/* On-disk stacks to store sorted portions of input data */static OnDiskStack stacks[NUM_TMP_FILES_MAX];
/* Actual number of temporary storages used */
ITER_T num_stacks;
/*
Read input data to buffer, sort buffer
and put to on-disk stack when buffer is full or
when stdin is terminated
*/
{
/* Input data buffer */static VAL_T buf[BUF_SIZE];
/* Actual number of items stored in buffer*/
ITER_T num_bufd = num_stacks = 0;
int scan_ok;
do {
scan_ok = (scanf(VAL_FMT, &buf[num_bufd]) == 1);
if (scan_ok) {
if (!buf[num_bufd]) {
fprintf(stderr, "Input data s.b. > 0\n");
exit(EXIT_FAILURE);
};
++num_bufd;
};
if ((BUF_SIZE == num_bufd) || (!scan_ok)) {
if (NUM_TMP_FILES_MAX == num_stacks) {
fprintf(stderr, "Input data limit exceeded\n");
exit(EXIT_FAILURE);
};
sortnflush(buf, num_bufd, &stacks[num_stacks]);
num_stacks++;
num_bufd = 0;
};
} while (scan_ok);
};
/*
Merge all data in stacks, write data to stdout
*/if (!num_stacks)
return EXIT_SUCCESS;
{
qsort(stacks, num_stacks, ODS_SIZE, compare_s);
while (stacks[0].top) {
printf(VAL_FMT, stacks[0].top);
if (!stacks[0].pfile) {
stacks[0].top = 0;
} else if (fread(&stacks[0].top, VAL_SIZE, 1, stacks[0].pfile) != 1) {
if (feof(stacks[0].pfile)) {
stacks[0].pfile = NULL;
stacks[0].top = 0;
} else {
fprintf(stderr, "Temporary file read failed\n");
exit(EXIT_FAILURE);
};
};
resort(stacks, num_stacks);
};
};
return EXIT_SUCCESS;
}
Заранее спасибо
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Здравствуйте, slava_phirsov, Вы писали:
_>Доброго времени суток всем читающим! Дали мне в одной фирме тестовое задание:
_>
_>Есть файл, который может содержать 32768 целых, неповторяющихся чисел в диапазоне от 1 до 32768. Числа в файле расположены случайным образом. Написать программу, осуществляющую сортировку данного файла, результатом которой будет файл с отсортированными по возрастанию числами. Для осуществления сортировки разрешено использовать максимально 4Кб оперативной памяти.
_>...и все это должно быть написано на С (не путать с C++) и работать под Linux. В общем, переизобрести GNU "sort -n". Ладно, я, покурив, сочинил овнокод, отослал, никакого ответа, ессно — как модно нынче стало выражаться, "слив засчитан". Но все равно интересно, чтобы коллеги посмотрели со стороны и высказали замечания — на будущее.
_>Собственно, вот оно: _>
_>...
_>
Я так думаю, что они все еще изучают твой очень длинный-длинный код...
А нельзя было сделать так: выделить 4Кб памяти, по биту на каждое возможное значение, далее пробежать по файлу и отметить те значения, которые там встретились, а дальше просто пробежать по 4Кб памяти и вывести те числа, которые в файле были:
char iwasthere[32768 / 8] = {}; // 4Кбwhile (ifil >> num)
iwasthere[num / 8] |= (1 << (num % 8));
for (int i = 0; i < 32768; ++i)
if (iwasthere[i / 8] & (1 << (i % 8)))
ofil << i << endl;
Здравствуйте, vadimcher, Вы писали:
V>А нельзя было сделать так: выделить 4Кб памяти, по биту на каждое возможное значение, далее пробежать по файлу и отметить те значения, которые там встретились, а дальше просто пробежать по 4Кб памяти и вывести те числа, которые в файле были
Кстати, да. Осталась одна надежда — гордо заявить, что мой код лучше, потому что будет быстрее работать (ну мало ли, бывают же чудеса, хе-хе). Надо попробовать написать твой вариант и прогнать под gprof-ом.
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
V>Я так думаю, что они все еще изучают твой очень длинный-длинный код... V>А нельзя было сделать так: выделить 4Кб памяти, по биту на каждое возможное значение, далее пробежать по файлу и отметить те значения, которые там встретились, а дальше просто пробежать по 4Кб памяти и вывести те числа, которые в файле были:
Здравствуйте, slava_phirsov, Вы писали:
_>Здравствуйте, vadimcher, Вы писали:
V>>А нельзя было сделать так: выделить 4Кб памяти, по биту на каждое возможное значение, далее пробежать по файлу и отметить те значения, которые там встретились, а дальше просто пробежать по 4Кб памяти и вывести те числа, которые в файле были
_>Кстати, да. Осталась одна надежда — гордо заявить, что мой код лучше, потому что будет быстрее работать (ну мало ли, бывают же чудеса, хе-хе). Надо попробовать написать твой вариант и прогнать под gprof-ом.
Здесь только два нюанса. Чтобы побыстрее работало массив лучше делать интами типа int[32768 / 8 / sizeof(int)], и, кроме того, у тебя входящие числа не от 0 до 32767, а от 1 до 32768, будь осторожен!
_>Есть файл, который может содержать 32768 целых, неповторяющихся чисел в диапазоне от 1 до 32768. Числа в файле расположены случайным образом. Написать программу, осуществляющую сортировку данного файла, результатом которой будет файл с отсортированными по возрастанию числами. Для осуществления сортировки разрешено использовать максимально 4Кб оперативной памяти.
_>...и все это должно быть написано на С (не путать с C++) и работать под Linux. В общем, переизобрести GNU "sort -n". Ладно, я, покурив, сочинил овнокод, отослал, никакого ответа, ессно — как модно нынче стало выражаться, "слив засчитан". Но все равно интересно, чтобы коллеги посмотрели со стороны и высказали замечания — на будущее.
На будущее я посоветовал бы знакомиться в таких случаях с творчеством Д. Кнута. Конкретно для этого случая рулит раздел 5.4 третьего тома — "Внешняя сортировка".
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
_>Есть файл, который может содержать 32768 целых, неповторяющихся чисел в диапазоне от 1 до 32768. Числа в файле расположены случайным образом. Написать программу, осуществляющую сортировку данного файла, результатом которой будет файл с отсортированными по возрастанию числами. Для осуществления сортировки разрешено использовать максимально 4Кб оперативной памяти.
А условие точно такое? Если заменить в первой фразе написать "Есть файл, который содержит 32768 целых" задача как раз для собеседования
Здравствуйте, Тролль-323, Вы писали:
_>>Есть файл, который может содержать 32768 целых, неповторяющихся чисел в диапазоне от 1 до 32768.
Т3>Так… Что-то я туплю на ночь глядя…
Т3>Это как бы шутка такая? На внимательность?
Т3>Тогда ведь получается, что он содержит каждое число от 1 до 32768 по одному разу.
Т3>Сортированный файл просто будет содержать тупо числа от 1 до 32768?
Здравствуйте, Sashaka, Вы писали:
S>Здравствуйте, Тролль-323, Вы писали:
_>>>Есть файл, который может содержать 32768 целых, неповторяющихся чисел в диапазоне от 1 до 32768.
Т3>>Так… Что-то я туплю на ночь глядя… Т3>>Это как бы шутка такая? На внимательность? Т3>>Тогда ведь получается, что он содержит каждое число от 1 до 32768 по одному разу. Т3>>Сортированный файл просто будет содержать тупо числа от 1 до 32768?
S>может содержать 32768, а может и меньше ? =)
А может и больше. А может вообще содержать не числа, а строки
В общем, аккуратнее надо с формулировками...
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
Read data from stdin, sort it
(RAM usage is strongly limited)
and write data to stdout.
Data values are integers in range 1..32768
(NB! Zero value is excluded, each input value
in sequence is unique, non-repetitive).
Algorithm doesn't store input data in buffer,
it stores one bit for each possible value, indicating
whether this particular value was encountered or not.
*/
/* RAM buffer size (in bytes)*/#define BUF_SIZE 4096
/* Value type */typedef unsigned VAL_T;
/* Iterator type */typedef unsigned ITER_T;
/* Single data value format string, is used by printf/scanf */#define VAL_FMT "%u\n"/* Get particular bit in particular byte value*/#define GET_BIT (buf[byte_pos] & (MASKS[bit_pos]))
/* Application entry point */int main(int argc, char* argv[]) {
/* Masks used to select single bit from the byte */static unsigned char MASKS[8] = {1, 2, 4, 8, 16, 32, 64, 128};
/* Input data buffer */static unsigned char buf[BUF_SIZE];
memset(buf, 0, BUF_SIZE);
/*
Read input data from stdin and accumulate it into buffer
in compressed form
*/
{
VAL_T value;
while (scanf(VAL_FMT, &value) == 1) {
ITER_T byte_pos, bit_pos;
if (!value) {
fprintf(stderr, "Input data s.b. > 0\n");
exit(EXIT_FAILURE);
};
--value;
byte_pos = value / 8;
bit_pos = (value % 8);
if (GET_BIT) {
fprintf(stderr, "Input data is duplicated\n");
exit(EXIT_FAILURE);
};
buf[byte_pos] |= MASKS[bit_pos];
};
};
/*
Write accumulated data to stdout
*/
{
ITER_T byte_pos, bit_pos;
for (byte_pos = 0; byte_pos < BUF_SIZE; ++byte_pos)
for (bit_pos = 0; bit_pos < 8; ++bit_pos)
if (GET_BIT)
printf(VAL_FMT, byte_pos * 8 + bit_pos + 1);
};
return EXIT_SUCCESS;
}
Как и следовало ожидать, gprof не сумел замерить скорость выполнения — выдал 0.0
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
_>>Есть файл, который может содержать 32768 целых, неповторяющихся чисел в диапазоне от 1 до 32768. Числа в файле расположены случайным образом. Написать программу, осуществляющую сортировку данного файла, результатом которой будет файл с отсортированными по возрастанию числами. Для осуществления сортировки разрешено использовать максимально 4Кб оперативной памяти.
СМ>А условие точно такое? Если заменить в первой фразе написать "Есть файл, который содержит 32768 целых" задача как раз для собеседования
А блин реально задача на внимательность
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Здравствуйте, Сергей Мухин, Вы писали:
СМ>Здравствуйте, slava_phirsov, Вы писали:
СМ>А условие точно такое? Если заменить в первой фразе написать "Есть файл, который содержит 32768 целых" задача как раз для собеседования
Как ни странно, мне эта мысль тоже приходила в голову. И насчет неопределенности формата данных в файле тоже мелькало, но я забил. А условие и в самом деле точно такое. А вот до решения с хранением флагов я как-то не допер... Тут надо или действительно быстро соображать, или просто знать готовое решение — задача из серии "Постройте 4 треугольника из 6-ти спичек", "Сколько раз надо спуститься в подвал, чтобы проверить какой выключатель какую лампочку контролирует", "Как измерить высоту дома при помощи барометра" ит.д. (список можно продолжать). Буду знать...
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Здравствуйте, Sergey, Вы писали:
S>На будущее я посоветовал бы знакомиться в таких случаях с творчеством Д. Кнута. Конкретно для этого случая рулит раздел 5.4 третьего тома — "Внешняя сортировка".
Achtung, ОФФТОП:
Да, только для таких случаев труд Кнута "Искусство программирования в сферическом вакууме" и нужен. Я серьезно — кроме как для решения тестовых задач мне ни разу не пришлось его листать. Увы, но то, что пишет дедушка Кнут, на практике мало востребованно (во всяком случае в моей практике) — гораздо чаще приходилось листать Майерса, книгу "Банды Четырех", Гласса, Бека. Ах да, раз мне потребовалось при решении задачи допускового анализа почитать то, что пишет Кнут про обход кортежей. Все здорово, но его супероптимизированный алгоритм считал минут двадцать, а мой некошерный управился за несколько секунд. И это не в упрек Кнуту будь сказано, просто его алгоритм обхода был рассчитан на некий абстрактный процедурно-ориентированный язык программирования, и все было организовано при помощи циклов, а я писал под Octave, и реализовал алгоритм в терминах операций с матрицами — дальнейшие пояснения, думаю, излишни. В конкретных условиях работы "неправильный" алгоритм оказался лучше "правильного". Поэтому я и хулигански приписал про "сферический вакуум"
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Здравствуйте, slava_phirsov, Вы писали:
_>Здравствуйте, Sergey, Вы писали:
S>>На будущее я посоветовал бы знакомиться в таких случаях с творчеством Д. Кнута. Конкретно для этого случая рулит раздел 5.4 третьего тома — "Внешняя сортировка".
_>Achtung, ОФФТОП:
_>Да, только для таких случаев труд Кнута "Искусство программирования в сферическом вакууме" и нужен. Я серьезно — кроме как для решения тестовых задач мне ни разу не пришлось его листать. Увы, но то, что пишет дедушка Кнут, на практике мало востребованно (во всяком случае в моей практике) — гораздо чаще приходилось листать Майерса, книгу "Банды Четырех", Гласса, Бека. Ах да, раз мне потребовалось при решении задачи допускового анализа почитать то, что пишет Кнут про обход кортежей. Все здорово, но его супероптимизированный алгоритм считал минут двадцать, а мой некошерный управился за несколько секунд. И это не в упрек Кнуту будь сказано, просто его алгоритм обхода был рассчитан на некий абстрактный процедурно-ориентированный язык программирования, и все было организовано при помощи циклов, а я писал под Octave, и реализовал алгоритм в терминах операций с матрицами — дальнейшие пояснения, думаю, излишни. В конкретных условиях работы "неправильный" алгоритм оказался лучше "правильного". Поэтому я и хулигански приписал про "сферический вакуум"
Зря ты так. Я думаю ты очень часто пользуешься трудами дедушки Кнута, даже не подозревая об этом...
_>Как ни странно, мне эта мысль тоже приходила в голову. И насчет неопределенности формата данных в файле тоже мелькало, но я забил. А условие и в самом деле точно такое. А вот до решения с хранением флагов я как-то не допер... Тут надо или действительно быстро соображать, или просто знать готовое решение — задача из серии "Постройте 4 треугольника из 6-ти спичек", "Сколько раз надо спуститься в подвал, чтобы проверить какой выключатель какую лампочку контролирует", "Как измерить высоту дома при помощи барометра" ит.д. (список можно продолжать). Буду знать...
Вот в этой книжке "Жемчужины программирования" описана твоя задача. Книга эта не так объёмна, как тот же Кнут, но там рассматриваются всякие интересные задачи на пределах возможности машины. А вообще, согласен, что это тестовое задание на знание алгоритмов. Сходу, не прочитав определённую литературу, правильное решение не получится.
Здравствуйте, vadimcher, Вы писали:
V>Зря ты так. Я думаю ты очень часто пользуешься трудами дедушки Кнута, даже не подозревая об этом...
И даже подозревая — когда работаю в Texmaker'е А когда я втыкаю комп в розетку, я получаю электроэнергию от ЛАЭС, т.е., сам того не подозревая, пользуюсь трудами Курчатова, вернее, Бора, вернее Эйнштейна — ну, короче, идея ясна. Пафос был не в том, что Кнут — не крут, а в том, что в повседневности его книга, вот лично мне, как среднему (во всех смыслах) кодеру не очень-то и нужна... Ты (и другие знатоки трудов Кнута) можешь меня поправить, но вот темы, которые реально мне нужны каждый (или почти каждый) день, и о которых Кнут не пишет:
Разработка архитектуры на макро или на микро-уровне (можно тут еще вспомнить недавние споры в этой же ветке форума на предмет — когда надо кидать исключение, когда возвращать код ошибки, а когда использовать assert)
Объектно-ориентированное программирование (паттерны)
Функциональное программирование
Организация работы программиста
Тестирование
Вот эту повседневность я у Кнута не нашел (может быть, плохо искал? ОК, ткните носом, плиз). Пример с Октавой я привел выше — Кнут ведь писал "вообще", а в каждом конкретном случае общее решение как правило проигрывает (хороший философский принцип, примером его могут служить и две реализации лисапеда, породившего данный топик — "общая", которая "по-честному" собирает данные во временные файлы и "чисто конкретная", которая хранит только флаги).
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
в предложенных решениях сразу нарушается условие задачи — 4кб т.к. выделяется место под массив и есть хотябы одна переменная )
также не учтено потребление памяти при загрузке программы
Здравствуйте, Sashaka, Вы писали:
Т3>>Тогда ведь получается, что он содержит каждое число от 1 до 32768 по одному разу.
Т3>>Сортированный файл просто будет содержать тупо числа от 1 до 32768?
S>может содержать 32768, а может и меньше ? =)
Вначале выделить массив целых из 32768. Затем обнулить его.
Затем заполнять.
При выводе игнорировать нули.
Минусы: неэффективно при маленьком размере файла.
Если немного подумать, то оказывается, что значение массива будет зависеть от индекса.
Поэтому массив целых можно заменить на массив булевых констант.
Если надо оптимизировать по памяти, то можно сжать булевы константы до битовых флагов, как писал vadimcher.