All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.tommyettinger.digital.ArrayTools Maven / Gradle / Ivy

There is a newer version: 0.5.1
Show newest version
/*
 * Copyright (c) 2022-2023 See AUTHORS file.
 *
 * 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.github.tommyettinger.digital;

import java.util.Arrays;
import java.util.Random;

/**
 * Static methods for various frequently-used operations on 1D and 2D arrays. Has methods for copying, inserting, and
 * filling 2D arrays of primitive types (char, int, float, double, and boolean). Has a few methods for creating ranges
 * of ints or chars easily as 1D arrays.
 */
public final class ArrayTools {

    /**
     * Cannot be instantiated.
     */
    private ArrayTools() {
    }

    /**
     * 256 Latin and Greek letters, in upper and lower case, chosen so none should look identical. Useful when randomly
     * assigning identifying characters to values when you only have one char to display, and it should look unique.
     */
    private static final char[] letters = {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a',
            'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'À', 'Á',
            'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý',
            'Þ', 'ß', 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 'ø', 'ù',
            'ú', 'û', 'ü', 'ý', 'þ', 'ÿ', 'Ā', 'ā', 'Ă', 'ă', 'Ą', 'ą', 'Ć', 'ć', 'Ĉ', 'ĉ', 'Ċ', 'ċ', 'Č', 'č', 'Ď', 'ď', 'Đ', 'đ', 'Ē', 'ē', 'Ĕ',
            'ĕ', 'Ė', 'ė', 'Ę', 'ę', 'Ě', 'ě', 'Ĝ', 'ĝ', 'Ğ', 'ğ', 'Ġ', 'ġ', 'Ģ', 'ģ', 'Ĥ', 'ĥ', 'Ħ', 'ħ', 'Ĩ', 'ĩ', 'Ī', 'ī', 'Ĭ', 'ĭ', 'Į', 'į',
            'İ', 'ı', 'Ĵ', 'ĵ', 'Ķ', 'ķ', 'ĸ', 'Ĺ', 'ĺ', 'Ļ', 'ļ', 'Ľ', 'ľ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'Ń', 'ń', 'Ņ', 'ņ', 'Ň', 'ň', 'ʼn', 'Ō', 'ō', 'Ŏ',
            'ŏ', 'Ő', 'ő', 'Œ', 'œ', 'Ŕ', 'ŕ', 'Ŗ', 'ŗ', 'Ř', 'ř', 'Ś', 'ś', 'Ŝ', 'ŝ', 'Ş', 'ş', 'Š', 'š', 'Ţ', 'ţ', 'Ť', 'ť', 'Ŧ', 'ŧ', 'Ũ', 'ũ',
            'Ū', 'ū', 'Ŭ', 'ŭ', 'Ů', 'ů', 'Ű', 'ű', 'Ų', 'ų', 'Ŵ', 'ŵ', 'Ŷ', 'ŷ', 'Ÿ', 'Ź', 'ź', 'Ż', 'ż', 'Ž', 'ž', 'Ǿ', 'ǿ', 'Ș', 'ș', 'Ț', 'ț',
            'Γ', 'Δ', 'Θ', 'Λ', 'Ξ', 'Π', 'Σ', 'Φ', 'Ψ', 'Ω', 'α', 'β', 'γ'};

    private static final String[] greekLowerCase = {"alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta",
            "theta", "iota", "kappa", "lambda", "mu", "nu", "xi", "omicron", "pi", "rho", "sigma", "tau", "upsilon",
            "phi", "chi", "psi", "omega",};
    private static final String[] greekUpperCase = {"ALPHA", "BETA", "GAMMA", "DELTA", "EPSILON", "ZETA", "ETA",
            "THETA", "IOTA", "KAPPA", "LAMBDA", "MU", "NU", "XI", "OMICRON", "PI", "RHO", "SIGMA", "TAU", "UPSILON",
            "PHI", "CHI", "PSI", "OMEGA",};
    private static final String[] demonsLowerCase = {
            "baal", "agares", "vassago", "samigina", "marbas", "valefor", "amon", "barbatos", "paimon", "buer",
            "gusion", "sitri", "beleth", "leraje", "eligos", "zepar", "botis", "bathin", "sallos", "purson",
            "marax", "ipos", "aim", "naberius", "glasya_labolas", "bune", "ronove", "berith", "astaroth", "forneus",
            "foras", "asmoday", "gaap", "furfur", "marchosias", "stolas", "phenex", "halphas", "malphas", "raum",
            "focalor", "vepar", "sabnock", "shax", "vine", "bifrons", "vual", "haagenti", "crocell", "furcas", "balam",
            "alloces", "caim", "murmur", "orobas", "gremory", "ose", "amy", "orias", "vapula", "zagan", "valac",
            "andras", "flauros", "andrealphus", "kimaris", "amdusias", "belial", "decarabia", "seere", "dantalion",
            "andromalius",};
    private static final String[] demonsUpperCase = {
            "BAAL", "AGARES", "VASSAGO", "SAMIGINA", "MARBAS", "VALEFOR", "AMON", "BARBATOS", "PAIMON", "BUER",
            "GUSION", "SITRI", "BELETH", "LERAJE", "ELIGOS", "ZEPAR", "BOTIS", "BATHIN", "SALLOS", "PURSON",
            "MARAX", "IPOS", "AIM", "NABERIUS", "GLASYA_LABOLAS", "BUNE", "RONOVE", "BERITH", "ASTAROTH", "FORNEUS",
            "FORAS", "ASMODAY", "GAAP", "FURFUR", "MARCHOSIAS", "STOLAS", "PHENEX", "HALPHAS", "MALPHAS", "RAUM",
            "FOCALOR", "VEPAR", "SABNOCK", "SHAX", "VINE", "BIFRONS", "VUAL", "HAAGENTI", "CROCELL", "FURCAS", "BALAM",
            "ALLOCES", "CAIM", "MURMUR", "OROBAS", "GREMORY", "OSE", "AMY", "ORIAS", "VAPULA", "ZAGAN", "VALAC",
            "ANDRAS", "FLAUROS", "ANDREALPHUS", "KIMARIS", "AMDUSIAS", "BELIAL", "DECARABIA", "SEERE", "DANTALION",
            "ANDROMALIUS",};
    private static final String[] chemistryLowerCase = {
            "hydrogen", "helium", "lithium", "beryllium", "boron", "carbon", "nitrogen", "oxygen", "fluorine", "neon",
            "sodium", "magnesium", "aluminium", "silicon", "phosphorus", "sulfur", "chlorine", "argon", "potassium",
            "calcium", "scandium", "titanium", "vanadium", "chromium", "manganese", "iron", "cobalt", "nickel",
            "copper", "zinc", "gallium", "germanium", "arsenic", "selenium", "bromine", "krypton", "rubidium",
            "strontium", "yttrium", "zirconium", "niobium", "molybdenum", "technetium", "ruthenium", "rhodium",
            "palladium", "silver", "cadmium", "indium", "tin", "antimony", "tellurium", "iodine", "xenon", "caesium",
            "barium", "lanthanum", "cerium", "praseodymium", "neodymium", "promethium", "samarium", "europium",
            "gadolinium", "terbium", "dysprosium", "holmium", "erbium", "thulium", "ytterbium", "lutetium", "hafnium",
            "tantalum", "tungsten", "rhenium", "osmium", "iridium", "platinum", "gold", "mercury", "thallium", "lead",
            "bismuth", "polonium", "astatine", "radon", "francium", "radium", "actinium", "thorium", "protactinium",
            "uranium", "neptunium", "plutonium", "americium", "curium", "berkelium", "californium", "einsteinium",
            "fermium", "mendelevium", "nobelium", "lawrencium", "rutherfordium", "dubnium", "seaborgium", "bohrium",
            "hassium", "meitnerium", "darmstadtium", "roentgenium", "copernicium", "nihonium", "flerovium", "moscovium",
            "livermorium", "tennessine", "oganesson",
    };
    private static final String[] chemistryUpperCase = {
            "HYDROGEN", "HELIUM", "LITHIUM", "BERYLLIUM", "BORON", "CARBON", "NITROGEN", "OXYGEN", "FLUORINE", "NEON",
            "SODIUM", "MAGNESIUM", "ALUMINIUM", "SILICON", "PHOSPHORUS", "SULFUR", "CHLORINE", "ARGON", "POTASSIUM",
            "CALCIUM", "SCANDIUM", "TITANIUM", "VANADIUM", "CHROMIUM", "MANGANESE", "IRON", "COBALT", "NICKEL",
            "COPPER", "ZINC", "GALLIUM", "GERMANIUM", "ARSENIC", "SELENIUM", "BROMINE", "KRYPTON", "RUBIDIUM",
            "STRONTIUM", "YTTRIUM", "ZIRCONIUM", "NIOBIUM", "MOLYBDENUM", "TECHNETIUM", "RUTHENIUM", "RHODIUM",
            "PALLADIUM", "SILVER", "CADMIUM", "INDIUM", "TIN", "ANTIMONY", "TELLURIUM", "IODINE", "XENON", "CAESIUM",
            "BARIUM", "LANTHANUM", "CERIUM", "PRASEODYMIUM", "NEODYMIUM", "PROMETHIUM", "SAMARIUM", "EUROPIUM",
            "GADOLINIUM", "TERBIUM", "DYSPROSIUM", "HOLMIUM", "ERBIUM", "THULIUM", "YTTERBIUM", "LUTETIUM", "HAFNIUM",
            "TANTALUM", "TUNGSTEN", "RHENIUM", "OSMIUM", "IRIDIUM", "PLATINUM", "GOLD", "MERCURY", "THALLIUM", "LEAD",
            "BISMUTH", "POLONIUM", "ASTATINE", "RADON", "FRANCIUM", "RADIUM", "ACTINIUM", "THORIUM", "PROTACTINIUM",
            "URANIUM", "NEPTUNIUM", "PLUTONIUM", "AMERICIUM", "CURIUM", "BERKELIUM", "CALIFORNIUM", "EINSTEINIUM",
            "FERMIUM", "MENDELEVIUM", "NOBELIUM", "LAWRENCIUM", "RUTHERFORDIUM", "DUBNIUM", "SEABORGIUM", "BOHRIUM",
            "HASSIUM", "MEITNERIUM", "DARMSTADTIUM", "ROENTGENIUM", "COPERNICIUM", "NIHONIUM", "FLEROVIUM", "MOSCOVIUM",
            "LIVERMORIUM", "TENNESSINE", "OGANESSON",
    };

    private static final String[] allSymbols = new String[greekLowerCase.length + greekUpperCase.length +
            demonsLowerCase.length + demonsUpperCase.length + chemistryLowerCase.length + chemistryUpperCase.length];
    static {
        int ins = 0;
        System.arraycopy(greekLowerCase, 0, allSymbols, 0, greekLowerCase.length);
        System.arraycopy(greekUpperCase, 0, allSymbols, ins += greekLowerCase.length, greekUpperCase.length);
        System.arraycopy(demonsLowerCase, 0, allSymbols, ins += greekUpperCase.length, demonsLowerCase.length);
        System.arraycopy(demonsUpperCase, 0, allSymbols, ins += demonsLowerCase.length, demonsUpperCase.length);
        System.arraycopy(chemistryLowerCase, 0, allSymbols, ins += demonsUpperCase.length, chemistryLowerCase.length);
        System.arraycopy(chemistryUpperCase, 0, allSymbols, ins += chemistryLowerCase.length, chemistryUpperCase.length);
    }
    /**
     * An unseeded Random instance (specifically, an {@link AlternateRandom}) that is used by {@link #shuffle(int[])}
     * and related overloads when given no Random argument or a null one. This cannot be relied upon to have a given
     * state, even if you set the seed; if you need a deterministic state, use your own seeded Random object and pass
     * it to shuffle(). You could assign null to this, but it's a very bad idea and will probably result in a crash; a
     * better idea is to assign a subclass of Random with a large state size, such as a RandomXS128 from libGDX or any
     * generator from juniper. The default AlternateRandom has quite a large state already (320 bits), and so is likely
     * to be better at shuffling than a RandomXS128, but RandomXS128 and all of juniper's generators allow reading and
     * directly setting the state, so if that matters to you, use one of those.
     */
    public static Random RANDOM = new AlternateRandom();

    private static final char[] emptyChars = new char[0];
    private static final int[] emptyInts = new int[0];
    private static final char[][] emptyChars2D = new char[0][0];
    private static final boolean[][] emptyBooleans2D = new boolean[0][0];
    private static final int[][] emptyInts2D = new int[0][0];
    private static final long[][] emptyLongs2D = new long[0][0];
    private static final float[][] emptyFloats2D = new float[0][0];
    private static final double[][] emptyDoubles2D = new double[0][0];
    private static final String[] emptyStrings = new String[0];

    /**
     * Stupidly simple convenience method that produces a range from 0 to end, not including end, as an int array.
     *
     * @param end the exclusive upper bound on the range
     * @return the range of ints as an int array
     */
    public static int[] range(int end) {
        if (end <= 0)
            return emptyInts;
        int[] r = new int[end];
        for (int i = 0; i < end; i++) {
            r[i] = i;
        }
        return r;
    }

