web-dev-qa-db-fra.com

Joindre une chaîne à l'aide de délimiteurs

Quel est le meilleur moyen de joindre une liste de chaînes en une chaîne combinée délimitée? Je suis principalement préoccupé par le moment d'arrêter l'ajout du délimiteur. Je vais utiliser C # pour mes exemples, mais j'aimerais que ce soit indépendant du langage.

EDIT: je n’ai pas utilisé StringBuilder pour simplifier légèrement le code.

Utilisez une boucle For

for(int i=0; i < list.Length; i++)
{
    result += list[i];
    if(i != list.Length - 1)
        result += delimiter;
}

Utilisez une boucle For pour régler le premier élément précédemment

result = list[0];
for(int i = 1; i < list.Length; i++)
    result += delimiter + list[i];

Cela ne fonctionnera pas pour un IEnumerable où vous ne connaissez pas la longueur de la liste à l'avance, 

Utiliser une boucle foreach

bool first = true;
foreach(string item in list)
{
    if(!first)
        result += delimiter;
    result += item;
    first = false;
}

Variation sur une boucle foreach

De la solution de Jon

StringBuilder builder = new StringBuilder();
string delimiter = "";
foreach (string item in list)
{
    builder.Append(delimiter);
    builder.Append(item);
    delimiter = ",";       
}
return builder.ToString();

Utiliser un itérateur

Encore de Jon

using (IEnumerator<string> iterator = list.GetEnumerator())
{
    if (!iterator.MoveNext())
        return "";
    StringBuilder builder = new StringBuilder(iterator.Current);
    while (iterator.MoveNext())
    {
        builder.Append(delimiter);
        builder.Append(iterator.Current);
    }
    return builder.ToString();
}

Quels autres algorithmes existe-t-il?

22
John Oxley

Il est impossible de donner ici une réponse véritablement indépendante de la langue, car différentes langues et plates-formes traitent les chaînes de manière différente et offrent différents niveaux de prise en charge intégrée pour la jonction de listes de chaînes. Vous pourriez prendre à peu près le code identique dans deux langues différentes, et ce serait génial dans une langue et terrible dans une autre.

En C #, vous pouvez utiliser:

StringBuilder builder = new StringBuilder();
string delimiter = "";
foreach (string item in list)
{
    builder.Append(delimiter);
    builder.Append(item);
    delimiter = ",";       
}
return builder.ToString();

Ceci prépend une virgule sur tous les éléments sauf le premier. Un code similaire serait bien aussi en Java.

EDIT: Voici une alternative, un peu comme celle de Ian plus tard mais travaillant sur un IEnumerable<string> général.

// Change to IEnumerator for the non-generic IEnumerable
using (IEnumerator<string> iterator = list.GetEnumerator())
{
    if (!iterator.MoveNext())
    {
        return "";
    }
    StringBuilder builder = new StringBuilder(iterator.Current);
    while (iterator.MoveNext())
    {
        builder.Append(delimiter);
        builder.Append(iterator.Current);
    }
    return builder.ToString();
}

EDIT près de 5 ans après la réponse originale ...

Dans .NET 4, string.Join était surchargé de manière significative. Il y a une surcharge prenant IEnumerable<T> qui appelle automatiquement ToString, et il y a une surcharge pour IEnumerable<string>. Donc, vous n'avez plus besoin du code ci-dessus ... pour .NET, de toute façon.

35
Jon Skeet

En .NET, vous pouvez utiliser la méthode String.Join :

string concatenated = String.Join(",", list.ToArray());

En utilisant .NET Reflector , nous pouvons découvrir comment il le fait:

