2009-05-19 21 views
11

Aquí está el escenario.¿Cómo puedo ordenar números lexicográficamente?

Me dieron una matriz 'A' de enteros. El tamaño de la matriz no es fijo. La función que se supone que debo escribir se puede llamar una vez con una matriz de solo unos pocos enteros, mientras que en otro momento, incluso podría contener miles de enteros. Además, cada número entero no necesita contener la misma cantidad de dígitos.

Se supone que debo 'ordenar' los números en la matriz de modo que la matriz resultante tenga los enteros ordenados de manera lexicográfica (es decir, que estén ordenados en función de sus representaciones de cadenas. Aquí "123" es la representación de cadena de 123) Tenga en cuenta que la salida debe contener enteros solamente, no sus equivalentes de cadena.

Por ejemplo: si la entrada es:

[12 | 2434 | 23 | 1 | 654 | 222 | 56 | 100000]

A continuación, la salida debe ser:

[1 | 100000 | 12 | 222 | 23 | 2434 | 56 | 654]

Mi planteamiento inicial: Convertí cada entero a su formato de cadena, luego agregó ceros a su derecho a realizar todos los enteros contienen el mismo número de dígitos (este fue el paso desordenado ya que implicaba el seguimiento, etc toma la solución es muy ineficiente) y luego hizo radix sort. Finalmente, eliminé los ceros acolchados, convertí las cadenas a sus enteros y las puse en la matriz resultante. Esta fue una solución muy ineficiente.

Me han hecho creer que la solución no necesita relleno, etc. y hay una solución simple en la que solo tiene que procesar los números de alguna manera (¿procesamiento de algunos bits?) Para obtener el resultado.

¿Cuál es la solución más eficiente para el espacio que se pueda imaginar? ¿Tiempo sabio?

Si está dando código, preferiría Java o pseudocódigo. Pero si eso no te conviene, cualquier lenguaje de ese tipo debería estar bien.

+1

¿Por qué necesita cero almohadilla? –

+0

Oh, el relleno de cero es obligatorio solo si hago una ordenación de radix (espero no estar equivocándome) porque es más fácil de esa manera. En él, simplemente examino una posición particular de cada número entero durante una iteración. Si hago un simple 'strcmp', supongo que no será necesario. – Skylark

+0

En realidad, si está haciendo una ordenación por radix desde s [0], entonces no necesita rellenar. –

Respuesta

9

Pseudo-código ejecutable (también conocido como Python): thenumbers.sort(key=str). Sí, sé que usar Python es como hacer trampa: solo es demasiado potente ;-). Pero en serio, esto también significa: si puede ordenar una matriz de cadenas lexicográficamente, como intrínsecamente puede hacerlo Python, simplemente haga la "cadena de clave" de cada número y ordene esa matriz auxiliar (luego puede reconstruir la matriz de números deseados una transformación str-> int, o haciendo el ordenamiento en los índices vía indirección, etc., etc.); esto se conoce como DSU (Decorar, Ordenar, Descodificar) y es lo que implementa el argumento key= al género de Python.

En más detalle (pseudocódigo):

  1. asignar una matriz de char ** aux siempre que el numbers array
  2. para i 0-length of numbers-1, aux[i]=stringify(numbers[i])
  3. asignar una matriz de int indices de la misma longitud
  4. para i 0-length of numbers-1, indices[i]=i
  5. tipo indices, utilizando como cmp(i,j)strcmp(aux[i],aux[j])
  6. asignar una matriz de int results de la misma longitud
  7. para i 0-length of numbers-1, results[i]=numbers[indices[i]]
  8. memcpy results sobre numbers
  9. gratis cada aux[i], y también aux, indices, results
+0

Eso es genial. Sin embargo, estoy en busca de un algoritmo, que una forma de hacerlo en un idioma particular. :) – Skylark

+0

... y no son los pasos del 1 al 9. Enumero bajo "con más detalle" lo suficiente de "un algoritmo" para usted ...? –

+0

Cuando dejé el comentario por primera vez, supongo que solo las primeras dos líneas estaban allí. :) Agregaste el algoritmo de pseudocódigo más tarde, ¿no? :) Ahora, esos pasos son útiles. ¡Gracias! – Skylark

2

Mi tentación Sería decir que la conversión int a cadena ocurriría en el código del comparador en lugar de a granel. Aunque esto puede ser más elegante desde una perspectiva de código, tendría que decir que el esfuerzo de ejecución sería mayor, ya que cada número se puede comparar varias veces.

