I wrote a C program that builds a social network graph of characters in a novel. The program takes two text files as input: a list of the names of the characters in the novel, and the file of the novel itself. The output is a list of name pairs, representing times where those two names occurred within five lines of each other in the text. A graph visualisation program like Gephi can use this data to build a social network graph, where the strength of the relationship between two characters is determined by the number of times their names co-occurred.
Any feedback on my implementation would be much appreciated. In particular I would like feedback on the find_co_occurrences function, since I feel like it could be more readable, but I'm not sure how to go about achieving that.
/*
* Name: SocialNetwork.c
* Author: Cathal W.
*
* Description:
*
* Program to generate a list of co-occurrences of characters in a novel.
* This can be used by the Gephi program to generate a social network
* graph displaying the relationships between the characters.
*
* A co-occurrence is defined as an occurrence of one character's name,
* followed by the other character's name within the next 5 lines.
*
* Two files are required as input: a names file and a novel file.
* The names file contains the names of each character, one on a new line.
* e.g.
* "Valjean
* Gavroche
* Marius"
*
* The novel file contains the entire text of the novel being analysed.
*
* The output produced is a .csv file with a list of co-occurrences in the
* following format:
* ""Valjean" "Gavroche"
* "Valjean" "Marius"
* "Gavroche" "Marius""
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_NAMES 200
#define MAX_NAME_LEN 80
#define MAX_LINE_LEN 300
#define THRESHOLD 5 // co-occurrence threshold (in number of lines)
struct node {
/* Node for linked list. */
int val;
struct node *next;
};
struct name_data {
/* Stores the array of names, and linked lists of name occurrences. */
char names[MAX_NAMES][MAX_NAME_LEN];
struct node* heads[MAX_NAMES]; // the head nodes of each of the linked lists
struct node* currs[MAX_NAMES]; // the current nodes of each of the linked lists
int n_names;
};
const char *NAMES_FILE = "./InputFiles/Les-Mis-Names.txt";
const char *NOVEL_FILE = "./InputFiles/Les-Mis-Full-Text.txt";
const char *OUTPUT_FILE = "./co-occurences.csv";
void read_names(struct name_data *name_data, const char *names_file);
void add_occurrence(int line_num, int name_id, struct name_data *name_data);
void find_occurrences(struct name_data *name_data, const char *novel_file);
void find_co_occurrences(struct name_data *name_data, const char *output_file);
int main(void) {
struct name_data *name_data = (struct name_data*)malloc(sizeof(struct name_data));
read_names(name_data, NAMES_FILE);
find_occurrences(name_data, NOVEL_FILE);
find_co_occurrences(name_data, OUTPUT_FILE);
return 0;
}
void read_names(struct name_data *name_data, const char *names_file) {
/*
* Read in the names from the file.
*
* Stores the array of names in name_data. If the names_file cannot be
* opened, this function exits the program.
*/
int i = 0;
FILE *fp = fopen(names_file, "r+");
if (fp == NULL) {
perror("Error opening names file");
exit(-1);
}
while (fscanf(fp, "%s", name_data->names[i++]) != EOF); // read the names
name_data->n_names = i-1;
fclose(fp);
}
void add_occurrence(int line_num, int name_id, struct name_data *name_data) {
/*
* Record the line number of an occurrence of a particular name.
*
* Adds the line_num to the linked list corresponding to this name (denoted
* by name_id). If the linked list is empty, it first initialises it.
*/
struct node *ptr = (struct node*)malloc(sizeof(struct node));
ptr->val = line_num;
ptr->next = NULL;
if (name_data->heads[name_id] == NULL) { // if the list is uninitialised
name_data->heads[name_id] = name_data->currs[name_id] = ptr;
}
else {
name_data->currs[name_id]->next = ptr;
name_data->currs[name_id] = ptr;
}
}
void find_occurrences(struct name_data *name_data, const char *novel_file) {
/*
* For each name, record a linked list of the line numbers of its occurrences
* in the novel.
*
* This function assumes that the names array in name_data has already been
* generated.
* If the novel_file cannot be opened, this function exits the program.
*/
int line_num = 0, name_id;
char *name, line[MAX_LINE_LEN];
FILE *fp = fopen(novel_file, "r+");
if (fp == NULL) {
perror("Error opening novel file");
exit(-1);
}
while (fgets(line, MAX_LINE_LEN, fp) != NULL) {
for (name_id = 0; name_id < name_data->n_names; name_id++) {
name = name_data->names[name_id];
/* if the name occurs in this line */
if (strstr(line, name) != NULL) {
add_occurrence(line_num, name_id, name_data);
}
}
line_num++;
}
fclose(fp);
}
void find_co_occurrences(struct name_data *name_data, const char *output_file) {
/*
* Generate and output a list of co-occurrences of names in the text.
*
* This function assumes that the linked lists in name_data have already
* been generated.
* If the output_file cannot be opened, this function exits the program.
*/
int i, j;
struct node *ptr1, *ptr2;
FILE *fp = fopen(output_file, "w+");
if (fp == NULL) {
perror("Error opening output file");
exit(-1);
}
/* get every pair of names */
for (i = 0; i < name_data->n_names; i++) {
for (j = i+1; j < name_data->n_names; j++) {
/* then find their co-occurrences */
ptr1 = name_data->heads[i];
while (ptr1 != NULL) {
ptr2 = name_data->heads[j];
while (ptr2 != NULL) {
if (abs(ptr2->val - ptr1->val) <= THRESHOLD) {
/* output the co-occurrence */
fprintf(fp, "\"%s\"\t\"%s\"\n", name_data->names[i], name_data->names[j]);
}
ptr2 = ptr2->next;
}
ptr1 = ptr1->next;
}
}
}
}