요소를 따라 목록을 하위 목록으로 분할
이 목록 ( List<String>)이 있습니다.
["a", "b", null, "c", null, "d", "e"]
그리고 나는 다음과 같은 것을 원합니다.
[["a", "b"], ["c"], ["d", "e"]]
즉 null, 목록 ( List<List<String>>) 목록을 얻기 위해 값을 구분 기호로 사용하여 하위 목록으로 목록을 분할하고 싶습니다 . Java 8 솔루션을 찾고 있습니다. 나는 시도 Collectors.partitioningBy했지만 내가 찾고있는 것이 확실하지 않습니다. 감사!
제가 지금 생각 해낸 유일한 해결책은 사용자 지정 수집기를 구현하는 것입니다.
솔루션을 읽기 전에 이에 대한 몇 가지 메모를 추가하고 싶습니다. 이 질문을 프로그래밍 연습으로 더 많이 가져 왔지만 병렬 스트림으로 수행 할 수 있는지 확실하지 않습니다.
따라서 파이프 라인이 병렬 로 실행되는 경우 자동으로 중단 된다는 점을 알고 있어야합니다 .
이것은 바람직한 동작 이 아니므 로 피해야 합니다. 이것이 (l1, l2) -> {l1.addAll(l2); return l1;}두 목록을 결합 할 때 병렬로 사용되어 잘못된 결과 대신 예외가 발생하도록 결합기 부분 (대신 )에 예외를 던지는 이유 입니다.
또한 이것은 목록 복사로 인해 매우 효율적이지 않습니다 (기본 배열을 복사하기 위해 기본 메서드를 사용하지만).
다음은 수집기 구현입니다.
private static Collector<String, List<List<String>>, List<List<String>>> splitBySeparator(Predicate<String> sep) {
final List<String> current = new ArrayList<>();
return Collector.of(() -> new ArrayList<List<String>>(),
(l, elem) -> {
if (sep.test(elem)) {
l.add(new ArrayList<>(current));
current.clear();
}
else {
current.add(elem);
}
},
(l1, l2) -> {
throw new RuntimeException("Should not run this in parallel");
},
l -> {
if (current.size() != 0) {
l.add(current);
return l;
}
);
}
그리고 그것을 사용하는 방법 :
List<List<String>> ll = list.stream().collect(splitBySeparator(Objects::isNull));
산출:
[[a, b], [c], [d, e]]
Joop Eggen 의 답변이 나왔기 때문에 병렬로 할 수있는 것 같습니다 (그에게 크레딧을주세요!). 이를 통해 사용자 지정 수집기 구현을 다음과 같이 줄입니다.
private static Collector<String, List<List<String>>, List<List<String>>> splitBySeparator(Predicate<String> sep) {
return Collector.of(() -> new ArrayList<List<String>>(Arrays.asList(new ArrayList<>())),
(l, elem) -> {if(sep.test(elem)){l.add(new ArrayList<>());} else l.get(l.size()-1).add(elem);},
(l1, l2) -> {l1.get(l1.size() - 1).addAll(l2.remove(0)); l1.addAll(l2); return l1;});
}
병렬성에 대한 문단을 쓸모 없게 만들었지 만 좋은 생각 나게 할 수 있기 때문에 그렇게했습니다.
Stream API가 항상 대체되는 것은 아닙니다. 스트림을 사용하는 것이 더 쉽고 적합한 작업이 있고 그렇지 않은 작업이 있습니다. 귀하의 경우에는 다음과 같은 유틸리티 메서드를 만들 수도 있습니다.
private static <T> List<List<T>> splitBySeparator(List<T> list, Predicate<? super T> predicate) {
final List<List<T>> finalList = new ArrayList<>();
int fromIndex = 0;
int toIndex = 0;
for(T elem : list) {
if(predicate.test(elem)) {
finalList.add(list.subList(fromIndex, toIndex));
fromIndex = toIndex + 1;
}
toIndex++;
}
if(fromIndex != toIndex) {
finalList.add(list.subList(fromIndex, toIndex));
}
return finalList;
}
그리고 그것을 List<List<String>> list = splitBySeparator(originalList, Objects::isNull);.
에지 케이스 확인을 위해 개선 할 수 있습니다.
이미 몇 가지 답변과 수용된 답변이 있지만이 주제에서 여전히 몇 가지 누락 된 점이 있습니다. 첫째, 스트림을 사용하여이 문제를 해결하는 것은 단지 연습 일 뿐이며 기존의 for-loop 접근 방식이 더 바람직하다는 데 합의 된 것 같습니다. 둘째, 지금까지 제공된 답변은 배열 또는 벡터 스타일 기술을 사용하는 접근 방식을 간과하여 스트림 솔루션을 상당히 개선한다고 생각합니다.
먼저, 토론 및 분석을위한 기존 솔루션이 있습니다.
static List<List<String>> splitConventional(List<String> input) {
List<List<String>> result = new ArrayList<>();
int prev = 0;
for (int cur = 0; cur < input.size(); cur++) {
if (input.get(cur) == null) {
result.add(input.subList(prev, cur));
prev = cur + 1;
}
}
result.add(input.subList(prev, input.size()));
return result;
}
이것은 대부분 간단하지만 약간의 미묘함이 있습니다. 한 가지 요점은 prev~ 에서 보류중인 하위 목록 cur이 항상 열려 있다는 것입니다. 우리가 만났을 때 우리는 null그것을 닫고 결과 목록에 추가하고 진행 prev합니다. 루프 후에는 무조건 하위 목록을 닫습니다.
또 다른 관찰은 이것이 값 자체가 아니라 인덱스에 대한 루프라는 것입니다. 따라서 우리는 향상된 "for-each"루프 대신 산술 for-loop를 사용합니다. 그러나 이는 값을 스트리밍하고 논리를 수집기에 넣는 대신 인덱스를 사용하여 하위 범위를 생성하는 스트리밍 할 수 있음을 제안합니다 ( Joop Eggen의 제안 된 솔루션 에서 수행 한 것처럼 ).
일단 우리가 그것을 깨달았을 때, 우리는 null입력 의 각 위치가 하위 목록의 구분 자라는 것을 알 수 있습니다 : 왼쪽에있는 하위 목록의 오른쪽 끝이고 (더하기 하나)는 하위 목록의 왼쪽 끝입니다. 권리. 엣지 케이스를 처리 할 수 있다면 null요소가 발생 하는 인덱스를 찾아서 하위 목록에 매핑하고 하위 목록을 수집 하는 접근 방식으로 이어집니다 .
결과 코드는 다음과 같습니다.
static List<List<String>> splitStream(List<String> input) {
int[] indexes = Stream.of(IntStream.of(-1),
IntStream.range(0, input.size())
.filter(i -> input.get(i) == null),
IntStream.of(input.size()))
.flatMapToInt(s -> s)
.toArray();
return IntStream.range(0, indexes.length-1)
.mapToObj(i -> input.subList(indexes[i]+1, indexes[i+1]))
.collect(toList());
}
null발생 하는 인덱스를 얻는 것은 매우 쉽습니다. 걸림돌 -1이 왼쪽과 size오른쪽 끝에 추가 되고 있습니다. 나는 Stream.of추가 하기 위해 사용 하고 flatMapToInt그들을 평평하게하기로 선택했습니다. (여러 가지 다른 접근 방식을 시도했지만 이것이 가장 깨끗한 것처럼 보였습니다.)
여기서 인덱스에 배열을 사용하는 것이 좀 더 편리합니다. 첫째, 배열에 접근하기위한 표기법이 List : indexes[i]vs indexes.get(i).. 둘째, 배열을 사용하면 권투를 피할 수 있습니다.
이 시점에서 배열의 각 인덱스 값 (마지막 제외)은 하위 목록의 시작 위치보다 하나 적습니다. 바로 오른쪽에있는 색인은 하위 목록의 끝입니다. 배열을 스트리밍하고 각 인덱스 쌍을 하위 목록에 매핑하고 출력을 수집하기 만하면됩니다.
토론
스트림 접근 방식은 for 루프 버전보다 약간 짧지 만 밀도가 높습니다. for-loop 버전은 익숙합니다. 우리는 항상 Java에서이 작업을 수행하지만이 루프가 수행해야하는 작업을 이미 알고 있지 않다면 명확하지 않습니다. prev수행중인 작업과 루프 종료 후 열린 하위 목록을 닫아야하는 이유 를 파악하기 전에 몇 가지 루프 실행을 시뮬레이션해야 할 수 있습니다 . (처음에는 그것을 가지고있는 것을 잊었지만 테스트에서 이것을 잡았습니다.)
스트림 접근 방식은 무슨 일이 일어나고 있는지 개념화하는 것이 더 쉽다고 생각합니다. 하위 목록 사이의 경계를 나타내는 목록 (또는 배열)을 가져옵니다. 그것은 쉬운 스트림 2 라이너입니다. 위에서 언급했듯이 어려운 점은 가장자리 값을 끝 부분에 고정하는 방법을 찾는 것입니다. 이를위한 더 나은 구문이 있다면, 예를 들어,
// Java plus pidgin Scala
int[] indexes =
[-1] ++ IntStream.range(0, input.size())
.filter(i -> input.get(i) == null) ++ [input.size()];
그것은 일을 훨씬 덜 복잡하게 만들 것입니다. (정말 필요한 것은 배열 또는 목록 이해입니다.) 인덱스가 있으면이를 실제 하위 목록에 매핑하고 결과 목록에 수집하는 것은 간단한 문제입니다.
물론 이것은 병렬로 실행할 때 안전합니다.
업데이트 2016-02-06
다음은 하위 목록 인덱스 배열을 만드는 더 좋은 방법입니다. 동일한 원칙을 기반으로하지만 인덱스 범위를 조정하고 인덱스를 연결하고 플랫 맵 할 필요가 없도록 필터에 몇 가지 조건을 추가합니다.
static List<List<String>> splitStream(List<String> input) {
int sz = input.size();
int[] indexes =
IntStream.rangeClosed(-1, sz)
.filter(i -> i == -1 || i == sz || input.get(i) == null)
.toArray();
return IntStream.range(0, indexes.length-1)
.mapToObj(i -> input.subList(indexes[i]+1, indexes[i+1]))
.collect(toList());
}
2016-11-23 업데이트
저는 Devoxx Antwerp 2016에서 Brian Goetz와 함께이 문제와 해결책을 다룬 "Thinking In Parallel"( 비디오 ) 강연을 공동 발표했습니다 . 제시된 문제는 null 대신 "#"으로 분할되는 약간의 변형이 있지만 그렇지 않으면 동일합니다. 강연에서 나는이 문제에 대한 많은 단위 테스트를 가졌다 고 언급했습니다. 루프 및 스트림 구현과 함께 독립 실행 형 프로그램으로 아래에 추가했습니다. 독자를위한 흥미로운 연습은 내가 여기에 제공 한 테스트 사례에 대해 다른 답변에서 제안 된 솔루션을 실행하고 어떤 것이 실패하고 왜 실패하는지 확인하는 것입니다. (다른 솔루션은 널로 분할하는 대신 술어를 기반으로 분할하도록 조정해야합니다.)
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
import static java.util.Arrays.asList;
public class ListSplitting {
static final Map<List<String>, List<List<String>>> TESTCASES = new LinkedHashMap<>();
static {
TESTCASES.put(asList(),
asList(asList()));
TESTCASES.put(asList("a", "b", "c"),
asList(asList("a", "b", "c")));
TESTCASES.put(asList("a", "b", "#", "c", "#", "d", "e"),
asList(asList("a", "b"), asList("c"), asList("d", "e")));
TESTCASES.put(asList("#"),
asList(asList(), asList()));
TESTCASES.put(asList("#", "a", "b"),
asList(asList(), asList("a", "b")));
TESTCASES.put(asList("a", "b", "#"),
asList(asList("a", "b"), asList()));
TESTCASES.put(asList("#"),
asList(asList(), asList()));
TESTCASES.put(asList("a", "#", "b"),
asList(asList("a"), asList("b")));
TESTCASES.put(asList("a", "#", "#", "b"),
asList(asList("a"), asList(), asList("b")));
TESTCASES.put(asList("a", "#", "#", "#", "b"),
asList(asList("a"), asList(), asList(), asList("b")));
}
static final Predicate<String> TESTPRED = "#"::equals;
static void testAll(BiFunction<List<String>, Predicate<String>, List<List<String>>> f) {
TESTCASES.forEach((input, expected) -> {
List<List<String>> actual = f.apply(input, TESTPRED);
System.out.println(input + " => " + expected);
if (!expected.equals(actual)) {
System.out.println(" ERROR: actual was " + actual);
}
});
}
static <T> List<List<T>> splitStream(List<T> input, Predicate<? super T> pred) {
int[] edges = IntStream.range(-1, input.size()+1)
.filter(i -> i == -1 || i == input.size() ||
pred.test(input.get(i)))
.toArray();
return IntStream.range(0, edges.length-1)
.mapToObj(k -> input.subList(edges[k]+1, edges[k+1]))
.collect(Collectors.toList());
}
static <T> List<List<T>> splitLoop(List<T> input, Predicate<? super T> pred) {
List<List<T>> result = new ArrayList<>();
int start = 0;
for (int cur = 0; cur < input.size(); cur++) {
if (pred.test(input.get(cur))) {
result.add(input.subList(start, cur));
start = cur + 1;
}
}
result.add(input.subList(start, input.size()));
return result;
}
public static void main(String[] args) {
System.out.println("===== Loop =====");
testAll(ListSplitting::splitLoop);
System.out.println("===== Stream =====");
testAll(ListSplitting::splitStream);
}
}
해결책은 Stream.collect. 빌더 패턴을 사용하여 Collector를 작성하려면 이미 솔루션으로 제공됩니다. 대안은 다른 오버로드 collect가 조금 더 원시적입니다.
List<String> strings = Arrays.asList("a", "b", null, "c", null, "d", "e");
List<List<String>> groups = strings.stream()
.collect(() -> {
List<List<String>> list = new ArrayList<>();
list.add(new ArrayList<>());
return list;
},
(list, s) -> {
if (s == null) {
list.add(new ArrayList<>());
} else {
list.get(list.size() - 1).add(s);
}
},
(list1, list2) -> {
// Simple merging of partial sublists would
// introduce a false level-break at the beginning.
list1.get(list1.size() - 1).addAll(list2.remove(0));
list1.addAll(list2);
});
보시다시피, 나는 항상 적어도 하나의 마지막 (빈) 문자열 목록이있는 문자열 목록 목록을 만듭니다.
- 첫 번째 함수는 문자열 목록의 시작 목록을 만듭니다. 결과 (유형이 지정된) 개체를 지정합니다.
- 두 번째 함수는 각 요소를 처리하기 위해 호출됩니다. 부분적인 결과와 요소에 대한 작업입니다.
- 세 번째는 실제로 사용되지 않으며 부분 결과를 결합해야 할 때 처리를 병렬화하는 데 사용됩니다.
누산기가있는 솔루션 :
@StuartMarks가 지적했듯이 결합기는 병렬 처리에 대한 계약을 완전히 채우지 않습니다.
@ArnaudDenoyelle의 주석으로 인해 reduce.
List<List<String>> groups = strings.stream()
.reduce(new ArrayList<List<String>>(),
(list, s) -> {
if (list.isEmpty()) {
list.add(new ArrayList<>());
}
if (s == null) {
list.add(new ArrayList<>());
} else {
list.get(list.size() - 1).add(s);
}
return list;
},
(list1, list2) -> {
list1.addAll(list2);
return list1;
});
- 첫 번째 매개 변수는 누적 된 개체입니다.
- 두 번째 함수가 누적됩니다.
- 세 번째는 앞서 언급 한 결합기입니다.
투표하지 마십시오. 나는 이것을 코멘트로 설명 할 충분한 장소가 없다 .
이것은있는 솔루션 Stream과 foreach그러나 이것은 알렉시스의 솔루션 또는 엄격하게 동등 foreach루프 (덜 분명, 나는 복사 생성자를 제거 할 수 없습니다)
List<List<String>> result = new ArrayList<>();
final List<String> current = new ArrayList<>();
list.stream().forEach(s -> {
if (s == null) {
result.add(new ArrayList<>(current));
current.clear();
} else {
current.add(s);
}
}
);
result.add(current);
System.out.println(result);
Java 8을 사용하여보다 우아한 솔루션을 찾고 싶다는 것을 알고 있지만이 경우를 위해 설계된 것이 아니라고 생각합니다. 그리고 Mr spoon이 말했듯이,이 경우 순진한 방식을 매우 선호합니다.
다음은 그룹화를 위해 목록 인덱스를 사용하는 그룹화 함수를 사용하는 또 다른 접근 방식입니다.
여기서는 value를 사용하여 해당 요소 다음의 첫 번째 인덱스로 요소를 그룹화합니다 null. 그래서, 당신의 예에서 "a"와 "b"것에 매핑 할 수 2. 또한 null값을 -1인덱스에 매핑하고 있으며 나중에 제거해야합니다.
List<String> list = Arrays.asList("a", "b", null, "c", null, "d", "e");
Function<String, Integer> indexGroupingFunc = (str) -> {
if (str == null) {
return -1;
}
int index = list.indexOf(str) + 1;
while (index < list.size() && list.get(index) != null) {
index++;
}
return index;
};
Map<Integer, List<String>> grouped = list.stream()
.collect(Collectors.groupingBy(indexGroupingFunc));
grouped.remove(-1); // Remove null elements grouped under -1
System.out.println(grouped.values()); // [[a, b], [c], [d, e]]
null.NET Framework에서 현재 최소 인덱스를 캐시하여 매번 요소 의 첫 번째 인덱스를 가져 오는 것을 방지 할 수도 있습니다 AtomicInteger. 업데이트 Function는 다음과 같습니다.
AtomicInteger currentMinIndex = new AtomicInteger(-1);
Function<String, Integer> indexGroupingFunc = (str) -> {
if (str == null) {
return -1;
}
int index = names.indexOf(str) + 1;
if (currentMinIndex.get() > index) {
return currentMinIndex.get();
} else {
while (index < names.size() && names.get(index) != null) {
index++;
}
currentMinIndex.set(index);
return index;
}
};
Marks Stuart 의 대답 은 간결하고 직관적이며 병렬 안전 (그리고 최고) 이지만 시작 / 종료 경계 트릭이 필요하지 않은 또 다른 흥미로운 솔루션을 공유하고 싶습니다.
문제 영역을 살펴보고 병렬성에 대해 생각해 보면 분할 및 정복 전략으로 쉽게 해결할 수 있습니다. 문제를 순회해야하는 연속 목록으로 생각하는 대신 문제를 동일한 기본 문제의 구성으로 볼 수 있습니다 null. 목록을 값으로 분할하는 것 입니다. 다음 재귀 전략을 사용하여 문제를 재귀 적으로 분석 할 수 있음을 직관적으로 쉽게 알 수 있습니다.
split(L) :
- if (no null value found) -> return just the simple list
- else -> cut L around 'null' naming the resulting sublists L1 and L2
return split(L1) + split(L2)
이 경우 먼저 null값을 검색하고 값을 찾는 순간 즉시 목록을 잘라 내고 하위 목록에 대한 재귀 호출을 호출합니다. null(기본 케이스)를 찾지 못하면 이 분기를 완료하고 목록을 반환합니다. 모든 결과를 연결하면 검색중인 목록이 반환됩니다.
사진은 천 단어의 가치가 있습니다.
알고리즘은 간단하고 완전합니다. 목록의 시작 / 끝의 가장자리 케이스를 처리하기 위해 특별한 트릭이 필요하지 않습니다. 빈 목록이나 null값만 있는 목록과 같은 엣지 케이스를 처리하기 위해 특별한 트릭이 필요하지 않습니다 . 또는로 끝나 null거나로 시작하는 목록 null.
이 전략의 간단한 구현은 다음과 같습니다.
public List<List<String>> split(List<String> input) {
OptionalInt index = IntStream.range(0, input.size())
.filter(i -> input.get(i) == null)
.findAny();
if (!index.isPresent())
return asList(input);
List<String> firstHalf = input.subList(0, index.getAsInt());
List<String> secondHalf = input.subList(index.getAsInt()+1, input.size());
return asList(firstHalf, secondHalf).stream()
.map(this::split)
.flatMap(List::stream)
.collect(toList());
}
먼저 null목록에서 값 의 색인을 검색 합니다. 찾을 수 없으면 목록을 반환합니다. 하나를 찾으면 목록을 2 개의 하위 목록으로 분할하고 그 위로 스트리밍 한 다음 재귀 적으로 split메서드를 다시 호출 합니다. 그런 다음 하위 문제의 결과 목록이 추출되어 반환 값에 대해 결합됩니다.
2 개의 스트림은 쉽게 parallel () 만들 수 있으며 문제의 기능적 분해로 인해 알고리즘이 여전히 작동합니다.
코드는 이미 매우 간결하지만 항상 다양한 방식으로 적용 할 수 있습니다. 예를 들어, 기본 사례에서 선택적인 값을 확인하는 대신에 대한 orElse메서드를 활용 OptionalInt하여 목록의 끝 인덱스를 반환하여 두 번째 스트림을 재사용하고 추가로 필터링 할 수 있습니다. 빈 목록 :
public List<List<String>> split(List<String> input) {
int index = IntStream.range(0, input.size())
.filter(i -> input.get(i) == null)
.findAny().orElse(input.size());
return asList(input.subList(0, index), input.subList(index+1, input.size())).stream()
.map(this::split)
.flatMap(List::stream)
.filter(list -> !list.isEmpty())
.collect(toList());
}
이 예제는 재귀 적 접근 방식의 단순성, 적응성 및 우아함을 나타 내기 위해서만 제공됩니다. 실제로이 버전은 약간의 성능 저하를 가져오고 입력이 비어있는 경우 실패합니다 (추가적으로 빈 검사가 필요할 수 있음) .
이 경우 재귀는 아마도 최선의 해결책이 아닐 수도 있지만 ( 스튜어트 마크스 인덱스를 찾는 알고리즘은 O (N)에 불과 하고 목록 매핑 / 분할은 상당한 비용이 든다), 단순하고 직관적 인 병렬 알고리즘으로 솔루션을 표현합니다. 부작용.
중지 기준 및 / 또는 부분적 결과 가용성이있는 사용 사례 또는 복잡성과 장점 / 단점에 대해 자세히 설명하지 않겠습니다. 다른 접근 방식은 단순히 반복적이거나 병렬화 할 수없는 지나치게 복잡한 솔루션 알고리즘을 사용했기 때문에이 솔루션 전략을 공유해야한다고 느꼈습니다.
이것은 매우 흥미로운 문제입니다. 나는 한 줄 해결책을 생각해 냈습니다. 성능이 좋지는 않지만 작동합니다.
List<String> list = Arrays.asList("a", "b", null, "c", null, "d", "e");
Collection<List<String>> cl = IntStream.range(0, list.size())
.filter(i -> list.get(i) != null).boxed()
.collect(Collectors.groupingBy(
i -> IntStream.range(0, i).filter(j -> list.get(j) == null).count(),
Collectors.mapping(i -> list.get(i), Collectors.toList()))
).values();
@Rohit Jain이 생각 해낸 비슷한 아이디어입니다. null 값 사이의 공간을 그룹화하고 있습니다. 정말로 원하는 경우 다음을 List<List<String>>추가 할 수 있습니다.
List<List<String>> ll = cl.stream().collect(Collectors.toList());
글쎄, 약간의 작업 후에 U는 단선 스트림 기반 솔루션을 고안했습니다. 그것은 궁극적으로 reduce()자연스러운 선택 인 것처럼 보였던 그룹화를 수행하는 데 사용 되지만 List<List<String>>감소에 의해 필요한 문자열을 얻는 것은 약간 추악 했습니다.
List<List<String>> result = list.stream()
.map(Arrays::asList)
.map(x -> new LinkedList<String>(x))
.map(Arrays::asList)
.map(x -> new LinkedList<List<String>>(x))
.reduce( (a, b) -> {
if (b.getFirst().get(0) == null)
a.add(new LinkedList<String>());
else
a.getLast().addAll(b.getFirst());
return a;}).get();
그것은 이다 그러나 1 개 라인!
질문의 입력으로 실행하면
System.out.println(result);
생성 :
[[a, b], [c], [d, e]]
다음은 AbacusUtil의 코드입니다 .
List<String> list = N.asList(null, null, "a", "b", null, "c", null, null, "d", "e");
Stream.of(list).splitIntoList(null, (e, any) -> e == null, null).filter(e -> e.get(0) != null).forEach(N::println);
선언 : 저는 AbacusUtil의 개발자입니다.
내 StreamEx 라이브러리에는 groupRuns이 문제를 해결하는 데 도움이되는 방법이 있습니다.
List<String> input = Arrays.asList("a", "b", null, "c", null, "d", "e");
List<List<String>> result = StreamEx.of(input)
.groupRuns((a, b) -> a != null && b != null)
.remove(list -> list.get(0) == null).toList();
The groupRuns method takes a BiPredicate which for the pair of adjacent elements returns true if they should be grouped. After that we remove groups containing nulls and collect the rest to the List.
This solution is parallel-friendly: you may use it for parallel stream as well. Also it works nice with any stream source (not only random access lists like in some other solutions) and it's somewhat better than collector-based solutions as here you can use any terminal operation you want without intermediate memory waste.
With String one can do:
String s = ....;
String[] parts = s.split("sth");
If all sequential collections (as the String is a sequence of chars) had this abstraction this could be doable for them too:
List<T> l = ...
List<List<T>> parts = l.split(condition) (possibly with several overloaded variants)
If we restrict the original problem to List of Strings (and imposing some restrictions on it's elements contents) we could hack it like this:
String als = Arrays.toString(new String[]{"a", "b", null, "c", null, "d", "e"});
String[] sa = als.substring(1, als.length() - 1).split("null, ");
List<List<String>> res = Stream.of(sa).map(s -> Arrays.asList(s.split(", "))).collect(Collectors.toList());
(진지하게 받아들이지 마세요 :))
그렇지 않으면 일반 이전 재귀도 작동합니다.
List<List<String>> part(List<String> input, List<List<String>> acc, List<String> cur, int i) {
if (i == input.size()) return acc;
if (input.get(i) != null) {
cur.add(input.get(i));
} else if (!cur.isEmpty()) {
acc.add(cur);
cur = new ArrayList<>();
}
return part(input, acc, cur, i + 1);
}
(이 경우 입력 목록에 null을 추가해야합니다.)
part(input, new ArrayList<>(), new ArrayList<>(), 0)
널 (또는 구분 기호)을 찾을 때마다 다른 토큰으로 그룹화하십시오. 나는 여기에 다른 정수를 사용했습니다 (홀더로 원자 사용)
그런 다음 생성 된 맵을 다시 매핑하여 목록 목록으로 변환합니다.
AtomicInteger i = new AtomicInteger();
List<List<String>> x = Stream.of("A", "B", null, "C", "D", "E", null, "H", "K")
.collect(Collectors.groupingBy(s -> s == null ? i.incrementAndGet() : i.get()))
.entrySet().stream().map(e -> e.getValue().stream().filter(v -> v != null).collect(Collectors.toList()))
.collect(Collectors.toList());
System.out.println(x);
저는 Stuart의 Thinking in Parallel에 대한 비디오를보고있었습니다. 그래서 비디오에서 그의 반응을보기 전에 그것을 해결하기로 결정했습니다. 시간에 따라 솔루션을 업데이트합니다. 지금은
Arrays.asList(IntStream.range(0, abc.size()-1).
filter(index -> abc.get(index).equals("#") ).
map(index -> (index)).toArray()).
stream().forEach( index -> {for (int i = 0; i < index.length; i++) {
if(sublist.size()==0){
sublist.add(new ArrayList<String>(abc.subList(0, index[i])));
}else{
sublist.add(new ArrayList<String>(abc.subList(index[i]-1, index[i])));
}
}
sublist.add(new ArrayList<String>(abc.subList(index[index.length-1]+1, abc.size())));
});
참고 URL : https://stackoverflow.com/questions/29095967/splitting-list-into-sublists-along-elements
'program story' 카테고리의 다른 글
| C에서 sizeof 연산자는 2.5m를 통과하면 8 바이트를 반환하지만 1.25m * 2를 통과하면 4 바이트를 반환합니다. (0) | 2020.11.17 |
|---|---|
| 교리-관계를 통해 새로운 실체가 발견되었습니다. (0) | 2020.11.17 |
| 형식 내부에 열거 형 사용-컴파일러 경고 C4482 C ++ (0) | 2020.11.17 |
| 자바 스크립트에 지연 추가 (0) | 2020.11.17 |
| 오른쪽 괄호가있는 정렬 된 목록 (HTML) 하위 알파? (0) | 2020.11.17 |