Me inclino a crear una nueva matriz que contenga tanto la representación int como la cadena (no estoy seguro de que necesite rellenar las cadenas para la comparación de cadenas para producir el orden que ha dado), ordene eso en el cadena y luego copie los valores int de nuevo a la matriz original.

No se me ocurre una forma matemática inteligente de ordenar esto, ya que con tu propia afirmación quieres ordenar lexicográficamente, así que necesitas transformar los números en cadenas para hacerlo.

3

Me gustaría simplemente convertirlos en cadenas, y luego ordenar y ordenar usando strcmp, que hace comparaciones de lex.

O bien, puede escribir una función "lexcmp" que compare dos números usando% 10 y/10, pero eso es básicamente lo mismo que llamar atoi muchas veces, por lo que no es una buena idea.

+0

¿Quiere decir convertir toda la matriz en una matriz de cadenas, o simplemente hacer la conversión cuando se realiza la comparación? –

+1

puede hacer cualquiera de las dos cosas, pero yo convierto toda la matriz para que solo lo haga una vez. De lo contrario, debe convertir cada número en muchos (log n) veces, lo que es costoso ... –

+0

Convertir el registro n veces no es costoso si está leyendo datos de caché o registros de la CPU (si está en una arquitectura rica en registros). Quizás tengas razón, pero me he encontrado con situaciones en las que hacer más trabajo con los datos en la memoria caché es mejor que preprocesar la matriz. –

0

Si vas para la eficiencia del espacio sabia, que iba a tratar simplemente haciendo el trabajo en la función de comparación de la clase

int compare(int a, int b) { 
    // convert a to string 
    // convert b to string 
    // return -1 if a < b, 0 if they are equal, 1 if a > b 
} 

Si es demasiado lento (es más lento que el preprocesamiento, seguro) , realice un seguimiento de las conversiones en algún lugar para que la función de comparación no siga teniendo que hacerlas.

2

Definitivamente no es necesario rellenar el resultado. No cambiará el orden de la comparación lexicográfica, será más propenso a errores y desperdiciará ciclos de CPU. El método eficiente más "eficiente en el espacio" sería convertir los números en cadenas cuando se comparen. De esta forma, no necesitaría asignar una matriz adicional, los números se compararían en su lugar.

Puede obtener una implementación razonablemente buena rápidamente simplemente convirtiéndolas en cadenas según sea necesario. La codificación de un número no es particularmente costosa y, dado que solo se trata de dos cadenas a la vez, es muy probable que permanezcan en la memoria caché de la CPU en todo momento.Por lo tanto, las comparaciones serán mucho más rápidas que en el caso en el que convierta toda la matriz en cadenas, ya que no será necesario cargarlas desde la memoria principal en la memoria caché. La gente tiende a olvidar que una CPU tiene una memoria caché y que los algoritmos que hacen mucho de su trabajo en un área local pequeña de memoria se beneficiarán enormemente del acceso a la memoria caché mucho más rápido. En algunas arquitecturas, la memoria caché es mucho más rápida que la memoria, por lo que puede hacer cientos de operaciones con sus datos en el tiempo que le habría tomado cargarla desde la memoria principal. Por lo tanto, hacer más trabajo en la función de comparación podría ser significativamente más rápido que el preprocesamiento de la matriz. Especialmente si tienes una gran variedad.

Intente hacer la serialización de cadenas y la comparación en una función de comparación y punto de referencia. Creo que será una buena solución. Ejemplo java-ish pseudo-código:

public static int compare(Number numA, Number numB) { 
    return numA.toString().compare(numB.toString()); 
} 

creo que ningún bit sabia comparaciones de lujo que podría hacer tendrían que ser aproximadamente equivalente al trabajo implicado en la conversión de los números a cadenas. Entonces probablemente no obtendrá un beneficio significativo. No se puede hacer un análisis directo para la comparación de bits, que le daría un orden diferente al tipo lexicográfico. Necesitarás poder calcular cada dígito para el número de todos modos, por lo que es más sencillo simplemente hacerlos. Puede haber algún truco astuto, pero cada avenida en la que puedo pensar es complicado, propenso a errores y mucho más trabajo de lo que vale.

+0

Como una ocurrencia tardía. Esto también puede depender en gran medida del idioma que esté usando. En C esto es probablemente cierto. En lenguajes más dinámicos, puede haber suficiente sobrecarga al llamar a la función de comparación para abrumar el beneficio de caché. –

3

La clasificación real se puede hacer por cualquier algoritmo que desee. La clave de este problema es encontrar la función de comparación que adecuadamente identificar qué número debe ser "menos" que otros, de acuerdo con este esquema:

bool isLessThan(int a, int b) 
{ 
    string aString = ToString(a); 
    string bString = ToString(b); 

    int charCount = min(aString.length(), bString.length()) 
    for (charIndex = 0; charIndex < charCount; charIndex++) 
    { 
     if (aString[charIndex] < bString[charIndex]) { return TRUE; } 
    } 

    // if the numbers are of different lengths, but identical 
    // for the common digits (e.g. 123 and 12345) 
    // the shorter string is considered "less" 
    return (aString.length() < bString.length()); 
} 
+0

Esa fue una comparación clara, gracias. Esto, en combinación con la conversión por lotes a cadenas, luego la clasificación, probablemente sea la mejor solución si nada funciona. – Skylark

+1

Ah, vale ... Decidiré la conversión por lotes o no, después de todas las respuestas. – Skylark

+0

La conversión de cadenas por lotes debería mejorar sustancialmente las cosas. No sé de una función de clasificación que tenga un rendimiento mejor que O (n), lo que significa que la conversión a una cadena tendrá que suceder para cada nodo más de una vez. ¡Mi función de comparación incluso haría cada una dos veces! Dada la cantidad de divisiones necesarias para convertir un entero en una cadena, me sorprendería que la conversión de cadenas no fuera su cuello de botella. –

0

optimización posible: En lugar de esto:

I convertido cada entero a su formato de cadena, entonces añadido ceros de su derecho a hacer todos los números enteros contienen el mismo número de dígitos

se puede multiplicar cada número por (10^N - log10 (número)), siendo N un número más grande que log10 de cualquiera de sus números.

+0

Aunque esto tendría 1 comparación igual a 100, por ejemplo (como lo hace el original). – dave4420

0
#!/usr/bin/perl 

use strict; 
use warnings; 

my @x = (12, 2434, 23, 1, 654, 222, 56, 100000); 

print $_, "\n" for sort @x; 

__END__ 

Algunos tiempos ... En primer lugar, con @x vacío:

C:\Temp> timethis s-empty 
TimeThis : Elapsed Time : 00:00:00.188 

Ahora, con 10.000 elementos generados al azar:

TimeThis : Elapsed Time : 00:00:00.219 

Esto incluye el tiempo necesario para generar el 10.000 elementos pero no el tiempo para enviarlos a la consola. La salida agrega alrededor de un segundo.

Por lo tanto, ahorrar algo de tiempo programador ;-)

+0

Hola, genial ... gracias por los números. – Skylark

4

Ya que menciona Java es el lenguaje real de que se trate:

No es necesario para convertir desde y hacia las cuerdas. En su lugar, defina su propio comparador y utilícelo en el género.

Específicamente:

Comparator<Integer> lexCompare = new Comparator<Integer>(){ 
    int compareTo(Integer x, Integer y) { 
     return x.toString().compareTo(y.toString()); 
    } 
}; 

A continuación, puede ordenar la matriz de esta manera:

int[] array = /* whatever */; 
Arrays.sort(array, lexCompare); 

(Nota: El desajuste int/Integer funciona de forma automática a través de auto-boxing)

1

Pseudocódigo:

sub sort_numbers_lexicographically (array) { 
    for 0 <= i < array.length: 
     array[i] = munge(array[i]); 
    sort(array); // using usual numeric comparisons 
    for 0 <= i < array.length: 
     array[i] = unmunge(array[i]); 
} 

Entonces, ¿qué son munge y unmunge?

munge es diferente según el tamaño entero. Por ejemplo:

sub munge (4-bit-unsigned-integer n) { 
    switch (n): 
     case 0: return 0 
     case 1: return 1 
     case 2: return 8 
     case 3: return 9 
     case 4: return 10 
     case 5: return 11 
     case 6: return 12 
     case 7: return 13 
     case 8: return 14 
     case 9: return 15 
     case 10: return 2 
     case 11: return 3 
     case 12: return 4 
     case 13: return 5 
     case 14: return 6 
     case 15: return 7 
} 

absoluto se refiere a lo que está haciendo munge está diciendo qué orden 4 bits enteros vienen en cuando ordenados lexigraphically. Estoy seguro de que puede ver que hay un patrón aquí --- no tuve que usar un interruptor --- y que puede escribir una versión de munge que maneja enteros de 32 bits de forma razonablemente fácil. Piense en cómo escribiría versiones de munge para enteros de 5, 6 y 7 bits si no puede ver el patrón inmediatamente.

