Question

J'ai eu une exigence de codage d'une chaîne de caractères 3 (toujours alphabets) dans un 2 octet [] tableau de 2 entiers. Ce devait être fait pour sauver des raisons d'espace et de performance.

Maintenant, l'exigence a un peu changé. La chaîne sera de longueur variable. Ce sera soit la longueur 3 (comme il est au-dessus) ou sera de longueur 4 et aura 1 caractère spécial au début. Le caractère spécial est fixé à savoir si nous choisissons @ il sera toujours @ et toujours au début. Donc, nous sommes sûrs que si la longueur de chaîne est 3, il aura seulement alphabets et si la longueur est de 4, le premier caractère sera toujours « @ » suivi de 3 alphabets

Je peux donc utiliser

charsAsNumbers[0] = (byte) (locationChars[0] - '@');

au lieu de

charsAsNumbers[0] = (byte) (chars[0] - 'A');

Puis-je encoder encore 3 ou 4 caractères à 2 tableau d'octets et les décoder en arrière? Si oui, comment?

Était-ce utile?

La solution

Oui, possible d'encoder un bit d'information supplémentaire tout en maintenant le codage précédent pour 3 valeurs de caractère. Mais puisque l'encodage original ne laisse pas de belles andains propres de numéros gratuits dans l'ensemble de sortie, la cartographie de l'ensemble supplémentaire de chaînes introduites en ajoutant que le caractère supplémentaire ne peut pas aider mais être un peu discontinue.

Par conséquent, je pense qu'il serait difficile de trouver des fonctions de cartographie qui gèrent ces discontinuités sans être à la fois maladroit et lent. Je conclus qu'une cartographie table est la seule solution raisonnable.

Je suis trop paresseux pour réorganiser votre code de cartographie, donc je l'intégra dans le code d'initialisation de table de la mine; ce qui élimine également de nombreuses possibilités d'erreurs de traduction :) Votre méthode encode() est ce que j'appelle OldEncoder.encode().

J'ai couru un petit programme de test pour vérifier que NewEncoder.encode() arrive avec les mêmes valeurs que OldEncoder.encode(), et est en plus capable d'encoder des chaînes avec un 4ème personnage principal. NewEncoder.encode() ne se soucie pas ce que le personnage, il va par la longueur String; pour decode(), le caractère utilisé peut être défini à l'aide PREFIX_CHAR. Je l'ai vérifié globe oculaire aussi que les valeurs du tableau d'octets pour cordes préfixés ne dupliquent aucun de ceux pour les chaînes non préfixées; et enfin, que encodées préfixé Les chaînes peuvent en effet être reconverti aux mêmes chaînes préfixés.

package tequilaguy;


public class NewConverter {

   private static final String[] b2s = new String[0x10000];
   private static final int[] s2b = new int[0x10000];
   static { 
      createb2s();
      creates2b();
   }

   /**
    * Create the "byte to string" conversion table.
    */
   private static void createb2s() {
      // Fill 17576 elements of the array with b -> s equivalents.
      // index is the combined byte value of the old encode fn; 
      // value is the String (3 chars). 
      for (char a='A'; a<='Z'; a++) {
         for (char b='A'; b<='Z'; b++) {
            for (char c='A'; c<='Z'; c++) {
               String str = new String(new char[] { a, b, c});
               byte[] enc = OldConverter.encode(str);
               int index = ((enc[0] & 0xFF) << 8) | (enc[1] & 0xFF);
               b2s[index] = str;
               // int value = 676 * a + 26 * b + c - ((676 + 26 + 1) * 'A'); // 45695;
               // System.out.format("%s : %02X%02X = %04x / %04x %n", str, enc[0], enc[1], index, value);
            }
         }
      }
      // Fill 17576 elements of the array with b -> @s equivalents.
      // index is the next free (= not null) array index;
      // value = the String (@ + 3 chars)
      int freep = 0;
      for (char a='A'; a<='Z'; a++) {
         for (char b='A'; b<='Z'; b++) {
            for (char c='A'; c<='Z'; c++) {
               String str = "@" + new String(new char[] { a, b, c});
               while (b2s[freep] != null) freep++;
               b2s[freep] = str;
               // int value = 676 * a + 26 * b + c - ((676 + 26 + 1) * 'A') + (26 * 26 * 26);
               // System.out.format("%s : %02X%02X = %04x / %04x %n", str, 0, 0, freep, value);
            }
         }
      }
   }

