web-dev-qa-db-fra.com

Fractionner la chaîne en paires clé-valeur

J'ai une chaîne comme celle-ci:

pet:cat::car:honda::location:Japan::food:sushi

Désormais, : indique les paires clé-valeur, tandis que :: sépare les paires . Je souhaite ajouter les paires clé-valeur à une carte.

Je peux y arriver en utilisant:

Map<String, String> map = new HashMap<String, String>();
String test = "pet:cat::car:honda::location:Japan::food:sushi";
String[] test1 = test.split("::");

for (String s : test1) {
    String[] t = s.split(":");
    map.put(t[0], t[1]);
}

for (String s : map.keySet()) {
    System.out.println(s + " is " + map.get(s));
}

Mais existe-t-il un moyen efficace de le faire?


Je pense que le code est inefficace car j'ai utilisé 2 objets String[] et appelé la fonction split à deux reprises. J'utilise aussi t[0] et t[1] qui peuvent générer une ArrayIndexOutOfBoundsException s'il n'y a pas de valeurs.

14
v1shnu

Vous pouvez faire un appel à split () et un passage sur String en utilisant le code suivant. Mais cela suppose bien sûr que la chaîne est valide en premier lieu:

    Map<String, String> map = new HashMap<String, String>();
    String test = "pet:cat::car:honda::location:Japan::food:sushi";

    // split on ':' and on '::'
    String[] parts = test.split("::?");

    for (int i = 0; i < parts.length; i += 2) {
        map.put(parts[i], parts[i + 1]);
    }

    for (String s : map.keySet()) {
        System.out.println(s + " is " + map.get(s));
    }

Ce qui précède est probablement un peu plus efficace que votre solution, mais si vous trouvez votre code plus clair, gardez-le, car il n'y a presque aucune chance qu'une telle optimisation ait un impact significatif sur les performances, à moins que vous fassiez des millions de fois. Quoi qu'il en soit, si c'est si important, vous devriez mesurer et comparer.

MODIFIER:

pour ceux qui se demandent ce que ::? signifie dans le code ci-dessus: String.split () prend une expression régulière en argument. Un séparateur est une sous-chaîne qui correspond à l'expression régulière. ::? est une expression régulière qui signifie: 1 colon, suivi de 0 ou 1 colon. Cela permet donc de considérer :: et : comme séparateurs.

12
JB Nizet

En utilisant la bibliothèque Guava, c'est un one-liner:

String test = "pet:cat::car:honda::location:Japan::food:sushi";
Map<String, String> map = Splitter.on( "::" ).withKeyValueSeparator( ':' ).split( test );
System.out.println(map);

Le résultat:

{pet=cat, car=honda, location=Japan, food=sushi}

Cela pourrait également fonctionner plus rapidement que JDK String.split car cela ne crée pas d’expression rationnelle pour "::".

Mise à jour il gère même correctement le casse de coin à partir des commentaires:

String test = "pet:cat::car:honda::location:Japan::food:sushi:::cool";
Map<String, String> map = Splitter.on( "::" ).withKeyValueSeparator( ':' ).split( test );
System.out.println(map);

La sortie est:

{pet=cat, car=honda, location=Japan, food=sushi, =cool}
15
Tagir Valeev

Votre solution est en effet quelque peu inefficace.

La personne qui vous a donné la ficelle à analyser est aussi un peu un clown. Il existe des formats de sérialisation standard, tels que JSON ou XML, pour lesquels des analyses rapides et efficaces existent. Inventer la roue carrée n’est jamais une bonne idée.

Première question: ça vous intéresse? Est-il suffisamment lent pour entraver les performances de votre application? Ce n’est probablement pas le cas, mais il n’ya qu’une façon de le savoir. Benchmark votre code.

Cela dit, des solutions plus efficaces existent. Ci-dessous un exemple

public static void main (String[] args) throws Java.lang.Exception
{
    String test = "pet:cat::car:honda::location:Japan::food:sushi";
    boolean stateiskey = true;

    Map<String, String> map = new HashMap<>();
    int keystart = 0;
    int keyend = 0;
    int valuestart = 0;
    int valueend = 0;

    for(int i = 0; i < test.length(); i++){
        char nextchar = test.charAt(i);
        if (stateiskey) {
            if (nextchar == ':') {
              keyend = i;           
              stateiskey = false;
              valuestart = i + 1;
            }
        } else {
            if (i == test.length() - 1 || (nextchar == ':' && test.charAt(i + 1) == ':')) {
                valueend = i;
                if (i + 1 == test.length()) valueend += 1; //compensate one for the end of the string
                String key = test.substring(keystart, keyend);
                String value = test.substring(valuestart, valueend);
                keystart = i + 2;
                map.put(key, value);
                i++;
                stateiskey = true;
            }
        }
    }

    System.out.println(map);
}

Cette solution est une machine à états finis avec seulement deux états. Il ne regarde chaque caractère que deux fois, une fois lorsqu'il le teste pour une limite et une fois lorsqu'il le copie dans la nouvelle chaîne de votre carte. C'est le montant minimum.

Il ne crée pas d'objets inutiles, tels que des constructeurs de chaînes, des chaînes ou des tableaux, mais maintient la pression de la collection à un niveau bas.

Il maintient une bonne localité. Le caractère suivant est probablement toujours en cache, la recherche est donc économique.

Cela a un coût grave qui n'en vaut probablement pas la peine:

  • C'est beaucoup plus compliqué et moins évident
  • Il y a toutes sortes de pièces mobiles
  • Il est plus difficile de déboguer lorsque votre chaîne est dans un format inattendu
  • Vos collègues vont vous haïr
  • Vous allez vous détester quand vous devez déboguer quelque chose

Ça vaut le coup? Peut être. À quelle vitesse avez-vous besoin que cette chaîne soit analysée exactement?

Un repère rapide et sale sur https://ideone.com/8T7twy me dit que pour cette chaîne, cette méthode est environ 4 fois plus rapide. Pour les chaînes plus longues, la différence est probablement un peu plus grande.

Mais votre version n’est encore que de 415 millisecondes pour 100 000 répétitions, celle-ci étant de 99 millisecondes.

2
Martijn

Essayez ce code - voir les commentaires pour une explication:

HashMap<String,String> hmap = new HashMap<>();
String str="abc:1::xyz:2::jkl:3";
String straraay[]= str.split("::?");

for(int i=0;i<straraay.length;i+=2) {
    hmap.put(straraay[i],straraay[i+1]);
}

for(String s:straraay){
    System.out.println(hmap.values()); //for Values only
    System.out.println(hmap.keySet()); //for keys only if you want to more clear
}
1
Debabrata Barik

Je ne sais pas si c'est la meilleure approche ou non, mais je pense que c'est une autre façon de faire la même chose sans utiliser deux fois la méthode split

Map<String, String> map = new HashMap<String, String>();
String test = "pet:cat::car:honda::location:Japan::food:sushi";
String[] test1 = test.replaceAll("::",":").split(":");
for(int i=0;i<test1.length;i=i+2)
{
     map.put(test1[i], test1[i+1]);
}

for (String s : map.keySet()) {
    System.out.println(s + " is " + map.get(s));
}

J'espère que ça va aider :)

0
Vishnu