javascript - solo - ¿Cómo compruebo si una cadena está formada exclusivamente por grupos de caracteres de la misma longitud?
regexp javascript (7)
Quiero identificar cadenas que se componen exclusivamente de grupos de caracteres de la misma longitud. Cada uno de estos grupos consta de al menos dos personajes idénticos. Entonces, aquí hay algunos ejemplos:
aabbcc true
abbccaa false
xxxrrrruuu false (too many r''s)
xxxxxfffff true
aa true (shortest possible positive example)
aabbbbcc true // I added this later to clarify my intention
@ilkkachu: Gracias por su comentario sobre la repetición del mismo grupo de personajes. He añadido el ejemplo anterior. Sí, quiero que la última muestra se pruebe como verdadera: una cadena formada por los dos grupos de letras aa, bb, bb, cc
.
¿Existe una forma sencilla de aplicar esta condición de verificación en una cadena usando expresiones regulares y JavaScript?
Mi primer intento fue hacer algo como
var strarr=[''aabbcc'',''abbccaa'',''xxxrrrruuu'',
''xxxxxfffff'',''aa'',''negative''];
var rx=/^((.)/2+)+$/;
console.log(strarr.map(str=>str+'': ''+!!str.match(rx)).join(''/n''));
Busca grupos de caracteres repetidos, pero aún no presta atención a estos grupos que tienen la misma longitud , como muestra la salida:
aabbcc: true
abbccaa: false
xxxrrrruuu: true // should be false!
xxxxxfffff: true
aa: true
aabbbbcc: true
negative: false
¿Cómo obtengo el cheque para buscar grupos de caracteres de la misma longitud ?
Aquí hay uno que corre en tiempo lineal:
function test(str) {
if (str.length === 0) return true;
let lastChar = str.charAt(0);
let seqLength = 1;
let lastSeqLength = null;
for (let i = 1; i < str.length; i++) {
if (str.charAt(i) === lastChar) {
seqLength++;
}
else if (lastSeqLength === null || seqLength === lastSeqLength) {
lastSeqLength = seqLength;
seqLength = 1;
lastChar = str.charAt(i);
}
else {
return false;
}
}
return (lastSeqLength === null || lastSeqLength === seqLength);
}
Dado que las expresiones regulares nunca han sido mi fuerte, aquí hay un enfoque que utiliza String#replace()
para agregar un delimitador a la cadena en el cambio de letra y luego usarlo para dividir en la matriz y verificar que todos los elementos en la matriz tengan la misma longitud
const values = [''aabbcc'', ''abbccaa'', ''xxxrrrruuu'', ''xxxxxfffff'', ''aa''];
const expect = [true, false, false, true, true];
const hasMatchingGroups = (str) => {
if(!str || str.length %2) return false;
const groups = str.replace(/[a-z]/g,(match, offset, string) => {
return string[offset + 1] && match !== string[offset + 1] ? match + ''|'' : match;
}).split(''|'');
return groups.every(s => s.length === groups[0].length)
}
values.forEach((s, i) => console.log(JSON.stringify([s,hasMatchingGroups(s), expect[i]])))
Dado que los requisitos cambiaron o no estaban claros, ya que esta es la tercera solución que estoy publicando. Para aceptar cadenas que podrían dividirse en grupos más pequeños como aabbbb
, podríamos:
- Encuentra todas las longitudes de todos los caracteres diferentes que son
2
y4
en este caso. - Empújalos en una matriz llamada
d
. - Encuentra la longitud más baja en el conjunto llamado
m
. - Compruebe si todos los valores en
d
no tienen resto cuando se divide porm
Manifestación
var words = [''aabbbcccdddd'', ''abbccaa'', ''xxxrrrruuu'', ''xxxxxfffff'', ''aab'', ''aabbbbccc''];
words.forEach(w => {
var d = [], m = Number.MAX_SAFE_INTEGER;
var s = w.replace(/(.)/1+/gy, x => {
d.push(l = x.length);
if (l < m) m = l;
return '''';
});
console.log(w + " => " + (s == '''' && !d.some(n => n % m != 0)));
});
La longitud del patrón repetido de los mismos caracteres debe especificarse dentro de la expresión regular. El siguiente fragmento de código crea expresiones regulares que buscan longitudes de cadena de 11 a 2. El ciclo for se sale una vez que se encuentra una coincidencia y la función devuelve la longitud del patrón encontrado:
function pat1(s){
for (var i=10;i;i--)
if(RegExp(''^((.)//2{''+i+''})+$'').exec(s))
return i+1;
return false;}
Si no se encuentra nada, se devuelve false
.
Si no se requiere la longitud del patrón, la expresión regular también se puede configurar de una sola vez (sin la necesidad del bucle for a su alrededor):
function pat2(s){
var rx=/^((.)/2)+$|^((.)/4{2})+$|^((.)/6{4})+$|^((.)/8{6})+$/;
return !!rx.exec(s);
}
Aquí están los resultados de ambas pruebas:
console.log(strarr.map(str=>
str+'': ''+pat1(str)
+'' ''+pat2(str)).join(''/n'')+''/n'');
aabbcc: 2 true
abbccaa: false false
xxxrrrruuu: false false
xxxxxfffff: 5 true
aa: 2 true
aabbbbcc: 2 true
negative: false false
La expresión regular en pat2 busca solo ciertos recuentos de repetición. Cuando se encuentran 1, 2, 4 o 6 repeticiones de un carácter anterior, el resultado es positivo. Los patrones encontrados tienen longitudes de 2,3,5 o 7 caracteres (¡números primos!). Con estas comprobaciones de longitud, cualquier longitud de patrón divisible por uno de estos números se considerará positiva (2,3,4,5,6,7,8,9,10,12,14,15,16,18,20). , 21,22,24, ...).
Otra solución sería usar replace()
junto con test()
. El primero reemplaza diferentes caracteres con su longitud correspondiente y el segundo busca los mismos números repetidos en la cadena anterior:
var str = ''aabbc'';
/^(/d+/n)/1*$/.test(str.replace(/(.)/1+/gy, x => x.length + ''/n''));
Manifestación:
var words = [''aabbcc'', ''abbccaa'', ''xxxrrrruuu'', ''xxxxxfffff'', ''aa''];
words.forEach(w =>
console.log(/^(/d+/n)/1*$/.test(w.replace(/(.)/1+/gy, x => x.length + ''/n'')))
);
Para obtener todos los grupos del mismo personaje tiene una solución regex fácil:
/(.)/1*/g
Solo repitiendo la referencia inversa /1
del personaje en el grupo de captura 1.
Luego simplemente verifica si hay una longitud en la matriz de cadenas de caracteres iguales que no coincida.
Fragmento de ejemplo:
function sameLengthCharGroups(str)
{
if(!str) return false;
let arr = str.match(/(.)/1*/g) //array with same character strings
.map(function(x){return x.length}); //array with lengths
let smallest_length = arr.reduce(function(x,y){return x < y ? x : y});
if(smallest_length === 1) return false;
return arr.some(function(n){return (n % smallest_length) !== 0}) == false;
}
console.log("-- Should be true :");
let arr = [''aabbcc'',''xxxxxfffff'',''aa''];
arr.forEach(function(s){console.log(sameLengthCharGroups(s)+'' : ''+ s)});
console.log("-- Should also be true :");
arr = [''aabbbbcc'',''224444'',''444422'',
''666666224444666666'',''666666444422'',''999999999666666333''];
arr.forEach(function(s){console.log(sameLengthCharGroups(s)+'' : ''+ s)});
console.log("-- Should be false :");
arr = [''abbcc'',''xxxrrrruuu'',''a'',''ab'','''',undefined];
arr.forEach(function(s){console.log(sameLengthCharGroups(s)+'' : ''+ s)});
Versión ECMAScript 6 con flechas gruesas (no funciona en IE)
function sameLengthCharGroups(str)
{
if(!str) return false;
let arr = str.match(/(.)/1*/g)
.map((x) => x.length);
let smallest_length = arr.reduce((x,y) => x < y ? x : y);
if(smallest_length === 1) return false;
return arr.some((n) => (n % smallest_length) !== 0) == false;
}
O usar exec en lugar de una coincidencia, que debería ser más rápido para cadenas grandes
Ya que puede salir del bucle while tan pronto como se encuentre una longitud diferente.
Pero esto tiene la desventaja de que de esta manera no puede obtener la longitud mínima de TODAS las longitudes antes de compararlas.
Por lo tanto, aquellos con la longitud mínima al final no se pueden encontrar como OK de esta manera.
function sameLengthCharGroups(str)
{
if(!str) return false;
const re = /(.)/1*/g;
let m, smallest_length;
while(m = re.exec(str)){
if(m.index === 0) {smallest_length = m[0].length}
if(smallest_length > m[0].length && smallest_length % m[0].length === 0){smallest_length = m[0].length}
if(m[0].length === 1 ||
// m[0].length !== smallest_length
(m[0].length % smallest_length) !== 0
) return false;
}
return true;
}
console.log("-- Should be true :");
let arr = [''aabbcc'',''xxxxxfffff'',''aa''];
arr.forEach(function(s){console.log(sameLengthCharGroups(s)+'' : ''+ s)});
console.log("-- Should also be true :");
arr = [''aabbbbcc'',''224444'',''444422'',
''666666224444666666'',''666666444422'',''999999999666666333''];
arr.forEach(function(s){console.log(sameLengthCharGroups(s)+'' : ''+ s)});
console.log("-- Should be false :");
arr = [''abbcc'',''xxxrrrruuu'',''a'',''ab'','''',undefined];
arr.forEach(function(s){console.log(sameLengthCharGroups(s)+'' : ''+ s)});
Usando el método de bandera adhesiva y replace
, podría hacer esto mucho más rápido. Este truco reemplaza las ocurrencias de la longitud del primero con una cadena vacía (y se detiene tan pronto como ocurre una ocurrencia con una longitud diferente) y luego verifica si quedan algunos caracteres:
var words = [''aabbcc'', ''abbccaa'', ''xxxrrrruuu'', ''xxxxxfffff'', ''aa''];
words.forEach(w => {
console.log(w + " => " + (w.replace(/(.)/1+/gy, ($0, $1, o) => {
return $0.length == (o == 0 ? l = $0.length : l) ? '''' : $0;
}).length < 1));
});