Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

Dynamic String Arrays in 3D

3.86/5 (4 votes)
12 Feb 2011CPOL3 min read 31.4K   155  
This article describes creating and managing 1D, 2D and 3D string arrays

Introduction

Ok, intro right... I haven't written an article for a long time now, so don't know about my writing skills still present, anyways...

This little paper is about multi-dimensional string arrays. Strings only, as there are already tons of tips about integers arrays, but not much info about this article's subject, and, completely no ready to use code, which you can just copy and paste to your project, so here we go. So, yeah, I am not going to write a long technical story about (!VERY IMPORTANT!) memory management and other things - I'll just give you a class, which you can just use and that's it. However, I am not stating that it is memory leaks proof, there maybe there will be bugs and stuff, but that is why you got the source to play with.

All I'll say about memory management in C is that: if you allocate memory - free it. Everywhere. Else memory leaks are guaranteed. So, I've told you that I will give you a class. Well, I call the whole thing a class, but this class actually is just a wrapper class for regular C functions. And, there is additional functionality, provided by my class - user can supply his own pointers. What for? Well, you can push your pointer from different languages for example.

Consider such a situation:

C++
wchar_t *test()
{
	static wchar_t *a = (wchar_t *)malloc(MAX_PATH);
	wcscpy(a, L"something");
	return a;
}

and this:

C++
void test(wchar_t *&pointer)
{
	pointer = (wchar_t *)malloc(MAX_PATH);
	wcscpy(pointer, L"something");
}

So, you can push your pointer, make class manage it, and then you can, on your own, do anything you like with it. That above example is kinda silly, but I am talking here about 3D arrays, where all this may become a little bit tricky.

Another thing, you may ask, why have I used good old C when there is C++ with all its benefits. First of all, because this class (ArrayWorker) works with native types: char and wchar_t, so once again, any output you'll get from it - you can easily share between different languages, like C and C# for example. So when I took a look at C++ and how many conversions I would need to make, I decided to focus on C.

And the last thing. ArrayWorker is arranged in a way that you are not limited to just 3D, you can make it 100D - it's all about adding one more level of indirection.

Background

So, what is string array anyway? You probably know that. What is multi-dimensional array? You probably know that too.

C++
wchar_t *String = L"some string";
wchar_t *Array1D[2] = {L"some", L"string"};
wchar_t *Array2D[2][2] =
{
	{L"some", L"String"},
	{L"some other", L"string"}
}
wchar_t *Array3D[2][2][2] =
{
	{
		{L"some", L"string"},
		{L"nice", L"string"}
	},
	{
		{L"another", L"String"},
		{L"just cut", L"this already"}
	}
}

And so on. These are static arrays - pretty much easy stuff. 1D Array is just like a strings container, 2D array is much like a table and 3D array is almost like a database. And now, all we need to do, is to create such arrays, but dynamic arrays, with dynamic memory allocation, reallocation, deallocation, removing strings, replacing strings, adding strings and so on. One can easily get lost in bytes and give up using StringBuilder in C#, but not us, comrades, we are proceeding forward.

Using the Code

Here is what we got for now.

C++
// ArrayWorker::_1D, public methods:
public:
_1D(IN BOOL UserArray = FALSE);	// if you want to supply own pointer, use TRUE
~_1D();
// if you want to supply own pointer, you have to initialize it
VOID InitUserArray(IN wchar_t **&uArray);
BOOL Append(IN std::string Temp);
// if you are using your own pointer, you have to add it to every public method
// std::wstring and std::string will be converted to wchar_t, like char as well
BOOL Append(IN std::string Temp, IN OUT wchar_t **&uArray);
BOOL Append(IN std::wstring Temp);
BOOL Append(IN std::wstring Temp, IN OUT wchar_t **&uArray);
BOOL Append(IN char *String);
BOOL Append(IN char *String, IN OUT wchar_t **&uArray);
BOOL Append(__in wchar_t *String);
BOOL Append(__in wchar_t *String, IN OUT wchar_t **&uArray);
unsigned int Length(VOID);	// get a length of all strings together
unsigned int Length(IN wchar_t **uArray, IN int count = -1);	// get length of 
							// your own array
