Как проверить, состоит ли строка только из групп символов одинаковой длины?
Я хочу определить строки, которые состоят исключительно из групп символов одинаковой длины. Каждая из этих групп состоит по крайней мере из двух одинаковых символов. Итак, вот несколько примеров:
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: Спасибо за ваше замечание относительно повторения той же группы персонажей. Я добавил пример выше. Да, я хочу, чтобы последний образец был проверен как истинный: строка, состоящая из двух буквенных групп aa, bb, bb, cc
.
Есть ли простой способ применить это условие - проверить строку, используя регулярные выражения и JavaScript?
Моя первая попытка состояла в том, чтобы сделать что-то вроде
var strarr=['aabbcc','abbccaa','xxxrrrruuu',
'xxxxxfffff','aa','negative'];
var rx=/^((.)\2+)+$/;
console.log(strarr.map(str=>str+': '+!!str.match(rx)).join('\n'));
Он ищет группы повторяющихся символов, но еще не обращает внимания на то, что эти группы имеют одинаковую длину, как показывает результат:
aabbcc: true
abbccaa: false
xxxrrrruuu: true // should be false!
xxxxxfffff: true
aa: true
aabbbbcc: true
negative: false
Как получить проверку для поиска групп символов одинаковой длины?
Ответы
Ответ 1
Для получения всех групп одного и того же символа имеет простое регулярное выражение:
/(.)\1*/g
Просто повторим обратную ссылку \1
персонажа в группе захвата 1.
Затем просто проверьте, есть ли длина в массиве одинаковых строк символов, которые не совпадают.
Пример фрагмента:
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)});
Ответ 2
Здесь тот, который работает в линейном времени:
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);
}
Ответ 3
Используя липкий флаг y
и метод replace
вы можете сделать это намного быстрее. Этот трюк заменяет вхождения первой длины пустой строкой (и останавливается, как только происходит различная длина), а затем проверяет, остались ли некоторые символы:
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));
});
Ответ 4
Другим обходным решением будет использование replace()
вместе с test()
. Первый заменяет разные символы соответствующей длиной, а второй ищет одинаковые повторяющиеся числа в предыдущей строке:
var str = 'aabbc';
/^(\d+\n)\1*$/.test(str.replace(/(.)\1+/gy, x => x.length + '\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')))
);
Ответ 5
Поскольку требования изменились или были непонятны, как сейчас, это третье решение, которое я публикую. Чтобы принять строки, которые можно разделить на более мелкие группы, такие как aabbbb
мы могли бы:
- Найти все длины всех разных символов, которые в этом случае равны
2
и 4
. - Вставьте их в массив с именем
d
. - Найдите наименьшую длину в наборе с именем
m
. - Убедитесь, что все значения в
d
имеют остатка при делении на m
демонстрация
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)));
});
Ответ 6
Длина повторяющегося шаблона того же символа должна быть указана в пределах регулярного выражения. Следующий фрагмент создает регулярные выражения, которые ищут строки длиной от 11 до 2. За цикл вызывается после совпадения, и функция возвращает длину найденного шаблона:
function pat1(s){
for (var i=10;i;i--)
if(RegExp('^((.)\\2{'+i+'})+$').exec(s))
return i+1;
return false;}
Если ничего не найдено, возвращается false
.
Если длина шаблона не требуется, регулярное выражение также может быть установлено за один раз (без необходимости цикла for вокруг него):
function pat2(s){
var rx=/^((.)\2)+$|^((.)\4{2})+$|^((.)\6{4})+$|^((.)\8{6})+$/;
return !!rx.exec(s);
}
Вот результаты обоих тестов:
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
Регулярное выражение в pat2 ищет определенные числа повторений. Когда найдено 1, 2, 4 или 6 повторений предыдущего символа, результат будет положительным. Найденные шаблоны имеют длину 2,3,5 или 7 символов (простые числа!). При таких длинных проверках любая длина образца, разделяемая одним из этих чисел, будет найдена как положительная (2,3,4,5,6,7,8,9,10,12,14,15,16,18,20, 21,22,24,...).
Ответ 7
Поскольку regex никогда не был моим forte здесь, используйте String#replace()
чтобы добавить разделитель в строку при изменении буквы, а затем использовать это для разделения на массив и проверить, что все элементы массива имеют одинаковую длину
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]])))