将一个逗号分隔的数字列表设置成范围。

时间:2022-06-23 12:03:04

I'm looking for a clever way to do the following operation:

我正在寻找一个聪明的方法来做以下操作:

Take a list of numbers:

取一个数字列表:

1, 2, 3, 4, 5, 12, 13, 14, 19

12 3 4 5 12 13 14 19。

and compact it into a string like so:

把它压缩成这样的字符串:

1-5, 12-14, 19

1 - 5、12 - 14、19

With the following rule: only compress into a range (i.e. use a dash) when the count of numbers in the range is 3 or more.

使用以下规则:当范围内的数字计数为3或更多时,只将其压缩为一个范围(即使用破折号)。

I.e.: 1, 2, 4, 5 would result in: 1, 2, 4, 5 and NOT: 1-2, 4-5

即。1、2、4、5的结果是1、2、4、5,而不是1- 2,4 -5。

5 个解决方案

#1


3  

I can only think about a custom collector... You can obviously create a method that would return this collector and the code would be really compact in this case, provided that the collector is hidden via a static factory method.

我只能想到一个定制的收藏家……显然,您可以创建一个方法来返回这个收集器,在这种情况下,代码将非常紧凑,只要收集器通过静态工厂方法隐藏。

Notice how the combiner is doing basically nothing, not good for parallel coding. I'm still trying to think of a good way to provide an implementation for it.

注意,组合器基本上什么都不做,对并行编码不太好。我仍然在想一个提供实现的好方法。

 List<String> result = IntStream.of(1, 2, 3, 4, 5, 12, 13, 14, 19)
            .boxed()
            .collect(Collector.of(
                    () -> {
                        List<List<Integer>> list = new ArrayList<>();
                        list.add(new ArrayList<>());
                        return list;
                    },
                    (list, x) -> {
                        List<Integer> inner = list.get(list.size() - 1);
                        if (inner.size() == 0) {
                            inner.add(x);
                        } else {
                            int lastElement = inner.get(inner.size() - 1);
                            if (lastElement == x - 1) {
                                inner.add(x);
                            } else {
                                List<Integer> oneMore = new ArrayList<>();
                                oneMore.add(x);
                                list.add(oneMore);
                            }
                        }
                    },
                    (left, right) -> {
                        throw new IllegalArgumentException("No parallel!");
                    },

                    list -> {

                        return list.stream()
                                .map(inner -> {
                                    if (inner.size() > 1) {
                                        return inner.get(0) + "-" + inner.get(inner.size() - 1);
                                    }
                                    return "" + inner.get(0);
                                }).collect(Collectors.toList());

                    }));

    System.out.println(result);

#2


8  

Now that we have seen several Stream variants, here the non-Stream variant for comparison:

现在我们已经看到了几个流变体,这里是用于比较的非流变体:

private static StringBuilder appendRange(StringBuilder sb, int start, int previous) {
    sb.append(start);
    if(start!=previous) sb.append(previous-start>1? " - ": ", ").append(previous);
    return sb;
}
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 12, 13, 14, 19);
StringBuilder sb = new StringBuilder();
int previous = list.get(0), start = previous;
for(int next: list.subList(1, list.size())) {
    if(previous+1 != next) {
        appendRange(sb, start, previous).append(", ");
        start = next;
    }
    previous = next;
}
String result = appendRange(sb, start, previous).toString();

#3


4  

Edit

I'm sorry, I misunderstand your requirement since my English is so bad. Thank everybody of forgiveness. I'll give a configurable compress method later to thank everybody.

很抱歉,我误解了你的要求,因为我的英语很差。谢谢大家的原谅。稍后我将给出一个可配置的压缩方法,以感谢所有人。

After working, I found I can't apply your rule above by use stream easily: "the count of numbers in the range is 3 or more." . so I down to the traditional approach. I wish it can helped you.

在工作之后,我发现我不能很容易地应用你的规则:“范围内的数字数是3或更多。”。所以我选择了传统的方法。我希望它能帮到你。

//        v--- "1-5, 12-14, 19"
String ranges = compress(asList(1,2,3,4,5, 12,13,14, 19)).collect(joining(", "));

//              v--- ["1", "2"]
Stream<String> lessThan3 = compress(asList(1, 2));