    /**
     * Fills the given int array with sequential ints from 0 to {@code buffer.length - 1}.
     *
     * @param buffer an int array that will be modified in-place; if null this returns null
     * @return buffer, after modifications
     */
    public static int[] range(int[] buffer) {
        int len;
        if (buffer == null || (len = buffer.length) == 0) return buffer;
        for (int i = 0; i < len; i++) {
            buffer[i] = i;
        }
        return buffer;
    }

    /**
     * Stupidly simple convenience method that produces a range from start to end, not including end, as an int array.
     *
     * @param start the inclusive lower bound on the range
     * @param end   the exclusive upper bound on the range
     * @return the range of ints as an int array
     */
    public static int[] range(int start, int end) {
        if (end - start <= 0)
            return emptyInts;
        int[] r = new int[end - start];
        for (int i = 0, n = start; n < end; i++, n++) {
            r[i] = n;
        }
        return r;
    }

    /**
     * Stupidly simple convenience method that produces a char range from start to end, including end, as a char array.
     *
     * @param start the inclusive lower bound on the range, such as 'a'
     * @param end   the inclusive upper bound on the range, such as 'z'
     * @return the range of chars as a char array
     */
    public static char[] charSpan(char start, char end) {
        if (end - start <= 0)
            return emptyChars;
        if (end == 0xffff) {

            char[] r = new char[0x10000 - start];
            for (char i = 0, n = start; n < end; i++, n++) {
                r[i] = n;
            }
            r[0xffff - start] = 0xffff;
            return r;
        }
        char[] r = new char[end - start + 1];
        for (char i = 0, n = start; n <= end; i++, n++) {
            r[i] = n;
        }
        return r;
    }
    
    /**
     * Stupidly simple convenience method that produces a char range from u0000 to uFFFF, limited to {@code buffer.length - 1}.
     *
     * @param buffer a char array that will be modified in-place; if null this returns null
     * @return the range of chars as a char array
     */
    public static char[] charSpan(char[] buffer) {
        int end;
        if (buffer == null || (end = Math.min(buffer.length - 1, 0xFFFF)) < 0) return buffer;
        for (char i = 0; end >= 0; i++, end--) {
            buffer[i] = i;
        }
        return buffer;
    }
    
    /**
     * Stupidly simple convenience method that produces a char range from u0000 to end, including end, as a char array.
     *
     * @param end the inclusive upper bound on the range; typically a char, but may be larger than 0xFFFF to repeat
     * @return the range of chars as a char array
     */
    public static char[] charSpan(int end) {
        if(end < 0) return emptyChars;
        char[] r = new char[end + 1];
        for (char i = 0; end >= 0; i++, end--) {
            r[i] = i;
        }
        return r;
    }

    /**
     * Stupidly simple convenience method that produces a char array containing only letters that can be reasonably
     * displayed with many fonts. The letters are copied from a single source
     * of 256 chars; if you need more chars, or you don't need pure letters, you can use {@link #charSpan(char, char)}.
     * This set does not contain "visual duplicate" letters, such as Latin alphabet capital letter 'A' and Greek
     * alphabet capital letter alpha, 'Α'; it does contain many accented Latin letters and the visually-distinct Greek
     * letters, up to a point.
     *
     * @param charCount the number of letters to return in an array; the maximum this will produce is 256
     * @return the range of letters as a char array
     */
    public static char[] letterSpan(int charCount) {
        if (charCount <= 0)
            return emptyChars;
        char[] r = new char[Math.min(charCount, 256)];
        System.arraycopy(letters, 0, r, 0, r.length);
        return r;
    }

    /**
     * Stupidly simple convenience method that produces a char array containing only letters that can be reasonably
     * displayed with many fonts. The letters are copied from a single source
     * of 256 chars; if you need more chars, or you don't need pure letters, you can use {@link #charSpan(char, char)}.
     * This set does not contain "visual duplicate" letters, such as Latin alphabet capital letter 'A' and Greek
     * alphabet capital letter alpha, 'Α'; it does contain many accented Latin letters and the visually-distinct Greek
     * letters, up to a point.
     *
     * @param start the index of the first letter in the source of 256 chars to use
     * @param charCount the number of letters to return in an array; the maximum this can produce is 256
     * @return the range of letters as a char array
     */
    public static char[] letterSpan(int start, int charCount) {
        if (charCount <= 0 || start < 0 || start >= 256)
            return emptyChars;
        char[] r = new char[Math.min(charCount, 256 - start)];
        System.arraycopy(letters, start, r, 0, r.length);
        return r;
    }

    /**
     * Gets the nth letter from the set of 256 visually distinct glyphs; from index 0 (returning 'A') to 255
     * (returning the Greek lower-case letter gamma, 'γ') and wrapping around if given negative numbers or numbers
     * larger than 255. This set does not contain "visual duplicate" letters, such as Latin alphabet capital letter 'A'
     * and Greek alphabet capital letter alpha, 'Α'; it does contain many accented Latin letters and the
     * visually-distinct Greek letters, up to a point.
     *
     * @param index typically from 0 to 255, but all ints are allowed and will produce letters
     * @return the letter at the given index in a 256-element portion of visually distinct letters
     */
    public static char letterAt(int index) {
        return letters[index & 255];
    }

    /**
     * Stupidly simple convenience method that produces a String array containing various short terms, such as the names
     * of Greek letters (24 in lower case and then 24 in upper case), the names of demons from the Ars Goetia (72
     * names in lower case and then 72 in upper case, some of which can include the underscore), and the names of
     * chemical elements from the periodic table (118 names in lower case and then 118 in upper case). This can be
     * useful for quickly obtaining Strings that are guaranteed to at least be different and not overly long. This group
     * of 428 total Strings is used to seed every Hasher in {@link Hasher#predefined}.
     *
     * @param stringCount the number of Strings to return in an array; the maximum this will produce is 428
     * @return the range of Strings as an array
     */
    public static String[] stringSpan(int stringCount) {
        if (stringCount <= 0)
            return emptyStrings;
        String[] r = new String[Math.min(stringCount, allSymbols.length)];
        System.arraycopy(allSymbols, 0, r, 0, r.length);
        return r;
    }

    /**
     * Stupidly simple convenience method that produces a String array containing various short terms, such as the names
     * of Greek letters (24 in lower case and then 24 in upper case), the names of demons from the Ars Goetia (72
     * names in lower case and then 72 in upper case, some of which can include the underscore), and the names of
     * chemical elements from the periodic table (118 names in lower case and then 118 in upper case). This can be
     * useful for quickly obtaining Strings that are guaranteed to at least be different and not overly long. This group
     * of 428 total Strings is used to seed every Hasher in {@link Hasher#predefined}.
     *
     * @param start the index of the first letter in the source of 428 Strings to use
     * @param charCount the number of Strings to return in an array; the maximum this can produce is 428
     * @return the range of letters as a char array
     */
    public static String[] stringSpan(int start, int charCount) {
        if (charCount <= 0 || start < 0 || start >= allSymbols.length)
            return emptyStrings;
        String[] r = new String[Math.min(charCount, allSymbols.length - start)];
        System.arraycopy(allSymbols, start, r, 0, r.length);
        return r;
    }

    /**
     * Gets the nth String from a set of 428 short terms, such as the names of Greek letters (24 in lower case and then
     * 24 in upper case), the names of demons from the Ars Goetia (72 names in lower case and then 72 in upper case,
     * some of which can include the underscore), and the names of chemical elements from the periodic table (118 names
     * in lower case and then 118 in upper case). This can be useful for quickly obtaining Strings that are guaranteed
     * to at least be different and not overly long. This group of 428 total Strings is used to seed every Hasher in
     * {@link Hasher#predefined}.
     *
     * @param index typically from 0 to 427, but all ints are allowed and will produce Strings
     * @return the String at the given index in a 428-element group of miscellaneous short terms
     */
    public static String stringAt(int index) {
        return allSymbols[(index & 1023) % allSymbols.length];
    }

    /**
     * Gets the nth String from a set of 24 names of Greek letters in lower case. This can be useful for quickly
     * obtaining Strings that are guaranteed to at least be different and not overly long.
     *
     * @param index typically from 0 to 23, but all ints are allowed and will produce Strings
     * @return the String at the given index in a 24-element group of Greek letter names
     */
    public static String greekLetterAt(int index) {
        return greekLowerCase[(index & 1023) % greekLowerCase.length];
    }

    /**
     * Gets the nth String from a set of
     * 72 names of demons from the Ars Goetia
     * in lower case. This can be useful for quickly obtaining Strings that are guaranteed to at least be different and
     * not overly long.
     *
     * @param index typically from 0 to 71, but all ints are allowed and will produce Strings
     * @return the String at the given index in a 72-element group of Greek letter names
     */
    public static String demonNameAt(int index) {
        return demonsLowerCase[(index & 1023) % demonsLowerCase.length];
    }

    /**
     * Gets the nth String from the 118 names of chemical elements on the periodic table in lower case. This can be
     * useful for quickly obtaining Strings that are guaranteed to at least be different and not overly long.
     *
     * @param index typically from 0 to 117, but all ints are allowed and will produce Strings
     * @return the String at the given index in a 118-element group of Greek letter names
     */
    public static String chemicalElementAt(int index) {
        return chemistryLowerCase[(index & 1023) % chemistryLowerCase.length];
    }

    /**
     * Gets up to 24 Strings from a set of 24 names of Greek letters, in upper case if {@code upperCase}
     * is true, otherwise in lower case. The {@code start} refers to the index of the first String in the set. The
     * {@code length} can change the number of Strings returned to less than 24, but not greater than 24.
     * @param start the index of the first String in the set to return; typically non-negative and less than 24
     * @param length how many String items to return; at most 24, and if 0 or less this returns an empty array
     * @param upperCase if true, each String will be in UPPER CASE.
     * @return up to 24 distinct String items
     */
    public static String[] greekLetters(int start, int length, boolean upperCase) {
        start = (start % 24 + 24) % 24;
        return stringSpan(start + (upperCase ? 24 : 0), Math.min(length, 24 - start));
    }

    /**
     * Gets up to 72 Strings from a set of 72 names of demons from the Ars Goetia, in upper case if {@code upperCase}
     * is true, otherwise in lower case. The {@code start} refers to the index of the first String in the set. The
     * {@code length} can change the number of Strings returned to less than 72, but not greater than 72.
     * @param start the index of the first String in the set to return; typically non-negative and less than 72
     * @param length how many String items to return; at most 72, and if 0 or less this returns an empty array
     * @param upperCase if true, each String will be in UPPER CASE.
     * @return up to 72 distinct String items
     */
    public static String[] demonNames(int start, int length, boolean upperCase) {
        start = (start % 72 + 72) % 72;
        return stringSpan(start + (upperCase ? 72 : 0) + 48, Math.min(length, 72 - start));
    }

    /**
     * Gets up to 118 Strings from a set of 118 names of chemical elements, in upper case if {@code upperCase}
     * is true, otherwise in lower case. The {@code start} refers to the index of the first String in the set. The
     * {@code length} can change the number of Strings returned to less than 118, but not greater than 118.
     * @param start the index of the first String in the set to return; typically non-negative and less than 118
     * @param length how many String items to return; at most 118, and if 0 or less this returns an empty array
     * @param upperCase if true, each String will be in UPPER CASE.
     * @return up to 118 distinct String items
     */
    public static String[] chemicalElements(int start, int length, boolean upperCase) {
        start = (start % 118 + 118) % 118;
        return stringSpan(start + (upperCase ? 118 : 0) + 48 + 144, Math.min(length, 118 - start));
    }

    /**
     * Gets a copy of the 2D char array, source, that has the same data but shares no references with source.
     *
     * @param source a 2D char array
     * @return a copy of source, or null if source is null
     */
    public static char[][] copy(char[][] source) {
        if (source == null)
            return null;
        if (source.length < 1)
            return emptyChars2D;
        char[][] target = new char[source.length][];
        for (int i = 0; i < source.length; i++) {
            final int len = source[i].length;
            target[i] = new char[len];
            System.arraycopy(source[i], 0, target[i], 0, len);
        }
        return target;
    }

    /**
     * Gets a copy of the 2D double array, source, that has the same data but shares no references with source.
     *
     * @param source a 2D double array
     * @return a copy of source, or null if source is null
     */
    public static double[][] copy(double[][] source) {
        if (source == null)
            return null;
        if (source.length < 1)
            return emptyDoubles2D;
        double[][] target = new double[source.length][];
        for (int i = 0; i < source.length; i++) {
            final int len = source[i].length;
            target[i] = new double[len];
            System.arraycopy(source[i], 0, target[i], 0, len);
        }
        return target;
    }


    /**
     * Gets a copy of the 2D float array, source, that has the same data but shares no references with source.
     *
     * @param source a 2D float array
     * @return a copy of source, or null if source is null
     */
    public static float[][] copy(float[][] source) {
        if (source == null)
            return null;
        if (source.length < 1)
            return emptyFloats2D;
        float[][] target = new float[source.length][];
        for (int i = 0; i < source.length; i++) {
            final int len = source[i].length;
            target[i] = new float[len];
            System.arraycopy(source[i], 0, target[i], 0, len);
        }
        return target;
    }

