fractionner la chaîne uniquement sur la première instance du caractère spécifié


272

Dans mon code, je scinde une chaîne basée sur _et récupère le deuxième élément du tableau.

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

Prend good_lucket me fournit luck. Fonctionne très bien!

Mais maintenant, j'ai une classe qui ressemble good_luck_buddy. Comment puis-je obtenir mon javascript pour ignorer le second _et me le donner luck_buddy?

J'ai trouvé cela var field = element.split(new char [] {'_'}, 2);dans la réponse ac # stackoverflow mais cela ne fonctionne pas. Je l'ai essayé chez jsFiddle ...

Réponses:


408

Utilisez des parenthèses de capture :

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

Ils sont définis comme

Si separatorcontient des parenthèses de capture, les résultats correspondants sont renvoyés dans le tableau.

Donc, dans ce cas, nous voulons diviser à _.+(c'est- à -dire que le séparateur divisé est une sous-chaîne commençant par _) mais aussi laisser le résultat contenir une partie de notre séparateur (c'est-à-dire tout ce qui suit _).

Dans cet exemple, notre séparateur (correspondant _(.+)) est _luck_buddyet le groupe capturé (dans le séparateur) l'est lucky_buddy. Sans la parenthèse de capture, la luck_buddy(correspondance .+) n'aurait pas été incluse dans le tableau de résultats, comme c'est le cas avec le simple fait splitque les séparateurs ne sont pas inclus dans le résultat.


21
Vous n'avez même pas besoin de (?), Utilisez simplement /_(.+)/ pour capturer 1 autres caractères après le premier _
Mark

3
Très élégant. Fonctionne comme un charme. Je vous remercie.
Ofeargall

12
Pour être clair, la raison pour laquelle cette solution fonctionne est que tout ce qui suit le premier _est mis en correspondance dans un groupe de capture et est ajouté à la liste de jetons pour cette raison.
Alan Moore

28
Quelqu'un sait pourquoi j'obtiens un élément de chaîne vide supplémentaire avec ceci: in: "Aspect Ratio: 16:9".split(/:(.+)/)out:["Aspect Ratio", " 16:9", ""]
katy lavallee

4
@katylavallee - Cela pourrait aider: stackoverflow.com/questions/12836062/… Puisque le séparateur est ": 16:9", il n'y a rien après le séparateur, créant ainsi la chaîne vide à la fin.
Derek 朕 會 功夫

232

Pour quoi avez-vous besoin d'expressions régulières et de tableaux?

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

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


5
string! == String. javascript est sensible à la casse.
kennebec

3
je pense que c'est la meilleure réponse. il est également possible d'obtenir une chaîne après la seconde _en écrivant:myString.substring( myString.indexOf('_', myString().indexOf('_') + 1) + 1 )
muratgozel

9
La réponse génère la deuxième partie de la chaîne. Et si vous voulez aussi la première partie? Avec var str = "good_luck_buddy", res = str.split(/_(.+)/);vous obtenez toutes les pièces:console.log(res[0]); console.log(res[1]);
Dim

1
@PeterLeger let split = [ string.substring(0, string.indexOf(options.divider)), string.substring(string.indexOf(options.divider) + 1) ]Voilà. Également avec support d'aiguille variable
Steffan

C'est du génie!
stuckedoverflow

36

J'évite RegExp à tout prix. Voici une autre chose que vous pouvez faire:

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

18
Celui qui a peur de RegExp ne sait jamais à quel point RegExp est génial. Vous devez trouver la porte vous-même. Une fois sur place, vous ne regarderez jamais en arrière. Demandez-moi encore dans quelques années et vous me direz à quel point c'est génial.
Christiaan Westerbeek

3
@yonas Prenez la pilule rouge!
2015

2
@yonas Ouais, prends la pilule rouge! Cela vous rendra la vie plus rapide, même pour les cordes courtes: jsperf.com/split-by-first-colon
Julian F. Weinert

15
Ha! J'ai écrit ce commentaire il y a 4+ ans. Je suis définitivement à bord avec RegExp maintenant! :)
yonas

3
@yonas tu ferais mieux de ne pas. RegExp est génial quand vous en avez besoin . Pas le cas ici. Vérifier le test mis à jour: jsperf.com/split-by-first-colon/2
metalim

11

Remplacez la première instance par un espace réservé unique, puis scindez à partir de là.

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

["good","luck_buddy"]

Ceci est plus utile lorsque les deux côtés de la séparation sont nécessaires.


3
Cela met une contrainte inutile sur la chaîne.
Yan Foto

Cette réponse a fonctionné pour moi lorsque toutes les réponses ci-dessus ne l'ont pas été.
GuitarViking

1
@YanFoto vous voulez dire en utilisant '&'? Ça pourrait être n'importe quoi.
sebjwallace