// usage: ToArray("one,two,three", ",");
// string will be parsed and each element will be added to array
void ToArray(IN char *String, IN char *Delimiter);
// usage: wchar_t **myarray; ToArray("one,two,three", ",", myarray);
void ToArray(IN char *String, IN char *Delimiter, IN OUT wchar_t **&uArray);
void ToArray(IN wchar_t *String, IN wchar_t *Delimiter);
void ToArray(IN wchar_t *String, IN wchar_t *Delimiter, IN OUT wchar_t **&uArray);
// ansi to unicode conversion; just a helper function
wchar_t * AStringToW(IN char * pstr);
// unicode to ansi conversion; just a helper function
char * WStringToA(IN wchar_t * pstr);
BOOL NullOrEmpty(IN wchar_t *String); // like you got in C#, helper function
BOOL NullOrEmpty(IN char *String);
// Usage: ToString(",");
// returns: one,two,three
char * ToString(IN char *Delimiter); // converts array to a string, 
			// where elements are separated with provided delimiter
char * ToString(IN char *Delimiter, IN wchar_t **uArray, IN int Elems = -1);
wchar_t * ToString(IN wchar_t *Delimiter);
wchar_t * ToString(IN wchar_t *Delimiter, IN wchar_t **uArray, IN int Elems = -1);
// replaces element in array with other element
// if replAtIndex provided, replaces at specific position only
// else can replace all instances of same string
// usage: Replace("something", "nothing", FALSE/*complete string is known*/, 
// TRUE/*wipe out every "something" strings*/, -1);
BOOL Replace(IN char *Search, IN char *Replace,
						  IN BOOL isSearchPartial = FALSE,
						  IN BOOL replaceAll = FALSE,
						  IN int replAtIndex = -1);
// same like above, but you can provide your own array
BOOL Replace(IN char *Search, IN char *Replace,
						  IN OUT wchar_t **&uArray,
						  IN int Elems = -1,
						  IN BOOL isSearchPartial = FALSE,
						  IN BOOL replaceAll = FALSE,
						  IN int replAtIndex = -1);
// unicode version
BOOL Replace(IN wchar_t *Search,IN wchar_t *Replace,
						  IN BOOL isSearchPartial = FALSE,
						  IN BOOL replaceAll = FALSE,
						  IN int replAtIndex = -1);
BOOL Replace(IN wchar_t *Search,IN wchar_t *Replace,
						  IN OUT wchar_t **&uArray,
						  IN int Elems = -1,
						  IN BOOL isSearchPartial = FALSE,
						  IN BOOL replaceAll = FALSE,
						  IN int replAtIndex = -1);
// get string at index
wchar_t * GetAtIndex(IN int Index);
wchar_t * GetAtIndex(IN int Index, IN wchar_t **uArray);
char * GetAtIndexA(IN int Index); // ansi version
char * GetAtIndexA(IN int Index, IN wchar_t **uArray);

// this one is like replace, but it removes string
BOOL Remove(IN wchar_t *Item = NULL,
						IN int Index = -1,
						IN BOOL isItemPartial = FALSE,
						IN BOOL removeAll = FALSE);
BOOL Remove(IN wchar_t **&uArray,
						IN int Elems = -1,
						IN wchar_t *Item = NULL,
						IN int Index = -1,
						IN BOOL isItemPartial = FALSE,
						IN BOOL removeAll = FALSE);

// this will return complete array
// usage: wchar_t **arr = ar->GetArray(); wprintf(L"%s %s %s", arr[0], arr[1], arr[2]);
wchar_t **GetArrayW();