    /**
     * Gets a copy of the 2D int array, source, that has the same data but shares no references with source.
     *
     * @param source a 2D int array
     * @return a copy of source, or null if source is null
     */
    public static int[][] copy(int[][] source) {
        if (source == null)
            return null;
        if (source.length < 1)
            return emptyInts2D;
        int[][] target = new int[source.length][];
        for (int i = 0; i < source.length; i++) {
            final int len = source[i].length;
            target[i] = new int[len];
            System.arraycopy(source[i], 0, target[i], 0, len);
        }
        return target;
    }

    /**
     * Gets a copy of the 2D long array, source, that has the same data but shares no references with source.
     *
     * @param source a 2D long array
     * @return a copy of source, or null if source is null
     */
    public static long[][] copy(long[][] source) {
        if (source == null)
            return null;
        if (source.length < 1)
            return emptyLongs2D;
        long[][] target = new long[source.length][];
        for (int i = 0; i < source.length; i++) {
            final int len = source[i].length;
            target[i] = new long[len];
            System.arraycopy(source[i], 0, target[i], 0, len);
        }
        return target;
    }

    /**
     * Gets a copy of the 2D boolean array, source, that has the same data but shares no references with source.
     *
     * @param source a 2D boolean array
     * @return a copy of source, or null if source is null
     */
    public static boolean[][] copy(boolean[][] source) {
        if (source == null)
            return null;
        if (source.length < 1)
            return emptyBooleans2D;
        boolean[][] target = new boolean[source.length][];
        for (int i = 0; i < source.length; i++) {
            final int len = source[i].length;
            target[i] = new boolean[len];
            System.arraycopy(source[i], 0, target[i], 0, len);
        }
        return target;
    }

    /**
     * Inserts as much of source into target at the given x,y position as target can hold or source can supply.
     * Modifies target in-place and also returns target for chaining.
     * Used primarily to place a smaller array into a different position in a larger array, often freshly allocated.
     *
     * @param source a 2D char array that will be copied and inserted into target
     * @param target a 2D char array that will be modified by receiving as much of source as it can hold
     * @param x      the x position in target to receive the items from the first cell in source
     * @param y      the y position in target to receive the items from the first cell in source
     * @return target, modified, with source inserted into it at the given position
     */
    public static char[][] insert(char[][] source, char[][] target, int x, int y) {
        if (source == null || target == null)
            return target;
        if (source.length < 1 || source[0].length < 1)
            return target;
        for (int i = 0; i < source.length && x + i < target.length; i++) {
            System.arraycopy(source[i], 0, target[x + i], y, Math.min(source[i].length, target[x + i].length - y));
        }
        return target;
    }

    /**
     * Inserts as much of source into target at the given x,y position as target can hold or source can supply.
     * Modifies target in-place and also returns target for chaining.
     * Used primarily to place a smaller array into a different position in a larger array, often freshly allocated.
     *
     * @param source a 2D double array that will be copied and inserted into target
     * @param target a 2D double array that will be modified by receiving as much of source as it can hold
     * @param x      the x position in target to receive the items from the first cell in source
     * @param y      the y position in target to receive the items from the first cell in source
     * @return target, modified, with source inserted into it at the given position
     */
    public static double[][] insert(double[][] source, double[][] target, int x, int y) {
        if (source == null || target == null)
            return target;
        if (source.length < 1 || source[0].length < 1)
            return target;
        for (int i = 0; i < source.length && x + i < target.length; i++) {
            System.arraycopy(source[i], 0, target[x + i], y, Math.min(source[i].length, target[x + i].length - y));
        }
        return target;
    }

    /**
     * Inserts as much of source into target at the given x,y position as target can hold or source can supply.
     * Modifies target in-place and also returns target for chaining.
     * Used primarily to place a smaller array into a different position in a larger array, often freshly allocated.
     *
     * @param source a 2D float array that will be copied and inserted into target
     * @param target a 2D float array that will be modified by receiving as much of source as it can hold
     * @param x      the x position in target to receive the items from the first cell in source
     * @param y      the y position in target to receive the items from the first cell in source
     * @return target, modified, with source inserted into it at the given position
     */
    public static float[][] insert(float[][] source, float[][] target, int x, int y) {
        if (source == null || target == null)
            return target;
        if (source.length < 1 || source[0].length < 1)
            return target;
        for (int i = 0; i < source.length && x + i < target.length; i++) {
            System.arraycopy(source[i], 0, target[x + i], y, Math.min(source[i].length, target[x + i].length - y));
        }
        return target;
    }

    /**
     * Inserts as much of source into target at the given x,y position as target can hold or source can supply.
     * Modifies target in-place and also returns target for chaining.
     * Used primarily to place a smaller array into a different position in a larger array, often freshly allocated.
     *
     * @param source a 2D int array that will be copied and inserted into target
     * @param target a 2D int array that will be modified by receiving as much of source as it can hold
     * @param x      the x position in target to receive the items from the first cell in source
     * @param y      the y position in target to receive the items from the first cell in source
     * @return target, modified, with source inserted into it at the given position
     */
    public static int[][] insert(int[][] source, int[][] target, int x, int y) {
        if (source == null || target == null)
            return target;
        if (source.length < 1 || source[0].length < 1)
            return target;
        for (int i = 0; i < source.length && x + i < target.length; i++) {
            System.arraycopy(source[i], 0, target[x + i], y, Math.min(source[i].length, target[x + i].length - y));
        }
        return target;
    }

    /**
     * Inserts as much of source into target at the given x,y position as target can hold or source can supply.
     * Modifies target in-place and also returns target for chaining.
     * Used primarily to place a smaller array into a different position in a larger array, often freshly allocated.
     *
     * @param source a 2D long array that will be copied and inserted into target
     * @param target a 2D long array that will be modified by receiving as much of source as it can hold
     * @param x      the x position in target to receive the items from the first cell in source
     * @param y      the y position in target to receive the items from the first cell in source
     * @return target, modified, with source inserted into it at the given position
     */
    public static long[][] insert(long[][] source, long[][] target, int x, int y) {
        if (source == null || target == null)
            return target;
        if (source.length < 1 || source[0].length < 1)
            return target;
        for (int i = 0; i < source.length && x + i < target.length; i++) {
            System.arraycopy(source[i], 0, target[x + i], y, Math.min(source[i].length, target[x + i].length - y));
        }
        return target;
    }

    /**
     * Inserts as much of source into target at the given x,y position as target can hold or source can supply.
     * Modifies target in-place and also returns target for chaining.
     * Used primarily to place a smaller array into a different position in a larger array, often freshly allocated.
     *
     * @param source a 2D boolean array that will be copied and inserted into target
     * @param target a 2D boolean array that will be modified by receiving as much of source as it can hold
     * @param x      the x position in target to receive the items from the first cell in source
     * @param y      the y position in target to receive the items from the first cell in source
     * @return target, modified, with source inserted into it at the given position
     */
    public static boolean[][] insert(boolean[][] source, boolean[][] target, int x, int y) {
        if (source == null || target == null)
            return target;
        if (source.length < 1 || source[0].length < 1)
            return target;
        for (int i = 0; i < source.length && x + i < target.length; i++) {
            System.arraycopy(source[i], 0, target[x + i], y, Math.min(source[i].length, target[x + i].length - y));
        }
        return target;
    }

    /**
     * Inserts as much of source into target at the given x,y position as target can hold or source can supply.
     * Modifies target in-place and also returns target for chaining.
     * Used primarily to place a smaller array into a different position in a larger array, often freshly allocated.
     * This does not create copies of any T items; {@code source} will reference the same T items as {@code target}.
     *
     * @param source a 2D generic array that will be copied and inserted into target
     * @param target a 2D generic array that will be modified by receiving as much of source as it can hold
     * @param x      the x position in target to receive the items from the first cell in source
     * @param y      the y position in target to receive the items from the first cell in source
     * @return target, modified, with source inserted into it at the given position
     */
    public static  T[][] insert(T[][] source, T[][] target, int x, int y) {
        if (source == null || target == null)
            return target;
        if (source.length < 1 || source[0].length < 1)
            return target;
        for (int i = 0; i < source.length && x + i < target.length; i++) {
            System.arraycopy(source[i], 0, target[x + i], y, Math.min(source[i].length, target[x + i].length - y));
        }
        return target;
    }

    /**
     * Takes a 2D source array and sets it into a 2D target array, as much as target can hold or source can supply.
     * Modifies target in-place and also returns target for chaining.
     * This is just like {@link #insert(char[][], char[][], int, int)} with x and y both always 0, but does slightly
     * less math per call, and can be clearer as to the intent of the method.
     *
     * @param source a 2D char array that will be copied and inserted into target
     * @param target a 2D char array that will be modified by receiving as much of source as it can hold
     * @return target, modified, with the values from source set as much as possible
     */
    public static char[][] set(char[][] source, char[][] target) {
        if (source == null || target == null || source.length == 0)
            return target;
        final int minWidth = Math.min(source.length, target.length);
        for (int i = 0; i < minWidth; i++) {
            System.arraycopy(source[i], 0, target[i], 0, Math.min(source[i].length, target[i].length));
        }
        return target;
    }

    /**
     * Takes a 2D source array and sets it into a 2D target array, as much as target can hold or source can supply.
     * Modifies target in-place and also returns target for chaining.
     * This is just like {@link #insert(float[][], float[][], int, int)} with x and y both always 0, but does slightly
     * less math per call, and can be clearer as to the intent of the method.
     *
     * @param source a 2D float array that will be copied and inserted into target
     * @param target a 2D float array that will be modified by receiving as much of source as it can hold
     * @return target, modified, with the values from source set as much as possible
     */
    public static float[][] set(float[][] source, float[][] target) {
        if (source == null || target == null || source.length == 0)
            return target;
        final int minWidth = Math.min(source.length, target.length);
        for (int i = 0; i < minWidth; i++) {
            System.arraycopy(source[i], 0, target[i], 0, Math.min(source[i].length, target[i].length));
        }
        return target;
    }

    /**
     * Takes a 2D source array and sets it into a 2D target array, as much as target can hold or source can supply.
     * Modifies target in-place and also returns target for chaining.
     * This is just like {@link #insert(double[][], double[][], int, int)} with x and y both always 0, but does slightly
     * less math per call, and can be clearer as to the intent of the method.
     *
     * @param source a 2D double array that will be copied and inserted into target
     * @param target a 2D double array that will be modified by receiving as much of source as it can hold
     * @return target, modified, with the values from source set as much as possible
     */
    public static double[][] set(double[][] source, double[][] target) {
        if (source == null || target == null || source.length == 0)
            return target;
        final int minWidth = Math.min(source.length, target.length);
        for (int i = 0; i < minWidth; i++) {
            System.arraycopy(source[i], 0, target[i], 0, Math.min(source[i].length, target[i].length));
        }
        return target;
    }

    /**
     * Takes a 2D source array and sets it into a 2D target array, as much as target can hold or source can supply.
     * Modifies target in-place and also returns target for chaining.
     * This is just like {@link #insert(int[][], int[][], int, int)} with x and y both always 0, but does slightly
     * less math per call, and can be clearer as to the intent of the method.
     *
     * @param source a 2D int array that will be copied and inserted into target
     * @param target a 2D int array that will be modified by receiving as much of source as it can hold
     * @return target, modified, with the values from source set as much as possible
     */
    public static int[][] set(int[][] source, int[][] target) {
        if (source == null || target == null || source.length == 0)
            return target;
        final int minWidth = Math.min(source.length, target.length);
        for (int i = 0; i < minWidth; i++) {
            System.arraycopy(source[i], 0, target[i], 0, Math.min(source[i].length, target[i].length));
        }
        return target;
    }

    /**
     * Takes a 2D source array and sets it into a 2D target array, as much as target can hold or source can supply.
     * Modifies target in-place and also returns target for chaining.
     * This is just like {@link #insert(long[][], long[][], int, int)} with x and y both always 0, but does slightly
     * less math per call, and can be clearer as to the intent of the method.
     *
     * @param source a 2D long array that will be copied and inserted into target
     * @param target a 2D long array that will be modified by receiving as much of source as it can hold
     * @return target, modified, with the values from source set as much as possible
     */
    public static long[][] set(long[][] source, long[][] target) {
        if (source == null || target == null || source.length == 0)
            return target;
        final int minWidth = Math.min(source.length, target.length);
        for (int i = 0; i < minWidth; i++) {
            System.arraycopy(source[i], 0, target[i], 0, Math.min(source[i].length, target[i].length));
        }
        return target;
    }

    /**
     * Takes a 2D source array and sets it into a 2D target array, as much as target can hold or source can supply.
     * Modifies target in-place and also returns target for chaining.
     * This is just like {@link #insert(boolean[][], boolean[][], int, int)} with x and y both always 0, but does slightly
     * less math per call, and can be clearer as to the intent of the method.
     *
     * @param source a 2D boolean array that will be copied and inserted into target
     * @param target a 2D boolean array that will be modified by receiving as much of source as it can hold
     * @return target, modified, with the values from source set as much as possible
     */
    public static boolean[][] set(boolean[][] source, boolean[][] target) {
        if (source == null || target == null || source.length == 0)
            return target;
        final int minWidth = Math.min(source.length, target.length);
        for (int i = 0; i < minWidth; i++) {
            System.arraycopy(source[i], 0, target[i], 0, Math.min(source[i].length, target[i].length));
        }
        return target;
    }

    /**
     * Takes a 2D source array and sets it into a 2D target array, as much as target can hold or source can supply.
     * Modifies target in-place and also returns target for chaining.
     * This is just like {@link #insert(Object[][], Object[][], int, int)} with x and y both always 0, but does slightly
     * less math per call, and can be clearer as to the intent of the method.
     * This does not create copies of any T items; {@code source} will reference the same T items as {@code target}.
     *
     * @param source a 2D generic array that will be copied and inserted into target
     * @param target a 2D generic array that will be modified by receiving as much of source as it can hold
     * @return target, modified, with the values from source set as much as possible
     */
    public static  T[][] set(T[][] source, T[][] target) {
        if (source == null || target == null || source.length == 0)
            return target;
        final int minWidth = Math.min(source.length, target.length);
        for (int i = 0; i < minWidth; i++) {
            System.arraycopy(source[i], 0, target[i], 0, Math.min(source[i].length, target[i].length));
        }
        return target;
    }

    /**
     * Takes a 2D source array and produces a usually-smaller section allocated as a new 2D array.
     *
     * @param source a 2D char array that will be copied and inserted into target
     * @param startX the lowest x coordinate to take from source
     * @param startY the lowest y coordinate to take from source
     * @param width the maximum x-size to take from source; may be less if less space was available
     * @param height the maximum y-size to take from source; may be less if less space was available
     * @return a new 2D array that tries to match the specified width and height, getting items from source
     */
    public static char[][] section(char[][] source, int startX, int startY, int width, int height) {
        if (source == null)
            return null;
        if (source.length == 0)
            return new char[0][0];
        final int minWidth = Math.min(source.length - startX, width);
        final int minHeight = Math.min(source[0].length - startY, height);
        if(minWidth == 0 || minHeight == 0)
            return new char[0][0];
        char[][] result = new char[minWidth][minHeight];
        for (int i = 0, x = startX; i < minWidth; i++, x++) {
            System.arraycopy(source[x], startY, result[i], 0, minHeight);
        }
        return result;
    }

    /**
     * Takes a 2D source array and produces a usually-smaller section allocated as a new 2D array.
     *
     * @param source a 2D byte array that will be copied and inserted into target
     * @param startX the lowest x coordinate to take from source
     * @param startY the lowest y coordinate to take from source
     * @param width the maximum x-size to take from source; may be less if less space was available
     * @param height the maximum y-size to take from source; may be less if less space was available
     * @return a new 2D array that tries to match the specified width and height, getting items from source
     */
    public static byte[][] section(byte[][] source, int startX, int startY, int width, int height) {
        if (source == null)
            return null;
        if (source.length == 0)
            return new byte[0][0];
        final int minWidth = Math.min(source.length - startX, width);
        final int minHeight = Math.min(source[0].length - startY, height);
        if(minWidth == 0 || minHeight == 0)
            return new byte[0][0];
        byte[][] result = new byte[minWidth][minHeight];
        for (int i = 0, x = startX; i < minWidth; i++, x++) {
            System.arraycopy(source[x], startY, result[i], 0, minHeight);
        }
        return result;
    }

    /**
     * Takes a 2D source array and produces a usually-smaller section allocated as a new 2D array.
     *
     * @param source a 2D short array that will be copied and inserted into target
     * @param startX the lowest x coordinate to take from source
     * @param startY the lowest y coordinate to take from source
     * @param width the maximum x-size to take from source; may be less if less space was available
     * @param height the maximum y-size to take from source; may be less if less space was available
     * @return a new 2D array that tries to match the specified width and height, getting items from source
     */
    public static short[][] section(short[][] source, int startX, int startY, int width, int height) {
        if (source == null)
            return null;
        if (source.length == 0)
            return new short[0][0];
        final int minWidth = Math.min(source.length - startX, width);
        final int minHeight = Math.min(source[0].length - startY, height);
        if(minWidth == 0 || minHeight == 0)
            return new short[0][0];
        short[][] result = new short[minWidth][minHeight];
        for (int i = 0, x = startX; i < minWidth; i++, x++) {
            System.arraycopy(source[x], startY, result[i], 0, minHeight);
        }
        return result;
    }

    /**
     * Takes a 2D source array and produces a usually-smaller section allocated as a new 2D array.
     *
     * @param source a 2D int array that will be copied and inserted into target
     * @param startX the lowest x coordinate to take from source
     * @param startY the lowest y coordinate to take from source
     * @param width the maximum x-size to take from source; may be less if less space was available
     * @param height the maximum y-size to take from source; may be less if less space was available
     * @return a new 2D array that tries to match the specified width and height, getting items from source
     */
    public static int[][] section(int[][] source, int startX, int startY, int width, int height) {
        if (source == null)
            return null;
        if (source.length == 0)
            return new int[0][0];
        final int minWidth = Math.min(source.length - startX, width);
        final int minHeight = Math.min(source[0].length - startY, height);
        if(minWidth == 0 || minHeight == 0)
            return new int[0][0];
        int[][] result = new int[minWidth][minHeight];
        for (int i = 0, x = startX; i < minWidth; i++, x++) {
            System.arraycopy(source[x], startY, result[i], 0, minHeight);
        }
        return result;
    }

    /**
     * Takes a 2D source array and produces a usually-smaller section allocated as a new 2D array.
     *
     * @param source a 2D long array that will be copied and inserted into target
     * @param startX the lowest x coordinate to take from source
     * @param startY the lowest y coordinate to take from source
     * @param width the maximum x-size to take from source; may be less if less space was available
     * @param height the maximum y-size to take from source; may be less if less space was available
     * @return a new 2D array that tries to match the specified width and height, getting items from source
     */
    public static long[][] section(long[][] source, int startX, int startY, int width, int height) {
        if (source == null)
            return null;
        if (source.length == 0)
            return new long[0][0];
        final int minWidth = Math.min(source.length - startX, width);
        final int minHeight = Math.min(source[0].length - startY, height);
        if(minWidth == 0 || minHeight == 0)
            return new long[0][0];
        long[][] result = new long[minWidth][minHeight];
        for (int i = 0, x = startX; i < minWidth; i++, x++) {
            System.arraycopy(source[x], startY, result[i], 0, minHeight);
        }
        return result;
    }

    /**
     * Takes a 2D source array and produces a usually-smaller section allocated as a new 2D array.
     *
     * @param source a 2D boolean array that will be copied and inserted into target
     * @param startX the lowest x coordinate to take from source
     * @param startY the lowest y coordinate to take from source
     * @param width the maximum x-size to take from source; may be less if less space was available
     * @param height the maximum y-size to take from source; may be less if less space was available
     * @return a new 2D array that tries to match the specified width and height, getting items from source
     */
    public static boolean[][] section(boolean[][] source, int startX, int startY, int width, int height) {
        if (source == null)
            return null;
        if (source.length == 0)
            return new boolean[0][0];
        final int minWidth = Math.min(source.length - startX, width);
        final int minHeight = Math.min(source[0].length - startY, height);
        if(minWidth == 0 || minHeight == 0)
            return new boolean[0][0];
        boolean[][] result = new boolean[minWidth][minHeight];
        for (int i = 0, x = startX; i < minWidth; i++, x++) {
            System.arraycopy(source[x], startY, result[i], 0, minHeight);
        }
        return result;
    }

    /**
     * Takes a 2D source array and produces a usually-smaller section allocated as a new 2D array.
     *
     * @param source a 2D float array that will be copied and inserted into target
     * @param startX the lowest x coordinate to take from source
     * @param startY the lowest y coordinate to take from source
     * @param width the maximum x-size to take from source; may be less if less space was available
     * @param height the maximum y-size to take from source; may be less if less space was available
     * @return a new 2D array that tries to match the specified width and height, getting items from source
     */
    public static float[][] section(float[][] source, int startX, int startY, int width, int height) {
        if (source == null)
            return null;
        if (source.length == 0)
            return new float[0][0];
        final int minWidth = Math.min(source.length - startX, width);
        final int minHeight = Math.min(source[0].length - startY, height);
        if(minWidth == 0 || minHeight == 0)
            return new float[0][0];
        float[][] result = new float[minWidth][minHeight];
        for (int i = 0, x = startX; i < minWidth; i++, x++) {
            System.arraycopy(source[x], startY, result[i], 0, minHeight);
        }
        return result;
    }

    /**
     * Takes a 2D source array and produces a usually-smaller section allocated as a new 2D array.
     *
     * @param source a 2D double array that will be copied and inserted into target
     * @param startX the lowest x coordinate to take from source
     * @param startY the lowest y coordinate to take from source
     * @param width the maximum x-size to take from source; may be less if less space was available
     * @param height the maximum y-size to take from source; may be less if less space was available
     * @return a new 2D array that tries to match the specified width and height, getting items from source
     */
    public static double[][] section(double[][] source, int startX, int startY, int width, int height) {
        if (source == null)
            return null;
        if (source.length == 0)
            return new double[0][0];
        final int minWidth = Math.min(source.length - startX, width);
        final int minHeight = Math.min(source[0].length - startY, height);
        if(minWidth == 0 || minHeight == 0)
            return new double[0][0];
        double[][] result = new double[minWidth][minHeight];
        for (int i = 0, x = startX; i < minWidth; i++, x++) {
            System.arraycopy(source[x], startY, result[i], 0, minHeight);
        }
        return result;
    }

    /**
     * Takes a 2D source array and produces a usually-smaller section allocated as a new 2D array. This does only a
     * shallow copy of the items in source; the exact references will still be shared between source and the result.
     *
     * @param source a 2D T array that will be copied and inserted into target
     * @param startX the lowest x coordinate to take from source
     * @param startY the lowest y coordinate to take from source
     * @param width the maximum x-size to take from source; may be less if less space was available
     * @param height the maximum y-size to take from source; may be less if less space was available
     * @return a new 2D array that tries to match the specified width and height, getting items from source
     */
    public static  T[][] section(T[][] source, int startX, int startY, int width, int height) {
        if (source == null)
            return null;
        if (source.length == 0)
            return Arrays.copyOf(source, 0);
        final int minWidth = Math.min(source.length - startX, width);
        final int minHeight = Math.min(source[0].length - startY, height);
        if(minWidth == 0 || minHeight == 0)
            return Arrays.copyOf(source, 0);
        T[][] result = Arrays.copyOf(source, minWidth);
        for (int i = 0, x = startX; i < minWidth; i++, x++) {
            result[i] = Arrays.copyOfRange(source[x], startY, startY + minHeight);
        }
        return result;
    }

    /**
     * Iterates (potentially more than once) through {@code contents} and assigns the values it gets from there into
     * {@code modifying}. This iterated in column-major order, so all y values in the first column of contents will be
     * assigned before the next column is entered. If every item in contents is used, this will keep filling, looping
     * back to start with the beginning of contents.
     * @param modifying a 2D char array that will be modified in-place; does not have to be rectangular
     * @param contents a char array or varargs that will be used to fill modifying
     * @return modifying, after it has been filled up
     */
    public static char[][] sequentialFill(char[][] modifying, char... contents) {
        if(modifying == null || contents == null)
            return modifying;
        final int limit = contents.length;
        if(limit == 0)
            return modifying;
        int ci = 0;
        for (int x = 0; x < modifying.length; x++) {
            for (int y = 0; y < modifying[x].length; y++) {
                modifying[x][y] = contents[ci++];
                if(ci == limit) ci = 0;
            }
        }
        return modifying;
    }

    /**
     * Iterates (potentially more than once) through {@code contents} and assigns the values it gets from there into
     * {@code modifying}. This iterated in column-major order, so all y values in the first column of contents will be
     * assigned before the next column is entered. If every item in contents is used, this will keep filling, looping
     * back to start with the beginning of contents.
     * @param modifying a 2D byte array that will be modified in-place; does not have to be rectangular
     * @param contents a byte array or varargs that will be used to fill modifying
     * @return modifying, after it has been filled up
     */
    public static byte[][] sequentialFill(byte[][] modifying, byte... contents) {
        if(modifying == null || contents == null)
            return modifying;
        final int limit = contents.length;
        if(limit == 0)
            return modifying;
        int ci = 0;
        for (int x = 0; x < modifying.length; x++) {
            for (int y = 0; y < modifying[x].length; y++) {
                modifying[x][y] = contents[ci++];
                if(ci == limit) ci = 0;
            }
        }
        return modifying;
    }

    /**
     * Iterates (potentially more than once) through {@code contents} and assigns the values it gets from there into
     * {@code modifying}. This iterated in column-major order, so all y values in the first column of contents will be
     * assigned before the next column is entered. If every item in contents is used, this will keep filling, looping
     * back to start with the beginning of contents.
     * @param modifying a 2D short array that will be modified in-place; does not have to be rectangular
     * @param contents a short array or varargs that will be used to fill modifying
     * @return modifying, after it has been filled up
     */
    public static short[][] sequentialFill(short[][] modifying, short... contents) {
        if(modifying == null || contents == null)
            return modifying;
        final int limit = contents.length;
        if(limit == 0)
            return modifying;
        int ci = 0;
        for (int x = 0; x < modifying.length; x++) {
            for (int y = 0; y < modifying[x].length; y++) {
                modifying[x][y] = contents[ci++];
                if(ci == limit) ci = 0;
            }
        }
        return modifying;
    }

