Wzór na określoną wariację z różnymi powtórzeniami

0

Cześć. Chciałbym obliczyć oraz wylistować wszystkie możliwe wariacje, które maja swoje warunki oraz określoną liczbę powtórzeń, np.:
A:
-ilość powtórzeń: 15
-warunek: brak
B:
-ilość powtórzeń: 10
-warunek: brak
C:
-ilość powtórzeń: 8
-warunek: minimum 3 powtórzenia A
np.
A,B,A,A,B,C,A,B,B,C.... (A i B mogą się powtarzać losowo do określonej liczby powtórzeń, C może zacząć się powtarzać gdy A zostało powtórzone minimum 3 razy)

Program mogę napisać w Javie, natomiast nie za bardzo wiem jak określić wzór który miałby to wyliczyć

1

Pod kątem samego zliczania kombinatorycznego na papierze dla konkretnego przypadku raczej nie ma w tym nic trudnego.
Jeśli chodzi o listowanie wszystkich możliwych wariacji oraz uwzględnienie warunków, może pomyśl o jakiejś prostej rekurencji?
Na pierwszy rzut oka, pomyślałem o czymś takim:
Na wejściu masz listę krotek <symbol, liczba_powtórzeń, funkcja>. Funkcja pozwala stwierdzić, czy na podstawie obecnego stanu ciągu możemy w aktualnym miejscu wstawić dany symbol.
Tworzysz pusta tablicę przetrzymującą obecny ciąg, na każdej pozycji iterujesz po stworzonej liście i dla elementów, które mają liczba_powtórzeń > 0 oraz funkcja zwraca wartość true i schodzisz w dół z rekursją, przekazując zaktualizowaną liste krotek.
Czyli w Twoim przypadku lista wyglądałaby tak: [('A', 15, () -> true), ('B', 10, () -> true), ('C', 8, (obecna_tablica) -> obecna_tablica.count('A') >= 3)]
Możesz wstawić A na pierwszym miejscu więc wywołujesz rekurencyjnie funkcję z tablicą stanu [A] oraz zmodyfikowana listą (teraz liczba powtórzeń A będzie o 1 mniejsza czyli 14.)
Możesz wstawić B na pierwszym miejscu więc wywołujesz rekurencyjnie funkcję z tablicą stanu [B] oraz zmodyfikowana listą (teraz liczba powtórzeń B będzie o 1 mniejsza czyli 9.)
Warunkiem zaprzestania rekursji jest fakt, że wszystkie powtórzenia dla symboli dla których funkcja zwraca true wynoszą zero (lub ich nie ma).
Jeśli dla wszystkich symboli liczba powtórzeń spadła do 0, wówczas mamy nową wariancję, więc możemy wypisać rekord i dodać go do zmiennej zliczajacej wariancje.
Pytaniem otwartym pozostaje jak funkcje miałyby być tworzone na podstawie tekstowego warunku, przykładowo wspomnianego "minimum 3 powtórzenia A".
Zda egzamin ;)?

0

@DonStefano: Dzięki, stworzyłem takiego potworka sugerując się Twoja odpowiedzią:

package pl.variants;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class AllVariants {
    private static final List<List<Option>> allVariants = new ArrayList<>();

    public static void main(String[] args) {
        allVariants.add(repeat(new ArrayList<>()));
        System.out.println(allVariants);
    }

    private static List<Option> repeat(List<Option> options) {
        while (Stream.of(Option.values()).anyMatch(option -> option.isFeasible(options))) {
            for (Option option : Option.values()) {
                if (option.isFeasible(options)) {
                    options.add(option);
                }
            }
        }
        return options;
    }
}

enum Option {
    A(15),
    B(10),
    C(8) {
        @Override
        boolean isFeasible(List<Option> options) {
            return super.isFeasible(options) && options.stream()
                    .filter(option -> option == A)
                    .count() >= 3;
        }
    };

    private final int maxRepeats;

    Option(int maxRepeats) {
        this.maxRepeats = maxRepeats;
    }

    boolean isFeasible(List<Option> options) {
        return options.stream()
                .filter(option -> option == Option.valueOf(this.name()))
                .count() < this.maxRepeats;
    }
}

Metoda wypisuje poprawnie potwórzenia (wraz z ilością i warunkami):

[[A, B, A, B, A, B, C, A, B, C, A, B, C, A, B, C, A, B, C, A, B, C, A, B, C, A, B, C, A, A, A, A, A]]

