Adjacent number algorithm grouper

前端 未结 13 1698
不思量自难忘°
不思量自难忘° 2021-02-15 06:55

By which I mean this:

Given the input set of numbers:

1,2,3,4,5 becomes \"1-5\".

1,2,3,5,7,9,10,11,12,14 becomes \"1-3, 5, 7, 9-12, 14\"

This is

相关标签:
13条回答
  • 2021-02-15 07:26

    I'm a bit late to the party, but anyway, here is my version using 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();
    }
    
    0 讨论(0)
  • 2021-02-15 07:29

    Erlang , perform also sort and unique on input and can generate programmatically reusable pair and also a string representation.

    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)].
    

    Test getting programmatically reusable pairs:

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

    result> [{1,5},{11,13},{34,34},{56,58},{3415,3415}]

    Test getting "pretty" string:

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

    result> ["1-5","11-13","34","56-58","3415"]

    hope it helps bye

    0 讨论(0)
  • 2021-02-15 07:30

    VBA

    Public Function convertListToRange(lst As String) As String
        Dim splLst() As String
        splLst = Split(lst, ",")
        Dim x As Long
        For x = 0 To UBound(splLst)
            Dim groupStart As Integer
            groupStart = splLst(x)
            Dim groupEnd As Integer
            groupEnd = groupStart
            Do While (x <= UBound(splLst) - 1)
                If splLst(x) - splLst(x + 1) <> -1 Then Exit Do
                groupEnd = splLst(x + 1)
                x = x + 1
            Loop
            convertListToRange = convertListToRange & IIf(groupStart = groupEnd, groupStart & ",", groupStart & "-" & groupEnd & ",")
        Next x
        convertListToRange = Left(convertListToRange, Len(convertListToRange) - 1)
    End Function
    

    convertListToRange("1,2,3,7,8,9,11,12,99,100,101")
    Return: "1-3,7-9,11-12,99-101"

    0 讨论(0)
  • 2021-02-15 07:35

    My first thought, in 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))
    

    Possibly could be cleaner, but it's still waaay easy.

    Plain translation to 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
    

    About the same.

    0 讨论(0)
  • 2021-02-15 07:36

    Transcript of an interactive J session (user input is indented 3 spaces, text in ASCII boxes is J output):

       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      |
    +-----------------------------------------------------------+
    

    Readable and elegant are in the eye of the beholder :D

    That was a good exercise! It suggests the following segment of 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;
    }
    

    Addendum

    In plain English, this algorithm finds all items where the previous item is not one less, uses them for the lower bounds; finds all items where the next item is not one greater, uses them for the upper bounds; and combines the two lists together item-by-item.

    Since J is pretty obscure, here's a short explanation of how that code works:

    x /: y sorts the array x on y. ~ can make a dyadic verb into a reflexive monad, so /:~ means "sort an array on itself".

    3 : '...' declares a monadic verb (J's way of saying "function taking one argument"). @ means function composition, so g =: 3 : '...' @ /:~ means "g is set to the function we're defining, but with its argument sorted first". "1 says that we operate on arrays, not tables or anything of higher dimensionality.

    Note: y is always the name of the only argument to a monadic verb.

    {. takes the first element of an array (head) and }: takes all but the last (curtail). ({.,}:)y effectively duplicates the first element of y and lops off the last element. 1+({.,}:)y adds 1 to it all, and ~: compares two arrays, true wherever they are different and false wherever they are the same, so y~:1+({.,}:)y is an array that is true in all the indices of y where an element is not equal to one more than the element that preceded it. (y~:1+({.,}:)y)#y selects all elements of y where the property stated in the previous sentence is true.

    Similarly, }. takes all but the first element of an array (behead) and {: takes the last (tail), so }.y,{:y is all but the first element of y, with the last element duplicated. (}.y,{:y)-1 subtracts 1 to it all, and again ~: compares two arrays item-wise for non-equality while # picks.

    ,. zips the two arrays together, into an array of two-element arrays. ~. nubs a list (eliminates duplicates), and is given the "1 rank, so it operates on the inner two-element arrays rather than the top-level array. This is @ composed with <, which puts each subarray into a box (otherwise J will extend each subarray again to form a 2D table).

    g2 is a mess of boxing and unboxing (otherwise J will pad strings to equal length), and is pretty uninteresting.

    0 讨论(0)
  • 2021-02-15 07:37

    As I wrote in comment, I am not fan of the use of value 0 as flag, making firstNumber both a value and a flag.

    I did a quick implementation of the algorithm in Java, boldly skipping the validity tests you already correctly covered...

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

    I won't say it is more elegant/efficient than your algorithm, of course... Just something different.

    Note that 1,5,6,9 can be written either 1,5-6,9 or 1,5,6,9, not sure what is better (if any).

    I remember having done something similar (in C) to group message numbers to Imap ranges, as it is more efficient. A useful algorithm.

    0 讨论(0)
提交回复
热议问题