我正在寻找一种聪明的方法来执行以下操作:
列出一个数字列表:
1,2,3,4,5,12,13,14,19
并将其压缩成如下字符串:
1-5,12-14,19
使用以下规则:当范围内的数字计数为3或更多时,仅压缩到范围内(即使用破折号)。
I.e。:1,2,4,5会导致:1,2,4,5和NOT:1-2,4-5
答案 0 :(得分:9)
现在我们已经看到了几个Stream变体,这里是非Stream变体用于比较:
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();
答案 1 :(得分:4)
对不起,我误解了你的要求,因为我的英语太差了。感谢所有人的宽恕。我稍后会提供一个可配置的compress
方法来感谢大家。
上班后,我发现我无法轻松使用您的规则:&#34; 该范围内的数字计数为3或更多。 < /强>&#34; 。所以我采用传统方法。我希望它可以帮助你。
// 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);
使用Stream<String>
立即构建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;
}
使用Stream<String>
懒惰地构建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);
}
这个怎么样? String
范围按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(", "))
));
答案 2 :(得分:4)
我只能考虑一个自定义收集器......显然你可以创建一个方法来返回这个收集器,在这种情况下代码将非常紧凑,前提是收集器是通过静态工厂方法隐藏的。
注意combiner
基本上没有做什么,不利于并行编码。我仍然在想一个为它提供实现的好方法。
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);
答案 3 :(得分:1)
我已经编写了Collector
的具体实现,它应该做你想做的事。
注意:尝试并行使用时,此实现会失败
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())
);
}
}
然后可以像这样使用:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 12, 13, 14, 19);
System.out.println(list.stream().collect(new RangeCollector()));
最终打印[1-6, 12-14, 19]
答案 4 :(得分:0)
我想建议更紧凑的解决方案:
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 +
'}';
}
}
}
它将每个数字与当前序列的起始编号对,然后按此from
编号对所有对进行分组,最后将地图转换为范围列表,如1-5
或普通数字。我希望你喜欢这个解决方案。
答案 5 :(得分:0)
这是数组范围压缩。这是一个Python解决方案:
size_t
我知道这是非常糟糕的代码,还不是很理想。如果有人需要帮助,他可以参考这一点。欢迎进行编辑,以使用良好的数据结构在Python中编写代码。
答案 6 :(得分:0)
我试图使其尽可能简单,您需要首先初始化一个起始值,一个起始位置变量和一个终止位置变量。如果开始变量是连续的,则继续增加,否则将开始位置和结束位置作为当前位置变量并附加结果。如果开始和结束位置的差为零,则只需追加该位置的一个值即可。如果值是1,则将两个值分别相加。如果大于1,请在结果中添加开始和结束位置变量,并用破折号分隔。
nums = [1, 2, 3, 4, 5, 12, 13, 14, 19]
nums.sort()
print(nums)
res = []
# Initialise the values for start and end position variables along with the start
start = nums[0]
sPos = 0
ePos = 0
for i in range(1,len(nums)):
if nums[i] == start+1:
# Keep incrementing ePos and start if the numbers are consecutive
ePos = i
start=start+1
else:
# In case there is a break in the sequence, calculate the difference between start and end
if sPos == ePos:
# If both pointing at same place add the element
res.append(nums[sPos])
elif ePos - sPos == 1:
# In case there are just 2 numbers then add both of them
res.append(nums[sPos])
res.append(nums[ePos])
else:
# In case there are more than 2 numbers, then add the start and end with dash in between
res.append(str(nums[sPos])+"-"+str(nums[ePos]))
start = nums[i]
sPos = i
ePos = i
# Finally just check in the same manner for the last elements
if sPos == ePos:
res.append(nums[sPos])
elif ePos - sPos == 1:
res.append(nums[sPos])
res.append(nums[ePos])
else:
res.append(str(nums[sPos])+"-"+str(nums[ePos]))
start = nums[i]
sPos = i
ePos = i
print(res)