//              v--- ["1-4"]
Stream<String> step2 = compress(asList(1, 3, 4), 2, 3);

Build the range of Stream<String> immediately by using Stream.Builder.

通过使用Stream.Builder,立即构建流的范围

static Stream<String> compress(List<Integer> numbers) {
    return compress(numbers, 1, 3);
}

static Stream<String> compress(List<Integer> numbers, int step, int minSize) {
    Builder<String> ranges = Stream.builder();
    IntBuffer queue = IntBuffer.allocate(minSize + 1);
    for (int it : numbers) {
        int prev = queue.position() - 1;
        if (prev >= 0 && queue.get(prev) + step < it) {
            copy(queue, ranges, minSize);
            queue.put(it);
        } else {
            if (queue.hasRemaining()) {
                queue.put(it);
            } else {
                queue.put(prev, it);
            }
        }
    }
    return copy(queue, ranges, minSize).build();
}

static Builder<String> copy(IntBuffer queue, Builder<String> target, int minSize) {
    queue.flip();
    if (queue.limit() >= minSize) {
        target.add(format("%d-%d", queue.get(0), queue.get(queue.limit() - 1)));
    } else {
        while (queue.hasRemaining()) target.add(Integer.toString(queue.get()));
    }
    queue.clear();
    return target;
}

Edit2

Build the range of Stream<String> lazily by using Spliterator.

使用Spliterator构建流 延迟的范围。

static Stream<String> compress(List<Integer> numbers, int step, int minSize) {
    return compress(numbers, minSize, (prev, current) -> current - prev <= step);
}


static Stream<String> compress(List<Integer> numbers,
                               int minSize,
                               IntBiPredicate rule) {
    return StreamSupport.stream(spliterator(numbers, minSize, rule), false);
}


static AbstractSpliterator<String> spliterator(List<Integer> numbers,
                                               int minSize,
                                               IntBiPredicate rule) {
    return new AbstractSpliterator<String>(numbers.size(), ORDERED) {
        private Iterator<Integer> data;
        private Queue<String> queue;
        private IntBuffer buff;


        @Override
        public boolean tryAdvance(Consumer<? super String> action) {
            init();
            return tryConsuming(action) || evaluate();
        }

        private void init() {
            if (data != null) return;
            data = numbers.iterator();
            queue = new LinkedList<>();
            buff = IntBuffer.allocate(minSize + 1);
        }

        private boolean tryConsuming(Consumer<? super String> action) {
            if (queue.isEmpty()) return false;
            action.accept(queue.poll());
            return true;
        }

        private boolean evaluate() {
            if (!data.hasNext()) {
                return buff.position() > 0 && fill();
            } else {
                evaluateNext(data.next());
                return true;
            }
        }

        private void evaluateNext(int it) {
            int prev = buff.position() - 1;
            if (prev >= 0 && !rule.test(buff.get(prev), it)) {
                fill();
                buff.put(it);
            } else {
                if (!buff.hasRemaining()) {
                    buff.put(buff.position() - 1, it);
                } else {
                    buff.put(it);
                }
            }
        }

        private boolean fill() {
            buff.flip();
            if (buff.limit() >= minSize) {
                int min = buff.get(0);
                int max = buff.get(buff.limit() - 1);
                queue.add(format("%d-%d", min, max));
            } else {
                while (buff.hasRemaining()) {
                    queue.add(Integer.toString(buff.get()));
                }
            }
            buff.clear();
            return true;
        }
    };
}

interface IntBiPredicate {
    boolean test(int first, int second);
}

Oldest

How about this? String ranges are grouped by n/m:

这个怎么样?字符串范围按n/m分组:

int m = 5 + 1; 
//        v--- "1-5, 12-14, 19"
String ranges =
     Stream.of(1, 2, 3, 4, 5, 12, 13, 14, 19)
           //       v--- calculate ranges until grouping is done
           .collect(collectingAndThen(
                groupingBy(
                    //     v--- group n by n/m
                    n -> n / m,
                    TreeMap::new,
                    // v--- summarizing the current group
                    summarizingInt(Integer::intValue) 
                ),
                summary -> summary.values()
                                  .stream()
                                  .map(
                       //create range string from IntSummaryStats ---v        
                                      it ->String.format(
                                          it.getMin()==it.getMax()?"%d":"%d-%d",
                                          it.getMin(),
                                          it.getMax()
                                      )
                                  )
                                  .collect(joining(", "))
            ));

