Task
Basically you want a Part function which accepts positional arguments. Let us extend the example a little further to get more levels:
list = {
{a1, {b1, c1}, d1, {e1, {f1, g1}}},
{a2, {b2, c2}, d2, {e2, {f2, g2}}},
{a3, {b3, c3}, d3, {e3, {f3, g3}}}
};
Suppose we want all a
, c
and g
s, the partspec would be:
list[[All, {1, {2}, {}, {{}, {2}}}]];
partspec = {1, {2}, {}, {{}, {2}}};
Core Solution
We define a function which will resolve positional part arguments. First we retrieve the positions for all atomic values:
positions = Position[partspec, _?AtomQ, Heads -> False]
{{1}, {2, 1}, {4, 2, 1}}
All but the last indices give us the position we need to extract later. The last index, however, has to be replaced with the actual number that was given in partspec:
parts = MapThread[
Join[Most@#1, {#2}] &,
{positions, Extract[partspec, positions]}
]
{{1}, {2, 2}, {4, 2, 2}}
The resolved partspec works with Extract
:
Extract[#, parts] & /@ list
{{a1, c1, g1}, {a2, c2, g2}, {a3, c3, g3}}
The final function is:
ResolvePositionalPartspec[partspec_] :=
With[{positions = Position[partspec, _?AtomQ, Heads -> False]},
MapThread[
Join[Most@#1, {#2}] &,
{positions, Extract[partspec, positions]}]
]
Extended Solution
To be able to combine it with other partspecs such as Range
s or All
, let's define another function which applies the ResolvePositionalPartspec function only to list arguments in Part
s:
PositionalPart[list_, partspec___] := Fold[
#2[#1] &,
list,
Replace[{partspec},
{listspec_List :> (Map[Extract[#, ResolvePositionalPartspec@listspec] &, #] &),
otherspec_ :> (Part[#, otherspec] &)},
{1}]
]
The idea is to replace each argument in the partspec with a pure function and subsequently apply it via Fold. Note that this does not exactly reproduce the behaviour of Part
on subsequent levels, but I found it to be quite practical anyhow.
Now we can use it with our list:
PositionalPart[list, All, partspec]
{{a1, c1, g1}, {a2, c2, g2}, {a3, c3, g3}}
PositionalPart[list, 1 ;; 2, partspec]
{{a1, c1, g1}, {a2, c2, g2}}
It will fail when the first partspec does not give a list (since Map
ping on level 1 is hard-coded in PositionalPart
):
PositionalPart[list, 1, partspec]
Extract::partd: Part specification {1} is longer than depth of object. >>
Use PositionalPart[list, 1;;1, partspec]
instead. Note that PositionalPart[list, {1}, partspec]
will not work because {1}
will be interpreted as a positional part argument, as in:
PositionalPart[list, {1}]
{{a1}, {a2}, {a3}}
Application
To use it an everyday coding, you can redefine Part
to work the way PositionalPart
does. When replacing Part, I would add something symbolic to the definition, similar to the UpTo[]
function, like:
Part[list, All, Positional[{1, {2}, {}, {{}, {2}}}]]
This means you will not loose the regular {}
syntax.
To achieve this, the Replace
statement in PositionalPart
would be modified to replace only specifications wrapped in Positional[]
with the Extract[ResolvePositionalPartspec]
function instead of replacing all lists.
{#, #2[[-1]]} & @@@ list
or{#[[1]], #[[2, -1]]} & /@ list
? $\endgroup$Partition[Flatten@list, 3][[;; , {1, 3}]]
$\endgroup$Part
doesn't allow what the OP expected withlist[[All, {1,{2}} ]]
, probably with a different syntax.. $\endgroup$