$lines = array_map('trim', file($file));
$lines_mentioning_john = array_keys(preg_grep('/john/i', $lines));
if(!empty($lines_mentioning_john))
{
$first_line_mentioning_john = $lines_mentioning_john[0];
unset($lines[$first_line_mentioning_john]);
$lines[] = 'john';
}
file_put_contents($file, join("\n", $lines));
[RESOLVED] array search delete replace line with search string
Many thanks weed packet for this speedy solution.
I've started checking it and it seems to work: no unwanted 'john' added!
I am now trying to do the same treatment throughout the file with several search strings ie not 'john' only but 'peter' also, etc..
more hastle for myself!
I assume that what my code was missing was this line with unset()???
Thank you again as it's the first t ime for me to come accross preg_grep().
Hi weedpacket!
This is me again...as I feared I am still in trouble with iterating this code as follows:
*one only line with 'john' is deleted. Meaning that if more than one line with 'john', the other lines with 'john' will remain.
'John' is added correctly at the end of the file. The file is not truncated.
(in the real world) the file is truncated to keep 'john' as single line for the whole file, meaning that all other lines are deleted.
Doing another search/replace with say 'peter' will give the same result.
'peter' will be the only line if searched last.
please note that I use a mutualised server supposed to be the best in the region...
Sorry to impose on you again
Thanking you in advance
Sounds strange.
Are you sure you have exactly same code in both places?
Because:
$lines[] = 'john'; ... means correctly to add 'john' as a new element at the end
But:
$lines = 'john'; ... will truncate and have only one element 'john'
Another thing, say we have:
peter;
johnny;
paul;
john;
jonathan;
william;
alan;
theresa;
If we use: preg_grep('/john/i', $lines)
then johnny will be deleted, but john be left untouched
But if we use: preg_grep('/john$/i', $lines)
then 'john' will be deleted, as this one matches exactly the word.
And so get not any match of 'johnny'
I don't know why it would be have differently in different situations; one guess is that the file was written on a operating system using one type of line-ending, and the server is running on an OS using a different one (i.e., Unix-based machines use "\n" to mark the end of a line, MacOS uses "\r", and Windows uses "\r\n"), so the entire file is ending up in one line (which naturally contains "john").
One thing that should be done to improve that code is change that [font=monospace]"\n"[/font] at the end to [font=monospace]PHP_EOL[/font]. Then at least when the file is written it will use the correct line break for the system the program's running on.
cosmexpert wrote:I am now trying to do the same treatment throughout the file with several search strings ie not 'john' only but 'peter' also, etc..
more hastle for myself!
If you're repeating code you're probably doing it wrong: this is why functions were created.
function move_name_to_end_of_file($file, $name)
{
// That code, with "john" replaced by $name;
// you'd want to change the expression in preg_grep to '/'.preg_quote($name).'/i'
}
I assume that what my code was missing was this line with unset()???
No; the problem was that you added john's line at the end regardless of whether you found the name or not. The other changes I wrote were just for efficiency (you were already reading the entire file in one step; it was simpler to write the entire file in one step, too. The temporary file was unnecessary also - although it would have been needed if you were originally reading line-by-line and writing concurrently).
Actually, now that I look at your original code again, something occurs to me..... You open the new file for appending: are you really planning on doubling the size of the file each time?
halojoy makes another observation (although the description and solution are incorrect); do you want "johnny" to be found when you look for "john"? Or "upjohn"? If not, then the pattern should be [font=monospace]'/\b'.preg_quote($name).'\b/i'[/font] (assuming you rewrite this as a function) so that it only matches entire words.
Oh, and I guess when you push the name onto the end of the array, you'd want to attach a semicolon, too, if those are there for some purpose. When posting code, please use the formatting tags described in the FAQ. Another thread stickied in the Coding Forum has some posters' observations about formatting code. I think I might have mistaken those semicolons in the text for PHP's end-of-statement semicolons.
Many thanks to both of you (weedpacket and halojoy) for your careful attention to my problem.
Back from sleep now!
Firstly the system tech info for the server is as follows:
' Linux imu215 2.6.27.42-imu-x86 #1 SMP Mon Dec 21 10:06:10 CET 2009 i686'
Secondly (and consequently ?), PHP_EOL makes no change compared to "\n" both locally and on the server.
I obviously do understand the necessity of the operator [], I do not want to double the file at every iteration, the reg expression \ 'john'\i is enough at present and I use ';' as delimiter but I add it easily after each search/delete/replace as you may check below.
I tried using the function you suggested as follows:
'
function move_name_to_end_of_file($file, $name)
{
$lines = array_map('trim', file($file));
$lines_search = array_keys(preg_grep('/'.preg_quote($name).'/i', $lines));
if(!empty($lines_search))
{
$first_line_search = $lines_search[0];
unset($lines[$first_line_search]);
$lines[] = '$name;';
}
file_put_contents($file, join("\n", $lines));
}
$lines = array_map('trim', file($file));
$i=0;
while ($i<count($lines))
{
if ($lines[$i]='/john/i' ||$lines[$i]='/peter/i' ) //line_to_change????
{
$lines[$i]=move_name_to_end_of_file($file, $lines[$i]);
$i++;
}
}
'
It has no effect on the file whatsoever.
If I modifiy the so-called line_to_change to:
if (stripos($lines[$i], 'john') === true || stripos($lines[$i], 'peter') === true )
then browser loops indefinitely.
Above tested locally only.
Thank you for your patience in keeping up with me...I need it.
Well, it looks like you are writing to the file in the loop by calling move_name_to_end_of_file() on each iteration. I think that a correct approach would be to modify the array of lines for every search string, and only when you are done with all of them do you write the new contents to the file.
Hello beautiful Singapore and thanks
This way of tackling the problem is not suitable according to weedpacket.
I'll wait for his reply until moving ahead again.
cosmexpert wrote:This way of tackling the problem is not suitable according to weedpacket.
Where exactly does Weedpacket say that? If he did, I daresay this is one the rare times that we've caught him being wrong
just above , before my reply, which is why I tried this function he suggests
cosmexpert wrote:just above , before my reply, which is why I tried this function he suggests
You should quote him. Frankly, I cannot find anything in what he wrote that contradicts my suggestion. In fact, notice:
Weedpacket wrote:The other changes I wrote were just for efficiency (you were already reading the entire file in one step; it was simpler to write the entire file in one step, too.
Here's a problem: if you call move_name_to_end_of_file() in a loop, only the last call of move_name_to_end_of_file() counts, since it is only that call that finally causes the file to be overwritten. Effectively, you are writing to the file in many steps, and only that last step provides the net effect.
EDIT:
Oh wait, I just noticed the use of file() in the function. That means that there should be a net effect for each iteration... but again, this is a case of writing and then reading to/from the file in many steps, when you just need to read once and write once.
What would be unsuitable would be writing a whole chunk of code over and over with just the name changing each time.
But it's also expensive to be reading and writing a file over and over with only one name shifting each time. It would make more sense to read the file once, make all of the desired changes, and then write it (once).
That would involve changing the function so that instead of the file name, it took the array of lines read from the file and returned the rearranged lines so that it could be written back out.
Then you'd have the names to shift in an array. Looping through the elements of that array, pass each of them (along with the lines) to the function and get back the rearranged lines, then go on to the next name.
The next idea would be to have that loop inside the function, and pass it both arrays.
One thing is certain: the whole point of having the function is to look for a name and shift it if necessary. Which means that
$lines = array_map('trim', file($file));
$i=0;
while ($i<count($lines))
{
if ($lines[$i]='/john/i' ||$lines[$i]='/peter/i' ) //line_to_change????
{
$lines[$i]=move_name_to_end_of_file($file, $lines[$i]);
$i++;
}
}
pretty much totally ignores what the function does (what would be the point of reading the file, looping through every line to check and maybe move it, and then writing the file if on every line you read the file, looping through every line to check and maybe move it...? Please, read what you're writing and ask yourself honestly if it makes the faintest bit of sense. (Incidentally, you'll notice that using [noparse]
...
[/noparse] around your code gives a better result than single quotes. See the FAQ for more).
But any how, where I left the code was passing it two arrays - one of lines and one of names. That looks like
function move_names_to_end($lines, $names)
{
foreach($names as $name)
{
$lines_mentioning_name = array_keys(preg_grep('/'.preg_quote($name).'/i', $lines));
if(!empty($lines_mentioning_name))
{
$first_line_mentioning_name = $lines_mentioning_name[0];
unset($lines[$first_line_mentioning_name]);
$lines[] = $name;
}
}
return $lines;
}
// And to use it...
$names = array('john', 'peter', 'whatever');
$lines = array_map('trim', file($file));
$lines = move_names_to_end($lines, $names);
file_put_contents($file, join(PHP_EOL, $lines));
Note the absence of all that other cruft - it's been moved into the function. It could be altered further, especially with some added domain knowledge, but it works and is pretty straightforward.
Thanks for your efforts...it is above my reach I have to say!
It seems to me though that there is a confusion about the file I need to manipulate.
Lines are (difficult) words separated by space (ie not a single word) and ended by one semicolon. Actually, it looks more like:
paul;
peter paul;
jonathan;
william johnny;
alan peter;
theresa;
peter alfred richard;
john;
As a consequence, the search/delete/replace treatment for 'peter and 'john' gives the following result (locally):
paul;
jonathan;
alan peter;
theresa;
peter alfred richard;
john;
peter
john
Rather messy isn't it?
and again
Thanks for your efforts...it is above my reach I have to say!
I have done a script that works.
Hope you can try it and see how it works for you.
One important thing is how you add new names to that file.
I think you should not use semicolon at the end.
Just add one "\n" (newline) at the end of each line.
This is how my script suppose you to add new line/name.
I use file_put_contents() in FILE_APPEND mode.
// To append, add one new name
$new = 'Laura';
file_put_contents($file, $new."\n", FILE_APPEND);
My script and the new function
that takes several search names at one time.
<?php
// Make a testfile
$file = 'names.txt';
$names = array('peter','paul','john','jonathan',
'william','alan','theresa','johnny');
$fp = fopen($file, 'w');
foreach($names as $name){
fwrite($fp,$name."\n");
}
fclose($fp);
// The Function
function move_name($lines, $search){
foreach($search as $name){
$pattern = '/\b'.preg_quote($name).'\b/i';
foreach($lines as $k=>$v){
if(preg_match($pattern,$v)){
unset($lines[$k]);
$lines[] = $name;
break;
}
}
}
return $lines;
}
////////////////
// Get file contents and run the test
$lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
// Same as: $search = array('john', 'peter');
$search[] = 'john';
$search[] = 'peter';
// Do it
$newlines = move_name($lines, $search);
// Save it
file_put_contents($file, implode("\n",$newlines)."\n");
// View $newlines
echo '<pre>'; var_export($newlines);
// To append, add one new name
$new = 'Laura';
file_put_contents($file, $new."\n", FILE_APPEND);
?>
Thanks halojoy
Appending a name is fairly straightforward..
I can see that my explanations about the file to manipulate were not accurate enough. Sorry about that!
This last code does seem to work (locally at least) with an array of single words. Unfortunatelky it does not (sigh!) with an array like:
array('peter','paul john','john','jonathan',
'william','alan peter','theresa','johnny john');
where the search/delete/replace names (here peter and john) may be alone or next to any other word(s) or part of another word(s).
For instance: 'johnny john' must be replaced by 'john', once only.
Shall I dare sign again with:
it is above my reach
thanks for your efforts
So you want to drop all the occurrences that match "john" and append "john;"? (I missed that semicolon again. Yay!) I misread your original description (I thought you only wanted to replace one line).
That simplifies things.
// Note; if there are any names in $names that are not in the $list,
// they will be added also.
function move_names_to_end($lines, $names)
{
// We can search for all the names at once
$pattern = '/\b'.join('|', array_map('preg_quote', $names)).'\b/i';
// PREG_GREP_INVERT: *drop* lines matching the pattern
// (i.e. any line containing one of the supplied names)
// and keep the rest.
$lines = preg_grep($pattern, $lines, PREG_GREP_INVERT);
// Now put those matched names back on the end of the list.
// First we add semicolons.
foreach($names as &$name)
{
$name = "$name;";
}
// Then append
$lines = array_merge($lines, $names);
return $lines;
}
Thanks for being still with me
Let me first repeat what I understand from your several suggestions
file ($file)
paul;
jonathan johnny;
alan peter;
theresa paul;
peter alfred richard;
johnny george;
william john;
code:
function move_names_to_end($lines, $names)
{
// We can search for all the names at once
$pattern = '/\b'.join('|', array_map('preg_quote', $names)).'\b/i';
// PREG_GREP_INVERT: *drop* lines matching the pattern
// (i.e. any line containing one of the supplied names)
// and keep the rest.
$lines = preg_grep($pattern, $lines, PREG_GREP_INVERT);
// Now put those matched names back on the end of the list.
// First we add semicolons.
foreach($names as &$name)
{
$name = "$name;";
}
// Then append
$lines = array_merge($lines, $names);
return $lines;
}
// And to use it...
$lines = array_map('trim', file($file));
$names = array('peter', 'john');
$lines = array_map('trim', file($file));
$lines = move_names_to_end($lines, $names);
file_put_contents($file, join(PHP_EOL, $lines));
result:
jonathan johnny;
theresa paul;
peter;
john;
expected result :
paul;
john;
peter;
theresa paul;
(tested locally only)
Getting rid of eventual duplicates would be easy adding array_unique(), but,here again result is not what I am aiming for.
Not begging though, but I feel I am unable to find the solution on my own.
I do appreciate your efforts to help me at this late hour in moonlit Paris
cosmexpert;10950204 wrote:Thanks halojoy
Appending a name is fairly straightforward..
I can see that my explanations about the file to manipulate were not accurate enough. Sorry about that!
This last code does seem to work (locally at least) with an array of single words. Unfortunatelky it does not (sigh!) with an array like:
array('peter','paul john','john','jonathan',
'william','alan peter','theresa','johnny john');where the search/delete/replace names (here peter and john) may be alone or next to any other word(s) or part of another word(s).
For instance: 'johnny john' must be replaced by 'john', once only.
You are right.
Whenever someone has got a question it is 100% important
that they try to be very precise to tell what they want to do.
All conditions and details that can be of importance must be told.
If somebody has managed to tell the situation in a proper way
then there will be a quick solution.
And somebody will have no problem to post such a good solution.
Most times when people do not get a quick and good working answer
is because they have not been able to explain as they should.
Most often they do not provide the full PHP Error message.
Youur issue has many details and is a lot to think about and to know about.
About your project.
Still I can not accept that you use semicolons after those names.
- john;
Because in my mind there is more negative than any good with it.
And I can see no useful thing about it.
If you need semicolons when displaying names
they can be added later whenever you want to display
echo $name . ';';
Thank you halojoy for your help and remarks
These semicolons might not be necessary. They helped me during the first steps of this particular code.
I hope that my last reply to weedpacket helps clarifying my intention.
To explain more, I further use the search strings (john and peter) in a mysql request, after adding jokers, to extract similar strings from that table.
'john' would give a result whereas 'johnny' would not.
I don't understand what you're wanting. You ask for the selected names ('peter' and 'john') to be moved to the end of the file, and that's what the code does as you show (still without using the appropriate markup tags!). Why would you expect 'theresa paul' to be on the end, and why would you expect the two names you chose to have moved to the end finish up in the middle? And you're saying "once only" again.
Why do you want "jonathan johnny" to be removed? I thought you wanted names that matched what you selected should be removed. "jonathan" or "johnny" are not in that list.
Like I said; I don't understand what you're asking for, and I'm smarter than the computer you're trying to order to do the job.
What. Do. You. Want. If you can't explain it clearly enough then you won't be able to write a program to do it.
I'm going to guess.
You have two lists of names. The first list is in a text file, the second comes from somewhere else and has already been put into an array. The first list needs tidying up: some people appear on this list more than once under different variations of their name. It looks like
paul;
jonathan johnny;
alan peter;
theresa paul;
peter alfred richard;
johnny george;
william john;
The second list has the names you want to use for each person who has duplicates. It looks like
peter
john
For each preferred name in the second list, you want to:
find all of the names in the first list that match it (where "match" means that the preferred name matches a whole word in the name from the first list)
replace the first matching name with the preferred name
and remove the rest of the matching names from the first list.
So taking those example lists and going through that process. I take "peter" and I find that it matches
paul;
jonathan johnny;
[b]alan peter;[/b]
theresa paul;
[b]peter alfred richard;[/b]
johnny george;
william john;
I replace the first one, "alan peter;" with "peter;", and delete "peter alfred richard", leaving me with
paul;
jonathan johnny;
peter;
theresa paul;
johnny george;
william john;
The other preferred name is "john". I look for matches
paul;
jonathan johnny;
peter;
theresa paul;
johnny george;
[b]william john;[/b]
I only find one, which I replace with "john;".
paul;
jonathan johnny;
peter;
theresa paul;
johnny george;
john;
Nope, that's still not right. Do you want "john" to match "jonathan johnny"? Do you want "jon" to match "jonathan johnny" as well? What about "nathan"?