int Count(VOID);	// counts array elements
int Count(IN wchar_t **uArray);
void Clear(VOID);	// clears array, so now it has 0 elements and is reusable
void Clear(IN OUT wchar_t **&uArray, IN OUT int Elems = -1);
void Destroy(VOID);	// destroys array, its instance is not usable anymore
void Destroy(IN OUT wchar_t **&uArray, IN OUT int Elems = -1);	

That's for 1D array. Here we go with 2D and 3D arrays.

C++
// ArrayWorker::_2D public methods
// only unicode is supported

_2D(IN BOOL UserTable = FALSE);
~_2D();
void InitUserTable(IN OUT wchar_t ***&uTable); 	// you are supplying your 
						// own pointer "uTable"
void AppendRow(IN wchar_t **Row);	// add row to a table = add 1D array to 2D array
void AppendRow(IN wchar_t **Row, IN OUT wchar_t ***&uTable);
void ReplaceRow(IN wchar_t **NewRow, IN int Index);
void ReplaceRow(IN wchar_t **NewRow, IN int Index, IN OUT wchar_t ***&uTable);
void RemoveRow(IN int Index);
void RemoveRow(IN int Index, IN OUT wchar_t ***&uTable);
unsigned int Size();
unsigned int Size(IN wchar_t ***uTable);
wchar_t ***GetTable();
int CountRows();
int CountRows(IN wchar_t ***uTable);
void Clear(VOID);
void Clear(IN OUT wchar_t ***&uTable, IN int Elems = -1);
void Destroy(VOID);
void Destroy(IN OUT wchar_t ***&uTable, IN int Elems = -1);
// ^ methods names speak for themselfs, right?
// 3D array has almost the same methods, the only difference is level of indirection
// based on such construction, you can extend dimensions of array.
// ArrayWorker::_3D public methods
_3D(IN BOOL UserDb = FALSE);
~_3D();
void InitUserDb(IN OUT wchar_t ****&uDb);	// uDb is your pointer
void AppendTable(IN wchar_t ***Tab);
void AppendTable(IN wchar_t ***Tab, IN OUT wchar_t ****&uDb);
void ReplaceTable(IN wchar_t ***NewTable, IN int Index);
void ReplaceTable(IN wchar_t ***NewTable, IN int Index, IN OUT wchar_t ****&uDb);
void RemoveTable(IN int Index);
void RemoveTable(IN int Index, IN OUT wchar_t ****&uDb);
wchar_t ****GetDataBase();	// returns 3D array, db->GetDataBase()[table][row][cell];
unsigned int Size();
unsigned int Size(IN wchar_t ****uDb);
int CountTables();
int CountTables(IN wchar_t ****uDb);
void Clear(VOID);
void Clear(IN OUT wchar_t ****&uDb, IN int Elems = -1);
void Destroy(VOID);
void Destroy(IN OUT wchar_t ****&uDb, IN int Elems = -1);

That's it. As you can see, there are overloads, like for Clear method. So, this "uArray", "uTable", and "uDb" are your pointers. Another thing, sometimes, there are params like: IN int Elems = -1 - here is an explanation. Basically, while adding elements, class counts them, but for example, if you are supplying your own array and you have made own counts, so you can supply them too. Almost in 100% this param is unused.

Alright, let's test it.

