8
$\begingroup$

Background

I'm working on an application in which I need to create and control two sets of locators. I know from reading the Mathematica documentation and certain posts on Mathematica.SE that this means I can't base my application on a Manipulate expression. I have very little experience with interactive applications based on the dynamic objects that sit below Manipulate, so I searched "multiple locators" to see what I could learn. Quite a bit as it turned out. However, to keep this short, I'll just say I finally settled on a approach described by jVincent in his answer to this question.

Question

In my situation I am going to have one set of locators that are displayed as black squares and a second set displayed as red dots. The application will start out by showing no locators in its content pane. The user will create as many of each kind as he/she wants by clicking on buttons provided for the purpose.

My adaptation of jVincent's code to my situation is as follows:

 DynamicModule[{black = {}, red = {}}, 
    Grid[{{
       Framed@Graphics[
          {Dynamic@MapIndexed[
              With[{i = #2[[1]]}, 
                 Locator[Dynamic[black[[i]]],
                    Style[■, Black]]]&, black],
           Dynamic@MapIndexed[
              With[{i = #2[[1]]},
                 Locator[Dynamic[red[[i]]],
                    Style[●, Red]]] &, red]},
          PlotRange -> {{0, 1}, {0, 1}}],
       SpanFromLeft},
       {Button["Add Black", AppendTo[black, RandomReal[1, 2]]], 
        Button["Add Red", AppendTo[red, RandomReal[1, 2]]]}}]]

two sets of locators

The code works well. I have no complaints. One expression in the code surprises and intrigues me, however. This is:

{Dynamic@MapIndexed[
    With[{i = #2[[1]]}, 
       Locator[Dynamic[black[[i]]],
          Style[■, Black]]]&, black]

It's clear what this does: it creates a list of locators based on a list of locations (pairs of real numbers). It's clear that the With trick is needed to work around the HoldFirst attribute of Dynamic. What is clever and surprising -- I would have never thought of it -- is the use of MapIndexed to obtain the indexes that must be inserted into black[[...]]. What I would have thought of is:

 {Dynamic@(With[{i = #}, 
     Locator[Dynamic[black[[i]]], Style[■, Black]]] &
        /@ Range@Length@black)

which is much more banal but gets the job done. I wonder why jVincent chose MapIndexed? Is it really that more efficient than mapping over a Range? Or is there some other deeper advantage beyond my ability to fathom.

I will say I don't think creating a range every time the expression in question is evaluated is much more expensive than what MapIndexed does to create the pairs it uses, but I could be wrong. I'm afraid I'm one those Mathematica programmers who knows the value of everything but the cost of nothing (remembering an old joke made about Lisp programers).

$\endgroup$
2
  • 6
    $\begingroup$ I don't think the answer is more complicated than "Why would you write foo[bar[[#]], {#}]& /@ Range@Length@bar when you can simply write MapIndexed[foo, bar]" :) $\endgroup$
    – rm -rf
    Commented Nov 15, 2012 at 6:05
  • $\begingroup$ This occurred to me, but I really wanted to learn what the community and especially jVincent had to say. Thankfully jVincent took the time to write a cogent and detailed answer which should be of general interest. $\endgroup$
    – m_goldberg
    Commented Nov 15, 2012 at 13:59

1 Answer 1

11
$\begingroup$

I believe I know exactly why jVincent chose MapIndexed rather then mapping over range. As rm-rf states, it does seem semantically simpler, however that's not the reason. It's due to habit and scaling. If for instance you have a 2d array of {x,y} sets that you want to create locators for, I generally write:

 MapIndexed[With[{i = #2}, Locator[Dynamic[array[[Sequence @@ i]]]]] &, array, {-2}]

Which creates the structure based on the information that I want locators using the second lowest level of array, but makes no assumptions on the dimensionality. Therefore it'll work with 1d,2d,nd depending on what array I send it. Because I use this construct rather often, I chose to write something very similar, however I decided against writing out the Sequence to make it simpler for new users to interpret.

All that being said, I almost always chose to use MapIndexed in cases where I want to map over the indexes of some structure, even if I don't need the values simultaneously. Simply because I find it better conveys the intention rather then using map and generating the indexes.

As for efficiency, I would argue that it's misguided to want to optimize a generator for dynamic interactive controls when it's returning in milliseconds, however if you where doing something different and operating on large datasets, it seems that there would be a gain in switching to map:

  SeedRandom[29321];
  test = RandomReal[1, 100000];

 MapIndexed[With[{i = #2},Locator[Dynamic[test[[Sequence@@i]]]]] &,test];//Timing//First
 MapIndexed[With[{i = #2[[1]]}, Locator[Dynamic[test[[i]]]]] &, test];// Timing//First
 Map[With[{i = #}, Locator[Dynamic[test[[i]]]]] &, Range@Length@test];// Timing//First

0.297

0.375

0.281

However in this case, if you where creating 100000 locators for a single graphic, you would most likely have other problems than the time it took to create them.

$\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.