Why sum of products can be viewed as normal form in algebraic data types? - haskell

I am reading a haskell book (page 412). In this book, there is an explanation about normal form for sum of products:
All the existing algebraic rules for products and sums apply in type systems, and that includes the distributive property. Let’s take a look at how that works in arithmetic:
2 * (3 + 4)
2 * (7)
14
We can rewrite this with the multiplication distributed over the addition and obtain the same result:
2 * 3 + 2 * 4
(6) + (8)
14
This is known as a “sum of products.” In normal arithmetic, the expression is in normal form when it’s been reduced to a final result. However, if you think of the numerals in the above expressions as representations of set cardinality, then the sum of products expression is in normal form, as there is no computation to perform.
I've known that a normal form indiciates that an expression is fully reduced. In the above description, author of the book explains that sum of products can be seen as in normal form when we think of expression as representations of set cardinality. I don't understand that.
Cardinality of types means how many different values can be included in that type (like set). For example, Bool type in haskell has cardinality of 2, which is addition of 1 for False and 1 for True each.
Is sum of products (2 * 3 + 2 * 4) a normal form? This expression can be reduced furthre more because fully reduced expression would be 14. I don't understand why sum of products and cardinality of it are related to be normal form.

Let's declare ourselves some types to represent the numbers:
data Two = OneOfTwo | TwoOfTwo
data Three = OneOfThree | TwoOfThree | ThreeOfThree
data Four = ... (similar)
Now we can see that the number of possible values of type Two is, in fact, 2. Same for Three, Four, and Seven.
Now if we construct a sum type:
data A = A Two
This type just straight up wraps a value of Two, so the number of possible values of A is also 2. So far so good?
Now let's construct a more complex one:
data B = B1 Three | B2 Four
Now, this type wraps either a value of type Three or a value of type Four (but not both at the same time!) This means that the number of possible values would be 3 + 4. Following so far?
Now, going further:
data C = C Two B
This type wraps two values at the same time - one value of type Two and one value of type B. This means that the number of possible values of C is the number of possible combinations of Two and B, which, as we know from middle-school mathematics, would be their product, or 2 * (3 + 4) = 2 * (7) = 14.
But here's the trick: we can write down an equivalent type in a different way:
data CNew = C1 Two Three | C2 Two Four
See what I did there? For CNew, the set of all possible combinations between values of Two, Three, and Four is the same as for C. Look: in both cases it's either a value of Two combined with a value of Three, or it's a value of Two combined with a value of Four. Except in CNew they're combined directly, but in C they're combined via B.
But the formula for CNew would be different: 2 * 3 + 2 * 4 = (6) + (8) = 14. This is what the book means.
Now to answer this bit more directly:
Is sum of products (2 * 3 + 2 * 4) a normal form? This expression can be reduced further more because fully reduced expression would be 14
This would be true if we were dealing with integer numbers, but we're not. We can rewrite C in the form of CNew, because that gives us all the same possible combinations of values. But we cannot rewrite them as a type that has straight up 14 possible values without combining 2, 3, and 4. That would be a completely new, unrelated type, as opposed to a combination of Two, Three, and Four.
And a possible terminology misunderstanding:
Is sum of products (2 * 3 + 2 * 4) a normal form?
The term "normal form" doesn't mean "the shortest". This term is usually used to denote a form that is very regular, and therefore easier to work with, and, crucially, that can represent all possible cases in the domain. In this case, normal form is defined as a "sum of products".
Could it be a "product of sums" instead? No, it couldn't, because, while a product of sums can always be converted to a sum of products, the reverse is not always possible, and this means that not every possible type would be representable in the normal form defined as "product of sums".
Could it be "just the number of possible values", like 14? Again no, because converting to such form loses some information (see above).

Related

Creating a dynamic array with given probabilities in Excel

