Skip to content

Commit

Permalink
Add Static.find and PairList.firstMatch
Browse files Browse the repository at this point in the history
  • Loading branch information
julianhyde committed Jan 11, 2024
1 parent 50a1c89 commit 0ec6818
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 34 deletions.
3 changes: 3 additions & 0 deletions src/main/java/net/hydromatic/morel/util/PairList.java
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ default ImmutableMap<T, U> toImmutableMap() {
* in this list. */
boolean allMatch(BiPredicate<T, U> predicate);

/** Returns the index of the first match of a predicate. */
int firstMatch(BiPredicate<T, U> predicate);

/** Returns whether the predicate is true for no pairs
* in this list. */
boolean noMatch(BiPredicate<T, U> predicate);
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/net/hydromatic/morel/util/PairLists.java
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,18 @@ static class MutablePairList<T, U> extends AbstractPairList<T, U> {
}
return true;
}

@SuppressWarnings("unchecked")
@Override public int firstMatch(BiPredicate<T, U> predicate) {
for (int i = 0, j = 0; i < list.size(); ++j) {
final T t = (T) list.get(i++);
final U u = (U) list.get(i++);
if (predicate.test(t, u)) {
return j;
}
}
return -1;
}
}

/** Empty immutable list of pairs.
Expand Down Expand Up @@ -367,6 +379,10 @@ static class EmptyImmutablePairList<T, U>
@Override public boolean noMatch(BiPredicate<T, U> predicate) {
return true;
}

@Override public int firstMatch(BiPredicate<T, U> predicate) {
return -1;
}
}

/** Immutable list that contains one pair.
Expand Down Expand Up @@ -452,6 +468,10 @@ static class SingletonImmutablePairList<T, U>
@Override public boolean noMatch(BiPredicate<T, U> predicate) {
return !predicate.test(t, u);
}

@Override public int firstMatch(BiPredicate<T, U> predicate) {
return predicate.test(t, u) ? 0 : -1;
}
}

/** Base class for a list that implements {@link RandomAccess}.
Expand Down Expand Up @@ -609,6 +629,18 @@ static class ArrayImmutablePairList<T, U>
}
return true;
}

@SuppressWarnings("unchecked")
@Override public int firstMatch(BiPredicate<T, U> predicate) {
for (int i = 0, j = 0; i < elements.length; ++j) {
final T t = (T) elements[i++];
final U u = (U) elements[i++];
if (predicate.test(t, u)) {
return j;
}
}
return -1;
}
}
}

Expand Down
22 changes: 22 additions & 0 deletions src/main/java/net/hydromatic/morel/util/Static.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.RandomAccess;
import java.util.Set;
import java.util.SortedMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collector;

/**
Expand Down Expand Up @@ -206,6 +208,26 @@ public static <E, T> ImmutableList<T> transformEager(
return b.build();
}

/** Returns the first index in a list where a predicate is true, or -1. */
public static <E> int find(List<? extends E> list, Predicate<E> predicate) {
if (list instanceof RandomAccess) {
for (int i = 0; i < list.size(); i++) {
if (predicate.test(list.get(i))) {
return i;
}
}
} else {
int i = 0;
for (E e : list) {
if (predicate.test(e)) {
return i;
}
++i;
}
}
return -1;
}

