#c #mergesort
#c #сортировка слиянием
Вопрос:
Наш инструктор дал нам файл csv, в котором мы должны отсортировать имена в алфавитном порядке на основе их фамилий. Но есть имена с одинаковой фамилией, и мой код работает только для их фамилии. Я не знаю, что добавить, чтобы отсортировать их по именам, когда у них одинаковая фамилия.
Это мой код:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define people 11
struct list_people {
char FirstName[20];
char LastName[20];
char Name[20];
char Age[5];
};
typedef struct list_people Details;
int merge_sort();
int merge(Details *[11], int, int, int);
int main() {
FILE *info;
int i, j;
Details A[people];
Details *cell[people];
for (i = 0; i < (people 1); i ) {
cell[i] = amp;A[i];
}
info = fopen("people.csv", "r");
for (i = 0; i < people; i ) {
fscanf(info," %[^,], %[^,], %8s", A[i].LastName, A[i].FirstName, A[i].Age);
}
fclose(info);
merge_sort(cell, 1, people - 1);
for (i = 1; i < people; i ) {
printf( "t %-20s %-20s Age:%-20s n", A[i].FirstName, A[i].LastName, A[i].Age);
}
}
int merge_sort(Details *A[], int low, int high) {
int mid;
if (low < high) {
mid = (low high) / 2;
merge_sort(A, low, mid);
merge_sort(A, mid 1, high);
merge(A, low, mid, high);
}
return 0;
}
int merge(Details *A[], int low, int mid, int high) {
int leftIndex = low;
int rightIndex = mid 1;
int combinedIndex = low;
int i, j;
Details tempA[people];
while (leftIndex <= mid amp;amp; rightIndex <= high) {
if (strcasecmp((A[leftIndex]->LastName), (A[rightIndex]->LastName)) <= 0) {
tempA[combinedIndex] = *(*(A leftIndex));
combinedIndex ;
leftIndex ;
} else {
tempA[combinedIndex] = *(*(A rightIndex));
combinedIndex ;
rightIndex ;
}
}
if (leftIndex == mid 1) {
while (rightIndex <= high) {
tempA[combinedIndex] = *(*(A rightIndex));
combinedIndex ;
rightIndex ;
}
} else {
while (leftIndex <= mid) {
tempA[combinedIndex] = *(*(A leftIndex));
combinedIndex ;
leftIndex ;
}
}
for (i = low; i <= high; i ) {
*(*(A i)) = tempA[i];
}
return 0;
}
Комментарии:
1. Вы можете принять ответ, нажав на серую галочку под его оценкой.
Ответ №1:
Вы могли бы сравнить элементы, используя что-то вроде этого:
int cmp;
// compare the last names
cmp = strcasecmp( ( A[leftIndex]->LastName), ( A[rightIndex]->LastName) );
if (cmp == 0)
{
// last names are identical so compare the first names
cmp = strcasecmp( ( A[leftIndex]->FirstName), ( A[rightIndex]->FirstName) );
}
if (cmp <= 0)
{
// ...
}
else
{
// ...
}
Ответ №2:
В коде есть несколько проблем:
-
размер массива
people
равен, поэтому цикл инициализации должен исключать значение индексаpeople
. Вместоfor (i = 0; i < (people 1); i )
вы должны написать:for (i = 0; i < people; i )
-
чтобы избежать переполнения буфера, вы должны указать максимальное количество байтов для чтения для каждого целевого массива в
fscanf()
:fscanf(info," [^,], [^,], %4s", A[i].LastName, A[i].FirstName, A[i].Age);
Вы также должны проверить возвращаемое значение
fscanf()
, чтобы обнаружить недопустимый ввод. -
чтобы упорядочить элементы с одинаковой фамилией, вы должны использовать функцию сравнения и сравнить
LastName
, затемFirstName
, затемAge
, возвращая первый неравный результат.
Вот пример:
int compareDetails(const Details *a, const Details *b) {
int res, age_a, age_b;
if ((res = strcasecmp(a->LastName, b->LastName)) != 0)
return res;
if ((res = strcasecmp(a->FirstName, b->FirstName)) != 0)
return res;
age_a = atoi(a->Age);
age_b = atoi(a->Age);
return (age_a > age_b) - (age_a < age_b);
}
Массивы в C основаны на нуле, поэтому значения индекса должны начинаться с 0
. В C также идиоматично указывать фрагмент массива с включенным первым индексом и исключенным последним индексом. Это позволяет использовать пустые фрагменты и упрощает merge_sort
код, избегая ошибок 1
/ -1
корректировок.
Массив cell
указателей является избыточным, поскольку вы сортируете массив Details
на месте. Вы можете упростить код таким образом:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define PEOPLE 11
struct list_people {
char FirstName[20];
char LastName[20];
char Name[20];
char Age[5];
};
typedef struct list_people Details;
int merge_sort(Details A[], int low, int high);
int main() {
FILE *info;
int i, j, n;
Details A[PEOPLE];
info = fopen("people.csv", "r");
if (info == NULL)
return 1;
for (n = 0; n < PEOPLE; n ) {
if (fscanf(info," [^,], [^,], %4s", A[n].LastName, A[n].FirstName, A[n].Age) != 3)
break;
}
fclose(info);
merge_sort(A, 0, n);
for (i = 0; i < n; i ) {
printf( "t %-20s %-20s Age:%-20s n", A[i].FirstName, A[i].LastName, A[i].Age);
}
return 0;
}
int compareDetails(const Details *a, const Details *b) {
int res, age_a, age_b;
if ((res = strcasecmp(a->LastName, b->LastName)) != 0)
return res;
if ((res = strcasecmp(a->FirstName, b->FirstName)) != 0)
return res;
age_a = atoi(a->Age);
age_b = atoi(a->Age);
return (age_a > age_b) - (age_a < age_b);
}
void merge(Details A[], int low, int mid, int high) {
int leftIndex = low;
int rightIndex = mid;
int combinedIndex = 0;
int i, j;
Details tempA[high - low];
while (leftIndex < mid amp;amp; rightIndex < high) {
if (compareDetails(amp;A[leftIndex], amp;A[rightIndex]) <= 0) {
tempA[combinedIndex] = A[leftIndex];
combinedIndex ;
leftIndex ;
} else {
tempA[combinedIndex] = A[rightIndex];
combinedIndex ;
rightIndex ;
}
}
while (leftIndex < mid) {
tempA[combinedIndex] = A[leftIndex];
combinedIndex ;
leftIndex ;
}
while (rightIndex < high) {
tempA[combinedIndex] = A[rightIndex];
combinedIndex ;
rightIndex ;
}
for (i = low; i < high; i ) {
A[i] = tempA[i - low];
}
}
int merge_sort(Details A[], int low, int high) {
if (high - low >= 2) {
int mid = low (high - low) / 2;
merge_sort(A, low, mid);
merge_sort(A, mid, high);
merge(A, low, mid, high);
}
return 0;
}
Ответ №3:
Если у вас две одинаковые фамилии, вам нужно сравнить с другим атрибутом и использовать его, чтобы определить, какой способ сортировки.
Во-первых, чтобы сделать вещи немного более разборчивыми, мы собираемся использовать вызовы strcasecmp
из выражений условий и сохранить их результат в некоторой переменной:
while ( leftIndex <= mid amp;amp; rightIndex <= high )
{
int lastNameResult = strcasecmp( A[leftIndex]->LastName, A[rightIndex]->LastName );
Затем в качестве первого шага мы собираемся разделить <= 0
регистр на два отдельных случая:
if ( lastNameResult < 0 )
{
// left < right, process as before
}
else if ( lastNameResult == 0 )
{
// new case to handle same last names
}
else
{
// left > right, process as before
}
Теперь в этом новом случае нам нужно сравнить со вторым атрибутом; обычный выбор будет FirstName
:
...
else if ( lastNameResult == 0 )
{
int firstNameResult = strcasecmp( A[leftIndex]->FirstName, A[rightIndex]->FirstName );
if ( firstNameResult < 0 )
{
// left < right, handle the same way you did for LastName
}
else if ( firstNameResult == 0 )
{
// left == right, need to compare against another attribute
}
else
{
// left > right, handle the same way you did for LastName
}
}
...
Если вы столкнулись с двумя записями с одинаковыми именами, вам нужно будет выбрать другой атрибут для сравнения (например, Age
) и поместить его в else if ( firstNameResult == 0 )
ветку. С другой стороны, вы можете объединить <
регистры ==
и и просто позволить повторяющимся записям попадать туда, куда они попадают.
Очевидно, что этот подход не очень хорошо масштабируется для более чем пары атрибутов, и вы заканчиваете дублированием кода. Лучший способ сделать это — отделить сравнения от слияний. Добавьте новую переменную, назовем ее mergeLeft
, для которой установлено значение true (отличное от нуля), если вам нужно объединить из левого списка, false ( 0
), если вам нужно объединить справа. Мы можем вычислить это без необходимости использовать целую цепочку if-else:
while ( leftIndex <= mid amp;amp; rightIndex <= high )
{
int lastNameResult = strcasecmp( A[leftIndex]->LastName, A[rightIndex]->LastName );
int firstNameResult = strcasecmp( A[leftIndex]->FirstName, A[rightIndex]->FirstName );
/**
* Yes, you can use logical expressions outside of an if condition. The
* parentheses aren't necessary in this case, but it should make the
* expression easier to understand. The result of this expression
* will either be 0 or 1.
*/
int mergeLeft = lastNameResult < 0 || (lastNameResult == 0 amp;amp; firstNameResult < 0);
mergeLeft
Переменной будет присвоено значение true ( 1
), если левое LastName
меньше правого LastName
, или если фамилии равны, а левое FirstName
меньше правого FirstName
. Пуф, этот уродливый вложенный if
оператор волшебным образом исчезает, и мы остаемся с этим в качестве тела вашего while
цикла:
if ( mergeLeft )
{
tempA[combinedIndex ] = *A[leftIndex ]; // I've combined the updates
} // of combinedIndex and leftIndex
else // within these statements,
{ // just to save a little space
tempA[combinedIndex ] = *A[rightIndex ]; // I also replaced pointer
} // notation with array notation
} // because it's less eye-stabby