Niestety ale to dopiero pierwsza kombinacja. Jak mogę stworzyć warunek w którym będę powtarzać to dopóki lista nie będzie posiadać wszystkich możliwych kombinacji?

1

W Twoim rozwiązaniu nie korzystasz z rekurencji, ale jesteś na dobrej drodze - obliczasz jak na razie jedną wariację, dodając kolejno możliwe symbole tak długo jak się da.
Ja miałem w głowie taki pomysł, aby stosować rekurencję, zchodząc w dół dla każdego możliwego symbolu, po krótce:

  1. Jedna egzekucja (oznaczmy 0), mamy stan = []
  2. Wykonujemy rekurencyjnie z egzekucji 0 dla symbolu A, ponieważ możemy go umieścić na początku, mamy stan = [A]. Oznaczamy egzekucję jako 1
  3. Wykonujemy rekurencyjnie z 0 dla B, stan = [B]. Oznaczamy egz jako 2
  4. Wykonujemy rekurencyjnie z egzekucji 1 dla symbolu A. Stan = [A, A]
  5. Wykonujemy z egz 1 dla symbolu B. Stan = [A, B]
  6. =,= z egzekucji 2 dla symbolu A. Stan = [B, A]
  7. =,= z egzekucji 2 dla symbolu B. Stan = [B, B]

I tak dalej, czyli klasyczna rekurencja. Wywołujemy funkcję z nowym stanem (nowym symbolem) i robimy tak samo dla każdego możliwego symbolu.
Jakbyś chciał to zwizualizować, to finalny wynik byłby w postaci drzewa wywołań, w każdym węźle byłby przechowywany aktualny stan - liście będą przechowywały stany końcowe - kompletne, pełne wariacje lub takie których się nie udało zrobić.

PS: Pamiętaj, że jak będziesz przekazywał obecny stan do nowej egzeukcji, to przekazuj jego kopię, inaczej będziesz mógł podmienić wartości w innych egzekucjach.

1

O, @Ephyron te drzewo które tam jest zilustrowane to to co próbowałem powyżej opisać

Fajnie:) To teraz "brute force", iterować po "outpucie", nakładając warunki.

0

@DonStefano: dzięki za pomoc, koniec końców zastosowałem takie rozwiązanie (2 dni temu, ale nie miałem za bardzo czasu odpisać :D)

package pl.variants;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

public class AllVariants {
    private static final List<List<Option>> allVariants = new ArrayList<>();
    private static final int MAX_LENGTH = Option.sum();

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        repeat(new ArrayList<>(Option.sum()));
        System.out.println(allVariants);
        System.out.println(Duration.ofMillis(System.currentTimeMillis() - start).toMillis());
    }

    private static void repeat(List<Option> options) {
        if (options.size() == MAX_LENGTH) {
            allVariants.add(options);
            return;
        }
        for (Option option : Option.values()) {
            List<Option> copy = new ArrayList<>(List.copyOf(options));
            if (option.isFeasible(options)) {
                copy.add(option);
            } else {
                continue;
            }
            repeat(new ArrayList<>(copy));
        }
    }

    public enum Option {
        A(6),
        B(3),
        C(5) {
            @Override
            boolean isFeasible(List<Option> options) {
                return super.isFeasible(options) && options.stream()
                        .filter(Objects::nonNull)
                        .filter(option -> option == Option.A)
                        .count() >= 1;
            }
        };

        private final int maxRepeats;

        Option(int maxRepeats) {
            this.maxRepeats = maxRepeats;
        }

        boolean isFeasible(List<Option> options) {
            return options.stream()
                    .filter(Objects::nonNull)
                    .filter(option -> option == Option.valueOf(this.name()))
                    .count() < this.maxRepeats;
        }

        static int sum() {
            return Stream.of(Option.values())
                    .mapToInt(value -> value.maxRepeats)
                    .sum();
        }
    }
}

Nie wiem czy jest sens w to dalej iść, bo no dosyć nie spodziewałem się tej liczby kombinacji, a ilość danych mam kilkakrotnie razy więcej więc sobie odpuszczę liczenie kombinacji. Da się to pewnie zrobić znacznie wydajniej nie kopiując listy, ale uzyskałem co chciałem. Dzięki @DonStefano i @lion137 za pomoc

1 użytkowników online, w tym zalogowanych: 0, gości: 1