Obtaining a matrix of complex values from associations giving the real and imaginary parts of each element?

I have a list of associations keyed by real and imaginary numbers, like so:

matrix = {
{<|"r" -> 0.368252, "i" -> 0.0199587|>,
<|"r" -> -0.461644, "i" -> 0.109868|>,
<|"r" -> -0.216081, "i" -> 0.562557|>,
<|"r" -> -0.479881, "i" -> -0.212978|>},

{<|"r" -> 0.105028, "i" -> 0.632264|>,
<|"r" -> 0.116589, "i" -> -0.490063|>,
<|"r" -> 0.463378, "i" -> 0.231656|>,
<|"r" -> -0.148665, "i" -> 0.212065|>},

{<|"r" -> 0.463253, "i" -> 0.201161|>,
<|"r" -> 0.460547, "i" -> 0.397829|>,
<|"r" -> 0.222257, "i" -> 0.0129121|>,
<|"r" -> 0.168641, "i" -> -0.544568|>},

{<|"r" -> 0.255221, "i" -> -0.364687|>,
<|"r" -> 0.191895, "i" -> -0.337437|>,
<|"r" -> -0.12278, "i" -> 0.551195|>,
<|"r" -> 0.560485, "i" -> 0.134702|>}
}


Given this, I can write

testmatrix = Join[Values[matrix], 2]


to get a matrix, but it is a matrix of tuples. How can I get the complex number defined in each <|r -> Re[z], i -> Im[z]|> rather than the tuples?

I'm a bit uncomfortable with both @HenrikSchumacher's and @kglr's (first) answers, as they rely on the correct sorting order of the keys in the Associations: they assume that every matrix element is precisely of the form of an association with first the "r" part and second the "i" part.

Without relying on the sorting order, or on the presence of both elements, and making more use of the essential features of associations, we can do

matrix /. a_Association :> Lookup[a, {"r", "i"}, 0].{1, I}


This solution works even if one matrix element is of the form <|"r" -> 2|> or <|"i" -> -3|> (it will default the other coordinate to zero). It also works if the order of the elements is interchanged: <|"r" -> 2, "i" -> -3|> gives the same result as <|"i" -> -3, "r" -> 2|>. It also works on higher-dimensional tensors or ragged structures.

Join[Values[matrix], 2].{1, I}


As J.M. pointed out, this can even be shortened to the following:

Values[matrix].{1, I}


A nice feature of this approach is that it produces packed arrays when possible.

• Even better: Values[matrix].{1, I}, which preserves the matrix structure. – J. M.'s ennui Mar 4 '19 at 0:53
Apply[Complex, matrix, {2}] (* or *)
Map[{#r, #i}.{1, I} &, matrix, {2}]


{{0.368252 +0.0199587 I,-0.461644+0.109868 I,-0.216081+0.562557 I,-0.479881-0.212978 I},
{0.105028 +0.632264 I,0.116589 -0.490063 I,0.463378 +0.231656 I,-0.148665+0.212065 I},
{0.463253 +0.201161 I,0.460547 +0.397829 I,0.222257 +0.0129121 I,0.168641 -0.544568 I},
{0.255221 -0.364687 I,0.191895 -0.337437 I,-0.12278+0.551195 I,0.560485 +0.134702 I}}

• Huh, that's a great one! A word of warning though: This method produces unpacked arrays. – Henrik Schumacher Mar 3 '19 at 23:48
• The Apply[Complex] method will also fail if the entries are not integers or inexact real numbers, e.g. Complex @@ {Pi, Sqrt[2]}. – J. M.'s ennui Mar 4 '19 at 0:55
• @J.M.iscomputer-less and Henrik, thank you both. Added an alternative version using Map + Dot. – kglr Mar 4 '19 at 17:44
• If you Apply or Map these at level {-2} instead of {2}`, then these methods will work for vectors and higher-dimensional tensors as well. – Roman Mar 5 '19 at 14:43