Arduino split Strings and get stringparts

Hi guys,

I have written a little useful function for getting stringparts by delivering a SplitChar. Some equivalent funtions in C# and C++ are already existing, but in Arduino's C it's missing.

I need it for one thing in one of my projects and i just want to share it to the Arduino comunity. No question, it's just to find if everyone searched for something like that.

best regards

String TestString = "123!456!789!abc!def!xyz!";

void setup()
{
	String StringSplits[10];
	Serial.begin(19200);
	Serial.print("String to split: ");
	Serial.println(TestString);
	
	delay(3000);	//3secs to see what you want to split ;)

	for (int i = 0; i < 10; i++)	//this for loop runs 10 times, bus just the number of parts which are possible will be saved in StringSplits[i]
	{
		StringSplits[i] = GetStringPartAtSpecificIndex(TestString, '!', i);	//give this function your string to split, your char to split and the index of the StringPart you want to get
		Serial.print("Part ");
		Serial.print(i);
		Serial.print(":");
		Serial.println(StringSplits[i]);	//see it on serial monitor
	}
	Serial.println("Done!");

}

void loop()
{

  //main stuff here, not needed in this example

}


/*
Get StringPart by splitting on an specific char
example:
============
"123!456!789!abc!def!xyz!"

SplitChar is "!"

there are 6 Parts (from Index 0 to 5) to get:
Part 0:123
Part 1:456
Part 2:789
Part 3:abc
Part 4:def
Part 5:xyz
everytime a "!" have to stay after the part you want to get
*/
String GetStringPartAtSpecificIndex(String StringToSplit, char SplitChar, int StringPartIndex)
{
	String originallyString = StringToSplit;
	String outString = "";
	for (int i1 = 0; i1 <= StringPartIndex; i1++)
	{
		outString = "";										//if the for loop starts again reset the outString (in this case other part of the String is needed to take out)
		int SplitIndex = StringToSplit.indexOf(SplitChar);	//set the SplitIndex with the position of the SplitChar in StringToSplit

		if (SplitIndex == -1)								//is true, if no Char is found at the given Index
		{
			//outString += "Error in GetStringPartAtSpecificIndex: No SplitChar found at String '" + originallyString + "' since StringPart '" + (i1-1) + "'";		//just to find Errors
			return outString;
		}
		for (int i2 = 0; i2 < SplitIndex; i2++)
		{
			outString += StringToSplit.charAt(i2);			//write the char at Position 0 of StringToSplit to outString
		}
		StringToSplit = StringToSplit.substring(StringToSplit.indexOf(SplitChar) + 1);	//change the String to the Substring starting at the position+1 where last SplitChar found
	}
	return outString;
}

Too bad the String class itself is absolute poison for the Arduino.

Using the String class usually results in a code size that is larger than necessary. For example, your sample program compiled to 4916 bytes. The following program which is similar, but uses C strings instead of the String class, compiled to 3354 bytes:

char TestString[] = "123!456!789!abc!def!xyz!";

void setup()
{
  char *StringSplits[10];
  int i = 0;
  
  Serial.begin(9600);
  Serial.print("String to split: ");
  Serial.println(TestString);
  
  GetStringPartAtSpecificIndex(TestString, "!", StringSplits); //give this function your string to split, your char to split and the index of the StringPart you want to get

  while (StringSplits[i] != NULL) //this for loop runs 10 times, bus just the number of parts which are possible will be saved in StringSplits[i]
  {
    Serial.print("Part ");
    Serial.print(i);
    Serial.print(":  ");
    Serial.println(StringSplits[i++]);  //see it on serial monitor
  }
  Serial.println("Done!");
}

void loop()
{
}

String GetStringPartAtSpecificIndex(char *StringToSplit, char *SplitChar, char *StringPartIndex[])
{
  char *ptr;
  int i = 0;
  
  ptr = strtok(StringToSplit, SplitChar);
  while (ptr) {
    StringPartIndex[i++] = ptr;
    ptr = strtok(NULL, SplitChar);
  }
  StringPartIndex[i] = NULL;   // Just to mark the end of the list
}

The function is useful but not correct as it does not handle all conditions correctly.

I added 2 testcases to the teststring, the empty part and the dangling end. The latter is not handled well.

TestString = "123!456!789!abc!def!xyz!!end";

Furthermore the performance grows worse for "higher" indices as all kind of intermediate Strings are created, only to be thrown away.

Please compare your version with the version2 below(added timing). Did not compare the footprint.

//
//    FILE: StringSplit.ino
//  AUTHOR: Rob Tillaart
// VERSION: 0.1.00
// PURPOSE: demo
//    DATE: 2016-01-24
//     URL: http://forum.arduino.cc/index.php?topic=374234
//
// Released to the public domain
//

uint32_t start;
uint32_t stop;

String TestString = "123!456!789!abc!def!xyz!!xxx!end";
String StringSplits[10];

void setup()
{
  Serial.begin(115200);
  Serial.print("Start ");
  Serial.println(__FILE__);

  Serial.print("String to split: ");
  Serial.println(TestString);

  delay(1000);
  for (int i = 0; i < 10; i++)
  {
    start = micros();
    StringSplits[i] = GetStringPartAtSpecificIndex(TestString, '!', i);
    stop = micros();
    Serial.print(stop - start);
    Serial.print("\tPart ");
    Serial.print(i);
    Serial.print("\t:");
    Serial.println(StringSplits[i]); //see it on serial monitor
  }
  Serial.println("Done 1!");

  delay(1000);
  for (int i = 0; i < 10; i++)
  {
    start = micros();
    StringSplits[i] = GetStringPartAtSpecificIndex2(TestString, '!', i);
    stop = micros();
    Serial.print(stop - start);
    Serial.print("\tPart ");
    Serial.print(i);
    Serial.print("\t: ");
    Serial.println(StringSplits[i]); //see it on serial monitor
  }
  Serial.println("Done 2!");
}