unmunge es el inverso de munge.

Así que puede evitar convertir cualquier cosa en una cadena --- no necesita ninguna memoria extra.

1

Si desea probar un preproceso-ordenar-postprocesar mejor, entonces tenga en cuenta que un int es como máximo 10 dígitos decimales (ignorando la firma por el momento).

Por lo tanto, los datos decimales codificados en binario caben en 64 bits. Calcule el dígito 0-> 1, 1-> 2 etc. y use 0 como terminador NUL (para asegurarse de que "1" sale a menos de "10"). Cambia cada dígito por turno, comenzando por el más pequeño, en la parte superior de un largo. Ordenar los largos, que saldrán en orden lexicográfico para los originales. A continuación, conviértalo de nuevo desplazando los dígitos de a uno por vez de la parte superior de cada longitud:

uint64_t munge(uint32_t i) { 
    uint64_t acc = 0; 
    while (i > 0) { 
     acc = acc >> 4; 
     uint64_t digit = (i % 10) + 1; 
     acc += (digit << 60); 
     i /= 10; 
    } 
    return acc; 
} 

uint32_t demunge(uint64_t l) { 
    uint32_t acc = 0; 
    while (l > 0) { 
     acc *= 10; 
     uint32_t digit = (l >> 60) - 1; 
     acc += digit; 
     l << 4; 
    } 
} 

O algo así. Ya que Java no tiene ints sin signo, tendrías que modificarlo un poco. Utiliza una gran cantidad de memoria de trabajo (el doble del tamaño de la entrada), pero aún es menor que su enfoque inicial. Puede ser más rápido que convertir en cadenas sobre la marcha en el comparador, pero utiliza más memoria de pico. Dependiendo de la GC, puede agitarse a través de menos memoria total y requerir menos recolección.

0

Un método realmente hacky (usando C) sería:

  • generar una nueva matriz de todos los valores convertidos a flotadores
  • hacer una especie usando la mantisa (significand) bits para la comparación

En Java (de here):

long bits = Double.doubleToLongBits(5894.349580349); 

boolean negative = (bits & 0x8000000000000000L) != 0; 
long exponent = bits & 0x7ff0000000000000L >> 52; 
long mantissa = bits & 0x000fffffffffffffL; 

s o usted ordenaría en el largo mantissa aquí.

+1

Eso los clasifica lexicográficamente en la base 2 (suponiendo que no haya pérdida de precisión). Interlocutor quiere que se clasifiquen lexicográficamente en la base 10. Entonces, ¿qué dijiste, pero usando BigDecimal podría ser un ganador? Probablemente no (mucho) más rápido que String, sin embargo. –

1

Si todos los números son menores que 1E + 18, puede convertir cada número a UINT64, multiplicar por diez y agregar uno, y luego multiplicar por diez hasta que sean al menos 1E + 19. Luego ordena esos. Para recuperar los números originales, divida cada número entre diez hasta que el último dígito no sea cero (debería ser uno) y luego divida por diez una vez más.

1

La pregunta no indica cómo tratar enteros negativos en el orden de clasificación lexicográfica. Los métodos basados ​​en cadenas presentados anteriormente normalmente ordenarán los valores negativos al frente; por ejemplo, {-123, -345, 0, 234, 78} quedaría en ese orden. Pero si se suponía que los signos negativos debían ignorarse, la orden de salida debería ser {0, -123, 234, -345, 78}. Uno podría adaptar un método basado en cuerdas para producir ese orden mediante pruebas adicionales algo engorrosas.

Puede ser más simple, tanto en teoría como en código, usar un comparador que compare partes fraccionarias de logaritmos comunes de dos enteros. Es decir, comparará las mantisas de los logaritmos de la base 10 de dos números. Un comparador basado en logaritmos se ejecutará más rápido o más lento que un comparador basado en cadenas, dependiendo de las especificaciones de rendimiento de coma flotante de una CPU y de la calidad de las implementaciones.

El código java que se muestra al final de esta respuesta incluye dos comparadores basados ​​en logaritmos: alogCompare y slogCompare. El primero ignora los signos, por lo que produciría {0, -123, 234, -345, 78} de {-123, -345, 0, 234, 78}.

Los grupos de números que se muestran a continuación son los resultados producidos por el programa java.

La sección "dar rand" muestra una matriz de datos aleatorios dar según se haya generado. Lee a lo ancho y luego abajo, 5 elementos por línea. Tenga en cuenta que las matrices sar, lara y lars inicialmente son copias sin clasificar de dar.

