ciąg podzielony tylko na pierwszą instancję określonego znaku

271

W moim kodzie dzielę ciąg na podstawie _drugiego elementu tablicy i chwytam go.

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

Bierze good_lucki zapewnia mi luck. Działa świetnie!

Ale teraz mam klasę, która wygląda good_luck_buddy. Jak sprawić, by mój skrypt javascript zignorował drugą _i dał mi luck_buddy?

Znalazłem to var field = element.split(new char [] {'_'}, 2);w odpowiedzi ac # stackoverflow, ale to nie działa. Próbowałem tego w jsFiddle ...

Ofeargall
źródło

Odpowiedzi:

406

Użyj przechwytywania nawiasów :

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

Są one zdefiniowane jako

Jeśli separatorzawiera przechwytywanie nawiasów, dopasowane wyniki są zwracane w tablicy.

Tak więc w tym przypadku chcemy podzielić na _.+(tzn. Separator rozdzielający będący podłańcuchem rozpoczynającym się od _), ale również pozwolić, aby wynik zawierał część naszego separatora (tj. Wszystko po _).

W tym przykładzie naszym separatorem (dopasowaniem _(.+)) jest, _luck_buddya przechwycona grupa (w obrębie separatora) lucky_buddy. Bez nawiasów przechwytujących luck_buddy(dopasowanie .+) nie zostałoby uwzględnione w tablicy wyników, ponieważ w przypadku zwykłego splitseparatora nie ma wyniku.

znak
źródło
21
Nie potrzebujesz nawet (?), Po prostu użyj /_(.+)/, aby przechwycić jeszcze 1 więcej znaków po pierwszym _
Mark
3
Bardzo elegancko. Działa jak marzenie. Dziękuję Ci.
Ofeargall
12
Dla jasności, to rozwiązanie działa, ponieważ wszystko po pierwszym _dopasowuje się do grupy przechwytywania i z tego powodu zostaje dodane do listy tokenów.
Alan Moore,
28
Każdy wie, dlaczego dostaję dodatkowy pusty ciąg znaków z tym: in: "Aspect Ratio: 16:9".split(/:(.+)/)out:["Aspect Ratio", " 16:9", ""]
katy lavallee
4
@katylavallee - To może pomóc: stackoverflow.com/questions/12836062/... Ponieważ separator jest ": 16:9", po separatorze nie ma nic, co tworzy pusty łańcuch na końcu.
Derek 朕 會 功夫
231

Do czego potrzebujesz wyrażeń regularnych i tablic?

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

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

Kennebec
źródło
5
ciąg! == Ciąg. javascript rozróżnia małe i wielkie litery.
kennebec 20.04.16
3
myślę, że to najlepsza odpowiedź. możliwe jest również uzyskanie ciągu po drugim _, pisząc:myString.substring( myString.indexOf('_', myString().indexOf('_') + 1) + 1 )
muratgozel
9
Odpowiedź wysyła drugą część ciągu. Co jeśli chcesz też pierwszą część? Otrzymasz var str = "good_luck_buddy", res = str.split(/_(.+)/);wszystkie części:console.log(res[0]); console.log(res[1]);
niedz.
1
@PeterLeger let split = [ string.substring(0, string.indexOf(options.divider)), string.substring(string.indexOf(options.divider) + 1) ]Tam masz. Również ze wsparciem zmiennej igły
Steffan
To jest geniusz!
Stuckedoverflow
36

Za wszelką cenę unikam RegExp. Oto kolejna rzecz, którą możesz zrobić:

