Java: заполнить двумерный массив или вложенные списки перестановкой дат, допуская нулевые и повторяющиеся значения только с учетом приоритета (не значений) - PullRequest
0 голосов
/ 29 марта 2019

Я выполняю операции с n числом дат и хочу заполнить массив nxm, содержащий все перестановки с учетом следующего:

  • Драйвером перестановок должен быть приоритет дат, а не значений
  • Допускается пустое значение и дубликаты / повторы
  • Не иметь повторяющихся перестановок
  • Даты определены, а не предоставлены (эта деталь реализации выходит за рамки вопроса)

Текущая работа:

Пока я разбил проблему на 3 общих случая:

  • Перестановки без нулевых значений и без дубликатов.
  • Перестановки с нулевыми значениями, но без дубликатов.
  • Остальные перестановки (нужна помощь с этим).

Пример прояснения ситуации:

У меня есть 4 даты, которые соответствуют датам рождения: Tim, Zoe, Liz и Ben.

Я хочу знать все перестановки для параллельных вселенных в отношении того, кто родился до кого (значения даты относительно актуальны). В случае null это будет означать, что человек не родился; а в случае дубликатов они означают, что люди родились в один день.

Я разделил проблему на 3 части. Я также представлял даты в виде чисел, чтобы иметь представление в массиве с числами, а позже я могу просто перевести эти числа в дату (может быть случайным образом или, возможно, как часть списка перечисляемых значений).

В качестве примера у меня будут следующие перечисленные даты:

1 = 2018-01-01
2 = 2019-01-01
3 = 2020-01-01
4 = 2021-01-01

Перестановки для 1-й части задачи (перестановки без нулевых значений и без дубликатов ) должно выглядеть так:

[ Tim , Zoe , Liz , Ben ]
[  1  ,  2  ,  3  ,  4  ]
[  1  ,  2  ,  4  ,  3  ]
[  1  ,  3  ,  2  ,  4  ]
[  1  ,  3  ,  4  ,  2  ]
[  1  ,  4  ,  3  ,  2  ]
[  1  ,  4  ,  2  ,  3  ]
[  2  ,  1  ,  3  ,  4  ]
  ...[16 more rows]...
[  4  ,  1  ,  2  ,  3  ]

Для 2-й части задачи мы можем заменить любое одиночное число на ноль во всех перестановках из предыдущего шага, и мы все равно будем последовательны. Пример будет следующим:

[ Tim , Zoe , Liz , Ben ]
[  ␀ ,  2  ,  3  ,  4  ]
[  ␀ ,  2  ,  4  ,  3  ]
[  ␀ ,  3  ,  2  ,  4  ]
[  ␀ ,  3  ,  4  ,  2  ]
[  ␀ ,  4  ,  3  ,  2  ]
[  ␀ ,  4  ,  2  ,  3  ]
[  2  , ␀  ,  3  ,  4  ]
  ...[16 more rows]...
[  4 ,  ␀  ,  2  ,  3  ]

Не могли бы вы помочь мне со следующей частью:

Хитрая часть включает в себя повторяющиеся значения, чтобы указать, что более чем один человек родился в ту же дату. Смотрите примеры ниже:

Это указывает на то, что Тим, Зои и Лиз родились в одну и ту же дату, и они моложе Бена.

[ Tim , Zoe , Liz , Ben ]
[  1  ,  1  ,  1  ,  2  ]

Это указывает на то, что Тим и Зои родились в одну и ту же дату, и они старше Бена; и Бен старше Лиз.

[ Tim , Zoe , Liz , Ben ]
[  3  ,  3  ,  1  ,  2  ]

Обе строки ниже означают, что Тим, Зои и Лиз родились в одну и ту же дату, и они моложе Бена, но только первая правильная , поскольку она показывает приоритет события в правильной последовательности.

<code>[ Tim , Zoe , Liz , Ben ]
[  1  ,  1  ,  1  ,  2  ]
<del>[  1  ,  1  ,  1  ,  3  ]</del>

Код для 1-й и 2-й частей проблемы

