2009-05-19 3 views
11

Вот сценарий.Как я могу сортировать числа лексикографически?

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

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

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

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

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

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

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

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

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

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

+1

Зачем вам нужна нулевая площадка? –

+0

О, нулевое заполнение требуется только в том случае, если я делаю сортировку радикса (надеюсь, что я не ошибаюсь), потому что это проще. В нем я просто рассматриваю конкретную позицию каждого целого числа во время итерации. Если я сделаю простой «strcmp», я думаю, это не потребуется. – Skylark

+0

На самом деле, если вы делаете сортировку radix с s [0], тогда вам не нужно прокладывать. –

ответ

9

Исполняемый псевдокод (он же 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. для I от 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. тетсру results над numbers
  9. бесплатно каждый aux[i], а также aux, indices, results
+0

Это круто. Тем не менее, я ищу алгоритм, а не способ сделать это на определенном языке. :) – Skylark

+0

... и не являются ли шаги с 1 по 9, я перечисляю под «более подробно» достаточно «алгоритма» для вас ...? –

+0

Когда я впервые оставил комментарий, я предполагаю, что там были только первые две строки. :) Вы добавили псевдокодный алгоритм позже, не так ли? :) Теперь эти шаги полезны. Благодаря! – Skylark

2

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

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

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

3

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

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

+0

Вы имеете в виду преобразовать весь массив в массив строк или просто сделать преобразование при сравнении? –

+1

вы можете сделать это, но я конвертирую весь массив, чтобы вы делали это только один раз. В противном случае вам нужно преобразовать каждое число много (log n) раз, что дорого ... –

+0

Преобразование log n раз не дорого, если вы читаете свои данные из кэша или регистров процессора (если вы находитесь в богатой регистром архитектуре). Возможно, вы правы, но я столкнулся с ситуациями, когда больше работы с данными в кэше бьет препроцессинг массива. –

0

Если вы собираетесь пространства-накрест эффективности, я хотел бы попробовать просто делать работу в функции сравнения того рода

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 
} 

Если это слишком медленно (это медленнее, чем предварительная обработка, конечно) , отслеживайте конверсии где-нибудь, чтобы функция сравнения не выполняла их.

2

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

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

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

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

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

+0

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

3

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

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

Это было аккуратное сравнение, спасибо. Это, в сочетании с пакетным преобразованием в строки, затем сортировка, вероятно, будет лучшим решением, если ничего не получится. – Skylark

+1

А, хорошо .. Я решаю пакетное преобразование или нет после всех ответов. – Skylark

+0

Преобразование пакетной строки должно существенно улучшить ситуацию. Я не знаю функции сортировки, которая работает лучше, чем O (n), что означает, что преобразование в строку должно выполняться для каждого узла более одного раза. Моя функция сравнения будет делать каждый раз два раза! Учитывая количество делений, необходимых для преобразования целого числа в строку, я был бы удивлен, если преобразование строки не было вашим узким местом. –

0

Возможная оптимизация: Вместо этого:

I конвертируется каждое целое число в его формат строки, а затем добавляют нули справа, чтобы все числа содержат одинаковое количество цифр

вы можете умножить каждое число по формуле (10^N - log10 (число)), N является число больше, чем log10 любого из ваших номеров.

+0

Хотя это, например, было бы равным 100, например (как и оригинал). – 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__ 

Некоторые тайминги ... Во-первых, с пустыми @x:

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

Теперь, с 10,000 случайно сгенерированных элементов:

TimeThis : Elapsed Time : 00:00:00.219 

Это включает в себя время, необходимое для создания 10000 но не время вывода их на консоль. Результат добавляет около секунды.

Таким образом, сэкономить время программиста ;-)

+0

Эй, круто .. спасибо за цифры. – Skylark

4

Поскольку вы упомянули 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 работает автоматически через авто-бокс)

1

псевдокод:

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 
} 

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

unmunge - это инверсия munge.

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

1

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

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

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 не имеет unsigned ints, вам придется немного изменить его. Он использует много рабочей памяти (в два раза больше размера ввода), но это все равно меньше, чем ваш первоначальный подход. Это может быть быстрее, чем преобразование в строки на лету в компараторе, но оно использует больше пиковой памяти. В зависимости от GC это может привести к сокращению объема памяти, хотя и требует меньше сбора.

0

Один действительно Hacky метод (с использованием C) будет:

  • создать новый массив всех значений преобразуется в поплавки
  • сделать вид, используя мантиссы (мантисса) биты для сравнения

В Java (от here):

long bits = Double.doubleToLongBits(5894.349580349); 

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

s o Вы бы отсортировали по длинному mantissa здесь.

+1

Это сортирует их лексикографически в базе 2 (не теряя точности). Участник хочет, чтобы их отсортировали лексикографически в базе 10. Так что вы сказали, но используя BigDecimal, могли бы стать победителем. Наверное, не (намного) быстрее, чем String. –

1

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

1

Вопрос не указывает, как обрабатывать отрицательные целые числа в лексикографическом порядке сортировки. Строковые методы, представленные ранее, обычно сортируют отрицательные значения на фронте; например, {-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 после сортировки с Arrays.sort(sar,lexCompare);, где lexCompare похож на Comparator показанный в ответ Джейсон Коэна.

В разделе «лар с журнала» показывает массив lars после сортировки по Arrays.sort(lars,slogCompare);, иллюстрирующая способ логарифм основе, что дает тот же порядок, как это делают 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"); 
    } 
} 
Смежные вопросы