Reputation: 11
I am trying to order some triangles based on their surface. To do so, the triangles are stored in an array of structs named triangle
. To sort this, my idea was to create a sort of parallel array with the surfaces of the triangle and swapping elements of both arrays "in parallel". The sorting is done in a void
function that receives the array and the number of elements in it.
The sorting seems to work, I checked by printing the array of surfaces, but when I print the array of triangles (both from inside and outside the function), it is not in the correct order. It is neither the sorted one nor the initial one.
I used bubble sort for sorting, I know it's inefficient but it's also very simple to implement. I know I could have used qsort
function, but for learning's sake I'd like to do this from scratch first. Here is the code, hopefully it is readable enough:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
struct triangle {
int a;
int b;
int c;
};
typedef struct triangle triangle;
void sort_by_area(triangle *tr, int n) {
double s[n];
double p, s_2;
int u;
triangle v;
for (int i = 0; i < n; i++) {
p = (tr[i].a + tr[i].b + tr[i].b);
p = p / 2.0;
s_2 = p * (p - tr[i].a) + (p - tr[i].b) + (p - tr[i].c);
s[i] = sqrt(s_2);
}
//bubble sort
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < (n - i - 1); j++) {
if (s[j] > s[j + 1]) {
u = s[j];
s[j] = s[j + 1];
s[j + 1] = u;
v = tr[j];
tr[j]= tr[j + 1];
tr[j + 1] = v;
//printf("swapped");
}
}
}
for (int i = 0; i < n; i++) {
printf("%f\n", s[i]);
if (i == (n - 1)) {
printf("\n\n");
}
}
}
int main() {
int n;
scanf("%d", &n);
triangle *tr = malloc(n * sizeof(triangle));
for (int i = 0; i < n; i++) {
scanf("%d%d%d", &tr[i].a, &tr[i].b, &tr[i].c);
}
sort_by_area(tr, n);
for (int i = 0; i < n; i++) {
printf("%d %d %d\n", tr[i].a, tr[i].b, tr[i].c);
}
return 0;
}
Here an input example that I tried for testing:
10
67 67 19
3 57 55
33 33 33
61 58 59
23 43 35
48 42 45
23 12 27
41 34 22
26 49 35
63 46 45
My result is
23 12 27
41 34 22
33 33 33
63 46 45
48 42 45
23 43 35
26 49 35
61 58 59
3 57 55
67 67 19
Upvotes: 1
Views: 125
Reputation: 1765
"I am trying to order some triangles based on their surface. To do so, the triangles are stored in an array of structs named triangle. To sort this my idea was to create a sort of parallel array with the surfaces of the triangle and swapping "in parallel" the two arrays. The sorting is done in a void function that receives the array and the number of elements in it"
You can get id done the way you are trying to, but understand that is far more easier when you have an abstraction closer to the data you have.
This is all you have in your code:
struct triangle
{
int a;
int b;
int c;
};
// ...
double s[n]; // array with surfaces for all triangles
// ...
void sort_by_area(triangle* tr, int n);
You are trying to use Heron's formula, but wrote it all wrong:
p = (tr[i].a+tr[i].b+tr[i].b);
p = p/2.0;
s_2 = p*(p-tr[i].a)+(p-tr[i].b)+(p-tr[i].c);
s[i] = sqrt(s_2);
tr[i].b
twice...s_2
is not a product of a sum. it is a productThis works:
p = tr[i].a + tr[i].b + tr[i].c;
p = p / 2.0;
s_2 = p * (p - tr[i].a) * (p - tr[i].b) *
(p - tr[i].c);
s[i] = sqrt(s_2);
As suggested by @Oka use functions.
Try simple things at first: the obvious triangle to test is 3,4,5 with area 6.
Look also into:
double s[n];
double p, s_2;
int u;
s[n]
is not in fact a C
thing. VLA
are seen mostly in use by students. Never in production code. Just allocate the array and free it at exit, since n
is a parameter.u
is a temporary place for swapping values in the sort function. Name it as save
or temp
or tmp
as everybodydouble
value, and s
is double[n]
, you can not save it in an int
value. u = s[j];
s[j] = s[j + 1];
s[j + 1] = u;
Since we want to display the triangles with the computed areas before and after sort, it may be better to save the area value inside Triangle
:.
typedef struct
{
int a;
int b;
int c;
double area;
} Triangle;
double Heron(Triangle*);
int show_triangle(Triangle*, const char* msg);
Heron
gets a Triangle
and returns its areashow_triangle
gets a Triangle
and an optional message and displays themtypedef struct
{
size_t capacity;
size_t size;
Triangle* T;
} Triangles;
Triangles* create_triangles(size_t size); //constructor
Triangles* destroy_triangles(Triangles*); // destructor
Triangles* get_triangles(const char* input);
int show_triangles(Triangles*, const char* msg);
create_triangles
returns an empty container --- C++
name for thisdestroy_triangles
does the expected.get_triangles
accepts a file name and returns a collection --- java
name for thisshow_triangles
uses show_triangle
and does the expectedint bubble_sort(Triangle* T, size_t size)
sorts the thing on area
valuemain
for the exampleWith this model main
can be more direct:
int main(void)
{
const char* in_file = "input.txt";
Triangles* coll = get_triangles(in_file);
show_triangles(coll, "***** before sort *****\n\n");
bubble_sort(coll->T, coll->size);
show_triangles(coll, "***** after sort *****\n\n");
destroy_triangles(coll);
return 0;
}
***** before sort *****
11 triangles in this set:
1 [ 3, 4, 5 A = 6.00]
2 [ 67, 67, 19 A = 630.07]
3 [ 3, 57, 55 A = 62.59]
4 [ 33, 33, 33 A = 471.55]
5 [ 61, 58, 59 A = 1522.35]
6 [ 23, 43, 35 A = 401.80]
7 [ 48, 42, 45 A = 869.02]
8 [ 23, 12, 27 A = 137.29]
9 [ 41, 34, 22 A = 373.86]
10 [ 26, 49, 35 A = 437.49]
11 [ 63, 46, 45 A = 1034.11]
***** after sort *****
11 triangles in this set:
1 [ 3, 4, 5 A = 6.00]
2 [ 3, 57, 55 A = 62.59]
3 [ 23, 12, 27 A = 137.29]
4 [ 41, 34, 22 A = 373.86]
5 [ 23, 43, 35 A = 401.80]
6 [ 26, 49, 35 A = 437.49]
7 [ 33, 33, 33 A = 471.55]
8 [ 67, 67, 19 A = 630.07]
9 [ 48, 42, 45 A = 869.02]
10 [ 63, 46, 45 A = 1034.11]
11 [ 61, 58, 59 A = 1522.35]
This is just a toy. Consider passing file name in the command line.
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#define itoa _itoa
typedef struct
{
int a;
int b;
int c;
double area;
} Triangle;
double Heron(Triangle*);
int show_triangle(Triangle*, const char* msg);
typedef struct
{
size_t capacity;
size_t size;
Triangle* T;
} Triangles;
Triangles* create_triangles(size_t size); //constructor
Triangles* destroy_triangles(Triangles*); // destructor
Triangles* get_triangles(const char* input);
int show_triangles(Triangles*, const char* msg);
void bubble_sort(Triangle* T, size_t size);
int main(void)
{
const char* in_file = "input.txt";
Triangles* coll = get_triangles(in_file);
show_triangles(coll, "\n\n***** before sort *****\n\n");
bubble_sort(coll->T, coll->size);
show_triangles(coll, "\n\n***** after sort *****\n\n");
destroy_triangles(coll);
return 0;
}
void bubble_sort(Triangle* T, size_t size)
{ // bubble sort
Triangle temp;
for (int i = 0; i < size - 1; i++)
{
for (int j = 0; j < (size - i - 1); j++)
{
if (T[j].area > T[j + 1].area)
{
temp = T[j];
T[j] = T[j + 1];
T[j + 1] = temp;
}
}
}
}
double Heron(Triangle* T)
{
double hp =
(T->a + T->b + T->c) / 2.; // half perimeter
return sqrt(
hp * (hp - T->a) * (hp - T->b) * (hp - T->c));
}
int show_triangle(Triangle* T, const char* msg)
{
if (T == NULL) return -1;
if (msg != NULL) printf("%s", msg);
printf(
"[%8d,%8d,%8d\tA = %10.2f] \n", T->a, T->b, T->c,
T->area);
return 0;
}
Triangles* create_triangles(size_t size)
{
// fprintf(stderr, "%llu triangles\n", size);
Triangles* set = malloc(sizeof(Triangles));
if (set == NULL) return NULL;
set->capacity = size;
set->size = 0; // now empty
// allocate array of triangles
set->T = malloc(size * sizeof(Triangle));
if (set->T == NULL)
{
free(set);
return NULL;
}
return set;
}
Triangles* destroy_triangles(Triangles* S)
{
if (S == NULL) return NULL;
free(S->T);
free(S);
return NULL;
}
Triangles* get_triangles(const char* input)
{
FILE* in = fopen(input, "r");
if (in == NULL) return NULL;
char line[256];
char* p = fgets(line, sizeof(line), in);
if (p == NULL)
{
fclose(in);
return NULL;
}
size_t size = atol(line);
if (size == 0)
{
fclose(in);
return NULL;
}
//fprintf(stderr, "%llu triangles\n", size);
if (size > 1000) size = 1000; // some limit
Triangles* set = create_triangles(size);
if (set == NULL)
{
fclose(in);
return NULL;
}
for (int i = 0, res = 0; i < size; i += 1)
{
res = fscanf(
in, "%d%d%d", &set->T[i].a, &set->T[i].b,
&set->T[i].c);
if (res != 3)
{
destroy_triangles(set);
fclose(in);
return NULL;
}
set->T[i].area = Heron(&set->T[i]);
set->size = size;
}; // for
fclose(in);
return set;
}
int show_triangles(Triangles* S, const char* msg)
{
if (S == NULL) return -1;
if (msg != NULL) printf("%s", msg);
printf("%llu triangles in this set:\n\n", S->size);
for (size_t i = 0; i < S->size; i += 1)
{
// get the index rigth justified
char num[20];
itoa((int)(1+i), num, 10);
char str[20];
sprintf(str, " %-4s", num);
show_triangle(&(S->T[i]), str);
}
return 0;
}
HIH
Upvotes: 1
Reputation: 26375
Your calculation for Heron's formula is incorrect, because in
p*(p-tr[i].a)+(p-tr[i].b)+(p-tr[i].c)
you are first multiplying the semiperimeter, p
, by p - tr[i].a
, and then adding it to the result of (p - tr[i].b) + (p - tr[i].c)
.
See: Operator precedence.
Instead, you are looking for the product of the semiperimeter and each side subtracted from the semiperimeter:
p * (p - a) * (p - b) * (p - c)
Variables for each side simplified.
The first thing to do is simplify your code by writing functions that do one thing, and do it well
static double herons(const struct triangle *t)
{
double p = (t->a + t->b + t->c) / 2.0;
return sqrt(p * (p - t->a) * (p - t->b) * (p - t->c));
}
and test those functions in isolation
#include <stdio.h>
#include <math.h>
struct triangle {
int a;
int b;
int c;
};
static double herons(const struct triangle *t)
{
double p = (t->a + t->b + t->c) / 2.0;
return sqrt(p * (p - t->a) * (p - t->b) * (p - t->c));
}
int main(void)
{
struct triangle tr[] = {
{ 67, 67, 19 },
{ 3 , 57, 55 },
{ 33, 33, 33 },
{ 61, 58, 59 },
{ 23, 43, 35 },
{ 48, 42, 45 },
{ 23, 12, 27 },
{ 41, 34, 22 },
{ 26, 49, 35 },
{ 63, 46, 45 }
};
for (size_t i = 0; i < (sizeof tr / sizeof *tr); i++)
printf("%2d %2d %2d herons(%.5f)\n", tr[i].a, tr[i].b, tr[i].c, herons(tr + i));
}
to see if the results are what you expect
67 67 19 herons(630.06919)
3 57 55 herons(62.58744)
33 33 33 herons(471.55083)
61 58 59 herons(1522.35344)
23 43 35 herons(401.79869)
48 42 45 herons(869.02154)
23 12 27 herons(137.28802)
41 34 22 herons(373.85952)
26 49 35 herons(437.49286)
63 46 45 herons(1034.10638)
and then move on from there.
For sorting, it may be easier to start with the vetted library function qsort
, and a naive approach of recalculating areas at each step
static int compare_by_area(const void *a, const void *b)
{
double l = herons(a);
double r = herons(b);
return (l > r) - (l < r);
}
qsort(tr, sizeof tr / sizeof *tr, sizeof *tr, compare_by_area);
Example usage, with the array defined above.
which when plugged into the previously tested example, gives a good looking result:
3 57 55 herons(62.58744)
23 12 27 herons(137.28802)
41 34 22 herons(373.85952)
23 43 35 herons(401.79869)
26 49 35 herons(437.49286)
33 33 33 herons(471.55083)
67 67 19 herons(630.06919)
48 42 45 herons(869.02154)
63 46 45 herons(1034.10638)
61 58 59 herons(1522.35344)
After you have a working baseline, start to modify just one part of the program. Here is a modified version of your sort function, using the naive approach of recalculating the areas at each step.
static void sort_by_area(struct triangle *tr, int n)
{
for (int i = 0; i < (n - 1); i++) {
for (int j = 0; j < (n - i - 1); j++) {
if (herons(tr + j) > herons(tr + j + 1)) {
struct triangle v = tr[j];
tr[j]= tr[j + 1];
tr[j + 1] = v;
}
}
}
}
If we test this, it gives the expected results. So we move on to caching the results
static void sort_by_area(struct triangle *tr, int n)
{
double s[n];
for (int i = 0; i < n; i++)
s[i] = herons(tr + i);
for (int i = 0; i < (n - 1); i++) {
for (int j = 0; j < (n - i - 1); j++) {
if (s[j] > s[j + 1]) {
double u = s[j];
s[j] = s[j+1];
s[j+1] = u;
struct triangle v = tr[j];
tr[j]= tr[j + 1];
tr[j + 1] = v;
}
}
}
}
which, unsurprisingly, still works because it is built on previously tested code.
Next: benchmark this sort against the naive qsort
approach.
Note: Generally, prefer size_t
to int
when dealing with object sizes (including array lengths).
Note: The VLA double s[n];
will fail for non-positive values of n
, or if n
causes the object to exhaust available (stack) memory.
Note: int u;
as the temporary variable for the double
swap in your code truncates the left-hand side of the comparison on every swap. This was not a problem when using your data, but would have been if given a data set containing two (or more) floating-point values that convert to the same integer value. A more aggressive warning level from your compiler should highlight this (gcc/clang: -Wconversion
[-Wfloat-conversion
]).
Upvotes: 3