public class Permuter {
    static void permute(List<Integer> list, int recursionNestingLevel, List<List<Integer>> results){
        for (int listIndex = recursionNestingLevel; listIndex < list.size(); listIndex++){
            Collections.swap(list, listIndex, recursionNestingLevel);
            permute(list, recursionNestingLevel + 1, results);
            Collections.swap(list, recursionNestingLevel, listIndex);
        }
        if (recursionNestingLevel == list.size() - 1){
            results.add(new ArrayList<>(list));
        }
    }
    public static void main(String[] args){
        List<Integer> listForPermutation = Arrays.asList(1,2,3,4);
        List<List<Integer>> permutations = new ArrayList<>();
        Permuter.permute(listForPermutation, 0, permutations);
        System.out.println("Permutations without null values and without duplicates:");
        System.out.println(permutations.stream().map(list -> list.toString()).collect(Collectors.joining(System.lineSeparator())));
        List<List<Integer>> permutationsWithNulls = permutations.stream().map(list -> list.stream().map(i -> i == 1 ? null : i).collect(Collectors.toList())).collect(Collectors.toList());
        System.out.println("Permutations without null values and without duplicates:");
        System.out.println(permutationsWithNulls.stream().map(list -> list.toString()).collect(Collectors.joining(System.lineSeparator())));

    }
}

1 Ответ

0 голосов
/ 05 апреля 2019

Способ, которым я решил это, разделял проблему на 2 общих случая, каждый с 2 ​​вариантами.Общие случаи:

1.Перестановки без повторений.

    [ Eli , Dex , Eva ]
    [  1  ,  2  ,  3  ]
    [  1  ,  3  ,  2  ]
    [  2  ,  1  ,  3  ]
    [  2  ,  3  ,  1  ]
    [  3  ,  1  ,  2  ]
    [  3  ,  2  ,  1  ]

2.Перестановки с повторениями.

    [ Eli , Dex , Eva ]
    [  1  ,  1  ,  1  ]
    [  1  ,  1  ,  2  ]
    [  1  ,  2  ,  1  ]
    [  2  ,  1  ,  1  ]
    [  2  ,  2  ,  1  ]
    [  2  ,  1  ,  2  ]
    [  1  ,  2  ,  2  ]

Я решил представить null значения с 0, поэтому возможны следующие варианты:

1.1.Перестановки без повторений, но со значениями null.

    [ Eli , Dex , Eva ]
    [  0  ,  1  ,  2  ]
    [  0  ,  2  ,  1  ]
    [  1  ,  0  ,  2  ]
    [  1  ,  2  ,  0  ]
    [  2  ,  0  ,  1  ]
    [  2  ,  1  ,  0  ]

2.1.Перестановки с повторениями и значениями null.

    [ Eli , Dex , Eva ]
    [  0  ,  0  ,  0  ]
    [  0  ,  0  ,  1  ]
    [  0  ,  1  ,  0  ]
    [  1  ,  0  ,  0  ]
    [  1  ,  1  ,  0  ]
    [  1  ,  0  ,  1  ]
    [  0  ,  1  ,  1  ]

После генерации приведенных выше числовых представлений отображение данных с датами сводится к замене чисел строками даты, а нулей - нулями.

