Finding and Replacing the First or Last Substring in a String

search-replace-graphicI found this image on http://search-replace.fr.tc/ using Google Image search.

Today, I found myself in a position where I needed to find a substring within a larger string and then replace the last occurrence of that substring. I started out using the str_ireplace function, but quickly realized there was no way to add an offset or a limit to that function. I then started searching the Internet for a solution. I came across a handful of places where someone had asked to replace the first instance of the substring, but all of the answers seemed to recommend using the preg_replace function (which allows you to set a limit).

With no good answer, I decided to set out and build my own function to do this. I have not tested the performance of this function to determine whether it’s slower or faster than using preg_replace, but I would imagine it’s probably faster (I’d love for someone to benchmark the comparison using a variety of string lengths to find out for sure, though).

Following is the function I came up with to replace the last instance of the string.

function str_replaceLast($s,$r,$str) {
	/* We'll first split our full string up into an array, splitting
	* it wherever the search string occurs. */
	$strArr = explode($s,$str);
	/* If the search string didn't occur within our full string, the
	* count of our new array is going to be 1. Therefore, we'll check
	* to make sure our new array has a count of greater than 1. If it
	* doesn't, we'll just go ahead and return the unaltered string. */
	if(count($strArr) <= 1) {
		return $str;
	}
	/* We will now "pop" the last element off of our new array and store
	* it in a new variable. */
	$newStr = array_pop($strArr);
	/* We now combine what's left of our array back into a string, inserting
	* the search string back where it was to begin with. Keep in mind that we
	* popped the last element out of the array, so it won't be added back into
	* the string, yet. */
	$str = implode($s,$strArr);
	/* Finally, we add the replacement string to the end of our string, and
	* then add the rest of the full string back on the end. We will then
	* return our altered version of the string. */
	$str .= $r.$newStr;
	return $str;
}

Because I didn’t need to use an array of search or replace elements, this function is set up to be very simple. If you try to feed it an array in the search or replace parameter, it’s most likely going to throw at least a PHP warning, and it’s going to fail.

However, if you think you’ll need to feed an array into the search and/or replace parameter, I have adjusted the function to handle that (though I haven’t tested it, so it may not work quite right). Following is that adjusted function:

function str_replaceLast($s,$r,$str) {
	if(is_array($s)) {
		if(is_array($r)) {
			/* If our search array and our replace array both
			* have the same number of values, we iterate through
			* them. If our search and replace items are both
			* arrays, but have different numbers of values, we
			* iterate through the search array and use the matching
			* values from the replace array, reusing the last
			* element until we run out of search elements */
			$s = array_values($s);
			$r = array_values($r);
			for($i=0;$i< count($r)) {
					$str = str_replaceLast($s[$i],$r[$i],$str);
				}
				else {
					$str = str_replaceLast($s[$i],$r[(count($r)-1)],$str);
				}
			}
		}
		else {
			/* If our search element is an array, but our replace
			* element is not, then we iterate through the search
			* array, using the static value of our replace variable
			* to perform the replacements. */
			foreach($s as $se) {
				$str = str_replaceLast($se,$r,$str);
			}
		}
	}
	/* We'll first split our full string up into an array, splitting
	* it wherever the search string occurs. */
	$strArr = explode($s,$str);
	/* If the search string didn't occur within our full string, the
	* count of our new array is going to be 1. Therefore, we'll check
	* to make sure our new array has a count of greater than 1. If it
	* doesn't, we'll just go ahead and return the unaltered string. */
	if(count($strArr) <= 1) {
		return $str;
	}
	/* We will now "pop" the last element off of our new array and store
	* it in a new variable. */
	$newStr = array_pop($strArr);
	/* We now combine what's left of our array back into a string, inserting
	* the search string back where it was to begin with. Keep in mind that we
	* popped the last element out of the array, so it won't be added back into
	* the string, yet. */
	$str = implode($s,$strArr);
	/* Finally, we add the replacement string to the end of our string, and
	* then add the rest of the full string back on the end. We will then
	* return our altered version of the string. */
	$str .= $r.$newStr;
	return $str;
}

Again, these functions are designed to find the last occurrence of a substring within your full string and replace it with a new value.

If you want to find and replace the first occurrence of a substring, we can make a few minor adjustments to the functions above and have a function that will do that, instead. Basically, we’re just going to use the array_shift function instead of the array_pop function. We also have to slightly rearrange the way we rebuild the string. Following is that new function.

function str_replaceFirst($s,$r,$str) {
	/* If you don't ever plan on feeding an array in the search
	* parameter or the replace parameter, you can delete everything
	* from below this comment until the comment that tells you to
	* stop deleting. */
	if(is_array($s)) {
		if(is_array($r)) {
			/* If our search array and our replace array both
			* have the same number of values, we iterate through
			* them. If our search and replace items are both
			* arrays, but have different numbers of values, we
			* iterate through the search array and use the matching
			* values from the replace array, reusing the last
			* element until we run out of search elements */
			$s = array_values($s);
			$r = array_values($r);
			for($i=0;$i< count($r)) {
					$str = str_replaceFirst($s[$i],$r[$i],$str);
				}
				else {
					$str = str_replaceFirst($s[$i],$r[(count($r)-1)],$str);
				}
			}
		}
		else {
			/* If our search element is an array, but our replace
			* element is not, then we iterate through the search
			* array, using the static value of our replace variable
			* to perform the replacements. */
			foreach($s as $se) {
				$str = str_replaceFirst($se,$r,$str,$offset,$num);
			}
		}
	}
	/* If you deleted the above information, stop deleting at this
	* comment. Everything else stays, regardless of whether you're
	* handling array inputs or not. */

	/* We'll first split our full string up into an array, splitting
	* it wherever the search string occurs. */
	$strArr = explode($s,$str);
	/* If the search string didn't occur within our full string, the
	* count of our new array is going to be 1. Therefore, we'll check
	* to make sure our new array has a count of greater than 1. If it
	* doesn't, we'll just go ahead and return the unaltered string. */
	if(count($strArr) <= 1) {
		return $str;
	}
	/* We will now "shift" the first element off of our new array and store
	* it in a new variable. Then, we'll "shift" the next element off of the
	* new array. Once we've got our first two elements, we'll combine them
	* with our replacement string in between (which is going in the same
	* place where the search string initially appeared). */
	$newStart = array_shift($strArr);
	$newStr = array_shift($strArr);
	$str = $newStart.$r.$newStr;
	/* If there's anything left of our new array, we will now combine what's
	* left back into a string, inserting the search string back where it was to
	* begin with. We will then return our rebuilt string. */
	$str .= implode($s,$strArr);
	return $str;
}