Elegant manipulation of the variables list

23

20

I have a list of global variables (some of them are indexed variables), for example:

varsH = Hold[U0[1], U0[2], B0, V0[1], V0[2]]

Or (if it is easier to handle) I can have them as a List of Strings:

varsS = {"U0[1]", "U0[2]", "B0", "V0[1]", "V0[2]"}

Now I need to be able to do the following things knowing only the position (i) of the variable in the list:

  1. Assign new value to the variable (the variable can already have a value)
  2. Clear the variable
  3. Get the current value of the variable
  4. Get the name of the variable as `String`

Additionally, I need to be able to

  5. Clear all the variables at once

What is the most elegant approach to this task? I know, it would be easier to have all the variables just as one indexed variable but I need names of the variables to be explicit.

By the term "elegant" I mean that every of the listed things should be written as short as possible (for a solution without helper functions). If there is no elegant way to avoid use of helper functions, they should be as clear as possible.

P.S. It is known that the variables should always be numerical or undefined.

Alexey Popkov

Posted 2012-09-07T08:14:08.740

Reputation: 50 220

Related: (280)

– Mr.Wizard – 2014-08-19T09:03:12.160

1Something like Scan[Clear, Hold[U0, B0, V0]] then? – J. M.'s ennui – 2012-09-07T08:24:46.747

Answers

17

I think that "elegant" should be syntax as close to the normal handling of symbols as possible.

I shall define a function, unimaginatively named bump, that has a syntax similar to Part but which allows operations on symbols by way of Unevaluated and UpSet. If you will consider other storage formats besides Hold[v1, v2, ...] e.g. Hold @ {v1, v2, ...} this might be simplified somewhat.

func_[a___, bump[lst_, idx___], b___] ^:= 
  func[a, #, b] & @ Part[List @@@ Unevaluated @@ {lst}, {1}, idx]

Examples:

varsH = Hold[U0[1], U0[2], B0, V0[1], V0[2]];

bump[varsH] = Range[5];    (* set all values *)

bump[varsH, 3] = 8;        (* reassignment to B0 *)

varsH[[3]]                 (* recall B0 *)

ToString @ bump[varsH, 3]  (* get name of B0 as String *)

bump[varsH, 3] =.          (* Unset B0 *)

bump[varsH] =.             (* Unset all *)

This works with other operations and shapes of lists too. (For consistency I think Hold @ {...} is better but I will use the original form.)

vars2 = Hold[a, b, {c1, c2}, d];

bump[vars2, 3, 1] = 5;
bump[vars2, 3, 2] = 7;
bump[vars2, 3] += 1;

List @@ vars2  (* show the result *)
{a, b, {6, 8}, d}
bump[vars2, 3] =.;
List @@ vars2
{a, b, {c1, c2}, d}

For a simpler function upon which this one is based see:

Assigning values to a list of variable names

Mr.Wizard

Posted 2012-09-07T08:14:08.740

Reputation: 259 163

1This is what I call "elegant"! I really enjoy your solution. The only doubt I have: are there possible side-effects of defining the UpValues in such a general way? – Alexey Popkov – 2013-01-10T13:15:28.030

1@Alexey Thanks. Yes, it would have side-effects. If you assume that bump is only used manually and not generated programmatically then it's probably okay. It would be safer no doubt to create a white-list of operations, or use an "environment" the way Leonid often does. – Mr.Wizard – 2013-01-10T13:18:09.357

@Simon Thank you, as I've said before that means a lot coming from you. Sorry I cost you an Accept. By the way, you can shorten the code in your answer, though perhaps less readably. For example: vars[[{1}]] /. _[x_] :> (x = 20). (I like _[x_] over _@x_ here only because it looks a little like an injection syringe which I think is neat for the "injector pattern.") – Mr.Wizard – 2013-01-11T00:58:36.983

On the other hand, _@x_ :> accurately represents a newbie's eye when he tries to parse the solution =P – rm -rf – 2013-01-11T01:01:00.433

@rm-rf lol -- do you think I should add some explanatory text? – Mr.Wizard – 2013-01-11T01:01:47.873

No, the answer's fine... was just continuing on your comment above :) – rm -rf – 2013-01-11T01:02:50.400

@Mr.Wizard, I'm always happy to lose an Accept to a better answer :-) I always thought that using a list as a part specification returned a list of parts, I didn't realise that it would use the head of the original expression. Hence my rather clunky construction using Extract to pull an element out of Hold and then wrap it back up in Hold again. – Simon Woods – 2013-01-11T12:42:11.210

@Simon (Thanks.) I can only assume many people have that misconception as this answer proved unexpectedly popular.

– Mr.Wizard – 2013-01-11T12:44:45.760

@Mr.Wizard, weirldy I knew that a Span part specification would retain the original head, but for some reason I though that a List part specification was a sort of special notation for "list of parts". It all makes much more sense now :-) – Simon Woods – 2013-01-11T13:56:48.877

11

Here's an approach similar to Simon Woods, but using Extract itself rather then following replacement rules:

 setTo[val_] := Function[var, var = val, HoldAll]
 mySymbolName = Function[var, ToString[Unevaluated[var]], HoldAll];

