# Filter Text by Lines

For a string like

str = "This is a multi-line
string with
three lines total";


is there a way to search and filter it by lines so that

searchLines[str, "line"]


returns

This is a multi-line
three lines total


?

You could try something like:

ClearAll[searchLines];
searchLines[text_,search_]:=StringJoin@Riffle[StringCases[
StringSplit[text,"\n"],
___~~search~~___
]/.{}->Nothing,"\n"];


searchLines[str, "line"]

"This is a multi-line
three lines total"

• Is there a way to add a paramater linesAround : NumberQ to the function that shows linesAround lines above and linesAround lines below the matching results? Sort of the like the -c option in grep? – George Mar 25 at 19:40
• @George I have not looked at grep that much but can you give an explicit example of the output? – user13892 Mar 25 at 21:43
• Sure. searchLines[str, "string", 1] on the str above should return str itself. – George Mar 25 at 21:51
str = "This is a multi-line
string with
three lines total";

fn = StringDelete["[]\n"]@*
StringReplace[RegularExpression["(?m)^^((?!line).)*"] :> "[]"];

fn[str]


One approach is to use the "Lines" element for ImportString/ExportString.

filterLines[str_, patt_] :=
ExportString[
Select[
ImportString[str, "Lines"],
StringContainsQ[patt]],
"Lines"]

filterLines[str, "line"]
(*
"This is a multi-line
three lines total"
*)


# Update

As Suba Thomas pointed out in comments, the ImportString/ExportString slows things down a lot. Using StringSplit and StringRiffle to perform the task is much faster:

filterLinesFaster[str_, patt_] :=
StringRiffle[
Select[StringSplit[str, "\n"], StringContainsQ[patt]],
"\n"]

First@RepeatedTiming[filterLines[str, "line"]]/
First@RepeatedTiming[filterLinesFast[str, "line"]]
(* 9.*10^1 *)


However, when I tried this on a much longer string, the speed difference shrinks considerably:

SeedRandom[1337];
superStr =
StringRiffle[
RandomChoice[
StringSplit[str, "\n"],
{1000}],
"\n"];

First@RepeatedTiming[filterLines[superStr, "line"]]/
First@RepeatedTiming[filterLinesFast[superStr, "line"]]
(* 3.2 *)


Still slower than the "fast" solution (not to mention Suba's and user13892's approaches, which are faster still), but it's interesting how much it shrinks. I think ImportString and ExportString may set up temporary files or streams to work with, which may add a substantial constant time to the task.

Anyway, I've used the ImportString/ExportString pair a lot in my own code to turn strings to lists of lines and back, and, well, I won't do that anymore!

• But it's rather slow? – Suba Thomas Mar 25 at 22:30
• Is it? Didn't benchmark. – Pillsy Mar 26 at 11:07
• Benchmarking with RepeatedTiming, shows this solution is ~150x slower than mine and ~120x slower than that of user13892. – Suba Thomas Mar 26 at 13:13
• Thanks for going the extra mile and the additional analysis. – Suba Thomas Mar 26 at 15:50