number_in_month exercise (Tycon Mismatch Error In SML function which tries to build a list) - smlnj

(* Write a function dates_in_month that takes a list of dates and a month (i.e., an int) and
returns a list holding the dates from the argument list of dates that are in the month.
The returned list should contain dates in the order they were originally given. *)
fun dates_in_months( datelist : (int*int*int) list, month : int) =
if null(tl (datelist))
then if #2(hd (datelist)) = month then #2(hd (datelist)) :: [] else []
else if #2(hd (datelist)) = month
then #2(hd (datelist)) :: number_in_month(tl datelist, month)
else number_in_month(tl datelist, month)
This is the error I get:
hw1.sml:55.22-55.78 Error: operator and operand do not agree [tycon mismatch]
operator domain: int * int list
operand: int * int
in expression:
(fn {2=<pat>,...} => 2) (hd datelist) ::
number_in_month (tl datelist,month)
val it = () : unit
Any help appreciated.

Found the correct way:
fun dates_in_month(datelist : (int*int*int) list, month : int) =
if null(tl (datelist))
then if #2(hd (datelist)) = month
then (hd (datelist)) :: []
else []
else if #2(hd (datelist)) = month
then (hd (datelist)) :: dates_in_month(tl datelist, month)
else dates_in_month(tl datelist, month)
I had several flaws, I was calling the function I copied code over from instead of the function itself + some other syntax issues. In addition, I added just the month element of the date-tuple to the list.

Related

number_in_month exercise (Compare two lists with each other)

