Как я могу отсортировать числа лексикографически? - PullRequest
13 голосов
/ 19 мая 2009

Вот сценарий.

Мне дан массив 'A' целых чисел. Размер массива не фиксирован. Функция, которую я должен написать, может быть вызвана один раз с массивом из нескольких целых чисел, а в другой раз она может даже содержать тысячи целых чисел. Кроме того, каждое целое число не обязательно должно содержать одинаковое количество цифр.

Я должен «отсортировать» числа в массиве так, чтобы в результирующем массиве были целые числа, упорядоченные лексикографическим образом (т.е. они отсортированы на основе их строковых представлений. Здесь «123» - строковое представление 123) , Обратите внимание, что выходные данные должны содержать только целые числа, а не их строковые эквиваленты.

Например: , если ввод:

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

Тогда вывод должен быть:

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

Мой первоначальный подход: Я преобразовал каждое целое число в его строковый формат, затем добавил нули справа от него, чтобы все целые числа содержали одинаковое количество цифр (это был грязный шаг, так как он включал отслеживание и т. Д. что делает решение очень неэффективным), а затем сделал основную сортировку. Наконец, я удалил дополненные нули, преобразовал строки обратно в их целые числа и поместил их в получившийся массив. Это было очень неэффективное решение.

Меня убеждают, что решение не нуждается в заполнении и т. Д., И есть простое решение, в котором вам просто нужно как-то обработать числа (некоторая обработка битов?), Чтобы получить результат.

Какое космическое наиболее эффективное решение вы можете придумать? Время-накрест?

Если вы даете код, я бы предпочел Java или псевдокод. Но если вас это не устраивает, любой такой язык должен подойти.

Ответы [ 14 ]

9 голосов
/ 19 мая 2009

Исполняемый псевдокод (он же Python): thenumbers.sort(key=str). Да, я знаю, что использование Python - это как обман - он просто слишком мощный ;-). А если серьезно, это также означает: если вы можете отсортировать массив строк лексикографически, как это может сделать сортировка Python, то просто сделайте «ключевую строку» из каждого числа и отсортируйте этот вспомогательный массив (вы можете затем восстановить нужный массив чисел с помощью преобразование str-> int или сортировка индексов через косвенное обращение и т. д.); это называется DSU (Decorate, Sort, Undecorate) и это то, что реализует аргумент key= для сортировки Python.

Более подробно (псевдокод):

  1. выделяет массив символов ** aux, пока массив numbers
  2. для i от 0 до length of numbers-1, aux[i]=stringify(numbers[i])
  3. выделить массив int indices такой же длины
  4. для меня от 0 до length of numbers-1, indices[i]=i
  5. сортировать indices, используя как cmp(i,j) strcmp(aux[i],aux[j])
  6. выделить массив int results такой же длины
  7. для меня от 0 до length of numbers-1, results[i]=numbers[indices[i]]
  8. memcpy results свыше numbers
  9. бесплатно каждые aux[i], а также aux, indices, results
5 голосов
/ 19 мая 2009

Поскольку вы упомянули, что речь идет о Java,

Вам не нужно конвертировать в и из строк. Вместо этого определите свой собственный компаратор и используйте его в сортировке.

В частности:

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

Затем вы можете отсортировать массив следующим образом:

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

(Примечание. Несоответствие int / Integer работает автоматически через автобокс)

3 голосов
/ 19 мая 2009

Фактическая сортировка может быть выполнена любым алгоритмом, который вам нравится. Ключом к этой проблеме является нахождение функции сравнения, которая будет правильно определять, какие числа должны быть «меньше, чем» другие, согласно этой схеме:

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());
}
3 голосов
/ 19 мая 2009

Я бы просто превратил их в строки, а затем отсортировал, а затем отсортировал, используя strcmp, который выполняет сравнение lex.

В качестве альтернативы вы можете написать функцию "lexcmp", которая сравнивает два числа, используя% 10 и / 10, но это в основном то же самое, что многократный вызов atoi, так что не очень хорошая идея.

2 голосов
/ 19 мая 2009

Вам определенно не нужно дополнять результат. Это не изменит порядок лексикографического сравнения, оно будет более подвержено ошибкам и будет просто тратить циклы процессора. Наиболее эффективным с точки зрения пространства методом является преобразование чисел в строки при их сравнении. Таким образом, вам не нужно выделять дополнительный массив, числа будут сравниваться на месте.

Вы можете быстро получить достаточно хорошую реализацию, просто преобразовав их в строки по мере необходимости. Строковое число не особенно дорого, и, поскольку вы имеете дело только с двумя строками одновременно, вполне вероятно, что они всегда будут оставаться в кэше ЦП. Таким образом, сравнение будет намного быстрее, чем в случае, когда вы преобразуете весь массив в строки, поскольку их не нужно загружать из основной памяти в кеш. Люди склонны забывать, что процессор имеет кэш и что алгоритмы, выполняющие большую часть своей работы в небольшой локальной области памяти, значительно выиграют от гораздо более быстрого доступа к кэшу. В некоторых архитектурах кэш-память намного быстрее, чем память, поэтому вы можете выполнять сотни операций с вашими данными за то время, которое потребовалось бы для загрузки их из основной памяти. Таким образом, выполнение большей работы в функции сравнения может быть значительно быстрее, чем предварительная обработка массива. Особенно если у вас большой массив.

Попробуйте выполнить сериализацию и сравнение строк в функции компаратора и сравните это. Я думаю, что это будет довольно хорошее решение. Пример псевдокода java-ish:

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