Класс Combinatorics.java вычисляет то, что я называю «моделью прецедентности», выполняя перестановки в массивах.Запустите метод main этого класса, чтобы сгенерировать числовое представление дат на основе приоритета, которое можно преобразовать в даты на основе простых жестко заданных или динамически генерируемых значений.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class Combinatorics {

    PrecedenceModel precedenceModel;

    public Combinatorics(int elementCount) {
        this.precedenceModel = new PrecedenceModel(elementCount);
    }

    public PrecedenceModel getPrecedenceModel() {
        return precedenceModel;
    }

    /**
     * Calculates all permutations of elements given in <b>{@code array}</b> recursively
     */
    public static void permute(int[] array, int nestingLevel, Set<List<Integer>> results, Predicate<int[]> filter){

        if (nestingLevel == array.length) {
            if(filter.test(array)) {
                results.add(Arrays.stream(array).boxed().collect(Collectors.toCollection(ArrayList::new)));             
            }
            return;
        }

        permute(array, nestingLevel + 1, results, filter);

        for (int index = nestingLevel + 1; index < array.length; index++) {
            if (array[index] == array[nestingLevel]) {
                continue;
            }
            array = array.clone();
            int temp = array[index];
            array[index] = array[nestingLevel];
            array[nestingLevel] = temp;
            permute(array, nestingLevel + 1, results, filter);
        }
    }

    /**
     * Calculates all   permutations of elements given in <b>{@code array}</b> recursively
     */
    public Set<List<Integer>> getPermutations(int[] array, Predicate<int[]> filter){

        Set<List<Integer>> results = new LinkedHashSet<>();
        permute(array, 0, results, filter);
        return results;
    }

    /**
     * Calculates all permutations with repeated values based on
     * the base matrix of the precedence model's matrices
     */
    public Set<List<Integer>> getPermutationsWithRepetitions(int[][] precedenceModelMatrix, Predicate<int[]> filter){

        Set<List<Integer>> permutationsWithRepetitions = new LinkedHashSet<>();

        if(filter.test(precedenceModelMatrix[precedenceModelMatrix.length - 1])) {
            permutationsWithRepetitions.add(
                Arrays.stream(precedenceModelMatrix[precedenceModelMatrix.length - 1])
                        .boxed().collect(Collectors.toCollection(ArrayList::new)));
        }

        for (int i = 1; i < (precedenceModelMatrix.length - 1); i++) {
            for (int j = i + 1; j < precedenceModelMatrix.length; j++) {
                int array[] = precedenceModelMatrix[i].clone();
                permutationsWithRepetitions.addAll(getPermutations(array, filter));
                for (int k = 0; k < i + 1; k++) {
                    array[k] = j - i + precedenceModelMatrix[0][0];
                }
                array[j] = precedenceModelMatrix[0][0];
                permutationsWithRepetitions.addAll(getPermutations(array, filter));
            }
        }

        return permutationsWithRepetitions;
    }

    private Set<List<Integer>> getPrecedenceModel(PermutationType ... permutationTypes){
        return getPrecedenceModel((a -> true), permutationTypes);
    }

    public Set<List<Integer>> getPrecedenceModel(PermutationType permutationTypes, Predicate<int[]> filter){
        return getPrecedenceModel(filter, permutationTypes);
    }

    private Set<List<Integer>> getPrecedenceModel(Predicate<int[]> filter, PermutationType ... permutationTypes){
        Set<List<Integer>> result = new LinkedHashSet<>();

        Set<PermutationType> permTypes = new LinkedHashSet<>(Arrays.asList(permutationTypes));
        System.out.println(permTypes);
        Set<List<Integer>> simplePermutations = null;
        Set<List<Integer>> permutationsWithRepetitions = null;
        Set<List<Integer>> permutationsWithSingleParameter = null;
        Set<List<Integer>> permutationsWithParameterRepetitions = null;
        if(permTypes.contains(PermutationType.ALL_PERMUTATIONS)) {
            simplePermutations = getPermutations(getPrecedenceModel().getBaseMatrix()[0], filter);
            permutationsWithRepetitions = getPermutationsWithRepetitions(getPrecedenceModel().getBaseMatrix(), filter);
            permutationsWithParameterRepetitions = getPermutationsWithRepetitions(getPrecedenceModel().getParameterMatrix(), filter);
            permutationsWithSingleParameter = getPermutations(getPrecedenceModel().getParameterMatrix()[0], filter);

            result.addAll(simplePermutations);
            result.addAll(permutationsWithRepetitions);
            result.addAll(permutationsWithParameterRepetitions);
            result.addAll(permutationsWithSingleParameter);

            return result;
        }
        if(permTypes.contains(PermutationType.SIMPLE_PERMUTATIONS)) {
            simplePermutations = getPermutations(getPrecedenceModel().getBaseMatrix()[0], filter);
            result.addAll(simplePermutations);
        }
        if(permTypes.contains(PermutationType.PERMUTATION_WITH_REPETITIONS)) {
            permutationsWithRepetitions = getPermutationsWithRepetitions(getPrecedenceModel().getBaseMatrix(), filter);
            result.addAll(permutationsWithRepetitions);
        }
        if(permTypes.contains(PermutationType.PERMUTATION_WITH_PARAMETER_REPETITIONS)) {
            permutationsWithParameterRepetitions = getPermutationsWithRepetitions(getPrecedenceModel().getParameterMatrix(), filter);
            result.addAll(permutationsWithParameterRepetitions);
        }
        if(permTypes.contains(PermutationType.PERMUTATION_WITH_SINGLE_PARAMETER)) {
            permutationsWithSingleParameter = getPermutations(getPrecedenceModel().getParameterMatrix()[0], filter);
            result.addAll(permutationsWithSingleParameter);
        }

        return result;
    }

    public static void main(String[] args){

        Combinatorics combinatorics = new Combinatorics(3);
//      Set<List<Integer>> precedenceModelBypass = combinatorics.getPermutations(new int[] {1,2,3}, (a -> true));
//      System.out.println(ApiUtils.collectionToStringN1Dn(precedenceModelBypass));

        Set<List<Integer>> precedenceModel = combinatorics.getPrecedenceModel(
//                  PermutationType.ALL_PERMUTATIONS//3(6)
//                  ,
                    PermutationType.SIMPLE_PERMUTATIONS//3(6)
                    ,
                    PermutationType.PERMUTATION_WITH_REPETITIONS//3(7)
//                  ,
//                  PermutationType.PERMUTATION_WITH_PARAMETER_REPETITIONS//3(7)
//                  ,
//                  PermutationType.PERMUTATION_WITH_SINGLE_PARAMETER//3(6)
                    );
        System.out.println(org.apache.commons.lang3.StringUtils.repeat('-', 200));
        System.out.println("Numeric Model. Count: " + precedenceModel.size());
        System.out.println(ApiUtils.collectionToStringN1Dn(precedenceModel));
    }
}

