# How to assign a list of numbers into letter grades

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?

• 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. Commented Sep 23, 2014 at 21:59

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"}*)

• 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! Commented Sep 24, 2014 at 0:22
Clear[grade]

{"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};



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

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]


• 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.

More handy in case of more notes (?).

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


{"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

• +1 I've seen cases where 26 letters aren't enough Commented Sep 23, 2014 at 22:02

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"}

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

• (+1) timings={2.53, 2.71, 3.01, 3.42, 4.68, 5.09, 13.59}; gradesF[{10, Infinity}, Reverse@{"ok", "Ouch"}][timings]:)
– kglr
Commented Sep 24, 2014 at 13:47
• @kguler your solution has merits (flexibility) which - in many cases - should be more important than seconds :)
– eldo
Commented Sep 24, 2014 at 14:11
• 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. Commented Oct 2, 2014 at 17:02
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];