5ubliminal@twitter

PHP Question: How Can I Replace Occurences 1 And 5 Of A String? : 5ubliminal's TellinYa

<a href="http://www.tellinya.com/art2/343/">PHP Question: How Can I Replace Occurences 1 And 5 Of A String? : 5ubliminal's TellinYa</a>
5ubliminal's YAMS
Bumped into a problem today… PHProblem!

Let's say I have a string where I know a certain substring repeats 5 times. I want to replace occurance (0,2,3) with another string but leave occurence 1,4 alone. Or just replace the first 3 occurences and let the rest live. Of course this needs to be case sensitive/insensitive.

How do you do that in PHP with one simple function?
And the winning answer is… You Don't!

So I had to write my own function to do the following.

  • Limit the replacements number. str_replace replaces all matches but I need to limit this to first X.
  • Specify the replacements indexes. I want to replace match 0,2 and 4 and let the rest be.
  • Indexes Array or Numeric Limit can be Negative. If I ask the function to replace -1 characters or index -1 I want last occurence gone.
  • Indexes Can Work Combined. I want to replace match 0,1,-1 and let the rest be.
  • I need to config case sensitivity as parameter. I want my function to be both case sensitive and insensitive friendly.

Is this too much to ask?

The approach:

I will show you the function declaration and explain each parameter.

function str_replace_limit($search, $replace, $subject, $limit = 0, $casensitive = false)

  1. $search is the string you search to replace.
  2. $replace is the replacement. Set to null to remove.
  3. $subject is the haystack where you replace the searched.
  4. $limit is the limit of replacements. It can be a number X meaning delete first X matches or it can be an array like array(0,2) replacing the first (0) and third (2) occurence and leaving the rest untouched. If numbers are negative occurences are calculated from the end.
  5. $casensitive is boolean. true = sensitive , false = insensitive.

Is there a more simple approach?

Not really. My function may look scary upfront but you don't need to understand it to use it. I have my own C++ way of handling things and some code there will shock the PHP coder but will look very natural to C++ ones. There were a few obstacles to overcome and if you really want to understand how this function works I'll try to explain it but if you don't you'll spare me a world full of trouble.

The function has a funny way of working. It splits the text and keeps null place holders where the searched string was found. Then, based on indexes, it replaces the place holders with the original string or the $replace string if marked for replace. Then the array is combined and output. I keep another array with the exact case of the matches so, when I put those that don't need to be replaced back, I have their original selves with uppers and lowers and so on :)

The PHP Code + Helper Functions.

