45
$\begingroup$

I have a function that takes two inputs and processes them for a single output. What I need is one that can take a varying number of inputs. and process them to a single output. Is this possible in Mathematica?

 DatasetAverage[inputData_, inputData2_] := Block[{dataAvg, v, w},
   v = Length[inputData];
   w = Length[inputData2];
   If [v != w,
    Print["DatasetAverage: Data sample sizes do not match"]];
   dataAvg = Table[Mean[{inputData[[i]], inputData2[[i]]}], {i, 1, Length[inputData2]}]
 ]
$\endgroup$
3
  • 1
    $\begingroup$ You may want to wait for other answers before accepting. I am sure the regulars here can offer more refined answers. $\endgroup$ Commented Jun 9, 2012 at 1:44
  • 1
    $\begingroup$ I discussed multiple-arg functions briefly in my book, which may add a little more info to the excellent answers you received here. $\endgroup$ Commented Jun 9, 2012 at 9:58
  • $\begingroup$ thanks @Leonid Shifrin I will review it! $\endgroup$ Commented Jun 10, 2012 at 17:02

4 Answers 4

51
$\begingroup$

Yes, for both named patterns and pure functions.

Pure functions

You can see that inherently they accept multiple arguments but discard those that are not used:

{#} &[1, 2, 3]  (* out: {1} *)

The object ##, (SlotSequence), represents all arguments wrapped in Sequence, e.g. Sequence[1, 2, 3]. (Internally it doesn't use Sequence but it behaves similarly in most places.)

{##} &[1, 2, 3]  (* out: {1, 2, 3} *)

You can combine # and ##, including their numbered forms:

{"first" -> #, "rest" -> {##2}, "all" -> {##}} &[1, 2, 3]
{"first" -> 1, "rest" -> {2, 3}, "all" -> {1, 2, 3}}

Named patterns

Using Blank*:

_ (Blank)
__ (BlankSequence)
___ (BlankNullSequence):

f[a_, b__] := {"first" -> a, "rest" -> {b}}

f[1, 2, 3] (* out: {"first" -> 1, "rest" -> {2, 3}} *)

__ requires an argument to be present while ___ does not:

f[1] (* out: f[1] *)

g[a_, b___] := {"first" -> a, "rest" -> {b}}

g[1] (* out: {"first" -> 1, "rest" -> {}} *)

Multiple variable length named patterns can be given and will by default be matched shortest first:

h[a_, b__, c__] := {"a" -> a, "b" -> {b}, "c" -> {c}}

h[1, 2, 3, 4, 5]  (* out: {"a" -> 1, "b" -> {2}, "c" -> {3, 4, 5}} *)

This can be controlled with Shortest and Longest:

i[a_, Longest[b__], c__] := {"a" -> a, "b" -> {b}, "c" -> {c}}

i[1, 2, 3, 4, 5] (* out: {"a" -> 1, "b" -> {2, 3, 4}, "c" -> {5}} *)

See this answer for an advanced use of these functions.

The Blank* functions are not the only way to create a variable length pattern. You can also use Repeated (..) or RepeatedNull (...):

j[x : _Real ..] := {x}

j[1.1, 1.2, 1.3]  (* out: {1.1, 1.2, 1.3} *)

These methods can be used in powerful ways such as destructuring.

Optional arguments

In addition to the variable length methods above one can make use of Optional parameters with or without the use of Default values. A basic example:

k[a_, b_: 3, c_: 5] := {a, b, c}

k[1]
k[1, 2]
k[1, 2, 3]
{1, 3, 5}

{1, 2, 5}

{1, 2, 3}

In the example above there are two optional parameters. By default they are filled in sequential order, meaning that in k[1, 2] the 2 is bound to b. This also can be controlled with Shortest and Longest as noted in the section above.

In a limited way optional arguments can also be used for pure functions:

Default can be used to set the default values for a given function, rather than specifying them as part of each function definition, but it must be used before they are defined, and it cannot be used to change them for existing definitions. (Why does Default behave like this?)

Default[m, 1] = 1;
Default[m, 2] = 3;
Default[m, 3] = 5;

m[a_., b_., c_.] := {a, b, c}

m[]
m[2]
m[2, 4]
m[2, 4, 6]
{1, 3, 5}

{2, 3, 5}

{2, 4, 5}

{2, 4, 6}

Also see:

Related Q&A's of a more advanced nature:

$\endgroup$
12
  • $\begingroup$ Thanks very much! Looks like I will be re-writing some code to take advantage of these features! $\endgroup$ Commented Jun 9, 2012 at 2:42
  • $\begingroup$ I saw the pure functions and instantly knew who wrote the answer... $\endgroup$ Commented Jun 9, 2012 at 3:02
  • $\begingroup$ @Brett I hope that's a good thing. :-) $\endgroup$
    – Mr.Wizard
    Commented Jun 9, 2012 at 7:15
  • 1
    $\begingroup$ @Mr.Wizard I found a slightly better way to do the same on this link: f[a__,b__] /; (Length@{a} == Length@{b})] := "ok". Maybe you can include this in your answer for completeness. Thanks again! $\endgroup$
    – sinner
    Commented Sep 21, 2016 at 14:28
  • 1
    $\begingroup$ @IonSme _. needs a matching Default definition set, in case the argument is omitted. You can specify a local default using x_: default. If you want the optional argument to disappear from the right hand side if it is omitted on the left, you can use something like x : Repeated[_, {0, 1}] or x_ | ___. Perhaps also read (44084). $\endgroup$
    – Mr.Wizard
    Commented May 13, 2019 at 0:33
10
$\begingroup$

Just one supplementary remark regarding conditions on the argument list:

Functions with a variable number of arguments can be very useful, and patterns like x__ allow you to define them easily.

But while the number of arguments may not be fixed, there may still be restrictions or limits on the allowed number. This can be enforced by using a condition as shown here:

f[x__] := Total@Apply[Times, Partition[{x}, 2], 2] /; EvenQ[Length[{x}]]

In this example, f is intended to form pairs from the arguments, multiply the pairs and add them. That's why there is a condition appended to the definition with /; - it insures that the definition is not used unless an even number of arguments is supplied.

If the condition isn't met, the function returns unevaluated - unless you provide another definition for a function with the same name with a different condition.

$\endgroup$
1
9
$\begingroup$

This may be close to what you want:

datasetAverage[inputDataList_] :=
  If[
    Equal @@ Map[Length, inputDataList] == False,
    Print["DatasetAverage: Data sample sizes do not match"],
    Mean[inputDataList]
  ]

Some sample inputs:

In[10]:= datasetAverage[{{1,2,3},{4,5,6},{7,8,9}}]
Out[10]= {4,5,6}

datasetAverage[{{1,2,3},{4,5,6,7}}]
DatasetAverage: Data sample sizes do not match

Edit

My earlier version of datasetAverage was defined using BlankSequence, but it did not make use of the fact that it can take an unspecified number of parameters. It is now rewritten using Blank.

Here is one version which uses BlankSequence:

datasetAverage2[inputDataList__] :=
  If[
    Equal @@ Map[Length, {inputDataList}] == False, 
    Print["DatasetAverage: Data sample sizes do not match"], 
    Mean[{inputDataList}]
  ]

The only difference is that I now enclose every occurrence of inputDataList in parentheses.

In[9]:= datasetAverage2[{1,2,3},{4,5,6},{7,8,9}]
Out[9]= {4,5,6}
$\endgroup$
4
  • $\begingroup$ Thank you! What allows for the multiple inputs? $\endgroup$ Commented Jun 9, 2012 at 1:38
  • 1
    $\begingroup$ @RHall I used the BlankSequence pattern, which stands for any sequence of one or more expressions. So now I can feed in a sequence of datasets. $\endgroup$ Commented Jun 9, 2012 at 1:40
  • $\begingroup$ I'm rather confused by your edit. Why did you convert this to a single argument function? $\endgroup$
    – Mr.Wizard
    Commented Jun 9, 2012 at 3:38
  • $\begingroup$ @Mr.Wizard I mixed up two ideas when I wrote up the answer. The original function does not actually require the use of BlankSequence. In fact, it fails if I evaluate datasetAverage[{1,2,3},{4,5,6}]. $\endgroup$ Commented Jun 9, 2012 at 3:48
8
$\begingroup$

Mean already does what your DataSetAverage is expected to do. From docs Mean > More Information:

enter image description here

that is, Mean map-threads over sub-lists in its argument.

So

Mean[{{a, b, c}, {u, v, w}}]

gives

enter image description here

and

Mean[{{a, b, c}, {u, v, w, x}}]

throws the error

enter image description here

So, you can use something like

ClearAll[dtAvrg]; 
dtAvrg[lst__List] :=  Check[Mean[{lst}], "DatasetAverage: Data sample sizes do not match",  Mean::rectt]  

examples:

 dtAvrg[{a, b, c}, {u, v, w}, {x, y, z}] 

returns

enter image description here

and

 dtAvrg[{1, 2, 3}, {4, 5, 6}, {7, 8, 9}]

gives {4,5,6}.

For lists with unequal lengths,

dtAvrg[{a, b, c}, {u, v, w}, {x, y, z, t}] 

returns

Mean::rectt: Rectangular array expected at position 1 in Mean[{{a,b,c},{u,v,w},{x,y,z,t}}].
"DatasetAverage: Data sample sizes do not match"  

If you like you can Quiet the error message and display your message only:

 Quiet@dtAvrg[{a, b, c}, {x, y, z, t}] 
 (* gives:  "DatasetAverage: Data sample sizes do not match" *)   
$\endgroup$
3
  • $\begingroup$ Great tip Thank you! $\endgroup$ Commented Jun 9, 2012 at 10:54
  • $\begingroup$ +1 Your method is much cleaner than mine. It is good to know about Check too. $\endgroup$ Commented Jun 9, 2012 at 17:27
  • 1
    $\begingroup$ @MichaelWijaya, thank you for the vote. $\endgroup$
    – kglr
    Commented Jun 9, 2012 at 17:36

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.