package nodebox.function; import com.google.common.base.Predicates; import com.google.common.collect.*; import nodebox.util.MathUtils; import nodebox.util.ReflectionUtils; import java.util.*; /** * Operations on lists. * <p> * When we say lists, we really mean Iterables, the most generic type of lists. * We do this because some functions return infinite Iterables. * <p> * Functions return the most specific type: for example, sort needs to build up a list from the input Iterable, * so it will return this list, avoiding the consumer to create another copy for processing. * <p> * Functions here are not concerned what is inside the items of the list. * They operate on the lists themselves, not on the contents of the list elements. */ public class ListFunctions { public static final FunctionLibrary LIBRARY; static { LIBRARY = JavaLibrary.ofClass("list", ListFunctions.class, "count", "first", "second", "rest", "last", "combine", "slice", "shift", "doSwitch", "distinct", "repeat", "reverse", "sort", "shuffle", "pick", "cull", "takeEvery", "keys", "zipMap"); } /** * Count the number of items in the list. * <p> * If the list is infinite, this function will keep on running forever. * * @param iterable The list items. * @return The total amount of items in the list. */ public static long count(Iterable<?> iterable) { if (iterable == null) return 0; return Iterables.size(iterable); } /** * Take the first item of the list. * * @param iterable The list items. * @return The first item of the list. */ public static Object first(Iterable<?> iterable) { if (iterable == null) return null; Iterator iterator = iterable.iterator(); if (iterator.hasNext()) { return iterator.next(); } return null; } /** * Take the second item of the list. * * @param iterable The list items. * @return The second item of the list. */ public static Object second(Iterable<?> iterable) { if (iterable == null) return null; Iterator iterator = iterable.iterator(); if (iterator.hasNext()) { iterator.next(); if (iterator.hasNext()) { return iterator.next(); } } return null; } /** * Take all but the first item of the list. * * @param iterable The list items. * @return A new list with the first item skipped. */ public static List<?> rest(Iterable<?> iterable) { if (iterable == null) return ImmutableList.of(); return ImmutableList.copyOf(Iterables.skip(iterable, 1)); } /** * Take the last item of the list. * * @param iterable The list items. * @return The last item of the list. */ public static Object last(Iterable<?> iterable) { if (iterable == null) return null; try { return Iterables.getLast(iterable); } catch (NoSuchElementException e) { return null; } } /** * Combine multiple lists into one. * * @param list1 The first list to combine. * @param list2 The second list to combine. * @param list3 The third list to combine. * @param list4 The fourth list to combine. * @param list5 The fifth list to combine. * @param list6 The sixth list to combine. * @param list7 The seventh list to combine. * @return A new list with all input lists combined. */ public static List<?> combine(Iterable list1, Iterable list2, Iterable list3, Iterable list4, Iterable list5, Iterable list6, Iterable list7) { Iterable<Iterable<?>> nonNullLists = Iterables.filter( Lists.<Iterable<?>>newArrayList(list1, list2, list3, list4, list5, list6, list7), Predicates.notNull()); return ImmutableList.copyOf(Iterables.concat(nonNullLists)); } /** * Take a portion of the original list. * * @param iterable The list items. * @param startIndex The starting index, zero-based. * @param size The amount of items. * @param invert Omit the given list items instead of retaining them. * @return A new list containing a slice of the original. */ public static List<?> slice(Iterable<?> iterable, long startIndex, long size, boolean invert) { if (iterable == null) return ImmutableList.of(); if (!invert) { Iterable<?> skipped = Iterables.skip(iterable, (int) startIndex); return ImmutableList.copyOf(Iterables.limit(skipped, (int) size)); } else { Iterable<?> firstList = Iterables.limit(iterable, (int) startIndex); Iterable<?> secondList = Iterables.skip(iterable, (int) (startIndex + size)); return ImmutableList.copyOf(Iterables.concat(firstList, secondList)); } } /** * Take the beginning elements from the beginning of the list and append them to the end of the list. * * @param iterable The list items. * @param amount The amount of items to shift. * @return A new list with the items shifted. */ public static List<?> shift(Iterable<?> iterable, long amount) { if (iterable == null) return ImmutableList.of(); int listSize = Iterables.size(iterable); if (listSize == 0) return ImmutableList.of(); int a = (int) amount % listSize; if (a < 0) { a += listSize; } if (a == 0) return ImmutableList.copyOf(iterable); Iterable<?> tail = Iterables.skip(iterable, a); Iterable<?> head = Iterables.limit(iterable, a); return ImmutableList.copyOf(Iterables.concat(tail, head)); } /** * Switch between multiple inputs. * * @param list1 The first input list. * @param list2 The second input list. * @param list3 The third input list. * @param list4 The fourth input list. * @param list5 The fifth input list. * @param list6 The sixth input list. * @param index The index of the input list to return. * @return A list with the specified index. */ public static List<?> doSwitch(Iterable list1, Iterable list2, Iterable list3, Iterable list4, Iterable list5, Iterable list6, long index) { Iterable<?> returnList; switch ((int) index % 6) { case 0: returnList = list1; break; case 1: returnList = list2; break; case 2: returnList = list3; break; case 3: returnList = list4; break; case 4: returnList = list5; break; case 5: returnList = list6; break; default: throw new AssertionError(); } if (returnList == null) return ImmutableList.of(); return ImmutableList.copyOf(returnList); } /** * Repeat the given sequence amount times. * * @param iterable The list items. * @param amount The amount of repetitions. * @param perItem Repeats the items one after another, e.g. aabbcc instead of ababab (the default). * @return A new list with the items repeated. */ public static List<?> repeat(Iterable<?> iterable, long amount, boolean perItem) { if (iterable == null) return ImmutableList.of(); if (amount < 1) return ImmutableList.of(); if (amount == 1) return ImmutableList.copyOf(iterable); if (perItem) { Iterator iterator = iterable.iterator(); ImmutableList.Builder<Object> builder = ImmutableList.builder(); while (iterator.hasNext()) { Object o = iterator.next(); for (int i = 0; i < amount; i++) builder.add(o); } return builder.build(); } else { Iterable<?>[] iterables = new Iterable<?>[(int) amount]; for (int i = 0; i < amount; i++) { iterables[i] = iterable; } return ImmutableList.copyOf(Iterables.concat(iterables)); } } /** * Reverse the items in the list. * * @param iterable The list items. * @return A new list with the items reversed. */ public static List<?> reverse(Iterable<?> iterable) { if (iterable == null) return ImmutableList.of(); return Lists.reverse(ImmutableList.copyOf(iterable)); } /** * Sort items in the list. * If a key is provided, sort according to this key, * otherwise perform sorting by natural sort order. * <p> * * @param iterable The list items. * @param key The key by which to order. * @return A new, sorted list. */ @SuppressWarnings("unchecked") public static List<?> sort(Iterable<?> iterable, final String key) { if (iterable == null) return ImmutableList.of(); try { if (key == null || key.length() == 0) { Object first = Iterables.getFirst(iterable, null); if (first instanceof Map) { Object firstKey = Iterables.getFirst(((Map) first).keySet(), null); if (firstKey != null && firstKey instanceof String) return sort(iterable, (String) firstKey); } return ImmutableList.copyOf(Ordering.natural().sortedCopy((Iterable<? extends Comparable>) iterable)); } Ordering<Object> keyOrdering = new Ordering<Object>() { public int compare(Object o1, Object o2) { Comparable c1 = (Comparable) DataFunctions.lookup(o1, key); Comparable c2 = (Comparable) DataFunctions.lookup(o2, key); return Ordering.natural().compare(c1, c2); } }; return ImmutableList.copyOf(keyOrdering.sortedCopy(iterable)); } catch (NullPointerException e) { throw new IllegalArgumentException("Invalid key for this type of object: " + key); } catch (ClassCastException e) { // This error occurs when an element is not a Comparable or when // there are elements of different types in the list. throw new IllegalArgumentException("To sort a list, all elements in the list need to be comparable and of the same type."); } } /** * Shuffle the items in the list. * <p> * Shuffling is stable: using the same seed will always return items in the same sort order. * * @param iterable The items to shuffle. * @param seed The random seed. * @return A new iterable with items in random order. */ public static List<?> shuffle(Iterable<?> iterable, long seed) { if (iterable == null) return ImmutableList.of(); List<?> l = Lists.newArrayList(iterable); Collections.shuffle(l, MathUtils.randomFromSeed(seed)); return ImmutableList.copyOf(l); } /** * Pick a number of items from the list. * <p> * Shuffling is stable: using the same seed will always return items in the same sort order. * * @param iterable The items to pick from. * @param amount The amount of items. * @param seed The random seed. * @return A new iterable with specified amount of items in random order. */ public static List<?> pick(Iterable<?> iterable, long amount, long seed) { if (iterable == null || amount <= 0) return ImmutableList.of(); List<?> l = Lists.newArrayList(iterable); Collections.shuffle(l, MathUtils.randomFromSeed(seed)); if (amount >= l.size()) return ImmutableList.copyOf(l); return ImmutableList.copyOf(l.subList(0, (int) amount)); } /** * Cycle indefinitely over the elements of the list. This creates an infinite list. * * @param iterable The list items. * @return A new infinite iterable. */ public static Iterable<?> cycle(Iterable<?> iterable) { if (iterable == null) return ImmutableList.of(); return Iterables.cycle(iterable); } /** * Cull a list of elements using a true/false pattern. * * @param iterable The list to filter. * @param booleans The pattern that determines which elements are retained and which are not. * @return The culled list. */ public static List<?> cull(Iterable<?> iterable, Iterable<Boolean> booleans) { if (iterable == null) return ImmutableList.of(); if (booleans == null || Iterables.isEmpty(booleans)) return ImmutableList.copyOf(iterable); ImmutableList.Builder<Object> results = ImmutableList.builder(); Iterator<?> booleanIterator = ((Iterable<?>) cycle(booleans)).iterator(); for (Object object : iterable) { boolean keep = (Boolean) booleanIterator.next(); if (keep) results.add(object); } return results.build(); } /** * Filter a list so it only contains unique elements (no duplicates). * * @param iterable The list to filter. * @param key Optionally, the lookup key that will be used to find distincts. * @return The filtered list. */ public static List<?> distinct(Iterable<?> iterable, String key) { if (iterable == null) return ImmutableList.of(); if (key != null) { key = key.trim().isEmpty() ? null : key; } Set<Integer> distinctKeys = new HashSet<>(); ImmutableList.Builder<Object> b = ImmutableList.builder(); for (Object object : iterable) { if (object == null) continue; final Integer hashCode; if (key == null) { hashCode = object.hashCode(); } else { Object v = DataFunctions.lookup(object, key); hashCode = v == null ? null : v.hashCode(); } if (hashCode != null && distinctKeys.contains(hashCode)) continue; distinctKeys.add(hashCode); b.add(object); } return b.build(); } public static List<?> takeEvery(Iterable<?> iterable, long n) { if (iterable == null) return ImmutableList.of(); ImmutableList.Builder<Object> b = ImmutableList.builder(); Iterator<?> iterator = iterable.iterator(); int i = 0; while (iterator.hasNext()) { Object o = iterator.next(); if (i % n == 0) { b.add(o); } i++; } return b.build(); } /** * Return a list of distinct keys found in the given maps. * @param iterable The list of maps. * @return A list of keys. */ public static List<?> keys(Iterable<?> iterable) { if (iterable == null) return ImmutableList.of(); ImmutableSet.Builder<String> b = ImmutableSet.<String>builder(); for (Object o : iterable) { if (o instanceof Map) { @SuppressWarnings("unchecked") Map<String,?> m = (Map<String,?>) o; b.addAll(m.keySet()); } else { b.addAll(ReflectionUtils.getProperties(o)); } } return b.build().asList(); } public static <K, V> Map<K, V> zipMap(Iterable<K> keys, Iterable<V> values) { if (keys == null || values == null) return ImmutableMap.of(); ImmutableMap.Builder<K, V> b = ImmutableMap.builder(); Iterator<K> keyIterator = keys.iterator(); Iterator<V> valueIterator = values.iterator(); while (keyIterator.hasNext() && valueIterator.hasNext()) { K key = keyIterator.next(); V value = valueIterator.next(); b.put(key, value); } return b.build(); } }