розділити рядок лише на першому екземплярі вказаного символу


271

У своєму коді я розділив рядок на основі _та захоплюю другий елемент у масиві.

var element = $(this).attr('class');
var field = element.split('_')[1];

Бере good_luckта забезпечує мене luck. Чудово працює!

Але зараз у мене клас, який схожий good_luck_buddy. Як змусити Javascript проігнорувати другий _і подати мені luck_buddy?

Я знайшов це var field = element.split(new char [] {'_'}, 2);у відповіді ac # stackoverflow, але це не працює. Я спробував це на jsFiddle ...

Відповіді:


406

Використовуйте захоплення дужок :

"good_luck_buddy".split(/_(.+)/)[1]
"luck_buddy"

Вони визначаються як

Якщо separatorмістять схожі дужки, відповідні результати повертаються в масив.

Тож у цьому випадку ми хочемо розділити на _.+(тобто роздільник розділення є підрядним рядком, починаючи з _), але також нехай результат містить деяку частину нашого роздільника (тобто все після _).

У цьому прикладі наш роздільник (узгодження _(.+)) є, _luck_buddyа захоплена група (всередині роздільника) є lucky_buddy. Без дужки захоплення luck_buddy(зіставлення .+) не було б включено в масив результатів, як це буває в простому випадку, splitколи роздільники не включаються в результат.


21
Вам навіть не потрібно (?), Просто використовуйте /_(.+)/, щоб зафіксувати ще 1 символу після першого _
Марк

3
Дуже елегантний. Працює як шарм. Дякую.
Ofeargall

12
Щоб було зрозуміло, причина цього рішення працює в тому, що все після першого _збігається всередині групи захоплення і з цієї причини додається до списку токенів.
Алан Мур

28
Хтось знає, чому я отримую додатковий порожній рядок з цим: in: "Aspect Ratio: 16:9".split(/:(.+)/)out:["Aspect Ratio", " 16:9", ""]
katy lavallee

4
@katylavallee - Це може допомогти: stackoverflow.com/questions/12836062/… Оскільки роздільник є ": 16:9", після сепаратора нічого немає, таким чином створюючи порожній рядок в кінці.
Дерек 朕 會 功夫

231

Для чого потрібні регулярні вирази та масиви?

myString = myString.substring(myString.indexOf('_')+1)

var myString= "hello_there_how_are_you"
myString = myString.substring(myString.indexOf('_')+1)
console.log(myString)


5
рядок! == Рядок javascript відрізняється від регістру.
kennebec

3
Я думаю, що це найкраща відповідь. також можна отримати рядок після другого _, написавши:myString.substring( myString.indexOf('_', myString().indexOf('_') + 1) + 1 )
muratgozel

9
Відповідь виводить другу частину рядка. Що робити, якщо ви хочете і першої частини? З var str = "good_luck_buddy", res = str.split(/_(.+)/);вами ви отримуєте всі частини:console.log(res[0]); console.log(res[1]);
Нд

1
@PeterLeger хай спліт = [ string.substring(0, string.indexOf(options.divider)), string.substring(string.indexOf(options.divider) + 1) ]Там у вас є. Також з підтримкою змінної голки
Steffan


36

Я уникаю RegExp будь-якою ціною. Ось ще одна річ, яку ви можете зробити:

"good_luck_buddy".split('_').slice(1).join('_')

18
Той, хто боїться RegExp, ніколи не може сказати, наскільки великий RegExp. Вам потрібно знайти двері самостійно. Опинившись, ти ніколи не озиратимешся. Запроси мене знову через кілька років, і ти скажеш мені, наскільки це здорово.
Крістіан Вестербек

3
@yonas Візьміть червону таблетку!
frnhr

2
@yonas Так, візьми червону таблетку! Це зробить ваше життя швидшим, навіть для коротких рядків: jsperf.com/split-by-first-colon
Джуліан Ф. Вайнерт

15
Га! Я написав цей коментар 4+ років тому. Я напевно зараз на борту з RegExp! :)
йона

2
@yonas, ти краще не роби. RegExp є приголомшливим, коли вам це потрібно . Тут не так. Перевірте оновлений тест: jsperf.com/split-by-first-colon/2
metalim

11

Замініть перший екземпляр унікальним заповнювачем, а потім розділіть звідти.