Класс PrecedenceModel.java генерирует пару квадратных матриц на основе количества элементов, которые будут использоваться для расчета премутаций.

import java.util.LinkedHashMap;
import java.util.Map;

public class PrecedenceModel {

    private int elementCount;
    private int[][] baseMatrix;
    private int[][] parameterMatrix;

    public PrecedenceModel(int elementCount) {
        this.elementCount = elementCount;
        setBaseMatrix(elementCount);
        setParameterMatrix(elementCount);
    }

    public int getElementCount() {
        return elementCount;
    }

    public int[][] getBaseMatrix() {
        return baseMatrix;
    }

    public void setBaseMatrix(int elementCount) {
        baseMatrix = new int[elementCount][elementCount];
        for (int i = 0; i < elementCount; i++) {
            for (int j = 0; j < elementCount; j++) {
                baseMatrix[i][j] = (j <= i) ? 1 : j + 1 - i; 
            }
        }
    }

    public int[][] getParameterMatrix() {
        return parameterMatrix;
    }

    public void setParameterMatrix(int elementCount) {
        parameterMatrix = new int[elementCount][elementCount];
        for (int i = 0; i < elementCount; i++) {
            for (int j = 0; j < elementCount; j++) {
                parameterMatrix[i][j] = (j <= i) ? 0 : j - i; 
            }
        }
    }

    @SafeVarargs
    public static <T> Map<T, Integer> toMap(T ... values) {
        Map<T, Integer> map = new LinkedHashMap<>(values.length);
        for (int i = 0; i < values.length; i++) {
            map.put(values[i], i);
        }

        return map;
    }
}

Класс PermutationType.java будетиспользоваться для классификации различных перестановок, которые мы можем получить.Его атрибут isParameterRequired будет указывать, есть ли в матрице нули, которые я считаю значениями параметра, потому что мы можем заменить их значениями null или любыми другими значениями позже.

public enum PermutationType {
    SIMPLE_PERMUTATIONS (false),
    PERMUTATION_WITH_SINGLE_PARAMETER (true),
    PERMUTATION_WITH_PARAMETER_REPETITIONS (true),
    PERMUTATION_WITH_REPETITIONS (false),
    ALL_PERMUTATIONS (true);

    private boolean isParameterRequired;

    private PermutationType(boolean isParameterRequired) {
        this.isParameterRequired = isParameterRequired;
    }
    public boolean isParameterRequired() {
        return isParameterRequired;
    }
}