I have previous asked, but no answer so I will try to rewrite the problem.
I have two lists. first list is dates (int * int * int) list and second list is only months. eg(1,2,3,4,5,6,7,8,9,10,11,12) int list
NOTE: assume any number in month list is not repeated and only 1-12.
I want to check how many dates has the same month as in the list of month.
example: [(87,09,08),(67,08,17),(90,08,23)], [1,5,8] = 2
I know how to recursively compare a list with a number, but i cant figure out how to recursivly compare a list with a list...
fun number_in_months (dates :( int * int * int) list, months : int list)=
if null dates
then 0
else if null months
then 0
else
let
fun dm_notempty (dates : (int * int * int) list, months : int list)=
if (null (tl dates) andalso null (tl months)) andalso (#2 (hd dates) <> hd months)
then 0
else if (null (tl dates) andalso null (tl months)) andalso (#2 (hd dates) = hd months)
then 1
else
let val dates_tl = dm_notempty(tl dates, tl months)
in
if #2(hd dates) = hd months
then dates_tl + 1
else dates_tl + 0
end
in
dm_notempty(dates, months)
end
Break the problem down into smaller problems.
For instance, imagine that you had a function number_in_month that counts the occurrences of a single month.
Then you could write this as
fun number_in_months (dates, []) = 0
| number_in_months (dates, x::xs) = number_in_month(dates, x) + number_in_months(dates, xs)
Now all that remains is that function, which is simpler to write because it only has one list argument, not two.
(Implementing it left as an exercise.)

Last Date Id Of Previous Months

My data-frame has a DateId (i.e. an integer column defining a date as a number of days since 1993-06-25). Objective is to calculate date id of the last day of month prior to each date in the column:
DateId -> _intermittent calc Date_ -> _result LastDayOfPriorMonthId_
9063 -> 2018-04-18 -> 9045 (i.e. 2018-03-31)
8771 -> 2017-06-30 -> 8741 (i.e. 2017-05-31)
9175 -> 2018-08-08 -> 9167 (i.e. 2018-07-31)
Solution would be really easy, but I'm running into issues with type conversion:
val a = Seq(9063, 8771, 9175).toDF("DateId")
val timeStart = to_date(lit("1993-06-25"))
val dateIdAdd : (Column) => Column = x => {x - date_add(timeStart, x).DATE_OF_MONTH}
The function compilation is failing with following error:
notebook:2: error: type mismatch;
found : org.apache.spark.sql.Column
required: Int
x - date_add(timeStart, x).DATE_OF_MONTH
Expressions like .cast(IntegerType) do not change the outcome (x is still a spark Column type and .cast(Int) is not applicable.
Please note: similar problem was addressed in this SO question, but the same approach is failing when the timeStart constant is applied here. Also using function would be preferred over expression, because the same calculation is used multiple columns with real data.
Can you translate from Java? Sorry, I don’t code Scala (yet).
private static final LocalDate baseDate = LocalDate.of(1993, Month.JUNE, 25);
public static long dateIdAdd(long dateId) {
LocalDate date = baseDate.plusDays(dateId);
LocalDate lastOfPrevMonth = YearMonth.from(date).minusMonths(1).atEndOfMonth();
return ChronoUnit.DAYS.between(baseDate, lastOfPrevMonth);
}
Edit: according to you (Dan, the asker), the Scala version is:
val baseDate = LocalDate.of(1993, Month.JUNE, 25)
val lastDayIdOfPriorMonth = udf((dateId : Long) => {
val date = baseDate.plusDays(dateId)
val lastOfPrevMonth = YearMonth.from(date).minusMonths(1).atEndOfMonth()
ChronoUnit.DAYS.between(baseDate, lastOfPrevMonth)
})
Let’s try it with your example dates (Java again):
System.out.println("9063 -> " + dateIdAdd(9063));
System.out.println("8771 -> " + dateIdAdd(8771));
System.out.println("9175 -> " + dateIdAdd(9175));
This prints:
9063 -> 9045
8771 -> 8741
9175 -> 9167
In your question you gave 9176 as desired result in the last case, but I believe that was a typo?
And please enjoy how clear and self-explanatory the code is.
After testing many options with Scala conversion function, hack based on UDF with Java string and SimpleDateFormat the only thing I could figure out:
val dateIdAdd = udf((dateId : Long) => {
val d = new SimpleDateFormat("yyyy-MM-dd")
val ts = d.parse("1993-06-25")
val tc = d.format(new Date(ts.getTime() + (24 * 3600 * 1000 * dateId)))
dateId - Integer.parseInt(tc.substring(tc.length()-2))
})
After adding another support function for validation and a simple select:
val dateIdToDate = udf((dateId : Long) => {
val d = new SimpleDateFormat("yyyy-MM-dd")
val ts = d.parse("1993-06-25")
d.format(new Date(ts.getTime() + (24 * 3600 * 1000 * dateId)))
})
val aa = a.select($"*"
, dateIdToDate($"DateId") as "CalcDateFromId"
, dateIdAdd($"DateId") as "CalcLastDayOfMonthId")
display(aa)
Expected results are generated (but I doubt this is the most efficient way available):
DateId CalcDateFromId CalcLastDayOfMonthId
9063 4/18/2018 9045
8771 6/30/2017 8741
9175 8/8/2018 9167

Combine multiple sequential entries in Scala/Spark

I have an array of numbers separated by comma as shown:
a:{108,109,110,112,114,115,116,118}
I need the output something like this:
a:{108-110, 112, 114-116, 118}
I am trying to group the continuous numbers with "-" in between.
For example, 108,109,110 are continuous numbers, so I get 108-110. 112 is separate entry; 114,115,116 again represents a sequence, so I get 114-116. 118 is separate and treated as such.
I am doing this in Spark. I wrote the following code:
import scala.collection.mutable.ArrayBuffer
def Sample(x:String):ArrayBuffer[String]={
val x1 = x.split(",")
var a:Int = 0
var present=""
var next:Int = 0
var yrTemp = ""
var yrAr= ArrayBuffer[String]()
var che:Int = 0
var storeV = ""
var p:Int = 0
var q:Int = 0
var count:Int = 1
while(a < x1.length)
{
yrTemp = x1(a)
if(x1.length == 1)
{
yrAr+=x1(a)
}
else
if(a < x1.length - 1)
{
present = x1(a)
if(che == 0)
{
storeV = present
}
p = x1(a).toInt
q = x1(a+1).toInt
if(p == q)
{
yrTemp = yrTemp
che = 1
}
else
if(p != q)
{
yrTemp = storeV + "-" + present
che = 0
yrAr+=yrTemp
}
}
else
if(a == x1.length-1)
{
present = x1(a)
yrTemp = present
che = 0
yrAr+=yrTemp
}
a = a+1
}
yrAr
}
val SampleUDF = udf(Sample(_:String))
I am getting the output as follows:
a:{108-108, 109-109, 110-110, 112, 114-114, 115-115, 116-116, 118}
I am not able to figure out where I am going wrong. Can you please help me in correcting this. TIA.
Here's another way:
def rangeToString(a: Int, b: Int) = if (a == b) s"$a" else s"$a-$b"
def reduce(xs: Seq[Int], min: Int, max: Int, ranges: Seq[String]): Seq[String] = xs match {
case y +: ys if (y - max <= 1) => reduce(ys, min, y, ranges)
case y +: ys => reduce(ys, y, y, ranges :+ rangeToString(min, max))
case Seq() => ranges :+ rangeToString(min, max)
}
def output(xs: Array[Int]) = reduce(xs, xs.head, xs.head, Vector())//.toArray
Which you can test:
println(output(Array(108,109,110,112,114,115,116,118)))
// Vector(108-110, 112, 114-116, 118)
Basically this is a tail recursive function - i.e. you take your "variables" as the input, then it calls itself with updated "variables" on each loop. So here xs is your array, min and max are integers used to keep track of the lowest and highest numbers so far, and ranges is the output sequence of Strings that gets added to when required.
The first pattern (y being the first element, and ys being the rest of the sequence - because that's how the +: extractor works) is matched if there's at least one element (ys can be an empty list) and it follows on from the previous maximum.
The second is if it doesn't follow on, and needs to reset the minimum and add the completed range to the output.
The third case is where we've got to the end of the input and just output the result, rather than calling the loop again.
Internet karma points to anyone who can work out how to eliminate the duplication of ranges :+ rangeToString(min, max)!
here is a solution :
def combineConsecutive(s: String): Seq[String] = {
val ints: List[Int] = s.split(',').map(_.toInt).toList.reverse
ints
.drop(1)
.foldLeft(List(List(ints.head)))((acc, e) => if ((acc.head.head - e) <= 1)
(e :: acc.head) :: acc.tail
else
List(e) :: acc)
.map(group => if (group.size > 1) group.min + "-" + group.max else group.head.toString)
}
val in = "108,109,110,112,114,115,116,118"
val result = combineConsecutive(in)
println(result) // List(108-110, 112, 114-116, 118)
}
This solution partly uses code from this question: Grouping list items by comparing them with their neighbors

Getting the date of the next day in PureScript

Is there anyway to write the following function more elegantly?
I can see some patterns but I'm not sure how to abstract them or how to find a simpler way to write the function.
type HasRemainder = Boolean
tomorrow :: Date -> Date
tomorrow date = unsafePartial $ canonicalDate y (fst m) (fst d)
where d :: Tuple Day HasRemainder
d = case toEnum $ 1 + fromEnum (day date) of
Just v -> Tuple v false
Nothing -> Tuple (unsafePartial $ fromJust $ toEnum 1) true
m :: Tuple Month HasRemainder
m = if snd d then
case toEnum $ 1 + fromEnum (month date) of
Just v -> Tuple v false
Nothing -> Tuple (unsafePartial $ fromJust $ toEnum 1) true
else Tuple (month date) false
y :: Year
y = if snd m then
case toEnum $ 1 + fromEnum (year date) of
Just v -> v
-- use 2018 arbitrarly if the conversion from int to Date fails
Nothing -> unsafePartial $ fromJust $ toEnum 2018
else (year date)
I'd do something like this:
import Data.DateTime as DT
import Data.Maybe (maybe)
import Data.Time.Duration (Days(..))
tomorrow :: DT.Date -> DT.Date
tomorrow dt = maybe dt DT.date $ DT.adjust (Days 1.0) (DT.DateTime dt bottom)
Although it will return the input date in the unlikely event that the given date is top (which is 31st December 275759 if I remember correctly).
There is an adjust function for Time and DateTime so it's just an oversight that Date is missing one.
I would try something along those lines
getDatePart datepart defaultval1 defaultval2 =
case toEnum $ defaultval1 + fromEnum datepart of
Just v -> Tuple v false
Nothing -> Tuple (unsafePartial $ fromJust $ toEnum defaultval2) true
getDatePartDefault d datepart defaultval1 defaultval2 =
if snd d then
getDatePart datepart defaultval1 defaultval2
else Tuple datepart false
tomorrow :: Date -> Date
tomorrow date = unsafePartial $ canonicalDate (fst y) (fst m) (fst d)
where d :: Tuple Day HasRemainder
d = getDatePart (day date) 1 1
m :: Tuple Month HasRemainder
m = getDatePartDefault d (month date) 1 1
y :: Tuple Year HasRemainder
y = getDatePartDefault d (year date) 1 2018
Take care: this is not tested
Using succ from Enum:
import Data.Date (Date)
import Data.Enum (succ)
import Data.Maybe (Maybe)
today :: Date
today = ...
tomorrow :: Maybe Date
tomorrow = succ today

Filling date gaps in Anorm results

i'm new to Scala, Play and Anorm, so I'm wondering how can I do this.
I have a query to my database, which returns a date, with a DD/MM HH:OO format, and a Long, which is a total.
I want to display a total per hour graph, so I create a byhour parser:
val byhour = {
get[Option[String]]("date") ~ get[Long]("total") map {
case date ~ total => (date, total)
}
And this, of course, only returns the dates where I have data. I want to fill the date gaps with the date and a total of 0, but I'm not sure how to do it.
Thanks in advance!
edit: I know it's possible to do this in MySQL, but I'd prefer to do this in Scala itself to keep the queries clean.
I don't think that related to Anorm directly, which there will allow you to fill gaps among parsed results afterward.
First option you get unordered result as List[(String, Long)] using .as(byhour.*), sort it by date and then fill with zero for missing date.
SQL"...".as(byhour.*).sortBy(_._1).
foldLeft(List.empty[(String, Long)]) {
case (p :: l, (d, t)) =>
(d, t) :: prefill(p, d, l)
case (l, (d, t)) =>
(d, t) :: l // assert l == Nil
}.reverse
/**
* #param p Previous/last tuple
* #param d Current/new date
* #param l List except `p`
* #return List based on `l` with `p` prepended and eventually before with some filler tuple prepended.
*/
def prefill(p: (String, Long), d: String, l: List[(String, Long)]): List[(String, Long)] = ???
Otherwise if you query returns results ordered by date you can use Anorm streaming API and fill gap as soon as it's discovered.
// Anorm 2.3
import anorm.Success
SQL"... ORDER BY date ASC".apply().
foldLeft(List.empty[(String, Long)]) {
case (l, row) =>
byhour(row) match {
case Success((d, t)) =>
l match {
case p :: ts =>
(d, t) :: prefill(p, d, l)
case _ => (d, t) :: l
}
case _ => ??? // parse error
}
}.reverse