I want to create a dynamic array that returns me X values based on given probabilities. For instance:
Imagine this is a gift box and you can open the box N times. What I want is to have N random results. For example, I want to get randomly 5 of these two rarities but based on their chances.
I have this following formula for now:
=index(A2:A3,randarray(5,1,1,rows(A2:A3),1). And this is the output I get:
The problem here is that I have a dynamic array with the 5 results BUT NOT BASED ON THE PROBABILITIES.
How can I add probabilities to the array?
Here is how you could generate a random outcome with defined probabilities for the entries (Google Sheets solution, not sure about Excel):
=ARRAYFORMULA(
VLOOKUP(
RANDARRAY(H1, 1),
{
{0; OFFSET(C2:C,,, COUNTA(C2:C) - 1)},
OFFSET(A2:A,,, COUNTA(C2:C))
},
2
)
)
This whole subject of random selection was treated very thoroughly in Donald Knuth's series of books, The Art of Computer Programming, vol 2, "Semi-Numerical Algorithms". In that book he presents an algorithm for selecting exactly X out of N items in a list using pseudo-random numbers. What you may not have considered is that after you have chosen your first item the probability array has changed to (X-1)/(N-1) if your first outcome was "Normal" or X/(N-1) if your first outcome was "Rare". This means you'll want to keep track of some running totals based on your prior outcomes to ensure your probabilities are dynamically updated with each pick. You can do this with formulas, but I'm not certain how the back-reference will perform inside an array formula. Microsoft's dynamic array documentation indicates that such internal array references are considered "circular" and are prohibited.
In any case, trying to extend this to 3+ outcomes is very problematic. In order to implement that algorithm with 3 choices (X + Y + Z = N picks) you would need to break this up into one random number for an X or not X choice and then a second random number for a Y or not Y choice. This becomes a recursive algorithm, beyond Excel's ability to cope in formulas.

Use dynamic programming to find a subset of numbers whose sum is closest to given number M

Given a set A of n positive integers a1, a2,... a3 and another positive integer M, I'm going to find a subset of numbers of A whose sum is closest to M. In other words, I'm trying to find a subset A′ of A such that the absolute value |M - 􀀀 Σ a∈A′| is minimized, where [ Σ a∈A′ a ] is the total sum of the numbers of A′. I only need to return the sum of the elements of the solution subset A′ without reporting the actual subset A′.
For example, if we have A as {1, 4, 7, 12} and M = 15. Then, the solution subset is A′ = {4, 12}, and thus the algorithm only needs to return 4 + 12 = 16 as the answer.
The dynamic programming algorithm for the problem should run in
O(nK) time in the worst case, where K is the sum of all numbers of A.
You construct a Dynamic Programming table of size n*K where
D[i][j] = Can you get sum j using the first i elements ?
The recursive relation you can use is: D[i][j] = D[i-1][j-a[i]] OR D[i-1][j] This relation can be derived if you consider that ith element can be added or left.
Time complexity : O(nK) where K=sum of all elements
Lastly you iterate over entire possible sum you can get, i.e. D[n][j] for j=1..K. Which ever is closest to M will be your answer.
For dynamic algorithm, we
Define the value we would work on
The set of values here is actually a table.
For this problem, we define value DP[i , j] as an indicator for whether we can obtain sum j using first i elements. (1 means yes, 0 means no)
Here 0<=i<=n, 0<=j<=K, where K is the sum of all elements in A
Define the recursive relation
DP[i+1 , j] = 1 , if ( DP[i,j] == 1 || DP[i,j-A[i+1]] ==1)
Else, DP[i+1, j] = 0.
Don't forget to initialize the table to 0 at first place. This solves boundary and trivial case.
Calculate the value you want
Through bottom-up implementation, you can finally fill the whole table.
Now, things become easy. You just need to find out the closest value to M in the table whose value is one.
Here, just work on DP[n][j], since n covers the whole set. Find the closest j to M whose value is 1.
Time complexity is O(kn), since you iterate k*n times in total.

Binary search - worst/avg case

I'm finding it difficult to understand why/how the worst and average case for searching for a key in an array/list using binary search is O(log(n)).
log(1,000,000) is only 6. log(1,000,000,000) is only 9 - I get that, but I don't understand the explanation. If one did not test it, how do we know that the avg/worst case is actually log(n)?
I hope you guys understand what I'm trying to say. If not, please let me know and I'll try to explain it differently.
Worst case
Every time the binary search code makes a decision, it eliminates half of the remaining elements from consideration. So you're dividing the number of elements by 2 with each decision.
How many times can you divide by 2 before you are down to only a single element? If n is the starting number of elements and x is the number of times you divide by 2, we can write this as:
n / (2 * 2 * 2 * ... * 2) = 1 [the '2' is repeated x times]
or, equivalently,
n / 2^x = 1
or, equivalently,
n = 2^x
So log base 2 of n gives you x, which is the number of decisions being made.
Finally, you might ask, if I used log base 2, why is it also OK to write it as log base 10, as you have done? The base does not matter because the difference is only a constant factor which is "ignored" by Big O notation.
Average case
I see that you also asked about the average case. Consider:
There is only one element in the array that can be found on the first try.
There are only two elements that can be found on the second try. (Because after the first try, we chose either the right half or the left half.)
There are only four elements that can be found on the third try.
You can see the pattern: 1, 2, 4, 8, ... , n/2. To express the same pattern going in the other direction:
Half the elements take the maximum number of decisions to find.
A quarter of the elements take one fewer decision to find.
etc.
Since half of the elements take the maximum amount of time, it doesn't matter how much less time the other elements take. We could assume that all elements take the maximum amount of time, and even if half of them actually take 0 time, our assumption would not be more than double whatever the true average is. We can ignore "double" since it is a constant factor. So the average case is the same as the worst case, as far as Big O notation is concerned.
For binary search, the array should be arranged in ascending or descending order.
In each step, the algorithm compares the search key value with the key value of the middle element of the array.
If the keys match, then a matching element has been found and its index, or position, is returned.
Otherwise, if the search key is less than the middle element's key, then the algorithm repeats its action on the sub-array to the left of the middle element.
Or, if the search key is greater,then the algorithm repeats its action on the sub-array to the right.
If the remaining array to be searched is empty, then the key cannot be found in the array and a special "not found" indication is returned.
So, a binary search is a dichotomic divide and conquer search algorithm. Thereby it takes logarithmic time for performing the search operation as the elements are reduced by half in each of the iteration.
For sorted lists which we can do a binary search, each "decision" made by the binary search compares your key to the middle element, if greater it takes the right half of the list, if less it will take the left half of the list (if it's a match it will return the element at that position) you effectively reduce your list by half for every decision yielding O(logn).
Binary search however, only works for sorted lists. For un-sorted lists you can do a straight search starting with the first element yielding a complexity of O(n).
O(logn) < O(n)
Although it entirely depends on how many searches you'll be doing, your inputs, etc what your best approach would be.
For Binary search the prerequisite is a sorted array as input.
• As the list is sorted:
• Certainly we don't have to check every word in the dictionary to look up a word.
• A basic strategy is to repeatedly halve our search range until we find the value.
• For example, look for 5 in the list of 9 #s below.v = 1 1 3 5 8 10 18 33 42
• We would first start in the middle: 8
• Since 5<8, we know we can look at just the first half: 1 1 3 5
• Looking at the middle # again, narrow down to 3 5
• Then we stop when we're down to one #: 5
How many comparison is needed: 4 =log(base 2)(9-1)=O(log(base2)n)
int binary_search (vector<int> v, int val) {
int from = 0;
int to = v.size()-1;
int mid;
while (from <= to) {
mid = (from+to)/2;
if (val == v[mid])
return mid;
else if (val > v[mid])
from = mid+1;
else
to = mid-1;
}
return -1;
}

express equivalence between several instances

Suppose I have a sig A in my module that is related to a sig B.
Imagine now that we have the several instances :
A$1 -> B$1 , A$2 -> B$2
and
A$1 -> B$2 , A$2 -> B$1
I would like to express that B$1 and B$2 are equivalent (under certain conditions) such that only this instance would be generated A$1 -> B , A$2 -> B.
One of the solution might be to use the keyword "one" while declaring the sig B, but it won't work in my case, because B have several fields, making B atoms not necessarily equivalent . In short 2 atoms are equivalent only if they have their fields of same values.
Ideally I would like to remove the numbering for B. but still be able to have several atoms B .
The Alloy Analyzer doesn't give you much control over how the subsequent instances are generated (or how to break symmetries), so you typically have to work around those issues at the model level.
For your example, maybe this is the right model
sig B{}
one sig BA extends B {}
sig A {
b: one BA
}
run { #A=2 }
First you say that the two instances you describe are equivalent, although at a superficial level they appear to have distinct values for A$1 and A$2. Then you say "2 atoms are equivalent only if ... their fields [have the] same values". If that's the definition of equivalence, then your two instances are not equivalent; if your two instances are equivalent, then your definition is not capturing what you want.
It sounds as if you mean (1) that B atoms are either (1a) always equivalent, or (1b) equivalent under certain circumstances, and (2) that A atoms are equivalent if their fields all have values which are either identical or equivalent. And as if you want to prohibit equivalent A atoms. If that's so, then your jobs are:
Define a predicate that is true for two B elements if and only if they are equivalent.
Define a predicate for two A elements that is true if and only if they are equivalent.
Define a predicate (or a fact) that specifies that no two A atoms in the instance are equivalent.
If your problem is just that the Analyzer is showing you instances of the model which are not interestingly distinct from each other, then see Aleksandar Milicevic's response.

Counting quantifiers - how

Let's say, I have to model a checkerboard and I want to say that at least 5 squares on the "A" vertical are empty. How do I do that in Alloy? Any other example with numbers different from 0 or 1 would be good. In other words, what do I do when "some" is not precise enough?
Thanks!
You can use the cardinality operator (#) to make assertions about the number of tuples in a relation, e.g.,
#r >= 5
says that the relation r must have at least 5 tuples.
You can also use the cardinality operator with an arbitrary expression, e.g.,
#board.cells >= 5
or
#{c: Cell | c in board.cells and ...} >= 5

Resources