Given Row
type
public record Row(bool? Left, string OperatorName, bool? Right, bool? Result);
and a list of Row
s.
This list is generated by a list of boolean operators (2 examples shown below) that I can omit the details for the sake of simplicity.
bool? xor(bool? x, bool? y) => x ^ y;
bool? xor_(bool? x, bool? y) => (!x & y) | (x & !y);
IEnumerable<Row> rows =
[
new Row(null, "_or_", null, null),
new Row(null, "_or_", true, true),
new Row(null, "_or_", false, null),
new Row(true, "_or_", true, true),
new Row(true, "_or_", false, true),
new Row(false, "_or_", false, false),
new Row(null, "and", null, null),
new Row(null, "and", true, null),
new Row(null, "and", false, false),
new Row(true, "and", true, true),
new Row(true, "and", false, false),
new Row(false, "and", false, false),
new Row(null, "or", null, null),
new Row(null, "or", true, true),
new Row(null, "or", false, null),
new Row(true, "or", true, true),
new Row(true, "or", false, true),
new Row(false, "or", false, false),
new Row(null, "or_", null, null),
new Row(null, "or_", true, true),
new Row(null, "or_", false, null),
new Row(true, "or_", true, true),
new Row(true, "or_", false, true),
new Row(false, "or_", false, false),
new Row(null, "xor", null, null),
new Row(null, "xor", true, null),
new Row(null, "xor", false, null),
new Row(true, "xor", true, false),
new Row(true, "xor", false, true),
new Row(false, "xor", false, false),
new Row(null, "xor_", null, null),
new Row(null, "xor_", true, null),
new Row(null, "xor_", false, null),
new Row(true, "xor_", true, false),
new Row(true, "xor_", false, true),
new Row(false, "xor_", false, false)
];
How to use Linq and rows
to produce the following output on the console?
Group 1:
xor
xor_
Group 2:
and
Group 3:
_or_
or
or_
First group by OperatorName
, so we have 6 groups
[
new Row(null, "_or_", null, null),
new Row(null, "_or_", true, true),
new Row(null, "_or_", false, null),
new Row(true, "_or_", true, true),
new Row(true, "_or_", false, true),
new Row(false, "_or_", false, false),
]
[
new Row(null, "and", null, null),
new Row(null, "and", true, null),
new Row(null, "and", false, false),
new Row(true, "and", true, true),
new Row(true, "and", false, false),
new Row(false, "and", false, false)
]
[
new Row(null, "or", null, null),
new Row(null, "or", true, true),
new Row(null, "or", false, null),
new Row(true, "or", true, true),
new Row(true, "or", false, true),
new Row(false, "or", false, false)
]
[
new Row(null, "or_", null, null),
new Row(null, "or_", true, true),
new Row(null, "or_", false, null),
new Row(true, "or_", true, true),
new Row(true, "or_", false, true),
new Row(false, "or_", false, false),
]
[
new Row(null, "xor", null, null),
new Row(null, "xor", true, null),
new Row(null, "xor", false, null),
new Row(true, "xor", true, false),
new Row(true, "xor", false, true),
new Row(false, "xor", false, false)
]
[
new Row(null, "xor_", null, null),
new Row(null, "xor_", true, null),
new Row(null, "xor_", false, null),
new Row(true, "xor_", true, false),
new Row(true, "xor_", false, true),
new Row(false, "xor_", false, false)
]
Because xor_
and xor
are identical truth tables (because (!x & y) | (x & !y)
and x ^ y
are tautologies), then they must be in the same group. The second grouping is my problem. How to get the following output on the console?
Group 1:
xor
xor_
Group 2:
and
Group 3:
_or_
or
or_
For those who are confused in understanding the problem, now I illustrate with a much simpler one.
I have a list of functions
and
(string name, Func<int,int> func) [] functions = new []
{
("a", x => 3*x + 1),
("b", x => 2*x + x + 1),
("c", x => x*x - x*x + 3*x +1),
("d", x => x*x),
("e", x => x*x + 1 -1),
("f", x => 2*(x+1) + x -1)
};
a list of inputs
int[] inputs = [0,1,2];
A list rows
is generated by applying inputs
to each function in functions
. For simulation purposes, rows
is hard coded as follows.
(int x, string funcName, int y)[] rows =
[
(0, "a", 1),
(1, "a", 4),
(2, "a", 7),
(0, "b", 1),
(1, "b", 4),
(2, "b", 7),
(0, "c", 1),
(1, "c", 4),
(2, "c", 7),
(0, "d", 0),
(1, "d", 1),
(2, "d", 4),
(0, "e", 0),
(1, "e", 1),
(2, "e", 4),
(0, "f", 1),
(1, "f", 4),
(2, "f", 7)
];
The expected output:
Group 1:
a
b
c
f
Group 2:
d
e
Why? because functions a
, b
, c
, f
can be simplified to 3*x+1
and functions d
, e
can be simplified to x*x
.
With a little setup, the actual operations are pretty easy. So, setup first.
For ease of hashing (and performance in general), we can encode your truth tables as simple integers where true
/false
/null
are two bits each, and the operands and results can be concatenated to one integer:
record Row(bool? Left, string OperatorName, bool? Right, bool? Result)
{
public ulong CodedRepresentation =>
Left switch { true => 1UL, false => 2UL, null => 3UL }
| (Right switch { true => 1UL, false => 2UL, null => 3UL } << 2)
| (Result switch { true => 1UL, false => 2UL, null => 3UL } << 4);
}
Next we need an enumerable comparer, which is something .Net never provided for some reason:
class EnumerableComparer<T> : IEqualityComparer<IEnumerable<T>>
{
public bool Equals(IEnumerable<T>? x, IEnumerable<T>? y) =>
x is not null && y is not null && !x.Except(y).Any() && !y.Except(x).Any();
public int GetHashCode([DisallowNull] IEnumerable<T> obj)
{
var hash = new HashCode();
foreach (var item in obj.Order())
hash.Add(item);
return hash.ToHashCode();
}
}
And lastly, the actual logic you're looking for:
foreach (var result in rows.GroupBy(r => r.OperatorName)
.Select(r => (name: r.Key, operations: r.Select(w => w.CodedRepresentation)))
.GroupBy(w => w.operations, new EnumerableComparer<ulong>()))
{
Console.WriteLine(string.Join(" = ", result.Select(w => w.name)));
}
All together, the result we get is what you'd expect:
and
or
xor = xor_