Which is then used as:

 a[1] = b = c = 1;
 vars = Hold[a, b, c];

 Extract[vars, 3, setTo[42]]
 Extract[vars, 3, mySymbolName]

And you can then clear the value using:

 Extract[vars, 3, Unset]

jVincent

Posted 2012-09-07T08:14:08.740

Reputation: 14 423

Sorry, disregard that comment, my brain was loose. +1 (I overlooked the fact that you of course Unset the symbol and wondered why it didn't appear to have the value 42.) – Mr.Wizard – 2013-01-10T13:14:10.683

1@Mr.Wizard I quickly pasted the code into a notebook to test figure out if something was wrong and also wondered why c wasn't getting set, I updated the answer slightly. :) – jVincent – 2013-01-10T13:17:01.480

6

a[1] = a[2] = b = c = 1;
vars = Hold[a[1], a[2], b, c];

Assign new value to the variable (the variable can already have a value)

Extract[vars, 1, Hold] /. Hold[x_] :> (x = 20)

Unset the variable

Extract[vars, 1, Hold] /. Hold[x_?ValueQ] :> (x =.)

Get the current value of the variable

Extract[vars, 1]

Get the name of the variable as String

Extract[vars, 1, Hold] /. Hold[x_] :> ToString[Unevaluated[x]]

Unset all variables

Cases[vars, x_?ValueQ :> (x =.), 1]

Simon Woods

Posted 2012-09-07T08:14:08.740

Reputation: 81 905

Some of the variables are indexed variables (U0[1] etc.) so Clear and SymbolName will not work. – Alexey Popkov – 2012-09-07T08:32:03.593

@AlexeyPopkov, oops I missed that! I'll have another look... – Simon Woods – 2012-09-07T08:43:20.863

Hold[x_[__]] | Hold[x_] :> Clear[x] in the new version clears all indexed variables with the same Head, not the one. – Alexey Popkov – 2012-09-07T09:15:12.823

The only workaround I see at the moment is: List @@ Replace[vars, x_?NumericQ :> Unset[x], {1}]. But it is not very elegant when only one variable should be cleared. – Alexey Popkov – 2012-09-07T09:24:50.437

For clearing one variable: Extract[vars, 1, Hold] /. Hold[x_?NumericQ] :> Unset[x] – Alexey Popkov – 2012-09-07T09:30:26.120

@AlexeyPopkov, updated with your suggestions. – Simon Woods – 2012-09-07T09:59:14.483

Would OP's "value" mean "numerical value"? If not, the pattern test NumericQ might be incorrect. – Sjoerd C. de Vries – 2012-09-07T14:20:38.377

@SjoerdC.deVries, good point. I've changed the code to use ValueQ instead. – Simon Woods – 2012-09-07T14:56:43.410

0

if you have Mathematica 10 use Association function:

asc=<|"U0[1]"-> U0[1], "U0[2]"-> U0[2], "B0"-> B0, "V0[1]"-> V01, "V0[2]"-> V0[2]|>

this new build in container function supports extracting its content by keys, which are strings in this case, and by indexes

asc = <|"U0[1]" -> U0[1], "U0[2]" -> U0[2], "B0" -> B0, "V0[1]" -> V01, "V0[2]" -> V0[2]|>;

(*1. Assign new value to the variable (the variable can already have a value)*)

asc["B0"] = "newB0"

(* <|"U0[1]" -> U0[1], "U0[2]" -> U0[2], "B0" -> "newB0", "V0[1]" -> V01,  "V0[2]" -> V0[2]|>*)

(*2. Clear the variable*)

Delete[asc, "B0"]

(*<|"U0[1]" -> U0[1], "U0[2]" -> U0[2], "V0[1]" -> V01, "V0[2]" -> V0[2]|>*)

(*3. Get the current value of the variable*)

asc["U0[1]"]

(*U0[1]*)

(*4. Get the name of the variable as `String`*)

all the syntax behind works if the key values in association ore strings, if you need the key to be a symbol the syntax in this case will be like this:

asc[symbol] = 10; asc

(*<|"U0[1]" -> U0[1], "U0[2]" -> U0[2], "B0" -> "newB0", "V0[1]" -> V01, "V0[2]" -> V0[2], symbol -> 10|>*)

asc[symbol]

(*10*)

asc[Key[symbol]]

(*10*)

(*5. Clear all the variables at once*)

asc =.

user24718

Posted 2012-09-07T08:14:08.740

Reputation: 1

The question asks for ways to do five tasks. Can you show how to do them with associations? – Michael E2 – 2015-01-14T16:46:50.293

1User, I appreciate your effort on this. However following your extended examples I believe that you misunderstood the question. The question is not simply how to track a series of values by key name but rather how to use a list of strings (or keys) to change the value of other Symbols. For example one should be able to do something like asc["B0"] = "newB0" and change the value of (global) Symbol B0 to "newB0". – Mr.Wizard – 2015-01-14T18:55:55.497