4
$\begingroup$

Considering we have a an association:

assc = <|"A" -> <|"a" -> 1, "aa" -> 2|>, "B" -> 0, "C" -> 5,"D" -> <|"d" -> 2, "dd" -> 12|>|>

Let's also consider we have 2 known lists and one list for nested keys (** - how to create this list? **):

bigKeys = {"A", "D"}
lst1 = {"a", "dd", "B"}
lst2 = {"aa", "d", "C"}

I would like to loop over assc and assign the values for the keys that are from lst1 and are not bigKeys - 0, and to the values for the keys that are from lst2 and again are not bigKeys - 1.

As a result to have:

asscNew = <|"A" -> <|"a" -> 0, "aa" -> 1|>, "B" -> 0, "C" -> 1,  "D" -> <|"d" -> 1, "dd" -> 0|>|>

Thanks!

$\endgroup$
  • 1
    $\begingroup$ Why was this question downvoted? I think it is an intruiging question with an excellent answer. $\endgroup$ – Fred Simons Jun 22 '15 at 11:23
5
$\begingroup$

This solution will traverse all nested associations regardless of depth and replace each value with the value that corresponds to its key in replacements.

f[Rule[key_, assoc_Association]] := Rule[key, AssociationMap[f, assoc]]
f[Rule[key_, val_]] := Rule[key, key /. replacements]

replacements = {
   "a" -> 1, "dd" -> 1, "B" -> 1,
   "aa" -> 0, "d" -> 0, "C" -> 0
   };

assoc = <|"A" -> <|"a" -> 1, "aa" -> 2|>, "B" -> 0, "C" -> 5, 
 "D" -> <|"d" -> 2, "dd" -> 12|>|>;

AssociationMap[f, assoc]
(* Out: <|"A" -> <|"a" -> 1, "aa" -> 0|>, "B" -> 1, "C" -> 0, 
 "D" -> <|"d" -> 0, "dd" -> 1|>|> *)

key /. replacements can be replaced by a more general transformation func[key, val].

$\endgroup$
  • $\begingroup$ Thanks for this answer Pickett, but this not really what I want to. I don't want to have static list to replace. I want to work dynamically on values. For example let's say, for the keys that are in lst1 check if the value is DateObject. $\endgroup$ – SuTron Jun 22 '15 at 9:14
  • $\begingroup$ @SuTron Can't you modify my solution to do this? Just replace key /. replacements with yourDynamicFunction[key, val]. $\endgroup$ – C. E. Jun 22 '15 at 9:17
3
$\begingroup$

MapIndexed can be used to map a function to the inner-most values in nested associations, for example:

MapIndexed[f, <| "A" -> <|"a" -> 1|>|>, {-1}]

(* <|"A" -> <|"a" -> f[1, {Key["A"], Key["a"]}]|>|> *)

The level specification {-1} selects only the deepest level in the expression. Note how f receives not only the value (1), but also the keys used to locate that particular value ({Key["A"], Key["a"]}).

Knowing this, we can define a helper function val which applies the rules specified in the question:

val[_, {___, Key[k_]}] /; MemberQ[lst1, k] := 0
val[_, {___, Key[k_]}] /; MemberQ[lst2, k] := 1
val[v_, _] := v

Now, MapIndexed can provide the desired result:

MapIndexed[val, assc, {-1}]

(* <|"A"-> <|"a"->0, "aa"->1|>, "B"-> 0, "C"-> 1, "D"-> <|"d"->1, "dd"->0|>|> *)

% === asscNew

(* True *)

Note that this solution automatically ignores keys that have nested associations -- there is no need to use bigKeys. If it is important to take bigKeys into consideration for some other reason, then we need only add one more rule (which must be the first rule):

ClearAll[val]
(* new rule *)
val[v_, {Key[k_], ___}] /; MemberQ[bigKeys, k] := v
(* same rules as before *)
val[_, {___, Key[k_]}] /; MemberQ[lst1, k] := 0
val[_, {___, Key[k_]}] /; MemberQ[lst2, k] := 1
val[v_, _] := v

This technique generalizes nicely to complex nested structures, allowing each value to be transformed by an arbitrary function of that value and its location ("path") within the structure. It also works with nested mixtures of lists and associations. By relaxing the level specification in MapIndexed, any or all levels of a structure can be transformed.

$\endgroup$
2
$\begingroup$

I've found a simple approach using the function Part (its shorthand is [[ ]]):

1.

If you write your list lst1 rather that way:

newlst1 = {{"A", "a"}, {"D", "dd"}, {"B"}}

you can assign any value (for example here 999) to all these keys simply with:

(assc[[##]] = 999) & @@@ newlst1;

You can check the result:

assc

<|"A" -> <|"a" -> 999, "aa" -> 2|>, "B" -> 999, "C" -> 5, "D" -> <|"d" -> 2, "dd" -> 999|>|>

2.

Allright, let's say you don't want to modify your lst1={"a", "dd", "B"};, the keys are unique so you don't want to loose your time.

Then, we'll just need to generate this key map first in order to help us:

keymap = Reap@MapIndexed[Sow[Rest@{##}] &, assc, {0, Infinity}] // 
    Flatten[#, 3] & // DeleteCases[#, {}] & // Map[#[[-1, 1]] -> # &, #] &

{"a" -> {Key["A"], Key["a"]}, "aa" -> {Key["A"], Key["aa"]}, "A" -> {Key["A"]}, "B" -> {Key["B"]}, "C" -> {Key["C"]}, "d" -> {Key["D"], Key["d"]}, "dd" -> {Key["D"], Key["dd"]}, "D" -> {Key["D"]}}

Then for example to assign a value, you just do (almost as in 1./):

(assc[[##]] = 111111) & @@@ (lst1 /. keymap);

You can check:

assc

<|"A" -> <|"a" -> 111111, "aa" -> 2|>, "B" -> 111111, "C" -> 5, "D" -> <|"d" -> 2, "dd" -> 111111|>|>

Explanation

You can use Part or [[ ]] with Associations almost as you do with Lists, except you use Keys as parameters (see the Part doc.).

Let's try some examples:

Given

assc = <|"A" -> <|"a" -> 1, "aa" -> 2|>, "B" -> 0, "C" -> 5, 
  "D" -> <|"d" -> 2, "dd" -> 12|>|>

see what does:

assc[["A"]]

<|"a" -> 1, "aa" -> 2|>

assc[["A", "a"]]

1

and you can directly assign new values:

assc[["A", "a"]] = 123456789;
assc

<|"A" -> <|"a" -> 123456789, "aa" -> 2|>, "B" -> 0, "C" -> 5, "D" -> <|"d" -> 2, "dd" -> 12|>|>

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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