"good_luck_buddy".split('_').slice(1).join('_')
yonas
źródło
18
Tego, kto boi się RegExp, nigdy nie można powiedzieć, jak wspaniały jest RegExp. Musisz sam znaleźć drzwi. Gdy już tam będziesz, nigdy nie będziesz oglądać się za siebie. Zapytaj mnie ponownie za kilka lat, a powiesz mé, jak wspaniale.
Christiaan Westerbeek,
3
@yonas Weź czerwoną pigułkę!
frnhr
2
@yonas Tak, weź czerwoną pigułkę! Sprawi,
Julian F. Weinert
15
Ha! Napisałem ten komentarz ponad 4 lata temu. Zdecydowanie jestem teraz na pokładzie RegExp! :)
yonas,
2
@yonas lepiej nie. RegExp jest niesamowity, gdy go potrzebujesz . Nie w tym przypadku. Sprawdź zaktualizowany test: jsperf.com/split-by-first-colon/2
metalim
11

Zamień pierwszą instancję na unikalny symbol zastępczy, a następnie podziel stamtąd.

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

["good","luck_buddy"]

Jest to bardziej przydatne, gdy potrzebne są obie strony podziału.

sebjwallace
źródło
2
To nakłada niepotrzebne ograniczenie na ciąg.
Yan Foto
Ta odpowiedź działała na mnie, gdy wszystkie powyższe odpowiedzi nie działały.
GuitarViking
1
@YanFoto masz na myśli, używając „&”? To może być cokolwiek.
sebjwallace
2
@sebjwallace Cokolwiek wybierzesz, oznacza to, że nie możesz mieć tego znaku w ciągu. Np. „Fish & chips_are_great” daje [fish, chips, are_great] Myślę, że.
Joe
@Joe Możesz użyć czegokolwiek zamiast „&” - to był tylko przykład. Możesz zamienić pierwsze wystąpienie _ na ¬, jeśli chcesz. Tak więc „fish & chips_are_great” zastąpiłoby pierwsze wystąpienie _ przez ¬, aby dać „fish & chips¬are_great”, a następnie podzielić przez ¬, aby uzyskać [„fish & chips”, „are_great”]
sebjwallace
8

Możesz użyć wyrażenia regularnego, takiego jak:

var arr = element.split(/_(.*)/)
Możesz użyć drugiego parametru, który określa limit podziału. tj .: var field = element.split ('_', 1) [1];
Chandu
źródło
6
Określa tylko liczbę zwróconych elementów podzielonych, a nie liczbę podziałów. 'good_luck_buddy'.split('_', 1);wraca właśnie['good']
Alex Vidal,
Dzięki założyłem, że tak. Zaktualizowano wpis, aby używał wyrażenia regularnego.
Chandu,
Czy (:?.*)miała to być grupa, która nie mogła schwytać? Jeśli tak, to powinno być (?:.*), ale jeśli to poprawisz, przekonasz się, że to już nie działa. (:?.*)dopasowuje opcjonalny, :po którym następuje zero lub więcej dowolnych znaków. To rozwiązanie działa z tego samego powodu, co robi @ MarkF: wszystko po pierwszym _jest dodawane do listy tokenów, ponieważ zostało dopasowane do grupy przechwytywania. (Ponadto gmodyfikator nie działa, gdy jest stosowany w wyrażeniu regularnym podzielonym.)
Alan Moore
Dzięki, nie zdawałem sobie z tego sprawy. Zaktualizowałem Regex i wypróbowałem go na kilku scenariuszach ...
Chandu,
1
Nie działa w ie8 i przełączam się z powrotem na indexOf i podłańcuch
Igor Alekseev
5

W dzisiejszych czasach String.prototype.splitrzeczywiście pozwala ograniczyć liczbę podziałów.

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

...

limit Opcjonalne

Nieujemna liczba całkowita ograniczająca liczbę podziałów. Jeśli podano, dzieli ciąg przy każdym wystąpieniu określonego separatora, ale zatrzymuje się, gdy w tablicy zostaną umieszczone wpisy limitów. Pozostały tekst nie jest w ogóle uwzględniony w tablicy.

Tablica może zawierać mniej wpisów niż limit, jeśli koniec łańcucha zostanie osiągnięty przed osiągnięciem limitu. Jeśli limit wynosi 0, podział nie jest wykonywany.

zastrzeżenie

