web-dev-qa-db-fra.com

Comment vérifier si une chaîne est entièrement composée de la même sous-chaîne?

Je dois créer une fonction qui prend une chaîne, et elle devrait retourner true ou false selon que l'entrée consiste en une séquence de caractères répétée. La longueur de la chaîne donnée est toujours supérieure à 1 Et la séquence de caractères doit avoir au moins une répétition.

"aa" // true(entirely contains two strings "a")
"aaa" //true(entirely contains three string "a")
"abcabcabc" //true(entirely containas three strings "abc")

"aba" //false(At least there should be two same substrings and nothing more)
"ababa" //false("ab" exists twice but "a" is extra so false)

J'ai créé la fonction ci-dessous:

function check(str){
  if(!(str.length && str.length - 1)) return false;
  let temp = '';
  for(let i = 0;i<=str.length/2;i++){
    temp += str[i]
    //console.log(str.replace(new RegExp(temp,"g"),''))
    if(!str.replace(new RegExp(temp,"g"),'')) return true;
  }
  return false;
}

console.log(check('aa')) //true
console.log(check('aaa')) //true
console.log(check('abcabcabc')) //true
console.log(check('aba')) //false
console.log(check('ababa')) //false

La vérification de cela fait partie du vrai problème. Je ne peux pas me permettre une solution non efficace comme celle-ci. Tout d'abord, il parcourt la moitié de la chaîne.

Le deuxième problème est qu'il utilise replace() dans chaque boucle, ce qui le ralentit. Existe-t-il une meilleure solution concernant les performances?

127
Maheer Ali

Il y a un petit théorème astucieux sur des chaînes comme celles-ci.

Une chaîne se compose du même motif répété plusieurs fois si et seulement si la chaîne est une rotation non triviale d'elle-même.

Ici, une rotation signifie supprimer un certain nombre de caractères de l'avant de la chaîne et les déplacer vers l'arrière. Par exemple, la chaîne hello peut être tournée pour former l'une de ces chaînes:

hello (the trivial rotation)
elloh 
llohe 
lohel 
ohell 

Pour voir pourquoi cela fonctionne, supposons d'abord qu'une chaîne se compose de k copies répétées d'une chaîne w. Ensuite, la suppression de la première copie du motif répété (w) de l'avant de la chaîne et le clouage sur le dos rendra la même chaîne. Le sens inverse est un peu plus difficile à prouver, mais l'idée est que si vous faites pivoter une chaîne et récupérez ce que vous avez commencé, vous pouvez appliquer cette rotation à plusieurs reprises pour paver la chaîne avec plusieurs copies du même modèle (ce modèle étant le chaîne dont vous aviez besoin pour aller à la fin de la rotation).

Maintenant, la question est de savoir si c'est le cas. Pour cela, il existe un autre beau théorème que nous pouvons utiliser:

Si x et y sont des chaînes de même longueur, alors x est une rotation de y si et seulement si x est une sous-chaîne de yy.

Par exemple, nous pouvons voir que lohel est une rotation de hello comme suit:

hellohello
   ^^^^^

Dans notre cas, nous savons que chaque chaîne x sera toujours une sous-chaîne de xx (elle apparaîtra deux fois, une fois à chaque copie de x). Donc, fondamentalement, nous avons juste besoin de vérifier si notre chaîne x est une sous-chaîne de xx sans lui permettre de correspondre au premier ou à mi-chemin. Voici un one-liner pour ça:

function check(str) {
    return (str + str).indexOf(str, 1) !== str.length;
}

En supposant que indexOf est implémenté à l'aide d'un algorithme de correspondance de chaîne rapide, cela s'exécutera dans le temps O (n), où n est la longueur de la chaîne d'entrée.

J'espère que cela t'aides!

184
templatetypedef

Vous pouvez le faire par un groupe de capture et backreference . Vérifiez simplement qu'il s'agit de la répétition de la première valeur capturée.

function check(str) {
  return /^(.+)\1+$/.test(str)
}

console.log(check('aa')) //true
console.log(check('aaa')) //true
console.log(check('abcabcabc')) //true
console.log(check('aba')) //false
console.log(check('ababa')) //false

Dans le RegExp ci-dessus:

  1. ^ et $ signifie ancres de début et de fin pour prédire la position.
  2. (.+) capture n'importe quel modèle et capture la valeur (sauf \n).
  3. \1 est la référence arrière de la première valeur capturée et \1+ vérifierait la répétition de la valeur capturée.

explication Regex ici

Pour le débogage RegExp, utilisez: https://regex101.com/r/pqlAuP/1/debugger

Performances: https://jsperf.com/reegx-and-loop/1

67
Pranav C Balan

L'approche algorithmique la plus rapide est peut-être de construire une fonction Z en temps linéaire:

