Как проверить, состоит ли строка только из групп символов одинаковой длины?

Я хочу определить строки, которые состоят исключительно из групп символов одинаковой длины. Каждая из этих групп состоит по крайней мере из двух одинаковых символов. Итак, вот несколько примеров:

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]])))