How can I calculate the longest duration in XQuery? - max

I have a problem with an exercise in XQuery. The logic:
Get the course that has the longest duration.
This is the structure of the xml file:
<training>
<course id="1">
<start>20170101</start>
<end>20170401</end>
</course>
</training>
I have done this:
for $x in doc("LMSGI06")//course
let $max := max($x/end - $x/start)
return
<duration>{$max}</duration>
And this is the result of my query:
<duration>300</duration>
<duration>400</duration>
<duration>400</duration>
<duration>400</duration>
<duration>10000</duration>
My query lists the duration of all the courses, but I need only the course that has the longest duration. Note that dates in the XML file have number format, not date format, because of that, I´m trying subtract dates like decimal numbers.

If you're using Saxon, then another option is the saxon:highest function:
saxon:highest(doc("LMSGI06")//course,
function($course) { $course/(end - start) }
)
However, your use of decimal subtraction rather than date/duration subtraction isn't sound. Consider the intervals 20161231-20170101 (one day: decimal difference = 8870) compared with 20170201-20170205 (four days: decimal difference = 4). The second duration is longer, but has a smaller decimal difference. So you should really convert the values to xs:date, and subtract them as xs:date values to get an xs:duration value.

You are currently evaluating max() inside of your for loop. The max() of a single number is that number.
You could get the max() of all the durations, then select the course with that duration:
let $doc := doc("LMSGI06")
let $maxDuration := max(
for $x in $doc//course
return $x/end - $x/start
)
return $doc//course[(end - start) eq $maxDuration]
Or you could order the courses by their duration, and then select the first one:
(
for $course in doc("LMSGI06")//course
order by $course/end - $course/start descending
return $course
)[1]

If you do not use Saxon but still want to process each course only once, you can reimplement the saxon:highest(...) function in pure XQuery 3.0:
declare function local:highest($seq, $key-func) {
head(
fold-left($seq, (),
function($max, $curr) {
let $key := $key-func($curr)
return if($max[2] >= $key) then $max else ($curr, $key)
}
)
)
};
Converting the date strings to the xs:date data type can be done as shown below:
declare function local:to-date($str) {
xs:date(concat(substring($str, 1, 4), '-',
substring($str, 5, 2), '-', substring($str, 7)))
};
With both of those functions, the solution to your exercise with correct date comparisons becomes very simple:
local:highest(
doc("LMSGI06")//course,
function($course) {
local:to-date($course/end) - local:to-date($course/start)
}
)

Related

Use median value of array as parameter in query

I want to use the resulting median as the parameter for calculating "larger". How can I do this?
{
"h": {
"data":
[1,3,5,2,6,8,4,23,7,3]
}
}
jsonata expression:
[{
"median" : $sort(h.data)[5],
"larger" : h.data.($ > 5)
}]
https://try.jsonata.org/1x8emp3oK
Solution - The key you're missing is the usage of variables. Also, the median of your set is 4.5, not 5 because the count of values is even, so the median becomes an average of two middle numbers.
In my solution, I calculate median, assign it to a variable using $variable_name := <calculation> syntax, and refer to that result further in the array filtering.
Keep in mind that if you're using variables, whole expression must be wrapped in parenthesis ( ... ).

How can I merge multiple overlapping date ranges and create new ones?

I have multiple date ranges each with a start and end date/time, containing a single value from which I want to create new ranges, where the overlapping range values are appended to a slice.
Date/time Ranges are the following:
[10:00, 10:15] = 7
[10:10, 10:20] = 9
[10:05, 10:25] = 2
[11:00, now] = 3
To illustrate it better please see the following image (I used only times here, to simplify it):
On the image a date range [10:00, 10:15] contains the value 7, [10:10, 10:20] = 9 and so on.
I would need to generate the following date ranges, where overlapping range values gets merged together:
[10:00, 10:05] = 7
[10:05, 10:10] = 7,2
[10:10, 10:15] = 7,2,9
[10:15, 10:20] = 2,9
[10:20, 10:25] = 2
[10:25, 11:00] = 2 <-- this was a gap, no overlap and not continuous.
[11:00, now] = 3
I used a struct to represent a range
type Range struct {
Start time.Time
End time.Time
Values []int
}
Is there an easy and efficient way of doing this?
Here's a sketch of an algorithm to do this:
The data struct would be:
type Boundary struct {
Time time.Time
AddRemove int
Value int
}
A Boundary would represent a Value added or removed from the list of values at a given time. For a range:
[from,to]=number
you create two Boundary objects:
b1:=Boundary{Time:from,AddRemove: 1, Value: number}
b2:=Boundary{Time:to,AddRemove:-1,Value:number}
You can then sort all boundary objects by time and AddRemove. If times are equal, you should process adds first, then removes. Once this is done, you can process the boundary objects, and create your ranges:
last:=time.Time{}
values:=map[int]struct{}{}
for _,b:=range boundaries {
if last.IsZero() {
last=b.Time
values[b.Value]=struct{}{}
} else {
// Create a new range here with [last,b.Time] with values given in `values`
if b.AddRemove==1 {
values[b.Value]=struct{}{}
} else {
delete(values,b.Value)
}
last=b.Time
}
}

