Message Boards Message Boards

0
|
2864 Views
|
24 Replies
|
6 Total Likes
View groups...
Share
Share this post:

Write a function which accepts a list or separate elements as arguments

Posted 1 year ago

I want to write a function which can accept a list or separate elements as arguments. If the user inputs separate elements, then they will be first converted into a list for further processing.

How to implement the above operation process?

Regards, Zhao

POSTED BY: Hongyi Zhao
24 Replies
Posted 1 year ago

Thank you for pointing this out. I come up with the following solution based on your tricks and tips:

In[496]:= AffModOneDotOnLeft // ClearAll;
AffModOneDotOnLeft[{x__?MatrixQ} | x__?MatrixQ] := Module[{dim,elms},
elms={x};
dim=Dimensions[{x}[[1]]][[1]]-1;

TransformationMatrix[
    AffineTransform[{#[[1 ;; dim, 1 ;; dim]], 
        Mod[#[[1 ;; dim, dim + 1]], 1]}] &@(Dot@@elms)] // Expand // 
  Together // FullSimplify
]

p={{-1, -1, 1, 3/4}, {1, -1, -1, -(5/4)}, {-1, 1, -1, -(5/4)}, {0, 0, 0,
   1}};
q={{-1, 0, 0, 0}, {0, -1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}};
r=Inverse[p];

MapAt[Mod[#,1]&, p . q . r,{1;;3,4}]==AffModOneDotOnLeft[p . q,r]==AffModOneDotOnLeft[p,q . r]==AffModOneDotOnLeft[p,q,r]==AffModOneDotOnLeft[{p,q,r}]

Out[501]= True
POSTED BY: Hongyi Zhao
Posted 1 year ago

Zhao, this is from my first reply to your original post:

f[{a_, b_, c_} | PatternSequence[a_, b_, c_]] := {a, b, c}

Try it with your p, q, r data. You will find it gives the expected result.

POSTED BY: Hans Milton
Posted 1 year ago

Dear Hans Milton,

Thank you for your clarification. The following code does the trick:

In[898]:= f[{x__} | PatternSequence[x__]] := {x}
p={{-1, -1, 1, 3/4}, {1, -1, -1, -(5/4)}, {-1, 1, -1, -(5/4)}, {0, 0, 0,
1}};
q={{-1, 0, 0, 0}, {0, -1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}};
r=Inverse[p];

f[{p, q, r}]
%==f[p, q, r]

Out[902]= {{{-1, -1, 1, 3/4}, {1, -1, -1, -(5/4)}, {-1, 
1, -1, -(5/4)}, {0, 0, 0, 1}}, {{-1, 0, 0, 0}, {0, -1, 0, 0}, {0, 
0, 1, 0}, {0, 0, 0, 1}}, {{-(1/2), 
0, -(1/2), -(1/4)}, {-(1/2), -(1/2), 
0, -(1/4)}, {0, -(1/2), -(1/2), -(5/4)}, {0, 0, 0, 1}}}

Out[903]= True
POSTED BY: Hongyi Zhao
Posted 1 year ago

And, as already discussed, this can be simplified to:

f[{x__} | x__] := {x}
POSTED BY: Hans Milton
Posted 1 year ago

Hi Eric Rimbey,

Thank you very much for your in-depth and systematic comments, explanations, and analysis. Base on your above reply, I come up with the following code snippet which meets my requirement:

In[401]:= myFunc[x_List] := Module[{dim,elms},
elms=x;
dim=Dimensions[x[[1]]][[1]];
  {dim,elms}
]
myFunc[args__] := myFunc[{args}]

p={{-1, -1, 1, 3/4}, {1, -1, -1, -(5/4)}, {-1, 1, -1, -(5/4)}, {0, 0, 0,
   1}};
q={{-1, 0, 0, 0}, {0, -1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}};
r=Inverse[p];

myFunc[p,q,r]
myFunc[{p,q,r}]
%==%%

Out[406]= {4, {{{-1, -1, 1, 3/4}, {1, -1, -1, -(5/4)}, {-1, 
    1, -1, -(5/4)}, {0, 0, 0, 1}}, {{-1, 0, 0, 0}, {0, -1, 0, 0}, {0, 
    0, 1, 0}, {0, 0, 0, 1}}, {{-(1/2), 
    0, -(1/2), -(1/4)}, {-(1/2), -(1/2), 
    0, -(1/4)}, {0, -(1/2), -(1/2), -(5/4)}, {0, 0, 0, 1}}}}

Out[407]= {4, {{{-1, -1, 1, 3/4}, {1, -1, -1, -(5/4)}, {-1, 
    1, -1, -(5/4)}, {0, 0, 0, 1}}, {{-1, 0, 0, 0}, {0, -1, 0, 0}, {0, 
    0, 1, 0}, {0, 0, 0, 1}}, {{-(1/2), 
    0, -(1/2), -(1/4)}, {-(1/2), -(1/2), 
    0, -(1/4)}, {0, -(1/2), -(1/2), -(5/4)}, {0, 0, 0, 1}}}}

Out[408]= True

Side note, I really don't understand the desire to use Module here at all.

Could you please give some more explanation to your above point of view?

POSTED BY: Hongyi Zhao
Posted 1 year ago

Module is a scoping construct. It sets up a "local scope" (as local as anything can be in Mathematica, anyway). So, the question is, under what circumstances would you want to do that? Well, I'm sure opinions vary, but for me it would be if I really needed to implement an imperative algorithm, something with mutable variables or a highly sequential nature. You just don't have any need for that here. If you want to introduce local variables as a way to clarify your code, the With construct does a better job of that, in my opinion.

Let's walk through an analysis of your code:

(* initial *)
myFunc[x_List] :=
 Module[{dim, elms}, 
  elms = x;
  dim = Dimensions[x[[1]]][[1]];
  {dim, elms}]

Initialization is straightforward, and you don't mutate elms, so you could simplify to this:

(* use initialzation in Module *)
myFunc[x_List] := Module[{dim = Dimensions[x[[1]]][[1]]}, {dim, x}]

You also don't mutate dim, so I'd bias toward the lighter weight With:

(* use constants instead of variables *)
myFunc[x_List] := With[{dim = Dimensions[x[[1]]][[1]]}, {dim, x}]

At this point, I don't think having dim is actually clarifying anything. So you could just do all of this directly:

(* just define the expression you want directly *)
myFunc[x_List] := {Dimensions[x[[1]]][[1]], x}
(* or a bit more directly *)
myFunc[x_List] := {Last@Dimensions@First@x, x}

Now, it's weird that your use cases passes in three matrices (optionally collected together in a list), but you only need one of them, specifically the first one. Without more context (e.g. do you expect exactly three square matrices of the same dimensions?), I can't be sure what further simplifications to suggest.

POSTED BY: Eric Rimbey
Posted 1 year ago

e.g. do you expect exactly three square matrices of the same dimensions?

All square matrices here should be affine transformation matrices of the same dimension, such as 3, in this case, they should be as follows:

In[3]:= AffineTransform[{Array[a, {3, 3}], 
   Array[b, {3}]}] // TransformationMatrix

Out[3]= {{a[1, 1], a[1, 2], a[1, 3], b[1]}, {a[2, 1], a[2, 2], 
  a[2, 3], b[2]}, {a[3, 1], a[3, 2], a[3, 3], b[3]}, {0, 0, 0, 1}}

I don't know how to accurately check these conditions that these parameters should meet, so I used an inappropriate judgment method.

POSTED BY: Hongyi Zhao
Posted 1 year ago

Let me first apologize for not having a good example with my description and let me give you an example and a specific explanation of what I actually want to achieve:

In[403]:= g[x : {__} | __] := {x} // Flatten

p={{-1, -1, 1, 3/4}, {1, -1, -1, -(5/4)}, {-1, 1, -1, -(5/4)}, {0, 0, 0,
   1}};
q={{-1, 0, 0, 0}, {0, -1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}};
r=Inverse[p];

g[p,q,r]
g[{p,q,r}]

Out[407]= {-1, -1, 1, 3/4, 1, -1, -1, -(5/4), -1, 1, -1, -(5/
  4), 0, 0, 0, 1, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -(
  1/2), 0, -(1/2), -(1/4), -(1/2), -(1/2), 0, -(1/4), 0, -(1/2), -(1/
  2), -(5/4), 0, 0, 0, 1}

Out[408]= {-1, -1, 1, 3/4, 1, -1, -1, -(5/4), -1, 1, -1, -(5/
  4), 0, 0, 0, 1, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -(
  1/2), 0, -(1/2), -(1/4), -(1/2), -(1/2), 0, -(1/4), 0, -(1/2), -(1/
  2), -(5/4), 0, 0, 0, 1}

But actually, the output above is not what I wanted. What I really want is the following result:

In[409]:= {p,q,r}

Out[409]= {{{-1, -1, 1, 3/4}, {1, -1, -1, -(5/4)}, {-1, 
   1, -1, -(5/4)}, {0, 0, 0, 1}}, {{-1, 0, 0, 0}, {0, -1, 0, 0}, {0, 
   0, 1, 0}, {0, 0, 0, 1}}, {{-(1/2), 
   0, -(1/2), -(1/4)}, {-(1/2), -(1/2), 
   0, -(1/4)}, {0, -(1/2), -(1/2), -(5/4)}, {0, 0, 0, 1}}}

And based on my tries, it seems that the following method suggested by Michael Rogers does the trick:

In[465]:= f1 // ClearAll;
f1[x_List] := x;
f1[args__] := Module[{elms},
elms=f1[{args}]
] 

p={{-1, -1, 1, 3/4}, {1, -1, -1, -(5/4)}, {-1, 1, -1, -(5/4)}, {0, 0, 0,
   1}};
q={{-1, 0, 0, 0}, {0, -1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}};
r=Inverse[p];

f1[p,q,r]=={p,q,r}
f1[{p,q,r}]=={p,q,r}

Out[471]= True

Out[472]= True

However, I don't know if there is a more concise and compact implementation.

On the other hand, I also tried the following example:

f // ClearAll;
f[x_List] := x;
f[args__] := Module[{dim,elms},
elms=f[{args}];
dim=5;
  {dim,elms}
]

args={{a},{b},{c}};
f[args]
f[args//Sequence]
f[{a},{b},{c}]

{{a}, {b}, {c}}

{{a}, {b}, {c}}

{5, {{a}, {b}, {c}}}

As you can see, the variable dim (5 in this example) is only returned in the last call. I am very confused about the logic of the code. Any hints will be appreciated.

POSTED BY: Hongyi Zhao
Posted 1 year ago

I think it would really be valuable for you to look at intermediate results, maybe use Trace, and come up with test cases that are both simpler and more robust. Side note, I really don't understand the desire to use Module here at all.

Anyway, just based on your original description,

a function which can accept a list or separate elements as arguments

I would have come up with this:

myFunc[list_List] := processTheStuff[list];
myFunc[args__] := myFunc[{args}]

I assume that you want the function to behave "the same" for each type of argument. My standard way of doing that is to choose which form is canonical, and use that form in the definition of the other. In this case, it seemed simpler to make the list form canonical.

Now, why does this even work at all? Because the Mathematica evaluator looks at the DownValues for a function in a particular order. It looks from most specific pattern to least specific pattern. The heuristic for "specificity of pattern" isn't always obvious (and in tie-breaker situations the definition evaluated first comes before the one evaluated later), but you can always just look at the DownValues and know that they will be inspected in that order. In this particular case, myFunc[list_List] is unambiguously more specific than myFunc[args__].

Let's test it. Here is where I think you get yourself in trouble. Start your testing with simple examples.

myFunc[a] (* processTheStuff[{a}] *)
myFunc[{a}] (* processTheStuff[{a}] *)
myFunc[a, b] (* processTheStuff[{a, b}] *)
myFunc[{a, b}] (* processTheStuff[{a, b}] *)
myFunc[{a}, b] (* processTheStuff[{{a}, b}] *)
myFunc[{a}, {b}] (* processTheStuff[{{a}, {b}}] *)
myFunc[{{a}, {b}}] (* processTheStuff[{{a}, {b}}] *)
(* and so on until you're satisfied *)

Side note, I'm dismissing the whole Flatten stuff, because that would fundamentally change the structure of your inputs, and so we wouldn't get the same behavior for each type of argument. If you need Flatten, that suggests a completely different question, or at least it seems that way to me.

Okay, let's try to answer your latest question. Mutating one of the definitions as you did is a good way to help understand the pattern matching. I'd be even more explicit about it. Let's redefine myFunc:

ClearAll[myFunc];
myFunc[list_List] := processA[list];
myFunc[args__] := processB[{args}];

And here's your test input:

args = {{a}, {b}, {c}}

Now let's test:

myFunc[args] (* processA[{{a}, {b}, {c}}] *)
myFunc[{a}, {b}, {c}] (* processB[{{a}, {b}, {c}}] *)

So that's pretty clear.

What about args // Sequence? Well, I think you think this is doing something different than it is. This is just wrapping args in the head Sequence. When Sequence expressions are encountered "inside" other expressions, the Sequence effectively gets stripped and you're left with the ... well, sequence that was inside Sequence.

args // Sequence (* Sequence[{{a}, {b}, {c}}] *)
JustToTest[args // Sequence] (* JustToTest[{{a}, {b}, {c}}] *)
myFunc[args // Sequence] (* processA[{{a}, {b}, {c}}] *)

What you might have thought you were doing was replacing the List head with Sequence. But you do that with Apply, aka @@

Sequence @@ args (* Sequence[{a}, {b}, {c}] *)
JustToTest[Sequence @@ args] (* JustToTest[{a}, {b}, {c}] *)
myFunc[Sequence @@ args] (* processB[{{a}, {b}, {c}}] *)
POSTED BY: Eric Rimbey
Posted 1 year ago

Zhao, looking at yesterdays discussion I have seen that PatternSequence is not really necessary in this code:

g[x : {__} | PatternSequence[__]] := {x} // Flatten

The code below works as well:

g[x : {__} | __] := {x} // Flatten
POSTED BY: Hans Milton
Posted 1 year ago

Got it. Thank you very much again.

POSTED BY: Hongyi Zhao
Posted 1 year ago

The only difference is the use of the following two parameter passing methods:

{x__}

x : {__}

They all seem to be a list, so I still don't understand the difference.

POSTED BY: Hongyi Zhao
Posted 1 year ago

Ok, I will try to be a bit more clear. This is the code:

f[{x__} | PatternSequence[x__]] := {x}

When the input argument is a list, the x represents the sequence of elements in the the list, not the whole list itself. In the function body that sequence of elements is inserted between curly brackets. The result is then just a copy of the input list. No need to flatten it.

The other example:

h[x : {__} | PatternSequence[__]] := {x} // Flatten

Here the x represents the whole list. When inserted between curly brackets the result is a list within a list. That needs to be flattened.

POSTED BY: Hans Milton
Posted 1 year ago

Hi Hans Milton,

As you can see, in the first example, a flattened list is always returned without using the Flatten command. Why?

POSTED BY: Hongyi Zhao
Posted 1 year ago

That is because if there is nothing to be flattened the function Flatten cannot do anything. It just returns the input list.

POSTED BY: Hans Milton

The spec in the OP is unclear whether f[{a, b}, c] should become f[{{a, b}, c}] (nested list) or f[{a, b, c}] (flattened list).

Nested list contruction

f1 // ClearAll;
f1[x_List] := x;
f1[args__] := f1[{args}];

Flattened list construction

f2 // ClearAll;
f2[x_List] := x;
f2[args__] := f2[Flatten@{args}]; (* or f2[args___] if no arguments are ok *)

Note that f2[{a, {b, c}, d}] yields {a, {b, c}, d} and f2[{a, {b, c}}, d] yields {a, b, c, d}. If the second, flattened list is desired, one could change the first definition to f2[x_?VectorQ] := x. I might use Flatten[x] inside the body of the first definition for f2[x_List]. In fact, for the flattened-list case, I would do the following:

f3 // ClearAll;
f3[args__] := (* or f3[arg___ *)
  With[{x = Flatten@{args}},
   (* body of function *)
   x];
POSTED BY: Michael Rogers
Posted 1 year ago

Nice trick. It does the job, as shown below:

In[183]:= f3 // ClearAll;
f3[args__] := (* or f3[arg___ *)
  Module[{x = Flatten@{args}},
   (* body of function *)
   x];

In[185]:= f3[{a, {b, c}, d}] 
f3[{a, {b, c}}, d]
f3[{p, q, r}]
f3[p, q, r]

Out[185]= {a, b, c, d}

Out[186]= {a, b, c, d}

Out[187]= {p, q, r}

Out[188]= {p, q, r}

N.B.: I changed With to Module, considering that the latter is more robust.

POSTED BY: Hongyi Zhao

Cool.

My view is that With and Module are different and have different uses. One is not more robust than the other. I used With to mimic the injection of arguments normally performed when a function is evaluated. If such injection is not needed, then usually (maybe always?) one can substitute Module. Of course an important restriction in With is that the parameter values cannot be changed (I suspect that's why With is slightly faster than Module).

Here is a use-case in which Module seems unfavored:

f3 // ClearAll;
f3[args__] := With[{eqs = Flatten@{args}},
   ContourPlot[eqs, {x, -2, 2}, {y, -2, 2}]];

f4 // ClearAll;
f4[args__] := Module[{eqs = Flatten@{args}},
   ContourPlot[eqs, {x, -2, 2}, {y, -2, 2}]];

f3[x + y == 2, x^2 + y^2 == 2, x^3 + y^3 == 2]

f4[x + y == 2, x^2 + y^2 == 2, x^3 + y^3 == 2]
POSTED BY: Michael Rogers
Posted 1 year ago

Nice example. But I still don't quite understand why the latter can't plot the corresponding functions in this case.

POSTED BY: Hongyi Zhao

ContourPlot[] analyzes its first argument in unevaluated form. Plot used to do this but has been recently changed. I'd have to check other plotters to see if they've changed. They all have the attribute HoldAll, which is normally used to keep arguments from being evaluated.

With injects the value of eqs, not a symbol. So the first argument of ContourPlot in f3[..] is a list of equations. ContourPlot analyzes this and plots a curve for each equation.

Module creates a localized symbol, and this symbol is what ContourPlot in f4[..] sees as its first argument. A symbol is not a list of equations, so ContourPlot assumes the symbol is a single numeric expression, a function of x and y. But it's not a numeric function and does not return a number when numbers for x and y are plugged in. So nothing is plotted. You can use ContourPlot[Evaluate[eqs],...] to force evaluation before the argument is passed.

POSTED BY: Michael Rogers
Posted 1 year ago

See my following testing based on your above examples:

In[160]:= (*You could use Alternatives together with PatternSequence*)
f[{x__} | PatternSequence[__]] := {x} 
f[{p, q, r}]
f[p, q, r]

Out[161]= {p, q, r}

Out[162]= {p, q, r}

In[163]:= (*Another example:*)
g[x : {__} | PatternSequence[__]] := {x}
g[{p, q, r}]
g[p, q, r]

Out[164]= {{p, q, r}}

Out[165]= {p, q, r}

As you can see, the former will always generate a list, while the latter will generate an embedded list when the argument is a list. Why is there such a difference?

Regards, Zhao

POSTED BY: Hongyi Zhao
Posted 1 year ago

When you modified my alternative example you forgot to include Flatten. Try this:

h[x : {__} | PatternSequence[__]] := {x} // Flatten
POSTED BY: Hans Milton
Posted 1 year ago

Another example:

In[43]:= g[x : {__} | PatternSequence[__]] := {x} // Flatten // Total

In[44]:= g[{p, q, r}]
Out[44]= p + q + r

In[45]:= g[p, q, r]
Out[45]= p + q + r
POSTED BY: Hans Milton
Posted 1 year ago

You could use Alternatives together with PatternSequence

In[19]:= f[{a_, b_, c_} | PatternSequence[a_, b_, c_]] := {a, b, c} //Total

In[20]:= f[{p, q, r}]
Out[20]= p + q + r

In[21]:= f[p, q, r]
Out[21]= p + q + r
POSTED BY: Hans Milton
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract