8

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).

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
m_goldberg
  • 107,779
  • 16
  • 103
  • 257
  • 6
    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]" :) – rm -rf Nov 15 '12 at 06:05
  • 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. – m_goldberg Nov 15 '12 at 13:59

1 Answers1

11

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.

jVincent
  • 14,766
  • 1
  • 42
  • 74