How can I add minutes and seconds to a datetime in lua?

I have a lua function to attempt to convert the time duration of the currently playing song e.g. hh:mm:ss to seconds.
function toSeconds (inputstr)
local mytable = string.gmatch(inputstr, "([^"..":".."]+)");
local conversion = { 60, 60, 24}
local seconds = 0;
--iterate backwards
local count = 0;
for i=1, v in mytable do
count = i+1
end
for i=1, v in mytable do
mytable[count-i]
seconds = seconds + v*conversion[i]
end
return seconds
end
in order to add it to os.time to get the estimated end time of a song.
but the hours may be missing, or the minutes may be missing on a short track.
When running against https://www.lua.org/cgi-bin/demo All I get is input:10: 'do' expected near 'in'
for the test script
function toSeconds (inputstr)
local mytable = string.gmatch(inputstr, "([^"..":".."]+)");
local conversion = { 60, 60, 24}
local seconds = 0;
--iterate backwards
local count = 0;
for i=1, v in mytable do
count = i+1
end
for i=1, v in mytable do
mytable[count-i]
seconds = seconds + v*conversion[i]
end
return seconds
end
print(toSeconds("1:1:1")
You're mixing up the two possible ways of writing a for loop:
a)
for i=1,10 do
print(i, "This loop is for counting up (or down) a number")
end
b)
for key, value in ipairs({"hello", "world"}) do
print(key, value, "This loop is for using an iterator function")
end
The first one, as you can see, simply counts up a number, i in this case. The second one is very generic and can be used to iterate over almost anything (for example using io.lines), but is most often used with pairs and ipairs to iterate over tables.
You also don't write for ... in tab, where tab is a table; you have to use ipairs for that, which then returns an iterator for the table (which is a function)
You're also using string.gmatch incorrectly; it doesn't return a table, but an iterator function over the matches of the pattern in the string, so you can use it like this:
local matches = {}
for word in some_string:gmatch("[^ ]") do
table.insert(matches, word)
end
which gives you an actual table containing the matches, but if you're only going to iterate over that table, you might as well use the gmatch loop directly.
for i=1, v in mytable do
count = i+1
end
I think you're just trying to count the elements in the table here? You can easily get the length of a table with the # operator, so #mytable
If you have a string like hh:mm:ss, but the hours and the minutes can be missing, the easiest thing might be to just fill them with 0. A somewhat hacky but short way to achieve this is to just append "00:00:" to your string, and look for the last 3 numbers in it:
local hours, minutes, seconds = ("00:00:"..inputstr):match("(%d%d):(%d%d):(%d%d)$")
If nothing is missing, you'll end up with something like 00:00:hh:mm:ss, which you only take the last 3 values of to end up with the correct time.

Logic in Custom Rounding off - VB Script - QTP