#4


1  

I've written a specific implementation of Collector which should do what you want.

我已经写了一个特定的收集器实现,它应该做您想做的事情。

NOTICE: this implementation fails horribly when trying to use in parallel

注意:当尝试并行使用时,这个实现失败了。

public class RangeCollector implements Collector<Integer, List<String>, List<String>>{

    private int last = 0;
    private LinkedList<Integer> intermediate = new LinkedList<>();

    @Override
    public Supplier<List<String>> supplier(){
        return ArrayList::new;
    }

    @Override
    public BiConsumer<List<String>, Integer> accumulator(){
        return ( finalList, current ) -> {
            if( current - last == 1 ){ // check if adjacent to last value
                intermediate.add(current);
            } else{
                if( intermediate.size() > 2 ){
                    finalList.add(intermediate.getFirst() + "-" + intermediate.getLast()); // add new range
                } else{
                    addLeftOverValues(finalList);
                }
                intermediate.clear();
                intermediate.add(current);
            }
            last = current;
        };
    }

    @Override
    public BinaryOperator<List<String>> combiner(){
        return (list, list2) -> {
            list.addAll(list2);
            return list;
        };
    }

    @Override
    public Function<List<String>, List<String>> finisher(){
        return ( finalList ) -> {
            if( !intermediate.isEmpty() ){
                addLeftOverValues(finalList);
            }
            return finalList;
        };
    }

    @Override
    public Set<Characteristics> characteristics(){
        return EnumSet.noneOf(Characteristics.class);
    }

    private void addLeftOverValues( List<String> list ){
        list.addAll(
            intermediate.stream()
                .map(String::valueOf)
                .collect(Collectors.toList())
       );
    }
}

which then can be used like this:

然后可以这样使用:

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 12, 13, 14, 19);
System.out.println(list.stream().collect(new RangeCollector()));

which finally prints [1-6, 12-14, 19]

最后打印出来[1-6,12-14,19]

#5


0  

I would like to suggest solution that is even more compact:

我想提出更紧凑的解决方案:

import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

public class CompactComaDelimitedNumbersTest {

    @Test
    public void testCompactingNumbersWithJavaStream() {
        //given:
        final List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 12, 13, 14, 19);

        //when:
        final List<Object> finalResult = list.stream()
                // Firstly let's pair every number with a number it starts from in
                // given sequence
                .reduce(new ArrayList<Pair<Integer, Integer>>(), (result, number) -> {
                    if (result.isEmpty()) {
                        result.add(new Pair<>(number, number));
                        return result;
                    }

                    final Pair<Integer, Integer> previous = result.get(result.size() - 1);
                    if (previous.getFirst() + 1 == number) {
                        result.add(new Pair<>(number, previous.getSecond()));
                    } else {
                        result.add(new Pair<>(number, number));
                    }
                    return result;
                }, (a, b) -> a)
                // Now let's group list of pair into a Map where key is a number 'from' and value is a list of values
                // in given sequence starting from 'from' number
                .stream()
                .collect(Collectors.groupingBy(Pair::getSecond, Collectors.mapping(Pair::getFirst, Collectors.toList())))
                // Finally let's sort entry set and convert into expected format
                .entrySet()
                .stream()
                .sorted(Comparator.comparing(Map.Entry::getKey))
                .map(e -> e.getValue().size() < 3 ?
                        e.getValue() :
                        Collections.singletonList(String.format("%d-%d", e.getValue().get(0), e.getValue().get(e.getValue().size() - 1))))
                .flatMap(Collection::stream)
                .collect(Collectors.toList());

        //then:
        assertThat(finalResult, is(equalTo(Arrays.asList("1-5", "12-14", 19))));

    }

    static final class Pair<T,K> {
        private final T first;
        private final K second;
        Pair(T first, K second) {
            this.first = first;
            this.second = second;
        }
        public T getFirst() {
            return first;
        }

        public K getSecond() {
            return second;
        }

        @Override
        public String toString() {
            return "Pair{" +
                    "first=" + first +
                    ", second=" + second +
                    '}';
        }
    }
}

