14
$\begingroup$

MapIndexed is a very handy built-in function. Suppose that I have the following list, called list:

list = {10, 20, 30, 40};

I can use MapIndexed to map an arbitrary function f across list:

{f[10, {1}], f[20, {2}], f[30, {3}], f[40, {4}]}

where the second argument to f is the part specification of each element of the list.

But, now, what if I would like to use MapIndexed only at certain elements? Suppose, for example, that I want to apply MapIndexed to only the second and third elements of list, obtaining the following:

{10, f[20, {2}], f[30, {3}], 40}

Unfortunately, there is no built-in "MapAtIndexed", as far as I can tell. What is a simple way to accomplish this? Thanks for your time.

$\endgroup$
0

6 Answers 6

6
$\begingroup$

If does the job and is simple enough:

MapIndexed[If[2 ≤ First@#2 ≤ 3, f[#, #2], #] &, list]
(* {10, f[20, {2}], f[30, {3}], 40} *)
$\endgroup$
8
$\begingroup$

Another way, using MapIndexed's functionality, like rm-rf's:

mapAtIndexed[f_, expr_, pos_, levelspec_: 1, opts : OptionsPattern[MapIndexed]] :=
 Module[{f0},
  f0[x_, p : Alternatives @@ pos] := f[x, p];
  f0[x_, _] := x;
  MapIndexed[f0, expr, levelspec, opts]
  ]

OP's example:

mapAtIndexed[g, list, {{2}, {3}}]
(* {10, g[20, {2}], g[30, {3}], 40} *)

Multiple levels (pay attention to the {2}):

mapAtIndexed[f, Table[10 i + j, {i, 4}, {j, 3}],
             {{1, 2}, {1, 3}, {2}, {2, 1}, {3, 2}, {4, 3}}, 2]
(* {{11, f[12, {1, 2}], f[13, {1, 3}]}, 
    f[{f[21, {2, 1}], 22, 23}, {2}],
    {31, f[32, {3, 2}], 33},
    {41, 42, f[43, {4, 3}]}} *)

Heads, too:

mapAtIndexed[f, list, {{2}, {4}, {0}}, Heads -> True]
(* f[List, {0}][10, f[20, {2}], 30, f[40, {4}]] *)

Alternate solution

In this we have to ensure f is mapped at the lower levels first, using Reverse @ Sort @ ..., since we're not using Map. However it's quite a bit faster.

mapAI2[f_, expr_, pos_] := Module[{e0 = expr},
  (e0[[Sequence @@ #]] = f[e0[[Sequence @@ #]], #]) & /@ Reverse @ Sort @ pos;
  e0
  ]

Example

mapAI2[f, Table[10 i + j, {i, 4}, {j, 3}],
       {{1, 2}, {3, 0}, {1, 3}, {2}, {2, 1}, {3, 2}, {4, 3}}]
(* {{11, f[12, {1, 2}], f[13, {1, 3}]},
    f[{f[21, {2, 1}], 22, 23}, {2}], 
    f[List, {3, 0}][31, f[32, {3, 2}], 33],
    {41, 42, f[43, {4, 3}]}} *)
$\endgroup$
5
$\begingroup$

Here's a form similar to Kuba's approach:

mapAtIndexed[f_, list_, pos_] :=
  ReplacePart[list, # :> f[list[[Sequence @@ #]], #] & /@ pos];

A pure pattern version:

mapAtIndexed[f_, list_, pos_] :=
  ReplacePart[list,
   i : (Alternatives @@ pos) :> f[list[[Sequence @@ i]], i]];

And I assume you're familiar with Position.

$\endgroup$
4
$\begingroup$

I'm sure one can improve following solution

SetAttributes[mapIndexedAt, HoldRest];
mapIndexedAt[f_, list_, pos_] := Do[list = MapAt[f[#, pos[[i]]] &, list, pos[[i]]]
                                    , {i, Length@pos}]

l = {1, 1, 1, 1};
mapIndexedAt[(#1 + #2) &, l, {2, 3}]
{1, 3, 4, 1}

It does not look good but at least it is not scanning through the list.

A little variation with Fold:

f = #1 + #2 &
Fold[ReplacePart[#1, #2 -> f[#1[[#2]], #2]] &, l, {2, 3}]
{1, 3, 4, 1}
$\endgroup$
4
$\begingroup$

Level one version

This is an adaptation of amr's answer (based on Kuba's answer)

mapAtLevOneIndexed[f_, list_, pos_] :=     
 ReplacePart[list, 
  Inner[Rule[#, f[#2, #]] &, pos, Part[list, pos], List]]

Example

mapAtLevOneIndexed[f, {1, 2, 6, 7}, {2, 3}]

-> {1, f[2, 2], f[6, 3], 7}

In the case you work at level one, I think the most convenient way to enter a position is just a single integer. Also I think that is the most convenient way for the position to occur in f, but this makes it a little different from MaxIndexed.

This may be a case where Thread is faster than Inner, maybe I will check later. The idea here is that we use both Part and ReplacePart both only once, to speed things up. This use of Part only works on level one. Despite this I don't think it is faster than rm-rf's answer, which is my favorite. Maybe it can be faster if the positions at which we want to "mapindex" are very sparse.

Deeper level version

Of course you can let Extract do the job of Part here and make it work on deeper levels. Below I use Thread just convenience, as Inner did not deal with lists of lists as I want. I do not answer which one is faster.

Deeper level version:

mapAtIndexed[f_, list_, pos_] :=
 ReplacePart[list, 
  Thread[Unevaluated[
    Rule[#, f[#2 // First, #]] &[pos, Extract[list, pos]]]]]

Examples

mapAtIndexed[f, {{{1}}, 2}, {{1, 1}}]

-> {{f[{1}, {1, 1}]}, 2}

mapAtIndexed[f, {{{1}}, 2}, {{1, 1, 1}, {2}}]

-> {{{f[1, {1, 1, 1}]}}, f[1, {2}]}

Here you have to be careful to enter the positions in the form {pos1, pos2}, rather than just pos1, but that can be easily overcome.

$\endgroup$
2
  • 1
    $\begingroup$ Thanks for investigation and extension :) ReplacePart based solutions seem to be ideal for this since they are not scanning through the list. $\endgroup$
    – Kuba
    Commented Jul 14, 2013 at 12:13
  • 1
    $\begingroup$ Ah, yeah, I like ReplacePart in theory, but it may be slower in practice than you might expect, especially for big lists. I suppose in theory Extract also does not scan the entire list. I would very much like it if there was a built in function that did what my myAtIndexed did, but for a given head inside the Thread. Maybe I will elaborate on this later in a new post :). $\endgroup$ Commented Jul 14, 2013 at 15:42
2
$\begingroup$

You can also do this:

list = {10, 20, 30, 40};

newlist =list/.{a_,b_,c_,d_}:>{a,f[b,2],f[c,3],d}

In this way, you give a lable to any element of your list, and using :>, you can map some other function on some of elements of your list. In the above code for example, a and d are not changed, while the second and thord elements are changed.

$\endgroup$

Your Answer

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

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