Вот немногобольше о классе PrecedenceModel.java .2 матрицы baseMatrix и parameterMatrix, сгенерированные этим классом, будут выглядеть следующим образом:

baseMatrix

[1, 2, 3, 4]
[1, 1, 2, 3]
[1, 1, 1, 2]
[1, 1, 1, 1]

parameterMatrix

[0, 1, 2, 3]
[0, 0, 1, 2]
[0, 0, 0, 1]
[0, 0, 0, 0]

baseMatrix будет использоваться для вычисления числового представления дат без null значений (или параметров).parameterMatrix будет использоваться для вычисления числового представления дат с null значениями (или параметрами).

Первая строка любой матрицы используется для вычисления всех перестановок без повторений .

Приближается сложная часть ...

Вторые и последние строки используются для расчета перестановок только с повторениями .Значения каждой из этих строк будут переставлены для создания определенных приоритетов, и каждый из этих результатов будет переставлен, чтобы получить окончательный результат всех возможных перестановок с повторениями.Например, давайте возьмем строку 2nd baseMatrix:

[1, 1, 2, 3] <- contains 2 repeated values

Чтобы получить различные приоритеты этого ряда, я перемешиваю приоритет повторяющихся элементов (1,1) с каждымиз неповторяющихся.Это может быть сделано путем сдвига значений, таких как:

 >>>>>>>
 ^  ^  v
 ^  ^  v
[1, 1, 2, 3] <- contains 2 repeated values
 ^  ^  v
 ^  ^  v
 <<<<<<<

Различные приоритеты для 2-й строки baseMatrix:

[1, 1, 2, 3]
[2, 2, 1, 3]
[1, 1, 2, 3]
[3, 3, 2, 1]

Тогда все перестановкииз всех различных приоритетов для 2-й строки должны быть рассчитаны.

Для завершения, все вышеупомянутые процедуры для 2-го должны применяться для 3-го ряд также.И так как 4-ая строка (граничный регистр) содержит все повторяющиеся элементы, с ней не нужно запускать никаких операций.

Следующий класс бонусов является лишь примером процесса, который я использую для вычислениябазовая матрица и различный приоритет для каждой строки в матрице.

import java.util.Arrays;

public class RationaleForRepetitions {

    private class PrecedenceModel {

        private int elementCount;
        private int[][] baseMatrix;

        public PrecedenceModel(int elementCount) {
            this.elementCount = elementCount;
            setBaseMatrix(elementCount);
        }

        public int getElementCount() {
            return elementCount;
        }

        public int[][] getBaseMatrix() {
            return baseMatrix;
        }

        public void setBaseMatrix(int elementCount) {
            baseMatrix = new int[elementCount][elementCount];
            for (int i = 0; i < elementCount; i++) {
                for (int j = 0; j < elementCount; j++) {
                    baseMatrix[i][j] = (j <= i) ? 1 : j + 1 - i; 
                }
            }
        }
    }

    public static void main(String[] args) {

        final int numberOfElements = 8; 

        PrecedenceModel precedenceModel = new RationaleForRepetitions().new PrecedenceModel(numberOfElements);

        System.out.println("Number of elements to calculate = " + precedenceModel.getElementCount() + System.lineSeparator());
        System.out.println("Precedence Model: ");
        for (int i = 0; i < precedenceModel.getElementCount(); i++) {
            System.out.println(Arrays.toString(precedenceModel.getBaseMatrix()[i]));
        }

        System.out.println(System.lineSeparator() + "Discard first and last rows (boundary cases). First row has no repetitions and last one is all repetitions." + System.lineSeparator());

        System.out.println("i -> j");
        for (int i = 1; i < (precedenceModel.getElementCount() - 1); i++) {
            System.out.print(i + " -> ");
            for (int j = i + 1; j < precedenceModel.getElementCount(); j++) {
                System.out.print(j - i + 1 + " ");
            }
            System.out.println();
        }
        System.out.println(System.lineSeparator());

        System.out.println("Permutations should run on each of the following results:" + System.lineSeparator());


        for (int i = 1; i < (precedenceModel.getElementCount() - 1); i++) {
            for (int j = i + 1; j < precedenceModel.getElementCount(); j++) {
                int array[] = precedenceModel.getBaseMatrix()[i].clone();
                System.out.println(Arrays.toString(array));
                for (int k = 0; k < i + 1; k++) {
                    array[k] = j - i + 1;
                }
                array[j] = 1;
                System.out.println(String.format("%s%s", Arrays.toString(array), System.lineSeparator()));

            }
            System.out.println(String.format("%2$s%2$s%2$s%1$s", System.lineSeparator(), "-------------------"));
        }

        System.out.println("Precedence Model: ");
        for (int i = 0; i < precedenceModel.getElementCount(); i++) {
            System.out.println(Arrays.toString(precedenceModel.getBaseMatrix()[i]));
        }
    }
}

