18
$\begingroup$

Consider the following:

data={"AB","CD","AF"};

Now I would like to delete all String from data which starts with "A".

Result: {"CD"}

$\endgroup$
2
  • $\begingroup$ May be somthing like DeleteDuplicates[data,First@Characters[#]=="A"] but abviously this is not working. $\endgroup$
    – John
    Apr 19, 2012 at 15:07
  • $\begingroup$ It is well known that Wolfram Language favors immutability, therefore deletion functionality can be meant in many ways, e.g. selecting strings that match a pattern, cases of string patterns, picking string patterns. But in all cases, including the DeleteCases, a copy of the data list with those patterns that do not match these cases is returned. Therefore deletion here is not meant in a mutable way like it often happens with other programming languages, e.g. remove an element from a Python list. This may be too obvious for mature users of the language, but for a newcomer it is frustrating. $\endgroup$ Aug 24, 2016 at 13:00

10 Answers 10

14
$\begingroup$

I am not sure how to do this using DeleteCases, but you can still use the Select function:

Select[data, StringTake[#, 1] != "A" &]

which has the desired result.

Edit Actually, you can also use DeleteCases like this:

DeleteCases[data, _?(StringTake[#, 1] == "A" &)]
$\endgroup$
15
$\begingroup$

Here is another one:

DeleteCases[data, _?(StringMatchQ[#, "A*"] &)]
$\endgroup$
3
  • 4
    $\begingroup$ The OP should take careful note of the parentheses around StringMatchQ[#, "A*"]& as the parser for PatternTest (?) is aggressive. Without it, the parser comes up with Function[PatternTest[Blank[], StringMatchQ][Slot[1], "A*"]] as opposed to the correct form PatternTest[Blank[], Function[StringMatchQ[Slot[1], "A*"]]]. $\endgroup$
    – rcollyer
    Apr 19, 2012 at 15:21
  • 2
    $\begingroup$ I don't want to post it as a new answer, as there are already quite a handful, and it is very similar to the one above but uses Condition instead of PatternTest: DeleteCases[data, x_ /; StringMatchQ[x, "A*"]]. And of course there are a million ways to write the same pattern differently. $\endgroup$ Apr 19, 2012 at 17:20
  • $\begingroup$ Thank you @IstvánZachar, that made me search on the differences between PatternTest and Condition. I do not think these are covered from WL Documentation. But I found this post enlightening ! $\endgroup$ Aug 24, 2016 at 9:47
7
$\begingroup$
 Pick[data, StringMatchQ[#, "A*"] & /@ data, False]
 (* => {"CD"} *)

EDIT: As noted in YvesKlett's comment, since StringMatchQ threads over its first argument, we can also use

 Pick[data, StringMatchQ[data, "A*"], False]

or

 Pick[#, StringMatchQ[#, "A*"], False]&@data
$\endgroup$
4
  • $\begingroup$ The disadvantage I can see is the double pass over data. But, it works, so +1. $\endgroup$
    – rcollyer
    Apr 19, 2012 at 15:24
  • 1
    $\begingroup$ Pick[data, StringMatchQ[data, "A*"], False] should work as well, since it accepts a list of strings as first argument... saves on wear and tear on your keyboard ;-) $\endgroup$
    – Yves Klett
    Apr 19, 2012 at 16:01
  • $\begingroup$ @YvesKlett, right! Thank you. $\endgroup$
    – kglr
    Apr 19, 2012 at 16:36
  • 1
    $\begingroup$ @rcollyer, thanks for the vote. For large lists, Pick "usually" more than compensates for the double-pass overhead associated with creation of the selector array, provided, of course, the selector array is carefully constructed. $\endgroup$
    – kglr
    Apr 19, 2012 at 17:00
6
$\begingroup$

Synopsis

It is not really an answer but a synopsis on answers of this post. I thought it will be useful for newcomers of WL patterns like me, so I thought to share it with you ;-)

Answers can be categorized in terms of the function and the pattern test. This is the list of patterns I have taken. I have also included negated forms that are used with Select.

strQ1   =StringTake[#,1]=="A"&;
strQ1Neg=StringTake[#,1] !="A" &;

strQ2   =StringStartsQ["A"];
strQ2Neg=Not@*StringStartsQ["A"];

strQ3   =StringMatchQ[#,"A*"]&;
strQ3Neg=Not@*strQ3;

strQ4    =StringMatchQ[#,"A"~~__]&;
strQ4Neg =StringMatchQ[#,Except["A"]~~__]&;
strQ4Neg2=StringFreeQ[#,"A"~~__]&;

strQ5    =StringMatchQ[#,"A"~~WordCharacter]&;
strQ5Neg =StringMatchQ[#,Except["A"]~~WordCharacter]&;
strQ5Neg2=StringFreeQ[#,"A"~~WordCharacter]&;

These can be used with the following WL functions, I use operational forms, wherever possible, and postfix notation to highlight the transformation.

DeleteCases

data // DeleteCases[_?strQ1]
data // DeleteCases[_?strQ2]
data // DeleteCases[_?strQ3]
data // DeleteCases[_?strQ4]
data // DeleteCases[_?strQ5]

Cases

data // Cases[Except[_?strQ1]]    
data // Cases[Except[_?strQ2]]    
data // Cases[Except[_?strQ3]]    
data // Cases[Except[_?strQ4]]    
data // Cases[Except[_?strQ5]]

Pick

data // Pick[#,strQ1/@#,False]&
data // Pick[#,strQ2@#,False]&
data // Pick[#,strQ3@#,False]&
data // Pick[#,strQ4@#,False]&
data // Pick[#,strQ5@#,False]&

Select

data // Select[strQ1Neg]
data // Select[strQ2Neg]
data // Select[strQ3Neg]
data // Select[strQ4Neg]
data // Select[strQ4Neg2]
data // Select[strQ5Neg]
data // Select[strQ5Neg2]

Output

{"CD"}

Benchmarking

I have decided to benchmark those answers above to find which one is the fastest. This is the procedure I have followed.

Timing

Each computation is measured with Timing that is repeated 100 times. Then I am taking the Mean of the results, e.g.

q1DeleteCases = Table[data // DeleteCases[_?strQ1] // Timing // First, {100}] // Mean

All values are converted to milliseconds, my $TimeUnit is 1/100. These are all the results per category.

{timingCases, timingDeleteCases, timingPick, timingSelect}

{{133.151,677.71,94.879,314.711,310.773},{115.781,674.823,84.6535,296.377,287.192},{94.8139,40.2126,8.64794,18.9432,18.7907},{89.8032,716.253,105.113,263.977,149.96,262.98,149.862}}

Barchart Plotting

patternTestsQ1toQ5={"q1:StringTake", "q2:StringStartsQ", "q3:StringMatchQ[#,\"A*\"]", "q4:StringMatchQ[#,\"A\"~~__]", "q5:StringMatchQ[#,\"A\"~~WordCharacter"};

patternTestsSelect = {"q1Neg", "q2Neg", "q3Neg", "q4Neg", "q4Neg2", "q5Neg", "q5Neg2"};

{
BarChart[timingDeleteCases,
ChartElementFunction->"GlassRectangle",ChartStyle->"Pastel",
ChartLegends->patternTestsQ1toQ5,AxesLabel->{"DeleteCases","ms"},TargetUnits->"ms",ChartLabels->Placed[timingDeleteCases,Top],ImageSize->Large],
BarChart[timingCases,
ChartElementFunction->"GlassRectangle",ChartStyle->"Pastel",
ChartLegends->patternTestsQ1toQ5,AxesLabel->{"Cases","ms"},TargetUnits->"ms",ChartLabels->Placed[timingCases,Top],ImageSize->Large],
BarChart[timingPick,
ChartElementFunction->"GlassRectangle",ChartStyle->"Pastel",
ChartLegends->patternTestsQ1toQ5,AxesLabel->{"Pick","ms"},TargetUnits->"ms",ChartLabels->Placed[timingPick,Top],ImageSize->Large],
BarChart[timingSelect,
ChartElementFunction->"GlassRectangle",ChartStyle->"Pastel",
ChartLegends->patternTestsSelect,AxesLabel->{"Select","ms"},TargetUnits->"ms",ChartLabels->Placed[timingSelect,Top],ImageSize->Large]
}

enter image description here

enter image description here

enter image description here

enter image description here

BarChart Comparison

Comparison of the first three BarCharts, columns are grouped by pattern test with a function chart legend and timing data labels at the top of each bar.

enter image description here

One can notice that timing for DeleteCases is slightly faster than Cases and there is a significant improvement on the speed of calculations for pattern matching that is based on the Pick function.

System Information

{$OperatingSystem,$ProcessorCount,$ProcessorType, MemoryInUse[],$Version}

{Unix,2,x86-64,257092208,10.3.1 for Linux x86 (64-bit) (December 8, 2015)}

Fastest Answer

All Pick answers are much faster compared to other solutions, but the one with this PatternTest q3:StringMatchQ[#,"A*"] is significantly faster. I am leaving justification to the experienced user of WL.

$\endgroup$
1
  • $\begingroup$ (+1) Just in case I point you to this answer for short discussion on the difference between Timing and AbsoluteTiming. String-matching functions internally use the PCRE library, and I can't to say for sure whether the CPU time spent in this library is included in Timing or not. $\endgroup$ Aug 25, 2016 at 1:55
5
$\begingroup$

Another method using Select but with what I find to be a more obvious notation.

Select[d, StringMatchQ[#, Except["A"] ~~ __] &]
$\endgroup$
4
$\begingroup$

Here's yet another solution that has not been mentioned using Select and StringFreeQ

Select[{"AB", "CD", "AF"}, StringFreeQ[#, "A" ~~ ___] &]
(* {"CD"} *)
$\endgroup$
3
$\begingroup$
Cases[data, Except[_?(StringMatchQ[#, "A*"] &)]]
{"CD"}
$\endgroup$
3
$\begingroup$

A possibly dangerous version that works for your example:

data = {"AB", "CD", "AF"};
Flatten[StringCases[data, Except["A"] ~~ __]]
{"CD"}

uh, and adding variety to the Pick faction:

Pick[data, Thread[StringTake[data, 1] != "A"]]
{"CD"}
$\endgroup$
4
  • $\begingroup$ +1 for posting the dangerous version. I came up with that one as well, and thought, "I don't like the look of this" $\endgroup$
    – tkott
    Apr 19, 2012 at 16:27
  • $\begingroup$ Sometimes you gotta take a risk ;-) $\endgroup$
    – Yves Klett
    Apr 19, 2012 at 16:47
  • $\begingroup$ Can you elucidate why the first one is dangerous? $\endgroup$
    – rcollyer
    Apr 19, 2012 at 17:02
  • $\begingroup$ I didn't test but you might get tangled up with other string configurations more easily? $\endgroup$
    – Yves Klett
    Apr 19, 2012 at 17:24
1
$\begingroup$

Here's a solution which may add flexibility if one is working with more complicated strings or required patterns

Select[ data, StringMatchQ[#, "A" ~~ WordCharacter] & ]

If, for example, one would want to select from data2 those strings which start with a numeric digit and which have "C" as a second digit:

data2 = {"AB1", "C2D", "3AF", "A41", "5CD", "FG6"};
Select[ data2, StringMatchQ[#, DigitCharacter ~~ "C" ~~ WordCharacter] & ]
$\endgroup$
1
$\begingroup$

This is a perfect fit for the newish (10.1) StringStartsQ function and "curried" operators:

Select[data, Not @* StringStartsQ["A"]]
(* {"CD"} *) 

EDIT. Also, here's a silly approach using Pick's optional "pattern" argument:

Pick[data, Characters[data], {Except["A"], __String}]
(* {"CD"} *)

The redundant-looking String qualifier is there because of the rather weird way that Pick uses pattern arguments.

$\endgroup$
1
  • 1
    $\begingroup$ Slight modification: data // Pick[#, # // StringStartsQ[#, "A"] &, False] & I was not aware of StringStartsQ until I saw your answer $\endgroup$
    – user1066
    Aug 24, 2016 at 19:25

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.