"good_luck_buddy".replace(/\_/,'&').split('&')

["good","luck_buddy"]

Це корисніше, коли потрібні обидві сторони розколу.


2
Це ставить непотрібне обмеження на рядок.
Ян Фото

Ця відповідь спрацювала для мене, коли всі перераховані вище відповіді не відповіли.
GuitarViking

1
@YanFoто ви маєте на увазі, використовуючи "&"? Це може бути що завгодно.
sebjwallace

2
@sebjwallace Що б ви не вибрали, це означає, що ви не можете мати цього символу в рядку. Наприклад, "fish & chips_are_great" дає [fish, чіпси, are_great] Я думаю.
Джо

@Joe Ви можете використовувати що-небудь замість "&" - це був лише приклад. Ви можете замінити перше виникнення _ на ¬, якщо хочете. Тож "fish & chips_are_great" замінить перше виникнення _ на ¬, щоб дати "fish & chips¬are_great", а потім розділити на ¬, щоб отримати ["fish & chips", "are_great"]
sebjwallace

8

Ви можете використовувати регулярний вираз на зразок:

var arr = element.split(/_(.*)/)
Можна використовувати другий параметр, який визначає межу розбиття. тобто: var field = element.split ('_', 1) [1];

6
Це лише визначає, скільки повернених елементів повертається, а не скільки разів він розбивається. 'good_luck_buddy'.split('_', 1);повертається просто['good']
Алекс Відал

Спасибі зробили припущення щодо цього. Оновлено публікацію, щоб використовувати регулярний вираз.
Чанду

Чи (:?.*)повинна була бути група, яка не захоплює? Якщо так, так і має бути (?:.*), але якщо ви виправите, ви виявите, що це більше не працює. (:?.*)збігається з необов'язковим, :після якого нуль або більше будь-якого символу. Це рішення закінчується роботою з тієї ж причини @ MarkF's: все після першого _додається до списку токенів, оскільки воно було узгоджене у групі захоплення. (Також gмодифікатор не впливає при використанні в розділеному регулярному вираженні.)
Алан Мур

Дякую, не усвідомлював цього. Оновили Regex і спробували його за пару сценаріїв ...
Чанду

1
Це не працює в ie8, і я переключаюсь назад на indexOf та substring
Ігор Алексєєв

5

На сьогоднішній день String.prototype.splitдійсно дозволяє обмежити кількість розщеплень.

str.split([separator[, limit]])

...

ліміт необов’язковий

Невід'ємне ціле число, що обмежує кількість розщеплень. Якщо це передбачено, розбиває рядок при кожному виникненні зазначеного роздільника, але зупиняється, коли в масив розміщуються обмежені записи. Будь-який текст, що залишився, взагалі не входить до масиву.

Масив може містити менше записів, ніж обмеження, якщо кінець рядка досягнуто до досягнення межі. Якщо межа дорівнює 0, розщеплення не проводиться.

застереження

Це може не працювати так, як ви очікуєте. Я сподівався, що він просто ігнорує решту роздільників, але замість цього, коли вона досягне межі, вона знову розщепить решту рядка, опустивши частину після розщеплення результатів повернення.

let str = 'A_B_C_D_E'
const limit_2 = str.split('_', 2)
limit_2
(2) ["A", "B"]
const limit_3 = str.split('_', 3)
limit_3
(3) ["A", "B", "C"]

Я сподівався на:

let str = 'A_B_C_D_E'
const limit_2 = str.split('_', 2)
limit_2
(2) ["A", "B_C_D_E"]
const limit_3 = str.split('_', 3)
limit_3
(3) ["A", "B", "C_D_E"]

Те ж саме. Схоже, PHP розщеплюється на "перше" та "решта".
BananaAcid

5

Це рішення спрацювало на мене

var str = "good_luck_buddy";
var index = str.indexOf('_');
var arr = [str.slice(0, index), str.slice(index + 1)];

//arr[0] = "good"
//arr[1] = "luck_buddy"

АБО

var str = "good_luck_buddy";
var index = str.indexOf('_');
var [first, second] = [str.slice(0, index), str.slice(index + 1)];

//first = "good"
//second = "luck_buddy"

Однак це не працює, якщо роздільник має більше 1 символу.
хайкам

4