It pairs every number with a starting number for a current sequence, then groups all pairs by this from number and lastly it converts map into list of ranges like 1-5 or plain numbers. I hope you like this solution.

它对每个数字与一个当前序列的起始号码配对,然后由这个数字组成所有对,最后它将地图转换成1-5或普通数字的列表。我希望你喜欢这个解决方案。

#1


3  

I can only think about a custom collector... You can obviously create a method that would return this collector and the code would be really compact in this case, provided that the collector is hidden via a static factory method.

我只能想到一个定制的收藏家……显然,您可以创建一个方法来返回这个收集器,在这种情况下,代码将非常紧凑,只要收集器通过静态工厂方法隐藏。

Notice how the combiner is doing basically nothing, not good for parallel coding. I'm still trying to think of a good way to provide an implementation for it.

注意,组合器基本上什么都不做,对并行编码不太好。我仍然在想一个提供实现的好方法。

 List<String> result = IntStream.of(1, 2, 3, 4, 5, 12, 13, 14, 19)
            .boxed()
            .collect(Collector.of(
                    () -> {
                        List<List<Integer>> list = new ArrayList<>();
                        list.add(new ArrayList<>());
                        return list;
                    },
                    (list, x) -> {
                        List<Integer> inner = list.get(list.size() - 1);
                        if (inner.size() == 0) {
                            inner.add(x);
                        } else {
                            int lastElement = inner.get(inner.size() - 1);
                            if (lastElement == x - 1) {
                                inner.add(x);
                            } else {
                                List<Integer> oneMore = new ArrayList<>();
                                oneMore.add(x);
                                list.add(oneMore);
                            }
                        }
                    },
                    (left, right) -> {
                        throw new IllegalArgumentException("No parallel!");
                    },

                    list -> {

                        return list.stream()
                                .map(inner -> {
                                    if (inner.size() > 1) {
                                        return inner.get(0) + "-" + inner.get(inner.size() - 1);
                                    }
                                    return "" + inner.get(0);
                                }).collect(Collectors.toList());

                    }));

    System.out.println(result);

#2


8  

Now that we have seen several Stream variants, here the non-Stream variant for comparison:

现在我们已经看到了几个流变体,这里是用于比较的非流变体:

private static StringBuilder appendRange(StringBuilder sb, int start, int previous) {
    sb.append(start);
    if(start!=previous) sb.append(previous-start>1? " - ": ", ").append(previous);
    return sb;
}
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 12, 13, 14, 19);
StringBuilder sb = new StringBuilder();
int previous = list.get(0), start = previous;
for(int next: list.subList(1, list.size())) {
    if(previous+1 != next) {
        appendRange(sb, start, previous).append(", ");
        start = next;
    }
    previous = next;
}
String result = appendRange(sb, start, previous).toString();

#3


4  

Edit

I'm sorry, I misunderstand your requirement since my English is so bad. Thank everybody of forgiveness. I'll give a configurable compress method later to thank everybody.

很抱歉,我误解了你的要求,因为我的英语很差。谢谢大家的原谅。稍后我将给出一个可配置的压缩方法,以感谢所有人。

After working, I found I can't apply your rule above by use stream easily: "the count of numbers in the range is 3 or more." . so I down to the traditional approach. I wish it can helped you.

在工作之后,我发现我不能很容易地应用你的规则:“范围内的数字数是3或更多。”。所以我选择了传统的方法。我希望它能帮到你。

//        v--- "1-5, 12-14, 19"
String ranges = compress(asList(1,2,3,4,5, 12,13,14, 19)).collect(joining(", "));

//              v--- ["1", "2"]
Stream<String> lessThan3 = compress(asList(1, 2));

//              v--- ["1-4"]
Stream<String> step2 = compress(asList(1, 3, 4), 2, 3);

Build the range of Stream<String> immediately by using Stream.Builder.

通过使用Stream.Builder,立即构建流的范围

static Stream<String> compress(List<Integer> numbers) {
    return compress(numbers, 1, 3);
}

static Stream<String> compress(List<Integer> numbers, int step, int minSize) {
    Builder<String> ranges = Stream.builder();
    IntBuffer queue = IntBuffer.allocate(minSize + 1);
    for (int it : numbers) {
        int prev = queue.position() - 1;
        if (prev >= 0 && queue.get(prev) + step < it) {
            copy(queue, ranges, minSize);
            queue.put(it);
        } else {
            if (queue.hasRemaining()) {
                queue.put(it);
            } else {
                queue.put(prev, it);
            }
        }
    }
    return copy(queue, ranges, minSize).build();
}

static Builder<String> copy(IntBuffer queue, Builder<String> target, int minSize) {
    queue.flip();
    if (queue.limit() >= minSize) {
        target.add(format("%d-%d", queue.get(0), queue.get(queue.limit() - 1)));
    } else {
        while (queue.hasRemaining()) target.add(Integer.toString(queue.get()));
    }
    queue.clear();
    return target;
}

Edit2

Build the range of Stream<String> lazily by using Spliterator.

使用Spliterator构建流 延迟的范围。

static Stream<String> compress(List<Integer> numbers, int step, int minSize) {
    return compress(numbers, minSize, (prev, current) -> current - prev <= step);
}


static Stream<String> compress(List<Integer> numbers,
                               int minSize,
                               IntBiPredicate rule) {
    return StreamSupport.stream(spliterator(numbers, minSize, rule), false);
}


static AbstractSpliterator<String> spliterator(List<Integer> numbers,
                                               int minSize,
                                               IntBiPredicate rule) {
    return new AbstractSpliterator<String>(numbers.size(), ORDERED) {
        private Iterator<Integer> data;
        private Queue<String> queue;
        private IntBuffer buff;


        @Override
        public boolean tryAdvance(Consumer<? super String> action) {
            init();
            return tryConsuming(action) || evaluate();
        }

        private void init() {
            if (data != null) return;
            data = numbers.iterator();
            queue = new LinkedList<>();
            buff = IntBuffer.allocate(minSize + 1);
        }

        private boolean tryConsuming(Consumer<? super String> action) {
            if (queue.isEmpty()) return false;
            action.accept(queue.poll());
            return true;
        }

        private boolean evaluate() {
            if (!data.hasNext()) {
                return buff.position() > 0 && fill();
            } else {
                evaluateNext(data.next());
                return true;
            }
        }

        private void evaluateNext(int it) {
            int prev = buff.position() - 1;
            if (prev >= 0 && !rule.test(buff.get(prev), it)) {
                fill();
                buff.put(it);
            } else {
                if (!buff.hasRemaining()) {
                    buff.put(buff.position() - 1, it);
                } else {
                    buff.put(it);
                }
            }
        }

        private boolean fill() {
            buff.flip();
            if (buff.limit() >= minSize) {
                int min = buff.get(0);
                int max = buff.get(buff.limit() - 1);
                queue.add(format("%d-%d", min, max));
            } else {
                while (buff.hasRemaining()) {
                    queue.add(Integer.toString(buff.get()));
                }
            }
            buff.clear();
            return true;
        }
    };
}

interface IntBiPredicate {
    boolean test(int first, int second);
}

Oldest

How about this? String ranges are grouped by n/m:

这个怎么样?字符串范围按n/m分组:

int m = 5 + 1; 
//        v--- "1-5, 12-14, 19"
String ranges =
     Stream.of(1, 2, 3, 4, 5, 12, 13, 14, 19)
           //       v--- calculate ranges until grouping is done
           .collect(collectingAndThen(
                groupingBy(
                    //     v--- group n by n/m
                    n -> n / m,
                    TreeMap::new,
                    // v--- summarizing the current group
                    summarizingInt(Integer::intValue) 
                ),
                summary -> summary.values()
                                  .stream()
                                  .map(
                       //create range string from IntSummaryStats ---v        
                                      it ->String.format(
                                          it.getMin()==it.getMax()?"%d":"%d-%d",
                                          it.getMin(),
                                          it.getMax()
                                      )
                                  )
                                  .collect(joining(", "))
            ));

#4


1  

I've written a specific implementation of Collector which should do what you want.

我已经写了一个特定的收集器实现,它应该做您想做的事情。

NOTICE: this implementation fails horribly when trying to use in parallel