C++
void Array1DTest(int loops)	// 100.000 loops
{
	ArrayWorker::_1D *row1 = new ArrayWorker::_1D();
	std::string stdtest = "somestd";
	std::wstring stdwstr = L"unicode";
	for(int i = 0; i <= loops; i++)
	{
		wprintf(L"Lopp nr: %d\n", i);
		row1->Append(stdtest);
		row1->Append(stdwstr);
		row1->Append("char");
		row1->Append(L"wchar");
		int len = row1->Length();
		char *tempstr = row1->ToString(",");
		row1->ToArray(tempstr, ",");
		wchar_t *wtempstr = row1->ToString(L",");
		row1->ToArray(wtempstr, L",");
		row1->Replace("char", 

			"newchar1111111111111111111111111111111111111111x");
		row1->Replace(L"wchar", 
			L"newwchar2222222222222222222222222222222222222222222222z");
		row1->Remove(NULL, 0);
		row1->Remove(NULL, 0);
		free(tempstr);
		free(wtempstr);
		if(i < loops)row1->Clear();
	}
	wchar_t **myrow = row1->GetArrayW();
	wprintf(L"\r\n%s | %s\r\n", myrow[0], myrow[1]); // newchar and newwchar
	delete row1;
}

Array 1D Test

C++
void Array2DTest(int loops)	// 100.000 loops
{
	ArrayWorker::_2D *Table = new ArrayWorker::_2D();
	wchar_t *testrow[] = {L"some", L"row", L"here", L"man"};
	wchar_t *somerow[] = { L"well", L"done", L"my", L"friend"};
	wchar_t *nicerow[] = { L"looks", L"like", L"its", L"done" };
	for(int i = 0; i <= loops; i++)
	{
		wprintf(L"Loop nr: %d\n", i);
		Table->AppendRow(testrow);
		Table->AppendRow(somerow);
		Table->RemoveRow(0);
		Table->ReplaceRow(nicerow, 0);
		if(i < loops)Table->Clear();
	}
	wchar_t ***tab = Table->GetTable();
	wprintf(L"row %d: %s %s %s %s\r\n", loops,
		tab[0][0], tab[0][1], tab[0][2], tab[0][3]);
	delete Table;
}

Array 2D Test

Ok here too.

C++
void Array3DTest(int loops)	// 100.000 loops
{
	while(loops >= 0)
	{
	ArrayWorker::_1D *row1 = new ArrayWorker::_1D();
	ArrayWorker::_1D *row2 = new ArrayWorker::_1D();

	ArrayWorker::_1D *row3 = new ArrayWorker::_1D();
	ArrayWorker::_1D *row4 = new ArrayWorker::_1D();

	row1->Append("nice");
	row1->Append("stuff");

	row2->Append("some");
	row2->Append("thing");

	row3->Append("another");
	row3->Append("thing");

	row4->Append("whatever");
	row4->Append("boy");

	ArrayWorker::_2D *table1 = new ArrayWorker::_2D();
	ArrayWorker::_2D *table2 = new ArrayWorker::_2D();
	table1->AppendRow(row1->GetArrayW());
	table1->AppendRow(row2->GetArrayW());

	table2->AppendRow(row3->GetArrayW());
	table2->AppendRow(row4->GetArrayW());

	ArrayWorker::_3D *db = new ArrayWorker::_3D();
	db->AppendTable(table1->GetTable());
	db->AppendTable(table2->GetTable());

	wchar_t ****dat = db->GetDataBase();
	wprintf(L"Table1: \n%s %s \n%s %s\n\nTable2: \n%s %s\n%s %s\n\n",
		dat[0][0][0], dat[0][0][1], dat[0][1][0], dat[0][1][1],
		dat[1][0][0], dat[1][0][1], dat[1][1][0], dat[1][1][1]);

	delete db;

	delete table2;
	delete table1;

	delete row4;
	delete row3;
	delete row2;
	delete row1;

	loops--;
	wprintf(L"Loop left: %d\n", loops);
	}
	wprintf(L"DONE!\n");
}

Array 3D Test

Looks like it's all ok, no memory leaks and stuff. Anyway, there were no tests done with supplied user arrays. The complete ArrayWorker class source code is attached to this article, however, it does not include testing functions. Cheers, csrss.

Points of Interest

Writing this class, I have learnt memory management probably in 100%. Also I would like to thank a CodeProject user, Andrew Brock, who clarified a few things to me, while answering my questions in CP forums.

History

  • 11th February, 2011: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)