    /**
     * Iterates (potentially more than once) through {@code contents} and assigns the values it gets from there into
     * {@code modifying}. This iterated in column-major order, so all y values in the first column of contents will be
     * assigned before the next column is entered. If every item in contents is used, this will keep filling, looping
     * back to start with the beginning of contents.
     * @param modifying a 2D int array that will be modified in-place; does not have to be rectangular
     * @param contents an int array or varargs that will be used to fill modifying
     * @return modifying, after it has been filled up
     */
    public static int[][] sequentialFill(int[][] modifying, int... contents) {
        if(modifying == null || contents == null)
            return modifying;
        final int limit = contents.length;
        if(limit == 0)
            return modifying;
        int ci = 0;
        for (int x = 0; x < modifying.length; x++) {
            for (int y = 0; y < modifying[x].length; y++) {
                modifying[x][y] = contents[ci++];
                if(ci == limit) ci = 0;
            }
        }
        return modifying;
    }

    /**
     * Iterates (potentially more than once) through {@code contents} and assigns the values it gets from there into
     * {@code modifying}. This iterated in column-major order, so all y values in the first column of contents will be
     * assigned before the next column is entered. If every item in contents is used, this will keep filling, looping
     * back to start with the beginning of contents.
     * @param modifying a 2D long array that will be modified in-place; does not have to be rectangular
     * @param contents a long array or varargs that will be used to fill modifying
     * @return modifying, after it has been filled up
     */
    public static long[][] sequentialFill(long[][] modifying, long... contents) {
        if(modifying == null || contents == null)
            return modifying;
        final int limit = contents.length;
        if(limit == 0)
            return modifying;
        int ci = 0;
        for (int x = 0; x < modifying.length; x++) {
            for (int y = 0; y < modifying[x].length; y++) {
                modifying[x][y] = contents[ci++];
                if(ci == limit) ci = 0;
            }
        }
        return modifying;
    }

    /**
     * Iterates (potentially more than once) through {@code contents} and assigns the values it gets from there into
     * {@code modifying}. This iterated in column-major order, so all y values in the first column of contents will be
     * assigned before the next column is entered. If every item in contents is used, this will keep filling, looping
     * back to start with the beginning of contents.
     * @param modifying a 2D boolean array that will be modified in-place; does not have to be rectangular
     * @param contents a boolean array or varargs that will be used to fill modifying
     * @return modifying, after it has been filled up
     */
    public static boolean[][] sequentialFill(boolean[][] modifying, boolean... contents) {
        if(modifying == null || contents == null)
            return modifying;
        final int limit = contents.length;
        if(limit == 0)
            return modifying;
        int ci = 0;
        for (int x = 0; x < modifying.length; x++) {
            for (int y = 0; y < modifying[x].length; y++) {
                modifying[x][y] = contents[ci++];
                if(ci == limit) ci = 0;
            }
        }
        return modifying;
    }

    /**
     * Iterates (potentially more than once) through {@code contents} and assigns the values it gets from there into
     * {@code modifying}. This iterated in column-major order, so all y values in the first column of contents will be
     * assigned before the next column is entered. If every item in contents is used, this will keep filling, looping
     * back to start with the beginning of contents.
     * @param modifying a 2D float array that will be modified in-place; does not have to be rectangular
     * @param contents a float array or varargs that will be used to fill modifying
     * @return modifying, after it has been filled up
     */
    public static float[][] sequentialFill(float[][] modifying, float... contents) {
        if(modifying == null || contents == null)
            return modifying;
        final int limit = contents.length;
        if(limit == 0)
            return modifying;
        int ci = 0;
        for (int x = 0; x < modifying.length; x++) {
            for (int y = 0; y < modifying[x].length; y++) {
                modifying[x][y] = contents[ci++];
                if(ci == limit) ci = 0;
            }
        }
        return modifying;
    }

    /**
     * Iterates (potentially more than once) through {@code contents} and assigns the values it gets from there into
     * {@code modifying}. This iterated in column-major order, so all y values in the first column of contents will be
     * assigned before the next column is entered. If every item in contents is used, this will keep filling, looping
     * back to start with the beginning of contents.
     * @param modifying a 2D double array that will be modified in-place; does not have to be rectangular
     * @param contents a double array or varargs that will be used to fill modifying
     * @return modifying, after it has been filled up
     */
    public static double[][] sequentialFill(double[][] modifying, double... contents) {
        if(modifying == null || contents == null)
            return modifying;
        final int limit = contents.length;
        if(limit == 0)
            return modifying;
        int ci = 0;
        for (int x = 0; x < modifying.length; x++) {
            for (int y = 0; y < modifying[x].length; y++) {
                modifying[x][y] = contents[ci++];
                if(ci == limit) ci = 0;
            }
        }
        return modifying;
    }

    /**
     * Iterates (potentially more than once) through {@code contents} and assigns the values it gets from there into
     * {@code modifying}. This iterated in column-major order, so all y values in the first column of contents will be
     * assigned before the next column is entered. If every item in contents is used, this will keep filling, looping
     * back to start with the beginning of contents. This does only a shallow copy of the items in contents; the exact
     * references will still be shared between contents and modifying.
     * @param modifying a 2D T array that will be modified in-place; does not have to be rectangular
     * @param contents a T array or varargs that will be used to fill modifying
     * @return modifying, after it has been filled up
     */
    @SafeVarargs
    public static  T[][] sequentialFill(T[][] modifying, T... contents) {
        if(modifying == null || contents == null)
            return modifying;
        final int limit = contents.length;
        if(limit == 0)
            return modifying;
        int ci = 0;
        for (int x = 0; x < modifying.length; x++) {
            for (int y = 0; y < modifying[x].length; y++) {
                modifying[x][y] = contents[ci++];
                if(ci == limit) ci = 0;
            }
        }
        return modifying;
    }

    /**
     * Creates a 2D array of the given width and height, filled with entirely with the value contents.
     * You may want to use {@link #fill(char[][], char)} to modify an existing 2D array instead.
     *
     * @param contents the value to fill the array with
     * @param width    the desired width
     * @param height   the desired height
     * @return a freshly allocated 2D array of the requested dimensions, filled entirely with contents
     */
    public static char[][] fill(char contents, int width, int height) {
        char[][] next = new char[width][height];
        for (int x = 0; x < width; x++) {
            Arrays.fill(next[x], contents);
        }
        return next;
    }

    /**
     * Creates a 2D array of the given width and height, filled with entirely with the value contents.
     * You may want to use {@link #fill(float[][], float)} to modify an existing 2D array instead.
     *
     * @param contents the value to fill the array with
     * @param width    the desired width
     * @param height   the desired height
     * @return a freshly allocated 2D array of the requested dimensions, filled entirely with contents
     */
    public static float[][] fill(float contents, int width, int height) {
        float[][] next = new float[width][height];
        for (int x = 0; x < width; x++) {
            Arrays.fill(next[x], contents);
        }
        return next;
    }

    /**
     * Creates a 2D array of the given width and height, filled with entirely with the value contents.
     * You may want to use {@link #fill(double[][], double)} to modify an existing 2D array instead.
     *
     * @param contents the value to fill the array with
     * @param width    the desired width
     * @param height   the desired height
     * @return a freshly allocated 2D array of the requested dimensions, filled entirely with contents
     */
    public static double[][] fill(double contents, int width, int height) {
        double[][] next = new double[width][height];
        for (int x = 0; x < width; x++) {
            Arrays.fill(next[x], contents);
        }
        return next;
    }

    /**
     * Creates a 2D array of the given width and height, filled with entirely with the value contents.
     * You may want to use {@link #fill(int[][], int)} to modify an existing 2D array instead.
     *
     * @param contents the value to fill the array with
     * @param width    the desired width
     * @param height   the desired height
     * @return a freshly allocated 2D array of the requested dimensions, filled entirely with contents
     */
    public static int[][] fill(int contents, int width, int height) {
        int[][] next = new int[width][height];
        for (int x = 0; x < width; x++) {
            Arrays.fill(next[x], contents);
        }
        return next;
    }

    /**
     * Creates a 2D array of the given width and height, filled with entirely with the value contents.
     * You may want to use {@link #fill(long[][], long)} to modify an existing 2D array instead.
     *
     * @param contents the value to fill the array with
     * @param width    the desired width
     * @param height   the desired height
     * @return a freshly allocated 2D array of the requested dimensions, filled entirely with contents
     */
    public static long[][] fill(long contents, int width, int height) {
        long[][] next = new long[width][height];
        for (int x = 0; x < width; x++) {
            Arrays.fill(next[x], contents);
        }
        return next;
    }

    /**
     * Creates a 2D array of the given width and height, filled with entirely with the value contents.
     * You may want to use {@link #fill(byte[][], byte)} to modify an existing 2D array instead.
     *
     * @param contents the value to fill the array with
     * @param width    the desired width
     * @param height   the desired height
     * @return a freshly allocated 2D array of the requested dimensions, filled entirely with contents
     */
    public static byte[][] fill(byte contents, int width, int height) {
        byte[][] next = new byte[width][height];
        for (int x = 0; x < width; x++) {
            Arrays.fill(next[x], contents);
        }
        return next;
    }

    /**
     * Creates a 2D array of the given width and height, filled with entirely with the value contents.
     * You may want to use {@link #fill(boolean[][], boolean)} to modify an existing 2D array instead.
     *
     * @param contents the value to fill the array with
     * @param width    the desired width
     * @param height   the desired height
     * @return a freshly allocated 2D array of the requested dimensions, filled entirely with contents
     */
    public static boolean[][] fill(boolean contents, int width, int height) {
        boolean[][] next = new boolean[width][height];
        if (contents) {
            for (int x = 0; x < width; x++) {
                Arrays.fill(next[x], true);
            }
        }
        return next;
    }

    /**
     * Fills {@code array2d} with {@code value}.
     * Not to be confused with {@link #fill(boolean, int, int)}, which makes a new 2D array.
     *
     * @param array2d a 2D array that will be modified in-place; no sub-arrays can be null
     * @param value   the value to fill all of array2D with
     */
    public static boolean[][] fill(boolean[][] array2d, boolean value) {
        final int width = array2d.length;
        for (int i = 0; i < width; i++) {
            Arrays.fill(array2d[i], value);
        }
        return array2d;
    }

    /**
     * Fills {@code array2d} with {@code value}.
     * Not to be confused with {@link #fill(char, int, int)}, which makes a new 2D array.
     *
     * @param array2d a 2D array that will be modified in-place; no sub-arrays can be null
     * @param value   the value to fill all of array2D with
     */
    public static char[][] fill(char[][] array2d, char value) {
        final int width = array2d.length;
        for (int i = 0; i < width; i++) {
            Arrays.fill(array2d[i], value);
        }
        return array2d;
    }

    /**
     * Fills {@code array2d} with {@code value}.
     * Not to be confused with {@link #fill(float, int, int)}, which makes a new 2D array.
     *
     * @param array2d a 2D array that will be modified in-place; no sub-arrays can be null
     * @param value   the value to fill all of array2D with
     */
    public static float[][] fill(float[][] array2d, float value) {
        final int width = array2d.length;
        for (int i = 0; i < width; i++) {
            Arrays.fill(array2d[i], value);
        }
        return array2d;
    }


    /**
     * Fills {@code array2d} with {@code value}.
     * Not to be confused with {@link #fill(double, int, int)}, which makes a new 2D array.
     *
     * @param array2d a 2D array that will be modified in-place; no sub-arrays can be null
     * @param value   the value to fill all of array2D with
     */
    public static double[][] fill(double[][] array2d, double value) {
        final int width = array2d.length;
        for (int i = 0; i < width; i++) {
            Arrays.fill(array2d[i], value);
        }
        return array2d;
    }

    /**
     * Fills {@code array2d} with {@code value}.
     * Not to be confused with {@link #fill(int, int, int)}, which makes a new 2D array.
     *
     * @param array2d a 2D array that will be modified in-place; no sub-arrays can be null
     * @param value   the value to fill all of array2D with
     */
    public static int[][] fill(int[][] array2d, int value) {
        final int width = array2d.length;
        for (int i = 0; i < width; i++) {
            Arrays.fill(array2d[i], value);
        }
        return array2d;
    }

    /**
     * Fills {@code array2d} with {@code value}.
     * Not to be confused with {@link #fill(long, int, int)}, which makes a new 2D array.
     *
     * @param array2d a 2D array that will be modified in-place; no sub-arrays can be null
     * @param value   the value to fill all of array2D with
     */
    public static long[][] fill(long[][] array2d, long value) {
        final int width = array2d.length;
        for (int i = 0; i < width; i++) {
            Arrays.fill(array2d[i], value);
        }
        return array2d;
    }

    /**
     * Fills {@code array2d} with {@code value}.
     * Not to be confused with {@link #fill(byte, int, int)}, which makes a new 2D array.
     *
     * @param array2d a 2D array that will be modified in-place; no sub-arrays can be null
     * @param value   the value to fill all of array2D with
     */
    public static byte[][] fill(byte[][] array2d, byte value) {
        final int width = array2d.length;
        for (int i = 0; i < width; i++) {
            Arrays.fill(array2d[i], value);
        }
        return array2d;
    }

