5

I can use a string template like so:

st = StringTemplate["a is `a` and b is `b`"];
st @ <|"a" -> 1, "b" -> 2|>

which gives

"a is 1 and b is 2"

But I would like the parameter b to be optional, so that if I call

st @ <|"a" -> 1|>

I get simply

"a is 1"

Something like this is possible with <* *>: For the case where b must be numeric, I can use

StringTemplate["a is `a`<* If[NumericQ[#b],\" and b is \",\"\"]*>`b`"]

but is there a way to tell whether a template parameter is present or absent, rather than numeric or otherwise?

Stephen Powell
  • 1,061
  • 5
  • 13

5 Answers5

6

You can use MissingQ instead of NumericQ:

template = StringTemplate["a is `a`<* If[!MissingQ[#b],\" and b is \",\"\"]*>`b`"];

Then:

template @ <|"a"->1|>
template @ <|"a"->1, "b"->2|>

"a is 1"

"a is 1 and b is 2"

Carl Woll
  • 130,679
  • 6
  • 243
  • 355
  • Thanks. I think this is the most direct solution to the question as posed. I hadn't appreciated what is actually happening when the key "b" is missing from the association: #b gives Missing["SlotAbsent","b"] and then TextString applied to that gives an empty string, which is spliced in. – Stephen Powell May 17 '19 at 07:40
  • It looks like the TemplateExpression isn't being syntax-colored, both in Mathematica and in Markdown/Prettify. Just wondering aloud. – Roman May 17 '19 at 09:25
4

Sorry for not using templates:

StringRiffle[ KeyValueMap[List]@<|"a" -> 1, "b" -> 2|>, " and ", " is "]
Kuba
  • 136,707
  • 13
  • 279
  • 740
2
st[sub_] := If[KeyMemberQ[sub, "b"], StringTemplate["a is `a` and b is `b`"], StringTemplate["a is `a`"]]@sub;
st[<|"a" -> 1|>]
st[<|"a" -> 1, "b" -> 2|>]

"a is 1"

"a is 1 and b is 2"

ALTERNATIVE:

Or you could make it more easily scalable for more different cases by defining:

strings = {{"a", "a is `a`"}, {"b", " and b is `b`"}};
compose[strings_, sub_] := Block[{st = ""},
  Do[If[KeyMemberQ[sub, element[[1]]], st = st <> element[[2]]];, {element, strings}];
  StringTemplate[st]@sub
]

the strings variable contains snippets to be added when first element is present. The function compose checks all cases and constructs the template

compose[strings, <|"a" -> 1|>]

"a is 1"

and

compose[strings, <|"a" -> 1, "b" -> 2|>]

"a is 1 and b is 2"

just add more entries to strings as you wish.

Kagaratsch
  • 11,955
  • 4
  • 25
  • 72
1

An old-fashioned solution?

st2[l_] := 
 l[[1, 1]] <> " is " <> ToString[l[[1, 2]]] <> 
  Table[" and " <> l[[n, 1]] <> " is " <> ToString[l[[n, 2]]], {n, 2, Length[l]}]

st2[{{"a", 1}}]

"a is 1"

st2[{{"a", 1}, {"b", 2}}]

"a is 1 and b is 2"

st2[{{"a", 1}, {"b", 2}, {"w", 5}, {"x", 10}}]

"a is 1 and b is 2 and w is 5 and x is 10"

MelaGo
  • 8,586
  • 1
  • 11
  • 24
0

Here's an implementation that uses a single StringTemplate and no (* *):

With[{st = 
   StringTemplate["a is `a` and b is `b`",
      InsertionFunction -> Replace[y : Except[_Missing] :> TextString[y]],
      CombinerFunction -> (StringJoin @@ BlockMap[Replace[{_, _Missing} -> Nothing], #, 2] &)]},
   {st[<|"a" -> 1, "b" -> 2|>], st[<|"a" -> 1|>]}
]

(* {"a is 1 and b is 2", "a is 1"} *)

The InsertionFunction makes sure that Missing["SlotAbsent",_] is not converted to "", and the CombinerFunction drops anything that is _Missing, and the preceding string.

Stephen Powell
  • 1,061
  • 5
  • 13