How to make lemmas about changes made to heap objects? - heap

I'm trying to implement a MaxHeap using Dafny based on the code from Intro. to Algorithms, CLRS 3rd edition, section 6.1, page 153 or the Max-Heapify function here. I switched from using recursion to a while loop because that seemed a bit easier to handle in Dafny.
Trying to prove the heap property on an array after calling the heapify function. In particular I was hoping to be able to use the require statement on heapify to assert that triples which didn't change in the heap which were satisfying the heap property before an update, are satisfying the heap property after the update.
However, after making any changes to the array it seems like it forgets all about the require/invariant statement. Even if I show that the value are the same before and after the update it still no longer passes the assertion. I pulled out the update into the swap method.
I was hoping I could write a lemma asserting this fact but it seems like lemmas don't allow modifying the heap or using old() or calling a method. Is there a way to write a lemma for this?
function method parent(i: int): int
{
i/2
}
function method left(i: int): int
{
2*i
}
function method right(i: int): int
{
2*i+1
}
class MaxHeap {
var data: array<int>
ghost var repr: set<object>
constructor(data: array<int>)
ensures this.data == data
ensures this in repr
{
this.data := data;
this.repr := {this};
}
predicate method MaxHeapChildren(i: int)
reads this, this.data
requires 1 <= i
{
(left(i) > this.data.Length || this.data[i-1] >= this.data[left(i)-1]) && (right(i) > this.data.Length || this.data[i-1] >= this.data[right(i)-1])
}
method heapify(i: int) returns (largest: int)
modifies this.data
requires 1 <= i <= this.data.Length
requires forall x :: i < x <= this.data.Length ==> MaxHeapChildren(x)
// ensures multiset(this.data[..]) == multiset(old(this.data[..]))
ensures forall x :: i <= x <= this.data.Length ==> MaxHeapChildren(x)
decreases this.data.Length - i
{
var i' := i;
ghost var oldi := i;
largest := i;
var l := left(i);
var r := right(i);
ghost var count := 0;
ghost var count' := 1;
while !MaxHeapChildren(i')
invariant count' == count + 1;
invariant 1 <= largest <= this.data.Length
invariant l == left(i')
invariant r == right(i')
invariant 1 <= i' <= this.data.Length
invariant i' == i || i' == left(oldi) || i' == right(oldi)
invariant largest == i'
invariant count == 0 ==> oldi == i
invariant oldi > 0
invariant count > 0 ==> oldi == parent(i')
invariant count > 0 ==> MaxHeapChildren(oldi)
invariant count > 0 ==> forall x :: i <= x < i' ==> old(this.data)[x] == this.data[x]
invariant count > 0 ==> forall x :: i <= x < i' && left(x+1) < this.data.Length ==> old(this.data)[left(x+1)] == this.data[left(x+1)]
invariant count > 0 ==> forall x :: i <= x < i' && right(x+1) < this.data.Length ==> old(this.data)[right(x+1)] == this.data[right(x+1)]
// invariant count > 0 ==> forall x :: i <= x <= i' && left(x+1) ==> MaxHeapChildren(left(x+1))
invariant forall x :: i <= x <= this.data.Length && x != i' ==> MaxHeapChildren(x)
decreases this.data.Length-i';
{
if l <= this.data.Length && this.data[l-1] > this.data[i'-1] {
largest := l;
}
if r <= this.data.Length && this.data[r-1] > this.data[largest-1] {
largest := r;
}
if largest != i' {
assert forall x :: i < x <= this.data.Length && x != i' ==> MaxHeapChildren(x);
swap(this, i', largest);
label AfterChange:
oldi := i';
assert MaxHeapChildren(oldi);
i' := largest;
assert forall x :: largest < x <= this.data.Length && x != i' ==> MaxHeapChildren(x);
l := left(i');
r := right(i');
assert forall x :: i <= x < i' ==> old#AfterChange(this.data[x]) == this.data[x] && left(x+1) < this.data.Length ==> old(this.data)[left(x+1)] == this.data[left(x+1)] && right(x+1) < this.data.Length ==> old(this.data)[right(x+1)] == this.data[right(x+1)];
}else{
assert MaxHeapChildren(i');
assert MaxHeapChildren(oldi);
}
count := count + 1;
count' := count' + 1;
}
}
}
method swap(heap: MaxHeap, i: int, largest: int)
modifies heap.data
requires 1 <= i < largest <= heap.data.Length
requires heap.data[largest-1] > heap.data[i-1]
requires left(i) <= heap.data.Length ==> heap.data[largest-1] >= heap.data[left(i)-1]
requires right(i) <= heap.data.Length ==> heap.data[largest-1] >= heap.data[right(i)-1]
requires forall x :: i <= x <= heap.data.Length && x != i ==> heap.MaxHeapChildren(x)
ensures heap.data[i-1] == old(heap.data[largest-1])
ensures heap.data[largest-1] == old(heap.data[i-1])
ensures heap.MaxHeapChildren(i)
ensures forall x :: 1 <= x <= heap.data.Length && x != i && x != largest ==> heap.data[x-1] == old(heap.data[x-1])
ensures forall x :: i <= x <= heap.data.Length && x != largest ==> heap.MaxHeapChildren(x)
{
ghost var oldData := heap.data[..];
var temp := heap.data[i-1];
heap.data[i-1] := heap.data[largest-1];
heap.data[largest-1] := temp;
var z:int :| assume i < z <= heap.data.Length && z != largest;
var lz: int := left(z);
var rz: int := right(z);
assert heap.data[z-1] == old(heap.data[z-1]);
assert lz != i && lz != largest && lz <= heap.data.Length ==> heap.data[lz-1] == old(heap.data[lz-1]);
assert rz != i && rz != largest && rz <= heap.data.Length ==> heap.data[rz-1] == old(heap.data[rz-1]);
assert heap.MaxHeapChildren(z);
}
/**
heapify(4)
length = 17
i = 4
left = 8, 7 (0based)
right = 9, 8 (0based)
x in 5 .. 17 :: MaxHeapChildren(x) (i+1)..17
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
[20,18,16,3,14,12,10, 8, 6, 4, 2, 0, 1,-2, 4, 4,-5]
i = 8
left = 16
right = 17
x in i' .. i-1 :: MaxHeapChildren (4..15)
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
[20,18,16,8,14,12,10, 3, 6, 4, 2, 0, 1,-2, 4, 4,-5]
i = 16
left = 32
right = 33
x in i' .. i-1 :: MaxHeapChildren (4..16) + 17.. MaxHeapChildren
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
[20,18,16,8,14,12,10, 4, 6, 4, 2, 0, 1,-2, 4, 3,-5]
*/

Yes, I think what you are looking for is called a twostate lemma
Basically, you can use old() in their specification, and at the call site, you can specify which heap to consider for calls to old() by suffixing the lemma's name by #a, if label a: existed before that method's call.

Related

Given an integer N. What is the smallest integer greater than N that only has 0 or 1 as its digits?

I have an integer N. I have to find the smallest integer greater than N that doesn't contain any digit other than 0 or 1. For example: If N = 12 then the answer is 100.
I have coded a brute force approach in C++.
int main() {
long long n;
cin >> n;
for (long long i = n + 1; ; i++) {
long long temp = i;
bool ok = true;
while (temp != 0) {
if ( (temp % 10) != 0 && (temp % 10) != 1) {
ok = false;
break;
}
temp /= 10;
}
if (ok == true) {
cout << i << endl;
break;
}
}
}
The problem is, my approach is too slow. I believe there is a very efficient approach to solve this. How can I solve this problem efficiently?
Increment N,
Starting from the left, scan until you find a digit above 1. Increment the partial number before it and zero out the rest.
E.g.
12 -> 13 -> 1|3 -> 10|0
101 -> 102 -> 10|2 -> 11|0
109 -> 110 -> 111|
111 -> 112 -> 11|2 -> 100|0
198 -> 199 -> 1|99 -> 10|00
1098 -> 1099 -> 10|99 -> 11|00
10203 -> 10204 -> 10|204 -> 11|000
111234 -> 111235 -> 111|235 -> 1000|000
...
Proof:
The requested number must be at least N+1, this is why we increment. We are now looking for a number greater or equal.
Let us call the prefix the initial 0/1 digits and suffix what comes after. We must replace the first digit of the suffix by a zero and set a larger prefix. The smallest prefix that fits is the current prefix plus one. And the smallest suffix that fits is all zeroes.
Update:
I forgot to specify that the prefix must be incremented as a binary number, otherwise forbidden digits could appear.
Another possibility would be the following one:
You start with the largest decimal number of the type "1111111...1111" supported by the data type used
The algorithm assumes that the input is smaller than this number; otherwise you'll have to use another data type.
Example: When using long long, you start with the number 1111111111111111111.
Then process each decimal digit from the left to the right:
Try to change the digit from 1 to 0.
If the result is still larger than your input, do the change (change the digit to 0).
Otherwise the digit remains 1.
Example
Input = 10103
Start: 111111
Step 1: [1]11111, try [0]11111; 011111 > 10103 => 011111
Step 2: 0[1]1111, try 0[0]1111; 001111 < 10103 => 011111
Step 3: 01[1]111, try 01[0]111; 010111 > 10103 => 010111
Step 4: 010[1]11, try 010[0]11; 010011 < 10103 => 010111
Step 5: 0101[1]1, try 0101[0]1; 010101 < 10103 => 010111
Step 6: 01011[1], try 01011[0]; 010110 > 10103 => 010110
Result: 010110
Proof of correctness:
We process digit by digit in this algorithm. In each step, there are digits whose value is already known and digits whose values are not known, yet.
In each step, we probe the leftmost unknown digit.
We set that digit to "0" and all other unknown digits to "1". Because the digit to be probed is the most significant of the unknown digits, the resulting number is the largest possible number with that digit being a "0". If this number is less or equal the input, the digit being probed must be a "1".
On the other hand, the resulting number is smaller than all possible numbers where the digit being probed is a "1". If the resulting number is larger than the input, the digit must be "0".
This means that we can calculate one digit in each step.
C code
(The C code should work under C++, too):
long long input;
long long result;
long long digit;
... read in input ...
result = 1111111111111111111ll;
digit = 1000000000000000000ll;
while( digit > 0 )
{
if(result - digit > input)
{
result -= digit;
}
digit /= 10;
}
... print out output ...
Let me suggest a couple of alternatives.
I. Incrementing. Consider it a modification of #YvesDaoust method.
Increase N by 1
Expand result with leading zero
Go from the last to the second digit
(a) if it is less than 2 then leave everything as is
(b) otherwise set it to 0 and increase preceding
Repeat steps 3a,b
Examples:
1. N = 0 -> 1 -> (0)|(1) -> 1
2. N = 1 -> 2 -> (0)|(2) -> (1)|(0) -> 10
3. N = 101 -> 102 -> (0)|(1)(0)(2) -> (0)|(1)(1)(0) -> (0)|(1)(1)(0) -> (0)|(1)(1)(0) -> 110
4. N = 298 -> 299 -> (0)|(2)(9)(9) -> (0)|(2)(10)(0) -> (0)|(3)(0)(0) -> (1)|(0)(0)(0) -> 1000
You get result in decimal format.
II. Dividing.
Increase N by 1
Set sum to 0
Divide result by 10 to get div (D) and mod (M) parts
Check M
(a) if M exceeds 1 then increase D
(b) otherwise increase sum by M*10k, where k is the current iteration number (starting with 0)
Repeat steps 3,4 until D is 0
Example 1:
1. N = 0 -> N = 1
2. sum = 0
3. 1/10 -> D == 0, M == 1 -> sum = sum + 1*10^0 == 1
4. D == 0 -> sum == 1
Example 2:
1. N = 1 -> N = 2
2. sum = 0
3. 2/10 -> D == 0, M == 2 -> D = D + 1 == 1
4. 1/10 -> D == 0, M == 1 -> sum = sum + 1*10^1 == 10
5. D == 0, sum == 10
Example 3:
1. N = 101 -> N = 102
2. sum = 0
3. 102/10 -> D == 10, M == 2 -> D = D + 1 == 11
4. 11/10 -> D == 1, M == 1 -> sum = sum + 1*10^1 = 10
5. 1/10 -> D == 0, M == 1 -> sum = sum + 1*10^2 == 10 + 100 == 110
6. D == 0, sum == 110
Example 4:
1. N = 298 -> N = 299
2. sum = 0
3. 299/10 -> D == 29, M == 9 -> D = D + 1 == 30
4. 30/10 -> D == 3, M == 0 -> sum = sum + 0*10^1 == 0
5. 3/10 -> D == 0, M == 3 -> D = D + 1
6. 1/10 -> D == 0, M == 1 -> sum = sum + 1*10^3 == 1000
7. D == 0, sum == 1000

How can I represent a set in ampl into a vector in c++?

I have two sets in ampl to be implemented in c++, set NS and set S.
But I'm not understanding the set S very well.
set N ordered := {1..n};
set NS ordered := 1..(2**n-1);
set S {s in NS} := {i in N: (s div 2**(ord(i)-1)) mod 2 = 1};
If n = 4, I will have:
set N := 1 2 3 4
set NS := 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15;
set S[1] := 1;
set S[2] := 2;
set S[3] := 1 2;
set S[4] := 3;
set S[5] := 1 3;
set S[6] := 2 3;
set S[7] := 1 2 3;
set S[8] := 4;
set S[9] := 1 4;
set S[10] := 2 4;
set S[11] := 1 2 4;
set S[12] := 3 4;
set S[13] := 1 3 4;
set S[14] := 2 3 4;
set S[15] := 1 2 3 4;
The first set can be easily created.
vector <int> NS;
int auxMax = pow(2,n)-1;
for (int i = 0; i < auxMax; i++) {
NS.push_back(i);
}
Although I know how the operators div (returns the truncated quotient when its left operand is divided by its right operand), mod (computes the remainder) and ord(returns the numerical position of [i] within the set N) work, I'm not able to make a struct of "for" to fed set S.
Can anyone help me understanding the generation of set S and transforming it to a vector in c++?
Thank you!
Im not familiar with ampl, but I'll give it a go:
Assuming I understood that {} translates to some form of vector type.
Result is the following:
int main()
{
const int n = 4;
int NS = pow(2, n) - 1;
std::vector< std::vector<int> > S;
for (int s = 1; s <= NS; s++) // {s in NS}
{
std::vector<int> iN;
for (int ord_i = 1; ord_i <= n; ord_i++) // {i in N}
{
if ((s / (int)pow(2, ord_i - 1)) % 2 == 1)
iN.push_back(ord_i);
}
S.push_back(iN);
}
}

shell sort sequence implementation in C++

I am reading about shell sort in Algorithms in C++ by Robert Sedwick.
Here outer loop to change the increments leads to this compact shellsort implementation, which uses the increment sequence 1 4 13 40 121 364 1093 3280 9841 . . . .
template <class Item>
void shellsort(Item a[], int l, int r)
{
int h;
for (h = 1; h <= (r - l) / 9; h = 3 * h + 1);
for (; h > 0; h = h / 3)
{
for (int i = l + h; i <= r; i++)
{
int j = i; Item v = a[i];
while (j >= l + h && v < a[j - h])
{
a[j] = a[j - h]; j -= h;
}
a[j] = v;
}
}
}
My question under what basis author is checking for condition h <= (r-l)/9, and why author is dividing by 9.
The loop:
for (h = 1; h <= (r - l) / 9; h = 3 * h + 1);
calculates the initial value of h. This value must be smaller than the range it will be used in:
h <= (r - l)
Everytime this condition passes, h gets updated to 3 * h + 1, which means that even though h is smaller than (r-l), the updated value might be larger. To prevent this, we could check if the next value of h would surpass the largest index:
(h * 3) + 1 <= (r - l)
This will make sure h is smaller than range of the array.
For example: say we have an array of size 42, which means indices go from 0 to 41. Using the condition as described above:
h = 1, is (3 * 1 + 1) <= (41 - 0) ? yes! -> update h to 4
h = 4, is (3 * 4 + 1) <= (41 - 0) ? yes! -> update h to 13
h = 13, is (3 * 13 + 1) <= (41 - 0) ? yes! -> update h to 40
h = 40, is (3 * 40 + 1) <= (41 - 0) ? no! => h will begin at 40
This means our initial h is 40, because h is only marginally smaller than the range of the array, very little work will be done, the algorithm will only check the following:
Does array[40] needs to be swapped with array[0] ?
Does array[41] needs to be swapped with array[1] ?
This is a bit useless, the first iteration only performs two checks. A smaller initial value of h means more work will get done in the first iteration.
Using:
h <= (r - l) / 9
ensures the initial value of h to be sufficiently small to allow the first iteration to do useful work. As an extra advantage, it also looks cleaner than the previous condition.
You could replace 9 by any value greater than 3. Why greater than 3? To ensure (h * 3) + 1 <= (r - l) is still true!
But do remember to not make the initial h too small: Shell Sort is based on Insertion Sort, which only performs well on small or nearly sorted arrays. Personally, I would not exceed h <= (r - l) / 15.

Decide(in Haskell) if a number is or not a palindrome without using lists

I need to check in Haskell if a four digit number is a palindrome, the problem is that I can't use lists, and in spite of having a fixed digit number, I should use recursion. I have been think on the problem and I couldn't get a solution using recursion. The closest that I could get was this:
pldrm :: Integer -> Bool
pldrm x
|x > 9999 = False
|x > 999 = (div x 1000 == mod x 10) && (mod (div x 100) 10) == div (mod x 100) 10
|otherwise = False
Do you have any idea? thanks
How about just checking if a number is equal to its reversal?
palindrome :: Integer -> Bool
palindrome x = reversal x == x
reversal :: Integral a => a -> a
reversal = go 0
where go a 0 = a
go a b = let (q,r) = b `quotRem` 10 in go (a*10 + r) q
This lets negative numbers like -121 be palindromes, which is easy to check for if you don't want that to be true.
nonNegativePalindrome x = x >= 0 && palindrome x
reversal gives us the integer with digits in reverse order of our input (ignoring the infinite leading zeroes implicit in 12 == ...000012).
reversal works by peeling off the digits from the bottom (using quotRem, which is a lot like divMod) and putting them together in reverse order (via muliplication and adding).
reversal 12345
= go 0 12345
= go 5 1234
= go 54 123
= go 543 12
= go 5432 1
= go 54321 0
= 54321
It's worth noting that n == reversal $ reversal n only if n is zero or has a non-zero 1s digit. (reversal (reversal 1200) == 12), but that integers in the range of reversal are all invertible: reversal x == reversal (reversal (reversal x)) forall x.
More thorough explanation of how to reach this solution in this blog post.
Ok, this is indeed a bit tricky and more math than Haskell so let's look at a possible solution (assuming a decimal system).
The idea is to use div and mod to get at the highest and lowest digit of a number.
Remember that you can write
(q,r) = n `divMod` m
to get numbers q and r so that q * m + r = n with 0 <= r < q. For m = 10 this
will conveniently get (for positive n):
in q all but the last digits
in r the last digit
remark: I had this wrong for some time - I hope it's correct now - the edge cases are really tricky.
palindrome :: Integer -> Bool
palindrome n = palin n (digits n)
where
palin x dgts
| x < 0 = False
| x == 0 = True
| x < 10 = dgts == 1
| otherwise = q == x `mod` 10 && palin inner (dgts-2)
where
inner = r `div` 10
(q,r) = x `divMod` size
size = 10^(dgts-1)
digits :: Integer -> Integer
digits x
| x < 10 = 1
| otherwise = 1 + digits (x `div` 10)
Obvious I did not know the size of your problem so digits will look for the number of digits:
digits 5445 = 4
digits 123 = 3
...
The edge cases are these:
| x < 0 = False
| x == 0 = True
| x < 10 = digits == 1
Obvious negative numbers should not be palindromes
if all digits are 0 then it's an palindrome
one-digit numbers are palindromes if indeed we are looking only at length 1 (this had me bad, as the inner of stuff like 1011 is a one digit nubmer 1)
The rest is based on this observations:
x div 10^(digits-1) = the highest digit (5445 div 1000 = 5)
x mod 10^(digits-1) = all but the highest digit (5445 mod 1000 = 445)
x mod 10 = the lowest digit (5445 mod 10 = 5)
number div 10 = remove the lowest digit (5445 div 10 = 544)
just to be safe let's test it using Quickcheck:
Let's use Quickcheck to test it (should be a nice example :D )
module Palindrome where
import Test.QuickCheck
main :: IO ()
main = do
checkIt palindrome
palindrome :: Integer -> Bool
palindrome n = palin n (digits n)
where
palin x dgts
| x < 0 = False
| x == 0 = True
| x < 10 = dgts == 1
| otherwise = q == x `mod` 10 && palin inner (dgts-2)
where
inner = r `div` 10
(q,r) = x `divMod` size
size = 10^(dgts-1)
digits :: Integer -> Integer
digits x
| x < 10 = 1
| otherwise = 1 + digits (x `div` 10)
checkIt :: (Integer -> Bool) -> IO ()
checkIt p =
quickCheckWith more (\n -> n < 0 || p n == (reverse (show n) == show n))
where more = stdArgs { maxSuccess = 10000, maxSize = 999999 }
seems ok:
runghc Palindrom.hs
+++ OK, passed 10000 tests.
If only four digit numbers considered, you can recursively subtract 1001 to check if first and last digits are equal and then subtract 0110 to check if middle digits are equal.
pldrm :: Int -> Bool
pldrm x
| x > 1000 = pldrm (x - 1001)
| x > 100 = pldrm (x - 110)
| otherwise = x == 0
Please note that this function will give incorrect results for numbers outside of [1000,9999] range.
It is a pity that you cannot use lists. Here is cumbersome solution based on arithmetic operations (works only for four-digit numbers):
pldrm :: Int -> Bool -- no need for Integer if you work only with four
-- digit numbers
pldrm x = (div x 1000 == mod x 10) && (div y 10 == mod y 10)
where y = rem x 1000 `quot` 10 -- extracts two inner digits
> pldrm 3113
True
> pldrm 3111
False
isPolindrom :: Integer -> Bool
isPolindrom n = if let i = read (reverse (show n)) :: Integer in i==n then True else False

What is wrong with my function

i have the following function that is supposed to return true if the passed argument is a reasonable date and false otherwise. the problem is that it is returning false even for obviously reasonable dates and i can't figure out what is wrong with it. anyone with sharper eyes please help. here it is:
fun reasonable_date(x: int*int*int) =
if #1 x > 0 andalso #2 x > 0 andalso #2 x <= 12 andalso #3 x > 0 andalso #3 x <= 31
then
if #2 x = 1 mod 2 andalso #2 x < 8 andalso #3 x <= 31 then true
else if #2 x = 0 mod 2 andalso #2 x >= 8 andalso #3 x <= 31
then true
else if #2 x = 0 mod 2 andalso #2 x < 8
then
if #2 x = 2 andalso (#3 x = 28 orelse #3 x = 29) then true
else if #2 x = 0 mod 2 andalso #3 x <= 30 then true
else false
else if #2 x = 1 mod 2 andalso #2 x > 8 andalso #3 x <=30 then true
else false
else false
Your current solution is impossible to maintain, and its logic looks like something that has been to hell and back :)
I would recommend that you break it up into smaller logical parts that ensure simple properties. Thus instead of first testing whether the year, month and day is greater or equal to one, you could group all the logic regarding years, months and days for itself
fun daysInMonth n =
List.nth([31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], n-1)
fun reasonable_date (y, m, d) =
(* Check year >= 1 *)
y >= 1 andalso
(* Check 1 <= month <= 12 *)
(m >= 1 andalso m <= 12) andalso
(* Check 1 <= day <= n, for n being the number of days in specified month *)
(d >= 1 andalso d <= daysInMonth m)
Obviously this doesn't handle leap years, however that is also quite simple to implement using a helper function, if the month is February. It could be done like this
fun reasonable_date (y, m, d) =
(* Check year >= 1 *)
y >= 1 andalso
(* Check 1 <= month <= 12 *)
(m >= 1 andalso m <= 12) andalso
(* Check 1 <= day <= n, for n being the number of days in specified month *)
(d >= 1 andalso
(* If February, and leap year *)
((m = 2 andalso isLeapYear y andalso d <= 29)
(* Any other month or non leap year *)
orelse d <= daysInMonth m))
You repeatedly use conditions like if #2 x = 1 mod 2. This is almost certainly does not work as you think it does. Here, mod is an arithmetic operator, meaning the remainder obtained when dividing 1 by 2, and not the mathematical expression saying that #2 x equals 1 modulo 2. Thus, instead of testing whether #2 x is odd, you're testing whether it equals 1. Following through your conditions, you really only allow true when #2 x is 1, so your reasonable dates must all be in January (and there may not even be any, I haven't worked through all conditions).
i prefer this solution which seems more readable
fun reasonable_date (y, m, d) =
let val daysInMonth =
List.nth([31, if isLeapYear y then 29 else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], m-1)
in
(* Check year >= 1 *)
y >= 1 andalso
(* Check 1 <= month <= 12 *)
(m >= 1 andalso m <= 12) andalso
(* Check 1 <= day <= n, for n being the number of days in specified month *)
(d >= 1 andalso d <= daysInMonth)
end
but may be i missed some tricks (i assume you already wrote an helper function isLeapYear)