net.sf.staccatocommons.collections.iterable.Iterables Maven / Gradle / Ivy
Show all versions of commons-collections Show documentation
/**
* Copyright (c) 2011, The Staccato-Commons Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*/
package net.sf.staccatocommons.collections.iterable;
import static net.sf.staccatocommons.collections.iterable.internal.IterablesInternal.*;
import static net.sf.staccatocommons.lang.tuple.Tuples.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import net.sf.staccatocommons.check.Ensure;
import net.sf.staccatocommons.defs.Applicable;
import net.sf.staccatocommons.defs.Applicable2;
import net.sf.staccatocommons.defs.Evaluable;
import net.sf.staccatocommons.defs.Evaluable2;
import net.sf.staccatocommons.defs.reduction.Accumulator;
import net.sf.staccatocommons.defs.reduction.Reduction;
import net.sf.staccatocommons.defs.tuple.Tuple2;
import net.sf.staccatocommons.defs.type.NumberType;
import net.sf.staccatocommons.lang.Option;
import net.sf.staccatocommons.lang.predicate.Equiv;
import net.sf.staccatocommons.lang.tuple.Tuples;
import net.sf.staccatocommons.restrictions.check.NonNull;
import net.sf.staccatocommons.restrictions.check.NotEmpty;
import net.sf.staccatocommons.restrictions.check.NotNegative;
import net.sf.staccatocommons.restrictions.check.Size;
import net.sf.staccatocommons.restrictions.processing.IgnoreRestrictions;
import org.apache.commons.lang.ObjectUtils;
/**
* Class methods that complement the {@link java.util.Collections}
* functionality, providing common algorithms for collections and iterables.
* With no exception, all these methods are eager, that is, processing is
* completely performed on method evaluation.
*
* Otherwise stated, null collections, functors and iterables are prohibited as
* parameter, but empty collections and iterables are allowed.
*
* {@link Iterables} class contains only side-effect-free methods that do not
* modify any of its arguments, and thus can be used with immutable collections.
*
* For algorithms that modify the state of the input collections, see
* {@link ModifiableIterables}. However, Stacatto-commons-collection API
* recommends to avoid those methods when possible, as will fail with
* unmodifiable collections.
*
* @author flbulgarelli
*/
public class Iterables {
/*
* Filtering
*/
/**
* Selects all elements that evaluate to true.
*
* @param iterable
* @param predicate
* @param
* @return a list containing only elements from the original iterable that
* evaluate to true
*/
@NonNull
public static List filter(@NonNull Iterable iterable,
@NonNull Evaluable predicate) {
return filterInternal(iterable, predicate, new LinkedList());
}
/**
* Selects at most the fist N elements from the iterable, according to its
* iteration order.
*
* @param
* @param iterable
* @param amountOfElements
* @return a new list containing at most the first N elements from original
* iterable.
*/
@NonNull
public static List take(@NonNull Iterable iterable, @NotNegative int amountOfElements) {
return takeInternal(iterable, amountOfElements, new ArrayList(amountOfElements));
}
/**
* Answers the result of aggregating the given iterable
elements
* using the given function
. The given {@link Iterable}
* must not be empty.
*
* @param
* the {@link Iterable}'s elements type
* @param iterable
* @param function
* the aggregation {@link Applicable}
* @return the result of aggregating the iterable
's element
*/
@NonNull
@IgnoreRestrictions
public static A reduce(@NotEmpty Iterable iterable,
@NonNull Applicable2 function) {
Ensure.isNotNull("function", function);
Iterator iter = iterable.iterator();
if (!iter.hasNext())
Ensure.fail(ITERABLE, iterable, "Must be not empty");
A result = iter.next();
while (iter.hasNext())
result = function.apply(result, iter.next());
return result;
}
/**
* Answers the result of aggregating the given initial
value and
* the {@link Iterable} elements using the given function
*
* As a particular case, if iterable
is empty, this method
* returns the initial value.
*
* @param
* the {@link Iterable}'s elements type
* @param
* the aggregated value type
* @param iterable
* @param initial
* @param function
* the aggregation {@link Applicable}
* @return the result of aggregating the initial value and the given
* iterable
's elements.
*/
@NonNull
public static B fold(@NonNull Iterable iterable, B initial,
@NonNull Applicable2 function) {
B result = initial;
for (A element : iterable)
result = function.apply(result, element);
return result;
}
/**
* Answers the result of aggregating the given iterable
using the
* given reduction
*
* @param
* the {@link Iterable}'s elements type
* @param
* the aggregated value type
* @param iterable
* the iterable to aggregate
* @param function
* the {@link Reduction} to apply to this iterable
* @return the result of aggregating the initial value and the given
* iterable
's elements.
*/
@NonNull
public static B reduce(@NonNull Iterable iterable, @NonNull Reduction reduction) {
Accumulator accum = reduction.newAccumulator();
for (A element : iterable) {
accum.accumulate(element);
}
return accum.value();
}
/*
* Search
*/
/**
* Alternative version of {@link #findOrNone(Iterable, Evaluable)}, where the
* element is returned if found, or a {@link NoSuchElementException} is thrown
* otherwise
*
* @param
* @param iterable
* @param predicate
* @return the element if found
* @throws NoSuchElementException
* if no element matches the predicate
*/
public static A find(@NonNull Iterable iterable, @NonNull Evaluable predicate) {
for (A o : iterable)
if (predicate.eval(o))
return o;
throw new NoSuchElementException();
}
/**
* Looks for a object that matches the given predicate. If such element does
* not exist, or collection is empty, returns {@link Option#none()}. Otherwise
* returns {@link Option#some(Object)}, for the first object found
*
* @param
* @param iterable
* non null
* @param predicate
* non null
* @return None if no element matches the predicate or collection is empty, or
* some(element) if at least one exists
*/
@NonNull
public static Option findOrNone(@NonNull Iterable iterable,
@NonNull Evaluable predicate) {
for (A o : iterable)
if (predicate.eval(o))
return Option.some(o);
return Option.none();
}
/**
* Returns the single element of the given collection. It must be of size 1,
* otherwise, throws an IllegalArgumentException.
*
* @param
* the collection type
* @param collection
* a single element (size==1) collection
* @return The unique element of the collection
*/
public static A single(@Size(1) Collection collection) {
return any(collection);
}
/**
* Returns any element from this iterable, or throws a
* {@link NoSuchElementException} it iterable is empty. Notice that
* any does not mean random, it may return always the same
* element - for example the first for a list -, but the exact element
* returned from the iterable unspecified.
*
* @param
* @param iterable
* @throws NoSuchElementException
* if the iterable is empty
* @return an element contained in the given iterable. This is nullable, if
* the iterables's iterator may return null.
*/
public static A any(@NonNull Iterable iterable) {
return iterable.iterator().next();
}
/**
* Gets any element of the given collection. Returns Option.some(element) if
* not empty, or Option.none(), if empty.
*
* @param
* @param iterable
* @return some(element) if not empty, or none, otherwise.
*/
@NonNull
public static Option anyOrNone(@NonNull Iterable iterable) {
Iterator iterator = iterable.iterator();
return iterator.hasNext() ? Option.some(iterator.next()) : Option. none();
}
/*
* Validating
*/
/**
* Tests if all the elements of the given {@link Iterable} satisfy the given
* predicate
*
* @param
* @param iterable
* @param predicate
* the predicate that will be used to evaluate each element
* @return true
if all the elements satisfy the given predicate,
* false
otherwise. As a particular case of this rule,
* this method will return true
for empty iterables,
* regardless of the predicate.
*/
public static boolean all(@NonNull Iterable iterable,
@NonNull Evaluable predicate) {
for (A o : iterable)
if (!predicate.eval(o))
return false;
return true;
}
/**
* Answers if all elements in the collection are equivalent using the given
* equivTest
.
*
* @param
* @param iterable
* May be empty.
* @param equivTest
* a predicate used to determine if two elements are equivalent
* @return true
if all element are the same object.
* false
otherwise. As a particular case of this rule, if
* this collection is empty or has only one element, it will return
* true
.
*/
public static boolean allEquivBy(@NonNull Iterable iterable,
Evaluable2 equivTest) {
Iterator iter = iterable.iterator();
if (!iter.hasNext())
return true;
A any = iter.next();
while (iter.hasNext())
if (!equivTest.eval(any, iter.next()))
return false;
return true;
}
/**
* Answers if all elements in the collection are equal.
*
* @param
* @param iterable
* non null. May be empty.
* @return true
if all element are equal. false
* otherwise. As a particular case of this rule, if this collection is
* empty or has only one element, it will return true
.
*/
public static boolean allEqual(@NonNull Iterable iterable) {
return allEquivBy(iterable, Equiv. equal());
}
/**
* Answers if all elements in the collection are the same object.
*
* @param
* @param iterable
* non null. May be empty.
* @return true
if all element are the same object.
* false
otherwise. As a particular case of this rule, if
* this collection is empty or has only one element, it will return
* true
.
*/
public static boolean allSame(@NonNull Iterable iterable) {
return allEquivBy(iterable, Equiv. same());
}
/**
* Tests if any of the elements of the given {@link Iterable} satisfy the
* given predicate
*
* @param
* @param iterable
* @param predicate
* the predicate that will be used to evaluate each element
* @return true
if at least one element satisfies the given
* predicate, false
otherwise. As a particular case of
* this rule, this method will return false
for empty
* iterables, regardless of the predicate.
*/
public static boolean any(@NonNull Iterable iterable, Evaluable predicate) {
for (A o : iterable)
if (predicate.eval(o))
return true;
return false;
}
/**
* Answers if the given {@link Iterable} is empty or not, that is, if its
* iterator returns at least one element.
*
* @param
* the iterable element type
* @param iterable
* @return if the iterable is empty or not
*/
public static boolean isEmpty(@NonNull Iterable iterable) {
return !iterable.iterator().hasNext();
}
/**
* Answers if the given {@link Iterable} is empty or null
*
* @param
* the iterable element type
* @param iterable
* @return if the iterable is null or empty
* @see #isEmpty(Iterable)
*/
public static boolean isNullOrEmpty(Iterable iterable) {
return iterable == null || isEmpty(iterable);
}
/**
* Answers if the given {@link Collection} is empty or null
*
* @param
* the collection element type
* @param collection
* @return if the collection is null or empty
*/
public static boolean isNullOrEmpty(Collection collection) {
return collection == null || collection.isEmpty();
}
/**
* Answers the size of the given iterable
, that is, the number of
* elements it retrieves
*
* @param iterable
* @return the number of elements in the given {@link Iterable}
*/
public static int size(@NonNull Iterable iterable) {
int size = 0;
for (Iterator iter = iterable.iterator(); iter.hasNext(); iter.next())
size++;
return size;
}
/**
* Test that the elements of both iterables are equal, and in the same order.
*
* @param
* @param iterable1
* first iterable
* @param iterable2
* second iterable
* @return true
if iterable1
has the same number of
* elements that iterable2
, and each Tuple2 formed by
* elements of both iterables at same position are equal.
* false
otherwise
*/
public static boolean equiv(@NonNull Iterable iterable1,
@NonNull Iterable iterable2) {
return equivBy(iterable1, iterable2, Equiv.equal().nullSafe());
}
/**
* Test that the elements of of both iterables are equal, and in the same
* order, using the given equalityTest
for determining equality
* of elements.
*
* @param equivTest
* @param equalityTest
* @return true
if iterable1
has the same number of
* elements that iterable2
, and each Tuple2 formed by
* elements of both iterables at same position are equivalent using
* the given eqivTest
. false
otherwise
*/
public static boolean equivBy(@NonNull Iterable iterable1,
@NonNull Iterable iterable2, Evaluable2 equivTest) {
Iterator iter = iterable1.iterator();
Iterator otherIter = iterable2.iterator();
while (iter.hasNext()) {
if (!otherIter.hasNext())
return false;
if (!equivTest.eval(iter.next(), otherIter.next()))
return false;
}
return !otherIter.hasNext();
}
/*
* Mapping
*/
/**
* Maps the given {@link Collection} into a new {@link List}, using the given
* function
*
* The resulting list contains contains the result of applying the given
* function
to each element retrieved from the original
* iterable
*
* @param
* the element type of the given collection
* @param
* the element type of the resulting list
* @param collection
* the source of the mapping.
* @param function
* an {@link Applicable} applied to each element of the source
* collection.
* @return a new, non null {@link List}. As a particular case, this method
* will return an empty list if the given collection is empty,
* regardless of the given {@link Applicable}
*/
@NonNull
public static List map(@NonNull Collection collection,
@NonNull Applicable function) {
return collectInternal(//
collection,
function,
new ArrayList(collection.size()));
}
/**
* Maps the given {@link Iterable} into a new list, using the given
* function
.
*
* The returned list contains contains the result of applying the given
* function
to each element retrieved from the original
* iterable
*
*
* @param
* the element type of the given iterables
* @param
* the element type of the resulting list
* @param iterable
* the source of the mapping.
* @param function
* the function applied to each element of the source iterable.
* @return a new, non null {@link List}. As a particular case, this method
* will return an empty list if the given iterable is empty
*/
@NonNull
public static List map(@NonNull Iterable iterable,
@NonNull Applicable function) {
return collectInternal(iterable, function, new LinkedList());
}
/**
* Maps the given iterable
into a list of iterables using the
* given function
, and flattens the result, concatenating all the
* resulting iterables into a {@link List}
*
* @param
* @param
* @param iterable
* @param function
* @return a new {@link List}
*/
@NonNull
public static List flatMap(@NonNull Iterable iterable,
@NonNull Applicable> function) {
LinkedList list = new LinkedList();
for (A element : iterable)
for (B applyedElement : function.apply(element))
list.add(applyedElement);
return list;
}
/*
* Sorting and converting
*/
/**
* Sorts a the given iterable
into a new list, using the given
* comparator.
*
* @param
* @param iterable
* the the collection.
* @param comparator
* @return a new list containing all the original colleciton elements, sorted
* using the given criteria, or an empty mutable list, if the original
* {@link Iterable} was empty.
*/
@NonNull
public static List toSortedList(@NonNull Iterable iterable,
@NonNull Comparator comparator) {
List list = new LinkedList();
addAllInternal(list, iterable);
java.util.Collections.sort(list, comparator);
return list;
}
/**
* Sorts a given iterable
using the given {@link Comparator} into
* a new {@link SortedSet}
*
* @param
* @param iterable
* the {@link Iterable} to sort
* @param comparator
* @return a sorted set containing the iterables elements sorted using the
* given comparator
*/
@NonNull
public static SortedSet toSortedSet(@NonNull Iterable iterable,
@NonNull Comparator comparator) {
TreeSet sortedSet = new TreeSet(comparator);
addAllInternal(sortedSet, iterable);
return sortedSet;
}
/**
* Sorts a given iterable
using its natural ordering
*
* @param
* @param iterable
* the {@link Iterable} to sort
* @return a sorted set containing the iterables elements sorted using the
* given comparator
*/
@NonNull
public static > SortedSet toSortedSet(@NonNull Iterable iterable) {
TreeSet sortedSet = new TreeSet();
addAllInternal(sortedSet, iterable);
return sortedSet;
}
/**
* Converts the given elements into a {@link Set}.
*
* @param
* @param elements
* @return a new {@link Set} that contains the elements given without
* repetitions
*/
public static Set toSet(A... elements) {
return toSet((Iterable) Arrays.asList(elements));
}
/**
* Converts the given collection into a {@link Set}. If the collection is
* already a set, it just returns it
*
* @param
* @param collection
* @return a new {@link Set} that contains all the elements from the given
* collection, or the given collection, if it is already a set
*/
public static Set toSet(Collection collection) {
return collection instanceof Set ? (Set) collection : new HashSet(collection);
}
/**
* Converts the given {@link Iterable} into a {@link Set}. If the
* iterable
is already a set, it just returns it
*
* @param
* @param iterable
* @return a new {@link Set} that contains all the elements from the given
* iterable
, or the given iterable
, if it is
* already a set
*/
@NonNull
public static Set toSet(@NonNull Iterable iterable) {
return ModifiableIterables.addAll(new HashSet(), iterable);
}
/**
* Converts the given collection into a {@link List}. If the collection is
* already a list, it just returns it
*
* @param
* @param collection
* @return a new {@link List} that contains all the elements from the given
* collection, or the given collection, if it is already a list
*/
public static List toList(@NonNull Collection collection) {
return collection instanceof List ? (List) collection : new ArrayList(collection);
}
/**
* Converts the given iterable into a {@link List}. If the iterable is already
* a list, it just returns it
*
* @param
* @param iterable
* @return a new {@link List} that contains all the elements from the given
* iterable, or the given collection, if it is already a list
*/
public static List toList(Iterable iterable) {
if (iterable instanceof List)
return (List) iterable;
LinkedList list = new LinkedList();
ModifiableIterables.addAll(list, iterable);
return list;
}
/* Partition */
/**
* Splits the given iterable by returning two {@link List}s, the first one
* containing those elements that satisfy the given predicate, and the second
* one containing those elements that do not satisfy it.
*
* For example, the following code:
*
*
* Iterables.partition(Arrays.asList(4, 8, 5, 20, 1), Predicate.greaterThan(5));
*
*
* will return a Tuple2 equal to:
*
*
* _(Arrays.asList(8, 20), Arrays.asList(4, 5, 1))
*
*
* @param
* @param iterable
* @param predicate
* the {@link Evaluable} used to determine if an elements goes into
* the first or second list
* @return a Tuple2 of lists
*/
public static Tuple2, List> partition(@NonNull Iterable iterable,
Evaluable predicate) {
List first = new LinkedList();
List second = new LinkedList();
for (A element : iterable)
if (predicate.eval(element))
first.add(element);
else
second.add(element);
return _(first, second);
}
/**
* Gets the zero-based, n
-th element of the given
* {@link Iterable}, according to its iteration order.
*
* @param
* @param iterable
* @param n
* @return the n-th element
* @throws IndexOutOfBoundsException
* if n is greater or equal than the size of the
* iterable
*/
public static A get(@NonNull Iterable iterable, int n) throws IndexOutOfBoundsException {
A element = null;
Iterator iter = iterable.iterator();
for (int i = 0; i <= n; i++)
try {
element = iter.next();
} catch (NoSuchElementException e) {
throw new IndexOutOfBoundsException("At " + n);
}
return element;
}
/**
* The zero-based index of a given element in the iterable when iterating over
* it
*
* @param
* @param iterable
* @param element
* @return the index of the element in the given iterable, or -1 if it is not
* contained on it
*/
public static int indexOf(@NonNull Iterable iterable, A element) {
int i = 0;
for (Object o : iterable) {
if (ObjectUtils.equals(element, o))
return i;
i++;
}
return -1;
}
/**
* If an element is before another when iterating through the given iterable.
*
* @param
* @param iterable
* @param previous
* the element to test if exist in the iterable and is be before next
* @param next
* the element to test if exists in the iterable and is after
* previous
* @return true if both elements are contained by the given iterable, and
* first element is before second one
*/
public static boolean isBefore(@NonNull Iterable iterable, A previous, A next) {
if (ObjectUtils.equals(previous, next))
return false;
boolean previousFound = false;
for (A o : iterable) {
if (!previousFound && ObjectUtils.equals(o, previous)) {
previousFound = true;
continue;
}
if (ObjectUtils.equals(o, next))
return previousFound;
}
return false;
}
/**
* Returns a {@link List} formed by the result of applying the given
* function
to each Tuple2 of elements from the two iterables
* given. If any if the {@link Iterable}s is shorter than the other one, the
* remaining elements are discarded.
*
*
* @param
* first iterable element type
* @param
* second iterable element type
* @param
* the resulting list element
* @param iterable1
* @param iterable2
* @param function
* the function to apply to each Tuple2
* @return a new list formed applying the given {@link Applicable2} to each
* Tuple2 of element retrieved from the given iterables. The resulting
* list size is the minimum of both iterables sizes. As a particular
* case, if any of both iterables is empty, returns an empty list,
* regardless of the given function
* @see #zip(Iterable, Iterable)
*/
@NonNull
public static List zip(@NonNull Iterable iterable1,
@NonNull Iterable iterable2, Applicable2 function) {
Iterator iter1 = iterable1.iterator();
Iterator iter2 = iterable2.iterator();
List result = new LinkedList();
while (iter1.hasNext() && iter2.hasNext())
result.add(function.apply(iter1.next(), iter2.next()));
return result;
}
/**
* Returns a {@link List} formed by Tuple2 of elements from the two iterables.
* If any if the {@link Iterable}s is shorter than the other one, the
* remaining elements are discarded.
*
* For example, the following code:
*
*
* Iterables.zip(Arrays.asList(10, 12, 14, 23), Arrays.asList(8, 7, 6))
*
*
* will return a list equal to:
*
*
* Arrays.asList(_(10, 8), _(12, 7), _(14, 6))
*
*
*
*
* @param
* first iterable element type
* @param
* second iterable element type
* @param iterable1
* @param iterable2
* @return a new list formed by Tuple2 of element retrieved from the given
* iterables. The resulting list size is the minimum of both iterables
* sizes. As a particular case, if any of both iterables is empty,
* returns an empty list.
* @see #zip(Iterable, Iterable, Applicable2)
*/
@NonNull
public static List> zip(@NonNull Iterable iterable1,
@NonNull Iterable iterable2) {
return zip(iterable1, iterable2, Tuples. toTuple2());
}
/**
* Answers the sum of the numeric elements of the given {@link Iterable},
* using the given {@link NumberType} to implement the addition. If the given
* iterable
is empty, it returns 0.
*
* For example, the following code:
*
*
* import static net.sf.staccatocommons.lang.number.NumberTypes.*;
* ...
* Iterables.sum(Arrays.asList(10, 60, 21), integer());
*
*
* will produce the result (Integer) 91
*
* @param
* @param iterable
* @param type
* @return fold(iterable, type.zero(), type.add())
*/
@NonNull
public static A sum(@NonNull Iterable iterable, @NonNull NumberType type) {
return fold(iterable, type.zero(), type.add());
}
/**
* Answers the product of the numeric elements of the given {@link Iterable},
* using the given {@link NumberType} to implement the multiplication. If the
* given iterable
is empty, it returns 1.
*
* For example, the following code:
*
*
* import static net.sf.staccatocommons.lang.number.NumberTypes.*;
* ...
* Iterables.product(Arrays.asList(2L, 4L, 8L, 3L), long_());
*
*
* will produce the result (Long) 192L
*
* @param
* @param iterable
* @param type
* @return fold(iterable, type.one(), type.multiply())
*/
@NonNull
public static A product(@NonNull Iterable iterable, @NonNull NumberType type) {
return fold(iterable, type.one(), type.multiply());
}
/**
* Answers the Cartesian product of the two given {@link Iterable}s.
*
* For example, the following code:
*
*
* Iterables.cross((Iterable<Integer>) Arrays.asList(1, 2), Arrays.asList('a', 'b'));
*
*
* Will produce a list equal to
*
*
* Arrays.asList(_(1, 'a'), _(1, 'b'), _(2, 'a'), _(2, 'b'))
*
*
* @param
* @param
* @param iterable1
* @param iterable2
* @return a new List with the Cartesian product of both collections.
*/
@NonNull
public static final List> cross(@NonNull Iterable iterable1,
@NonNull Iterable iterable2) {
return cross(iterable1, iterable2, new LinkedList());
}
/**
* Answers the Cartesian product of the two given {@link Collection}s.
*
* For example, the following code:
*
*
* Iterables.cross(Arrays.asList(1, 2), Arrays.asList('a', 'b'));
*
*
* Will produce a list equal to
*
*
* Arrays.asList(_(1, 'a'), _(1, 'b'), _(2, 'a'), _(2, 'b'))
*
*
* @param
* @param
* @param collection1
* @param collection2
* @return a new List with the Cartesian product of both collections.
*/
@NonNull
public static final List> cross(@NonNull Collection collection1,
@NonNull Collection collection2) {
return cross(collection1, collection2, new ArrayList(collection1.size() * collection2.size()));
}
@NonNull
private static List> cross(@NonNull Iterable iterable1,
@NonNull Iterable iterable2, @NonNull List> result) {
for (A a : iterable1)
for (B b : iterable2) {
result.add(_(a, b));
}
return result;
}
}