注意:当尝试并行使用时,这个实现失败了。

public class RangeCollector implements Collector<Integer, List<String>, List<String>>{

    private int last = 0;
    private LinkedList<Integer> intermediate = new LinkedList<>();

    @Override
    public Supplier<List<String>> supplier(){
        return ArrayList::new;
    }

    @Override
    public BiConsumer<List<String>, Integer> accumulator(){
        return ( finalList, current ) -> {
            if( current - last == 1 ){ // check if adjacent to last value
                intermediate.add(current);
            } else{
                if( intermediate.size() > 2 ){
                    finalList.add(intermediate.getFirst() + "-" + intermediate.getLast()); // add new range
                } else{
                    addLeftOverValues(finalList);
                }
                intermediate.clear();
                intermediate.add(current);
            }
            last = current;
        };
    }

    @Override
    public BinaryOperator<List<String>> combiner(){
        return (list, list2) -> {
            list.addAll(list2);
            return list;
        };
    }

    @Override
    public Function<List<String>, List<String>> finisher(){
        return ( finalList ) -> {
            if( !intermediate.isEmpty() ){
                addLeftOverValues(finalList);
            }
            return finalList;
        };
    }

    @Override
    public Set<Characteristics> characteristics(){
        return EnumSet.noneOf(Characteristics.class);
    }

    private void addLeftOverValues( List<String> list ){
        list.addAll(
            intermediate.stream()
                .map(String::valueOf)
                .collect(Collectors.toList())
       );
    }
}

which then can be used like this:

然后可以这样使用:

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 12, 13, 14, 19);
System.out.println(list.stream().collect(new RangeCollector()));

which finally prints [1-6, 12-14, 19]

最后打印出来[1-6,12-14,19]

#5


0  

I would like to suggest solution that is even more compact:

我想提出更紧凑的解决方案:

import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

public class CompactComaDelimitedNumbersTest {

    @Test
    public void testCompactingNumbersWithJavaStream() {
        //given:
        final List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 12, 13, 14, 19);

        //when:
        final List<Object> finalResult = list.stream()
                // Firstly let's pair every number with a number it starts from in
                // given sequence
                .reduce(new ArrayList<Pair<Integer, Integer>>(), (result, number) -> {
                    if (result.isEmpty()) {
                        result.add(new Pair<>(number, number));
                        return result;
                    }

                    final Pair<Integer, Integer> previous = result.get(result.size() - 1);
                    if (previous.getFirst() + 1 == number) {
                        result.add(new Pair<>(number, previous.getSecond()));
                    } else {
                        result.add(new Pair<>(number, number));
                    }
                    return result;
                }, (a, b) -> a)
                // Now let's group list of pair into a Map where key is a number 'from' and value is a list of values
                // in given sequence starting from 'from' number
                .stream()
                .collect(Collectors.groupingBy(Pair::getSecond, Collectors.mapping(Pair::getFirst, Collectors.toList())))
                // Finally let's sort entry set and convert into expected format
                .entrySet()
                .stream()
                .sorted(Comparator.comparing(Map.Entry::getKey))
                .map(e -> e.getValue().size() < 3 ?
                        e.getValue() :
                        Collections.singletonList(String.format("%d-%d", e.getValue().get(0), e.getValue().get(e.getValue().size() - 1))))
                .flatMap(Collection::stream)
                .collect(Collectors.toList());

        //then:
        assertThat(finalResult, is(equalTo(Arrays.asList("1-5", "12-14", 19))));

    }

    static final class Pair<T,K> {
        private final T first;
        private final K second;
        Pair(T first, K second) {
            this.first = first;
            this.second = second;
        }
        public T getFirst() {
            return first;
        }

        public K getSecond() {
            return second;
        }

        @Override
        public String toString() {
            return "Pair{" +
                    "first=" + first +
                    ", second=" + second +
                    '}';
        }
    }
}

It pairs every number with a starting number for a current sequence, then groups all pairs by this from number and lastly it converts map into list of ranges like 1-5 or plain numbers. I hope you like this solution.

它对每个数字与一个当前序列的起始号码配对,然后由这个数字组成所有对,最后它将地图转换成1-5或普通数字的列表。我希望你喜欢这个解决方案。