public static unsafe string Join(string separator, string[] value, int startIndex, int count)
{
    if (separator == null)
    {
        separator = Empty;
    }
    if (value == null)
    {
        throw new ArgumentNullException("value");
    }
    if (startIndex < 0)
    {
        throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_StartIndex"));
    }
    if (count < 0)
    {
        throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NegativeCount"));
    }
    if (startIndex > (value.Length - count))
    {
        throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_IndexCountBuffer"));
    }
    if (count == 0)
    {
        return Empty;
    }
    int length = 0;
    int num2 = (startIndex + count) - 1;
    for (int i = startIndex; i <= num2; i++)
    {
        if (value[i] != null)
        {
            length += value[i].Length;
        }
    }
    length += (count - 1) * separator.Length;
    if ((length < 0) || ((length + 1) < 0))
    {
        throw new OutOfMemoryException();
    }
    if (length == 0)
    {
        return Empty;
    }
    string str = FastAllocateString(length);
    fixed (char* chRef = &str.m_firstChar)
    {
        UnSafeCharBuffer buffer = new UnSafeCharBuffer(chRef, length);
        buffer.AppendString(value[startIndex]);
        for (int j = startIndex + 1; j <= num2; j++)
        {
            buffer.AppendString(separator);
            buffer.AppendString(value[j]);
        }
    }
    return str;
}
22
Ian Nelson

Il y a peu de raisons de le rendre agnostique lorsque certaines langues fournissent une prise en charge de cette fonctionnalité sur une seule ligne, par exemple, celle de Python.

",".join(sequence)

Voir la documentation jointe pour plus d'informations.

8
Hank Gay

Pour python, assurez-vous que vous avez une liste de chaînes, sinon ','. Join (x) échouera. Pour une méthode sûre avec 2.5+

delimiter = '","'
delimiter.join(str(a) if a else '' for a in list_object)

Le "str (a) si un else" est bon pour les types None, sinon str () finit par faire "None", ce qui n'est pas Nice;)

5
Christian Witts

En PHP implode () :

$string = implode($delim, $array);
5
Jeremy L

Je voudrais exprimer cela récursivement.

  • Vérifiez si le nombre d'arguments de chaîne est égal à 1. Si c'est le cas, renvoyez-le.
  • Sinon, recurse, mais combinez les deux premiers arguments avec le séparateur.

Exemple dans Common LISP:

 (jointure manquante (délimiteur et chaînes restantes) 
 (si (null (chaînes restantes)) 
 (premières chaînes) 
 (appliquer # 'join 
 délimiteur 
 (concaténer 'chaîne 
 (premières chaînes) 
 délimiteur 
 (deuxième chaînes)) 
 (cddr chaînes)))) 

