Skip to main content

programming - Struct equivalent in Mathematica?


I really miss having something like a struct in Mathematica. I know of (and regularly use) a couple of programming techniques which feel like a struct (e.g., using downvalues), but are ultimately unsatisfactory (perhaps I'm using downvalues incorrectly). What programming approaches are available which provide similar functionality to a struct?


Here's an abbreviated (and hopefully not too obtuse) example of how I use downvalues to emulate a struct. In this case, I'm distinguishing between TLC and TEC (these are sets of parameters for two different phases of a Moon mission, trans-lunar cruise and trans-earth cruise):


deadBandWidth[X][TLC] ^= 10. \[Degree];
deadBandWidth[Y][TLC] ^= 10. \[Degree];
deadBandWidth[Z][TLC] ^= 20. \[Degree];
sunSearchAngle[Z][TLC] ^= 230. \[Degree];
sunSearchRate[Z][TLC] ^= 1. \[Degree]/Second;
sunSearchAngle[X][TLC] ^= 75. \[Degree];
sunSearchRate[X][TLC] ^= 1. \[Degree]/Second;

safingSpinRate[TLC] ^= (360. \[Degree])/Day;
sunVector[TLC] ^= {-Cos[45. \[Degree]], 0., Sin[45. \[Degree]]};
safingSpinAxis[TLC] ^= sunVector[TLC];

deadBandWidth[X][TEC] ^= 20. \[Degree];
deadBandWidth[Y][TEC] ^= 20. \[Degree];
deadBandWidth[Z][TEC] ^= 20. \[Degree];
sunSearchAngle[Z][TEC] ^= 230. \[Degree];
sunSearchRate[Z][TEC] ^= 1. \[Degree]/Second;
sunSearchAngle[X][TEC] ^= 75. \[Degree];

sunSearchRate[X][TEC] ^= 1. \[Degree]/Second;
safingSpinRate[TEC] ^= (360. \[Degree])/Hour;
sunVector[TEC] ^= {0., 0., +1.};
safingSpinAxis[TEC] ^= sunVector[TEC];

?TLC
Global`TLC
safingSpinAxis[TLC]^={-0.707107,0.,0.707107}
safingSpinRate[TLC]^=6.28319/Day
sunVector[TLC]^={-0.707107,0.,0.707107}

deadBandWidth[X][TLC]^=0.174533
deadBandWidth[Y][TLC]^=0.174533
deadBandWidth[Z][TLC]^=0.349066
sunSearchAngle[X][TLC]^=1.309
sunSearchAngle[Z][TLC]^=4.01426
sunSearchRate[X][TLC]^=0.0174533/Second
sunSearchRate[Z][TLC]^=0.0174533/Second

Answer



Update: Mathematica 10 has introduced Association, which can be used as a close equivalent of structs.


params = <| "par1" -> 1, "par2" -> 2 |>


params["par1"]
(* ==> 1 *)

In version 10 pure functions can have named arguments, and can be effectively used as expression templates where the slots can be populated from an association. This is similar to the technique I describe in the original version of this post (below the line).


#par1 + #par2 & [params]

will evaluate to 1 + 2 then to 3.


That said, my personal workflow still fits better with the approach described below the line (withRules). The reason for this is that I tend to build up calculations interactively and incrementally. This means that I do not start by writing the equivalent of an expression template (which would require thinking ahead...). Instead I start with all the values explicitly written out, and later I replace them with a global variable. This global variable can be simply Unset, and given a local value using withRules, then eventually changed into a function argument.





Quoting the OP's comment:



Most of the work I do involves constructing mathematical models and then testing various scenarios against those models. I'd like to be able to populate a particular scenario and then pass that scenario to a model. I'd also like to be able to copy that scenario, modify one or more parameters, and then pass the new scenario to the model.



The requirement, as I understand, is to be able to pass many parameter values around in a structured way. Lists of rules are convenient for this:


params = {par1 -> 1, par2 -> 2, par3 -> {x,y,z}}

They can be extracted like this:


par1 /. params


(* ==> 1 *)

Once I wrote a function for substituting such parameter lists into bigger pieces of code:


ClearAll[withRules]
SetAttributes[withRules, HoldAll]
withRules[rules_, expr_] :=
First@PreemptProtect@Internal`InheritedBlock[
{Rule, RuleDelayed},
SetAttributes[{Rule, RuleDelayed}, HoldFirst];
Hold[expr] /. rules

]

It can be used like this:


withRules[params,
par1 + par2
]

(* ==> 3 *)

withRules can contain complex code inside, and all occurrences of par1, par2, etc. will be substituted with the values from the parameter list.



We can also write a function for easily modifying only a single parameter (from the whole list), and returning a new parameter list. Here's a simple implementation:


setParam[paramList_, newRules_] :=
DeleteDuplicates[Join[newRules, paramList],
First[#1] === First[#2] &]

Example usage:


setParam[params, {par2 -> 10}]

(* ==> {par2 -> 10, par1 -> 1, par3 -> {x, y, z}} *)


Another list which has a different value for par2 is returned.




If needed, this could be extended to support more complex, structured lists such as { par1 -> 1, group1 -> {par2x -> 10, par2y -> 20}}, much how like the built-in option-handling works.




Addendum by celtschk: It's possible to extract a value from a list of rules using OptionValue as well: OptionValue[params, par1].


Comments

Popular posts from this blog

functions - Get leading series expansion term?

Given a function f[x] , I would like to have a function leadingSeries that returns just the leading term in the series around x=0 . For example: leadingSeries[(1/x + 2)/(4 + 1/x^2 + x)] x and leadingSeries[(1/x + 2 + (1 - 1/x^3)/4)/(4 + x)] -(1/(16 x^3)) Is there such a function in Mathematica? Or maybe one can implement it efficiently? EDIT I finally went with the following implementation, based on Carl Woll 's answer: lds[ex_,x_]:=( (ex/.x->(x+O[x]^2))/.SeriesData[U_,Z_,L_List,Mi_,Ma_,De_]:>SeriesData[U,Z,{L[[1]]},Mi,Mi+1,De]//Quiet//Normal) The advantage is, that this one also properly works with functions whose leading term is a constant: lds[Exp[x],x] 1 Answer Update 1 Updated to eliminate SeriesData and to not return additional terms Perhaps you could use: leadingSeries[expr_, x_] := Normal[expr /. x->(x+O[x]^2) /. a_List :> Take[a, 1]] Then for your examples: leadingSeries[(1/x + 2)/(4 + 1/x^2 + x), x] leadingSeries[Exp[x], x] leadingSeries[(1/x + 2 + (1 - 1/x...

How to thread a list

I have data in format data = {{a1, a2}, {b1, b2}, {c1, c2}, {d1, d2}} Tableform: I want to thread it to : tdata = {{{a1, b1}, {a2, b2}}, {{a1, c1}, {a2, c2}}, {{a1, d1}, {a2, d2}}} Tableform: And I would like to do better then pseudofunction[n_] := Transpose[{data2[[1]], data2[[n]]}]; SetAttributes[pseudofunction, Listable]; Range[2, 4] // pseudofunction Here is my benchmark data, where data3 is normal sample of real data. data3 = Drop[ExcelWorkBook[[Column1 ;; Column4]], None, 1]; data2 = {a #, b #, c #, d #} & /@ Range[1, 10^5]; data = RandomReal[{0, 1}, {10^6, 4}]; Here is my benchmark code kptnw[list_] := Transpose[{Table[First@#, {Length@# - 1}], Rest@#}, {3, 1, 2}] &@list kptnw2[list_] := Transpose[{ConstantArray[First@#, Length@# - 1], Rest@#}, {3, 1, 2}] &@list OleksandrR[list_] := Flatten[Outer[List, List@First[list], Rest[list], 1], {{2}, {1, 4}}] paradox2[list_] := Partition[Riffle[list[[1]], #], 2] & /@ Drop[list, 1] RM[list_] := FoldList[Transpose[{First@li...

front end - keyboard shortcut to invoke Insert new matrix

I frequently need to type in some matrices, and the menu command Insert > Table/Matrix > New... allows matrices with lines drawn between columns and rows, which is very helpful. I would like to make a keyboard shortcut for it, but cannot find the relevant frontend token command (4209405) for it. Since the FullForm[] and InputForm[] of matrices with lines drawn between rows and columns is the same as those without lines, it's hard to do this via 3rd party system-wide text expanders (e.g. autohotkey or atext on mac). How does one assign a keyboard shortcut for the menu item Insert > Table/Matrix > New... , preferably using only mathematica? Thanks! Answer In the MenuSetup.tr (for linux located in the $InstallationDirectory/SystemFiles/FrontEnd/TextResources/X/ directory), I changed the line MenuItem["&New...", "CreateGridBoxDialog"] to read MenuItem["&New...", "CreateGridBoxDialog", MenuKey["m", Modifiers-...