I have an algorithm that evaluates in input byte[]
with a source byte[]
. Several conditions can be encountered:
- Match found
- No match found
- End of input array
- End of source array
- Input wildcard (*) matches against any source byte
All of these conditions are dealt with in this algorithm. Having been my first pass at this algorithm, I know there are improvements that can be made. Not only am I looking for cleaner code and possibly refactoring to find re-usable bits, this needs to be as efficient as possible. Right now this will compare an 18 byte input array against an 1170 byte source array in 2ms.
Here is the current implementation of the algorithm:
private bool HasMatchBytes(byte[] bytes, out int length, ref int position) {
var hasMatch = false;
var isComplete = false;
var b = position;
var end = position;
// - this loop depends on internal advancement of the counter [v]
for (var v = 0; v < bytes.Length; ) {
var isMatch = false;
// - this loop depends on internal advancement of the counter [b]
while (b < mByteStream.Length &&
!isComplete) {
// wildcard doesn't care the value
if (bytes[v] == '*') {
isMatch = true;
}
else {
isMatch = bytes[v] == mByteStream[b];
}
// only advance positions if hasMatch is currently false
if (isMatch && !hasMatch) {
hasMatch = true;
position = b++;
++v;
}
// current position does not match and invalidates previous buffers
else if (!isMatch && hasMatch) {
hasMatch = false;
// reset v (bytes array)
v = 0;
// advance b (stream array)
++b;
}
// any other condition should advance b (stream array)
// and v (bytes array)
else {
// advance v (bytes array)
if (isMatch && hasMatch) {
++v;
}
// we could have reached the end of v
if (v < bytes.Length) {
if (hasMatch) {
end = ++b;
}
else {
++b;
}
}
// if so, force completion
else {
isComplete = true;
}
}
}
// isMatch means the current byte indexes match
// assumptions:
// wherever the buffer position is, the results are
// true to the current start position.
if (hasMatch && v < bytes.Length) {
++v;
end = ++b;
if (b >= mByteStream.Length) {
hasMatch = false;
}
}
// with match and at end of bytes length algorithm is complete
if (hasMatch &&
v >= bytes.Length) {
isComplete = true;
end = ++b;
}
// if end of source reached then shut down algorithm
if (b >= mByteStream.Length) {
v = bytes.Length;
isComplete = true;
end = mByteStream.Length;
}
}
length = end - position;
return hasMatch;
}
...using .Net 4.5 if that helps.
Edit per comment
In all cases, the algorithm will return true if the source sequence contains a match against the full input array sequence. Each byte must match specifically in sequence except where a wildcard (*)
is specified.
This algorithm is intended to continue reading until end of the source array. If the end of the source array is reached before another full iterative match of the input array, the algorithm would return false. But the calling method uses the returned position plus the length of the input sequence to determine if there could possibly be another match in the source.
Here is the calling code:
public IEnumerable<IMetaToken> Matches(IMatchToken matchToken) {
var bytes = matchToken.GetValues();
var length = 0;
var position = 0;
while ((position + bytes.Length) < mByteStream.Length) {
if (HasMatchBytes(bytes, out length, ref position)) {
var start = position;
position += length;
var token = new MetaToken(start, length);
yield return token;
}
else {
position += length;
}
}
}
FWIW: This is part of a Regex
-like class that handles expressions against byte arrays. First you have to create a format object and then have the ByteRegex
class find Matches
against the supplied source sequence.
I've decided to take @svick's advice ...well, somewhat. I've already worked on renaming the indexes, etc.
I have another class that manipulates a MemoryStream
and I could use that to encapsulate this operation. At least the code would be in a class with the same order of operations.
I am also going to refactor this huge method into 2 static iterator methods: IterateNeedle(...)
and IterateHaystack(...)
. Names are likely to be changed to protect the innocent but you get the idea. I'll post some code once I have it reworked.
UPDATE Iterators to the rescue
I don't know if I should post my refactors as an answer. Might be helpful for people to see implementation of the IEnumerator
state machine. I'll post explanation as an answer. There is a lot of code though and it is specialized so I'm hesitant because it might draw a lot of questions that are off topic.