La méthode la plus idiomatique consiste à utiliser reduce, mais cela étend presque exactement les mêmes instructions que ci-dessus:

 (jointure déliée (délimiteur et chaînes restantes) 
 (réduire (lambda (ab) 
 (concaténer la chaîne d'un délimiteur b)) 
 chaînes)) 
4
Svante

J'ajoutais toujours le délimètre et le retirais à la fin si nécessaire. De cette façon, vous n'exécutez pas une instruction if pour chaque itération de la boucle lorsque vous ne souhaitez effectuer le travail qu'une seule fois.

StringBuilder sb = new StringBuilder();

foreach(string item in list){
    sb.Append(item);
    sb.Append(delimeter);
}

if (list.Count > 0) {
    sb.Remove(sb.Length - delimter.Length, delimeter.Length)
}
4
Chris Tillapaugh
List<string> aaa = new List<string>{ "aaa", "bbb", "ccc" };
string mm = ";";
return aaa.Aggregate((a, b) => a + mm + b);

et vous obtenez

aaa;bbb;ccc

lambda est assez pratique

4
eponie

Le problème est que les langages informatiques ont rarement une chaîne booléenne, c'est-à-dire des méthodes de type chaîne qui ne font rien d'utile. SQL Server au moins a est [not] null et nullif, ce qui, une fois combiné, résout le problème du séparateur, soit dit en passant: isnotnull (nullif (columnvalue, ""), "," + columnvalue))

Le problème est que dans les langues, il y a des booléens et des chaînes, et que le twain ne doit jamais se rencontrer, sauf sous des formes de code laides, par exemple.

concatstring = chaine1 + "," + chaine2; if (fubar) concatstring + = chaine3 concatstring + = chaine4 etc.

J'ai tenté avec brio d'éviter toute cette laideur, en jouant avec des virgules et en concaténant des jointures, mais il me reste encore quelques-unes d'entre elles, notamment des erreurs SQL Server lorsque j'ai manqué une des virgules et qu'une variable est vide.

Jonathan

3
Jonathan Campbell

En C #, vous pouvez simplement utiliser String.Join (separator, string_list)

2
vartec

En Java 8, nous pouvons utiliser:

List<String> list = Arrays.asList(new String[] { "a", "b", "c" });
System.out.println(String.join(",", list)); //Output: a,b,c

Pour avoir un préfixe et un suffixe, nous pouvons le faire

StringJoiner joiner = new StringJoiner(",", "{", "}");
list.forEach(x -> joiner.add(x));
System.out.println(joiner.toString()); //Output: {a,b,c}

Avant Java 8, vous pouvez faire comme la réponse de Jon

StringBuilder sb = new StringBuilder(prefix);
boolean and = false;
for (E e : iterable) {        
    if (and) {
        sb.append(delimiter);
    }
    sb.append(e);
    and = true;
}
sb.append(suffix);
1
gladiator

Depuis que vous avez tagué cette langue agnostique,

Voici comment vous le feriez en python

# delimiter can be multichar like "| trlalala |"
delimiter = ";"
# sequence can be any list, or iterator/generator that returns list of strings
result = delimiter.join(sequence)
#result will NOT have ending delimiter 

Edit: / Je vois que plusieurs personnes m'ont battu. Désolé pour la dupication 

1
Luka Marinko

Ceci est une solution de travail en C #, en Java, vous pouvez utiliser la même chose pour chaque itérateur.

        string result = string.Empty; 

        // use stringbuilder at some stage.
        foreach (string item in list)
            result += "," + item ;

        result = result.Substring(1);
        // output:  "item,item,item"

Si vous utilisez .NET, vous voudrez peut-être utiliser une méthode d'extension pour pouvoir exécuter list.ToString (",") Pour plus de détails, consultez Separator Delimited ToString pour Array, List, Dictionary, Generic IEnumerable

// contains extension methods, it must be a static class.
public static class ExtensionMethod
{
    // apply this extension to any generic IEnumerable object.
    public static string ToString<T>(this IEnumerable<T> source,
      string separator)
    {
        if (source == null)
           throw new ArgumentException("source can not be null.");

        if (string.IsNullOrEmpty(separator))
           throw new ArgumentException("separator can not be null or empty.");

        // A LINQ query to call ToString on each elements
        // and constructs a string array.
        string[] array =
         (from s in source
          select s.ToString()
          ).ToArray();

        // utilise builtin string.Join to concate elements with
        // customizable separator.
        return string.Join(separator, array);
    }
}

EDIT: pour des raisons de performances, remplacez le code de concaténation par la solution de constructeur de chaînes mentionnée dans ce fil.

1
Ray Lu

Vu le Python répondre comme 3 fois, mais pas de Ruby?!?!?

la première partie du code déclare un nouveau tableau. Ensuite, vous pouvez simplement appeler la méthode .join () et transmettre le délimiteur, qui retournera une chaîne avec le délimiteur au milieu. Je crois que la méthode join appelle la méthode .to_s sur chaque élément avant sa concaténation.

["ID", "Description", "Active"].join(",")
>> "ID, Description, Active"

cela peut être très utile lorsque vous combinez la méta-programmation avec une interaction de base de données.

est-ce que quelqu'un sait si c # a quelque chose de similaire à cette syntaxe sugar?

1
taelor

Je pense que la meilleure façon de faire quelque chose comme ça est (je vais utiliser un pseudo-code, donc nous allons le rendre vraiment agnostique):

function concat(<array> list, <boolean> strict):
  for i in list:
    if the length of i is zero and strict is false:
      continue;
    if i is not the first element:
      result = result + separator;
    result = result + i;
  return result;

le deuxième argument de concat(), strict, est un indicateur permettant de savoir si d'éventuelles chaînes vides doivent être prises en compte dans la concaténation ou non.

J'ai l'habitude de ne pas envisager l'ajout d'un séparateur final; d'autre part, si strict est faux, la chaîne résultante pourrait être exempte d'éléments tels que "A, B , F", à condition que le séparateur soit une virgule, mais se présenterait plutôt comme "A, B, F".

1
Federico Zancan

Pour Java, une réponse très complète a été donnée dans cette question ou cette question .

C'est utiliser StringUtils.join dans Apache Commons 

String result = StringUtils.join(list, ", ");
0
Ethan Heilman

Java (de la solution de Jon):

    StringBuilder sb = new StringBuilder();
    String delimiter = "";
    for (String item : items) {
        sb.append(delimiter).append(item);
        delimeter = ", ";
    }
    return sb.toString();
0
ceklock
string result = "";
foreach(string item in list)
{
    result += delimiter + item;
}
result = result.Substring(1);

Edit: Bien sûr, vous n’utiliseriez ni l’un ni l’autre de vos algorithmes pour concaténer des chaînes. Avec C # /. NET, vous utiliseriez probablement un StringBuilder:

StringBuilder sb = new StringBuilder();
foreach(string item in list)
{
    sb.Append(delimiter);
    sb.Append(item);
}
string result = sb.ToString(1, sb.Length-1);

Et une variante de cette solution:

StringBuilder sb = new StringBuilder(list[0]);
for (int i=1; i<list.Count; i++)
{
    sb.Append(delimiter);
    sb.Append(list[i]);
}
string result = sb.ToString();

Les deux solutions n'incluent aucun contrôle d'erreur.

0
M4N

c'est comme ça que python résout le problème:

','.join(list_of_strings)

Je n'ai jamais compris la nécessité d'utiliser des «algorithmes» dans des cas triviaux

0
SilentGhost

Vous pouvez écrire votre propre méthode AppendTostring (chaîne, délimiteur) qui ajoute le délimiteur si et seulement si la chaîne n'est pas vide. Ensuite, vous appelez simplement cette méthode dans n’importe quelle boucle sans avoir à vous soucier de l’ajout ou de la suppression.

Edit: mieux encore, bien sûr, d’utiliser une sorte de StringBuffer dans la méthode si elle est disponible.

0
tehvan

Dans .NET, j'utiliserais si possible la méthode String.join, qui vous permet de spécifier un séparateur et un tableau de chaînes. Une liste peut être convertie en tableau avec ToArray, mais je ne sais pas quel en serait l'impact sur les performances.

Les trois algorithmes que vous mentionnez sont ceux que j'utiliserais (j'aime le deuxième parce qu'il n'a pas de déclaration if, mais si la longueur n'est pas connue, j'utiliserais le troisième car il ne duplique pas le code). La seconde ne fonctionnera que si la liste n'est pas vide, ce qui pourrait prendre une autre instruction if.

