3

The following is just an example that captures what I want to do (more on it at the bottom).

I'm trying to do something that can be paraphrased as follows: I have a global list, say list = {0,0,1,0,0,1,1,1,1}. I have a function which can modify the global list:

f[element_] := (If[element == 1, list = Select[list, # != 1 &]]; counter++)

My problem is that when I map the function on the list, it goes through all the elements of the initial version of it, without skipping the values that have been eliminated:

counter = 0;
f /@ list;
counter
(* 9 *)

How do I get around this problem? (I'm ok with making the list locally defined)

EDIT: From the comments I gather that it might be useful to explain what I actually want to do. Well, here it is. I have a quantum system which is transformed and it may incur in errors: my list is a list of possible errors. My function looks at the errors and determines if they are correctable or not. To save time, if an error $E$ is not correctable I want to skip those errors in the list that come from composing $E$ with others.

corey979
  • 23,947
  • 7
  • 58
  • 101
Ziofil
  • 2,470
  • 16
  • 30
  • Is your goal to compute counter...? When you first hit an element that is 1 all the other 1s will not be selected, so what do you need the modified list for? – Marius Ladegård Meyer Feb 09 '16 at 22:03
  • No, this is only an example. My function does much else on other objects in the meantime. My actual list is a list of possible errors on a quantum system and my function checks if they are correctable. If one is not correctable I want it to skip checking all the ones that "include it" because they must be uncorrectable as well. – Ziofil Feb 09 '16 at 22:12
  • 3
    Seems like your better off using Select on list rather than reassigning it lots of times inside f. – evanb Feb 09 '16 at 22:15
  • I would advise (not only in Mathematica) to avoid mutating state at all cost. As others have suggested better use Select and leave the original list intact or use Sow and Reap to build a list of results if you really have to do things iteratively. – Sascha Feb 09 '16 at 22:23
  • I would be curious to know, what you are actually trying to do. It seems to me that your question is ill-posed. – Berg Feb 09 '16 at 22:27
  • My goal is not to compute the final list. I want to use the list as a set of inputs, which may change depending on the results of the function. Also, I don't want to mutate states at all costs, if there is a workaround I'll be very happy! – Ziofil Feb 09 '16 at 22:27
  • @Ziofil you may want to edit your question and clarify what your goal is. The code from your question leads one to believe you want to count the number of entries in a list that are not equal to 1 – Sascha Feb 09 '16 at 22:30
  • By the way, /@ works as intended: First the right hand side is evaluated and then the function is applied to this (new) List. Changes to list have thus no effect on the result. See Trace[f/@list] for details. – Berg Feb 09 '16 at 22:33
  • 1
    So, basically, you want to stop after first 1? – Kuba Feb 10 '16 at 07:16
  • Why do you need Map at all? You may just check if 1 is in the list? Or am I missing something? – garej Feb 10 '16 at 10:12
  • @Ziofil Does my answer address your needs or did I miss the point? – Mr.Wizard Feb 27 '16 at 01:52

3 Answers3

3

I think you are misunderstanding the nature of Map. It applies a head to a series of elements, then evaluates the complete expression. Please see my answer to Scan vs. Map vs. Apply for examples and further description.

What you want might be handled by redefining f rather than redefining list.

ClearAll[f, list, counter]

f[element_] := (If[element == 1, f[1] = "skip"]; counter++)

list = {0, 0, 1, 0, 0, 1, 1, 1, 1};

counter = 0;
f /@ list
counter
{0, 1, 2, 3, 4, "skip", "skip", "skip", "skip"}

5

After the first instance of f[1] is evaluated a new definition f[1] = "skip" is added to the rule table. Further evaluations of f[1] trigger that rule instead (because it is more specific) and do not increase the counter. The new rule could also be pattern based, e.g.:

ClearAll[f, list, counter]

f[element_] := (If[element == 1, f[x_ /; x == 1] = x]; counter++)

list = {0, 0, 1, 0, 0, 1, 1.0, 1, 1};

counter = 0;
f /@ list
{0, 1, 2, 3, 4, 1, 1., 1, 1}

This returns unmodified any elements which match the Condition x == 1, after that rule is initially set by evaluation of the third element of the list.

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
2

You may use Scan and Return.

Return from f after you reset the list.

f[element_] := (If[element == 1, list = Select[list, # != 1 &]; 
   Return[]]; counter++;)

Then Scan the function with the Return .

Scan[f, list]

counter
(* 4 *)

Hope this helps.

Edmund
  • 42,267
  • 3
  • 51
  • 143
1

A solution is to manage the loop yourself, i.e.

list = {0, 0, 1, 0, 0, 1, 1, 1, 1};

p = MapIndexed[Prepend[#2, #1] &, list];

f[{element_}] := (If[First[element] == 1, p = Select[p, First[#] != 1 &]]; counter++)

f[{}] := counter++

counter = 0;

Array[f[Cases[p, {_, #}]] &, Length[p]];

counter

9

list = Map[First, p]

{0, 0, 0, 0}

Chris Degnen
  • 30,927
  • 2
  • 54
  • 108