public static <E> List<E> intersect(List<E> list0,
Iterable<? extends E> list1) {
final ImmutableList.Builder<E> list2 = ImmutableList.builder();
Expand Down
109 changes: 75 additions & 34 deletions src/test/java/net/hydromatic/morel/PairListTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -353,42 +353,83 @@ private <T, U> void validate(PairList<T, U> pairList,
}

@Test void testTransform() {
final PairList<String, Integer> list3 =
final PairList<String, Integer> mutableList3 =
PairList.copyOf("a", 1, null, 5, "c", 3);
assertThat(list3.transform((s, i) -> s + i),
is(Arrays.asList("a1", "null5", "c3")));
assertThat(list3.transform2((s, i) -> s + i),
is(Arrays.asList("a1", "null5", "c3")));
final PairList<String, Integer> immutableList3 =
PairList.copyOf("a", 1, "null", 5, "c", 3);

final PairList<String, Integer> mutableList0 = PairList.of();

final PairList<String, Integer> mutableList1 = PairList.of("a", 1);
final PairList<String, Integer> doubleList1 =
ImmutablePairList.copyOf("a", 1, "a", 1);

for (boolean mutable : new boolean[] {false, true}) {
PairList<String, Integer> list0 =
mutable ? mutableList0 : mutableList0.immutable();
PairList<String, Integer> list1 =
mutable ? mutableList1 : mutableList1.immutable();
PairList<String, Integer> list3 =
mutable ? mutableList3 : immutableList3;

assertThat(list0.transform((s, i) -> s + i), empty());

assertThat(list1.transform((s, i) -> s + i), is(ImmutableList.of("a1")));

assertThat(list3.transform((s, i) -> s + i),
is(Arrays.asList("a1", "null5", "c3")));
assertThat(list3.transform2((s, i) -> s + i),
is(Arrays.asList("a1", "null5", "c3")));

final BiPredicate<String, Integer> gt2 = (s, i) -> i > 2;
assertThat(list3.anyMatch(gt2), is(true));
assertThat(list3.allMatch(gt2), is(false));
assertThat(list3.noMatch(gt2), is(false));
assertThat(list3.firstMatch(gt2), is(1));

final BiPredicate<String, Integer> negative = (s, i) -> i < 0;
assertThat(list3.anyMatch(negative), is(false));
assertThat(list3.allMatch(negative), is(false));
assertThat(list3.noMatch(negative), is(true));
assertThat(list3.firstMatch(negative), is(-1));

final BiPredicate<String, Integer> positive = (s, i) -> i > 0;
assertThat(list3.anyMatch(positive), is(true));
assertThat(list3.allMatch(positive), is(true));
assertThat(list3.noMatch(positive), is(false));
assertThat(list3.firstMatch(positive), is(0));

final BiPredicate<String, Integer> isNull = (s, i) -> s == null;
if (mutable) {
assertThat(list3.anyMatch(isNull), is(true));
assertThat(list3.allMatch(isNull), is(false));
assertThat(list3.noMatch(isNull), is(false));
assertThat(list3.firstMatch(isNull), is(1));
} else {
// In the immutable version, null has been replaced with "null"
assertThat(list3.anyMatch(isNull), is(false));
assertThat(list3.allMatch(isNull), is(false));
assertThat(list3.noMatch(isNull), is(true));
assertThat(list3.firstMatch(isNull), is(-1));
}

final PairList<String, Integer> list0 = PairList.of();
assertThat(list0.transform((s, i) -> s + i), empty());

final BiPredicate<String, Integer> gt2 = (s, i) -> i > 2;
assertThat(list3.anyMatch(gt2), is(true));
assertThat(list3.allMatch(gt2), is(false));
assertThat(list3.noMatch(gt2), is(false));

final BiPredicate<String, Integer> negative = (s, i) -> i < 0;
assertThat(list3.anyMatch(negative), is(false));
assertThat(list3.allMatch(negative), is(false));
assertThat(list3.noMatch(negative), is(true));

final BiPredicate<String, Integer> positive = (s, i) -> i > 0;
assertThat(list3.anyMatch(positive), is(true));
assertThat(list3.allMatch(positive), is(true));
assertThat(list3.noMatch(positive), is(false));

final BiPredicate<String, Integer> isNull = (s, i) -> s == null;
assertThat(list3.anyMatch(isNull), is(true));
assertThat(list3.allMatch(isNull), is(false));
assertThat(list3.noMatch(isNull), is(false));

// All predicates behave the same on the empty list
Arrays.asList(gt2, negative, positive, isNull).forEach(p -> {
assertThat(list0.anyMatch(p), is(false));
assertThat(list0.allMatch(p), is(true)); // trivially
assertThat(list0.noMatch(p), is(true));
});
// All predicates behave the same on the empty list
Arrays.asList(gt2, negative, positive, isNull).forEach(p -> {
assertThat(list0.anyMatch(p), is(false));
assertThat(list0.allMatch(p), is(true)); // trivially
assertThat(list0.noMatch(p), is(true));
assertThat(list0.firstMatch(p), is(-1));
});

// All predicates on the 1-element list have the same answer as the same
// predicate on the 2-element list that is the 1-element list doubled.
Arrays.asList(gt2, negative, positive, isNull).forEach(p -> {
assertThat(list1.anyMatch(p), is(doubleList1.anyMatch(p)));
assertThat(list1.allMatch(p), is(doubleList1.anyMatch(p)));
assertThat(list1.noMatch(p), is(doubleList1.noMatch(p)));
assertThat(list1.firstMatch(p), is(doubleList1.firstMatch(p)));
});
}
}

@Test void testBuilder() {
Expand Down
11 changes: 11 additions & 0 deletions src/test/java/net/hydromatic/morel/UtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

import static net.hydromatic.morel.ast.AstBuilder.ast;
import static net.hydromatic.morel.eval.Codes.isNegative;
Expand Down Expand Up @@ -189,6 +190,16 @@ private <E> void checkShorterThan(Iterable<E> iterable, int size) {
assertThat(Static.shorterThan(iterable, 1_000_000), is(size < 1_000_000));
}

/** Tests {@link Static#find(List, Predicate)}. */
@Test void testFind() {
final List<Integer> list = Arrays.asList(1, 7, 3);
final List<Integer> emptyList = Collections.emptyList();
assertThat(Static.find(list, i -> i > 0), is(0));
assertThat(Static.find(list, i -> i > 1), is(1));
assertThat(Static.find(list, i -> i > 10), is(-1));
assertThat(Static.find(emptyList, i -> i > 0), is(-1));
}

/** Unit tests for {@link Pos}. */
@Test void testPos() {
final BiConsumer<String, String> check = (s, posString) -> {
Expand Down

0 comments on commit 0ec6818

Please sign in to comment.