2
@sebjwallace Quoi que vous choisissiez, cela signifie que vous ne pouvez pas avoir ce caractère dans la chaîne. Par exemple, "fish & chips_are_great" donne [fish, chips, are_great] je pense.
Joe

@Joe Vous pouvez utiliser n'importe quoi au lieu de '&' - ce n'était qu'un exemple. Vous pouvez remplacer la première occurrence de _ par ¬ si vous le souhaitez. Ainsi, "fish & chips_are_great" remplacerait la première occurrence de _ par ¬ pour donner "fish & chips¬are_great" puis divisé par ¬ pour obtenir ["fish & chips", "are_great"]
sebjwallace

8

Vous pouvez utiliser l'expression régulière comme:

var arr = element.split(/_(.*)/)
Vous pouvez utiliser le deuxième paramètre qui spécifie la limite du fractionnement. c'est-à-dire: var field = element.split ('_', 1) [1];

6
Cela spécifie uniquement le nombre d'éléments fractionnés qui sont renvoyés, et non le nombre de fois où il est fractionné. 'good_luck_buddy'.split('_', 1);revient juste['good']
Alex Vidal

Merci a fait une supposition à ce sujet. Mise à jour du message pour utiliser une expression régulière.
Chandu

Était (:?.*)censé être un groupe non capturant? Si c'est le cas, cela devrait l'être (?:.*), mais si vous le corrigez, vous constaterez que cela ne fonctionne plus. (:?.*)correspond à un caractère facultatif :suivi de zéro ou plusieurs caractères. Cette solution finit par fonctionner pour la même raison que le fait @ MarkF: tout après le premier_ est ajouté à la liste des jetons car il a été mis en correspondance dans un groupe de capture. (De plus, le gmodificateur n'a aucun effet lorsqu'il est utilisé dans une expression régulière fractionnée.)
Alan Moore

Merci, je ne m'en étais pas rendu compte. Mis à jour le Regex et essayé sur plusieurs scènes ...
Chandu

1
Cela ne fonctionne pas dans ie8 et je reviens à indexOf et à substring
Igor Alekseev

6

Cette solution a fonctionné pour moi

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"

OU

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
Cependant, cela ne fonctionne pas si le séparateur a plus d'un caractère.
haykam

5

De nos jours, String.prototype.splitcela permet en effet de limiter le nombre de scissions.

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

...

limite Facultatif

Un entier non négatif limitant le nombre de divisions. S'il est fourni, fractionne la chaîne à chaque occurrence du séparateur spécifié, mais s'arrête lorsque les entrées de limite ont été placées dans le tableau. Tout texte restant n'est pas du tout inclus dans le tableau.

Le tableau peut contenir moins d'entrées que limit si la fin de la chaîne est atteinte avant que la limite ne soit atteinte. Si la limite est 0, aucun fractionnement n'est effectué.

caveat

Cela pourrait ne pas fonctionner comme vous vous y attendez. J'espérais qu'il ignorerait simplement le reste des délimiteurs, mais à la place, lorsqu'il atteint la limite, il fractionne à nouveau la chaîne restante, omettant la partie après la séparation des résultats de retour.

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

J'espérais:

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

Pareil ici. On dirait que PHP se divise en «premier» et «reste».
BananaAcid

4

Javascript n'a String.splitmalheureusement aucun moyen de limiter le nombre réel de divisions. Il a un deuxième argument qui spécifie combien d'éléments fractionnés réels sont retournés, ce qui n'est pas utile dans votre cas. La solution serait de diviser la chaîne, de décaler le premier élément, puis de rejoindre les éléments restants:

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

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

Je vois que la fonction de division n'aide pas, mais l'utilisation d'une expression régulière semble y parvenir. Il doit spécifier que vous faites référence à la fonction Split elle-même, de manière native.
Dan Hanly

1
Intéressant, cette solution distille le problème vers une solution plus lisible / gérable. Dans mon cas de conversion d'un nom complet en premier et dernier (oui, nos exigences ont forcé cette logique), cette solution a fonctionné le mieux et était plus lisible que les autres. Merci
Sukima

Ce n'est plus vrai :)
Kraken

3

J'ai besoin des deux parties de la ficelle, alors, regex lookbehind m'aide.

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


3

Avec l'aide de la déstructuration, cela peut être plus lisible:

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

2

La solution la plus rapide?

J'ai couru quelques benchmarks , et cette solution a énormément gagné: 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);
};

Comparaison des performances avec d'autres solutions

Le seul concurrent proche était la même ligne de code, sauf en utilisant substrau lieu de slice.

D'autres solutions que j'ai essayé d'impliquer split ou ont RegExppris un gros coup de performance et étaient environ 2 ordres de grandeur plus lents. L'utilisation joindes résultats splitajoute, bien sûr, une pénalité de performance supplémentaire.