Следующий код только для правильного отображения / печати результатов , и его можно опустить, если ссылки из класса Combinatoricsудалены.

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;

public class ApiUtils {

    public static Map<String, String> mergeMapsWithPrecedence(Map<String, String> ... higherToLowerPrecedenceMaps) {
        int mapLength = 0;
        for (Map<String, String> map : higherToLowerPrecedenceMaps) {
//          mapLength += map.size();
            mapLength = map.size() > mapLength ? map.size() : mapLength;
        }

        Map<String, String> superMap = new HashMap<>(mapLength);
        for (Map<String, String> map : higherToLowerPrecedenceMaps) {
            map.forEach((k,v) -> {
                if( ! superMap.containsKey(k) ) {
                    superMap.put(k, v);
                }
            });
        }

        return superMap;
    }

    public static <T> Map<Integer, T> getMap(T ... values) {
        Map<Integer, T> map = new HashMap<>(values.length);
        for (int i = 0; i < values.length; i++) {
            map.put(i, values[i]);
        }

        return map;
    }

    public static <T> List<T> getList(T ... values) {
        return new ArrayList<>(Arrays.asList(values));
    }

    public static void appendNewLine(StringBuffer stringBuffer, String content) {
        stringBuffer
            .append(content)
            .append(System.lineSeparator());
    }

    public static void appendKeyValueNewLine(StringBuffer stringBuffer, String key, String value, String delimiter) {
        stringBuffer
            .append(key)
            .append(delimiter)
            .append(value)
            .append(System.lineSeparator());
    }

    public static List<String> replaceParameterizedList(List<String> data) {
        return
            data
                .stream()
                .filter(s -> ! ApiConstantsUtils.MISSING.equals(s))
                .map(s -> ApiConstantsUtils.NULL.equals(s) ? null : s)
                .collect(Collectors.toList());
    }

    public static Map<String, String> replaceParameterizedMap(Map<String, String> data) {
        Map<String, String> updatedData = new HashMap<>();
        for (Entry<String, String> element : data.entrySet()) {

            if (ApiConstantsUtils.NULL.equals(element.getValue())) {
                updatedData.put(element.getKey(), null);
            } else if ( ! ApiConstantsUtils.MISSING.equals(element.getValue()) ) {
                updatedData.put(element.getKey(), element.getValue());
            }
        }

        return updatedData;
    }

    public static List<Map<String, String>> replaceParameterizedListOfMaps(List<Map<String, String>> data) {
        return
            data
                .stream()
                .map(map -> replaceParameterizedMap(map))
                .collect(Collectors.toList());
    }

    /**
     * Naming conventions for methods that print nested collections:
     * 
     * Nx = (N)esting, x = level
     * Sn = (D)elimiter. n = newLine
     */
    public static <T extends Collection<?>> String collectionToStringN1Dn(Collection<T> collection) {
        return
            collection
                .stream()
                .map(list -> list.toString())
                .collect(Collectors.joining(System.lineSeparator()));
    }

    public static void printStackTrace() {
        for (StackTraceElement ste : Thread.currentThread().getStackTrace()) {
            System.out.println(ste.toString());
        }
    }

    public static void creatFileInRoot(String fileContent) {
        creatFileInRoot(Arrays.asList(fileContent), "txt");
    }

    public static void creatFileInRoot(Iterable<String> fileContent) {
        creatFileInRoot(fileContent, "txt");
    }