void loop()
{
}


String GetStringPartAtSpecificIndex(String StringToSplit, char SplitChar, int StringPartIndex)
{
  String originallyString = StringToSplit;
  String outString = "";
  for (int i1 = 0; i1 <= StringPartIndex; i1++)
  {
    outString = ""; //if the for loop starts again reset the outString (in this case other part of the String is needed to take out)
    int SplitIndex = StringToSplit.indexOf(SplitChar); //set the SplitIndex with the position of the SplitChar in StringToSplit

    if (SplitIndex == -1) //is true, if no Char is found at the given Index
    {
      //outString += "Error in GetStringPartAtSpecificIndex: No SplitChar found at String '" + originallyString + "' since StringPart '" + (i1-1) + "'"; //just to find Errors
      return outString;
    }
    for (int i2 = 0; i2 < SplitIndex; i2++)
    {
      outString += StringToSplit.charAt(i2); //write the char at Position 0 of StringToSplit to outString
    }
    StringToSplit = StringToSplit.substring(StringToSplit.indexOf(SplitChar) + 1); //change the String to the Substring starting at the position+1 where last SplitChar found
  }
  return outString;
}


String GetStringPartAtSpecificIndex2(String StringToSplit, char SplitChar, int StringPartIndex)
{
  int cnt = 0;
  int pos = 0;
  for (int il = 0; il < StringToSplit.length(); il++)
  {
    if (cnt == StringPartIndex)
    {
      int bpos = pos;
      while (pos < StringToSplit.length() && StringToSplit[pos] != SplitChar) pos++;
      return StringToSplit.substring(bpos, pos);
    }
    if (StringToSplit[il] == SplitChar)
    {
      pos = il + 1;
      cnt++;
    }
  }
  return "";
}

think it can be tweaked even more.

@robtillaart: I got 5608 when I compiled it.

@econjack: the whole sketch with both functions in it, or only one?

Ok squeezed a few bytes out

//
//    FILE: StringSplit.ino
//  AUTHOR: Rob Tillaart
// VERSION: 0.1.01
// PURPOSE: demo
//    DATE: 2016-01-24
//     URL: http://forum.arduino.cc/index.php?topic=374234
//
// Released to the public domain
//

uint32_t start;
uint32_t stop;

String TestString = "123!456!789!abc!def!xyz!!xxx!end";
String StringSplits[10];

void setup()
{
  Serial.begin(115200);
  Serial.print("Start ");
  Serial.println(__FILE__);

  Serial.print("String to split: ");
  Serial.println(TestString);

  delay(1000);
  for (int i = 0; i < 10; i++)
  {
    start = micros();
    StringSplits[i] = GetStringPartAtSpecificIndex(TestString, '!', i);
    stop = micros();
    Serial.print(stop - start);
    Serial.print("\tPart ");
    Serial.print(i);
    Serial.print("\t: ");
    Serial.println(StringSplits[i]); //see it on serial monitor
  }
  Serial.println("Done 1!");

  delay(1000);
  for (int i = 0; i < 10; i++)
  {
    start = micros();
    StringSplits[i] = GetStringPartAtSpecificIndex2(TestString, '!', i);
    stop = micros();
    Serial.print(stop - start);
    Serial.print("\tPart ");
    Serial.print(i);
    Serial.print("\t: ");
    Serial.println(StringSplits[i]); //see it on serial monitor
  }
  Serial.println("Done 2!");
}

void loop()
{
}


String GetStringPartAtSpecificIndex(String StringToSplit, char SplitChar, int StringPartIndex)
{
  String originallyString = StringToSplit;
  String outString = "";
  for (int i1 = 0; i1 <= StringPartIndex; i1++)
  {
    outString = ""; //if the for loop starts again reset the outString (in this case other part of the String is needed to take out)
    int SplitIndex = StringToSplit.indexOf(SplitChar); //set the SplitIndex with the position of the SplitChar in StringToSplit

    if (SplitIndex == -1) //is true, if no Char is found at the given Index
    {
      //outString += "Error in GetStringPartAtSpecificIndex: No SplitChar found at String '" + originallyString + "' since StringPart '" + (i1-1) + "'"; //just to find Errors
      return outString;
    }
    for (int i2 = 0; i2 < SplitIndex; i2++)
    {
      outString += StringToSplit.charAt(i2); //write the char at Position 0 of StringToSplit to outString
    }
    StringToSplit = StringToSplit.substring(StringToSplit.indexOf(SplitChar) + 1); //change the String to the Substring starting at the position+1 where last SplitChar found
  }
  return outString;
}


String GetStringPartAtSpecificIndex2(String StringToSplit, char SplitChar, int StringPartIndex)
{
  byte cnt = 0;
  for (int il = 0; il < StringToSplit.length(); il++)
  {
    if (cnt == StringPartIndex)
    {
      byte beginpos = il;
      byte pos = il;
      while (pos < StringToSplit.length() && StringToSplit[pos] != SplitChar) pos++;
      return StringToSplit.substring(beginpos, pos);
    }
    if (StringToSplit[il] == SplitChar)
    {
      cnt++;
    }
  }
  return "";
}

IDE 1.5.8 - 5,662 bytes

Almost 6K of code to do what strtok can do in a few lines? Am I missing something here?

Regards,
Ray L.