Une quatrième variante pourrait consister à placer un séparateur devant chaque élément concaténé, puis à supprimer le premier séparateur du résultat. 

Si vous concaténez des chaînes dans une boucle, notez que dans les cas non triviaux, l'utilisation d'un constructeur de chaînes surpassera considérablement les concaténations de chaînes répétées.

0
Renze de Waal

Groovy a également une méthode String Object.join (String).

0
user159313

De http://dogsblog.softwarehouse.co.zw/post/2009/02/11/IEnumerable-to-Comma-Separated-List-(and-more).aspx

Une des choses que je déteste quand je développe est de faire une liste d'identifiants séparés par des virgules, c'est SO simple mais il y a toujours du code laid ... Les solutions habituelles sont de passer en revue et de mettre une virgule après chaque élément, puis de le supprimer. dernier caractère, ou avoir une instruction if pour vérifier si vous êtes au début ou à la fin de la liste. Vous trouverez ci-dessous une solution que vous pouvez utiliser avec n’importe quel IEnumberable, c’est-à-dire une liste, un tableau, etc. C’est également le moyen le plus efficace de le faire car elle repose sur une affectation qui est préférable à l’édition d’une chaîne ou à l’utilisation d’un if.

public static class StringExtensions
{
    public static string Splice<T>(IEnumerable<T> args, string delimiter)
    {
        StringBuilder sb = new StringBuilder();
        string d = "";
        foreach (T t in args)
        {
            sb.Append(d);
            sb.Append(t.ToString());
            d = delimiter;
        }
        return sb.ToString();
    }
}

Maintenant, il peut être utilisé avec n'importe quel IEnumerable, par exemple.

StringExtensions.Splice (billingTransactions.Select (t => t.id), ",")

nous donner 31,32,35 

0
Matthew Hood

Dans Clojure, vous pouvez simplement utiliser clojure.contrib.str-utils/str-join:

(str-join ", " list)

Mais pour l'algorithme actuel:

(reduce (fn [res cur] (str res ", " cur)) list)
0
wtetzner