    /**
     * Fills {@code array2d} with identical references to {@code value} (not copies).
     * Note that there is no fill() method that creates a new 2D array of a generic type.
     *
     * @param array2d a 2D array that will be modified in-place; no sub-arrays can be null
     * @param value   the value to fill all of array2D with
     */
    public static  T[][] fill(T[][] array2d, T value) {
        final int width = array2d.length;
        for (int i = 0; i < width; i++) {
            Arrays.fill(array2d[i], value);
        }
        return array2d;
    }

    /**
     * Fills {@code array3d} with {@code value}.
     * Not to be confused with {@link #fill(boolean[][], boolean)}, which fills a 2D array instead of a 3D one, or with
     * {@link #fill(boolean, int, int)}, which makes a new 2D array.
     * This is named differently to avoid ambiguity between a 1D array of {@code boolean[][]}, which this can take, and a
     * 2D array of {@code boolean[]}, which could be given to {@link #fill(Object[][], Object)}, but could also be given
     * to this.
     *
     * @param array3d a 3D array that will be modified in-place; no sub-arrays can be null
     * @param value   the value to fill all of array3d with
     */
    public static boolean[][][] fill(boolean[][][] array3d, boolean value) {
        final int depth = array3d.length;
        final int width = depth == 0 ? 0 : array3d[0].length;
        final int height = width == 0 ? 0 : array3d[0][0].length;
        if (depth > 0 && width > 0) {
            for (int i = 0; i < width; i++) {
                for (int j = 0; j < height; j++) {
                    Arrays.fill(array3d[i][j], value);
                }
            }
        }
        return array3d;
    }
    
    /**
     * Fills {@code array3d} with {@code value}.
     * Not to be confused with {@link #fill(char[][], char)}, which fills a 2D array instead of a 3D one, or with
     * {@link #fill(char, int, int)}, which makes a new 2D array.
     * This is named differently to avoid ambiguity between a 1D array of {@code char[][]}, which this can take, and a
     * 2D array of {@code char[]}, which could be given to {@link #fill(Object[][], Object)}, but could also be given
     * to this.
     *
     * @param array3d a 3D array that will be modified in-place; no sub-arrays can be null
     * @param value   the value to fill all of array3d with
     */
    public static char[][][] fill(char[][][] array3d, char value) {
        final int depth = array3d.length;
        final int width = depth == 0 ? 0 : array3d[0].length;
        final int height = width == 0 ? 0 : array3d[0][0].length;
        if (depth > 0 && width > 0) {
            for (int i = 0; i < width; i++) {
                for (int j = 0; j < height; j++) {
                    Arrays.fill(array3d[i][j], value);
                }
            }
        }
        return array3d;
    }
    
    /**
     * Fills {@code array3d} with {@code value}.
     * Not to be confused with {@link #fill(float[][], float)}, which fills a 2D array instead of a 3D one, or with
     * {@link #fill(float, int, int)}, which makes a new 2D array.
     * This is named differently to avoid ambiguity between a 1D array of {@code float[][]}, which this can take, and a
     * 2D array of {@code float[]}, which could be given to {@link #fill(Object[][], Object)}, but could also be given
     * to this.
     *
     * @param array3d a 3D array that will be modified in-place; no sub-arrays can be null
     * @param value   the value to fill all of array3d with
     */
    public static float[][][] fill(float[][][] array3d, float value) {
        final int depth = array3d.length;
        final int width = depth == 0 ? 0 : array3d[0].length;
        final int height = width == 0 ? 0 : array3d[0][0].length;
        if (depth > 0 && width > 0) {
            for (int i = 0; i < width; i++) {
                for (int j = 0; j < height; j++) {
                    Arrays.fill(array3d[i][j], value);
                }
            }
        }
        return array3d;
    }

    /**
     * Fills {@code array3d} with {@code value}.
     * Not to be confused with {@link #fill(double[][], double)}, which fills a 2D array instead of a 3D one, or with
     * {@link #fill(double, int, int)}, which makes a new 2D array.
     * This is named differently to avoid ambiguity between a 1D array of {@code double[][]}, which this can take, and a
     * 2D array of {@code double[]}, which could be given to {@link #fill(Object[][], Object)}, but could also be given
     * to this.
     *
     * @param array3d a 3D array that will be modified in-place; no sub-arrays can be null
     * @param value   the value to fill all of array3d with
     */
    public static double[][][] fill(double[][][] array3d, double value) {
        final int depth = array3d.length;
        final int width = depth == 0 ? 0 : array3d[0].length;
        final int height = width == 0 ? 0 : array3d[0][0].length;
        if (depth > 0 && width > 0) {
            for (int i = 0; i < width; i++) {
                for (int j = 0; j < height; j++) {
                    Arrays.fill(array3d[i][j], value);
                }
            }
        }
        return array3d;
    }

    /**
     * Fills {@code array3d} with {@code value}.
     * Not to be confused with {@link #fill(int[][], int)}, which fills a 2D array instead of a 3D one, or with
     * {@link #fill(int, int, int)}, which makes a new 2D array.
     * This is named differently to avoid ambiguity between a 1D array of {@code int[][]}, which this can take, and a
     * 2D array of {@code int[]}, which could be given to {@link #fill(Object[][], Object)}, but could also be given
     * to this.
     *
     * @param array3d a 3D array that will be modified in-place; no sub-arrays can be null
     * @param value   the value to fill all of array3d with
     */
    public static int[][][] fill(int[][][] array3d, int value) {
        final int depth = array3d.length;
        final int width = depth == 0 ? 0 : array3d[0].length;
        final int height = width == 0 ? 0 : array3d[0][0].length;
        if (depth > 0 && width > 0) {
            for (int i = 0; i < width; i++) {
                for (int j = 0; j < height; j++) {
                    Arrays.fill(array3d[i][j], value);
                }
            }
        }
        return array3d;
    }

    /**
     * Fills {@code array3d} with {@code value}.
     * Not to be confused with {@link #fill(long[][], long)}, which fills a 2D array instead of a 3D one, or with
     * {@link #fill(long, int, int)}, which makes a new 2D array.
     * This is named differently to avoid ambiguity between a 1D array of {@code long[][]}, which this can take, and a
     * 2D array of {@code long[]}, which could be given to {@link #fill(Object[][], Object)}, but could also be given
     * to this.
     *
     * @param array3d a 3D array that will be modified in-place; no sub-arrays can be null
     * @param value   the value to fill all of array3d with
     */
    public static long[][][] fill(long[][][] array3d, long value) {
        final int depth = array3d.length;
        final int width = depth == 0 ? 0 : array3d[0].length;
        final int height = width == 0 ? 0 : array3d[0][0].length;
        if (depth > 0 && width > 0) {
            for (int i = 0; i < width; i++) {
                for (int j = 0; j < height; j++) {
                    Arrays.fill(array3d[i][j], value);
                }
            }
        }
        return array3d;
    }

    /**
     * Fills {@code array3d} with {@code value}.
     * Not to be confused with {@link #fill(byte[][], byte)}, which fills a 2D array instead of a 3D one, or with
     * {@link #fill(byte, int, int)}, which makes a new 2D array.
     * This is named differently to avoid ambiguity between a 1D array of {@code byte[][]}, which this can take, and a
     * 2D array of {@code byte[]}, which could be given to {@link #fill(Object[][], Object)}, but could also be given
     * to this.
     *
     * @param array3d a 3D array that will be modified in-place; no sub-arrays can be null
     * @param value   the value to fill all of array3d with
     */
    public static byte[][][] fill(byte[][][] array3d, byte value) {
        final int depth = array3d.length;
        final int width = depth == 0 ? 0 : array3d[0].length;
        final int height = width == 0 ? 0 : array3d[0][0].length;
        if (depth > 0 && width > 0) {
            for (int i = 0; i < width; i++) {
                for (int j = 0; j < height; j++) {
                    Arrays.fill(array3d[i][j], value);
                }
            }
        }
        return array3d;
    }

    /**
     * Fills {@code array3d} with identical references to {@code value} (not copies).
     * Not to be confused with {@link #fill(Object[][], Object)}, which fills a 2D array instead of a 3D one.
     * This is named differently to avoid ambiguity between a 1D array of {@code T[][]}, which this can take, and a
     * 2D array of {@code T[]}, which could be given to {@link #fill(Object[][], Object)}, but could also be given
     * to this.
     *
     * @param array3d a 3D array that will be modified in-place; no sub-arrays can be null
     * @param value   the value to fill all of array3d with
     */
    public static  T[][][] fill(T[][][] array3d, T value) {
        final int depth = array3d.length;
        final int width = depth == 0 ? 0 : array3d[0].length;
        final int height = width == 0 ? 0 : array3d[0][0].length;
        if (depth > 0 && width > 0) {
            for (int i = 0; i < width; i++) {
                for (int j = 0; j < height; j++) {
                    Arrays.fill(array3d[i][j], value);
                }
            }
        }
        return array3d;
    }

    /**
     * Fills a subsection of {@code array2d} with {@code value}, with the section defined by start/end x/y.
     * Not to be confused with {@link #fill(boolean, int, int)}, which makes a new 2D array.
     *
     * @param array2d a 2D array that will be modified in-place
     * @param value   the value to fill all of array2D with
     * @param startX  the first x position to fill (inclusive)
     * @param startY  the first y position to fill (inclusive)
     * @param endX    the last x position to fill (inclusive)
     * @param endY    the last y position to fill (inclusive)
     */
    public static boolean[][] fill(boolean[][] array2d, boolean value, int startX, int startY, int endX, int endY) {
        final int width = array2d.length;
        final int height = width == 0 ? 0 : array2d[0].length;
        for (int x = startX; x <= endX && x < width; x++) {
            for (int y = startY; y <= endY && y < height; y++) {
                array2d[x][y] = value;
            }
        }
        return array2d;
    }

    /**
     * Fills a subsection of {@code array2d} with {@code value}, with the section defined by start/end x/y.
     * Not to be confused with {@link #fill(char, int, int)}, which makes a new 2D array.
     *
     * @param array2d a 2D array that will be modified in-place
     * @param value   the value to fill all of array2D with
     * @param startX  the first x position to fill (inclusive)
     * @param startY  the first y position to fill (inclusive)
     * @param endX    the last x position to fill (inclusive)
     * @param endY    the last y position to fill (inclusive)
     */
    public static char[][] fill(char[][] array2d, char value, int startX, int startY, int endX, int endY) {
        final int width = array2d.length;
        final int height = width == 0 ? 0 : array2d[0].length;
        for (int x = startX; x <= endX && x < width; x++) {
            for (int y = startY; y <= endY && y < height; y++) {
                array2d[x][y] = value;
            }
        }
        return array2d;
    }

    /**
     * Fills a subsection of {@code array2d} with {@code value}, with the section defined by start/end x/y.
     * Not to be confused with {@link #fill(float, int, int)}, which makes a new 2D array.
     *
     * @param array2d a 2D array that will be modified in-place
     * @param value   the value to fill all of array2D with
     * @param startX  the first x position to fill (inclusive)
     * @param startY  the first y position to fill (inclusive)
     * @param endX    the last x position to fill (inclusive)
     * @param endY    the last y position to fill (inclusive)
     */
    public static float[][] fill(float[][] array2d, float value, int startX, int startY, int endX, int endY) {
        final int width = array2d.length;
        final int height = width == 0 ? 0 : array2d[0].length;
        for (int x = startX; x <= endX && x < width; x++) {
            for (int y = startY; y <= endY && y < height; y++) {
                array2d[x][y] = value;
            }
        }
        return array2d;
    }

    /**
     * Fills a subsection of {@code array2d} with {@code value}, with the section defined by start/end x/y.
     * Not to be confused with {@link #fill(double, int, int)}, which makes a new 2D array.
     *
     * @param array2d a 2D array that will be modified in-place
     * @param value   the value to fill all of array2D with
     * @param startX  the first x position to fill (inclusive)
     * @param startY  the first y position to fill (inclusive)
     * @param endX    the last x position to fill (inclusive)
     * @param endY    the last y position to fill (inclusive)
     */
    public static double[][] fill(double[][] array2d, double value, int startX, int startY, int endX, int endY) {
        final int width = array2d.length;
        final int height = width == 0 ? 0 : array2d[0].length;
        for (int x = startX; x <= endX && x < width; x++) {
            for (int y = startY; y <= endY && y < height; y++) {
                array2d[x][y] = value;
            }
        }
        return array2d;
    }

    /**
     * Fills a subsection of {@code array2d} with {@code value}, with the section defined by start/end x/y.
     * Not to be confused with {@link #fill(int, int, int)}, which makes a new 2D array.
     *
     * @param array2d a 2D array that will be modified in-place
     * @param value   the value to fill all of array2D with
     * @param startX  the first x position to fill (inclusive)
     * @param startY  the first y position to fill (inclusive)
     * @param endX    the last x position to fill (inclusive)
     * @param endY    the last y position to fill (inclusive)
     */
    public static int[][] fill(int[][] array2d, int value, int startX, int startY, int endX, int endY) {
        final int width = array2d.length;
        final int height = width == 0 ? 0 : array2d[0].length;
        for (int x = startX; x <= endX && x < width; x++) {
            for (int y = startY; y <= endY && y < height; y++) {
                array2d[x][y] = value;
            }
        }
        return array2d;
    }

