Removing repeated statements from nested conditions in Ocaml - ocaml

As an exercise in class, we are supposed to calculate the entry price for people in a night club, provided their age and gender. The under 25 get 20% off, and Females/NBs get 50% off, stacking multiplicatively.
While my code works, it repeats the gender check twice, which is poor form and could cause problems in a more complex application. How can I spare the repetition ?
(* OCaml Version *)
let entry_price age gender =
if age < 18
then (failwith "Must be over 18 to enter")
else let price = 12.0 in
if age <= 25
then let price = (price *. 0.8) in
if gender == 'f' || gender == 'x'
then (price *. 0.5)
else prix
else if gender == 'f' || gender == 'x'
then (price *. 0.5)
else price;;
Here is a Python version that does not repeat itself, thanks to ternary operators : (Could also be done with non-nested ifs, which Ocaml disallows)
# Python version
def entry_price(age : int, gender : str):
if age < 18:
raise ArgumentError("Must be over 18 to enter")
return (
12.0
*(0.8 if (age<25) else 1)
*(0.5 if gender in ['f', 'x'] else 1)
)

You can pretty much copy the python version:
let entry_price age gender =
if age < 18
then failwith "Must be over 18 to enter"
else
12.0
*. (if age < 25 then 0.8 else 1.0)
*. (if gender = 'f' || gender = 'x' then 0.5 else 1.0);;
There's only a slight difference in the if expression syntax, and you need to use the float-specific multiplication operator, *., instead of the int-specific *.
Also, to pick a nit, there are no statements in OCaml. Statements are an imperative/procedural construct, ie. do this, then do that. OCaml is an expression-based language, where each expression evaluates to a value. You can still discard the value of an expression, and thereby make it look like a statement, but the compiler will complain unless you are very explicit about discarding it since that is usually a user error.

#glennsl provides a nice direct translation of the Python code to OCaml, but there are other ways we can approach this.
If we create a variant type to describe ages, we can write a categorize_age function which will translate a numeric age to a descriptive term we can pattern match on. It also gives us a convenient place in the future to change what ages we use to make these determinations.
Then we can use a match on both age and gender to consider the four possible outcomes:
Under 18
Under 25 and either 'f' or 'x' gender
25 or older and either 'f' or 'x' gender
Anyone else 18 or older
type age_cat = Under_age | Legal | Adult
let categorize_age a =
if a < 18 then Under_age
else if a <= 25 then Legal
else Adult
let entry_price age gender =
let price = 12.0 in
match categorize_age age, gender with
| Under_age, _ -> failwith "Must be over 18 to enter"
| Legal, ('f' | 'x') -> price *. 0.8 *. 0.5 (* or just 0.4 *)
| Adult, ('f' | 'x') -> price *. 0.5 *. 0.5 (* or just 0.25 *)
| _ -> price
Looking at the logic here, we actually don't need to specify Adult, ('f' | 'x') because we know that we've already matched cases where when gender is 'f' or 'x', age is either Under_age or Legal. Thus we can use a wildcard _ for the pattern: _, ('f' | 'x').
The last pattern is a wildcard because we're matching any case that hasn't already been matched, and those values are not relevant to the outcome, so there's no point in binding name(s) to them.
let entry_price age gender =
let price = 12.0 in
match categorize_age age, gender with
| Under_age, _ -> failwith "Must be over 18 to enter"
| Legal, ('f' | 'x') -> price *. 0.8 *. 0.5 (* or just 0.4 *)
| _, ('f' | 'x') -> price *. 0.5 *. 0.5 (* or just 0.25 *)
| _ -> price

Related

VTL Velocity Template Language - Problems with type casting

