Tout le monde connaît un moyen efficace et efficace de rechercher/faire correspondre un modèle d'octet dans un tableau d'octets [], puis de renvoyer les positions.
Par exemple
byte[] pattern = new byte[] {12,3,5,76,8,0,6,125};
byte[] toBeSearched = new byte[] {23,36,43,76,125,56,34,234,12,3,5,76,8,0,6,125,234,56,211,122,22,4,7,89,76,64,12,3,5,76,8,0,6,125}
Puis-je suggérer quelque chose qui n'implique pas la création de chaînes, la copie de tableaux ou de code non sécurisé:
using System;
using System.Collections.Generic;
static class ByteArrayRocks {
static readonly int [] Empty = new int [0];
public static int [] Locate (this byte [] self, byte [] candidate)
{
if (IsEmptyLocate (self, candidate))
return Empty;
var list = new List<int> ();
for (int i = 0; i < self.Length; i++) {
if (!IsMatch (self, i, candidate))
continue;
list.Add (i);
}
return list.Count == 0 ? Empty : list.ToArray ();
}
static bool IsMatch (byte [] array, int position, byte [] candidate)
{
if (candidate.Length > (array.Length - position))
return false;
for (int i = 0; i < candidate.Length; i++)
if (array [position + i] != candidate [i])
return false;
return true;
}
static bool IsEmptyLocate (byte [] array, byte [] candidate)
{
return array == null
|| candidate == null
|| array.Length == 0
|| candidate.Length == 0
|| candidate.Length > array.Length;
}
static void Main ()
{
var data = new byte [] { 23, 36, 43, 76, 125, 56, 34, 234, 12, 3, 5, 76, 8, 0, 6, 125, 234, 56, 211, 122, 22, 4, 7, 89, 76, 64, 12, 3, 5, 76, 8, 0, 6, 125 };
var pattern = new byte [] { 12, 3, 5, 76, 8, 0, 6, 125 };
foreach (var position in data.Locate (pattern))
Console.WriteLine (position);
}
}
Modifier (par IAbstract) - déplacer le contenu de poster ici car ce n'est pas une réponse =
Par curiosité, j'ai créé une petite référence avec les différentes réponses.
Voici les résultats pour un million d'itérations:
solution [Locate]: 00:00:00.7714027
solution [FindAll]: 00:00:03.5404399
solution [SearchBytePattern]: 00:00:01.1105190
solution [MatchBytePattern]: 00:00:03.0658212
Utilisez Méthodes LINQ.
public static IEnumerable<int> PatternAt(byte[] source, byte[] pattern)
{
for (int i = 0; i < source.Length; i++)
{
if (source.Skip(i).Take(pattern.Length).SequenceEqual(pattern))
{
yield return i;
}
}
}
Très simple!
Utilisez l'efficacité algorithme de Boyer-Moore .
Il est conçu pour trouver des chaînes avec des chaînes, mais vous avez besoin de peu d'imagination pour projeter cela sur des tableaux d'octets.
En général, la meilleure réponse est: utilisez n'importe quel algorithme de recherche de chaînes que vous aimez :).
À l'origine, j'ai publié un ancien code que j'utilisais, mais j'étais curieux de savoir --- de Jb Evain benchmarks . J'ai trouvé que ma solution était stupide et lente. Il semble que bruno conde SearchBytePattern est le plus rapide. Je ne pouvais pas comprendre pourquoi d'autant plus qu'il utilise un Array.Copy et une méthode d'extension. Mais il y a des preuves dans les tests de Jb, donc bravo à bruno.
J'ai encore simplifié les bits, donc j'espère que ce sera la solution la plus claire et la plus simple. (Tout le travail acharné de bruno conde) Les améliorations sont les suivantes:
converti en méthode d'extension
public static List<int> IndexOfSequence(this byte[] buffer, byte[] pattern, int startIndex)
{
List<int> positions = new List<int>();
int i = Array.IndexOf<byte>(buffer, pattern[0], startIndex);
while (i >= 0 && i <= buffer.Length - pattern.Length)
{
byte[] segment = new byte[pattern.Length];
Buffer.BlockCopy(buffer, i, segment, 0, pattern.Length);
if (segment.SequenceEqual<byte>(pattern))
positions.Add(i);
i = Array.IndexOf<byte>(buffer, pattern[0], i + 1);
}
return positions;
}
Notez que la dernière instruction du bloc while
doit être i = Array.IndexOf<byte>(buffer, pattern[0], i + 1);
au lieu de i = Array.IndexOf<byte>(buffer, pattern[0], i + pattern.Length);
. Regardez le commentaire de Johan. Un simple test pourrait prouver que:
byte[] pattern = new byte[] {1, 2};
byte[] toBeSearched = new byte[] { 1, 1, 2, 1, 12 };
Avec i = Array.IndexOf<byte>(buffer, pattern[0], i + pattern.Length);
, rien n'est retourné. i = Array.IndexOf<byte>(buffer, pattern[0], i + 1);
renvoie le résultat correct.
Voici ma proposition, plus simple et plus rapide:
int Search(byte[] src, byte[] pattern)
{
int c = src.Length - pattern.Length + 1;
int j;
for (int i = 0; i < c; i++)
{
if (src[i] != pattern[0]) continue;
for (j = pattern.Length - 1; j >= 1 && src[i + j] == pattern[j]; j--) ;
if (j == 0) return i;
}
return -1;
}
Ma solution:
class Program
{
public static void Main()
{
byte[] pattern = new byte[] {12,3,5,76,8,0,6,125};
byte[] toBeSearched = new byte[] { 23, 36, 43, 76, 125, 56, 34, 234, 12, 3, 5, 76, 8, 0, 6, 125, 234, 56, 211, 122, 22, 4, 7, 89, 76, 64, 12, 3, 5, 76, 8, 0, 6, 125};
List<int> positions = SearchBytePattern(pattern, toBeSearched);
foreach (var item in positions)
{
Console.WriteLine("Pattern matched at pos {0}", item);
}
}
static public List<int> SearchBytePattern(byte[] pattern, byte[] bytes)
{
List<int> positions = new List<int>();
int patternLength = pattern.Length;
int totalLength = bytes.Length;
byte firstMatchByte = pattern[0];
for (int i = 0; i < totalLength; i++)
{
if (firstMatchByte == bytes[i] && totalLength - i >= patternLength)
{
byte[] match = new byte[patternLength];
Array.Copy(bytes, i, match, 0, patternLength);
if (match.SequenceEqual<byte>(pattern))
{
positions.Add(i);
i += patternLength - 1;
}
}
}
return positions;
}
}
Il me manquait une méthode/réponse LINQ :-)
/// <summary>
/// Searches in the haystack array for the given needle using the default equality operator and returns the index at which the needle starts.
/// </summary>
/// <typeparam name="T">Type of the arrays.</typeparam>
/// <param name="haystack">Sequence to operate on.</param>
/// <param name="needle">Sequence to search for.</param>
/// <returns>Index of the needle within the haystack or -1 if the needle isn't contained.</returns>
public static IEnumerable<int> IndexOf<T>(this T[] haystack, T[] needle)
{
if ((needle != null) && (haystack.Length >= needle.Length))
{
for (int l = 0; l < haystack.Length - needle.Length + 1; l++)
{
if (!needle.Where((data, index) => !haystack[l + index].Equals(data)).Any())
{
yield return l;
}
}
}
}
Ma version de la réponse de Foubar ci-dessus, qui évite de chercher après la fin de la botte de foin, et permet de spécifier un décalage de départ. Suppose que l'aiguille n'est pas vide ou plus longue que la botte de foin.
public static unsafe long IndexOf(this byte[] haystack, byte[] needle, long startOffset = 0)
{
fixed (byte* h = haystack) fixed (byte* n = needle)
{
for (byte* hNext = h + startOffset, hEnd = h + haystack.LongLength + 1 - needle.LongLength, nEnd = n + needle.LongLength; hNext < hEnd; hNext++)
for (byte* hInc = hNext, nInc = n; *nInc == *hInc; hInc++)
if (++nInc == nEnd)
return hNext - h;
return -1;
}
}
La réponse de Jb Evain a:
for (int i = 0; i < self.Length; i++) {
if (!IsMatch (self, i, candidate))
continue;
list.Add (i);
}
puis la fonction IsMatch vérifie d'abord si candidate
va au-delà de la longueur du tableau recherché.
Ce serait plus efficace si la boucle for
était codée:
for (int i = 0, n = self.Length - candidate.Length + 1; i < n; ++i) {
if (!IsMatch (self, i, candidate))
continue;
list.Add (i);
}
à ce stade, un pourrait éliminer également le test depuis le début de IsMatch
, tant que vous vous contractez via des conditions préalables pour ne jamais l'appeler avec des paramètres "illégaux". Remarque: Correction d'un bug ponctuel en 2019.
Ce sont les méthodes les plus simples et les plus rapides que vous pouvez utiliser, et il n'y aurait rien de plus rapide que celles-ci. C'est dangereux, mais c'est pour cela que nous utilisons des pointeurs, c'est la vitesse. Alors ici, je vous propose mes méthodes d'extension que j'utilise la recherche d'un seul, et une liste d'index des occurrences. Je voudrais dire que c'est le code le plus propre ici.
public static unsafe long IndexOf(this byte[] Haystack, byte[] Needle)
{
fixed (byte* H = Haystack) fixed (byte* N = Needle)
{
long i = 0;
for (byte* hNext = H, hEnd = H + Haystack.LongLength; hNext < hEnd; i++, hNext++)
{
bool Found = true;
for (byte* hInc = hNext, nInc = N, nEnd = N + Needle.LongLength; Found && nInc < nEnd; Found = *nInc == *hInc, nInc++, hInc++) ;
if (Found) return i;
}
return -1;
}
}
public static unsafe List<long> IndexesOf(this byte[] Haystack, byte[] Needle)
{
List<long> Indexes = new List<long>();
fixed (byte* H = Haystack) fixed (byte* N = Needle)
{
long i = 0;
for (byte* hNext = H, hEnd = H + Haystack.LongLength; hNext < hEnd; i++, hNext++)
{
bool Found = true;
for (byte* hInc = hNext, nInc = N, nEnd = N + Needle.LongLength; Found && nInc < nEnd; Found = *nInc == *hInc, nInc++, hInc++) ;
if (Found) Indexes.Add(i);
}
return Indexes;
}
}
Repéré avec Locate, il est 1,2-1,4x plus rapide
La vitesse n'est pas tout. Avez-vous vérifié leur cohérence?
Je n'ai pas testé tout le code répertorié ici. J'ai testé mon propre code (qui n'était pas totalement cohérent, je l'avoue) et IndexOfSequence. J'ai trouvé que pour de nombreux tests, IndexOfSequence était un peu plus rapide que mon code, mais avec des tests répétés, j'ai trouvé qu'il était moins cohérent. En particulier, il semble avoir le plus de mal à trouver des modèles à la fin du tableau, mais il leur manquera parfois aussi au milieu du tableau.
Mon code de test n'est pas conçu pour l'efficacité, je voulais juste avoir un tas de données aléatoires avec des chaînes connues à l'intérieur. Ce modèle de test est à peu près comme un marqueur de limite dans un flux de téléchargement de formulaire http. C'est ce que je cherchais lorsque j'ai parcouru ce code, alors j'ai pensé que je le testerais avec le type de données que je rechercherais. Il semble que plus le motif est long, plus IndexOfSequence manquera souvent une valeur.
private static void TestMethod()
{
Random rnd = new Random(DateTime.Now.Millisecond);
string Pattern = "-------------------------------65498495198498";
byte[] pattern = Encoding.ASCII.GetBytes(Pattern);
byte[] testBytes;
int count = 3;
for (int i = 0; i < 100; i++)
{
StringBuilder TestString = new StringBuilder(2500);
TestString.Append(Pattern);
byte[] buf = new byte[1000];
rnd.NextBytes(buf);
TestString.Append(Encoding.ASCII.GetString(buf));
TestString.Append(Pattern);
rnd.NextBytes(buf);
TestString.Append(Encoding.ASCII.GetString(buf));
TestString.Append(Pattern);
testBytes = Encoding.ASCII.GetBytes(TestString.ToString());
List<int> idx = IndexOfSequence(ref testBytes, pattern, 0);
if (idx.Count != count)
{
Console.Write("change from {0} to {1} on iteration {2}: ", count, idx.Count, i);
foreach (int ix in idx)
{
Console.Write("{0}, ", ix);
}
Console.WriteLine();
count = idx.Count;
}
}
Console.WriteLine("Press ENTER to exit");
Console.ReadLine();
}
(évidemment, j'ai converti IndexOfSequence d'une extension en une méthode normale pour ce test)
Voici un exemple d'exécution de ma sortie:
change from 3 to 2 on iteration 1: 0, 2090,
change from 2 to 3 on iteration 2: 0, 1045, 2090,
change from 3 to 2 on iteration 3: 0, 1045,
change from 2 to 3 on iteration 4: 0, 1045, 2090,
change from 3 to 2 on iteration 6: 0, 2090,
change from 2 to 3 on iteration 7: 0, 1045, 2090,
change from 3 to 2 on iteration 11: 0, 2090,
change from 2 to 3 on iteration 12: 0, 1045, 2090,
change from 3 to 2 on iteration 14: 0, 2090,
change from 2 to 3 on iteration 16: 0, 1045, 2090,
change from 3 to 2 on iteration 17: 0, 1045,
change from 2 to 3 on iteration 18: 0, 1045, 2090,
change from 3 to 1 on iteration 20: 0,
change from 1 to 3 on iteration 21: 0, 1045, 2090,
change from 3 to 2 on iteration 22: 0, 2090,
change from 2 to 3 on iteration 23: 0, 1045, 2090,
change from 3 to 2 on iteration 24: 0, 2090,
change from 2 to 3 on iteration 25: 0, 1045, 2090,
change from 3 to 2 on iteration 26: 0, 2090,
change from 2 to 3 on iteration 27: 0, 1045, 2090,
change from 3 to 2 on iteration 43: 0, 1045,
change from 2 to 3 on iteration 44: 0, 1045, 2090,
change from 3 to 2 on iteration 48: 0, 1045,
change from 2 to 3 on iteration 49: 0, 1045, 2090,
change from 3 to 2 on iteration 50: 0, 2090,
change from 2 to 3 on iteration 52: 0, 1045, 2090,
change from 3 to 2 on iteration 54: 0, 1045,
change from 2 to 3 on iteration 57: 0, 1045, 2090,
change from 3 to 2 on iteration 62: 0, 1045,
change from 2 to 3 on iteration 63: 0, 1045, 2090,
change from 3 to 2 on iteration 72: 0, 2090,
change from 2 to 3 on iteration 73: 0, 1045, 2090,
change from 3 to 2 on iteration 75: 0, 2090,
change from 2 to 3 on iteration 76: 0, 1045, 2090,
change from 3 to 2 on iteration 78: 0, 1045,
change from 2 to 3 on iteration 79: 0, 1045, 2090,
change from 3 to 2 on iteration 81: 0, 2090,
change from 2 to 3 on iteration 82: 0, 1045, 2090,
change from 3 to 2 on iteration 85: 0, 2090,
change from 2 to 3 on iteration 86: 0, 1045, 2090,
change from 3 to 2 on iteration 89: 0, 2090,
change from 2 to 3 on iteration 90: 0, 1045, 2090,
change from 3 to 2 on iteration 91: 0, 2090,
change from 2 to 1 on iteration 92: 0,
change from 1 to 3 on iteration 93: 0, 1045, 2090,
change from 3 to 1 on iteration 99: 0,
Je ne veux pas choisir IndexOfSequence, c'est juste celui avec qui j'ai commencé à travailler aujourd'hui. J'ai remarqué à la fin de la journée qu'il semblait y avoir des modèles manquants dans les données, j'ai donc écrit mon propre modèle de correspondance ce soir. Mais ce n'est pas aussi rapide. Je vais le modifier un peu plus pour voir si je peux le rendre 100% cohérent avant de le poster.
Je voulais juste rappeler à tout le monde qu'ils devraient tester des choses comme celle-ci pour s'assurer qu'ils donnent de bons résultats reproductibles avant de leur faire confiance dans le code de production.
J'ai créé une nouvelle fonction en utilisant les conseils de ma réponse et la réponse d'Alnitak.
public static List<Int32> LocateSubset(Byte[] superSet, Byte[] subSet)
{
if ((superSet == null) || (subSet == null))
{
throw new ArgumentNullException();
}
if ((superSet.Length < subSet.Length) || (superSet.Length == 0) || (subSet.Length == 0))
{
return new List<Int32>();
}
var result = new List<Int32>();
Int32 currentIndex = 0;
Int32 maxIndex = superSet.Length - subSet.Length;
while (currentIndex < maxIndex)
{
Int32 matchCount = CountMatches(superSet, currentIndex, subSet);
if (matchCount == subSet.Length)
{
result.Add(currentIndex);
}
currentIndex++;
if (matchCount > 0)
{
currentIndex += matchCount - 1;
}
}
return result;
}
private static Int32 CountMatches(Byte[] superSet, int startIndex, Byte[] subSet)
{
Int32 currentOffset = 0;
while (currentOffset < subSet.Length)
{
if (superSet[startIndex + currentOffset] != subSet[currentOffset])
{
break;
}
currentOffset++;
}
return currentOffset;
}
la seule partie dont je ne suis pas si heureux est le
currentIndex++;
if (matchCount > 0)
{
currentIndex += matchCount - 1;
}
partie ... J'aurais aimé utiliser un if sinon pour éviter le -1, mais cela se traduit par une meilleure prédiction de branche (même si je ne suis pas sûr que cela importera autant) ..
J'ai essayé différentes solutions et j'ai fini par modifier celle de SearchBytePattern. J'ai testé sur une séquence de 30k et c'est rapide :)
static public int SearchBytePattern(byte[] pattern, byte[] bytes)
{
int matches = 0;
for (int i = 0; i < bytes.Length; i++)
{
if (pattern[0] == bytes[i] && bytes.Length - i >= pattern.Length)
{
bool ismatch = true;
for (int j = 1; j < pattern.Length && ismatch == true; j++)
{
if (bytes[i + j] != pattern[j])
ismatch = false;
}
if (ismatch)
{
matches++;
i += pattern.Length - 1;
}
}
}
return matches;
}
Dis moi ce que tu penses.
Voici une solution que j'ai trouvée. J'ai inclus des notes que j'ai trouvées en cours de mise en œuvre. Il peut correspondre en avant, en arrière et avec différents montants de remémoration (en/déc), par ex. direction; à partir de n'importe quel décalage dans la botte de foin.
Toute entrée serait géniale!
/// <summary>
/// Matches a byte array to another byte array
/// forwards or reverse
/// </summary>
/// <param name="a">byte array</param>
/// <param name="offset">start offset</param>
/// <param name="len">max length</param>
/// <param name="b">byte array</param>
/// <param name="direction">to move each iteration</param>
/// <returns>true if all bytes match, otherwise false</returns>
internal static bool Matches(ref byte[] a, int offset, int len, ref byte[] b, int direction = 1)
{
#region Only Matched from offset Within a and b, could not differ, e.g. if you wanted to mach in reverse for only part of a in some of b that would not work
//if (direction == 0) throw new ArgumentException("direction");
//for (; offset < len; offset += direction) if (a[offset] != b[offset]) return false;
//return true;
#endregion
//Will match if b contains len of a and return a a index of positive value
return IndexOfBytes(ref a, ref offset, len, ref b, len) != -1;
}
///Here is the Implementation code
/// <summary>
/// Swaps two integers without using a temporary variable
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
internal static void Swap(ref int a, ref int b)
{
a ^= b;
b ^= a;
a ^= b;
}
/// <summary>
/// Swaps two bytes without using a temporary variable
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
internal static void Swap(ref byte a, ref byte b)
{
a ^= b;
b ^= a;
a ^= b;
}
/// <summary>
/// Can be used to find if a array starts, ends spot Matches or compltely contains a sub byte array
/// Set checkLength to the amount of bytes from the needle you want to match, start at 0 for forward searches start at hayStack.Lenght -1 for reverse matches
/// </summary>
/// <param name="a">Needle</param>
/// <param name="offset">Start in Haystack</param>
/// <param name="len">Length of required match</param>
/// <param name="b">Haystack</param>
/// <param name="direction">Which way to move the iterator</param>
/// <returns>Index if found, otherwise -1</returns>
internal static int IndexOfBytes(ref byte[] needle, ref int offset, int checkLength, ref byte[] haystack, int direction = 1)
{
//If the direction is == 0 we would spin forever making no progress
if (direction == 0) throw new ArgumentException("direction");
//Cache the length of the needle and the haystack, setup the endIndex for a reverse search
int needleLength = needle.Length, haystackLength = haystack.Length, endIndex = 0, workingOffset = offset;
//Allocate a value for the endIndex and workingOffset
//If we are going forward then the bound is the haystackLength
if (direction >= 1) endIndex = haystackLength;
#region [Optomization - Not Required]
//{
//I though this was required for partial matching but it seems it is not needed in this form
//workingOffset = needleLength - checkLength;
//}
#endregion
else Swap(ref workingOffset, ref endIndex);
#region [Optomization - Not Required]
//{
//Otherwise we are going in reverse and the endIndex is the needleLength - checkLength
//I though the length had to be adjusted but it seems it is not needed in this form
//endIndex = needleLength - checkLength;
//}
#endregion
#region [Optomized to above]
//Allocate a value for the endIndex
//endIndex = direction >= 1 ? haystackLength : needleLength - checkLength,
//Determine the workingOffset
//workingOffset = offset > needleLength ? offset : needleLength;
//If we are doing in reverse swap the two
//if (workingOffset > endIndex) Swap(ref workingOffset, ref endIndex);
//Else we are going in forward direction do the offset is adjusted by the length of the check
//else workingOffset -= checkLength;
//Start at the checkIndex (workingOffset) every search attempt
#endregion
//Save the checkIndex (used after the for loop is done with it to determine if the match was checkLength long)
int checkIndex = workingOffset;
#region [For Loop Version]
///Optomized with while (single op)
///for (int checkIndex = workingOffset; checkIndex < endIndex; offset += direction, checkIndex = workingOffset)
///{
///Start at the checkIndex
/// While the checkIndex < checkLength move forward
/// If NOT (the needle at the checkIndex matched the haystack at the offset + checkIndex) BREAK ELSE we have a match continue the search
/// for (; checkIndex < checkLength; ++checkIndex) if (needle[checkIndex] != haystack[offset + checkIndex]) break; else continue;
/// If the match was the length of the check
/// if (checkIndex == checkLength) return offset; //We are done matching
///}
#endregion
//While the checkIndex < endIndex
while (checkIndex < endIndex)
{
for (; checkIndex < checkLength; ++checkIndex) if (needle[checkIndex] != haystack[offset + checkIndex]) break; else continue;
//If the match was the length of the check
if (checkIndex == checkLength) return offset; //We are done matching
//Move the offset by the direction, reset the checkIndex to the workingOffset
offset += direction; checkIndex = workingOffset;
}
//We did not have a match with the given options
return -1;
}
Pourquoi rendre le simple difficile? Cela peut être fait dans n'importe quelle langue utilisant des boucles for. En voici un en C #:
en utilisant System; en utilisant System.Collections.Generic; namespace BinarySearch { class Program { statique void Main (chaîne [] arguments) { octet [] motif = nouvel octet [] {12,3,5,76,8,0,6,125} ; octet [] toBeSearched = nouvel octet [] {23,36,43,76,125,56,34,234,12,3,5,76,8,0,6,125,234,56,211,
122,22,4,7,89,76,64,12,3,5,76,8,0,6,125}; List <int> occurences = findOccurences (toBeSearched, pattern ); foreach (int occurrence dans occurences) { Console.WriteLine ("Correspondance trouvée commençant à un index basé sur 0:" + occurrence); } } Liste statique <int> findOccurences (octet [] meule de foin, octet [] aiguille) { List <int> occurences = new List <int> (); For (int i = 0; i <haystack.Length; i ++) { if (aiguille [0] == botte de foin [i]) { bool found = true; int j, k; for ( j = 0, k = i; j <aiguille.Longueur; j ++, k ++) { si (k> = meule de foin.Longueur || aiguille [j]! = meule de foin [k]) { found = false; break; } } if (found) { occurences.Add (i - 1 ); i = k; } } } événements de retour; } } }
Vous pouvez utiliser ORegex:
var oregex = new ORegex<byte>("{0}{1}{2}", x=> x==12, x=> x==3, x=> x==5);
var toSearch = new byte[]{1,1,12,3,5,1,12,3,5,5,5,5};
var found = oregex.Matches(toSearch);
Sera trouvé deux matchs:
i:2;l:3
i:6;l:3
Complexité: O (n * m) dans le pire des cas, dans la vraie vie, c'est O(n) à cause de la machine à états interne. Il est plus rapide que .NET Regex dans certains cas. Il est compact , rapide et spécialement conçu pour la mise en correspondance de modèles de tableaux.
J'utiliserais une solution qui fait la correspondance en convertissant en chaîne ...
Vous devez écrire une fonction simple implémentant algorithme de recherche Knuth-Morris-Pratt . Ce sera l'algorithme simple le plus rapide que vous pouvez utiliser pour trouver les index corrects. (Vous pouvez utiliser Boyer-Moore mais cela nécessitera plus de configuration.
Après avoir optimisé l'algorithme, vous pouvez essayer de rechercher d'autres types d'optimisations. Mais vous devriez commencer par les bases.
Par exemple, le "plus rapide" actuel est la solution Locate de Jb Evian.
si vous regardez le noyau
for (int i = 0; i < self.Length; i++) {
if (!IsMatch (self, i, candidate))
continue;
list.Add (i);
}
Après une correspondance du sous-algorithme, il commencera à trouver une correspondance à i + 1, mais vous savez déjà que la première correspondance possible serait i + candidat. Donc, si vous ajoutez,
i += candidate.Length -2; // -2 instead of -1 because the i++ will add the last index
ce sera beaucoup plus rapide lorsque vous vous attendez à beaucoup d'occurrences du sous-ensemble dans le sur-ensemble. (Bruno Conde le fait déjà dans sa solution)
Mais ce n'est que la moitié de l'algorithme KNP, vous devez également ajouter un paramètre supplémentaire à la méthode IsMatch appelé numberOfValidMatches qui serait un paramètre out.
cela se résoudrait comme suit:
int validMatches = 0;
if (!IsMatch (self, i, candidate, out validMatches))
{
i += validMatches - 1; // -1 because the i++ will do the last one
continue;
}
et
static bool IsMatch (byte [] array, int position, byte [] candidate, out int numberOfValidMatches)
{
numberOfValidMatches = 0;
if (candidate.Length > (array.Length - position))
return false;
for (i = 0; i < candidate.Length; i++)
{
if (array [position + i] != candidate [i])
return false;
numberOfValidMatches++;
}
return true;
}
Un peu de refactoring et vous pouvez utiliser le numberOfValidMatches comme variable de boucle, et réécrire la boucle Locate en utilisant un certain temps pour éviter les -2 et -1. Mais je voulais juste préciser comment ajouter l'algorithme KMP.
merci de prendre le temps...
C'est le code que j'utilisais/testais avant de poser ma question ... La raison pour laquelle je pose cette question était que je suis certain que je n'utilise pas le code optimal pour le faire ... alors merci encore pour avoir pris le temps!
private static int CountPatternMatches(byte[] pattern, byte[] bytes)
{
int counter = 0;
for (int i = 0; i < bytes.Length; i++)
{
if (bytes[i] == pattern[0] && (i + pattern.Length) < bytes.Length)
{
for (int x = 1; x < pattern.Length; x++)
{
if (pattern[x] != bytes[x+i])
{
break;
}
if (x == pattern.Length -1)
{
counter++;
i = i + pattern.Length;
}
}
}
}
return counter;
}
Quelqu'un qui voit des erreurs dans mon code? Est-ce considéré comme une approche hackeuse? J'ai essayé presque tous les échantillons que vous avez postés et je semble obtenir des variations dans les résultats des matchs. J'ai exécuté mes tests avec un tableau d'octets de ~ 10 Mo comme tableau toBeSearched.
Voici ma solution (pas la plus performante). Il repose sur le fait que la conversion octets/latin-1 est sans perte, ce qui est pas vrai pour les conversions octets/ASCII ou octets/UTF8.
Ses avantages sont que cela fonctionne (tm) pour toutes les valeurs d'octets (certaines autres solutions ne fonctionnent pas correctement avec les octets 0x80-0xff) et peuvent être étendues pour effectuer une correspondance regex plus avancée.
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
class C {
public static void Main() {
byte[] data = {0, 100, 0, 255, 100, 0, 100, 0, 255};
byte[] pattern = {0, 255};
foreach (int i in FindAll(data, pattern)) {
Console.WriteLine(i);
}
}
public static IEnumerable<int> FindAll(
byte[] haystack,
byte[] needle
) {
// bytes <-> latin-1 conversion is lossless
Encoding latin1 = Encoding.GetEncoding("iso-8859-1");
string sHaystack = latin1.GetString(haystack);
string sNeedle = latin1.GetString(needle);
for (Match m = Regex.Match(sHaystack, Regex.Escape(sNeedle));
m.Success; m = m.NextMatch()) {
yield return m.Index;
}
}
}
C'est ma propre approche sur le sujet. J'ai utilisé des pointeurs pour m'assurer qu'il est plus rapide sur des baies plus grandes. Cette fonction retournera la première occurrence de la séquence (ce dont j'avais besoin dans mon propre cas).
Je suis sûr que vous pouvez le modifier un peu afin de renvoyer une liste avec toutes les occurrences.
Ce que je fais est assez simple. Je boucle dans le tableau source (meule de foin) jusqu'à ce que je trouve le premier octet du motif (aiguille). Lorsque le premier octet est trouvé, je continue de vérifier séparément si les octets suivants correspondent aux octets suivants du modèle. Sinon, je continue la recherche comme d'habitude, à partir de l'index (dans la botte de foin) que j'étais auparavant, avant d'essayer de faire correspondre l'aiguille.
Voici donc le code:
public unsafe int IndexOfPattern(byte[] src, byte[] pattern)
{
fixed(byte *srcPtr = &src[0])
fixed (byte* patternPtr = &pattern[0])
{
for (int x = 0; x < src.Length; x++)
{
byte currentValue = *(srcPtr + x);
if (currentValue != *patternPtr) continue;
bool match = false;
for (int y = 0; y < pattern.Length; y++)
{
byte tempValue = *(srcPtr + x + y);
if (tempValue != *(patternPtr + y))
{
match = false;
break;
}
match = true;
}
if (match)
return x;
}
}
return -1;
}
Code sécurisé ci-dessous:
public int IndexOfPatternSafe(byte[] src, byte[] pattern)
{
for (int x = 0; x < src.Length; x++)
{
byte currentValue = src[x];
if (currentValue != pattern[0]) continue;
bool match = false;
for (int y = 0; y < pattern.Length; y++)
{
byte tempValue = src[x + y];
if (tempValue != pattern[y])
{
match = false;
break;
}
match = true;
}
if (match)
return x;
}
return -1;
}
Je suis un peu en retard à la fête Que diriez-vous d'utiliser l'algorithme de Boyer Moore mais recherchez des octets au lieu de chaînes. code c # ci-dessous.
EyeCode Inc.
class Program {
static void Main(string[] args) {
byte[] text = new byte[] {12,3,5,76,8,0,6,125,23,36,43,76,125,56,34,234,12,4,5,76,8,0,6,125,234,56,211,122,22,4,7,89,76,64,12,3,5,76,8,0,6,123};
byte[] pattern = new byte[] {12,3,5,76,8,0,6,125};
BoyerMoore tmpSearch = new BoyerMoore(pattern,text);
Console.WriteLine(tmpSearch.Match());
Console.ReadKey();
}
public class BoyerMoore {
private static int ALPHABET_SIZE = 256;
private byte[] text;
private byte[] pattern;
private int[] last;
private int[] match;
private int[] suffix;
public BoyerMoore(byte[] pattern, byte[] text) {
this.text = text;
this.pattern = pattern;
last = new int[ALPHABET_SIZE];
match = new int[pattern.Length];
suffix = new int[pattern.Length];
}
/**
* Searches the pattern in the text.
* returns the position of the first occurrence, if found and -1 otherwise.
*/
public int Match() {
// Preprocessing
ComputeLast();
ComputeMatch();
// Searching
int i = pattern.Length - 1;
int j = pattern.Length - 1;
while (i < text.Length) {
if (pattern[j] == text[i]) {
if (j == 0) {
return i;
}
j--;
i--;
}
else {
i += pattern.Length - j - 1 + Math.Max(j - last[text[i]], match[j]);
j = pattern.Length - 1;
}
}
return -1;
}
/**
* Computes the function last and stores its values in the array last.
* last(Char ch) = the index of the right-most occurrence of the character ch
* in the pattern;
* -1 if ch does not occur in the pattern.
*/
private void ComputeLast() {
for (int k = 0; k < last.Length; k++) {
last[k] = -1;
}
for (int j = pattern.Length-1; j >= 0; j--) {
if (last[pattern[j]] < 0) {
last[pattern[j]] = j;
}
}
}
/**
* Computes the function match and stores its values in the array match.
* match(j) = min{ s | 0 < s <= j && p[j-s]!=p[j]
* && p[j-s+1]..p[m-s-1] is suffix of p[j+1]..p[m-1] },
* if such s exists, else
* min{ s | j+1 <= s <= m
* && p[0]..p[m-s-1] is suffix of p[j+1]..p[m-1] },
* if such s exists,
* m, otherwise,
* where p is the pattern and m is its length.
*/
private void ComputeMatch() {
/* Phase 1 */
for (int j = 0; j < match.Length; j++) {
match[j] = match.Length;
} //O(m)
ComputeSuffix(); //O(m)
/* Phase 2 */
//Uses an auxiliary array, backwards version of the KMP failure function.
//suffix[i] = the smallest j > i s.t. p[j..m-1] is a prefix of p[i..m-1],
//if there is no such j, suffix[i] = m
//Compute the smallest shift s, such that 0 < s <= j and
//p[j-s]!=p[j] and p[j-s+1..m-s-1] is suffix of p[j+1..m-1] or j == m-1},
// if such s exists,
for (int i = 0; i < match.Length - 1; i++) {
int j = suffix[i + 1] - 1; // suffix[i+1] <= suffix[i] + 1
if (suffix[i] > j) { // therefore pattern[i] != pattern[j]
match[j] = j - i;
}
else {// j == suffix[i]
match[j] = Math.Min(j - i + match[i], match[j]);
}
}
/* Phase 3 */
//Uses the suffix array to compute each shift s such that
//p[0..m-s-1] is a suffix of p[j+1..m-1] with j < s < m
//and stores the minimum of this shift and the previously computed one.
if (suffix[0] < pattern.Length) {
for (int j = suffix[0] - 1; j >= 0; j--) {
if (suffix[0] < match[j]) { match[j] = suffix[0]; }
}
{
int j = suffix[0];
for (int k = suffix[j]; k < pattern.Length; k = suffix[k]) {
while (j < k) {
if (match[j] > k) {
match[j] = k;
}
j++;
}
}
}
}
}
/**
* Computes the values of suffix, which is an auxiliary array,
* backwards version of the KMP failure function.
*
* suffix[i] = the smallest j > i s.t. p[j..m-1] is a prefix of p[i..m-1],
* if there is no such j, suffix[i] = m, i.e.
* p[suffix[i]..m-1] is the longest prefix of p[i..m-1], if suffix[i] < m.
*/
private void ComputeSuffix() {
suffix[suffix.Length-1] = suffix.Length;
int j = suffix.Length - 1;
for (int i = suffix.Length - 2; i >= 0; i--) {
while (j < suffix.Length - 1 && !pattern[j].Equals(pattern[i])) {
j = suffix[j + 1] - 1;
}
if (pattern[j] == pattern[i]) {
j--;
}
suffix[i] = j + 1;
}
}
}
}
J'ai rencontré ce problème l'autre jour, essayez ceci:
public static long FindBinaryPattern(byte[] data, byte[] pattern)
{
using (MemoryStream stream = new MemoryStream(data))
{
return FindBinaryPattern(stream, pattern);
}
}
public static long FindBinaryPattern(string filename, byte[] pattern)
{
using (FileStream stream = new FileStream(filename, FileMode.Open))
{
return FindBinaryPattern(stream, pattern);
}
}
public static long FindBinaryPattern(Stream stream, byte[] pattern)
{
byte[] buffer = new byte[1024 * 1024];
int patternIndex = 0;
int read;
while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
{
for (int bufferIndex = 0; bufferIndex < read; ++bufferIndex)
{
if (buffer[bufferIndex] == pattern[patternIndex])
{
++patternIndex;
if (patternIndex == pattern.Length)
return stream.Position - (read - bufferIndex) - pattern.Length + 1;
}
else
{
patternIndex = 0;
}
}
}
return -1;
}
Il ne fait rien couper, reste simple.
Voici un code simple que j'ai écrit en utilisant uniquement les types de données de base: (Il retourne l'index de première occurrence)
private static int findMatch(byte[] data, byte[] pattern) {
if(pattern.length > data.length){
return -1;
}
for(int i = 0; i<data.length ;){
int j;
for(j=0;j<pattern.length;j++){
if(pattern[j]!=data[i])
break;
i++;
}
if(j==pattern.length){
System.out.println("Pattern found at : "+(i - pattern.length ));
return i - pattern.length ;
}
if(j!=0)continue;
i++;
}
return -1;
}
J'ai essayé de comprendre la proposition de Sanchez et d'effectuer une recherche plus rapide.Les performances du code ci-dessous sont presque égales.Mais le code est plus compréhensible.
public int Search3(byte[] src, byte[] pattern)
{
int index = -1;
for (int i = 0; i < src.Length; i++)
{
if (src[i] != pattern[0])
{
continue;
}
else
{
bool isContinoue = true;
for (int j = 1; j < pattern.Length; j++)
{
if (src[++i] != pattern[j])
{
isContinoue = true;
break;
}
if(j == pattern.Length - 1)
{
isContinoue = false;
}
}
if ( ! isContinoue)
{
index = i-( pattern.Length-1) ;
break;
}
}
}
return index;
}
Juste une autre réponse qui est facile à suivre et assez efficace pour un type d'opération O(n) sans utiliser de code dangereux ou copier des parties des tableaux source.
Assurez-vous de tester. Certaines des suggestions trouvées sur ce sujet sont susceptibles de se produire.
static void Main(string[] args)
{
// 1 1 1 1 1 1 1 1 1 1 2 2 2
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
byte[] buffer = new byte[] { 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 5, 5, 0, 5, 5, 1, 2 };
byte[] beginPattern = new byte[] { 1, 0, 2 };
byte[] middlePattern = new byte[] { 8, 9, 10 };
byte[] endPattern = new byte[] { 9, 10, 11 };
byte[] wholePattern = new byte[] { 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
byte[] noMatchPattern = new byte[] { 7, 7, 7 };
int beginIndex = ByteArrayPatternIndex(buffer, beginPattern);
int middleIndex = ByteArrayPatternIndex(buffer, middlePattern);
int endIndex = ByteArrayPatternIndex(buffer, endPattern);
int wholeIndex = ByteArrayPatternIndex(buffer, wholePattern);
int noMatchIndex = ByteArrayPatternIndex(buffer, noMatchPattern);
}
/// <summary>
/// Returns the index of the first occurrence of a byte array within another byte array
/// </summary>
/// <param name="buffer">The byte array to be searched</param>
/// <param name="pattern">The byte array that contains the pattern to be found</param>
/// <returns>If buffer contains pattern then the index of the first occurrence of pattern within buffer; otherwise, -1</returns>
public static int ByteArrayPatternIndex(byte[] buffer, byte[] pattern)
{
if (buffer != null && pattern != null && pattern.Length <= buffer.Length)
{
int resumeIndex;
for (int i = 0; i <= buffer.Length - pattern.Length; i++)
{
if (buffer[i] == pattern[0]) // Current byte equals first byte of pattern
{
resumeIndex = 0;
for (int x = 1; x < pattern.Length; x++)
{
if (buffer[i + x] == pattern[x])
{
if (x == pattern.Length - 1) // Matched the entire pattern
return i;
else if (resumeIndex == 0 && buffer[i + x] == pattern[0]) // The current byte equals the first byte of the pattern so start here on the next outer loop iteration
resumeIndex = i + x;
}
else
{
if (resumeIndex > 0)
i = resumeIndex - 1; // The outer loop iterator will increment so subtract one
else if (x > 1)
i += (x - 1); // Advance the outer loop variable since we already checked these bytes
break;
}
}
}
}
}
return -1;
}
/// <summary>
/// Returns the indexes of each occurrence of a byte array within another byte array
/// </summary>
/// <param name="buffer">The byte array to be searched</param>
/// <param name="pattern">The byte array that contains the pattern to be found</param>
/// <returns>If buffer contains pattern then the indexes of the occurrences of pattern within buffer; otherwise, null</returns>
/// <remarks>A single byte in the buffer array can only be part of one match. For example, if searching for 1,2,1 in 1,2,1,2,1 only zero would be returned.</remarks>
public static int[] ByteArrayPatternIndex(byte[] buffer, byte[] pattern)
{
if (buffer != null && pattern != null && pattern.Length <= buffer.Length)
{
List<int> indexes = new List<int>();
int resumeIndex;
for (int i = 0; i <= buffer.Length - pattern.Length; i++)
{
if (buffer[i] == pattern[0]) // Current byte equals first byte of pattern
{
resumeIndex = 0;
for (int x = 1; x < pattern.Length; x++)
{
if (buffer[i + x] == pattern[x])
{
if (x == pattern.Length - 1) // Matched the entire pattern
indexes.Add(i);
else if (resumeIndex == 0 && buffer[i + x] == pattern[0]) // The current byte equals the first byte of the pattern so start here on the next outer loop iteration
resumeIndex = i + x;
}
else
{
if (resumeIndex > 0)
i = resumeIndex - 1; // The outer loop iterator will increment so subtract one
else if (x > 1)
i += (x - 1); // Advance the outer loop variable since we already checked these bytes
break;
}
}
}
}
if (indexes.Count > 0)
return indexes.ToArray();
}
return null;
}