15
$\begingroup$

Here is my list:

collection = {76.6256, 51.9264, 50.238, 14.4203, 80.9205, 12.2036, 2.39568,
              38.2747, 12.4422, 29.9621}

collection /. {x_ /; x < 50 -> "F"} 

and this gives me

{76.6256, 51.9264, 50.238, "F", 80.9205, "F", "F", "F", "F", "F"}

as expected. But I want to add more rules with ranges like that. I'm not sure how to do this. I've tried:

collection /. {{x_ /; x < 50 -> "F"}, {x_ /; 50 <= x < 56 -> "D"},
               {x_ /; 56 <= x < 71 -> "C"}, {x_ /; 71 <= x < 85 -> "B"},
               {x_ /; 85 <= x <= 100 -> "A"}}

and that yields

{{76.6256, 51.9264, 50.238, "F", 80.9205, "F", "F", "F", "F", "F"},
   {76.6256, "D", "D", 14.4203, 80.9205, 12.2036, 2.39568, 38.2747, 12.4422, 29.9621},
   {76.6256, 51.9264, 50.238, 14.4203, 80.9205, 12.2036, 2.39568, 38.2747,
         12.4422, 29.9621},
   {"B", 51.9264, 50.238, 14.4203, "B", 12.2036, 2.39568, 38.2747,
         12.4422, 29.9621},
   {76.6256, 51.9264, 50.238, 14.4203, 80.9205, 12.2036, 2.39568, 38.2747,
         12.4422, 29.9621}}`

and I've tried a few others which don't even work at all. What is the simplest way of going about this? My real question is, how do I construct a list of rules where I can determine which range (0-50, 50-60, etc.) each number in the list is in?

$\endgroup$
1
  • $\begingroup$ Your replacement rules should be a list of rules, not a list of lists. When it's a list mylist of lists, ReplaceAll (/.) returns a list of results, one for each list in mylist. See @belisarius's answer. $\endgroup$
    – Michael E2
    Sep 23, 2014 at 21:59

7 Answers 7

17
$\begingroup$

Just put all your rules in a list. You don't need the lower bounds. The first matching rule in the list is applied:

s = {x_ /; x < 50 -> "F", x_ /; x < 56 -> "D", x_ /; x < 71 -> "C", 
     x_ /; x < 85 -> "B", x_ /; x <= 100 -> "A"};

collection /. s

(* {"B", "D", "D", "F", "B", "F", "F", "F", "F", "F"}*)
$\endgroup$
1
  • 1
    $\begingroup$ Crap! I swear I tried this exact same thing and it didn't work for me! But perhaps I forgot a bracket or something. This is definitely the simplest and was exactly what I was looking for. Thank you very much and thanks to everyone who contributed. Your help is really appreciated! $\endgroup$ Sep 24, 2014 at 0:22
17
$\begingroup$
Clear[grade]

grade[x_?NumericQ] = Piecewise[{
    {"F", x < 50}, {"D", x < 56},
    {"C", x < 71}, {"B", x < 85}},
   "A"];

collection = {76.6256, 51.9264, 50.238,
   14.4203, 80.9205, 12.2036, 2.39568,
   38.2747, 12.4422, 29.9621};

grade /@ collection

{"B", "D", "D", "F", "B", "F", "F", "F", "F", "F"}

$\endgroup$
10
$\begingroup$

For a large number of values a better method is to use Interpolation with an order of zero.

First build the InterpolatingFunction:

points = {{50, "F"}, {56, "D"}, {71, "C"}, {85, "B"}, {100, "A"}};

fn = Interpolation[points, InterpolationOrder -> 0];

Then:

fn[collection] // Quiet
{"B", "D", "D", "F", "B", "F", "F", "F", "F", "F"}

Performance

I claimed that for "a large number of values" the method above is superior to replacement rules. Here is a BenchmarkPlot to support that assertion:

letters = StringJoin /@ Tuples[CharacterRange["A", "Z"], 3];
numbers = Sort @ RandomSample[Range[100000], 26^3];
points = {numbers, letters}\[Transpose];
rand = RandomReal[100000, 50000];
f1 = ReplaceAll[rand, (x_ /; x < # -> #2) & @@@ #] &;
f2 = Interpolation[#, InterpolationOrder -> 0][rand] &;
Off[InterpolatingFunction::dmval]

Needs["GeneralUtilities`"]