To może nie działać w oczekiwany sposób. Miałem nadzieję, że po prostu zignoruje resztę ograniczników, ale zamiast tego, gdy osiągnie limit, ponownie dzieli pozostały ciąg, pomijając część po podziale z wyników zwracanych.

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

Miałem nadzieję na:

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"]
Kraken
źródło
To samo tutaj. Wygląda na to, że PHP dzieli się na „pierwsze” i „pozostałe”.
BananaAcid
5

To rozwiązanie działało dla mnie

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"

LUB

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"
Darren Lee
źródło
Nie działa to jednak, jeśli rozdzielacz ma więcej niż 1 znak.
haykam
4

Javascript String.splitniestety nie ma możliwości ograniczenia rzeczywistej liczby podziałów. Ma drugi argument, który określa liczbę rzeczywistych podzielonych elementów, co nie jest przydatne w twoim przypadku. Rozwiązaniem byłoby podzielenie łańcucha, przesunięcie pierwszego elementu, a następnie ponowne dołączenie do pozostałych elementów:

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

parts.shift(); // removes the first item from the array
var field = parts.join('_');
Alex Vidal
źródło
Widzę, że funkcja podziału nie pomaga, ale wydaje się, że użycie wyrażenia regularnego pozwala to osiągnąć. Powinien określać, że masz na myśli samą funkcję Split.
Dan Hanly,
1
Ciekawe, że to rozwiązanie ogranicza problem do rozwiązania bardziej czytelnego / zarządzalnego. W moim przypadku zamiany pełnej nazwy na pierwszą i ostatnią (tak, nasze wymagania wymusiły taką logikę) to rozwiązanie działało najlepiej i było bardziej czytelne niż inne. Dzięki
Sukima,
To już nie jest prawda :)
Kraken
3

Potrzebuję dwóch części łańcucha, więc wyrażenie regularne wygląda, jakby mi w tym pomogło.

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

Édipo Costa Rebouças
źródło
3

Przy pomocy przydziału destrukcyjnego może być bardziej czytelny:

let [first, ...rest] = "good_luck_buddy".split('_')
rest = rest.join('_')
ont.rif
źródło
2

Najszybsze rozwiązanie?

Przeprowadziłem kilka testów porównawczych , a to rozwiązanie odniosło ogromny sukces: 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);
};

Porównanie wydajności z innymi rozwiązaniami

Jedynym konkurentem był ten sam wiersz kodu, z wyjątkiem użycia substrzamiast slice.

Inne rozwiązania próbowałem udziałem splitlub RegExps wziął duży spadek wydajności i były o 2 rzędy wielkości wolniejsze. Korzystanie joinz wyników splitoczywiście dodaje dodatkową utratę wydajności.

Dlaczego są wolniejsze? Za każdym razem, gdy trzeba utworzyć nowy obiekt lub tablicę, JS musi zażądać części pamięci od systemu operacyjnego. Ten proces jest bardzo wolny.

Oto kilka ogólnych wskazówek na wypadek, gdy ścigasz testy porównawcze:

  • Nowe dynamiczne alokacje pamięci dla obiektów {}lub tablic [](takich jak te, które splittworzą) będą dużo kosztować wydajność.
  • RegExp wyszukiwanie jest bardziej skomplikowane, a zatem wolniejsze niż wyszukiwanie ciągów.
  • Jeśli masz już tablicę, tablice destrukcyjne są tak samo szybkie, jak ich bezpośrednie indeksowanie i wyglądają świetnie.

Usuwanie poza pierwszą instancją

Oto rozwiązanie, które podzieli na n-tą instancję włącznie. To nie jest tak szybkie, ale na pytanie PO, gobble(element, '_', 1)jest wciąż> 2x szybsze niż rozwiązanie RegExplub spliti może zrobić więcej:

/*
`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);
}

Przy powyższej definicji gobble('path/to/file.txt', '/')podałby nazwę pliku i gobble('prefix_category_item', '_', 1)usunąłby przedrostek jak pierwsze rozwiązanie w tej odpowiedzi.


  1. Testy przeprowadzono w Chrome 70.0.3538.110 na macOSX 10.14.
Chaim Leib Halbert
źródło
Daj spokój ... Jest rok 2019 ... Czy ludzie nadal naprawdę mikrodrukują tego rodzaju rzeczy?
Victor Schröder,
Zgadzam się. Mimo że mikrodrukowanie jest nieco interesujące, w celu optymalizacji należy polegać na kompilatorze lub tłumaczu. Mb ktoś, kto to czyta, buduje kompilator lub używa ejs / embedded i nie może używać wyrażenia regularnego. Jednak wygląda to ładniej w moim konkretnym przypadku niż wyrażenie regularne. (Chciałbym usunąć „najszybsze rozwiązanie”)
TamusJRoyce
1

Rozwiązanie Marka F jest niesamowite, ale nie jest obsługiwane przez stare przeglądarki. Rozwiązanie Kennebec jest niesamowite i obsługiwane przez stare przeglądarki, ale nie obsługuje wyrażenia regularnego.

Tak więc, jeśli szukasz rozwiązania, które dzieli łańcuch tylko raz, które jest obsługiwane przez stare przeglądarki i obsługuje wyrażenie regularne, oto moje rozwiązanie:

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

pmrotule
źródło
1

Dla początkujących, takich jak ja, którzy nie są przyzwyczajeni do wyrażeń regularnych, zadziałało to rozwiązanie obejścia:

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

Metoda slice () wyodrębnia część ciągu i zwraca nowy ciąg, a metoda indexOf () zwraca pozycję pierwszego znalezionego wystąpienia określonej wartości w ciągu.

MZulkarnain Jaranee
źródło
To nie jest obejście, ale właściwy sposób na zrobienie tego;)
Victor Schröder
1

Użyj replace()metody string z wyrażeniem regularnym :

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

Ten wyrażenie regularne dopasowuje 0 lub więcej znaków przed pierwszym _i samym _sobą. Dopasowanie zostanie następnie zastąpione pustym ciągiem.

James T.
źródło
Ta document.body.innerHTMLczęść jest całkowicie bezużyteczna.
Victor Schröder,
@ VictorSchröder, jak można się spodziewać wyjścia fragmentu bez document.body.innerHTML?
James T
1
document.bodyzależy od modelu DOM, który będzie obecny i nie będzie działał w czystym środowisku JavaScript. console.logwystarczy do tego celu lub po prostu pozostaw wynik w zmiennej do kontroli.
Victor Schröder
@ VictorSchröder Nie sądzę, że spowodowałoby to wiele zamieszania, ale mimo to edytowałem.
James T
0

To działało dla mnie w Chrome + FF:

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

Jeśli potrzebujesz również klucza, spróbuj:

"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)
oriadam
źródło
0

Oto jeden RegExp, który załatwia sprawę.

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

Najpierw wymusza rozpoczęcie meczu od początku z „^”. Następnie dopasowuje dowolną liczbę znaków, które nie są „_”, innymi słowy wszystkie znaki przed pierwszym „_”.

„?” oznacza minimalną liczbę znaków, które sprawiają, że cały wzór jest dopasowany, do znaku „. *?” ponieważ po nim następuje „_”, który jest następnie uwzględniany w dopasowaniu jako jego ostatni znak.

Dlatego ten split () używa takiej pasującej części jak „rozdzielacz” i usuwa go z wyników. Usuwa więc wszystko do pierwszego „_” włącznie, a resztę stanowi drugi element wyniku. Pierwszym elementem jest „” reprezentujący część przed dopasowaną częścią. Jest to „”, ponieważ mecz zaczyna się od początku.

Istnieją inne RegExpy, które działają równie dobrze jak /_(.*)/ podane przez Chandu w poprzedniej odpowiedzi.

Zaletą /^.*?_/ jest to, że możesz zrozumieć, co robi bez konieczności posiadania wiedzy na temat specjalnej roli, jaką grupy przechwytują w funkcji replace ().

Panu Logic
źródło