Я думаю, что любые причудливые побитовые сравнения, которые вы могли бы сделать, должны быть приблизительно эквивалентны работе, связанной с преобразованием чисел в строки. Таким образом, вы, вероятно, не получите значительную выгоду. Вы не можете просто сделать прямой бит для сравнения битов, который бы дал вам другой порядок, чем лексикографический тип. В любом случае вам нужно будет уметь вычислять каждую цифру для числа, поэтому проще всего сделать из них строки. Может быть, какая-то хитрая уловка, но каждый путь, который я могу придумать, не совсем простой, сложен, подвержен ошибкам и требует гораздо больше работы, чем стоит.

2 голосов
/ 19 мая 2009

Мой соблазн был бы сказать, что преобразование int в строку будет происходить в коде компаратора, а не в массиве. Хотя это может быть более элегантно с точки зрения кода, я должен сказать, что усилия по выполнению будут больше, поскольку каждое число может сравниваться несколько раз.

Я бы склонялся к созданию нового массива, содержащего как int, так и строковое представление (не уверен, что вам нужно заполнить версии строк для сравнения строк, чтобы получить заданный вами порядок), отсортируйте его по строке а затем скопируйте значения int обратно в исходный массив.

Я не могу придумать умный математический способ сортировки, так как по вашему собственному утверждению вы хотите отсортировать лексикографически, поэтому для этого вам нужно преобразовать числа в строки.

1 голос
/ 02 июля 2014

Вопрос не указывает, как обращаться с отрицательными целыми числами в лексикографическом порядке сортировки. Представленные ранее строковые методы обычно сортируют отрицательные значения во фронт; например, {-123, -345, 0, 234, 78} будут оставлены в этом порядке. Но если знаки минуса должны были игнорироваться, порядок вывода должен быть {0, -123, 234, -345, 78}. Можно было бы применить метод на основе строк для получения этого порядка с помощью несколько громоздких дополнительных тестов.

В теории и в коде может быть проще использовать компаратор, который сравнивает дробные части общих логарифмов двух целых чисел. То есть он будет сравнивать мантиссы из 10 основных логарифмов двух чисел. Компаратор на основе логарифма будет работать быстрее или медленнее, чем компаратор на основе строк, в зависимости от характеристик производительности процессора с плавающей запятой и качества реализаций.

Java-код, показанный в конце этого ответа, включает два логарифмических компаратора: alogCompare и slogCompare. Первый игнорирует знаки, поэтому выдает {0, -123, 234, -345, 78} из {-123, -345, 0, 234, 78}.

Числовые группы, показанные ниже, являются выводом, произведенным программой java.

В разделе «dar rand» показан массив случайных данных dar в том виде, как он сгенерирован. Он читает поперек, а затем вниз, 5 элементов в строке. Обратите внимание, что массивы sar, lara и lars изначально являются несортированными копиями dar.

Секция «dar sort» - это dar после сортировки по Arrays.sort(dar);.

В разделе «sar lex» показан массив sar после сортировки с Arrays.sort(sar,lexCompare);, где lexCompare аналогично Comparator, показанному в ответе Джейсона Коэна.

Раздел «lar s log» показывает массив lars после сортировки по Arrays.sort(lars,slogCompare);, иллюстрируя метод, основанный на логарифме, который дает тот же порядок, что и do lexCompare, и другие методы, основанные на строках.

Раздел «lar a log» показывает массив lara после сортировки по Arrays.sort(lara,alogCompare);, иллюстрируя метод, основанный на логарифме, который игнорирует знаки минус.

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

Java-код показан ниже.

// 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");
    }
}
1 голос
/ 27 июня 2012

Если все числа меньше 1E + 18, вы можете привести каждое число к UINT64, умножить на десять и добавить один, а затем умножить на десять, пока они не станут не меньше 1E + 19. Тогда сортируйте их. Чтобы вернуть исходные числа, делите каждое число на десять до тех пор, пока последняя цифра не станет ненулевой (она должна быть единицей), а затем разделите на десять еще раз.

1 голос
/ 19 мая 2009

Если вы хотите попробовать лучший preprocess-sort-postprocess, то обратите внимание, что int имеет длину не более 10 десятичных цифр (без учета подписи на данный момент).

Таким образом, двоично-десятичные данные для него умещаются в 64 бита. Цифра карты 0-> 1, 1-> 2 и т. Д., И используйте 0 в качестве терминатора NUL (чтобы «1» выходило меньше, чем «10»). Сдвигайте каждую цифру по очереди, начиная с самой маленькой, в верхнюю часть длинной. Сортируйте длинные, которые получатся в лексикографическом порядке для оригинальных целых. Затем преобразуйте обратно, сдвигая цифры по одной за раз назад из верхней части каждого длинного:

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;
    }
}

Или что-то в этом роде. Поскольку в Java нет беззнаковых целых, вам придется немного его изменить. Он использует много рабочей памяти (вдвое больше входного), но это все же меньше, чем ваш первоначальный подход. Это может быть быстрее, чем преобразование в строки на лету в компараторе, но он использует больше пиковой памяти. В зависимости от ГХ, он может пробить себе путь через меньший общий объем памяти и потребовать меньшего сбора.

1 голос
/ 19 мая 2009

псевдокод:

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]);
}

Итак, что такое munge и unmunge?

munge отличается в зависимости от целочисленного размера. Например:

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
}

По сути, Munge говорит, в каком порядке идут 4-битные целые числа при лексикографической сортировке. Я уверен, что вы видите, что здесь есть шаблон - мне не пришлось использовать переключатель - и что вы можете написать версию munge, которая обрабатывает 32-битные целые числа достаточно легко. Подумайте, как бы вы написали версии munge для 5, 6 и 7-битных целых, если вы не можете сразу увидеть шаблон.

unmunge является обратной величиной munge.

Так что вы можете избежать преобразования чего-либо в строку - вам не нужна дополнительная память.

...