I have a data file where decimal points aren't specified for a decimal number. The number is just described in the layout for the data file as first 2 digits as real and next 2 digits as decimal and it varies for different fields, the real and decimal part
So an actual number 12345.6789 is specified as 123456789. When I want this to be rounded off to 2 decimal points to match the value in application, I use the below logic
Public Function Rounding(NumberValue, DecimalPoints, RoundOff)
Rounder= Roundoff+1
Difference = DecimalPoints - Rounder
NumberValue = Mid(NumberValue, 1, Len(NumberValue)-Difference)
RealNumber=Mid(NumberValue,1,Len(NumberValue)-Rounder)
DecimalNumber=Right(NumberValue,Rounder)
NumberValue = RealNumber&"."&DecimalNumber
NumberValue = Cdbl(NumberValue)
NumberValue = Round(NumberValue, Roundoff)
Rounding = FormatNumber(NumberValue,Difference+1,,,0)
End Function
However the problem with this logic is that I am not able to round off decimals when the number has 0 as the decimal value
For an Example, lets take 12345.0000 which I want to round off to 2 decimal points
My function returns it as 12345 whereas I want this to be returned as 12345.00
Any ideas on how this logic could be tweaked to get the desired output or is that not possible at all?
To get the decimal places, use the Formatnumber function. See http://msdn.microsoft.com/en-us/library/ws343esk(v=vs.84).aspx - the default is normally 2 decimal places, but it is region settings specific when using the defaults.
Your script also has a small issue if the decimalpoints variable matches the roundoff variable - it will not populate Rounding with a result. I am also not sure why you are comparing DecimalPoints to Roundoff (-1) ?
I've revised the entire routine - it should do what you want (although I don't know what values you are feeding it) - So now it will work like this:
Doing 4 digits:
Rounding (123450001, 4, 2)
Result:
12345.00
Doing 2 digits:
Rounding (123450001, 2, 2)
Result:
1234500.01
Doing 4 digits (increments if > .5)
Rounding (876512345678, 8, 4)
Result:
8765.1235
Revised simplified function that should do everything you are asking:
Public Function Rounding(NumberValue, DecimalPoints, RoundOff )
RealNumber = Mid(NumberValue, 1, Len(NumberValue)-DecimalPoints)
DecimalNumber = Round("." & Right(NumberValue,DecimalPoints), RoundOff)
Rounding = FormatNumber(RealNumber + DecimalNumber,RoundOff,,,0)
End Function
Here's a working version of your Function:
Public Function Rounding(NumberValue, DecimalPoints, RoundOff)
RealNumber=left(NumberValue,Len(NumberValue)-DecimalPoints)
DecimalNumber="." & Right(NumberValue,DecimalPoints)
NumberValue = RealNumber + DecimalNumber
NumberValue = Round(NumberValue,RoundOff)
Rounding = FormatNumber(NumberValue, RoundOff,,,0)
End Function
I'm pretty sure you won't be able to use the Round() function for what you need. Take a look at the FormatNumber() or FormatCurrency() functions as they have the option to "IncludeLeadingZero".
Take a look at the answer from the following link for more information:
vbscript round to 2 decimal places using Ccur

xquery- how to obtain min/max value from a set of values that are obtained by subtracting consecutive members from a list

In an xquery expression, I have obtained a set of values within a for-expression, and one value is in a separate variable.
Now, I want to subtract the single value from first value of the list, and then subtract consecutive members of the list from each other-- and in the resulting set of difference values, I want to obtain the min/max values...
The query upto now looks like this--
let $value1:= 1998
let $rows_citations:=
$doc//div[#id="patent_citations"]
/div[#id="patent_citations_v"]
/table[#class="rel_patent"]
/tbody/tr[1]
/following-sibling::tr
for $pos in $rows_citations/position()
let $date2_c := customfn:dateconverter1($rows_citations[$pos]/td[3])
Now the subtraction I want is between first value of date2_c and value 1, and after that between consecutive members of date2_c... And from the resulting list I want the min/max values... How do I go about doing this?
I am esp. confused about creating a new list variable that stores all the differences, esp. when we are already inside a for loop, and are iterating over each value of a list (via variable date2_c)
I. This XQuery 3.0 query (which is also a pure XPath 3.0 expression):
let $pVal := 1,
$vList := (2,4,7,11,16),
$vList2 := ($pVal, subsequence($vList, 1, count($vList)-1)),
$vSubtactedList :=
map-pairs(function($m as xs:integer, $n as xs:integer) as xs:integer
{
$m - $n
},
$vList,
$vList2
)
return
(min($vSubtactedList), max($vSubtactedList))
produces the wanted result the minimum and maximum values from the list of subtractions:
1 5
II. XQuery 1.0 solution:
let $pVal := 1,
$vList := (2,4,7,11,16),
$vList2 := ($pVal, subsequence($vList, 1, count($vList)-1)),
$vSubtactedList :=
for $i in 1 to count($vList)
return
$vList[$i] - $vList2[$i]
return
(min($vSubtactedList), max($vSubtactedList))
This again produces the same correct result:
1 5

Resources