BenchmarkPlot[{f1, f2}, Sort@RandomSample[points, #] &, 2^Range[14], 
 "IncludeFits" -> True, TimeConstraint -> 30]

enter image description here

  • Note the logarithmic scale.

  • The x axis is the number of levels (letters) in the assignment.

  • Time to construct each InterpolatingFunction is included; reuse would be faster.

ReplaceAll is faster with only a few levels, but from eight and up InterpolatingFunction is faster, and with many levels orders of magnitude faster.

$\endgroup$
9
$\begingroup$

More handy in case of more notes (?).

notes = {"F", "D", "C", "B", "A"};
bl = BinLists[collection, {{0, 50, 56, 71, 85, 100}}];

collection /. Flatten@MapThread[Thread[# -> #2] &, {bl, notes}]
{"B", "D", "D", "F", "B", "F", "F", "F", "F", "F"}

or

notes = {"F", "D", "C", "B", "A"}
levels = {50, 56, 71, 85, 100}
Block[{x},
 Function[note[x_] := Which[##]] @@ Flatten[MapThread[{x <= #, #2} &, 
                                                      {levels, notes}]]

     ]

note /@ collection
$\endgroup$
1
  • 1
    $\begingroup$ +1 I've seen cases where 26 letters aren't enough $\endgroup$ Sep 23, 2014 at 22:02
6
$\begingroup$

You can use Fold to successively apply a list of rules.

Fold[#1 /. #2 &, collection, {{x_ /; x < 50 -> "F"},
    {x_ /; 50 <= x < 56 -> "D"}, {x_ /; 56 <= x < 71 -> "C"},
    {x_ /; 71 <= x < 85 -> "B"}, {x_ /; 85 <= x <= 100 -> "A"}}]
{"B", "D", "D", "F", "B", "F", "F", "F", "F", "F"}
$\endgroup$
5
$\begingroup$
collection /. 
 x_?NumberQ :> Which[x < 50, "F", x < 56, "D", x < 71, "C", x < 85, "B", x <= 100, "A"]

{"B", "D", "D", "F", "B", "F", "F", "F", "F", "F"}

Timing comparison for 1000 random reals repeated 1000 times

enter image description here

$\endgroup$
3
  • $\begingroup$ (+1) timings={2.53, 2.71, 3.01, 3.42, 4.68, 5.09, 13.59}; gradesF[{10, Infinity}, Reverse@{"ok", "Ouch"}][timings]:) $\endgroup$
    – kglr
    Sep 24, 2014 at 13:47
  • $\begingroup$ @kguler your solution has merits (flexibility) which - in many cases - should be more important than seconds :) $\endgroup$
    – eldo
    Sep 24, 2014 at 14:11
  • $\begingroup$ Please see the timing plot in my answer. It doesn't invalidate your timings but it clarifies and supports what I meant about the performance of Interpolation. $\endgroup$
    – Mr.Wizard
    Oct 2, 2014 at 17:02
4
$\begingroup$
ClearAll[gradesF];
gradesF[x_: {49, 59, 79, 89, 100}, y_: {"A", "B", "C", "D", "F"}] := 
  Function[{z}, Piecewise[{#, z < #2} & @@@ (Flatten /@ Thread[{Reverse@y, x}])], Listable];

gradesF[][collection]
(* {"C","D","D","F","B","F","F","F","F","F"} *)

gradesF[{50, 56, 71, 85, 100}][collection]
(* {"B","D","D","F","B","F","F","F","F","F"} *)

gradesF[{69, 100}, {"Pass", "Fail"}][collection]
(* {"Pass","Fail","Fail","Fail","Pass","Fail","Fail","Fail","Fail",\
"Fail"} *)
$\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.