/*
 * natnumset - functions for sets of natural numbers not exceeding a fixed bound
 *
 * Copyright (C) 1999,2021  Ernest Bowen
 *
 * Calc is open software; you can redistribute it and/or modify it under
 * the terms of the version 2.1 of the GNU Lesser General Public License
 * as published by the Free Software Foundation.
 *
 * Calc is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU Lesser General
 * Public License for more details.
 *
 * A copy of version 2.1 of the GNU Lesser General Public License is
 * distributed with calc under the filename COPYING-LGPL.  You should have
 * received a copy with calc; if not, write to Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Under source code control:	1997/09/07 23:53:51
 * File existed as early as:	1997
 *
 * Share and enjoy!  :-)	http://www.isthe.com/chongo/tech/comp/calc/
 */

/*
 * Functions for sets of natural numbers not exceeding a fixed bound B.
 *
 * The default value for B is 100; B may be assigned another
 * value n by setbound(n); with no argument, setbound() returns the current
 * upper bound.
 *
 * A set S is stored as an object with one element with one component S.s;
 * This component is a string of just sufficient size to include m bits,
 * where m is the maximum integer in S.
 *
 * With zero or more integer arguments, set(a, b, ...) returns the set
 * whose elements are those of a, b, ... in [0, B].  Note that arguments
 * < 0 or > B are ignored.
 *
 * In an assignment of a set-valued lvalue to an lvalue, as in
 *
 *		A = set(1,2,3);
 *		B = A;
 *
 * the sets share the same data string, so a change to either has the effect
 * of changing both.  A set equal to A but with a different string can be
 * created by
 *
 *		B = A | set()
 *
 * The functions empty() and full() return the empty set and the set of all
 * integers in [0,B] respectively.
 *
 * isset(A) returns 1 or 0 according as A is or is not a set
 *
 * test(A) returns 0 or 1 according as A is or is not the empty set
 *
 * isin(A, n) for set A and integer n returns 1 if n is in A, 0 if
 *	0 <= n <= B and n is not in A, the null value if n < 0 or n > B.
 *
 * addmember(A, n) adds n as a member of A, provided n is in [0, B];
 *	 this is also achieved by A |= n.
 *
 * rmmember(A, n) removes n from A if it is a member; this is also achieved
 *	by A \= n.
 *
 * The following unary and binary operations are defined for sets A, B.
 *	For binary operations with one argument a set and the other an
 *	integer n, the integer taken to represent set(n).
 *
 *	A | B = union of A and B, integers in at least one of A and B
 *	A & B = intersection of A and B, integers in both A and B
 *	A ~ B = symmetric difference (boolean sum) of A and Bi, integers
 *			in exactly one of A and B
 *	A \ B = set difference, integers in A but not in B
 *
 *	~A	= complement of A, integers not in A
 *	#A	= number of integers in A
 *	!A	= 1 or 0 according as A is empty or not empty
 *	+A	= sum of the members of A
 *
 *	min(A)	= least member of A, -1 for empty set
 *	max(A)	= greatest member of A, -1 for empty set
 *	sum(A)	= sum of the members of A
 *
 * In the following a and b denote arbitrary members of A and B:
 *
 *	A + B = set of sums a + b
 *	A - B = set of differences a - b
 *	A * B = set of products a * b
 *	A ^ n = set of powers a ^ n
 *	A % m = set of integers congruent to a mod m
 *
 *	A == B returns 1 or not according as A and B are equal or not
 *	A != B = !(A == B)
 *	A <= B returns 1 if A is a subset of B, i.e. every member of A is
 *		a member of B
 *	A < B = ((A <= B) && (A != B))
 *	A >= B = (B <= A)
 *	A > B = (B < A)
 *
 * Expressions may be formed from the above "arithmetic" operations in
 * the usual way, with parentheses for variations from the usual precedence
 * rules.  For example
 *
 *	A + 3 * A ^ 2 + (A - B) ^ 3
 *
 * returns the set of integers expressible as
 *
 *	a_1 + 3 * a_2 ^ 2 + (a_3 - b) ^3
 *
 * where a_1, a_2, a_3 are in A, and b is in B.
 *
 * primes(a, b) returns the set of primes between a and b inclusive.
 *
 * interval(a, b) returns the integers between a and b inclusive
 *
 * isinterval(A) returns 1 if A is a non-empty interval, 0 otherwise.
 *
 * randset(n, a, b) returns a random set of n integers between a and b
 *	inclusive; a defaults to 0, b to N-1.  An error occurs if
 *	n is too large.
 *
 * polyvals(L, A) for L = list(c_0, c_1, c_2, ...) returns the set of
 * values of
 *
 *	c_0 + c_1 * a + c_2 * a^2 + ...
 *
 * for a in the set A.
 *
 * polyvals2(L, A, B) returns the set of values of poly(L, i, j) for i in
 *	A and j in B.  Here L is a list whose members are integers or
 *	lists of integers, the latter representing polynomials in the
 *	second variable.  For example, with L = list(0, list(0, 1), 1),
 *	polyvals2(L, A, B) will return the values of i^2 + i * j for
 *	i in A, j in B.
 *
 */


