6
$\begingroup$

Is there a way to return a part of a list by reference, for reading and writing?

I want to do this so I can easily manipulate "rule-styled structured", i.e., hierarchical data that is loaded from a MAT file with struct's. Example:

obj = {"Name" -> "Something", "Age" -> 10};

I think this is a relatively simple way to get / set fields, which deals with nested structures by limiting Position to levelspec 1:

RuleQ[x_] := False;
RuleQ[rule_Rule] := True;
RuleQ[{}] := True; (* An empty list can be a Rule set... *)
RuleQ[rules_List] := SameQ[Head[First[rules]], Rule];

StructQ[x_] := False;
StructQ[rules_List?RuleQ] := True;

FindFirst[list_List, item_] := Module[{res},
   res = Flatten[Position[list, item, 1]];
   Return[If[Length[res] > 0, First[res], {}]];
   ];

FieldRule[field_] := field -> _;

GetFieldIndex[obj_?StructQ, field_] := FindFirst[obj, FieldRule[field]];

SetField[obj_?StructQ, field_, value_] := Module[{index},
   index = GetFieldIndex[obj, field];
   If[NumberQ[index],
    obj[[index, 2]] = value;
    ,
    AppendTo[obj, field -> value];
    ];
   ];
SetAttributes[SetField, HoldFirst];

GetField[obj_?StructQ, field_] := Module[{index},
   index = GetFieldIndex[obj, field];
   (* How to return a reference? *)
   Return[obj[[index, 2]]];
   ];
SetAttributes[GetField, HoldFirst];

So you can do something like

Print[GetField[obj, "Name"]];
SetField[obj, "Name", "REPLACED"];
Print[GetField[obj, "Name"]];

All that being said, I would like to do something like this:

GetField[obj, "Name"] = "REPLACED";

An example of my hope for such a feature:

GetFieldByReference[obj_?StructQ, field_] :=
    Return[Part[obj, {GetFieldIndex[obj, field], 2}];
SetAttributes[GetFieldByReference, HoldReturn];
obj = {"Name" -> "Something", "Age" -> 10};
GetFieldByReference[obj, "Name"] = "REPLACED";

The contents of obj would then be:

{"Name" -> "REPLACED", "Age" -> 10}
$\endgroup$
5
  • $\begingroup$ Can you show some example of what you want? I like to use ReplaceAll for this kind of operation. $\endgroup$
    – Murta
    Oct 25, 2012 at 21:10
  • $\begingroup$ I would like to know if there is some functionality, such as SetAttribute[HoldReturn], similar to the Hold... in Attributes[]. In that way, I could return a part of a list, and then assign to that knowing that it is referring directly to a subset of that list. Then that could be used to implement something like GetField[obj, "Name"] = "REPLACED" $\endgroup$ Oct 26, 2012 at 4:19
  • $\begingroup$ Just realized that isn't really an example haha, I will add one to the example post. $\endgroup$ Oct 26, 2012 at 4:23
  • 2
    $\begingroup$ In this answer, I have implemented the rather generic pass-by-reference mechanism which might work for you. $\endgroup$ Oct 27, 2012 at 21:17
  • $\begingroup$ That is a great looking framework right there! Right now performance and assignment syntax are not yet an issue for us, but if it gets to be I will definitely weigh the options between yours and Istvan's static object structure. Thank you! $\endgroup$ Dec 4, 2012 at 16:06

1 Answer 1

10
$\begingroup$

I've experimented with this kind of structure-representation years before, when I thought that object oriented programming is something that must be implemented in Mathematica. Since, I've learned that it does not really worth it. The core of the method is to set up a reference object (generally, via a constructor, but here we take it as granted) and set up a field $\rightarrow$ position dispatch table that is used when accessing fields.

For static object structure

Create a reference: this should be the reference format of a class of objects, with head Entry (any arbitrary name suffices). Any non built-in symbol or string will be identified as a field-name. Note that you can use hierarchical lists or functions, as in case of $slot, and fields can be repeated.

reference = 
  Entry[$type, $size, $method, $slot[{"a", "b"}, {"a", "b"}, {"a", "b"}]];

Extract all user-defined symbols/strings. (Unsorted union can be used instead of Union to keep the fields in order, but it is not essential.)

fields = Union@
  Rest@Cases[reference, 
    Except[List, _Symbol | _String], {0, Infinity}, Heads -> True]
{"a", "b", $method, $size, $slot, $type}

Now set up the dispatch table that is used to replace field names with positions. We include All and similar position specifiers for better usability.

dispatch = 
 Dispatch@Join[
   Rule[#, Last@DeleteCases[First@Position[{reference}, #], 0]] & /@ 
    fields, {All -> All, None | Identity | Null -> Sequence[]}];

Define the access function:

GetField[obj_] := obj;
GetField[obj_, par__] := Part[obj, Sequence @@ ReplaceAll[{par}, dispatch]];

Now create a real object (this should be created with a constructor or by instantiating a class member):

object = Entry[Real, 13, "N\\A", {{1, 2}, {3, 4}, {5, 6}}];

Let's access some parts:

GetField[object, $type]            ==> Real
GetField[object, $size]            ==> 12
GetField[object, $slot]            ==> {{1, 2}, {3, 4}, {5, 6}}
GetField[object, $slot, 2, "a"]    ==> 3
GetField[object, $slot, All, "b"]  ==> {2, 4, 6}

Define the set function:

SetField[obj_, par__, val_] := ReplacePart[obj, ReplaceAll[{par}, dispatch] -> val];

...and modify the structure. One can define a SetFieldTofunction that modifies the original object instead of a copy of it (like AddTo vs. Add), but it is left for sake of simplicity.

SetField[object, $type, Integer]
SetField[object, $size, 21]
SetField[object, $slot, 3, 1, 555]
Entry[Integer, 13, "N\\A", {{1, 2}, {3, 4}, {5, 6}}]
Entry[Real, 21, "N\\A", {{1, 2}, {3, 4}, {5, 6}}]
Entry[Real, 13, "N\\A", {{1, 2}, {3, 4}, {555, 6}}]

For dynamic object structure

If one allows dynamic objects where new fields can be introduced on the fly, I suggest using a 1D list of rules, like in the OP. This method assumes that all elements in object are rules.

Remove[GetField, SetField, object];
GetField[obj_] := obj;
GetField[obj_, field_] := field /. obj;
SetField[obj_] := obj;
SetField[obj_, field_, val_] := Module[{pos},
   pos = Position[obj, field, {0, \[Infinity]}, 1];
   If[pos === {},
    Join[obj, {field -> val}],
    ReplacePart[obj, {First@First@pos, 2} -> val]
   ]];

object = {"f1" -> 1, "f2" -> 2, "f3" -> 3};

GetField[object, "f2"]      ==> 2
SetField[object, "f3", 4]   ==> {"f1" -> 1, "f2" -> 2, "f3" -> 4}
SetField[object, "f4", 5]   ==> {"f1" -> 1, "f2" -> 2, "f3" -> 3, "f4" -> 5}
$\endgroup$
6
  • $\begingroup$ Nicely conceived. +1! $\endgroup$
    – Jagra
    Oct 25, 2012 at 21:32
  • $\begingroup$ Thanks @Jagra, it took me some 3 years back then to streamline the method. And this is only the core of it. Actually, I even got it work with dots, like this: Get.object.$slot.(2).(1). $\endgroup$ Oct 25, 2012 at 21:35
  • $\begingroup$ Wow, this seems like a pretty awesome system, thank you for posting it. May I ask if you have used it for dynamic structures, where the fields are not known a-priori? My initial thought for an implementation would involve using your system to make a 'dynamic' object, and then from a list of rules (or whatever associative structure is chosen) glean the fields and values and store them, and have associated getters and setters. However, that seems like it would have the same functionality as the first implementation and require more work. $\endgroup$ Oct 26, 2012 at 4:17
  • 1
    $\begingroup$ @eacousineau No, I always used a static class that is predefined so that instances have uniform structures. If you want to deal with dynamic structures, I suggest dropping hierarchical internal structure and simply use a 1D list of named elements, like field->*value*, just like you did in your post. See my edit. $\endgroup$ Oct 26, 2012 at 9:46
  • $\begingroup$ I have been working with a similar implementation in your edit and my original post, but sticking with hierarchical data, and it has been working out quite well. Granted, it is not high performance, but we are using it for loading a modular system configuration with minimal size. One side note, I had to change GetField[] to GetFields[] since it interferes with JLink` (and also renaming SetField[] to `SetFields[]). Thank you for your advice, and I will definitely use your structure when we have static data structures. $\endgroup$ Dec 3, 2012 at 16:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.