La fonction Z de cette chaîne est un tableau de longueur n où le i-ème élément est égal au plus grand nombre de caractères à partir de la position i qui coïncide avec les premiers caractères de s.

En d'autres termes, z [i] est la longueur du préfixe commun le plus long entre s et le suffixe de s commençant à i.

Implémentation C++ pour référence:

vector<int> z_function(string s) {
    int n = (int) s.length();
    vector<int> z(n);
    for (int i = 1, l = 0, r = 0; i < n; ++i) {
        if (i <= r)
            z[i] = min (r - i + 1, z[i - l]);
        while (i + z[i] < n && s[z[i]] == s[i + z[i]])
            ++z[i];
        if (i + z[i] - 1 > r)
            l = i, r = i + z[i] - 1;
    }
    return z;
}

Implémentation JavaScript
Ajout d'optimisations - création d'une moitié de z-array et sortie anticipée

function z_function(s) {
  var n = s.length;
  var z = Array(n).fill(0);
  var i, l, r;
  //for our task we need only a half of z-array
  for (i = 1, l = 0, r = 0; i <= n/2; ++i) {
    if (i <= r)
      z[i] = Math.min(r - i + 1, z[i - l]);
    while (i + z[i] < n && s[z[i]] == s[i + z[i]])
      ++z[i];

      //we can check condition and return here
     if (z[i] + i === n && n % i === 0) return true;
    
    if (i + z[i] - 1 > r)
      l = i, r = i + z[i] - 1;
  }
  return false; 
  //return z.some((zi, i) => (i + zi) === n && n % i === 0);
}
console.log(z_function("abacabacabac"));
console.log(z_function("abcab"));

Ensuite, vous devez vérifier les index i qui divisent n. Si vous trouvez une telle i que i+z[i]=n alors la chaîne s peut être compressée à la longueur i et vous pouvez retourner true.

Par exemple, pour

string s= 'abacabacabac'  with length n=12`

z-array est

(0, 0, 1, 0, 8, 0, 1, 0, 4, 0, 1, 0)

et nous pouvons trouver que pour

i=4
i+z[i] = 4 + 8 = 12 = n
and
n % i = 12 % 4 = 0`

donc s peut être représenté comme une sous-chaîne de longueur 4 répétée trois fois.

29
MBo

J'ai lu la réponse de gnasher729 et l'ai mise en œuvre. L'idée est que s'il y a des répétitions, alors il doit y avoir (aussi) un nombre premier de répétitions.

function* primeFactors (n) {
    for (var k = 2; k*k <= n; k++) {
        if (n % k == 0) {
            yield k
            do {n /= k} while (n % k == 0)
        }
    }
    if (n > 1) yield n
}

function check (str) {
    var n = str.length
    primeloop:
    for (var p of primeFactors(n)) {
        var l = n/p
        var s = str.substring(0, l)
        for (var j=1; j<p; j++) {
            if (s != str.substring(l*j, l*(j+1))) continue primeloop
        }
        return true
    }
    return false
}

Un algorithme légèrement différent est le suivant:

function check (str) {
    var n = str.length
    for (var p of primeFactors(n)) {
        var l = n/p
        if (str.substring(0, n-l) == str.substring(l)) return true
    }
    return false
}

J'ai mis à jour la page jsPerf qui contient les algorithmes utilisés sur cette page.

23
user42723

Supposons que la chaîne S a une longueur N et est constituée de doublons de la sous-chaîne s, alors la longueur de s divise N. Par exemple, si S a une longueur de 15, alors la sous-chaîne a une longueur de 1, 3 ou 5.

Soit S composé de (p * q) copies de s. Alors S est également constitué de p copies de (s, répétées q fois). On a donc deux cas: Si N est premier ou 1, alors S ne peut être constitué que de copies de la sous-chaîne de longueur 1. Si N est composite, alors il suffit de vérifier les sous-chaînes s de longueur N/p pour les nombres premiers p divisant la longueur de S.

Déterminez donc N = la longueur de S, puis trouvez tous ses facteurs premiers dans le temps O (sqrt (N)). S'il n'y a qu'un seul facteur N, vérifiez si S est la même chaîne répétée N fois, sinon pour chaque facteur premier p, vérifiez si S consiste en p répétitions des premiers N/p caractères.

17
gnasher729

Je pense qu'une fonction récursive pourrait également être très rapide. La première observation est que la longueur maximale du motif répété est la moitié de la longueur de la chaîne totale. Et nous pourrions simplement tester toutes les longueurs de motifs répétées possibles: 1, 2, 3, ..., str.length/2

La fonction récursive isRepeating (p, str) teste si ce modèle est répété dans str.

Si str est plus long que le motif, la récursivité nécessite que la première partie (même longueur que p) soit une répétition ainsi que le reste de str. Donc str est effectivement divisé en morceaux de longueur p.length.

Si le motif et str testés sont de taille égale, la récursivité se termine ici, avec succès.