static N;		/* Number of integers in [0,B], = B + 1 */
static M;		/* Maximum string size required, = N // 8 */

obj set {s};

define isset(a) = istype(a, obj set);

define setbound(n)
{
	local v;

	v = N - 1;
	if (isnull(n))
		return v;
	if (!isint(n) || n < 0)
		quit "Bad argument for setbound";
	N = n + 1;
	M = quo(N, 8, 1);	/* M // 8 rounded up */
	if (v >= 0)
		return v;
}

setbound(100);

define empty() = obj set = {""};

define full()
{
	local v;

	obj set v;
	v.s = M * char(-1);
	if (!ismult(N, 8)) v.s[M-1] = 255 >> (8 - N & 7);
	return v;
}

define isin(a, b)
{
	if (!isset(a) || !isint(b))
		quit "Bad argument for isin";
	return bit(a.s, b);
}

define addmember(a, n)
{
	if (!isset(a) || !isint(n))
		quit "Bad argument for addmember";
	if (n < N && n >= 0)
		setbit(a.s, n);
}

define rmmember(a, n)
{
	if (n < N && n >= 0)
		setbit(a.s, n, 0);
}

define set()
{
	local i, v, s;

	s = M * char(0);
	for (i = 1; i <= param(0); i++) {
		v = param(i);
		if (!isint(v))
			quit "Non-integral argument for set";
		if (v >= 0 && v < N)
			setbit(s, v);
	}
	return mkset(s);
}


define mkset(s)
{
	local h, m;

	if (!isstr(s))
		quit "Non-string argument for mkset";
	h = highbit(s);
	if (h >= N)
		quit "Too-long string for mkset";
	m = quo(h + 1, 8, 1);
	return obj set = {head(s, m)};
}


define primes(a,b)
{
	local i, s, m;

	if (isnull(b)) {
		if (isnull(a)) {
			a = 0;
			b = N - 1;
		}
		else b = 0;
	}

	if (!isint(a) || !isint(b))
		quit "Non-integer argument for primes";
	if (a > b)
		swap(a,b);
	if (b < 0 || a >= N)
		return empty();
	a = max(a, 0);
	b = min(b, N-1);
	s = M * char(0);
	for (i = a; i <= b; i++)
		if (isprime(i))
			setbit(s, i);
	return mkset(s);
}

define set_max(a) = highbit(a.s);

define set_min(a) = lowbit(a.s);

define set_not(a) = !a.s;

define set_cmp(a,b)
{
	if (isset(a) && isset(b))
		return a.s != b.s;
	return 1;
}