<?
//-----------------------------------------------------------------
//-- Copyright 5ubliminal 2008. (5ubliminal.com)
// http://www.tellinya.com/art2/343/
// You can do anything with it except selling it or claiming it.
// Copyright notice must remain intact and links are appreciated!
//-----------------------------------------------------------------
function str_replace_limit($search, $replace, $subject, $limit = 0, $casensitive = false){
    //-- parameters incorrect -> end here!
    if(!is_string($search) || !is_string($subject) || !strlen($search) || !strlen($subject))
        return $subject;
    //-- limit is array or number, empty arrays are no-no
    if(
        (!is_numeric($limit) && !is_array($limit)) ||
        (is_array($limit) && (count($limit)==0))
    ) $limit = 0;
    //-- if we have no limit we let regular function take over
    if(!$limit && $casensitive)
        return str_replace($search, $replace, $subject);
    //-- if we have no limit and case insensitive we send to str_ireplace (PHP5+)
    if(!$limit && function_exists("str_ireplace"))
        return str_ireplace($search, $replace, $subject);
    //-- if we have no limit but we are here it means PHP4 is running so we replicate stri_replace
    //-- Step1. we mark occurences
    $occurence_matrix = array(); //-- we record the slices in here
    $occurences_matrix = array(); //-- we record the actual case sensitive matches
    while(true){
        //-- the occurence location
        $str_location = ($casensitive ? strstr($subject, $search) : stristr($subject, $search));
        if(!$str_location) break;
        //-- we pop the last thing. this ensures we keep last occurence once
        array_pop($occurence_matrix);
        //-- if first character points to a match the slice_front_len = 0
        $slice_font_len = strlen($subject)-strlen($str_location);
        //-- but if we use substr with 0 it returns the whole string so ... bad!
        $slice_front = null; //-- init to null
        if($slice_font_len) //-- avoidance for 0 len:)
            $slice_front = substr($subject, 0, $slice_font_len);
        $slice_back = substr($str_location, strlen($search));
        //-- we rebuild the $subject based on slices and replacement
        $subject = $slice_back;
        array_push($occurence_matrix, (string)$slice_front);
        array_push($occurence_matrix, null);
        //-- we record the actual match. as it could be case insensitive,
        //-- if we put it back we might mistake the case.
        array_push($occurences_matrix, substr($str_location, 0, strlen($search)));
        array_push($occurence_matrix, $slice_back);
    }
    //-- no match?! we end here.
    if(!count($occurence_matrix)) return $subject;
    //-- let's record the locations where the $search string was found
    $null_positions = array(); $pos = 0;
    while($pos<count($occurence_matrix)){
        if(is_null($occurence_matrix[$pos])) array_push($null_positions,$pos);
        $pos++;
    }
    //-- let's prepare the limit array now
    if(is_numeric($limit)){
        $limit_len = $limit;
        $limit = array_keys($null_positions);
        if($limit_len>0){    
            //-- we keep first abs($limit_len) indexes
            $limit = array_slice($limit, 0, abs($limit_len));
        }elseif($limit_len<0){
            //-- we keep last abs($limit_len) indexes
            $limit = array_slice($limit, abs($limit_len));
        }
    }
    // else we have an array so we're cool
    //-- we know have an array with occurences to replace
    $replace_pos = 0;
    foreach($occurence_matrix as $key => $value){
        if(!is_null($value)) continue;
        $should_replace = (bool)in_array($replace_pos, $limit);
        if(!$should_replace){ //-- we check for negative indexes
            $should_replace = (bool)in_array($replace_pos-count($null_positions), $limit);
        }
        //-- we replace here with the original match or the replacement
        $occurence_matrix[$key] = ($should_replace ? $replace : $occurences_matrix[$replace_pos]);
        $replace_pos++;
    }
    //--
    return implode(null,$occurence_matrix);
}
//-- This function defaults as case sensitive.
function str_nreplace($search, $replace, $subject, $limit = 0){
    return str_replace_limit($search, $replace, $subject, $limit, true);
}
//-- This function defaults as case insensitive.
function stri_nreplace($search, $replace, $subject, $limit = 0){
    return str_replace_limit($search, $replace, $subject, $limit, false);
}
//-- Same as main function only it defaults $replace to null = removes
function str_remove_limit($search, $subject, $limit = 0, $casensitive = false){
    return str_replace_limit($search, null, $subject, $limit, $casensitive);
}
//-- This function replaces with null and is case sensitive.
//-- It actually removes the searched string.
function str_nremove($search, $subject, $limit = 0){
    return str_remove_limit($search, $subject, $limit, true);
}
//-- This function replaces with null and is case insensitive.
//-- It actually removes the searched string.
function stri_nremove($search, $subject, $limit = 0){
    return str_remove_limit($search, $subject, $limit, false);
}
?>

Have I tested it?

I have it used on several sites and works perfectly. If you want to know the count of matches use substr_count. If will hint you about the removable indexes. If you find bugs or have ideas you know the drill. Comment form is below :) Holler!

3 Comments Posted By Readers :

Add your comment
#1 Kyo from Ukraine
Posted on Saturday, 12 April, 2008
Sorry for the offtop, but can you reccomend some good sources for a newb to learn PHP from scratch? Thanks in advance, just trying to be more understanding reader ))).
#2 5ubliminal web
Posted on Saturday, 12 April, 2008
I can help you. Hit the bookstore and get a thick PHP and MySQL book. And have fun!
There's no such thing as online learning.
#3 Kyo from Ukraine
Posted on Saturday, 12 April, 2008
Man, your blog already has all answers to my questions! ))) I think, I'll have to read it from the beginning. Thanks for the advice.
5ubliminal's TellinYa.com SEM & SEO Blog © 2007 - All rights reserved unless mentioned otherwise .
Rendered On : [Saturday, 13 March, 2010 - 20:38:09 GMT]   No Ajax / Flash Used Here
" PHP Question: How Can I Replace Occurences 1 And 5 Of A String? : 5ubliminal's TellinYa "
Close
Tellinya.com is relocating to blog.5ubliminal.com. This blog is no longer maintained and comments are no longer accepted / answered.