    /**
     * Fills a subsection of {@code array2d} with {@code value}, with the section defined by start/end x/y.
     * Not to be confused with {@link #fill(long, int, int)}, which makes a new 2D array.
     *
     * @param array2d a 2D array that will be modified in-place
     * @param value   the value to fill all of array2D with
     * @param startX  the first x position to fill (inclusive)
     * @param startY  the first y position to fill (inclusive)
     * @param endX    the last x position to fill (inclusive)
     * @param endY    the last y position to fill (inclusive)
     */
    public static long[][] fill(long[][] array2d, long value, int startX, int startY, int endX, int endY) {
        final int width = array2d.length;
        final int height = width == 0 ? 0 : array2d[0].length;
        for (int x = startX; x <= endX && x < width; x++) {
            for (int y = startY; y <= endY && y < height; y++) {
                array2d[x][y] = value;
            }
        }
        return array2d;
    }

    /**
     * Fills a subsection of {@code array2d} with identical references to {@code value} (not copies), with the section
     * defined by start/end x/y.
     *
     * @param array2d a 2D array that will be modified in-place
     * @param value   the value to fill all of array2D with
     * @param startX  the first x position to fill (inclusive)
     * @param startY  the first y position to fill (inclusive)
     * @param endX    the last x position to fill (inclusive)
     * @param endY    the last y position to fill (inclusive)
     */
    public static  T[][] fill(T[][] array2d, T value, int startX, int startY, int endX, int endY) {
        final int width = array2d.length;
        final int height = width == 0 ? 0 : array2d[0].length;
        for (int x = startX; x <= endX && x < width; x++) {
            for (int y = startY; y <= endY && y < height; y++) {
                array2d[x][y] = value;
            }
        }
        return array2d;
    }

    /**
     * Reverses the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be reversed in-place
     * @return the array passed in, after reversal
     */
    public static long[] reverse(long[] data) {
        int sz;
        if (data == null || (sz = data.length) <= 0) return data;
        long t;
        for (int i = 0, j = sz - 1; i < j; i++, j--) {
            t = data[j];
            data[j] = data[i];
            data[i] = t;
        }
        return data;
    }

    /**
     * Reverses the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be reversed in-place
     * @return the array passed in, after reversal
     */
    public static boolean[] reverse(boolean[] data) {
        int sz;
        if (data == null || (sz = data.length) <= 0) return data;
        boolean t;
        for (int i = 0, j = sz - 1; i < j; i++, j--) {
            t = data[j];
            data[j] = data[i];
            data[i] = t;
        }
        return data;
    }

    /**
     * Reverses the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be reversed in-place
     * @return the array passed in, after reversal
     */
    public static char[] reverse(char[] data) {
        int sz;
        if (data == null || (sz = data.length) <= 0) return data;
        char t;
        for (int i = 0, j = sz - 1; i < j; i++, j--) {
            t = data[j];
            data[j] = data[i];
            data[i] = t;
        }
        return data;
    }

    /**
     * Reverses the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be reversed in-place
     * @return the array passed in, after reversal
     */
    public static float[] reverse(float[] data) {
        int sz;
        if (data == null || (sz = data.length) <= 0) return data;
        float t;
        for (int i = 0, j = sz - 1; i < j; i++, j--) {
            t = data[j];
            data[j] = data[i];
            data[i] = t;
        }
        return data;
    }

    /**
     * Reverses the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be reversed in-place
     * @return the array passed in, after reversal
     */
    public static double[] reverse(double[] data) {
        int sz;
        if (data == null || (sz = data.length) <= 0) return data;
        double t;
        for (int i = 0, j = sz - 1; i < j; i++, j--) {
            t = data[j];
            data[j] = data[i];
            data[i] = t;
        }
        return data;
    }

    /**
     * Reverses the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be reversed in-place
     * @return the array passed in, after reversal
     */
    public static int[] reverse(int[] data) {
        int sz;
        if (data == null || (sz = data.length) <= 0) return data;
        int t;
        for (int i = 0, j = sz - 1; i < j; i++, j--) {
            t = data[j];
            data[j] = data[i];
            data[i] = t;
        }
        return data;
    }

    /**
     * Reverses the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be reversed in-place
     * @return the array passed in, after reversal
     */
    public static byte[] reverse(byte[] data) {
        int sz;
        if (data == null || (sz = data.length) <= 0) return data;
        byte t;
        for (int i = 0, j = sz - 1; i < j; i++, j--) {
            t = data[j];
            data[j] = data[i];
            data[i] = t;
        }
        return data;
    }

    /**
     * Reverses the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be reversed in-place
     * @return the array passed in, after reversal
     */
    public static  T[] reverse(T[] data) {
        int sz;
        if (data == null || (sz = data.length) <= 0) return data;
        T t;
        for (int i = 0, j = sz - 1; i < j; i++, j--) {
            t = data[j];
            data[j] = data[i];
            data[i] = t;
        }
        return data;
    }
    
    /**
     * Shuffles the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @return the array passed in, after shuffling
     */
    public static long[] shuffle(long[] data) {
        return shuffle(data, RANDOM);
    }
    
    /**
     * Shuffles the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @return the array passed in, after shuffling
     */
    public static long[] shuffle(long[] data, Random rand) {
        int sz;
        if (data == null || (sz = data.length) <= 0) return data;
        if (rand == null) rand = RANDOM;
        long t;
        for (int i = sz - 1; i > 0; i--) {
            int j = rand.nextInt(i + 1);
            t = data[i];
            data[i] = data[j];
            data[j] = t;
        }
        return data;
    }

    /**
     * Shuffles a section of the array given as a parameter, in-place, and returns the modified original array.
     * This can be useful for shuffling collections such as libGDX's LongArray.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @param offset the index of the first element of the array that can be shuffled
     * @param length the length of the section to shuffle
     * @return the array passed in, after shuffling
     */
    public static long[] shuffle(long[] data, Random rand, int offset, int length) {
        if(data == null || data.length == 0) return data;
        offset = Math.min(Math.max(0, offset), data.length);
        length = Math.min(data.length - offset, Math.max(0, length));
        if (rand == null) rand = RANDOM;
        for (int i = offset + length - 1; i > offset; i--) {
            int ii = offset + rand.nextInt(i + 1 - offset);
            long temp = data[i];
            data[i] = data[ii];
            data[ii] = temp;
        }
        return data;
    }

    /**
     * Shuffles the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @return the array passed in, after shuffling
     */
    public static boolean[] shuffle(boolean[] data) {
        return shuffle(data, RANDOM);
    }
    
    /**
     * Shuffles the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @return the array passed in, after shuffling
     */
    public static boolean[] shuffle(boolean[] data, Random rand) {
        int sz;
        if (data == null || (sz = data.length) <= 0) return data;
        if (rand == null) rand = RANDOM;
        boolean t;
        for (int i = sz - 1; i > 0; i--) {
            int j = rand.nextInt(i + 1);
            t = data[i];
            data[i] = data[j];
            data[j] = t;
        }
        return data;
    }

    /**
     * Shuffles a section of the array given as a parameter, in-place, and returns the modified original array.
     * This can be useful for shuffling collections such as libGDX's BooleanArray.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @param offset the index of the first element of the array that can be shuffled
     * @param length the length of the section to shuffle
     * @return the array passed in, after shuffling
     */
    public static boolean[] shuffle(boolean[] data, Random rand, int offset, int length) {
        if(data == null || data.length == 0) return data;
        offset = Math.min(Math.max(0, offset), data.length);
        length = Math.min(data.length - offset, Math.max(0, length));
        if (rand == null) rand = RANDOM;
        for (int i = offset + length - 1; i > offset; i--) {
            int ii = offset + rand.nextInt(i + 1 - offset);
            boolean temp = data[i];
            data[i] = data[ii];
            data[ii] = temp;
        }
        return data;
    }

    /**
     * Shuffles the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @return the array passed in, after shuffling
     */
    public static char[] shuffle(char[] data) {
        return shuffle(data, RANDOM);
    }
    
    /**
     * Shuffles the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @return the array passed in, after shuffling
     */
    public static char[] shuffle(char[] data, Random rand) {
        int sz;
        if (data == null || (sz = data.length) <= 0) return data;
        if (rand == null) rand = RANDOM;
        char t;
        for (int i = sz - 1; i > 0; i--) {
            int j = rand.nextInt(i + 1);
            t = data[i];
            data[i] = data[j];
            data[j] = t;
        }
        return data;
    }

    /**
     * Shuffles a section of the array given as a parameter, in-place, and returns the modified original array.
     * This can be useful for shuffling collections such as libGDX's CharArray.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @param offset the index of the first element of the array that can be shuffled
     * @param length the length of the section to shuffle
     * @return the array passed in, after shuffling
     */
    public static char[] shuffle(char[] data, Random rand, int offset, int length) {
        if(data == null || data.length == 0) return data;
        offset = Math.min(Math.max(0, offset), data.length);
        length = Math.min(data.length - offset, Math.max(0, length));
        if (rand == null) rand = RANDOM;
        for (int i = offset + length - 1; i > offset; i--) {
            int ii = offset + rand.nextInt(i + 1 - offset);
            char temp = data[i];
            data[i] = data[ii];
            data[ii] = temp;
        }
        return data;
    }

    /**
     * Shuffles the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @return the array passed in, after shuffling
     */
    public static float[] shuffle(float[] data) {
        return shuffle(data, RANDOM);
    }
    
    /**
     * Shuffles the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @return the array passed in, after shuffling
     */
    public static float[] shuffle(float[] data, Random rand) {
        int sz;
        if (data == null || (sz = data.length) <= 0) return data;
        if (rand == null) rand = RANDOM;
        float t;
        for (int i = sz - 1; i > 0; i--) {
            int j = rand.nextInt(i + 1);
            t = data[i];
            data[i] = data[j];
            data[j] = t;
        }
        return data;
    }

    /**
     * Shuffles a section of the array given as a parameter, in-place, and returns the modified original array.
     * This can be useful for shuffling collections such as libGDX's FloatArray.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @param offset the index of the first element of the array that can be shuffled
     * @param length the length of the section to shuffle
     * @return the array passed in, after shuffling
     */
    public static float[] shuffle(float[] data, Random rand, int offset, int length) {
        if(data == null || data.length == 0) return data;
        offset = Math.min(Math.max(0, offset), data.length);
        length = Math.min(data.length - offset, Math.max(0, length));
        if (rand == null) rand = RANDOM;
        for (int i = offset + length - 1; i > offset; i--) {
            int ii = offset + rand.nextInt(i + 1 - offset);
            float temp = data[i];
            data[i] = data[ii];
            data[ii] = temp;
        }
        return data;
    }

    /**
     * Shuffles the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @return the array passed in, after shuffling
     */
    public static double[] shuffle(double[] data) {
        return shuffle(data, RANDOM);
    }
    
    /**
     * Shuffles the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @return the array passed in, after shuffling
     */
    public static double[] shuffle(double[] data, Random rand) {
        int sz;
        if (data == null || (sz = data.length) <= 0) return data;
        if (rand == null) rand = RANDOM;
        double t;
        for (int i = sz - 1; i > 0; i--) {
            int j = rand.nextInt(i + 1);
            t = data[i];
            data[i] = data[j];
            data[j] = t;
        }
        return data;
    }

    /**
     * Shuffles a section of the array given as a parameter, in-place, and returns the modified original array.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @param offset the index of the first element of the array that can be shuffled
     * @param length the length of the section to shuffle
     * @return the array passed in, after shuffling
     */
    public static double[] shuffle(double[] data, Random rand, int offset, int length) {
        if(data == null || data.length == 0) return data;
        offset = Math.min(Math.max(0, offset), data.length);
        length = Math.min(data.length - offset, Math.max(0, length));
        if (rand == null) rand = RANDOM;
        for (int i = offset + length - 1; i > offset; i--) {
            int ii = offset + rand.nextInt(i + 1 - offset);
            double temp = data[i];
            data[i] = data[ii];
            data[ii] = temp;
        }
        return data;
    }

    /**
     * Shuffles the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @return the array passed in, after shuffling
     */
    public static int[] shuffle(int[] data) {
        return shuffle(data, RANDOM);
    }
    
    /**
     * Shuffles the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @return the array passed in, after shuffling
     */
    public static int[] shuffle(int[] data, Random rand) {
        int sz;
        if (data == null || (sz = data.length) <= 0) return data;
        if (rand == null) rand = RANDOM;
        int t;
        for (int i = sz - 1; i > 0; i--) {
            int j = rand.nextInt(i + 1);
            t = data[i];
            data[i] = data[j];
            data[j] = t;
        }
        return data;
    }

    /**
     * Shuffles a section of the array given as a parameter, in-place, and returns the modified original array.
     * This can be useful for shuffling collections such as libGDX's IntArray.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @param offset the index of the first element of the array that can be shuffled
     * @param length the length of the section to shuffle
     * @return the array passed in, after shuffling
     */
    public static int[] shuffle(int[] data, Random rand, int offset, int length) {
        if(data == null || data.length == 0) return data;
        offset = Math.min(Math.max(0, offset), data.length);
        length = Math.min(data.length - offset, Math.max(0, length));
        if (rand == null) rand = RANDOM;
        for (int i = offset + length - 1; i > offset; i--) {
            int ii = offset + rand.nextInt(i + 1 - offset);
            int temp = data[i];
            data[i] = data[ii];
            data[ii] = temp;
        }
        return data;
    }