define set_rel(a,b)
{
	local c;

	if (a == b)
		return 0;
	if (isset(a)) {
		if (isset(b)) {
			c = a & b;
			if (c == a)
				return -1;
			if (c == b)
				return 1;
			return;
		}
		if (!isint(b))
			return set_rel(a, set(b));
	}
	if (isint(a))
		return set_rel(set(a), b);
}


define set_or(a, b)
{
	if (isset(a)) {
		if (isset(b))
			return obj set = {a.s | b.s};
		if (isint(b))
			return a | set(b);
	}
	if (isint(a))
		return set(a) | b;
	return newerror("Bad argument for set_or");
}

define set_and(a, b)
{
	if (isint(a))
		return set(a) & b;
	if (isint(b))
		return a & set(b);
	if (!isset(a) || !isset(b))
		return newerror("Bad argument for set_and");
	return mkset(a.s & b.s);
}


define set_comp(a) = full() \ a;

define set_setminus(a,b)
{
	if (isint(a))
		return set(a) \ b;
	if (isint(b))
		return a \ set(b);
	if (!isset(a) || !isset(b))
		return newerror("Bad argument for set_setminus");
	return mkset(a.s \ b.s);
}


define set_xor(a,b)
{
	if (isint(a))
		return set(a) ~ b;
	if (isint(b))
		return a ~ set(b);
	if (!isset(a) || !isset(b))
		return newerror("Bad argument for set_xor");
	return mkset(a.s ~ b.s);
}

define set_content(a) = #a.s;

define set_add(a, b)
{
	local s, i, j, m, n;

	if (isint(a))
		return set(a) + b;
	if (isint(b))
		return a + set(b);
	if (!isset(a) || !isset(b))
		return newerror("Bad argument for set_add");
	if (!a || !b)
		return empty();
	m = highbit(a.s);
	n = highbit(b.s);
	s = M * char(0);
	for (i = 0; i <= m; i++)
		if (isin(a, i))
			for (j = 0; j <= n && i + j < N; j++)
				if (isin(b, j))
					setbit(s, i + j);
	return mkset(s);
}

define set_sub(a,b)
{
	local s, i, j, m, n;

	if (isint(b))
		return a - set(b);
	if (isint(a))
		return set(a) - b;
	if (isset(a) && isset(b)) {
		if (!a || !b)
			return empty();
		m = highbit(a.s);
		n = highbit(b.s);
		s = M * char(0);
		for (i = 0; i <= m; i++)
			if (isin(a, i))
				for (j = 0; j <= n && j <= i; j++)
					if (isin(b, j))
						setbit(s, i - j);
		return mkset(s);
	}
	return newerror("Bad argument for set_sub");
}

define set_mul(a, b)
{
	local s, i, j, m, n;

	if (isset(a)) {
		s = M * char(0);
		m = highbit(a.s);
		if (isset(b)) {
			if (!a || !b)
				return empty();
			n = highbit(b.s);
			for (i = 0; i <= m; ++i)
				if (isin(a, i))
					for (j = 1; j <= n && i * j < N; ++j)
						if (isin(b, j))
							setbit(s, i * j);
			return mkset(s);
		}
		if (isint(b)) {
			if (b == 0) {
				if (a)
					return set(0);
				return empty();
			}
			s = M * char(0);
			for (i = 0; i <= m && b * i < N; ++i)
				if (isin(a, i))
					setbit(s, b * i);
			return mkset(s);
		}
	}
	if (isint(a))
		return b * a;
	return newerror("Bad argument for set_mul");
}

define set_square(a)
{
	local s, i, m;

	s = M * char(0);
	m = highbit(a.s);
	for (i = 0; i <= m && i^2 < N; ++i)
		if (bit(a.s, i))
			setbit(s, i^2);
	return mkset(s);
}

define set_pow(a, n)
{
	local s, i, m;

	if (!isint(n) || n < 0)
		quit "Bad exponent for set_power";
	s = M * char(0);
	m = highbit(a.s);
	for (i = 0; i <= m && i^n < N; ++i)
		if (bit(a.s, i))
			setbit(s, i^n);
	return mkset(s);
}

define set_sum(a)
{
	local v, m, i;

	v = 0;
	m = highbit(a.s);
	for (i = 0; i <= m; ++i)
		if (bit(a.s, i))
			v += i;
	return v;
}

define set_plus(a) = set_sum(a);

define interval(a, b)
{
	local i, j, s;
	static tail = "\0\1\3\7\17\37\77\177\377";

	if (!isint(a) || !isint(b))
		quit "Non-integer argument for interval";
	if (a > b)
		swap(a, b);
	if (b < 0 || a >= N)
		return empty();
	a = max(a, 0);
	b = min(b, N-1);
	i = quo(a, 8, 0);
	j = quo(b, 8, 0);
	s = M * char(0);
	if (i == j) {
		s[i] = tail[b + 1 - 8 * i] \ tail[a - 8 * i];
		return mkset(s);
	}
	s[i] = ~tail[a - 8 * i];
	while (++i < j)
		s[i] = -1;
	s[j] = tail[b + 1 - 8 * j];
	return mkset(s);
}

define isinterval(a)
{
	local i, max, s;

	if (!isset(a))
		quit "Non-set argument for isinterval";

	s = a.s;
	if (!s)
		return 0;
	for (i = lowbit(s) + 1, max = highbit(s); i < max; i++)
		if (!bit(s, i))
			return 0;
	return 1;
}

define set_mod(a, b)
{
	local s, m, i, j;

	if (isset(a) && isint(b)) {
		s = M * char(0);
		m = highbit(a.s);
		for (i = 0; i <= m; i++)
			if (bit(a.s, i))
				for (j = 0; j < N; j++)
					if (meq(i, j, b))
						setbit(s, j);
		return mkset(s);
	}
	return newerror("Bad argument for set_mod");
}

define randset(n, a, b)
{
	local m, s, i;

	if (isnull(a))
		a = 0;
	if (isnull(b))
		b = N - 1;
	if (!isint(n) || !isint(a) || !isint(b) || n < 0 || a < 0 || b < 0)
		quit "Bad argument for randset";
	if (a > b)
		swap(a, b);
	m = b - a + 1;
	if (n > m)
		return newerror("Too many numbers specified for randset");
	if (2 * n > m)
		return interval(a,b) \ randset(m - n, a, b);
	++b;
	s = M * char(0);
	while (n-- > 0) {
		do
			i = rand(a, b);
		while
			(bit(s, i));
		setbit(s, i);
	}
	return mkset(s);
}

define polyvals(L, A)
{
	local s, m, v, i;

	if (!islist(L))
		quit "Non-list first argument for polyvals";
	if (!isset(A))
		quit "Non-set second argument for polyvals";
	m = highbit(A.s);
	s = M * char(0);
	for (i = 0; i <= m; i++)
		if (bit(A.s, i)) {
			v = poly(L,i);
			if (v >> 0 && v < N)
				setbit(s, v);
		}
	return mkset(s);
}

define polyvals2(L, A, B)
{
	local s1, s2, s, m, n, i, j, v;

	s1 = A.s;
	s2 = B.s;
	m = highbit(s1);
	n = highbit(s2);
	s = M * char(0);
	for (i = 0; i <= m; i++)
		if (bit(s1, i))
			for (j = 0; j <= n; j++)
				if (bit(s2, j)) {
					v = poly(L, i, j);
					if (v >= 0 && v < N)
						setbit(s, v);
				}
	return mkset(s);
}

define set_print(a)
{
	local i, s, m;

	s = a.s;
	i = lowbit(s);
	print "set(":;
	if (i >= 0) {
		print i:;
		m = highbit(s);
		while (++i <= m)
			if (bit(s, i))
				print ",":i:;
	}
	print ")",;
}

local N, M;	/* End scope of static variables N, M */
