I'm developing a web application for simulating electronic circuits and I'm trying to add backward propagation options (guess you can immagine why). During refactoring I came up with a base class for logic operations and wanted to ask for some feedback (whatever pops up in your mind) on it. The class code is the following:
public static class LogicOperationUtilities
{
private static readonly int _numberOfLogicalOperators = Enum.GetValues(typeof(LogicValue)).Length;
/// <summary>
/// Get the number of logical elements.
/// </summary>
public static int NumberOfLogicalElements
{
get { return _numberOfLogicalOperators; }
}
public static Func<LogicValue, LogicValue, LogicValue[][], int, LogicValue> ForwardBinaryOperation
{
get
{
return (x, y, truthTable, numberOfElements) =>
{
int numberOfLogicalElements = NumberOfLogicalElements;
// this works only if assignment of _combinations is done in this way:
// _combinations = new LogicalValue[][]
// {
// new LogicalValue[] { LogicalValue.Unknown, LogicalValue.Unknown, Result },
// new LogicalValue[] { LogicalValue.Unknown, LogicalValue.False, Result },
// new LogicalValue[] { LogicalValue.Unknown, LogicalValue.True, Result },
// new LogicalValue[] { LogicalValue.False, LogicalValue.Unknown, Result },
// new LogicalValue[] { LogicalValue.False, LogicalValue.False, Result },
// new LogicalValue[] { LogicalValue.False, LogicalValue.True, Result },
// new LogicalValue[] { LogicalValue.True, LogicalValue.Unknown, Result },
// new LogicalValue[] { LogicalValue.True, LogicalValue.False, Result },
// new LogicalValue[] { LogicalValue.True, LogicalValue.True, Result }
// };
int rowNumber = (numberOfLogicalElements * (int)x) + (int)y;
return truthTable[rowNumber][numberOfLogicalElements - 1];
};
}
}
public static Func<LogicValue, LogicValue[][], int, IEnumerable<Tuple<LogicValue, LogicValue>>> BackwardBinaryOperation
{
get
{
return (x, truthTable, numberOfElements)
=> truthTable.Where(combination => combination[numberOfElements - 1] == x)
.Select(combination => new Tuple<LogicValue, LogicValue>(combination[0], combination[1]));
}
}
// Not operation
//
public static LogicValue[][] NotOperationTruthTable
{
get
{
return new LogicValue[][]
{
new LogicValue[] { LogicValue.Unknown, LogicValue.Unknown},
new LogicValue[] { LogicValue.False, LogicValue.True},
new LogicValue[] { LogicValue.True, LogicValue.False}
};
}
}
private static readonly int _notOperationNumberOfCombinations = NotOperationTruthTable.Length;
private static readonly int _notOperationNumberOfElements = NotOperationTruthTable[0].Length;
/// <summary>
/// Get the function that calculates the result of the Not operation.
/// </summary>
public static Func<LogicValue, LogicValue> ForwardNotOperation
{
get { return (x) => NotOperationTruthTable[(byte)x][1]; }
}
/// <summary>
/// Get the function that calculates the input values that give a certain specified output.
/// </summary>
public static Func<LogicValue, IEnumerable<LogicValue>> BackwardNotOperation
{
get
{
return (x) => NotOperationTruthTable.Where(combination => combination[1] == x)
.Select(combination => combination[0]);
}
}
// AND operation
//
/// <summary>
/// The truth table for the logical AND operation
/// </summary>
public static LogicValue[][] AndOperationTruthTable
{
get
{
return new LogicValue[][]
{
new LogicValue[] { LogicValue.Unknown, LogicValue.Unknown, LogicValue.Unknown },
new LogicValue[] { LogicValue.Unknown, LogicValue.False, LogicValue.False },
new LogicValue[] { LogicValue.Unknown, LogicValue.True, LogicValue.Unknown },
new LogicValue[] { LogicValue.False, LogicValue.Unknown, LogicValue.False },
new LogicValue[] { LogicValue.False, LogicValue.False, LogicValue.False },
new LogicValue[] { LogicValue.False, LogicValue.True, LogicValue.False },
new LogicValue[] { LogicValue.True, LogicValue.Unknown, LogicValue.Unknown },
new LogicValue[] { LogicValue.True, LogicValue.False, LogicValue.False },
new LogicValue[] { LogicValue.True, LogicValue.True, LogicValue.True }
};
}
}
private static readonly int _andOperationNumberOfCombinations = AndOperationTruthTable.Length;
private static readonly int _andOperationNumberOfElements = AndOperationTruthTable[0].Length;
public static Func<LogicValue, LogicValue, LogicValue> ForwardAndOperation
{
get
{
return (x, y) => ForwardBinaryOperation(x, y, AndOperationTruthTable, _andOperationNumberOfElements);
}
}
public static Func<LogicValue, IEnumerable<Tuple<LogicValue, LogicValue>>> BackwardAndOperation
{
get
{
return (x) => BackwardBinaryOperation(x, AndOperationTruthTable, _andOperationNumberOfElements);
}
}
// NAND operation
//
public static LogicValue[][] NandOperationTruthTable
{
get
{
return new LogicValue[][]
{
new LogicValue[] { LogicValue.Unknown, LogicValue.Unknown, LogicValue.Unknown },
new LogicValue[] { LogicValue.Unknown, LogicValue.False, LogicValue.True },
new LogicValue[] { LogicValue.Unknown, LogicValue.True, LogicValue.Unknown },
new LogicValue[] { LogicValue.False, LogicValue.Unknown, LogicValue.True },
new LogicValue[] { LogicValue.False, LogicValue.False, LogicValue.True },
new LogicValue[] { LogicValue.False, LogicValue.True, LogicValue.True },
new LogicValue[] { LogicValue.True, LogicValue.Unknown, LogicValue.Unknown },
new LogicValue[] { LogicValue.True, LogicValue.False, LogicValue.True },
new LogicValue[] { LogicValue.True, LogicValue.True, LogicValue.False }
};
}
}
private static readonly int _nandOperationNumberOfCombinations = NandOperationTruthTable.Length;
private static readonly int _nandOperationNumberOfElements = NandOperationTruthTable[0].Length;
public static Func<LogicValue, LogicValue, LogicValue> ForwardNandOperation
{
get
{
return (x, y) => ForwardBinaryOperation(x, y, NandOperationTruthTable, _nandOperationNumberOfElements);
}
}
public static Func<LogicValue, IEnumerable<Tuple<LogicValue, LogicValue>>> BackwardNandOperation
{
get { return (x) => BackwardBinaryOperation(x, NandOperationTruthTable, _nandOperationNumberOfElements); }
}
// OR operation
//
public static LogicValue[][] OrOperationTruthTable
{
get
{
return new LogicValue[][]
{
new LogicValue[] { LogicValue.Unknown, LogicValue.Unknown, LogicValue.Unknown },
new LogicValue[] { LogicValue.Unknown, LogicValue.False, LogicValue.Unknown },
new LogicValue[] { LogicValue.Unknown, LogicValue.True, LogicValue.True },
new LogicValue[] { LogicValue.False, LogicValue.Unknown, LogicValue.Unknown },
new LogicValue[] { LogicValue.False, LogicValue.False, LogicValue.False },
new LogicValue[] { LogicValue.False, LogicValue.True, LogicValue.True },
new LogicValue[] { LogicValue.True, LogicValue.Unknown, LogicValue.True },
new LogicValue[] { LogicValue.True, LogicValue.False, LogicValue.True },
new LogicValue[] { LogicValue.True, LogicValue.True, LogicValue.True }
};
}
}
private static readonly int _orOperationNumberOfCombinations = OrOperationTruthTable.Length;
private static readonly int _orOperationNumberOfElements = OrOperationTruthTable[0].Length;
public static Func<LogicValue, LogicValue, LogicValue> ForwardOrOperation
{
get
{
return (x, y) => ForwardBinaryOperation(x, y, OrOperationTruthTable, _orOperationNumberOfElements);
}
}
public static Func<LogicValue, IEnumerable<Tuple<LogicValue, LogicValue>>> BackwardOrOperation
{
get { return (x) => BackwardBinaryOperation(x, OrOperationTruthTable, _orOperationNumberOfElements); }
}
// NOR operation
//
public static LogicValue[][] NorOperationTruthTable
{
get
{
return new LogicValue[][]
{
new LogicValue[] { LogicValue.Unknown, LogicValue.Unknown, LogicValue.Unknown },
new LogicValue[] { LogicValue.Unknown, LogicValue.False, LogicValue.Unknown },
new LogicValue[] { LogicValue.Unknown, LogicValue.True, LogicValue.False },
new LogicValue[] { LogicValue.False, LogicValue.Unknown, LogicValue.Unknown },
new LogicValue[] { LogicValue.False, LogicValue.False, LogicValue.True },
new LogicValue[] { LogicValue.False, LogicValue.True, LogicValue.False },
new LogicValue[] { LogicValue.True, LogicValue.Unknown, LogicValue.False },
new LogicValue[] { LogicValue.True, LogicValue.False, LogicValue.False },
new LogicValue[] { LogicValue.True, LogicValue.True, LogicValue.False }
};
}
}
private static readonly int _norOperationNumberOfCombinations = NorOperationTruthTable.Length;
private static readonly int _norOperationNumberOfElements = NorOperationTruthTable[0].Length;
public static Func<LogicValue, LogicValue, LogicValue> ForwardNorOperation
{
get
{
return (x, y) => ForwardBinaryOperation(x, y, NorOperationTruthTable, _norOperationNumberOfElements);
}
}
public static Func<LogicValue, IEnumerable<Tuple<LogicValue, LogicValue>>> BackwardNorOperation
{
get { return (x) => BackwardBinaryOperation(x, NorOperationTruthTable, _norOperationNumberOfElements); }
}
// XOR operation
//
public static LogicValue[][] XorOperationTruthTable
{
get
{
return new LogicValue[][]
{
new LogicValue[] { LogicValue.Unknown, LogicValue.Unknown, LogicValue.Unknown },
new LogicValue[] { LogicValue.Unknown, LogicValue.False, LogicValue.Unknown },
new LogicValue[] { LogicValue.Unknown, LogicValue.True, LogicValue.Unknown },
new LogicValue[] { LogicValue.False, LogicValue.Unknown, LogicValue.Unknown },
new LogicValue[] { LogicValue.False, LogicValue.False, LogicValue.False },
new LogicValue[] { LogicValue.False, LogicValue.True, LogicValue.True },
new LogicValue[] { LogicValue.True, LogicValue.Unknown, LogicValue.Unknown },
new LogicValue[] { LogicValue.True, LogicValue.False, LogicValue.True },
new LogicValue[] { LogicValue.True, LogicValue.True, LogicValue.False }
};
}
}
private static readonly int _xorOperationNumberOfCombinations = XorOperationTruthTable.Length;
private static readonly int _xorOperationNumberOfElements = XorOperationTruthTable[0].Length;
public static Func<LogicValue, LogicValue, LogicValue> ForwardXorOperation
{
get
{
return (x, y) => ForwardBinaryOperation(x, y, XorOperationTruthTable, _xorOperationNumberOfElements);
}
}
public static Func<LogicValue, IEnumerable<Tuple<LogicValue, LogicValue>>> BackwardXorOperation
{
get
{
return (x) => BackwardBinaryOperation(x, XorOperationTruthTable, _xorOperationNumberOfElements);
}
}
// NXOR operation
//
public static LogicValue[][] NxorOperationTruthTable
{
get
{
return new LogicValue[][]
{
new LogicValue[] { LogicValue.Unknown, LogicValue.Unknown, LogicValue.Unknown },
new LogicValue[] { LogicValue.Unknown, LogicValue.False, LogicValue.Unknown },
new LogicValue[] { LogicValue.Unknown, LogicValue.True, LogicValue.Unknown },
new LogicValue[] { LogicValue.False, LogicValue.Unknown, LogicValue.Unknown },
new LogicValue[] { LogicValue.False, LogicValue.False, LogicValue.True },
new LogicValue[] { LogicValue.False, LogicValue.True, LogicValue.False },
new LogicValue[] { LogicValue.True, LogicValue.Unknown, LogicValue.Unknown },
new LogicValue[] { LogicValue.True, LogicValue.False, LogicValue.False },
new LogicValue[] { LogicValue.True, LogicValue.True, LogicValue.True }
};
}
}
private static readonly int _nxorOperationNumberOfCombinations = NxorOperationTruthTable.Length;
private static readonly int _nxorOperationNumberOfElements = NxorOperationTruthTable[0].Length;
public static Func<LogicValue, LogicValue, LogicValue> ForwardNxorOperation
{
get
{
return (x, y) => ForwardBinaryOperation(x, y, NxorOperationTruthTable, _nxorOperationNumberOfElements);
}
}
public static Func<LogicValue, IEnumerable<Tuple<LogicValue, LogicValue>>> BackwardNxorOperation
{
get
{
return (x) => BackwardBinaryOperation(x, NxorOperationTruthTable, _nxorOperationNumberOfElements);
}
}
}
This class is used in the following way in a second class which should be the class that is used from the various gates:
public class TruthTable
{
private readonly LogicValue[][] _truthTable;
/// <summary>
/// The types of logic operations.
/// </summary>
public enum LogicOperation
{
NOT,
AND,
OR,
XOR,
NAND,
NOR,
NXOR
}
/// <summary>
/// Get the logic operation this TruthTable instance refers to.
/// </summary>
public LogicOperation Operation { get; private set; }
/// <summary>
/// Get a flag indicating if the truth table is of a
/// binary operation or not.
/// </summary>
public bool IsBinaryOperation
{
get { return Operation != LogicOperation.NOT; }
}
/// <summary>
/// Get the truth table (as a matrix) for the specified
/// logic operation.
/// </summary>
public LogicValue[,] OperationTruthTable
{
get
{
int numberOfCombinations = _truthTable.Length;
int numberOfElements = _truthTable[0].Length;
var result = new LogicValue[numberOfCombinations, numberOfElements];
for (int i = 0; i < numberOfCombinations; i++)
{
for (int j = 0; j < numberOfElements; j++)
{
result[i, j] = _truthTable[i][j];
}
}
return result;
}
}
/// <summary>
/// Get the result value of a unary logic operation with
/// input x.
/// </summary>
/// <returns>
/// The result of the unary operation with input x.
/// </returns>
public LogicValue this[LogicValue x]
{
get
{
if (IsBinaryOperation)
{
throw new InvalidOperationException(
"This operation is valid only for unary operations.");
}
return _truthTable[(int)x][1];
}
}
/// <summary>
/// Get the result value of a binary logic operation with
/// input x and y.
/// </summary>
/// <returns>
/// The result of the binary operation with input x and y.
/// </returns>
public LogicValue this[LogicValue x, LogicValue y]
{
get
{
if (!IsBinaryOperation)
{
throw new InvalidOperationException(
"This operation is valid only for binary operations.");
}
return _truthTable[3 * (int)x + (int)y][2];
}
}
/// <summary>
/// Constructor of a TruthTable instance.
/// </summary>
/// <param name="logicOperation">
/// The logic operation this truth table refers to.
/// </param>
public TruthTable(LogicOperation logicOperation)
{
switch (logicOperation)
{
case LogicOperation.AND:
_truthTable = LogicOperationUtilities.AndOperationTruthTable;
break;
case LogicOperation.NAND:
_truthTable = LogicOperationUtilities.NandOperationTruthTable;
break;
case LogicOperation.NOR:
_truthTable = LogicOperationUtilities.NorOperationTruthTable;
break;
case LogicOperation.NOT:
_truthTable = LogicOperationUtilities.NotOperationTruthTable;
break;
case LogicOperation.NXOR:
_truthTable = LogicOperationUtilities.NxorOperationTruthTable;
break;
case LogicOperation.OR:
_truthTable = LogicOperationUtilities.OrOperationTruthTable;
break;
case LogicOperation.XOR:
_truthTable = LogicOperationUtilities.XorOperationTruthTable;
break;
}
Operation = logicOperation;
}
}
Just for completing the scenario, LogicValue
is an enum defined in the following way:
public enum LogicValue : byte
{
Unknown = 0,
False = 1,
True = 2
}
For the testing part (for which I'd appreciate some feedback also) I used the following:
LogicOperationTests.cs
[TestClass]
public class LogicOperationTests
{
[TestMethod]
public void ForwardNotOperationTest()
{
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNotOperation(LogicValue.Unknown));
Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardNotOperation(LogicValue.False));
Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardNotOperation(LogicValue.True));
}
[TestMethod]
public void BackwardNotOperationTest()
{
foreach (LogicValue value in Enum.GetValues(typeof(LogicValue)))
{
foreach (var logicalValue in LogicOperationUtilities.BackwardNotOperation(value))
{
Assert.AreEqual(value, LogicOperationUtilities.ForwardNotOperation(logicalValue));
}
}
}
[TestMethod]
public void ForwardAndOperationTest()
{
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardAndOperation(LogicValue.Unknown, LogicValue.Unknown));
Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardAndOperation(LogicValue.Unknown, LogicValue.False));
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardAndOperation(LogicValue.Unknown, LogicValue.True));
Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardAndOperation(LogicValue.False, LogicValue.Unknown));
Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardAndOperation(LogicValue.False, LogicValue.False));
Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardAndOperation(LogicValue.False, LogicValue.True));
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardAndOperation(LogicValue.True, LogicValue.Unknown));
Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardAndOperation(LogicValue.True, LogicValue.False));
Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardAndOperation(LogicValue.True, LogicValue.True));
}
[TestMethod]
public void BackwardAndOperationTest()
{
foreach (LogicValue value in Enum.GetValues(typeof(LogicValue)))
{
foreach (var logicalValue in LogicOperationUtilities.BackwardAndOperation(value))
{
Assert.AreEqual(value, LogicOperationUtilities.ForwardAndOperation(logicalValue.Item1, logicalValue.Item2));
}
}
}
[TestMethod]
public void ForwardNandOperationTest()
{
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNandOperation(LogicValue.Unknown, LogicValue.Unknown));
Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardNandOperation(LogicValue.Unknown, LogicValue.False));
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNandOperation(LogicValue.Unknown, LogicValue.True));
Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardNandOperation(LogicValue.False, LogicValue.Unknown));
Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardNandOperation(LogicValue.False, LogicValue.False));
Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardNandOperation(LogicValue.False, LogicValue.True));
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNandOperation(LogicValue.True, LogicValue.Unknown));
Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardNandOperation(LogicValue.True, LogicValue.False));
Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardNandOperation(LogicValue.True, LogicValue.True));
}
[TestMethod]
public void BackwardNandOperationTest()
{
foreach (LogicValue value in Enum.GetValues(typeof(LogicValue)))
{
foreach (var logicalValue in LogicOperationUtilities.BackwardNandOperation(value))
{
Assert.AreEqual(value, LogicOperationUtilities.ForwardNandOperation(logicalValue.Item1, logicalValue.Item2));
}
}
}
[TestMethod]
public void ForwardOrOperationTest()
{
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardOrOperation(LogicValue.Unknown, LogicValue.Unknown));
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardOrOperation(LogicValue.Unknown, LogicValue.False));
Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardOrOperation(LogicValue.Unknown, LogicValue.True));
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardOrOperation(LogicValue.False, LogicValue.Unknown));
Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardOrOperation(LogicValue.False, LogicValue.False));
Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardOrOperation(LogicValue.False, LogicValue.True));
Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardOrOperation(LogicValue.True, LogicValue.Unknown));
Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardOrOperation(LogicValue.True, LogicValue.False));
Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardOrOperation(LogicValue.True, LogicValue.True));
}
[TestMethod]
public void BackwardOrOperationTest()
{
foreach (LogicValue value in Enum.GetValues(typeof(LogicValue)))
{
foreach (var logicalValue in LogicOperationUtilities.BackwardOrOperation(value))
{
Assert.AreEqual(value, LogicOperationUtilities.ForwardOrOperation(logicalValue.Item1, logicalValue.Item2));
}
}
}
[TestMethod]
public void ForwardNorOperationTest()
{
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNorOperation(LogicValue.Unknown, LogicValue.Unknown));
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNorOperation(LogicValue.Unknown, LogicValue.False));
Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardNorOperation(LogicValue.Unknown, LogicValue.True));
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNorOperation(LogicValue.False, LogicValue.Unknown));
Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardNorOperation(LogicValue.False, LogicValue.False));
Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardNorOperation(LogicValue.False, LogicValue.True));
Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardNorOperation(LogicValue.True, LogicValue.Unknown));
Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardNorOperation(LogicValue.True, LogicValue.False));
Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardNorOperation(LogicValue.True, LogicValue.True));
}
[TestMethod]
public void BackwardNorOperationTest()
{
foreach (LogicValue value in Enum.GetValues(typeof(LogicValue)))
{
foreach (var logicalValue in LogicOperationUtilities.BackwardNorOperation(value))
{
Assert.AreEqual(value, LogicOperationUtilities.ForwardNorOperation(logicalValue.Item1, logicalValue.Item2));
}
}
}
[TestMethod]
public void ForwardXorOperationTest()
{
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardXorOperation(LogicValue.Unknown, LogicValue.Unknown));
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardXorOperation(LogicValue.Unknown, LogicValue.False));
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardXorOperation(LogicValue.Unknown, LogicValue.True));
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardXorOperation(LogicValue.False, LogicValue.Unknown));
Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardXorOperation(LogicValue.False, LogicValue.False));
Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardXorOperation(LogicValue.False, LogicValue.True));
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardXorOperation(LogicValue.True, LogicValue.Unknown));
Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardXorOperation(LogicValue.True, LogicValue.False));
Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardXorOperation(LogicValue.True, LogicValue.True));
}
[TestMethod]
public void BackwardXorOperationTest()
{
foreach (LogicValue value in Enum.GetValues(typeof(LogicValue)))
{
foreach (var logicalValue in LogicOperationUtilities.BackwardXorOperation(value))
{
Assert.AreEqual(value, LogicOperationUtilities.ForwardXorOperation(logicalValue.Item1, logicalValue.Item2));
}
}
}
[TestMethod]
public void ForwardNxorOperationTest()
{
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNxorOperation(LogicValue.Unknown, LogicValue.Unknown));
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNxorOperation(LogicValue.Unknown, LogicValue.False));
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNxorOperation(LogicValue.Unknown, LogicValue.True));
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNxorOperation(LogicValue.False, LogicValue.Unknown));
Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardNxorOperation(LogicValue.False, LogicValue.False));
Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardNxorOperation(LogicValue.False, LogicValue.True));
Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNxorOperation(LogicValue.True, LogicValue.Unknown));
Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardNxorOperation(LogicValue.True, LogicValue.False));
Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardNxorOperation(LogicValue.True, LogicValue.True));
}
[TestMethod]
public void BackwardNxorOperationTest()
{
foreach (LogicValue value in Enum.GetValues(typeof(LogicValue)))
{
foreach (var logicalValue in LogicOperationUtilities.BackwardNxorOperation(value))
{
Assert.AreEqual(value, LogicOperationUtilities.ForwardNxorOperation(logicalValue.Item1, logicalValue.Item2));
}
}
}
}
and:
TruthTableTests.cs
[TestClass]
public class TruthTableTests
{
[TestMethod]
public void AndTruthTableCreationTest()
{
TruthTable table = new TruthTable(TruthTable.LogicOperation.AND);
Assert.IsTrue(table.IsBinaryOperation);
Assert.IsTrue(HaveSameSize(LogicOperationUtilities.AndOperationTruthTable, table.OperationTruthTable));
Assert.IsTrue(ContainSameElements(LogicOperationUtilities.AndOperationTruthTable, table.OperationTruthTable));
}
[TestMethod]
public void NandTruthTableCreationTest()
{
TruthTable table = new TruthTable(TruthTable.LogicOperation.NAND);
Assert.IsTrue(table.IsBinaryOperation);
Assert.IsTrue(HaveSameSize(LogicOperationUtilities.NandOperationTruthTable, table.OperationTruthTable));
Assert.IsTrue(ContainSameElements(LogicOperationUtilities.NandOperationTruthTable, table.OperationTruthTable));
}
[TestMethod]
public void OrTruthTableCreationTest()
{
TruthTable table = new TruthTable(TruthTable.LogicOperation.OR);
Assert.IsTrue(table.IsBinaryOperation);
Assert.IsTrue(HaveSameSize(LogicOperationUtilities.OrOperationTruthTable, table.OperationTruthTable));
Assert.IsTrue(ContainSameElements(LogicOperationUtilities.OrOperationTruthTable, table.OperationTruthTable));
}
[TestMethod]
public void NorTruthTableCreationTest()
{
TruthTable table = new TruthTable(TruthTable.LogicOperation.NOR);
Assert.IsTrue(table.IsBinaryOperation);
Assert.IsTrue(HaveSameSize(LogicOperationUtilities.NorOperationTruthTable, table.OperationTruthTable));
Assert.IsTrue(ContainSameElements(LogicOperationUtilities.NorOperationTruthTable, table.OperationTruthTable));
}
[TestMethod]
public void NotTruthTableCreationTest()
{
TruthTable table = new TruthTable(TruthTable.LogicOperation.NOT);
Assert.IsFalse(table.IsBinaryOperation);
Assert.IsTrue(HaveSameSize(LogicOperationUtilities.NotOperationTruthTable, table.OperationTruthTable));
Assert.IsTrue(ContainSameElements(LogicOperationUtilities.NotOperationTruthTable, table.OperationTruthTable));
}
[TestMethod]
public void XorTruthTableCreationTest()
{
TruthTable table = new TruthTable(TruthTable.LogicOperation.XOR);
Assert.IsTrue(table.IsBinaryOperation);
Assert.IsTrue(HaveSameSize(LogicOperationUtilities.XorOperationTruthTable, table.OperationTruthTable));
Assert.IsTrue(ContainSameElements(LogicOperationUtilities.XorOperationTruthTable, table.OperationTruthTable));
}
[TestMethod]
public void NxorTruthTableCreationTest()
{
TruthTable table = new TruthTable(TruthTable.LogicOperation.NXOR);
Assert.IsTrue(table.IsBinaryOperation);
Assert.IsTrue(HaveSameSize(LogicOperationUtilities.NxorOperationTruthTable, table.OperationTruthTable));
Assert.IsTrue(ContainSameElements(LogicOperationUtilities.NxorOperationTruthTable, table.OperationTruthTable));
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void IndexOperatorTest()
{
TruthTable unary = new TruthTable(TruthTable.LogicOperation.NOT);
TruthTable binary = new TruthTable(TruthTable.LogicOperation.AND);
var values = Enum.GetValues(typeof(LogicValue));
var shouldFail = unary[LogicValue.False, LogicValue.False];
var shouldAlsoFail = binary[LogicValue.False];
foreach (LogicValue x in values)
{
Assert.AreSame(unary[x], LogicOperationUtilities.ForwardNotOperation(x));
foreach (LogicValue y in values)
{
Assert.AreSame(binary[x, y], LogicOperationUtilities.ForwardAndOperation(x, y));
}
}
}
public bool HaveSameSize(LogicValue[][] truthTable, LogicValue[,] truthTableMatrix)
{
if (truthTable.Length != truthTableMatrix.GetLength(0))
{
return false;
}
var numberOfColumns = truthTableMatrix.GetLength(1);
for (int i = 0; i < truthTable.Length; i++)
{
if (truthTable[i].Length != numberOfColumns)
{
return false;
}
}
return true;
}
public bool ContainSameElements(LogicValue[][] truthTable, LogicValue[,] truthTableMatrix)
{
var rows = truthTable.Length;
var cols = truthTableMatrix.GetLength(1);
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
if (truthTable[i][j] != truthTableMatrix[i, j])
{
return false;
}
}
}
return true;
}
}
As usual, I'm open to any type of feedback, even though for now I'm concentrating more on functionality and readability rather than performance.
Let me know if you have any questions.
1 Answer 1
Do not use (if possible) static classes. You save few bytes and few CPU cycles but they're harder to test if you need to mock their implementation. It's a general rule then it may allow exceptions (not in this case, IMO).
Do not return Func<LogicValue, LogicValue, LogicValue[][], int, LogicValue>
from a method (or property). It's far too complex to be clear. Think you have a function with four parameters (one of which is a jagged array) and you need to understand which is what at calling point.
Keep (as much as possible) coherent names: you have a property NumberOfLogicalElements
that returns _numberOfLogicalOperators
. Unless you have a very good reason you should pick one name and use that one everywhere. Are they elements or operators? Is there any difference? Do they need to be different? If yes then name should make clear why.
In, for example, ForwardBinaryOperation
you do not need a local variable named numberOfLogicalElements
. Drop it, JIT compiler will inline property access. Usually you should avoid such micro-optimizations.
In the same (static) class you have properties to do different things, ForwardBinaryOperation
and NotOperationTruthTable
. I'd move them to separate classes (but see later).
Your enum
values are upper-case, follow common casing convention: AND
is not an acronym and even in that case it shouldn't be coded in upper-case, name it And
.
The main point (IMO) is that LogicOperationUtilities
is an utility class. Call it utility or helper but it always smells bad. Many times (not always) when you need such class you are, in reality, doing something bad elsewhere.
In your case I'd simply drop LogicOperationUtilities
class and LogicOperation
enum and I'd use inheritance to do same job.
Make LogicOperation
an abstract base class. Speaking in domain language you may say something like this: "Each logic operation has a variable number of inputs and a truth table to map each combination to one (or more) output value(s)".
Peak keywords from this to build your domain model: LogicValue
to represent a logical state, Input
to represent a single input for a LogicOperation
and Output
to represent a single output. Because each operation may have multiple inputs/outputs you will also need InputCollection
and OutputCollection
. Because one output may be connected to an input you may abstract type of Input
and Output
into a generic Port
class. Note that you have a LogicValue
enumeration but you already have same type built-in in .NET, plus all C# syntactic sugar: drop LogicValue
in favor of bool?
.
You will then add more: "Each port has a name and each logic operation may refer to its inputs/outputs using that name or their - arbitrary - indices". Going in the right direction? Now let's say that "Each input port can receive a value, input ports from outside and output ports from a logic operation".
Now you know you have to define a Port
class (with a Name
and a Value
property) and two derived classes: InputPort
and OutputPort
. Each LogicOperation
class will have a property Inputs
(of type InputCollection
, an almost empty Collection<InputPort>
) for its inputs and an Output
property (of type OutputCollection
, an almost empty Collection<OutputPort>
).
Few more details: "Must be available these binary operations: And, Or, Nand, Nor, Xor and Nxor and one unary operator Not".
Now you know you have two main classes (that define how many inputs/outputs you have). Define an abstract BinaryLogicOperation
and another abstract UnaryLogicOperation
. Concrete classes (And
, Or
and so on) will derive from them.
Note we moved responsability from TruthTable
(a God class and its utility class) to the proper place: behavior is defined in each class and you're using inheritance to hide it.
What's then a truth-table? TruthTable
is then simply a combination of inputs (InputCollection
) associated with a LogicOperation
. Its output column is calculated. Note that in this way you may build simple digital logic simulator simply connecting inputs and outputs and C# events will do the job, see this Proof of Concept:
abstract class Port {
public bool? Value {
get { return _value; }
set {
if (_value != value) {
_value = value;
OnValueChanged(EventArgs.Empty);
}
}
public event EventHandler ValueChanged;
protected virtual void OnValueChanged(EventArgs e) {
var valueChanged = ValueChanged;
if (valueChanged != null) {
valueChanged(this, e);
}
}
}
private bool? _value;
}
Over this you then should add INotifyPropertyChanged
implementation and, possibly, IObservable
for example to better integrate with Reactive Extensions. To code/build/design/load your digital network simulation will be as easy as attaching few events. I'd also suggest to take a look to Task Parallel Library for a different approach to this problem (they have different usage scenarios but it's greatly off-topic to discuss them here).
You may have noted I almost didn't write any code, you already have a lot of it but what I tried to express is the logical flow, code is merely a detail here.
One thing about testing. You're exposing an implementation detail (your utility class) just to test it. In my opinion tests should check only public interface of your components. Testing public interface all the implementation details should be involved (and code coverage should quickly confirm this). They shouldn't check private implementation details because they're free to change and what you want to ensure is valid is component behavior, not how it's implemented. If you can't test it unless you test your implementation then there is something wrong (or you have too convoluted and/or useless code). BTW an isolated domain model will also help you in tests.
-
\$\begingroup\$ Can you please explain further what you mean by number 3? \$\endgroup\$Der Kommissar– Der Kommissar2015年11月23日 17:10:34 +00:00Commented Nov 23, 2015 at 17:10
-
1\$\begingroup\$ A property NumberOfLogicalElements returns value of a field numberOfLogicalOperators. Elements or Operators but I'd pick one _definition for that thing and I'd use it extensively (or in future I may ask myself if I really meant something different, in that case I'd use more clear and different names). \$\endgroup\$Adriano Repetti– Adriano Repetti2015年11月23日 17:13:21 +00:00Commented Nov 23, 2015 at 17:13
-
\$\begingroup\$ Lots of useful feedback in this answer. I totally ignored the possibility of utility classes being a code smell and the domain model was not well built. Thanks a lot \$\endgroup\$Gentian Kasa– Gentian Kasa2015年11月24日 09:05:14 +00:00Commented Nov 24, 2015 at 9:05
-
\$\begingroup\$ @GentianKasa utility classes are not always code smell, honestly I'm not so pure about this but often some refactoring (from top) will wipe them out. You welcome, I didn't write much code, I wanted to outline one possible different approach (separately from other suggestions). \$\endgroup\$Adriano Repetti– Adriano Repetti2015年11月24日 09:10:31 +00:00Commented Nov 24, 2015 at 9:10
-
1\$\begingroup\$ I don't care that much about the presence of code, that can always be written afterwards. It's the design and testing logic that I loved the most from the answer ;) \$\endgroup\$Gentian Kasa– Gentian Kasa2015年11月24日 09:16:54 +00:00Commented Nov 24, 2015 at 9:16
Tuple.Create(...)
instead ofnew Tuple<T,U>(...)
as it is more idiomatic and it allows for generic parameter inference. \$\endgroup\$