   /**
    * Create the "string to byte" conversion table.
    * Done by inverting the "byte to string" table.
    */
   private static void creates2b() {
      for (int b=0; b<0x10000; b++) {
         String s = b2s[b];
         if (s != null) {
            int sval;
            if (s.length() == 3) {
               sval = 676 * s.charAt(0) + 26 * s.charAt(1) + s.charAt(2) - ((676 + 26 + 1) * 'A');
            } else {
               sval = 676 * s.charAt(1) + 26 * s.charAt(2) + s.charAt(3) - ((676 + 26 + 1) * 'A') + (26 * 26 * 26);
            }
            s2b[sval] = b;
         }
      }
   }

   public static byte[] encode(String str) {
      int sval;
      if (str.length() == 3) {
         sval = 676 * str.charAt(0) + 26 * str.charAt(1) + str.charAt(2) - ((676 + 26 + 1) * 'A');
      } else {
         sval = 676 * str.charAt(1) + 26 * str.charAt(2) + str.charAt(3) - ((676 + 26 + 1) * 'A') + (26 * 26 * 26);
      }
      int bval = s2b[sval];
      return new byte[] { (byte) (bval >> 8), (byte) (bval & 0xFF) };
   }

   public static String decode(byte[] b) {
      int bval = ((b[0] & 0xFF) << 8) | (b[1] & 0xFF);
      return b2s[bval];
   }

}

Je l'ai laissé quelques expressions constantes complexes dans le code, en particulier les pouvoirs de ses 26 choses. Le code ressemble terriblement mystérieux autrement. Vous pouvez laisser ceux qu'ils sont sans perte de performance, comme le compilateur les replie comme Kleenex.


Mise à jour:

Comme l'horreur des approches X-mas, je serai sur la route pendant un certain temps. J'espère que vous trouverez cette réponse et le code dans le temps de faire bon usage. À l'appui dont les efforts que je vais jeter dans mon petit programme de test. Il ne vérifie pas directement des choses, mais imprime les résultats des conversions de toutes les manières importantes et vous permet de les vérifier à l'oeil et la main. Je bidouillé avec mon code (petits coups secs une fois que j'ai eu l'idée de base vers le bas) jusqu'à ce que tout semblait OK là-bas. Vous pouvez tester plus mécaniquement et de façon exhaustive.

package tequilaguy;

public class ConverterHarness {

//   private static void runOldEncoder() {
//      for (char a='A'; a<='Z'; a++) {
//         for (char b='A'; b<='Z'; b++) {
//            for (char c='A'; c<='Z'; c++) {
//               String str = new String(new char[] { a, b, c});
//               byte[] enc = OldConverter.encode(str);
//               System.out.format("%s : %02X%02X%n", str, enc[0], enc[1]);
//            }
//         }
//      }
//   }

   private static void testNewConverter() {
      for (char a='A'; a<='Z'; a++) {
         for (char b='A'; b<='Z'; b++) {
            for (char c='A'; c<='Z'; c++) {
               String str = new String(new char[] { a, b, c});
               byte[] oldEnc = OldConverter.encode(str);
               byte[] newEnc = NewConverter.encode(str);
               byte[] newEnc2 = NewConverter.encode("@" + str);
               System.out.format("%s : %02X%02X %02X%02X %02X%02X %s %s %n", 
                     str, oldEnc[0], oldEnc[1], newEnc[0], newEnc[1], newEnc2[0], newEnc2[1],
                     NewConverter.decode(newEnc), NewConverter.decode(newEnc2));
            }
         }
      }
   }
   public static void main(String[] args) {
      testNewConverter();
   }

}

Autres conseils