На String.splitжаль, JavaScript не має можливості обмежити фактичну кількість розбитків. У ній є другий аргумент, який вказує, скільки реальних повернених елементів повертається, що не корисно у вашому випадку. Рішенням було б розділити рядок, зсунути перший елемент, а потім знову приєднатись до решти елементів:

var element = $(this).attr('class');
var parts = element.split('_');

parts.shift(); // removes the first item from the array
var field = parts.join('_');

Я бачу, що функція розбиття не допомагає, але, мабуть, використовуючи регулярний вираз, досягти цього. Слід вказати, що ви посилаєтесь на саму функцію "Спліт".
Ден Хенлі

1
Цікаво, що це рішення відводить проблему до більш зрозумілого / керованого рішення. У моєму випадку перетворення повного імені в перше і останнє (так, наші вимоги змусили цю логіку) це рішення працювало найкраще і було більш читабельним, ніж інші. Спасибі
Сукіма

Це вже не вірно :)
Кракен

3

Мені потрібні дві частини струни, тож, зворотній вигляд ззаду допоможе мені в цьому.

const full_name = 'Maria do Bairro';
const [first_name, last_name] = full_name.split(/(?<=^[^ ]+) /);
console.log(first_name);
console.log(last_name);


3

За допомогою руйнування завдання може бути більш читабельним:

let [first, ...rest] = "good_luck_buddy".split('_')
rest = rest.join('_')

2

Найшвидше рішення?

Я провів деякі орієнтири , і це рішення виграло надзвичайно: 1

str.slice(str.indexOf(delim) + delim.length)

// as function
function gobbleStart(str, delim) {
    return str.slice(str.indexOf(delim) + delim.length);
}

// as polyfill
String.prototype.gobbleStart = function(delim) {
    return this.slice(this.indexOf(delim) + delim.length);
};

Порівняння продуктивності з іншими рішеннями

Єдиним близьким суперником був той самий рядок коду, за винятком використання substrзамість slice.

Інші рішення, які я спробував включити splitабо RegExpвзяли великий удар, і були приблизно на 2 порядки повільніше. Використання joinрезультатів split, звичайно, додає додатковий штрафний показник.

Чому вони повільніші? Кожен раз, коли має бути створений новий об’єкт або масив, JS повинен запитати шматок пам'яті в ОС. Цей процес відбувається дуже повільно.

Ось декілька загальних рекомендацій, якщо ви переслідуєте орієнтири:

  • Нові динамічні розподіли пам’яті для об’єктів {}або масивів [](на зразок того, який splitстворюється) коштуватимуть дорого у продуктивності.
  • RegExp пошук складніший і, отже, повільніший, ніж рядковий пошук.
  • Якщо у вас вже є масив, деструктивні масиви проходять так само швидко, як явно їх індексувати, і виглядають приголомшливо.

Виведення за межі першої інстанції

Ось рішення, яке розріже до і включить n-й екземпляр. Це не зовсім так швидко, але на питання , що ОП, в gobble(element, '_', 1)ще> 2 рази швидше , ніж RegExpабо splitрозчину і може зробити більше:

/*
`gobble`, given a positive, non-zero `limit`, deletes
characters from the beginning of `haystack` until `needle` has
been encountered and deleted `limit` times or no more instances
of `needle` exist; then it returns what remains. If `limit` is
zero or negative, delete from the beginning only until `-(limit)`
occurrences or less of `needle` remain.
*/
function gobble(haystack, needle, limit = 0) {
  let remain = limit;
  if (limit <= 0) { // set remain to count of delim - num to leave
    let i = 0;
    while (i < haystack.length) {
      const found = haystack.indexOf(needle, i);
      if (found === -1) {
        break;
      }
      remain++;
      i = found + needle.length;
    }
  }

  let i = 0;
  while (remain > 0) {
    const found = haystack.indexOf(needle, i);
    if (found === -1) {
      break;
    }
    remain--;
    i = found + needle.length;
  }
  return haystack.slice(i);
}

З вищенаведеним визначенням, gobble('path/to/file.txt', '/')дав би ім'я файлу та gobble('prefix_category_item', '_', 1)видалив би префікс, як і перше рішення у цій відповіді.


  1. Тести проводилися в Chrome 70.0.3538.110 на macOSX 10.14.

Давай ... Настає 2019 рік ... Чи справді люди там все-таки мікробензують подібні речі?
Віктор Шредер

