Skip to content

Commit 15ae8d8

Browse files
l46kokcopybara-github
authored andcommitted
Fix filter/map macro to be linear in time and space complexity
PiperOrigin-RevId: 781245388
1 parent 97667f5 commit 15ae8d8

4 files changed

Lines changed: 131 additions & 1 deletion

File tree

runtime/src/main/java/dev/cel/runtime/BUILD.bazel

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ java_library(
272272
exports = [":base"],
273273
deps = [
274274
":base",
275+
":concatenated_list_view",
275276
":dispatcher",
276277
":evaluation_exception",
277278
":evaluation_exception_builder",
@@ -306,6 +307,7 @@ cel_android_library(
306307
visibility = ["//visibility:private"],
307308
deps = [
308309
":base_android",
310+
":concatenated_list_view",
309311
":dispatcher_android",
310312
":evaluation_exception",
311313
":evaluation_exception_builder",
@@ -398,6 +400,7 @@ cel_android_library(
398400
tags = [
399401
],
400402
deps = [
403+
":concatenated_list_view",
401404
"//common:error_codes",
402405
"//common:options",
403406
"//common:runtime_exception",
@@ -419,6 +422,7 @@ java_library(
419422
tags = [
420423
],
421424
deps = [
425+
":concatenated_list_view",
422426
"//common:error_codes",
423427
"//common:options",
424428
"//common:runtime_exception",
@@ -1094,3 +1098,10 @@ cel_android_library(
10941098
"@maven_android//:com_google_guava_guava",
10951099
],
10961100
)
1101+
1102+
java_library(
1103+
name = "concatenated_list_view",
1104+
srcs = ["ConcatenatedListView.java"],
1105+
# used_by_android
1106+
visibility = ["//visibility:private"],
1107+
)
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License aj
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.runtime;
16+
17+
import java.util.AbstractList;
18+
import java.util.ArrayList;
19+
import java.util.Collection;
20+
import java.util.Iterator;
21+
import java.util.List;
22+
import java.util.NoSuchElementException;
23+
24+
/**
25+
* A custom list view implementation that allows O(1) concatenation of two lists. Its primary
26+
* purpose is to facilitate efficient accumulation of lists for later materialization. (ex:
27+
* comprehensions that dispatch `add_list` to concat N lists together).
28+
*
29+
* <p>This is does not support any of the standard list operations from {@link java.util.List}.
30+
*/
31+
final class ConcatenatedListView<E> extends AbstractList<E> {
32+
private final List<List<? extends E>> sourceLists;
33+
private int totalSize = 0;
34+
35+
ConcatenatedListView() {
36+
this.sourceLists = new ArrayList<>();
37+
}
38+
39+
ConcatenatedListView(Collection<? extends E> collection) {
40+
this();
41+
addAll(collection);
42+
}
43+
44+
@Override
45+
public boolean addAll(Collection<? extends E> collection) {
46+
if (!(collection instanceof List)) {
47+
// size() is O(1) iff it's a list
48+
throw new IllegalStateException("addAll must be called with lists, not collections");
49+
}
50+
51+
sourceLists.add((List<? extends E>) collection);
52+
totalSize += collection.size();
53+
return true;
54+
}
55+
56+
@Override
57+
public E get(int index) {
58+
throw new UnsupportedOperationException("get method not supported.");
59+
}
60+
61+
@Override
62+
public int size() {
63+
return totalSize;
64+
}
65+
66+
@Override
67+
public Iterator<E> iterator() {
68+
return new ConcatenatingIterator();
69+
}
70+
71+
/** Custom iterator to provide a flat view of all concatenated collections */
72+
private class ConcatenatingIterator implements Iterator<E> {
73+
private int index = 0;
74+
private Iterator<? extends E> iterator = null;
75+
76+
@Override
77+
public boolean hasNext() {
78+
while (iterator == null || !iterator.hasNext()) {
79+
if (index < sourceLists.size()) {
80+
iterator = sourceLists.get(index).iterator();
81+
index++;
82+
} else {
83+
return false;
84+
}
85+
}
86+
return true;
87+
}
88+
89+
@Override
90+
public E next() {
91+
if (!hasNext()) {
92+
throw new NoSuchElementException();
93+
}
94+
return iterator.next();
95+
}
96+
97+
@Override
98+
public void remove() {
99+
throw new UnsupportedOperationException("remove method not supported");
100+
}
101+
}
102+
}

runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ public Object evalTrackingUnknowns(
173173
throws CelEvaluationException {
174174
ExecutionFrame frame = newExecutionFrame(resolver, functionResolver, listener);
175175
IntermediateResult internalResult = evalInternal(frame, ast.getExpr());
176+
176177
return internalResult.value();
177178
}
178179

@@ -924,6 +925,11 @@ private IntermediateResult evalComprehension(
924925
accuValue = IntermediateResult.create(new LazyExpression(compre.accuInit()));
925926
} else {
926927
accuValue = evalNonstrictly(frame, compre.accuInit());
928+
if (accuValue.value() instanceof List) {
929+
ConcatenatedListView<Object> lv =
930+
new ConcatenatedListView<>((List<Object>) accuValue.value());
931+
accuValue = IntermediateResult.create(lv);
932+
}
927933
}
928934
int i = 0;
929935
for (Object elem : iterRange) {
@@ -950,6 +956,12 @@ private IntermediateResult evalComprehension(
950956
frame.popScope();
951957
}
952958

959+
// Materialize view back into an immutable list to facilitate O(1) lookups.
960+
if (accuValue.value() instanceof ConcatenatedListView) {
961+
accuValue =
962+
IntermediateResult.create(ImmutableList.copyOf((Collection<?>) accuValue.value()));
963+
}
964+
953965
frame.pushScope(Collections.singletonMap(accuVar, accuValue));
954966
IntermediateResult result;
955967
try {

runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,12 @@ public static boolean matches(String string, String regexp, CelOptions celOption
9191

9292
/** Concatenates two lists into a new list. */
9393
public static <E> List<E> concat(List<E> first, List<E> second) {
94-
// TODO: return a view instead of an actual copy.
94+
if (first instanceof ConcatenatedListView) {
95+
// Comprehensions use a more efficient list view for performing O(1) concatenation
96+
first.addAll(second);
97+
return first;
98+
}
99+
95100
List<E> result = new ArrayList<>(first.size() + second.size());
96101
result.addAll(first);
97102
result.addAll(second);

0 commit comments

Comments
 (0)