str_repeat(A, B) repeat string A, B times:
$string = "This is a " . str_repeat("test", 2) .
"! " . str_repeat("hello", 3) . " and Bye!";
// Return "This is a testtest! hellohellohello and Bye!"
I need reverse operation:
str_shrink($string, array("hello", "test"));
// Return "This is a test(x2)! hello(x3) and Bye!" or
// "This is a [test]x2! [hello]x3 and Bye!"
Best and efficient way for create str_shrink function?
Here are two versions that I could come up with.
The first uses a regular expression and replaces duplicate matches of the $needle
string with a single $needle
string. This is the most vigorously tested version and handles all possibilities of inputs successfully (as far as I know).
function str_shrink( $str, $needle)
{
if( is_array( $needle))
{
foreach( $needle as $n)
{
$str = str_shrink( $str, $n);
}
return $str;
}
$regex = '/(' . $needle . ')(?:' . $needle . ')+/i';
return preg_replace_callback( $regex, function( $matches) { return $matches[1] . '(x' . substr_count( $matches[0], $matches[1]) . ')'; }, $str);
}
The second uses string manipulation to continually replace occurrences of the $needle
concatenated with itself. Note that this one will fail if $needle.$needle
occurs more than once in the input string (The first one does not have this problem).
function str_shrink2( $str, $needle)
{
if( is_array( $needle))
{
foreach( $needle as $n)
{
$str = str_shrink2( $str, $n);
}
return $str;
}
$count = 1; $previous = -1;
while( ($i = strpos( $str, $needle.$needle)) > 0)
{
$str = str_replace( $needle.$needle, $needle, $str);
$count++;
$previous = $i;
}
if( $count > 1)
{
$str = substr( $str, 0, $previous) . $needle .'(x' . $count . ')' . substr( $str, $previous + strlen( $needle));
}
return $str;
}
Edit: I didn't realize that the desired output wanted to include the number of repetitions. I've modified my examples accordingly.
I think you can try with:
<?php
$string = "This is a testtest! hellohellohello and Bye!";
function str_shrink($string, $array){
$tr = array();
foreach($array as $el){
$n = substr_count($string, $el);
$tr[$el] = $el.'(x'.$n.')';
$pattern[] = '/('.$el.'\(x'.$n.'\))+/i';
}
return preg_replace($pattern, '${1}', strtr($string,$tr));
}
echo $string;
echo '<br/>';
echo str_shrink($string,array('test','hello')); //This is a test(x2)! hello(x3) and Bye!
?>
I have a second version in order to works with strings:
<?php
$string = "This is a testtest! hellohellohello and Bye!";
function str_shrink($string, $array){
$tr = array();
$array = is_array($array) ? $array : array($array);
foreach($array as $el){
$sN = 'x'.substr_count($string, $el);
$tr[$el] = $el.'('.$sN.')';
$pattern[] = '/('.$el.'\('.$sN.'\))+/i';
}
return preg_replace($pattern, '${1}', strtr($string,$tr));
}
echo $string;
echo '<br/>';
echo str_shrink($string,array('test','hello')); //This is a test(x2)! hello(x3) and Bye!
echo '<br/>';
echo str_shrink($string,'test'); //This is a test(x2)! hellohellohello and Bye!
?>
You can play around with tis one, not tested a lot though
function shrink($s, $parts, $mask = "%s(x%d)"){
foreach($parts as $part){
$removed = 0;
$regex = "/($part)+/";
preg_match_all($regex, $s, $matches, PREG_OFFSET_CAPTURE);
if(!$matches)
continue;
foreach($matches[0] as $m){
$offset = $m[1] - $removed;
$nb = substr_count($m[0], $part);
$counter = sprintf($mask, $part, $nb);
$s = substr($s, 0, $offset) . $counter . substr($s, $offset + strlen($m[0]));
$removed += strlen($m[0]) - strlen($part);
}
}
return $s;
}
I kept it short:
function str_shrink($haystack, $needles, $match_case = true) {
if (!is_array($needles)) $needles = array($needles);
foreach ($needles as $k => $v) $needles[$k] = preg_quote($v, '/');
$regexp = '/(' . implode('|', $needles) . ')+/' . ($match_case ? '' : 'i');
return preg_replace_callback($regexp, function($matches) {
return $matches[1] . '(x' . (strlen($matches[0]) / strlen($matches[1])) . ')';
}, $haystack);
}
The behavior of cases like str_shrink("aaa", array("a", "a(x3)"))
is it returns "a(x3)"
, which I thought was more likely intended if you're specifying an array. For the other behavior, giving a result of "a(x3)(x1)"
, call the function with each needle individually.
If you don't want multiples of one to get "(x1)" change:
return $matches[1] . '(x' . (strlen($matches[0]) / strlen($matches[1])) . ')';
to:
$multiple = strlen($matches[0]) / strlen($matches[1]);
return $matches[1] . (($multiple > 1) ? '(x' . $multiple . ')' : '');