# Can I exit a Map over a list of data pairs?

I've written a function, which I intended to Map over lists of some 8,000 element pairs.

I created the "toy" code below to illustrate my problem.

yld = 0.0742;
pVal = 10000;
pVal2 = 10000;
dataPairs = {{a, 0.0027}, {b, 0.0027}, {c, 0.0027}, {d, 0.002}, {e, 0}, {f, 0.0027}, {g, 0.002}};

f[dataPairs_] :=  Module[{date, prem, pValX, preQ},
prem = yld * dataPairs[[2]]*pVal2;
pValX = pVal2 + prem;
preQ = Which[
pVal >= pValX, True,
pVal < pValX, False];
{dataPairs[[1]], preQ}];

f[#] & /@ dataPairs


Which outputs:

{{a, False}, {b, False}, {c, False}, {d, False}, {e, True}, {f, False}, {g, False}}


I have 11,500 of these 8,000 element long lists. It gets be a lot of run time.

From each of these lists, I really only need the mapping to run until I get a the first paired output with True in it, e.g., {e, True}.

On average I expect to get a True, within the first 1250 applications of the function (f[]) to each of the 8,000 element long lists. If I can break the mapping when I first get True, it would vastly reduce the run time of the larger program.

Using NestWhile or FoldWhile, didn't seem to make sense because I don't need to build up iterative calculation on calculation.

I've considered SelectFirst, with the test for True as the criteria, but haven't had success.

Thoughts and suggestions appreciated.

• TakeWhile[list, Not@*Last]? Commented Feb 27, 2023 at 21:28

Edit

Edited to address a comment made by the OP. I seem to have lost sight of the original requirement stated in the question.

I don't think you can exit from Map, but the documentation points out that either Throw or Return may be used to exit from Scan.

This may be sufficient, or even the preferred option.

To quote (the much missed) Mr. Wizard:

Scan "visits" (sub)expressions, evaluates each of them, and returns Null
Map wraps (sub)expressions in a given Head, and returns the modified input

Using Return

Scan[If[f[#][[2]], Return[f@#]]&, dataPairs]

{e, True}


Using Throw/Catch

Catch[Scan[If[f[#][[2]], Throw[f@#]] &, dataPairs]]

(* {e, True}  *)


The only 'side effect' is to return the list obtained by calling f on the subexpression where the last part of that function call is True

Using Do/Return

Substituting Do for Scan:

Do[If[Last@f@dataPairs[[i]],Return[f@dataPairs[[i]]]], {i,Length@dataPairs}]

(* {e, True} *)


Map/Scan

mat={
{{a, 0.0027}, {b, 0.0027}, {c, 0.0027}, {d, 0.002}, {e, 0}, {f, 0.0027}, {g, 0.002}},
{{a, 0}, {b, 0.0027}, {c, 0.0027}, {d, 0.002}, {e, 0}, {f, 0.0027}, {g, 0.002}},
{{a, 0.0027}, {b, 0.0027}, {c, 0.0027}, {d, 0.002}, {e, 002}, {f, 0.0027}, {g, 0.000}}
}

Scan[If[f[#][[2]], Return[f@#]]&,#]&/@mat

(* {{e, True}, {a, True}, {g, True}} *)

• Testing this now on my full application and code (well the first couple of thousand). It seems to work, but I can't say I understand how it works. Can I trouble you for an explanation? Does it have something to do with f[#][[2]]? Commented Feb 27, 2023 at 23:48
• Catch[Scan[If[f[#][[2]], Throw[f[#]]] &, dataPairs]] actually gives me the precise output that I need: {e, True}. Still, very interesting solution! Many thanks. Commented Feb 28, 2023 at 20:02

Your selection function can be simplified. I'm not sure what output you actually want, so I'll show two options.

(* the parameters as you've given them *)
yld = 0.0742;
pVal = 10000;
pVal2 = 10000;
dataPairs = {{a, 0.0027}, {b, 0.0027}, {c, 0.0027}, {d, 0.002}, {e, 0}, {f, 0.0027}, {g, 0.002}};

(* a selector function *)
fAlt[data_] := pVal2 (yld*Last@data + 1) <= pVal


The following will get you the first pair that satisfies fAlt:

SelectFirst[dataPairs, fAlt]


The following will get you the list up until (and excluding) the first pair that satisfies fAlt:

TakeWhile[dataPairs, Not@*fAlt]

• Thanks very much for your answer. Your SelectFirst[dataPairs, fAlt] most suited to my needs. I intend to put it into a speed match with @user1066's answer ;-). Commented Mar 2, 2023 at 1:02