Ivan  Levantsov
Ivan Levantsov

Reputation: 45

Reordering string's lines using php regexp

I need to reorder lines in the string using php regexp. But I don't know how to tell php to not change the same line twice. Let me explain.

Input string is:

$comment = "
some text

{Varinat #3 smth}
{Varinat #4 smth else}
{Varinat #1 smth else 1}
some another text
{Varinat #2 smth else 2}
{Varinat #5 smth else 5}
";

I need to order variants:

$comment = "
some text

{Varinat #1 smth else 1}
{Varinat #2 smth else 2}
{Varinat #3 smth}
some another text
{Varinat #4 smth else}
{Varinat #5 smth else 5}    
";

I've got code:

$variants = [
    3 => 1,
    4 => 2,
    1 => 3,
    2 => 4,
    5 => 5,
];

$replacements = [];
foreach ($variants as $key => $variant) {
    $replacements['/{Varinat #'.$variant.'\ /is'] = '{Varinat #'.$key . ' ';
}


$comment = preg_replace(array_keys($replacements), array_values($replacements), $comment);

echo $comment;

But it does exta changes:

some text

{Varinat #1 smth}
{Varinat #2 smth else}
{Varinat #1 smth else 1}
some another text
{Varinat #2 smth else 2}
{Varinat #5 smth else 5}

As you can see lines 1 & 2 are doubled. It happens because php does change: 3->1 and then 1->3.

I have only uggly solution: change lines as

3 => 1*,
4 => 2*,
1 => 3*,
2 => 4*,
5 => 5*,

and then remove *

Is there more elegant solution?

Upvotes: 3

Views: 63

Answers (2)

Niet the Dark Absol
Niet the Dark Absol

Reputation: 324750

Why not build an algorithm to actually do what you want to do, that is to sort the {Varinat lines?

$lines = explode("\n",$comment); // assuming $comment is your input here
$numbered_lines = array_map(null,$lines,range(1,count($lines)));
usort($numbered_lines,function($a,$b) {
    if( preg_match('(^\{Varinat #(\d+))', $a[0], $match_a)
     && preg_match('(^\{Varinat #(\d+))', $b[0], $match_b)) {
        return $match_a[1] - $match_b[1]; // sort based on variant number
    }
    return $a[1] - $b[1]; // sort based on line number
});
$sorted_lines = array_column($numbered_lines,0);
$result = implode("\n",$sorted_lines);

For some reason the code above does not work in PHP 7. Here's an alternative that does.

$lines = explode("\n",$comment);
$processed = array_map(function($line) {
    if( preg_match('(^\{Varinat #(\d+))', $line, $match)) {
        return [$line,$match[1]];
    }
    return [$line,null];
}, $lines);
$variant = array_filter($processed,function($data) {return $data[1];});
usort($variant,function($a,$b) {return $a[1] - $b[1];});
$sorted = array_map(function($data) use (&$variant) {
    if( $data[1]) return array_shift($variant)[0];
    else return $data[0];
},$processed);
$result = implode("\n",$sorted);

This works by first tagging each line with its "variant" number, if it has one. Then it filters the list down to just those lines and sorts them. Finally it loops over all lines once more, and either keeps it as is (if it isn't a variant) or replaces it with the next sorted variant line.

> Demo on 3v4l

Upvotes: 3

Łukasz Jakubek
Łukasz Jakubek

Reputation: 1013

More complicated, but algorithm could look like that:

  1. Replace lines starts with "{Variant #" with unique placeholder, i.e. "%line" . $i where $i is auto-increment, and store removed part in temporary array. It could be done by preg_replace_callback() method
  2. Sort temporary array
  3. Replace placeholders with successive items from temporary array again by preg_replace_callback().

Upvotes: 0

Related Questions