Регулярное выражение, чтобы соответствовать 1 или два символа в наборе, и убедитесь, что они разные

Ладно, банда, вот моя загадка:

Я ищу соответствие строки, используя ванильный JavaScript test(), функция от RegExp прототип, чтобы проверить входную переменную inp:

/{CONDITION}/.test(inp)

Строка должна соответствовать следующим условиям:

  • Это может быть один или два символа длиной. Достаточно просто.

    /^*{1,2}$/.test(inp)
    
  • Это должно быть без учета регистра. Нет проблем.

    /^*{1,2}$/i.test(inp)
    
  • Если только один символ, он должен состоять только из символов [tmbrcl]

    /^[tmblcr]{1}$/i.test(inp)
    
  • Если два символа длиной, первый символ должен быть [tmb] ИЛИ ЖЕ [lcr]и второе должно быть любого набора, первого нет. Хорошо:

    /^([tmblcr]{1})$|^([tmb]{1}[lcr]{1})|^([lcr]{1}[tmb]{1})$/i.test(inp)
    

Примеры:

't'   // Good
'B'   // Good
'Rc'  // Good
'bl'  // Good
'tb'  // bad
'mm'  // Bad
'cC'  // Bad
'BB'  // Bad
'Bob' // Bad
'5'   // Bad
'Ċ'   // Still Bad
'ß'   // Suspiciously Bad
''  // Now you're just screwing with me
'上'  // You know what? I don't care if this fails gracefully or not. ^&%* you.

Моя цель здесь состоит в том, чтобы разобрать пользовательский ввод, который укажет вертикальное и горизонтальное положение ('T'/'M'/'B' представляющий 'Top'/'Middle'/'Bottom' а также 'L'/'C'/'R' представляющий 'Left'/'Center'/'Right'соответственно). Пользователю должно быть разрешено передавать любую перестановку двух группировок, в любом случае, в любом порядке (или только одну, в этом случае другой выводится по умолчанию).

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

  let errd  = false;
      set1  = 'TMB',
      set2  = 'LCR',
      sets  = set1 + set2;
  if(inp.length === 1 && sets.indexOf(inp) === -1) errd = true;
  else if(inp.length === 2){
      let inpArr = inp.split('');
      errd = (set1.indexOf(inpArr[0]) === set1.indexOf(inpArr[1]) === -1 || set2.indexOf(inpArr[0]) === set2.indexOf(inpArr[1]) === -1);
  }else errd = true;

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

/^[SINGLE (S)]$|^[CASE A/B]$|^[CASE B/A]$/i

Я имею в виду, что если бы было три,

/^[S]$|^[AB]$|^[AC]$|^[BC]$|^[BA]$|^[CA]$|^[CB]$|^[ABC]$|^[ACB]$|^[BAC]$|^[BCA]$|^[CAB]$|^[CBA]$/i

или (боги помогают мне) ЧЕТЫРЕ персонажа с подобным набором ограничений? Я относительно новичок в RegEx, и мне интересно, не хватает ли здесь основного принципа. У меня есть рабочее решение ("/^[S]|[AB]|[BA]$/"версия), но это на самом деле ПРАВИЛЬНЫЙ?

РЕДАКТИРОВАТЬ

Спасибо за звездный, исчерпывающий ответ, Sweeper!

(Вот рабочий код в контексте, на случай, если позже он кому-нибудь поможет):

orient: function(objQS, rPos='TL', offsetWidth=0, offsetHeight=0)  {
    try{

        // objQS accepts a QuerySelector string or an HTMLElement
        let obj = (typeof(objQS) === 'string') ? document.querySelector(objQS) : objQS;
        console.log('obj', obj, obj.getBoundingClientRect())
        if(null == obj || typeof(obj) !== 'object'){ throw('Invalid Target!'); }
        let objBRC = obj.getBoundingClientRect();

        // rPos accepts TL, T/TC, TR, ML, M/C/MC, MR, BL, B/BC, BR (case- and order-insensitive)
        if(!/^(?:[tmbrcl]|[tmb][rcl]|[rcl][tmb])$/i.test(rPos)){ throw('Invalid orientation specified!'); }

        // Accomodate single-character entry of 'm' or 'c', both taken to mean 'mc' ('m'iddle-'c'enter)
        if(/^[mc]$/i.test(rPos)) { rPos = 'mc'; } 

        // Set default orientation to top-left (tl/lt), meaning we have nothing to do for 't'op or 'l'eft
        let osT = objBRC.y + offsetHeight,                       // Note we add the user-set offsets to our bases
            osL = objBRC.x + offsetWidth;                        // so they carry though to the other options.
        if(/m/i.test(rPos))      { osT += (objBRC.height / 2); } // Adjust vertically for 'm'iddle (top + height/2)
        if(/b/i.test(rPos))      { osT += objBRC.height; }       // Adjust vertically for 'b'ottom (top + height)
        if(/c/i.test(rPos))      { osL += (objBRC.width / 2); }  // Adjust horizontally for 'c'enter (left + width/2)
        if(/r/i.test(rPos))      { osL += objBRC.width; }        // Adjust horizontally for 'r'ight (left + width)

        objBRC.offsetTop  = osT;
        objBRC.offsetLeft = osL;
        this.place(osL, osT);
        console.log('return', 'objBRC:', objBRC)
        return objBRC;
    }catch(e){
        console.group('ERROR DETAILS (Error in callout.orient)');
        console.error('Error details:\n  - ', e);
        console.groupEnd();
        return false;
    }
}

1 ответ

Решение

Ваше регулярное выражение может быть значительно сокращено до этого:

/^(?:[tmbrcl]|[tmb][rcl]|[rcl][tmb])$/i

который я считаю достаточно хорошим решением. Это читается довольно ясно:

Между началом и концом строки есть три варианта:

  • один из [tmbrcl]
  • один из [tmb] затем один из [rcl]
  • один из [rcl] затем один из [tmb]

Вам на самом деле не нужны все эти {1}s.

РЕДАКТИРОВАТЬ:

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

Одним из способов является это:

  1. Есть одно регулярное выражение для каждого из наборов:

    var r1 = /[abc]/i // notice the missing ^ and $ anchors
    var r2 = /[def]/i
    var r3 = /[ghi]/i
    
  2. Поместите их все в массив

    var regexes = [r1, r2, r3]
    
  3. Переберите массив и посчитайте, сколько регулярных выражений соответствуют строке

  4. Количество регулярных выражений, соответствующих строке, должно быть равно длине строки.

Обратите внимание, что это предполагает, что ваши множества не пересекаются.

Другие вопросы по тегам