Алгоритм смежных чисел - PullRequest
       27

Алгоритм смежных чисел

9 голосов
/ 29 октября 2008

Под этим я подразумеваю:

При заданном наборе чисел:

1,2,3,4,5 становится "1-5".

1,2,3,5,7,9,10,11,12,14 становится "1-3, 5, 7, 9-12, 14"

Это лучшее, что мне удалось придумать: [C #]

Что кажется мне немного неаккуратным, поэтому вопрос в том, есть ли более удобочитаемое и / или элегантное решение для этого?

public static string[] FormatInts(int[] ints)
{
    if (ints == null)
        throw new ArgumentNullException("ints"); // hey what are you doing?

    if (ints.Length == 0)
        return new string[] { "" }; // nothing to process

    if (ints.Length == 1)
        return new string[] { ints[0].ToString() }; // nothing to process

    Array.Sort<int>(ints); // need to sort these lil' babies
    List<string> values = new List<string>();

    int lastNumber  = ints[0]; // start with the first number
    int firstNumber = ints[0]; // same as above

    for (int i = 1; i < ints.Length; i++)
    {
        int current     = ints[i];
        int difference  = (lastNumber - current ); // compute difference between last number and current number

        if (difference == -1) // the numbers are adjacent
        {
            if (firstNumber == 0) // this is the first of the adjacent numbers
            {
                firstNumber = lastNumber;
            }
            else // we're somehow in the middle or at the end of the adjacent number set
            {
                lastNumber = current;
                continue;
            }
        }
        else
        {
            if (firstNumber > 0 && firstNumber != lastNumber) // get ready to print a set of numbers
            {
                values.Add(string.Format("{0}-{1}", firstNumber, lastNumber));
                firstNumber = 0; // reset
            }
            else // print a single value
            {
                values.Add(string.Format("{0}", lastNumber));
            }
        }

        lastNumber = current;
    }

    if (firstNumber > 0) // if theres anything left, print it out
    {
        values.Add(string.Format("{0}-{1}", firstNumber, lastNumber));                
    }

    return values.ToArray();
}

Ответы [ 13 ]

10 голосов
/ 29 октября 2008

Я переписал ваш код так:

    public static string[] FormatInts(int[] ints)
    {
        Array.Sort<int>(ints);
        List<string> values = new List<string>();

        for (int i = 0; i < ints.Length; i++)
        {
            int groupStart = ints[i];
            int groupEnd = groupStart;
            while (i < ints.Length - 1 && ints[i] - ints[i + 1] == -1)
            {
                groupEnd = ints[i + 1];
                i++;
            }
            values.Add(string.Format(groupEnd == groupStart ? "{0}":"{0} - {1}", groupStart, groupEnd));
        }
        return values.ToArray();
    }

А потом:

/////////////////
int[] myInts = { 1,2,3,5,7,9,10,11,12,14 };
string[] result = FormatInts(myInts); // now result haves "1-3", "5", "7", "9-12", "14"
3 голосов
/ 16 декабря 2009

Я немного опоздал на вечеринку, но в любом случае, вот моя версия с использованием Linq:

public static string[] FormatInts(IEnumerable<int> ints)
{
 var intGroups = ints
  .OrderBy(i => i)
  .Aggregate(new List<List<int>>(), (acc, i) =>
  {
   if (acc.Count > 0 && acc.Last().Last() == i - 1) acc.Last().Add(i);
   else acc.Add(new List<int> { i });

   return acc;
  });

 return intGroups
  .Select(g => g.First().ToString() + (g.Count == 1 ? "" : "-" + g.Last().ToString()))
  .ToArray();
}
3 голосов
/ 29 октября 2008

См. Как бы вы отобразили массив целых чисел в виде набора диапазонов? (Алгоритм)

Мой ответ на поставленный выше вопрос:

void ranges(int n; int a[n], int n)
{
  qsort(a, n, sizeof(*a), intcmp);
  for (int i = 0; i < n; ++i) {
    const int start = i;
    while(i < n-1 and a[i] >= a[i+1]-1)
      ++i;
    printf("%d", a[start]);
    if (a[start] != a[i])
      printf("-%d", a[i]);
    if (i < n-1)
      printf(",");
  }
  printf("\n");
}
3 голосов
/ 29 октября 2008

Чистый функциональный Python:

#!/bin/env python

def group(nums):
    def collect((acc, i_s, i_e), n):
        if n == i_e + 1: return acc, i_s, n
        return acc + ["%d"%i_s + ("-%d"%i_e)*(i_s!=i_e)], n, n
    s = sorted(nums)+[None]
    acc, _, __ = reduce(collect, s[1:], ([], s[0], s[0]))
    return ", ".join(acc)

assert group([1,2,3,5,7,9,10,11,12,14]) == "1-3, 5, 7, 9-12, 14"
1 голос
/ 25 августа 2010

Erlang, выполнять также сортировку и уникальность на входе и может генерировать программно многократно используемую пару, а также строковое представление.

group(List) ->
    [First|_] = USList = lists:usort(List),
    getnext(USList, First, 0).
getnext([Head|Tail] = List, First, N) when First+N == Head ->
    getnext(Tail, First, N+1);
getnext([Head|Tail] = List, First, N) ->
    [ {First, First+N-1} | getnext(List, Head, 0) ];
getnext([], First, N) -> [{First, First+N-1}].
%%%%%% pretty printer
group_to_string({X,X}) -> integer_to_list(X);
group_to_string({X,Y}) -> integer_to_list(X) ++ "-" ++ integer_to_list(Y);
group_to_string(List) -> [group_to_string(X) || X <- group(List)].

Тест получения программно многоразовых пар:

shell> тестирование: группа ([34,3415,56,58,57,11,12,13,1,2,3,3,4,5]).

результат> [{1,5}, {11,13}, {34,34}, {56,58}, {3415,3415}]

Тест на получение "красивой" строки:

shell> testing: group_to_string ([34,3415,56,58,57,11,12,13,1,2,3,3,4,5]).

результат> ["1-5", "11-13", "34", "56-58", "3415"]

надеюсь, это поможет прощай

1 голос
/ 31 октября 2008

Вот моя запись на Хаскеле:

runs lst = map showRun $ runs' lst

runs' l = reverse $ map reverse $ foldl newOrGlue [[]] l 

showRun [s] = show s
showRun lst = show (head lst) ++ "-" ++ (show $ last lst)

newOrGlue [[]] e = [[e]]
newOrGlue (curr:other) e | e == (1 + (head curr)) = ((e:curr):other)
newOrGlue (curr:other) e | otherwise              = [e]:(curr:other)

и примерный прогон:

T> runs [1,2,3,5,7,9,10,11,12,14]

["1-3","5","7","9-12","14"]
1 голос
/ 30 октября 2008

Стенограмма интерактивного J сеанса (ввод пользователя имеет отступ с 3 пробелами, текст в полях ASCII - вывод J):

   g =: 3 : '<@~."1((y~:1+({.,}:)y)#y),.(y~:(}.y,{:y)-1)#y'@/:~"1
   g 1 2 3 4 5
+---+
|1 5|
+---+
   g 1 2 3 5 7 9 10 11 12 14
+---+-+-+----+--+
|1 3|5|7|9 12|14|
+---+-+-+----+--+
   g 12 2 14 9 1 3 10 5 11 7
+---+-+-+----+--+
|1 3|5|7|9 12|14|
+---+-+-+----+--+
   g2 =: 4 : '<(>x),'' '',>y'/@:>@:(4 :'<(>x),''-'',>y'/&.>)@((<@":)"0&.>@g)
   g2 12 2 14 9 1 3 10 5 11 7
+---------------+
|1-3 5 7 9-12 14|
+---------------+
   (;g2) 5 1 20 $ (i.100) /: ? 100 $ 100
+-----------------------------------------------------------+
|20 39 82 33 72 93 15 30 85 24 97 60 87 44 77 29 58 69 78 43|
|                                                           |
|67 89 17 63 34 41 53 37 61 18 88 70 91 13 19 65 99 81  3 62|
|                                                           |
|31 32  6 11 23 94 16 73 76  7  0 75 98 27 66 28 50  9 22 38|
|                                                           |
|25 42 86  5 55 64 79 35 36 14 52  2 57 12 46 80 83 84 90 56|
|                                                           |
| 8 96  4 10 49 71 21 54 48 51 26 40 95  1 68 47 59 74 92 45|
+-----------------------------------------------------------+
|15 20 24 29-30 33 39 43-44 58 60 69 72 77-78 82 85 87 93 97|
+-----------------------------------------------------------+
|3 13 17-19 34 37 41 53 61-63 65 67 70 81 88-89 91 99       |
+-----------------------------------------------------------+
|0 6-7 9 11 16 22-23 27-28 31-32 38 50 66 73 75-76 94 98    |
+-----------------------------------------------------------+
|2 5 12 14 25 35-36 42 46 52 55-57 64 79-80 83-84 86 90     |
+-----------------------------------------------------------+
|1 4 8 10 21 26 40 45 47-49 51 54 59 68 71 74 92 95-96      |
+-----------------------------------------------------------+

Читаемый и элегантный в глазах смотрящего: D

Это было хорошее упражнение! Он предлагает следующий сегмент Perl:

sub g {
    my ($i, @r, @s) = 0, local @_ = sort {$a<=>$b} @_;
    $_ && $_[$_-1]+1 == $_[$_] || push(@r, $_[$_]),
    $_<$#_ && $_[$_+1]-1 == $_[$_] || push(@s, $_[$_]) for 0..$#_;
    join ' ', map {$_ == $s[$i++] ? $_ : "$_-$s[$i-1]"} @r;
}

Добавление

На простом английском языке этот алгоритм находит все элементы, где предыдущий элемент не на единицу меньше, использует их для нижних границ; находит все элементы, у которых следующий элемент не больше, использует их для верхних границ; и объединяет два списка по пунктам.

Поскольку J довольно неясен, вот краткое объяснение того, как работает этот код:

x /: y сортирует массив x по y. ~ может превратить двоичный глагол в возвратную монаду, поэтому /:~ означает «сортировать массив по себе».

3 : '...' объявляет монадический глагол (способ J сказать «функция, принимающая один аргумент»). @ означает композицию функции, поэтому g =: 3 : '...' @ /:~ означает "g установлена ​​для функции, которую мы определяем, но с ее аргументом, отсортированным первым". "1 говорит, что мы работаем с массивами, а не с таблицами или чем-то более высокой размерности.

Примечание: y - это всегда имя единственного аргумента монадического глагола.

{. принимает первый элемент массива (head), а }: принимает все, кроме последнего (curtail). ({.,}:)y эффективно дублирует первый элемент y и обрезает последний элемент. 1+({.,}:)y добавляет 1 ко всему, и ~: сравнивает два массива: true, где они различны, и false, где они одинаковые, поэтому y~:1+({.,}:)y - это массив, истинный во всех индексах y, где Элемент не равен ни на один больше, чем элемент, который предшествовал ему. (y~:1+({.,}:)y)#y выбирает все элементы y, если свойство, указанное в предыдущем предложении, имеет значение true.

Аналогично, }. принимает все, кроме первого элемента массива (behead), а {: - последний (tail), поэтому }.y,{:y - все, кроме первого элемента y, с последним элементом дублируется. (}.y,{:y)-1 вычитает 1 для всего этого, и снова ~: сравнивает два массива поэлементно для неравенства, в то время как # выбирает.

,. объединяет два массива в массив из двух элементов. ~. нумерует список (удаляет дубликаты) и получает ранг "1, поэтому он работает с внутренними двухэлементными массивами, а не с массивом верхнего уровня. Это @, составленный из <, который помещает каждый вложенный массив в прямоугольник (в противном случае J снова расширит каждый вложенный массив для формирования 2D-таблицы).

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

1 голос
/ 29 октября 2008

Моя первая мысль в Python:

def seq_to_ranges(seq):
    first, last = None, None
    for x in sorted(seq):
        if last != None and last + 1 != x:
            yield (first, last)
            first = x
        if first == None: first = x
        last = x
    if last != None: yield (first, last)
def seq_to_ranges_str(seq):
    return ", ".join("%d-%d" % (first, last) if first != last else str(first) for (first, last) in seq_to_ranges(seq))

Возможно, может быть чище, но все равно легко.

Простой перевод на Haskell:

import Data.List
seq_to_ranges :: (Enum a, Ord a) => [a] -> [(a, a)]
seq_to_ranges = merge . foldl accum (id, Nothing) . sort where
    accum (k, Nothing) x = (k, Just (x, x))
    accum (k, Just (a, b)) x | succ b == x = (k, Just (a, x))
                             | otherwise   = (k . ((a, b):), Just (x, x))
    merge (k, m) = k $ maybe [] (:[]) m
seq_to_ranges_str :: (Enum a, Ord a, Show a) => [a] -> String
seq_to_ranges_str = drop 2 . concatMap r2s . seq_to_ranges where
    r2s (a, b) | a /= b    = ", " ++ show a ++ "-" ++ show b
               | otherwise = ", " ++ show a

Примерно то же самое.

1 голос
/ 29 октября 2008

Короткий и сладкий Рубин

def range_to_s(range)
  return range.first.to_s if range.size == 1
  return range.first.to_s + "-" + range.last.to_s
end

def format_ints(ints)
  range = []
  0.upto(ints.size-1) do |i|
    range << ints[i]
    unless (range.first..range.last).to_a == range
      return range_to_s(range[0,range.length-1]) + "," + format_ints(ints[i,ints.length-1])
    end
  end
  range_to_s(range)  
end
1 голос
/ 29 октября 2008

Как я уже писал в комментарии, я не фанат использования значения 0 в качестве флага, поэтому firstNumber является значением и флагом.

Я сделал быструю реализацию алгоритма на Java, смело пропуская тесты валидности, которые вы уже правильно рассмотрели ...

public class IntListToRanges
{
    // Assumes all numbers are above 0
    public static String[] MakeRanges(int[] numbers)
    {
        ArrayList<String> ranges = new ArrayList<String>();

        Arrays.sort(numbers);
        int rangeStart = 0;
        boolean bInRange = false;
        for (int i = 1; i <= numbers.length; i++)
        {
            if (i < numbers.length && numbers[i] - numbers[i - 1] == 1)
            {
                if (!bInRange)
                {
                    rangeStart = numbers[i - 1];
                    bInRange = true;
                }
            }
            else
            {
                if (bInRange)
                {
                    ranges.add(rangeStart + "-" + numbers[i - 1]);
                    bInRange = false;
                }
                else
                {
                    ranges.add(String.valueOf(numbers[i - 1]));
                }
            }
        }
        return ranges.toArray(new String[ranges.size()]);
    }

    public static void ShowRanges(String[] ranges)
    {
        for (String range : ranges)
        {
            System.out.print(range + ","); // Inelegant but quickly coded...
        }
        System.out.println();
    }

    /**
     * @param args
     */
    public static void main(String[] args)
    {
        int[] an1 = { 1,2,3,5,7,9,10,11,12,14,15,16,22,23,27 };
        int[] an2 = { 1,2 };
        int[] an3 = { 1,3,5,7,8,9,11,12,13,14,15 };
        ShowRanges(MakeRanges(an1));
        ShowRanges(MakeRanges(an2));
        ShowRanges(MakeRanges(an3));
        int L = 100;
        int[] anr = new int[L];
        for (int i = 0, c = 1; i < L; i++)
        {
            int incr = Math.random() > 0.2 ? 1 : (int) Math.random() * 3 + 2;
            c += incr;
            anr[i] = c;
        }
        ShowRanges(MakeRanges(anr));
    }
}

Я не скажу, что он более элегантен / эффективен, чем ваш алгоритм, конечно ... Просто что-то другое.

Обратите внимание, что 1,5,6,9 можно записать как 1,5-6,9 или 1,5,6,9, но не уверен, что лучше (если есть).

Я помню, что делал нечто подобное (в C) для группировки номеров сообщений по диапазонам Imap, так как это более эффективно. Полезный алгоритм.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...