5
$\begingroup$

Suppose that I have a string str. str contains headings -- which are written in commented lines -- and data. For example:

str = "# Heading 1
# Heading 2
@ Heading 3
@ Heading 4
Data 1
Data 2
Data 3";

where # and @ denote commented lines.

Now, I would like to write a function removeComments to remove all lines in str that are commented. That is, I would like to remove all lines in str that start with the character # or the character @.

I have come up with one way to do this, I think. My procedure is to first split str into lines using StringSplit with the "\n" delimiter; then use DeleteCases to delete all lines in that list which begin with either # or @; and, finally, convert the list back to a string using Riffle and StringJoin. So, I have the following:

removeComments[str_String] := Module[{result},
  result = StringSplit[str, "\n"];
  result = 
   DeleteCases[
    result, _?(StringMatchQ[#, ("#" ~~ ___) | ("@" ~~ ___)] &)];
  result = StringJoin[Riffle[result, "\n"]];
  result
  ]
removeComments[str]

which gives the following correct output (a string):

Data 1
Data 2
Data 3

However, now suppose that one of my data items (for example, a single space in front of Data 2) has one or more leading spaces:

str = "# Heading 1
# Heading 2
@ Heading 3
@ Heading 4
Data 1
 Data 2
Data 3";

removeComments[str]

In this case, I get the incorrect output:

Data 1
Data 3

That is, Data 2 is omitted altogether. Something is clearly wrong with my approach, but I haven't been able to find my mistake (although it is probably obvious). Could you please help me find what is wrong? Thanks for your time.

$\endgroup$
6
  • $\begingroup$ Do you wish to keep the leading space before "Data 2" or do you wish to discard it? $\endgroup$
    – Mr.Wizard
    Commented Aug 30, 2013 at 21:50
  • $\begingroup$ @Mr.Wizard Sorry; I was not clear. I would like to keep the leading space before Data 2. $\endgroup$
    – Andrew
    Commented Aug 30, 2013 at 21:51
  • $\begingroup$ @Mr.Wizard No problem! :-) Thank you for your time and help. $\endgroup$
    – Andrew
    Commented Aug 30, 2013 at 22:31
  • $\begingroup$ Andrew, I notice that you didn't Accept my answer. Is that an oversight or do you find it lacking? Can I improve it? $\endgroup$
    – Mr.Wizard
    Commented Sep 4, 2013 at 11:21
  • $\begingroup$ @Mr.Wizard Sorry! It was just an oversight on my part; I have used your help but forgot to Accept. Your answer has been extremely helpful to me! Thank you! $\endgroup$
    – Andrew
    Commented Sep 4, 2013 at 13:07

2 Answers 2

9
$\begingroup$

Corrected answer

My original answer (see edit history) was not correct, unless all of your data lines are contiguous.

str = "# Heading 1\n# Heading 2\n@ Heading 3\n@ Heading 4\nData 1\n Data 2\nData 3\n@ \
Heading 5\nData 4";

StringReplace[str, Shortest[StartOfLine ~~ "@" | "#" ~~ ___ ~~ "\n"] :> ""]
"Data 1\n Data 2\nData 3\nData 4"

Or:

"" <> StringSplit[str, Shortest[StartOfLine ~~ "@" | "#" ~~ ___ ~~ "\n"]]
"Data 1\n Data 2\nData 3\nData 4"

And the RE equivalent:

First @ StringPattern`PatternConvert[Shortest[StartOfLine ~~ "@" | "#" ~~ ___ ~~ "\n"]]
"(?ms)^[@#].*?\n"
"" <> StringSplit[str, RegularExpression["(?ms)^[@#].*?\n"]]
"Data 1\n Data 2\nData 3\nData 4"
$\endgroup$
4
  • $\begingroup$ Thanks! Yes, this works as desired, in that all data is retained (including the leading space before Data 2). $\endgroup$
    – Andrew
    Commented Aug 30, 2013 at 21:55
  • 1
    $\begingroup$ @Andrew Okay, great. I added the RE equivalent if you enjoy such things. $\endgroup$
    – Mr.Wizard
    Commented Aug 30, 2013 at 21:56
  • 2
    $\begingroup$ Although it hasn't been specified clearly I think it might be more robust to use ___ instead of just __ to also discard empty comment lines, which otherwise well might cause problems when trying to interpret the remaining lines. The RE equivalent would be .* instead of .+ $\endgroup$ Commented Sep 1, 2013 at 11:38
  • $\begingroup$ @Albert Good point; I'll change it. $\endgroup$
    – Mr.Wizard
    Commented Sep 1, 2013 at 11:39
3
$\begingroup$

This answer only discusses the implied question of why the OP's code fails. I think this should be made clear because it puzzled me until Mr.Wizard and rm -rf enlightened me. I would like keep others from falling into the same trap.

I was studying the code posted in this question, trying to learn why it failed, when I ran into the following.

StringMatchQ[" ", "@"]

True

I found this very surprising and posted a question on it for a very short period. Mr.Wizard and rm -rf quickly pointed out that @ was a wild card character and that my code should be

StringMatchQ[" ", "\\@"]

False

This same mistake also occurs in the code posted in the question above. With the following minor change, the OP's code works.

removeComments[str_String] := 
  Module[{result},
    result = StringSplit[str, "\n"];
    result = 
      DeleteCases[result, _?(StringMatchQ[#, ("#" ~~ ___) | ("\\@" ~~ ___)] &)];
    StringJoin[Riffle[result, "\n"]]]

removeComments[
  "# Heading 1\n# Heading 2\n@ Heading 3\n@ Heading 4\nData 1\n Data 2\nData 3"]

Data 1
$\ $Data 2
Data 3

$\endgroup$
4
  • 1
    $\begingroup$ +1 for providing the other half of the answer; I enjoy problem solving more than debugging so I skipped this (important) part. $\endgroup$
    – Mr.Wizard
    Commented Aug 30, 2013 at 23:24
  • 1
    $\begingroup$ Incidentally the pattern could be condesed like this: StringMatchQ[#, "#" | "\\@" ~~ ___] $\endgroup$
    – Mr.Wizard
    Commented Aug 31, 2013 at 1:38
  • $\begingroup$ What is an rm -rf? $\endgroup$
    – Hector
    Commented Aug 31, 2013 at 1:57
  • $\begingroup$ @Hector. It's a kind of moderator :-) $\endgroup$
    – m_goldberg
    Commented Aug 31, 2013 at 1:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.