Я згоден. Хоча мікробенчмаркінг трохи цікавий, для оптимізації слід покластися на компілятора чи перекладача. Хто знає. Мб, хто читає це, створює компілятор або використовує ejs / embedded і не може використовувати регулярний вираз. Однак для мого конкретного випадку це виглядає приємніше, ніж регулярний вираз. (Я б
зняв

1

Рішення Mark F є приголомшливим, але воно не підтримується старими браузерами. Рішення Кеннебека є приголомшливим та підтримується старими браузерами, але не підтримує регулярний вираз.

Отже, якщо ви шукаєте рішення, яке розбиває рядок лише один раз, яке підтримується старими браузерами та підтримує регулярний вираз, ось моє рішення:

String.prototype.splitOnce = function(regex)
{
    var match = this.match(regex);
    if(match)
    {
        var match_i = this.indexOf(match[0]);
        
        return [this.substring(0, match_i),
        this.substring(match_i + match[0].length)];
    }
    else
    { return [this, ""]; }
}

var str = "something/////another thing///again";

alert(str.splitOnce(/\/+/)[1]);


1

Для початківців, як я, які не звикли до регулярного вираження, це рішення вирішило:

   var field = "Good_Luck_Buddy";
   var newString = field.slice( field.indexOf("_")+1 );

метод slice () витягує частину рядка і повертає нову рядок, а метод indexOf () повертає позицію першого знайденого зустрічання заданого значення в рядку.


Це не обхід, але правильний спосіб зробити це;)
Віктор Шредер

1

Використовуйте рядовий replace()метод з регулярним виразом :

var result = "good_luck_buddy".replace(/.*?_/, "");
console.log(result);

Цей регулярний вираз відповідає 0 або більше символів перед першим _і _самим. Потім відповідність замінюється порожнім рядком.


document.body.innerHTMLЧастина тут абсолютно марно.
Віктор Шредер

@ VictorSchröder, як ви очікуєте побачити вихід фрагмента без document.body.innerHTML?
Джеймс Т

1
document.bodyЗалежить від присутності DOM, і він не працюватиме в чистому середовищі JavaScript. console.logдостатньо для цієї мети або просто залишити результат у змінній для перевірки.
Віктор Шредер

@ VictorSchröder Я не думаю, що це спричинило б велику плутанину, але я все-таки редагував.
Джеймс T

0

Це працювало для мене на Chrome + FF:

"foo=bar=beer".split(/^[^=]+=/)[1] // "bar=beer"
"foo==".split(/^[^=]+=/)[1] // "="
"foo=".split(/^[^=]+=/)[1] // ""
"foo".split(/^[^=]+=/)[1] // undefined

Якщо вам також потрібен ключ, спробуйте це:

"foo=bar=beer".split(/^([^=]+)=/) // Array [ "", "foo", "bar=beer" ]
"foo==".split(/^([^=]+)=/) // [ "", "foo", "=" ]
"foo=".split(/^([^=]+)=/) // [ "", "foo", "" ]
"foo".split(/^([^=]+)=/) // [ "foo" ]

//[0] = ignored (holds the string when there's no =, empty otherwise)
//[1] = hold the key (if any)
//[2] = hold the value (if any)

0

Ось один RegExp, який робить трюк.

'good_luck_buddy' . split(/^.*?_/)[1] 

По-перше, це змушує матч розпочати з початку з «^». Тоді вона відповідає будь-якій кількості символів, яка не є "_", іншими словами всіма символами до першого "_".

"?" означає, що мінімальна кількість символів, за якими вся відповідність візерунка узгоджується зі знаком ". *?" тому що за ним слідує "_", яке потім включається в матч як його останній символ.

Тому цей спліт () використовує таку частину, що відповідає, як "спліттер" і вилучає її з результатів. Таким чином, він видаляє все до, включаючи перший '_' і дає решту як другий елемент результату. Перший елемент "" представляє частину перед узгодженою частиною. Це тому, що матч починається з початку.

Є й інші RegExps, які так само працюють, як і /_(.*)/, надані Чанду в попередній відповіді.

/^.*?_/ має перевагу в тому, що ви можете зрозуміти, що він робить, не знаючи про особливу роль, яку групи захоплення грають із заміною ().

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.