com.cognitect.transducers.Fns Maven / Gradle / Ivy
// Copyright 2014 Cognitect. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.cognitect.transducers;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.cognitect.transducers.Impl.reduce;
public class Fns {
/**
* Converts an step function into a complete reducing function. If passed an IReducingFunction, which
* is an IStepFunction, returns it; otherwise returns a new instance of IReducingFunction with a step
* function that forwards to the given IStepFunction, a single-arity apply method (used for completing
* reduction) that returns its argument (it's an identity function), and a zero-arity apply method
* (used for initializing a new return value for a reduction if one is not provided) that throws an
* IllegalStateException.
* @param sf The step function to convert to an IReducingFunction, if it is not one already
* @param the return type of the step function and reducing function
* @param the input type of the step function and the reducing function
* @return a new reducing function, or the input step function if it is already a reducing function
*/
@SuppressWarnings("unchecked")
public static IReducingFunction completing(final IStepFunction sf) {
if (sf instanceof IReducingFunction)
return (IReducingFunction) sf;
else
return new AReducingFunction() {
@Override
public R apply(R result, T input, AtomicBoolean reduced) {
return sf.apply(result, input, reduced);
}
};
}
/**
* Reduces input using transformed reducing function. Transforms reducing function by applying
* transducer. Reducing function must implement zero-arity apply that returns initial result
* to start reducing process.
* @param xf a transducer (or composed transducers) that transforms the reducing function
* @param rf a reducing function
* @param input the input to reduce
* @param return type
* @param type of input expected by reducing function
* @param type of input and type accepted by reducing function returned by transducer
* @return result of reducing transformed input
*/
public static R transduce(ITransducer xf, IReducingFunction rf, Iterable input) {
IReducingFunction _xf = xf.apply(rf);
return reduce(_xf, rf.apply(), input);
}
/**
* Reduces input using transformed reducing function. Transforms reducing function by applying
* transducer. Step function is converted to reducing function if necessary. Accepts initial value
* for reducing process as argument.
* @param xf a transducer (or composed transducers) that transforms the reducing function
* @param rf a reducing function
* @param init an initial value to start reducing process
* @param input the input to reduce
* @param return type
* @param type expected by reducing function
* @param type of input and type accepted by reducing function returned by transducer
* @return result of reducing transformed input
*/
public static R transduce(ITransducer xf, IStepFunction rf, R init, Iterable input) {
IReducingFunction _rf = completing(rf);
IReducingFunction _xf = xf.apply(_rf);
return reduce(_xf, init, input);
}
/**
* Transduces input into collection using built-in reducing function.
* @param xf a transducer (or composed transducers) that transforms the reducing function
* @param init an initial collection to start reducing process
* @param input the input to put into the collection
* @param return type
* @param type the collection contains
* @param type of input and type accepted by reducing function returned by transducer
* @return
*/
public static , A, B> R into(ITransducer xf, R init, Iterable input) {
return transduce(xf, new AReducingFunction() {
@Override
public R apply(R result, A input, AtomicBoolean reduced) {
result.add(input);
return result;
}
}, init, input);
}
/**
* Composes a transducer with another transducer, yielding a new transducer that
* @param left left hand transducer
* @param right right hand transducer
* @param reducing function input type
* @param reducing function input type
* @param reducing function input type
* @return
*/
public static ITransducer compose(final ITransducer left, final ITransducer right) {
return left.comp(right);
}
// *** transducers
/**
* Creates a transducer that transforms a reducing function by applying a mapping
* function to each input.
* @param f a mapping function from one type to another (can be the same type)
* @param input type of input reducing function
* @param input type of output reducing function
* @return a new transducer
*/
public static ITransducer map(final Function f) {
return new ATransducer() {
@Override
public IReducingFunction apply(final IReducingFunction rf) {
return new AReducingFunctionOn(rf) {
@Override
public R apply(R result, B input, AtomicBoolean reduced) {
return rf.apply(result, f.apply(input), reduced);
}
};
}
};
}
/**
* Creates a transducer that transforms a reducing function by applying a
* predicate to each input and processing only those inputs for which the
* predicate is true.
* @param p a predicate function
* @param input type of input and output reducing functions
* @return a new transducer
*/
public static ITransducer filter(final Predicate p) {
return new ATransducer() {
@Override
public IReducingFunction apply(IReducingFunction rf) {
return new AReducingFunctionOn(rf) {
@Override
public R apply(R result, A input, AtomicBoolean reduced) {
if (p.test(input))
return rf.apply(result, input, reduced);
return result;
}
};
}
};
}
/**
* Creates a transducer that transforms a reducing function by accepting
* an iterable of the expected input type and reducing it
* @param input type of input reducing function
* @param input type of output reducing function
* @return a new transducer
*/
public static > ITransducer cat() {
return new ATransducer() {
@Override
public IReducingFunction apply(final IReducingFunction rf) {
return new AReducingFunctionOn(rf) {
@Override
public R apply(R result, B input, AtomicBoolean reduced) {
return reduce(rf, result, input, reduced);
}
};
}
};
}
/**
* Creates a transducer that transforms a reducing function using
* a composition of map and cat.
* @param f a mapping function from one type to another (can be the same type)
* @param input type of input reducing function
* @param output type of output reducing function and iterable of input type
* of input reducing function
* @param input type of output reducing function
* @return a new transducer
*/
public static , C> ITransducer mapcat(Function f) {
return map(f).comp(Fns.cat());
}
/**
* Creates a transducer that transforms a reducing function by applying a
* predicate to each input and not processing those inputs for which the
* predicate is true.
* @param p a predicate function
* @param input type of input and output reducing functions
* @return a new transducer
*/
public static ITransducer remove(final Predicate p) {
return new ATransducer() {
@Override
public IReducingFunction apply(IReducingFunction rf) {
return new AReducingFunctionOn(rf) {
@Override
public R apply(R result, A input, AtomicBoolean reduced) {
if (!p.test(input))
return rf.apply(result, input, reduced);
return result;
}
};
}
};
}
/**
* Creates a transducer that transforms a reducing function such that
* it only processes n inputs, then the reducing process stops.
* @param n the number of inputs to process
* @param input type of input and output reducing functions
* @return a new transducer
*/
public static ITransducer take(final long n) {
return new ATransducer() {
@Override
public IReducingFunction apply(IReducingFunction rf) {
return new AReducingFunctionOn(rf) {
long taken = 0;
@Override
public R apply(R result, A input, AtomicBoolean reduced) {
R ret = result;
if (taken < n) {
ret = rf.apply(result, input, reduced);
taken++;
} else {
reduced.set(true);
}
return ret;
}
};
}
};
}
/**
* Creates a transducer that transforms a reducing function such that
* it processes inputs as long as the provided predicate returns true.
* If the predicate returns false, the reducing process stops.
* @param p a predicate used to test inputs
* @param input type of input and output reducing functions
* @return a new transducer
*/
public static ITransducer takeWhile(final Predicate p) {
return new ATransducer() {
@Override
public IReducingFunction apply(IReducingFunction rf) {
return new AReducingFunctionOn(rf) {
@Override
public R apply(R result, A input, AtomicBoolean reduced) {
R ret = result;
if (p.test(input)) {
ret = rf.apply(result, input, reduced);
} else {
reduced.set(true);
}
return ret;
}
};
}
};
}
/**
* Creates a transducer that transforms a reducing function such that
* it skips n inputs, then processes the rest of the inputs.
* @param n the number of inputs to skip
* @param input type of input and output reducing functions
* @return a new transducer
*/
public static ITransducer drop(final long n) {
return new ATransducer() {
@Override
public IReducingFunction apply(IReducingFunction rf) {
return new AReducingFunctionOn(rf) {
long dropped = 0;
@Override
public R apply(R result, A input, AtomicBoolean reduced) {
R ret = result;
if (dropped < n) {
dropped++;
} else {
ret = rf.apply(result, input, reduced);
}
return ret;
}
};
}
};
}
/**
* Creates a transducer that transforms a reducing function such that
* it skips inputs as long as the provided predicate returns true.
* Once the predicate returns false, the rest of the inputs are
* processed.
* @param p a predicate used to test inputs
* @param input type of input and output reducing functions
* @return a new transducer
*/
public static ITransducer dropWhile(final Predicate p) {
return new ATransducer() {
@Override
public IReducingFunction apply(IReducingFunction rf) {
return new AReducingFunctionOn(rf) {
boolean drop = true;
@Override
public R apply(R result, A input, AtomicBoolean reduced) {
if (drop && p.test(input)) {
return result;
}
drop = false;
return rf.apply(result, input, reduced);
}
};
}
};
}
/**
* Creates a transducer that transforms a reducing function such that
* it processes every nth input.
* @param n The frequence of inputs to process (e.g., 3 processes every third input).
* @param The input type of the input and output reducing functions
* @return a new transducer
*/
public static ITransducer takeNth(final long n) {
return new ATransducer() {
@Override
public IReducingFunction apply(IReducingFunction rf) {
return new AReducingFunctionOn(rf) {
long nth = 0;
@Override
public R apply(R result, A input, AtomicBoolean reduced) {
return ((nth++ % n) == 0) ? rf.apply(result, input, reduced) : result;
}
};
}
};
}
/**
* Creates a transducer that transforms a reducing function such that
* inputs that are keys in the provided map are replaced by the corresponding
* value in the map.
* @param smap a map of replacement values
* @param the input type of the input and output reducing functions
* @return a new transducer
*/
public static ITransducer replace(final Map smap) {
return map(new Function() {
@Override
public A apply(A a) {
if (smap.containsKey(a))
return smap.get(a);
return a;
}
});
}
/**
* Creates a transducer that transforms a reducing function by applying a
* function to each input and processing the resulting value, ignoring values
* that are null.
* @param f a function for processing inputs
* @param the input type of the input and output reducing functions
* @return a new transducer
*/
public static ITransducer keep(final Function f) {
return new ATransducer() {
@Override
public IReducingFunction apply(IReducingFunction rf) {
return new AReducingFunctionOn(rf) {
@Override
public R apply(R result, A input, AtomicBoolean reduced) {
A _input = f.apply(input);
if (_input != null)
return rf.apply(result, _input, reduced);
return result;
}
};
}
};
}
/**
* Creates a transducer that transforms a reducing function by applying a
* function to each input and processing the resulting value, ignoring values
* that are null.
* @param f a function for processing inputs
* @param the input type of the input and output reducing functions
* @return a new transducer
*/
public static ITransducer keepIndexed(final BiFunction f) {
return new ATransducer() {
@Override
public IReducingFunction apply(IReducingFunction rf) {
return new AReducingFunctionOn(rf) {
long n = 0;
@Override
public R apply(R result, A input, AtomicBoolean reduced) {
n++;
A _input = f.apply(n, input);
if (_input != null)
return rf.apply(result, _input, reduced);
return result;
}
};
}
};
}
/**
* Creates a transducer that transforms a reducing function such that
* consecutive identical input values are removed, only a single value
* is processed.
* @param the input type of the input and output reducing functions
* @return a new transducer
*/
public static ITransducer dedupe() {
return new ATransducer() {
@Override
public IReducingFunction apply(IReducingFunction rf) {
return new AReducingFunctionOn(rf) {
A prior = null;
@Override
public R apply(R result, A input, AtomicBoolean reduced) {
R ret = result;
if (prior != input) {
prior = input;
ret = rf.apply(result, input, reduced);
}
return ret;
}
};
}
};
}
/**
* Creates a transducer that transforms a reducing function such that
* it has the specified probability of processing each input.
* @param prob the probability between expressed as a value between 0 and 1.
* @param the input type of the input and output reducing functions
* @return a new transducer
*/
public static ITransducer randomSample(final Double prob) {
return filter(new Predicate() {
@Override
public boolean test(A a) {
return (Math.random() < prob);
}
});
}
/**
* Creates a transducer that transforms a reducing function that processes
* iterables of input into a reducing function that processes individual inputs
* by gathering series of inputs for which the provided partitioning function returns
* the same value, only forwarding them to the next reducing function when the value
* the partitioning function returns for a given input is different from the value
* returned for the previous input.
* @param f the partitioning function
* @param the input type of the input and output reducing functions
* @param the type returned by the partitioning function
* @return a new transducer
*/
public static ITransducer, A> partitionBy(final Function f) {
return new ATransducer, A>() {
@Override
public IReducingFunction apply(final IReducingFunction> rf) {
return new IReducingFunction() {
List part = new ArrayList();
Object mark = new Object();
Object prior = mark;
@Override
public R apply() {
return rf.apply();
}
@Override
public R apply(R result) {
R ret = result;
if (!part.isEmpty()) {
List copy = new ArrayList(part);
part.clear();
ret = rf.apply(result, copy, new AtomicBoolean());
}
return rf.apply(ret);
}
@Override
public R apply(R result, A input, AtomicBoolean reduced) {
P val = f.apply(input);
if ((prior == mark) || (prior.equals(val))) {
prior = val;
part.add(input);
return result;
} else {
List copy = new ArrayList(part);
prior = val;
part.clear();
R ret = rf.apply(result, copy, reduced);
if (!reduced.get()) {
part.add(input);
}
return ret;
}
}
};
}
};
}
/**
* Creates a transducer that transforms a reducing function that processes
* iterables of input into a reducing function that processes individual inputs
* by gathering series of inputs into partitions of a given size, only forwarding
* them to the next reducing function when enough inputs have been accrued. Processes
* any remaining buffered inputs when the reducing process completes.
* @param n the size of each partition
* @param the input type of the input and output reducing functions
* @return a new transducer
*/
public static ITransducer, A> partitionAll(final int n) {
return new ATransducer, A>() {
@Override
public IReducingFunction apply(final IReducingFunction> rf) {
return new IReducingFunction() {
List part = new ArrayList(n);
@Override
public R apply() {
return rf.apply();
}
@Override
public R apply(R result) {
R ret = result;
if (!part.isEmpty()) {
List copy = new ArrayList(part);
part.clear();
ret = rf.apply(result, copy, new AtomicBoolean());
}
return rf.apply(ret);
}
@Override
public R apply(R result, A input, AtomicBoolean reduced) {
part.add(input);
if (n == part.size()) {
List copy = new ArrayList(part);
part.clear();
return rf.apply(result, copy, reduced);
}
return result;
}
};
}
};
}
}