La sección "dar sort" es dar después de ordenar por Arrays.sort(dar);.

La sección “sar lex” muestra array sar después de la clasificación con Arrays.sort(sar,lexCompare);, donde lexCompare es similar a la Comparator se muestra en la respuesta de Jason Cohen.

La sección “lar s registro” muestra array lars después de la clasificación por Arrays.sort(lars,slogCompare);, que ilustra un método basado en el logaritmo que da el mismo orden que hacer lexCompare y otros métodos basados ​​en cadenas.

La sección "lar a log" muestra la matriz lara después de ordenar por Arrays.sort(lara,alogCompare);, lo que ilustra un método basado en logaritmos que ignora los signos menos.

dar rand -335768 115776  -9576 185484  81528 
dar rand  79300   0  3128  4095 -69377 
dar rand  -67584  9900 -50568 -162792  70992 

dar sort -335768 -162792 -69377 -67584 -50568 
dar sort  -9576   0  3128  4095  9900 
dar sort  70992  79300  81528 115776 185484 

sar lex -162792 -335768 -50568 -67584 -69377 
sar lex  -9576   0 115776 185484  3128 
sar lex  4095  70992  79300  81528  9900 

lar s log -162792 -335768 -50568 -67584 -69377 
lar s log  -9576   0 115776 185484  3128 
lar s log  4095  70992  79300  81528  9900 

lar a log   0 115776 -162792 185484  3128 
lar a log -335768  4095 -50568 -67584 -69377 
lar a log  70992  79300  81528  -9576  9900 

El código de Java se muestra a continuación.

// Code for "How can I sort numbers lexicographically?" - jw - 2 Jul 2014 
import java.util.Random; 
import java.util.Comparator; 
import java.lang.Math; 
import java.util.Arrays; 
public class lex882954 { 
// Comparator from Jason Cohen's answer 
    public static Comparator<Integer> lexCompare = new Comparator<Integer>(){ 
     public int compare(Integer x, Integer y) { 
      return x.toString().compareTo(y.toString()); 
     } 
    }; 
// Comparator that uses "abs." logarithms of numbers instead of strings 
    public static Comparator<Integer> alogCompare = new Comparator<Integer>(){ 
     public int compare(Integer x, Integer y) { 
      Double xl = (x==0)? 0 : Math.log10(Math.abs(x)); 
      Double yl = (y==0)? 0 : Math.log10(Math.abs(y)); 
      Double xf=xl-xl.intValue(); 
      return xf.compareTo(yl-yl.intValue()); 
     } 
    }; 
// Comparator that uses "signed" logarithms of numbers instead of strings 
    public static Comparator<Integer> slogCompare = new Comparator<Integer>(){ 
     public int compare(Integer x, Integer y) { 
      Double xl = (x==0)? 0 : Math.log10(Math.abs(x)); 
      Double yl = (y==0)? 0 : Math.log10(Math.abs(y)); 
      Double xf=xl-xl.intValue()+Integer.signum(x); 
      return xf.compareTo(yl-yl.intValue()+Integer.signum(y)); 
     } 
    }; 
// Print array before or after sorting 
    public static void printArr(Integer[] ar, int asize, String aname) { 
     int j; 
     for(j=0; j < asize; ++j) { 
      if (j%5==0) 
       System.out.printf("%n%8s ", aname); 
      System.out.printf(" %9d", ar[j]); 
     } 
     System.out.println(); 
    } 
// Main Program -- to test comparators 
    public static void main(String[] args) { 
     int j, dasize=15, hir=99; 
     Random rnd = new Random(12345); 
     Integer[] dar = new Integer[dasize]; 
     Integer[] sar = new Integer[dasize]; 
     Integer[] lara = new Integer[dasize]; 
     Integer[] lars = new Integer[dasize]; 

     for(j=0; j < dasize; ++j) { 
      lara[j] = lars[j] = sar[j] = dar[j] = rnd.nextInt(hir) * 
       rnd.nextInt(hir) * (rnd.nextInt(hir)-44); 
     } 
     printArr(dar, dasize, "dar rand"); 
     Arrays.sort(dar); 
     printArr(dar, dasize, "dar sort"); 
     Arrays.sort(sar, lexCompare); 
     printArr(sar, dasize, "sar lex"); 
     Arrays.sort(lars, slogCompare); 
     printArr(lars, dasize, "lar s log"); 
     Arrays.sort(lara, alogCompare); 
     printArr(lara, dasize, "lar a log"); 
    } 
} 
Cuestiones relacionadas