# Streamlining Dataset Select meta-programming

Imagine you are deep in studying the Powerball Winning Numbers starting from 2010:

SetOptions[Dataset, MaxItems -> 5];

data = Dataset[AssociationThread[{"Date", "Numbers", "Multiplier"}, #]&
][[2 ;; ]]]


Suppose you have two lucky numbers, say, 13 and 57, and you would like to select just the entries where at least one of them is present:

data[Select[StringContainsQ[#Numbers, "13"] ||
StringContainsQ[#Numbers, "57"] &]]


You idly notice that the realized probability of one of your numbers being selected is close enough to the theoretical value, so if the organizers are cheating, at least they do it in a probabilistically-believable way.

N[{246/1391, 1 - (67/69)^6}] (* {0.176851, 0.161787} *)


Soon your friends learn about your studies and bombard you with questions about their lucky numbers - and some have as many as 15! As you become tired of typing StringContainsQ repeatedly, you decided to write a MetaSelector, and make it work generically with any field. You start with a naive definition:

containsAny[field_, variants_] :=
Select[Function[Evaluate[Or @@
Map[StringContainsQ[field, #, IgnoreCase -> True]&,
variants
]]]]


And use it to define your personal lucky number selector. Even though Wolfram Language complaints about something you don't understand, your code magically works:

While the code works, something is unsettling about all these error messages; although you can suppress them with Quiet, you decided to be honest. After meditating on the Wolfram Language evaluation tutorial for a few hours, you come up with this code:

containsAny1[field_, variants_] :=
ReleaseHold @ Select[Function[Evaluate[
(Or @@ variants) /.
x_String -> Hold @ StringContainsQ[field, x, IgnoreCase -> True]]]]

myCorrectLucky = containsAny1[#Numbers, {"13", "57"}]
data[myCorrectLucky]


Everything is working as expected now, and you pat yourself on the back. But you keep wondering: could you make your code shorter and more idiomatic?

A couple of ways to select rows that contain 13 or 57 in the Numbers column could be:

data = Dataset[
AssociationThread[{"Date", "Numbers", "Multiplier"}, #] & /@
Import["https://data.ny.gov/api/views/d6yy-54nr/rows.csv?\

r1 = data[
Select[ContainsAny[{13, 57}]@*ToExpression@*
StringSplit@#"Numbers" &]]

OR

r2 = data[
Select[StringContainsQ[
Alternatives @@ ToString /@ {13, 57}]@#"Numbers" &]]

r1==r2


True

There are 246 entries that match the criterion.

• Thanks! It's an elegant solution, although, for my purposes, I need to deal with strings that are not convertible to numbers. But the second solution is excellent - indeed, we can give StringContainsQ a pattern, and I learned today that Slot could be applied to a string, not just a symbol. So the winning formula is containsAny2[field_, variants_] := Select[Function[ StringContainsQ[Alternatives @@ variants]@Slot[field]]] Nov 16, 2022 at 7:04

After meditating for another little bit, you notice that the only reason you had to use a Hold was because Mathematica tried to evaluate the StringContainsQ too early. Why? Because you asked it to Evaluate; otherwise, the content of Function would be held as usual.

You reflect on why you included the Evaluate. It was there so that the result of containsAny would look like the sort of Select a human would write. If you take out the Evaluate, the output is a bit funny, but fine: defining

containsAny[field_, variants_] := Select[Function[(Or @@ variants) /. x_String :> StringContainsQ[field, x, IgnoreCase -> True]]]


we have

containsAny[#Numbers, {"13", "57"}] ----> Select[Or @@ {"13", "57"} /.
x$_String :> StringContainsQ[#Numbers, x$, IgnoreCase -> True] &]


And this does the trick if fed into data.

• Thanks - I forgot about the delayed evaluation; it's elegant. Nov 16, 2022 at 7:06