Si la longueur est différente (se produit pour "aba" et le motif "ab") ou si les pièces sont différentes, alors false est renvoyé, propageant la récursivité.

function check(str)
{
  if( str.length==1 ) return true; // trivial case
  for( var i=1;i<=str.length/2;i++ ) { // biggest possible repeated pattern has length/2 characters

    if( str.length%i!=0 ) continue; // pattern of size i doesn't fit
    
    var p = str.substring(0, i);
    if( isRepeating(p,str) ) return true;
  }
  return false;
}


function isRepeating(p, str)
{
  if( str.length>p.length ) { // maybe more than 2 occurences

    var left = str.substring(0,p.length);
    var right = str.substring(p.length, str.length);
    return left===p && isRepeating(p,right);
  }
  return str===p; 
}

console.log(check('aa')) //true
console.log(check('aaa')) //true 
console.log(check('abcabcabc')) //true
console.log(check('aba')) //false
console.log(check('ababa')) //false

Performances: https://jsperf.com/reegx-and-loop/1

10
Axel Podehl

A écrit cela en Python. Je sais que ce n'est pas la plate-forme, mais cela a pris 30 minutes. P.S. => PYTHON

def checkString(string):
    gap = 1 
    index= 0
    while index < len(string)/2:
        value  = [string[i:i+gap] for i in range(0,len(string),gap) ]

        x = [string[:gap]==eachVal for eachVal in value]

        if all(x):
            print("THEY ARE  EQUAL")
            break 

        gap = gap+1
        index= index+1 

checkString("aaeaaeaaeaae")
7
JustABeginner

Mon approche est similaire à gnasher729, en ce sens qu'elle utilise la longueur potentielle de la sous-chaîne comme objectif principal, mais elle est moins mathématique et nécessite moins de processus:

L: longueur de la chaîne d'origine

S: longueurs potentielles de sous-chaînes valides

Boucle S de (partie entière de) L/2 à 1. Si L/S est un entier, comparez votre chaîne d'origine aux premiers caractères S de la chaîne d'origine répétés L/S fois.

La raison du bouclage à partir de L/2 en arrière et non à partir de 1 est d'obtenir la plus grande sous-chaîne possible. Si vous voulez la boucle de sous-chaîne la plus petite possible de 1 à L/2. Exemple: "abababab" a à la fois "ab" et "abab" comme sous-chaînes possibles. Lequel des deux serait plus rapide si vous vous souciez uniquement d'un résultat vrai/faux dépend du type de chaînes/sous-chaînes auxquelles il sera appliqué.

6
SunKnight0

Le code Mathematica suivant presque détecte si la liste est répétée au moins une fois. Si la chaîne est répétée au moins une fois, elle renvoie vrai, mais elle peut également retourner vrai si la chaîne est une combinaison linéaire de chaînes répétées.

IsRepeatedQ[list_] := Module[{n = Length@list},
   Round@N@Sum[list[[i]] Exp[2 Pi I i/n], {i, n}] == 0
];

Ce code recherche la contribution "pleine longueur", qui doit être nulle dans une chaîne répétée, mais la chaîne accbbd est également considérée comme répétée, car elle est la somme des deux chaînes répétées ababab et 012012.

L'idée est d'utiliser la transformée de Fourier rapide et de rechercher les spectres de fréquence. En regardant d'autres fréquences, on devrait également pouvoir détecter cet étrange scénario.

5
Per Alexandersson

L'idée de base ici est d'examiner toute sous-chaîne potentielle, en commençant à la longueur 1 et en s'arrêtant à la moitié de la longueur de la chaîne d'origine. Nous examinons uniquement les longueurs de sous-chaîne qui divisent la longueur de chaîne d'origine de manière égale (c'est-à-dire str.length% substring.length == 0).

Cette implémentation examine le premier caractère de chaque itération de sous-chaîne possible avant de passer au deuxième caractère, ce qui pourrait gagner du temps si les sous-chaînes sont censées être longues. Si aucune incompatibilité n'est trouvée après avoir examiné la sous-chaîne entière, alors nous retournons true.

Nous retournons false lorsque nous manquons de sous-chaînes potentielles pour vérifier.

function check(str) {
  const len = str.length;
  for (let subl = 1; subl <= len/2; ++subl) {
    if ((len % subl != 0) || str[0] != str[subl])
      continue;
    
    let i = 1;
    for (; i < subl; ++i)
    {
      let j = 0;
      for (; j < len; j += subl)
        if (str[i] != str[j + i])
          break;
      if (j != len)
        break;
    }
    
    if (i == subl)
      return true;
  }
  return false;
}

console.log(check('aa')) //true
console.log(check('aaa')) //true
console.log(check('abcabcabc')) //true
console.log(check('aba')) //false
console.log(check('ababa')) //false
4
Austin Mullins