/*
* esodata - data structures and other things, of varying utility
* Copyright 2022, Ben Culkin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package bjc.esodata;
import static bjc.functypes.Combinators.*;
import java.util.*;
import java.util.function.*;
import bjc.data.*;
/**
* A list which can contain sublists of itself.
*
* N.B: Be careful if you form a recursive list, as there is no form of detection
* in place for that. Some operations may work, but those that do a deep traversal
* of the list will not.
*
* @author Ben Culkin
*
* @param The type contained in the list.
*/
public class NestList extends AbstractList>>
{
private final List>> backing;
/**
* Create a new empty nesting list.
*/
public NestList() {
backing = new ArrayList<>();
}
/**
* Create a new empty nesting list with the given capacity.
*
* @param cap The capacity for the nesting list.
*/
public NestList(int cap) {
backing = new ArrayList<>(cap);
}
/**
* Add an element to this list.
*
* @param element The element to add to the list.
*
* @return Whether we could add the element.
*/
public boolean addItem(Element element) {
return backing.add(Either.left(element));
}
/**
* Add a sublist to this list.
*
* @param element The sublist to add to this list.
*
* @return Whether we could add the sublist.
*/
public boolean addItem(NestList element) {
return backing.add(Either.right(element));
}
/**
* Add elements to this list.
*
* @param elements The elements to add to the list.
*
* @return Whether we could add each element.
*/
public boolean[] addItems(@SuppressWarnings("unchecked") Element... elements) {
boolean[] vals = new boolean[elements.length];
for (int i = 0; i < vals.length; i++)
{
vals[i] = addItem(elements[i]);
}
return vals;
}
/**
* Add sublists to this list.
*
* @param elements The sublists to add to this list.
*
* @return Whether we could add each sublist.
*/
public boolean[] addItems(@SuppressWarnings("unchecked") NestList... elements) {
boolean[] vals = new boolean[elements.length];
for (int i = 0; i < vals.length; i++)
{
vals[i] = addItem(elements[i]);
}
return vals;
}
/**
* Add a sublist with the given elements to this list.
*
* @param elements The elements of the sublist.
*
* @return Whether or not we could add the sublist.
*/
public boolean addSublist(@SuppressWarnings("unchecked") Element... elements) {
NestList container = new NestList<>(elements.length);
for (Element ele : elements) {
container.addItem(ele);
}
return addItem(container);
}
/**
* Return an iterator over a flattened version of this list.
*
* N.B: In certain cases involving empty sublists, the hasNext() operation\
* may not be 100% accurate. Be warned.
*
* @return An iterator over a flattened variant of this list.
*/
public ListIterator flatIterator() {
return new FlatNestIterator<>(listIterator());
}
/**
* Flatten one level of nesting from this list.
*
* @return The list with one level of nesting flattened.
*/
public NestList flatten() {
NestList flatterList = new NestList<>(size());
backing.forEach((element) ->
element.pick(flatterList::addItem, flatterList::addAll)
);
return flatterList;
}
/**
* Flatten this list recursively.
*
* @return A flattened form of this list.
*/
public List deepFlatten() {
List flatList = new ArrayList<>();
flatIterator().forEachRemaining(flatList::add);
return flatList;
}
/**
* Get the total number of elements contained in this list and all sublists.
*
* @return The total number of elements contained in this list.
*/
public int deepSize() {
int size = 0;
for (Either> element : backing)
{
size += element.extract((ele) -> 1, (lst) -> lst.deepSize());
}
return size;
}
/**
* Replace all of the elements in this list in-place with transformed versions.
*
* @param elementOperator The operator to apply to elements.
* @param listOperator The operator to apply to sublists.
*/
public void replace(
UnaryOperator elementOperator,
UnaryOperator> listOperator) {
backing.replaceAll((ele) -> ele.map(elementOperator, listOperator));
}
/**
* Perform a shallow mapping over this list.
*
* @param The new element type.
*
* @param elementMapper The function to map elements.
* @param listMapper The function to map lists.
*
* @return A new list containing the mapped elements.
*/
public NestList map(
Function elementMapper,
Function, NestList> listMapper)
{
NestList nest = new NestList<>(backing.size());
for (Either> element : backing)
{
nest.add(element.map(elementMapper, listMapper));
}
return nest;
}
/**
* Perform a recursive mapping over this list.
*
* @param The new element type.
*
* @param mapper The element mapper.
*
* @return A new list with the same structure, but transformed elements.
*/
public NestList deepMap(
Function mapper)
{
return map(mapper, (lst) -> lst.deepMap(mapper));
}
/**
* Perform a mapping on the list with controllable recursion.
*
* Inspired by the function of the same name from Raku.
*
* @param The new element type.
*
* @param recurPredicate Determines whether to recur into a list or not.
* @param elementMapper The mapper on elements.
* @param listMapper The mapper on lists we aren't recursing into.
*
* @return A new list with its elements mapped.
*/
public NestList duckMap(
Predicate> recurPredicate,
Function elementMapper,
Function, NestList> listMapper)
{
return map(
elementMapper,
iftt(recurPredicate,
(list) -> list.duckMap(
recurPredicate, elementMapper, listMapper),
listMapper
)
);
}
/**
* Perform a reduction over this list.
*
* @param