6
$\begingroup$

I have a list of reals and I'm replacing those which are contained in a given interval with two different values depending on wheter the selected reals are positive or negative.

I came up with this (overly convoluted) code:

list = {-2., -1.6, -1.2, -0.8, -0.4, 0., 0.4, 0.8, 1.2, 1.6, 2.}

list /. MapThread[#2 -> #1 &, {If[TrueQ[#], 1, -1] & /@ Positive /@ #, #}]&@Cases[list, x_ /; -1 < x < 1]  

This does the work, but I'm sure there are more efficent ways of dealing with replacing elements of a list according to multiple criteria.
My question is how could I make a better use of pattern matching to solve the problem and thus possibly shorten/simplify the code.

Edit:
Generalizing the problem, given a list of numbers, I need to:
1. Identify all the numbers between a choosen interval
2. Check which of these numbers are positive and which are negative
3. Replace each of them with one of the two new choosen values, according to their sign

Here's another example with my old code:

list = {-3., -2.6, -2.2, -1.8, -1.4, -1., -0.6, -0.2, 0.2, 0.6, 1., 1.4, 1.8, 2.2, 2.6`, 3.}  

{min, max} = {-1.5, 3};

{newValueIfPositive, newValueIfNegative} = {50, 700};

list /. MapThread[#2 -> #1 &, {If[TrueQ[#], newValueIfPositive,newValueIfNegative] & /@ Positive /@ #, #}] &@Cases[list, x_ /; min < x < max]
$\endgroup$
9
  • 4
    $\begingroup$ Like this: {-2., -1.6, -1.2, -0.8, -0.4, 0., 0.4, 0.8, 1.2, 1.6, 2.} /. x_ /; Abs[x] < 1 :> (1 - 2 UnitStep[-x])? $\endgroup$ Jul 24, 2016 at 10:43
  • $\begingroup$ Let's make it more Listable~ $\endgroup$
    – Wjx
    Jul 24, 2016 at 10:58
  • 1
    $\begingroup$ Why not just use two rules? list /. {x_ /; -1 < x <= 0 -> -1, x_ /; 0 < x < 1 -> 1} $\endgroup$ Jul 24, 2016 at 11:05
  • $\begingroup$ An alternative way: Block[{b = UnitBox[list/2]}, list + b (Sign[list] + list)]a bit slower than @SimonWoods ' answer $\endgroup$
    – Wjx
    Jul 24, 2016 at 11:11
  • 1
    $\begingroup$ For your general problem: list /. x_ /; min < x < max :> Rescale[Sign[x], {-1, 1}, {700, 50}]. But did you think about how to handle 0? $\endgroup$ Jul 24, 2016 at 14:01

2 Answers 2

6
$\begingroup$

Given:

list = {-3., -2.6, -2.2, -1.8, -1.4, -1., -0.6, -0.2, 0.2, 0.6, 1., 1.4, 1.8, 2.2, 2.6, 3.};
{min, max} = {-1.5, 3};
{pos, non} = {50, 700};

I'd just stick with a direct expression of the requirement:

Replace[list, x_ /; min < x < max :> If[Positive[x], pos, non], {1}]

(*
{-3., -2.6, -2.2, -1.8, 700, 700, 700, 700, 50, 50, 50, 50, 50, 50, 50, 3.}
*)

This performs well enough, even for large lists, that I wouldn't bother trying to vectorize it unless a performance profile revealed that this expression is the application hot-spot. Even then, effective optimization would require more information about the wider application context to see whether significant performance improvements are to be gained by an algorithm change as opposed to micro-optimizing this particular expression.

In the case at hand, /. could be used instead of Replace[…, {1}]:

list /. x_ /; min < x < max :> If[Positive[x], pos, non]

However, this relies upon the coincidence that when < is applied to lists it does not return a boolean value thus causing the pattern matcher to descend into the list elements to try further replacements. For different conditions, the replacement operation might mistakenly attempt to transform the entire top-level expression instead of each list element individually. Code written in this way is brittle since a seemingly innocuous change to the pattern condition can radically change the program meaning. For that reason, I tend to use Replace with an explicit level specification as a defensive measure.

$\endgroup$
2
$\begingroup$

A rather convoluted method to apply this transformation, without pattern matching:

rep[a_List, lo_, hi_, n_, p_] :=
  With[{u = Unitize @ Clip[a, {lo, hi}, {0, 0}]},
    Join[{p}, a, {n}][[(1 - u) Range[2, Length @ a + 1] + Sign[a u]]]
  ]

list = {-2., -1.6, -1.2, -0.8, -0.4, 0., 0.4, 0.8, 1.2, 1.6, 2.};

rep[list, -1, 1, neg, pos]
{-2., -1.6, -1.2, neg, neg, 0., pos, pos, 1.2, 1.6, 2.}
$\endgroup$

Your Answer

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

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