    public static void creatFileInRoot(Iterable<String> fileContent, String extension) {
        Path outputPath = ApiConstantsUtils.OUTPUT_FOLDER;
        if(extension == null || StringUtils.isBlank(extension)) {
            extension = "txt";
        }
        String filename =
                ApiReflectionUtils.getCallerCallerClassName().getSimpleName() +
                "_" + Instant.now().toEpochMilli() +
                "." + extension;
        filename = filename.replaceAll(" ", "_");

        creatFileInRoot(outputPath, filename, fileContent);
    }

    private static void creatFileInRoot(Path outputPath, String filename, Iterable<String> fileContent) {
        outputPath.toFile().mkdirs();
        try {
            Files.write(outputPath.resolve(filename), fileContent, StandardOpenOption.CREATE_NEW);
            System.out.println("File created: " + outputPath.resolve(filename).toAbsolutePath().toString());
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("Error while attempting to create file: " + outputPath.resolve(filename).toAbsolutePath().toString());
        }
    }
}


public class ApiReflectionUtils {

    public static Class<?> getCallerClassName() {
        StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
        for (int i = 1; i < stElements.length; i++) {
            StackTraceElement ste = stElements[i];
            if (!ste.getClassName().equals(ApiReflectionUtils.class.getName())
                    && ste.getClassName().indexOf("java.lang.Thread") != 0) {
                try {
                    return Class.forName(ste.getClassName());
                } catch (ClassNotFoundException e) {}
            }
        }
        return null;
    }

    public static Class<?> getCallerCallerClassName() {
        StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
        String callerClassName = null;
        for (int i = 1; i < stElements.length; i++) {
            StackTraceElement ste = stElements[i];
            if (!ste.getClassName().equals(ApiReflectionUtils.class.getName())
                    && ste.getClassName().indexOf("java.lang.Thread") != 0) {
                if (callerClassName == null) {
                    callerClassName = ste.getClassName();
                } else if (!callerClassName.equals(ste.getClassName())) {
                    try {
                        return Class.forName(ste.getClassName());
                    } catch (ClassNotFoundException e) {}
                }
            }
        }
        return null;
    }
}


import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ApiConstantsUtils {

    public static final String CALCULATE = "calculate";
    public static final String NULL = "[NULL]";
    public static final String MISSING = "[MISSING]";
    public static final Path OUTPUT_FOLDER = Paths.get("src/main/resources/output");



    public enum ComparisonCardinality{
        ONE_TO_ONE,
        ONE_TO_MANY,
        MANY_TO_ONE,
        MANY_TO_MANY;

        public static ComparisonCardinality getElementComparisonCardinality(List<?> objectsLeft, List<?> objectsRight) {

            if (objectsLeft.size() == 1 && objectsRight.size() == 1) {
                return ONE_TO_ONE;
            } else if (objectsLeft.size() == 1 && objectsRight.size() > 1) {
                return ONE_TO_MANY;
            } else if (objectsLeft.size() > 1 && objectsRight.size() == 1) {
                return MANY_TO_ONE;
            } else if (objectsLeft.size() > 1 && objectsRight.size() == objectsLeft.size()) {
                return MANY_TO_MANY;
            }

            return null;
        }
    }

    public enum ComparisonOperator{
        EQUAL_TO("=", "equal"),
        DISTINCT_FROM("<>", "distinct"),
        GREATER_THAN(">", "greater"),
        LESS_THAN("<", "less");

        private String operator;
        private String keyWord;
        ComparisonOperator(String operator, String keyWord) {
            this.operator = operator;
            this.keyWord = keyWord;
        }
        public String getOperator() {
            return operator;
        }
        public String getKeyWord() {
            return keyWord;
        }
        private static Map<String,ComparisonOperator> enumValues = new HashMap<>();
        static {
            for (ComparisonOperator comparisonOperator : ComparisonOperator.values()) {
                enumValues.put(comparisonOperator.keyWord, comparisonOperator);
            }
        }
        public static ComparisonOperator getByKeyWord(String operatorKeyWord) {
            return enumValues.get(operatorKeyWord);
        }
    }

}
...