14
$\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$
  • $\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 '14 at 21:59
16
$\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
    $\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$ – Sultan of Swing Sep 24 '14 at 0:22
16
$\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$
9
$\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$
8
$\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
    $\begingroup$ +1 I've seen cases where 26 letters aren't enough $\endgroup$ – Dr. belisarius Sep 23 '14 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$
  • $\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 '14 at 13:47
  • $\begingroup$ @kguler your solution has merits (flexibility) which - in many cases - should be more important than seconds :) $\endgroup$ – eldo Sep 24 '14 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 '14 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, privacy policy and cookie policy

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