I have written the VTL below to check to see if variables(attributes) I am getting from my PIM system are not blank. If they are set them to zero. Then mathematically add the two variables.
The problem I am having is they must be strings or treated as string. The result of ($FordR12 + $DodgeR12) is the number concatenated. Example: 58 + 58 = 5858 How do I add these strings mathematically as numbers?
#if(${R12 Sales Rev Ford VDSP}== "")
#set($FordR12 = 0)
#else
#set($FordR12 = ${R12 Sales Rev Ford VDSP})
#end
#if(${R12 Sales Rev Dodge VDSP}== "")
#set($DodgeR12 = 0)
#else
#set($DodgeR12 = ${R12 Sales Rev Dodge VDSP})
#end
#set($Total = ($FordR12 + $DodgeR12))
$Total
I have looked online for ways to type cast the variables, including the VTL online documentation. All have failed so far :(
It seems like your variables are strings, which means VTL will concatenate them instead of adding them together.
You can get access to Integer.parseInt and convert them to Integers first, like this:
#set($Integer = 0)
#if(${R12 Sales Rev Ford VDSP} == "")
#set($FordR12 = 0)
#else
#set($FordR12 = $Integer.parseInt(${R12 Sales Rev Ford VDSP}))
#end
#if(${R12 Sales Rev Dodge VDSP} == "")
#set($DodgeR12 = 0)
#else
#set($DodgeR12 = $Integer.parseInt(${R12 Sales Rev Dodge VDSP}))
#end
#set($Total = ($FordR12 + $DodgeR12))
$Total
Though it doesn't appear you're using an AWS service here, here's a Mapping Tool link which is still helpful for trying these things out: https://mappingtool.dev/app/apigateway/b08ed9b630114561134d7a41315d817c
Note that I've changed your variable name because it's not valid in standard VTL.

Syntax errors in main function - SML/NJ [deleting DO VAL, deleting VAL ID, SEMICOLON ID, deleting SEMICOLON END SEMICOLON]

May someone highlight to me why I am getting the syntax errors for the main function, so that I can fix it. I am quite new to the language. Actually I was introduced to it through the assignment, so I am totally lost as to how to refactor it to avoid the syntax error:
val IDs = [410021001,410021002,410021003,410021004,410021005,410021006,410021007,410021008,410021009,410021010];
val Names = ["Alan","Bob","Carrie","David","Ethan","Frank","Gary","Helen","Igor","Jeff"]: string list;
val HW1 = [90.0,85.0,90.0,117.0,85.0,90.0,117.0,117.0,117.0,117.0] : real list;
val HW2 = [84.5,49.0,110.5,85.0,56.0,65.0,65.0,59.5,50.0,50.0] : real list;
val HW3 = [117.0,117.0,117.0,0.0,65.0,117.0,50.0,51.0,75.0,75.0] : real list;
val Midterm = [60.0,57.0,6.0,44.0,72.0,43.0,54.0,75.0,53.0,75.0] : real list;
val Final = [66.0,64.0,62.0,55.0,66.0,75.0,75.0,75.0,75.0,75.0] : real list;
fun score(HW1, HW2, HW3, Midterm, Final) =
round(HW1 * 0.1 + HW2 * 0.1 + HW3 * 0.1 + Midterm * 0.3 + Final * 0.4);
fun letterGrade(score) =
if score >= 90 then "A+"
else if score >= 85 then "A"
else if score >= 80 then "A-"
else if score >= 77 then "B+"
else if score >= 73 then "B"
else if score >= 70 then "B-"
else if score >= 67 then "C+"
else if score >= 63 then "C"
else if score >= 60 then "C-"
else if score >= 50 then "D"
else "E";
val i = 0
val max = length(IDs)
fun main() =
while i < max do
var ind_score = score(HW1[i], HW2[i], HW3[i], Midterm[i], Final[i])
var grade = letterGrade(ind_score)
print(IDs[i], " ", Names[i], " ", ind_score, " ", grade)
i = i + 1
end
end
This is the error I am producing after running my programme, which shows that my errors start at this function:
Terminal feedback
Part 1 - Straightforward corrections
I'll go from the simplest to the most complex. I'll also provide a more functional implementation in the end, without the while loop.
The construct var does not exist in ML. You probably meant val ind_score = ...
Array indexing is not done by array[i]. You need (as with everything else) a function to do that. The function happens to be List.nth. So, everywhere you have HW1[i], you should have List.nth(HW1, i).
Most language constructs expect a single expression, so you usually cannot simply string commands as we do in imperative languages. Thus, there are some constructs missing after the do in your while.
Variables in functional languages are usually immutable by default, so you have to indicate when you want something to be mutable. In your while, you want i to be mutable, so it has to be declared and used as such: val i = ref 0. When using the value, you have to use the syntax !i to get the 'current' value of the variable (essentially, de-referencing it).
The function call syntax in ML does not use (). When you call a function like score(a, b, c, d) what you are doing is creating a tuple (a, b, c, d) and passing it as a single argument to the function score. This is an important distinction because you are actually passing a tuple to your print function, which does not work because print expects a single argument of type string. By the way, the string concatenation operator is ^.
If you do all these changes, you'll get to the following definition of main. It is quite ugly but we will fix that soon:
val i = ref 0 (* Note that i's type is "int ref". Think about it as a pointer to an integer *)
val max = length(IDs)
fun main() =
while !i < max do (* Notice how annoying it is to always de-reference i and that the syntax to access a list element is not very convenient *)
let (* The 'let in end' block is the way to define values that will be used later *)
val ind_score = score(List.nth(HW1, !i), List.nth(HW2, !i), List.nth(HW3, !i), List.nth(Midterm, !i), List.nth(Final, !i))
val grade = letterGrade(ind_score)
in ( (* The parenthesis here allow stringing together a series of "imperative" operations *)
print(Int.toString(List.nth(IDs, !i)) ^ " " ^ List.nth(Names, !i) ^ " " ^ Int.toString(ind_score) ^ " " ^ grade ^ "\n");
i := !i + 1 (* Note the syntax := to re-define the value of i *)
)
end;
Part 2 - Making it more functional
Functional language programs are typically structured differently from imperative programs. A lot of small functions, pattern matching and recursion are typical. The code below is an example of how you could improve your main function (it is by no means "optimal" in terms of style though). A clear advantage of this implementation is that you do not even need to worry about the length of the lists. All you need to know is what to do when they are empty and when they are not.
(* First, define how to print the information of a single student.
Note that the function has several arguments, not a single argument that is a tuple *)
fun printStudent id name hw1 hw2 hw3 midterm final =
let
val ind_score = score (hw1, hw2, hw3, midterm, final)
val grade = letterGrade ind_score
in
print(Int.toString(id) ^ " " ^ name ^ " " ^ Int.toString(ind_score) ^ " " ^ grade ^ "\n")
end;
(* This uses pattern matching to disassemble the lists and print each element in order.
The first line matches an empty list on the first element (the others don't matter) and return (). Think of () as None in Python.
The second line disassemble each list in the first element and the rest of the list (first::rest), print the info about the student and recurse on the rest of the list.
*)
fun printAllStudents (nil, _, _, _, _, _, _) = ()
| printAllStudents (id::ids, name::names, hw1::hw1s, hw2::hw2s, hw3::hw3s, mid::midterms, final::finals) =
(printStudent id name hw1 hw2 hw3 mid final;
printAllStudents(ids, names, hw1s, hw2s, hw3s, midterms, finals));
printAllStudents(IDs, Names, HW1, HW2, HW3, Midterm, Final);
Note that it is a bit of a stretch to say that this implementation is more legible than the first one, even though it is slightly more generic. There is a way of improving it significantly though.
Part 3 - Using records
You may have noticed that there is a lot of repetition on the code above because we keep having to pass several lists and arguments. Also, if a new homework or test was added, several functions would have to be reworked. A way to avoid this is to use records, which work similarly to structs in C. The code below is a refactoring of the original code using a Student record. Note that, even though it has a slightly larger number of lines than your original code, it is (arguably) easier to understand and easier to update, if needed. The important part about records is that to access a field named field, you use an accessor function called #field:
(* Create a record type representing a student *)
type Student = {id:int, name:string, hw1:real, hw2:real, hw3:real, midterm:real, final:real};
(* Convenience function to construct a list of records from the individual lists of values *)
fun makeListStudents (nil, _, _, _, _, _, _) = nil (* if the input is empty, finish the list *)
| makeListStudents (id::ids, name::names, hw1::hw1s, hw2::hw2s, hw3::hw3s, mid::midterms, final::finals) = (* otherwise, add one record to the list and recurse *)
{id=id, name=name, hw1=hw1, hw2=hw2, hw3=hw3, midterm=mid, final=final} :: makeListStudents(ids, names, hw1s, hw2s, hw3s, midterms, finals);
val students = makeListStudents (IDs, Names, HW1, HW2, HW3, Midterm, Final);
fun score ({hw1, hw2, hw3, midterm, final, ...}: Student): int = (* Note the special patter matching syntax *)
round(hw1 * 0.1 + hw2 * 0.1 + hw3 * 0.1 + midterm * 0.3 + final * 0.4);
fun letterGrade (score) =
if score >= 90 then "A+"
else if score >= 85 then "A"
else if score >= 80 then "A-"
else if score >= 77 then "B+"
else if score >= 73 then "B"
else if score >= 70 then "B-"
else if score >= 67 then "C+"
else if score >= 63 then "C"
else if score >= 60 then "C-"
else if score >= 50 then "D"
else "E";
(* Note how this function became more legible *)
fun printStudent (st: Student) =
let
val ind_score = score(st)
val grade = letterGrade(ind_score)
in
print(Int.toString(#id(st)) ^ " " ^ #name(st) ^ " " ^ Int.toString(ind_score) ^ " " ^ grade ^ "\n")
end;
(* Note how, now that we have everything in a single list, we can use map *)
fun printAllStudents (students) = map printStudent students;
printAllStudents(students);

Replacement for chained if-statements

I'm new to SML and I'm at the point where I can write functional code but I'm unsure of whether there's a more proper or idiomatic way to do things. SML only allows value constructors in patterns, so a case statement doesn't work below. SML also doesn't allow multiple else-if statements.
The following works, but has an ugly triply-nested for-loop. Is there a more idiomatic way to write the following code?
datatype coins = penny | nickle | dime | quarter;
fun valueToCoins 0 = nil
| valueToCoins x =
if x >= 25
then quarter::valueToCoins(x-25)
else
if x >= 10
then dime::valueToCoins(x-10)
else
if x >= 5
then nickle::valueToCoins(x-5)
else penny::valueToCoins(x-1);
The comments have addressed this, but really you've done the right thing. You just need to format it properly and it looks reasonable.
datatype coins = penny | nickle | dime | quarter;
fun valueToCoins 0 = nil
| valueToCoins x =
if x >= 25 then
quarter :: valueToCoins(x - 25)
else if x >= 10 then
dime :: valueToCoins(x - 10)
else if x >= 5 then
nickle :: valueToCoins(x - 5)
else
penny :: valueToCoins(x - 1);

Retrieve matching values from branch to leaf

So im trying to make a tree with companies as the branch and subcompanies of the company as leafs.
Every company/subcompany consists of a name, a gross income and a list with their subcompanies.
I've made a function that returns a tuple with (name,gross income) for all the companies/subcompanies in the company tree.
What I would like to do now, is to find the total income for a certain company/subcompany, which means that the function should add all the subcompanies gross income together with the companies and return the result. It should only have a string as input (the name of the company).
type Department = a' list
and a' = | SubDep of string * float
| Company of string * float * Department;;
let company =
Company("Arla", 1000.0, [SubDep("Karolines Køkken", 500.0);
Company("Lurpak", 200.0, [SubDep("Butter",100.0); SubDep("Milk", 100.0)]);
SubDep("Arla Buko", 100.0);
Company("Riberhus", 200.0, [SubDep("Cheese",200.0)])
]);;
So an example run should be:
companyTotalIncome "Arla" company;;
And the result should be: 2400
To sum everything up you can do something like this (don't really know why you'll need the name of the company in here as for your example you just summed it all up):
let rec depIncome =
function
| SubDep (_, income) -> income
| Company (_,income, sub) -> income + totalIncome sub
and totalIncome =
List.sumBy depIncome
but your data structures are real strange (why list of companies a department?)
I would do maybe like this:
type Department =
{ name : String
income : float
}
type Company =
{ name : String
departmens : Department list
subCompanies : Company list
}
and then
let rec income company =
company.departments |> List.sumBy (fun d -> d.income }
+ company.subCompanies |> List.sumBy income
of course you can move the income to the company too (or add one there too) - I really don't know what you want to do - just my 5cts.

Code to Tell Joke if User is Between Certain Age

age = 0
joke = 'What do you call a cow with no legs? Ground beef.'
myName = raw_input('Hello! What is your name?')
myAge = raw_input('Tell me your age, ' + myName + ', to hear a joke!')
if age < 6:
print('You are too young to hear this joke.')
elif age > 12:
print('You are too old to hear this joke.')
elif age == range(6, 12):
print(joke)
The code, when run, only says that I am too young to hear the joke even if I put a number higher than 6 or even higher than 12. Could you help me fix this problem?
There's no need for the last elif. Just use else without a condition.
If both age < 6 and age > 12 evaluate to false, we know that 6 ≤ a ≤ 12. There's no need to check for it.
BTW, range() returns an iterable, not something you can compare with a number. The comparison will always fail because you're comparing two things that are incompatible.
You're assigning the input into variable "myAge" and are testing variable "age" in your if statement.
Replace
myAge = raw_input('Tell me your age, ' + myName + ', to hear a joke!')
with
age = input('Tell me your age, ' + myName + ', to hear a joke!')