    /**
     * Shuffles the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @return the array passed in, after shuffling
     */
    public static short[] shuffle(short[] data) {
        return shuffle(data, RANDOM);
    }
    
    /**
     * Shuffles the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @return the array passed in, after shuffling
     */
    public static short[] shuffle(short[] data, Random rand) {
        int sz;
        if (data == null || (sz = data.length) <= 0) return data;
        if (rand == null) rand = RANDOM;
        short t;
        for (int i = sz - 1; i > 0; i--) {
            int j = rand.nextInt(i + 1);
            t = data[i];
            data[i] = data[j];
            data[j] = t;
        }
        return data;
    }

    /**
     * Shuffles a section of the array given as a parameter, in-place, and returns the modified original array.
     * This can be useful for shuffling collections such as libGDX's ShortArray.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @param offset the index of the first element of the array that can be shuffled
     * @param length the length of the section to shuffle
     * @return the array passed in, after shuffling
     */
    public static short[] shuffle(short[] data, Random rand, int offset, int length) {
        if(data == null || data.length == 0) return data;
        offset = Math.min(Math.max(0, offset), data.length);
        length = Math.min(data.length - offset, Math.max(0, length));
        if (rand == null) rand = RANDOM;
        for (int i = offset + length - 1; i > offset; i--) {
            int ii = offset + rand.nextInt(i + 1 - offset);
            short temp = data[i];
            data[i] = data[ii];
            data[ii] = temp;
        }
        return data;
    }

    /**
     * Shuffles the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @return the array passed in, after shuffling
     */
    public static byte[] shuffle(byte[] data) {
        return shuffle(data, RANDOM);
    }
    
    /**
     * Shuffles the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @return the array passed in, after shuffling
     */
    public static byte[] shuffle(byte[] data, Random rand) {
        int sz;
        if (data == null || (sz = data.length) <= 0) return data;
        if (rand == null) rand = RANDOM;
        byte t;
        for (int i = sz - 1; i > 0; i--) {
            int j = rand.nextInt(i + 1);
            t = data[i];
            data[i] = data[j];
            data[j] = t;
        }
        return data;
    }

    /**
     * Shuffles a section of the array given as a parameter, in-place, and returns the modified original array.
     * This can be useful for shuffling collections such as libGDX's ByteArray.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @param offset the index of the first element of the array that can be shuffled
     * @param length the length of the section to shuffle
     * @return the array passed in, after shuffling
     */
    public static byte[] shuffle(byte[] data, Random rand, int offset, int length) {
        if(data == null || data.length == 0) return data;
        offset = Math.min(Math.max(0, offset), data.length);
        length = Math.min(data.length - offset, Math.max(0, length));
        if (rand == null) rand = RANDOM;
        for (int i = offset + length - 1; i > offset; i--) {
            int ii = offset + rand.nextInt(i + 1 - offset);
            byte temp = data[i];
            data[i] = data[ii];
            data[ii] = temp;
        }
        return data;
    }

    /**
     * Shuffles the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @return the array passed in, after shuffling
     */
    public static  T[] shuffle(T[] data) {
        return shuffle(data, RANDOM);
    }
    
    /**
     * Shuffles the array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @return the array passed in, after shuffling
     */
    public static  T[] shuffle(T[] data, Random rand) {
        int sz;
        if (data == null || (sz = data.length) <= 0) return data;
        if (rand == null) rand = RANDOM;
        T t;
        for (int i = sz - 1; i > 0; i--) {
            int j = rand.nextInt(i + 1);
            t = data[i];
            data[i] = data[j];
            data[j] = t;
        }
        return data;
    }


    /**
     * Shuffles a section of the array given as a parameter, in-place, and returns the modified original array.
     * This can be useful for shuffling collections such as libGDX's Array types.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @param offset the index of the first element of the array that can be shuffled
     * @param length the length of the section to shuffle
     * @return the array passed in, after shuffling
     */
    public static  T[] shuffle(T[] data, Random rand, int offset, int length) {
        if(data == null || data.length == 0) return data;
        offset = Math.min(Math.max(0, offset), data.length);
        length = Math.min(data.length - offset, Math.max(0, length));
        if (rand == null) rand = RANDOM;
        for (int i = offset + length - 1; i > offset; i--) {
            int ii = offset + rand.nextInt(i + 1 - offset);
            T temp = data[i];
            data[i] = data[ii];
            data[ii] = temp;
        }
        return data;
    }


    /**
     * Shuffles the rectangular 2D array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @return the array passed in, after shuffling
     */
    public static boolean[][] shuffle2D(boolean[][] data) {
        return shuffle2D(data, RANDOM);
    }

    /**
     * Shuffles the rectangular 2D array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @return the array passed in, after shuffling
     */
    public static boolean[][] shuffle2D(boolean[][] data, Random rand) {
        int sz, wide;
        if (data == null || data[0] == null || (sz = data.length * (wide = data[0].length)) <= 0) return data;
        if (rand == null) rand = RANDOM;
        boolean t;
        int ix = wide - 1, iy = data.length - 1;
        for (int i = sz - 1; i > 0; i--, ix--) {
            int j = rand.nextInt(i + 1), jy = j / wide, jx = j - jy * wide;
            t = data[iy][ix];
            data[iy][ix] = data[jy][jx];
            data[jy][jx] = t;
            if(ix == 0) {
                ix = wide;
                iy--;
            }
        }
        return data;
    }

    /**
     * Shuffles the rectangular 2D array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @return the array passed in, after shuffling
     */
    public static char[][] shuffle2D(char[][] data) {
        return shuffle2D(data, RANDOM);
    }

    /**
     * Shuffles the rectangular 2D array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @return the array passed in, after shuffling
     */
    public static char[][] shuffle2D(char[][] data, Random rand) {
        int sz, wide;
        if (data == null || data[0] == null || (sz = data.length * (wide = data[0].length)) <= 0) return data;
        if (rand == null) rand = RANDOM;
        char t;
        int ix = wide - 1, iy = data.length - 1;
        for (int i = sz - 1; i > 0; i--, ix--) {
            int j = rand.nextInt(i + 1), jy = j / wide, jx = j - jy * wide;
            t = data[iy][ix];
            data[iy][ix] = data[jy][jx];
            data[jy][jx] = t;
            if(ix == 0) {
                ix = wide;
                iy--;
            }
        }
        return data;
    }

    /**
     * Shuffles the rectangular 2D array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @return the array passed in, after shuffling
     */
    public static long[][] shuffle2D(long[][] data) {
        return shuffle2D(data, RANDOM);
    }

    /**
     * Shuffles the rectangular 2D array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @return the array passed in, after shuffling
     */
    public static long[][] shuffle2D(long[][] data, Random rand) {
        int sz, wide;
        if (data == null || data[0] == null || (sz = data.length * (wide = data[0].length)) <= 0) return data;
        if (rand == null) rand = RANDOM;
        long t;
        int ix = wide - 1, iy = data.length - 1;
        for (int i = sz - 1; i > 0; i--, ix--) {
            int j = rand.nextInt(i + 1), jy = j / wide, jx = j - jy * wide;
            t = data[iy][ix];
            data[iy][ix] = data[jy][jx];
            data[jy][jx] = t;
            if(ix == 0) {
                ix = wide;
                iy--;
            }
        }
        return data;
    }
    
    /**
     * Shuffles the rectangular 2D array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @return the array passed in, after shuffling
     */
    public static int[][] shuffle2D(int[][] data) {
        return shuffle2D(data, RANDOM);
    }

    /**
     * Shuffles the rectangular 2D array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @return the array passed in, after shuffling
     */
    public static int[][] shuffle2D(int[][] data, Random rand) {
        int sz, wide;
        if (data == null || data[0] == null || (sz = data.length * (wide = data[0].length)) <= 0) return data;
        if (rand == null) rand = RANDOM;
        int t;
        int ix = wide - 1, iy = data.length - 1;
        for (int i = sz - 1; i > 0; i--, ix--) {
            int j = rand.nextInt(i + 1), jy = j / wide, jx = j - jy * wide;
            t = data[iy][ix];
            data[iy][ix] = data[jy][jx];
            data[jy][jx] = t;
            if(ix == 0) {
                ix = wide;
                iy--;
            }
        }
        return data;
    }

    /**
     * Shuffles the rectangular 2D array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @return the array passed in, after shuffling
     */
    public static byte[][] shuffle2D(byte[][] data) {
        return shuffle2D(data, RANDOM);
    }

    /**
     * Shuffles the rectangular 2D array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @return the array passed in, after shuffling
     */
    public static byte[][] shuffle2D(byte[][] data, Random rand) {
        int sz, wide;
        if (data == null || data[0] == null || (sz = data.length * (wide = data[0].length)) <= 0) return data;
        if (rand == null) rand = RANDOM;
        byte t;
        int ix = wide - 1, iy = data.length - 1;
        for (int i = sz - 1; i > 0; i--, ix--) {
            int j = rand.nextInt(i + 1), jy = j / wide, jx = j - jy * wide;
            t = data[iy][ix];
            data[iy][ix] = data[jy][jx];
            data[jy][jx] = t;
            if(ix == 0) {
                ix = wide;
                iy--;
            }
        }
        return data;
    }
    
    /**
     * Shuffles the rectangular 2D array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @return the array passed in, after shuffling
     */
    public static short[][] shuffle2D(short[][] data) {
        return shuffle2D(data, RANDOM);
    }

    /**
     * Shuffles the rectangular 2D array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @return the array passed in, after shuffling
     */
    public static short[][] shuffle2D(short[][] data, Random rand) {
        int sz, wide;
        if (data == null || data[0] == null || (sz = data.length * (wide = data[0].length)) <= 0) return data;
        if (rand == null) rand = RANDOM;
        short t;
        int ix = wide - 1, iy = data.length - 1;
        for (int i = sz - 1; i > 0; i--, ix--) {
            int j = rand.nextInt(i + 1), jy = j / wide, jx = j - jy * wide;
            t = data[iy][ix];
            data[iy][ix] = data[jy][jx];
            data[jy][jx] = t;
            if(ix == 0) {
                ix = wide;
                iy--;
            }
        }
        return data;
    }

    /**
     * Shuffles the rectangular 2D array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @return the array passed in, after shuffling
     */
    public static float[][] shuffle2D(float[][] data) {
        return shuffle2D(data, RANDOM);
    }

    /**
     * Shuffles the rectangular 2D array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @return the array passed in, after shuffling
     */
    public static float[][] shuffle2D(float[][] data, Random rand) {
        int sz, wide;
        if (data == null || data[0] == null || (sz = data.length * (wide = data[0].length)) <= 0) return data;
        if (rand == null) rand = RANDOM;
        float t;
        int ix = wide - 1, iy = data.length - 1;
        for (int i = sz - 1; i > 0; i--, ix--) {
            int j = rand.nextInt(i + 1), jy = j / wide, jx = j - jy * wide;
            t = data[iy][ix];
            data[iy][ix] = data[jy][jx];
            data[jy][jx] = t;
            if(ix == 0) {
                ix = wide;
                iy--;
            }
        }
        return data;
    }

    /**
     * Shuffles the rectangular 2D array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @return the array passed in, after shuffling
     */
    public static double[][] shuffle2D(double[][] data) {
        return shuffle2D(data, RANDOM);
    }

    /**
     * Shuffles the rectangular 2D array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @return the array passed in, after shuffling
     */
    public static double[][] shuffle2D(double[][] data, Random rand) {
        int sz, wide;
        if (data == null || data[0] == null || (sz = data.length * (wide = data[0].length)) <= 0) return data;
        if (rand == null) rand = RANDOM;
        double t;
        int ix = wide - 1, iy = data.length - 1;
        for (int i = sz - 1; i > 0; i--, ix--) {
            int j = rand.nextInt(i + 1), jy = j / wide, jx = j - jy * wide;
            t = data[iy][ix];
            data[iy][ix] = data[jy][jx];
            data[jy][jx] = t;
            if(ix == 0) {
                ix = wide;
                iy--;
            }
        }
        return data;
    }

    /**
     * Shuffles the rectangular 2D array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @return the array passed in, after shuffling
     */
    public static  T[][] shuffle2D(T[][] data) {
        return shuffle2D(data, RANDOM);
    }

    /**
     * Shuffles the rectangular 2D array given as a parameter, in-place, and returns the modified original.
     *
     * @param data an array that will be shuffled in-place
     * @param rand a possibly-seeded random number generator; can be null to use the unseeded {@link #RANDOM}
     * @return the array passed in, after shuffling
     */
    public static  T[][] shuffle2D(T[][] data, Random rand) {
        int sz, wide;
        if (data == null || data[0] == null || (sz = data.length * (wide = data[0].length)) <= 0) return data;
        if (rand == null) rand = RANDOM;
        T t;
        int ix = wide - 1, iy = data.length - 1;
        for (int i = sz - 1; i > 0; i--, ix--) {
            int j = rand.nextInt(i + 1), jy = j / wide, jx = j - jy * wide;
            t = data[iy][ix];
            data[iy][ix] = data[jy][jx];
            data[jy][jx] = t;
            if(ix == 0) {
                ix = wide;
                iy--;
            }
        }
        return data;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy