Monday Mini-Golf #7: Simplify ingredient measurements

Monday Mini-Golf: A series of short challenges, posted (hopefully!) every Monday.
Sorry it's late; I realized 90% of the way through writing out a different idea that it was a duplicate.

My family is rather large, so we eat a lot of food. We usually need to double, triple, or even quadruple recipes to make enough food! But as multiplying the measurements can be a pain, it'd be nice to have a program to do this for us.

Challenge

Your challenge is to create a program or function that takes in a measurement as a number N and a letter L, and returns the same measurement, simplified as much as possible. Here's the required measurement units (all are American, like my family), and their corresponding letters:

1 cup (c) = 16 tablespoons (T) = 48 teaspoons (t)
1 pound (l) = 16 ounces (o)
1 gallon (g) = 4 quarts (q) = 8 pints (p) = 128 fluid ounces (f)

"simplified as much as possible" means:

• Using the largest measurement unit possible. Each unit can have a remainder of 1/4, 1/3, 1/2, 2/3, or 3/4.
• Turning the result into a mixed number, if necessary.

For example, 4 o is four ounces, which becomes 1/4 l, a quarter pound. 8 t, 8 teaspoons, becomes 2 2/3 T.

Details

• The input may be taken in any reasonable format; same with output. (1 t, 1,"t", 1\nt, etc.)
• Make sure any fractional part is dealt with properly. (11/4 in place of 1 1/4 is not allowed.)
• The number will always be a mixed number, and will always have a denominator of 2, 3, or 4 (or none). (no 1 1/8 T, no 1.5 T, etc.)
• As a result of the above, no downward conversions (e.g. cups to tablespoons) are ever needed.
• The letter will always be one of the letters listed above (Tcfglopqt).

Test-cases

Here's an large list, hopefully covering all types of cases:

Input   | Output
--------+--------
1/2 t   | 1/2 t
3/4 t   | 1/4 T
1 t     | 1/3 T
1 1/2 t | 1/2 T
2 t     | 2/3 T
2 1/4 t | 3/4 T
2 1/2 t | 2 1/2 t
3 t     | 1 T
10 t    | 3 1/3 T
16 t    | 1/3 c
5 1/3 T | 1/3 c
8 T     | 1/2 c
16 T    | 1 c
36 T    | 2 1/4 c
1/4 c   | 1/4 c
1024 c  | 1024 c
1 o     | 1 o
4 o     | 1/4 l
5 1/3 o | 1/3 l
5 2/3 o | 5 2/3 o
8 o     | 1/2 l
28 o    | 1 3/4 l
28 l    | 28 l
2 f     | 2 f
4 f     | 1/4 p
8 f     | 1/4 q
16 f    | 1/2 q
32 f    | 1/4 g
64 f    | 1/2 g
128 f   | 1 g
2/3 p   | 1/3 q
1 1/3 p | 2/3 q
2 p     | 1/4 g
1 q     | 1/4 g

Scoring

Our kitchen is very small, so the code should be as short as possible, so as not to make the kitchen more cramped. Shortest valid code in bytes wins; tiebreaker goes to submission that reached its final byte count first. The winner will be chosen next Monday, Nov 9. Good luck!

Please note that this challenge is similar to, but not a duplicate of, World Big Dosa.

• – Alex A. Nov 4 '15 at 2:13
• @AlexA. Ah, yes, I forgot to link to that. IMHO, it's sufficiently different: 1) it takes a different input format. 2) the output is quite a bit different. 3) more conversion types are required. 3a) the 1/8 measurement is not used. – ETHproductions Nov 4 '15 at 2:16
• @ETHproductions similiar spirit is equivalent to duplicate. – Akangka Nov 4 '15 at 10:11
• This'd never happen in proper, excuse me, metric units ;) – Adriaan Nov 4 '15 at 20:00
• Your golfs are getting less and less mini. – Dennis Nov 5 '15 at 4:27

Mathematica, 349 334 330 322 bytes

This answer section felt a little lonely. So uh, here's my attempt. Input should be given as in the test cases.

n=ToExpression@StringSplit@InputString[];i=#~Mod~1&;b=#&@@n;If[Length@n==3,{x,y,z}=n,{x,y,z}=If[IntegerQ@b,{b,0,Last@n},{0,b,Last@n}]];v={0,1/4,1/3,1/2,2/3,3/4};s=<|T->16,t->3,o->16,q->4,p->2,f->16|>;r=<|T->c,t->T,o->l,f->p,p->q,q->g|>;If[v~MemberQ~i[a=(x+y)/s@z],{x,y,z}={Floor@a,i@a,r@z}]~Do~3;Print@Row[{x,y,z}/. 0->""]

Explanation

First get the user input, split that input on whitespace, and assign that to n. i=#~Mod~1& creates a function that gets the fractional part of a number, by taking it mod 1. b=#&@@n will simply get the first item in n; that would be everything up to the first space.

If n is 3 elements long, that means we have an integer, a fraction, and a unit. {x,y,z}=n will assign x, y and z to be the three parts of n. The other case is that n is not 3 elements long; that means it’ll be 2 elements long instead. To stay consistent with above, we want x to be the integer part, y to be the fraction, and z the unit. So in this case, we need to check:

• If b (the first element of n) is an integer, then x=b, y=0 and z=Last@n (the last element of n).
• If b isn’t an integer, that means we only have a fraction with no integer. So we want to swap x and y from above; instead, x=0, y=b, and z is the same as above.

Now we need to set up some lists:

v = {0, 1/4, 1/3, 1/2, 2/3, 3/4} is the list of acceptable fractions, as stated in the question.

s = <|T -> 16, t -> 3, o -> 16, q -> 4, p -> 2, f -> 16|> is an association (key-value pair, like a dictionary in Python) that represents the amount needed of a given unit to go “up” to one of the next largest unit. For example, o -> 16 is because 16 ounces are required before we go up to 1 pound.

r = <|T -> c, t -> T, o -> l, f -> p, p -> q, q -> g|> is the association that actually represents what the next unit up is. For example, T -> c means one unit larger than Tablespoons is cups.

If[v~MemberQ~i[a = (x + y)/s@z], {x, y, z} = {Floor@a, i@a, r@z}]~Do~3

Now, the maximum number of times we need to go up a unit is 3; that would be fluid ounces (f) -> pints (p) -> quarts (q) -> gallon (g). So, now we do the following 3 times:

• Add x and y, (the integer and fractional parts)
• From the s association above, get element z; that is, access the current unit, and get the corresponding value in that association.
• Divide (x+y) by that value we got above, assign it to a, then get its fractional part.
• If that part is in the list v, then we can go up one unit; set x to a rounded down (integer part), set y to the fractional part of a, then access the association r with the current unit z to get the next unit up, and set that to z.
• If it’s not part of v instead, we don’t do anything, since it can’t be simplified.

Once that’s done 3 times, we print out the result:

Print@Row[{x,y,z}/. 0->””]

This simply prints out {x,y,z} in a row, but replaces any zeroes (if there’s no integer, or no fraction), with an empty string, so those don’t print out.