Pas directement une réponse, mais voici comment je l'encodage:

   public static byte[] encode(String s) {
      int code = s.charAt(0) - 'A' + (32 * (s.charAt(1) - 'A' + 32 * (s.charAt(2) - 'A')));
      byte[] encoded = { (byte) ((code >>> 8) & 255), (byte) (code & 255) };
      return encoded;
   }

La première ligne utilise le schéma de Horner pour assembler arithmétiquement 5 bits de chaque caractère dans un nombre entier. Il échouera horriblement si l'un de vos caractères d'entrée dehors de la plage [A-`].

La deuxième ligne assemble une matrice de 2 octets à partir de l'octet de début et de fin de l'entier.

Décodage pourrait se faire de la même manière, avec les étapes renversées.


UPDATE avec le code (mettre mon pied où ma bouche est, ou quelque chose comme ça):

public class TequilaGuy {

   public static final char SPECIAL_CHAR = '@';

   public static byte[] encode(String s) {
      int special = (s.length() == 4) ? 1 : 0;
      int code = s.charAt(2 + special) - 'A' + (32 * (s.charAt(1 + special) - 'A' + 32 * (s.charAt(0 + special) - 'A' + 32 * special)));
      byte[] encoded = { (byte) ((code >>> 8) & 255), (byte) (code & 255) };
      return encoded;
   }

   public static String decode(byte[] b) {
      int code = 256 * ((b[0] < 0) ? (b[0] + 256) : b[0]) + ((b[1] < 0) ? (b[1] + 256) : b[1]);
      int special = (code >= 0x8000) ? 1 : 0;
      char[] chrs = { SPECIAL_CHAR, '\0', '\0', '\0' };
      for (int ptr=3; ptr>0; ptr--) {
         chrs[ptr] = (char) ('A' + (code & 31));
         code >>>= 5;
      }
      return (special == 1) ? String.valueOf(chrs) : String.valueOf(chrs, 1, 3);
   }

   public static void testEncode() {
      for (int spcl=0; spcl<2; spcl++) {
         for (char c1='A'; c1<='Z'; c1++) {
            for (char c2='A'; c2<='Z'; c2++) {
               for (char c3='A'; c3<='Z'; c3++) {
                  String s = ((spcl == 0) ? "" : String.valueOf(SPECIAL_CHAR)) + c1 + c2 + c3;
                  byte[] cod = encode(s);
                  String dec = decode(cod);
                  System.out.format("%4s : %02X%02X : %s\n", s, cod[0], cod[1], dec);
               }
            }
         }
      }
   }

   public static void main(String[] args) {
      testEncode();
   }

}

Dans votre alphabet, vous utilisez seulement 15 des 16 bits disponibles de la sortie. Donc, vous pouvez simplement régler le MSB (bit le plus significatif) si la chaîne est de longueur 4 puisque le caractère spécial est fixé.

L'autre option consiste à utiliser une table de traduction. Il suffit de créer une chaîne avec tous les caractères valides:

String valid = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ";

L'indice d'un caractère dans cette chaîne est le codage dans la sortie. Maintenant, créez deux tableaux:

byte encode[] = new byte[256];
char decode[] = new char[valid.length ()];
for (int i=0; i<valid.length(); i++) {
    char c = valid.charAt(i);
    encode[c] = i;
    decode[i] = c;
}

Maintenant, vous pouvez rechercher les valeurs pour chaque direction dans les tableaux et ajouter un caractère que vous aimez dans un ordre quelconque.

Vous trouverez cela beaucoup plus facile si vous venez d'utiliser la classe java.nio.charset.CharsetEncoder pour convertir vos personnages en octets. Il serait même travailler pour des caractères autres que ASCII. Même String.getBytes serait beaucoup moins de code au même effet de base.

Si le « caractère spécial » est fixe et vous êtes toujours au courant qu'une chaîne de 4 caractères commence par ce caractère spécial, puis le char est lui-même ne fournit aucune information utile.

Si la chaîne est de 3 caractères, puis faites ce que vous avez fait avant; si elle est 4 caractères, exécutez l'ancien algorithme sur le sous-chaîne de la chaîne commençant par le 2ème caractère.

Suis-je penser trop simplement ou pensez-vous trop dur?

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top