Pourquoi sont-ils plus lents? Chaque fois qu'un nouvel objet ou tableau doit être créé, JS doit demander un morceau de mémoire au système d'exploitation. Ce processus est très lent.

Voici quelques directives générales, au cas où vous poursuivriez des repères:

  • Nouvelles allocations de mémoire dynamique pour les objets {} ou les tableaux [](comme celui qui splitcrée) coûteront beaucoup en performances.
  • RegExp les recherches sont plus compliquées et donc plus lentes que les recherches de chaînes.
  • Si vous avez déjà un tableau, la déstructuration des tableaux est à peu près aussi rapide que leur indexation explicite, et a l'air génial.

Suppression au-delà de la première instance

Voici une solution qui découpera jusqu'à et incluant la nième instance. Ce n'est pas aussi rapide, mais sur la question de l'OP, il gobble(element, '_', 1)est toujours> 2x plus rapide qu'une solution RegExpor splitet peut faire plus:

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

Avec la définition ci-dessus, gobble('path/to/file.txt', '/')donnerait le nom du fichier et gobble('prefix_category_item', '_', 1)supprimerait le préfixe comme la première solution dans cette réponse.


  1. Les tests ont été exécutés dans Chrome 70.0.3538.110 sur macOSX 10.14.

Allez ... Nous sommes en 2019 ... Est-ce que les gens utilisent encore vraiment ce genre de chose?
Victor Schröder

Je suis d'accord. Bien que le micro-benchmarking soit légèrement intéressant, vous devriez vous fier à un compilateur ou à un traducteur pour les optimisations. Mb quelqu'un lisant ceci construit un compilateur ou utilise ejs / embedded et ne peut pas utiliser regex. Cependant, cela semble plus agréable pour mon cas spécifique qu'un regex. (Je supprimerais la "solution la plus rapide")
TamusJRoyce

1

La solution de Mark F est géniale mais elle n'est pas prise en charge par les anciens navigateurs. La solution de Kennebec est impressionnante et prise en charge par les anciens navigateurs, mais ne prend pas en charge l'expression régulière.

Donc, si vous cherchez une solution qui ne divise votre chaîne qu'une seule fois, qui est prise en charge par les anciens navigateurs et prend en charge l'expression régulière, voici ma solution:

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

Pour les débutants comme moi qui ne sont pas habitués à l'expression régulière, cette solution de contournement a fonctionné:

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

La méthode slice () extrait une partie d'une chaîne et renvoie une nouvelle chaîne et la méthode indexOf () renvoie la position de la première occurrence trouvée d'une valeur spécifiée dans une chaîne.


Ce n'est pas une solution de contournement, mais une bonne façon de le faire;)
Victor Schröder

1

Utilisez la replace()méthode chaîne avec une expression régulière :

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

Cette expression régulière correspond à 0 ou plusieurs caractères avant le premier _, et le _lui - même. La correspondance est alors remplacée par une chaîne vide.


La document.body.innerHTMLpartie ici est complètement inutile.
Victor Schröder

@ VictorSchröder comment vous attendez-vous à voir la sortie de l'extrait sans document.body.innerHTML?
James T

2
document.bodydépend du DOM pour être présent et ne fonctionnera pas sur un environnement JavaScript pur. console.logest suffisant à cet effet ou simplement laisser le résultat dans une variable pour inspection.
Victor Schröder

@ VictorSchröder Je ne pense pas que cela aurait causé beaucoup de confusion, mais j'ai quand même édité.
James T

0

Cela a fonctionné pour moi sur Chrome + FF:

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

Si vous avez également besoin de la clé, essayez ceci:

"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

Voici un RegExp qui fait l'affaire.

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

Tout d'abord, il force le match à démarrer depuis le début avec le '^'. Ensuite, il correspond à n'importe quel nombre de caractères qui ne sont pas «_», c'est-à-dire tous les caractères avant le premier «_».

Le '?' signifie qu'un nombre minimal de caractères qui font correspondre le motif entier sont mis en correspondance par le '. *?' car il est suivi de «_», qui est ensuite inclus dans la correspondance comme son dernier caractère.

Par conséquent, ce split () utilise une partie correspondante comme son «séparateur» et le supprime des résultats. Ainsi, il supprime tout jusqu'au premier «_» inclus, et vous donne le reste comme 2e élément du résultat. Le premier élément est "" représentant la pièce avant la pièce correspondante. C'est "" car le match commence depuis le début.

Il existe d'autres RegExps qui fonctionnent aussi bien que /_(.*)/ donné par Chandu dans une réponse précédente.

Le /^.*?_/ a l'avantage que vous pouvez comprendre ce qu'il fait sans avoir à connaître le rôle spécial que les groupes de capture jouent avec replace ().

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.