Rechner

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Steazy
User
Beiträge: 1
Registriert: Freitag 28. Januar 2022, 19:02

Ich möchte ein Programm schreiben, welches, wie im Spiel Rechen Fuchs, aus 5 Ziffern eine Zahl berechnet mit Addition, Subtraktion, Division und Multiplikation. Ich habe bereits angefangen. Jedoch kann ich erst mit 2 Ziffern eine bestimmte Zahl berechnen. Hätte jemand eine Idee, wie man das Ganze mit 5 Ziffern machen kann ohne alle möglichen Kombinationen zu schreiben?


ziel=input("Welche Zahl soll errechnet werden?")
z1=input("1. Zahl, die zur Verfügung steht:")
z2=input("2. Zahl, die zur Verfügung steht:")
z3=input("3. Zahl, die zur Verfügung steht:")
z4=input("4. Zahl, die zur Verfügung steht:")

def kombzwei(a,b,zielzahl):
if a+b==zielzahl:
print(str(a)+" + "+str(b)+" = "+str(zielzahl))
if a-b==zielzahl:
print(str(a)+" - "+str(b)+" = "+str(zielzahl))
if b-a==zielzahl:
print(str(b)+" - "+str(a)+" = "+str(zielzahl))
if a*b==zielzahl:
print(str(a)+" * "+str(b)+" = "+str(zielzahl))
if a/b==zielzahl:
print(str(a)+" / "+str(b)+" = "+str(zielzahl))
if b/a==zielzahl:
print(str(b)+" / "+str(a)+" = "+str(zielzahl))
def kombdrei(a,b,c,zielzahl):
if a+b+c==zielzahl:
print(str(a)+" + "+str(b)+" + "+str(c)+" = "+str(zielzahl))

kombzwei(z1,z2,ziel)
kombzwei(z1,z3,ziel)
kombzwei(z1,z4,ziel)
kombzwei(z2,z3,ziel)
kombzwei(z2,z4,ziel)
kombzwei(z3,z4,ziel)
kombdrei(z1,z2,z3,ziel)
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@Steazy,

ich bin mir nicht ganz sicher was die genauen Regeln von Rechen Fuchs sind.
Folgende Lösung ist unter der Annahme entstanden, dass
mindestens zwei, maximal fünf einstellige Ganzzahlen eingegeben werden können,
jeder Rechenoperator mindestens null, maximal ein mal vorkommen darf.
Außerdem wird nicht berücksichtig, dass das:
2 · 5 = 10
5 · 2 = 10
eigentlich nur eine Rechenaufgabe ist.

Die Idee beruht darauf, dass man durch Python's itertools
https://docs.python.org/3/library/itert ... -itertools
Die möglichen Permutationen aller eingegebenen Zahlen und
die möglichen Permutationen aller Rechenoperatoren bestimmt.
Dann wird aus diesen das kartesische Produkt gebildet.

Theoretisch hat man dann schon alle möglichen Rechenaufgaben.
Mit dem sympy Paket für symbolische Mathematik
https://www.sympy.org/en/index.html
kann man dann die Rechenaufgaben ausrechnen. Das hat den Vorteil, dass zum Beispiel Punkt-vor-Strichrechnung beachtet wird.
Es werden nur die Rechenaufgaben ausgegeben, die als Ergebnis die gewünschte Zielzahl berechnen.

Die Hälfte des Programms besteht nur daraus die Benutzereingabe zu holen und einigermaßen zu validieren. Daher sieht es vielleicht etwas komplizierter aus als es ist.

Code: Alles auswählen

from math import isclose
from itertools import permutations, product, zip_longest
from sympy import sympify

OPERATOR_MAPPING = {
    "+": "+",
    "-": "-",
    "*": "\u00b7",
    "/": "\u00f7",
}


def get_target_input():
    while True:
        try:
            target = int(input("Welche Zahl soll errechnet werden? "))
        except ValueError:
            print("Bitte nur Zahlen eingeben!")
        else:
            return target


def get_number_input():
    while True:
        number_input = input(
            "Durch Leerzeichen getrennte Ganzzahlen von 1 bis 9 eingeben: "
        )
        numbers = number_input.split()
        if len(numbers) < 2:
            print("Die Eingabe muss mindestens 2 Ganzzahlen enthalten!")
        elif len(numbers) > 5:
            print("Die Eingabe darf nicht mehr als 5 Ganzzahlen enthalten!")
        else:
            try:
                map(int, numbers)
            except ValueError:
                print("Bitte nur Zahlen eingeben!")
            else:
                if not all(len(number) == 1 for number in numbers):
                    print("Bitte nur einstellige Zahlen eingeben!")
            return numbers


def find_calculations():
    target_number = get_target_input()
    digits = get_number_input()
    operators = ["+", "-", "*", "/"]
    for digit_permutation, operator_permutation in product(
        permutations(digits), permutations(operators, r=len(digits) - 1)
    ):
        str_expr = " ".join(
            f"{digit} {operator}"
            for digit, operator in zip_longest(
                digit_permutation,
                operator_permutation,
                fillvalue="",
            )
        )
        expr = sympify(str_expr)
        result = expr.evalf()

        if isclose(result, target_number, abs_tol=0):
            output_expr = " ".join(
                f"{digit} {OPERATOR_MAPPING[operator]}"
                for digit, operator in zip(
                    digit_permutation[:-1],
                    operator_permutation,
                )
            )
            yield f"{output_expr} {digit_permutation[-1]} = {result:0.0f}"


def main():
    for calculation in find_calculations():
        print(calculation)


if __name__ == "__main__":
    main()


"""
Beispiel Ausgabe:

Welche Zahl soll errechnet werden? 10 
Durch Leerzeichen getrennte Ganzzahlen von 1 bis 9 eingeben: 1 2 3 4 5

2 ÷ 1 · 4 - 3 + 5 = 10
2 ÷ 1 · 4 + 5 - 3 = 10
2 · 4 ÷ 1 - 3 + 5 = 10
2 · 4 ÷ 1 + 5 - 3 = 10
2 · 4 - 3 ÷ 1 + 5 = 10
2 · 4 - 3 + 5 ÷ 1 = 10
2 · 4 + 5 ÷ 1 - 3 = 10
2 · 4 + 5 - 3 ÷ 1 = 10
3 ÷ 2 · 4 - 1 + 5 = 10
3 ÷ 2 · 4 + 5 - 1 = 10
3 · 4 ÷ 2 - 1 + 5 = 10
3 · 4 ÷ 2 + 5 - 1 = 10
4 ÷ 1 · 2 - 3 + 5 = 10
4 ÷ 1 · 2 + 5 - 3 = 10
4 · 2 ÷ 1 - 3 + 5 = 10
4 · 2 ÷ 1 + 5 - 3 = 10
4 · 2 - 3 ÷ 1 + 5 = 10
4 ÷ 2 · 3 - 1 + 5 = 10
4 · 2 - 3 + 5 ÷ 1 = 10
4 ÷ 2 · 3 + 5 - 1 = 10
4 · 2 + 5 ÷ 1 - 3 = 10
4 · 2 + 5 - 3 ÷ 1 = 10
4 · 3 ÷ 2 - 1 + 5 = 10
4 · 3 ÷ 2 + 5 - 1 = 10
5 ÷ 1 + 2 · 4 - 3 = 10
5 - 1 + 3 ÷ 2 · 4 = 10
5 ÷ 1 - 3 + 2 · 4 = 10
5 - 1 + 3 · 4 ÷ 2 = 10
5 ÷ 1 - 3 + 4 · 2 = 10
5 - 1 + 4 ÷ 2 · 3 = 10
5 ÷ 1 + 4 · 2 - 3 = 10
5 - 1 + 4 · 3 ÷ 2 = 10
5 + 2 ÷ 1 · 4 - 3 = 10
5 + 2 · 4 ÷ 1 - 3 = 10
5 + 2 · 4 - 3 ÷ 1 = 10
5 - 3 ÷ 1 + 2 · 4 = 10
5 - 3 ÷ 1 + 4 · 2 = 10
5 - 3 + 2 ÷ 1 · 4 = 10
5 + 3 ÷ 2 · 4 - 1 = 10
5 - 3 + 2 · 4 ÷ 1 = 10
5 - 3 + 4 ÷ 1 · 2 = 10
5 + 3 · 4 ÷ 2 - 1 = 10
5 - 3 + 4 · 2 ÷ 1 = 10
5 + 4 ÷ 1 · 2 - 3 = 10
5 + 4 · 2 ÷ 1 - 3 = 10
5 + 4 · 2 - 3 ÷ 1 = 10
5 + 4 ÷ 2 · 3 - 1 = 10
5 + 4 · 3 ÷ 2 - 1 = 10
"""
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

@Steazy: Das ist ein perfektes Beispiel für Rekursion. Du weißt, wie du das Problem für 2 Zahlen lösen kannst (Rekursionsende), und um das Problem für 3 (bzw. allgemein n) Zahlen zu lösen, musst du das Problem für 2 (bzw. allgemein n - 1) Zahlen lösen, und dann nochmal für die eine übrig gebliebene Zahl zusammen mit dem Ergebnis des Rekursionsschrittes:

Code: Alles auswählen

#!/usr/bin/env python3

from contextlib import suppress
from fractions import Fraction
from operator import add
from operator import mul
from operator import sub
from operator import truediv

from attr import attrib
from attr import attrs

OPERATORS = {"+": add, "-": sub, "*": mul, "/": truediv}


@attrs(frozen=True)
class Term:
    lhs = attrib()
    operator = attrib()
    rhs = attrib()

    def eval(self):
        return OPERATORS[self.operator](self.lhs.eval(), self.rhs.eval())

    def __str__(self):
        return f"({self.lhs} {self.operator} {self.rhs})"


@attrs(frozen=True)
class Literal:
    value = attrib()

    def eval(self):
        return self.value

    def __str__(self):
        return str(self.value)


def iter_terms(lhs, rhs):
    for op in OPERATORS:
        yield Term(lhs, op, rhs)
        yield Term(rhs, op, lhs)


def combinations(numbers):
    if len(numbers) == 2:
        yield from iter_terms(*numbers)
    else:
        for i, number in enumerate(numbers):
            rest = numbers[:i] + numbers[i + 1:]
            for term in combinations(rest):
                yield from iter_terms(number, term)


def main():
    numbers = [Literal(Fraction(n)) for n in range(1, 6)]
    for term in combinations(numbers):
        with suppress(ZeroDivisionError):
            if term.eval() == 10:
                print(term)


if __name__ == "__main__":
    main()
Ich bin dabei davon ausgegangen, dass beliebige Klammerungen erlaubt sind und dass `a * b` und `b * a` unterschiedliche Aufgaben sind:

Code: Alles auswählen

$ ./main.py | wc -l
3580
$ ./main.py | shuf | head
(5 - (2 - (4 + (1 * 3))))
((5 / (4 - (1 * 3))) * 2)
((2 * (4 / (3 + 1))) * 5)
((((3 / 1) + 4) - 2) + 5)
(5 * (2 / (4 - (3 / 1))))
(4 + (((3 * 1) + 5) - 2))
(1 + ((5 - (4 - 2)) * 3))
(((3 - (4 - 5)) + 1) * 2)
(5 * ((2 * (1 * 3)) - 4))
(((2 + (4 / 1)) / 3) * 5)
Wenn du das nicht willst, müsstest du `iter_terms` das Kommutativ- und Assoziativgesetz beibringen.

@rogerb: `map` ist lazy, wie kann da jemals ein `ValueError` geworfen werden? Und das `return` danach sollte doch eigentlich auch im `else`-Block stehen?
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@narpfel,

Danke, hatte ich übersehen. So müsste es passen:

Code: Alles auswählen

from math import isclose
from itertools import permutations, product, zip_longest
from sympy import sympify

OPERATOR_MAPPING = {
    "+": "+",
    "-": "-",
    "*": "\u00b7",
    "/": "\u00f7",
}


def get_target_input():
    while True:
        try:
            target = int(input("Welche Zahl soll errechnet werden? "))
        except ValueError:
            print("Bitte nur Zahlen eingeben!")
        else:
            return target


def get_number_input():
    while True:
        number_input = input(
            "Durch Leerzeichen getrennte Ganzzahlen von 1 bis 9 eingeben: "
        )
        numbers = number_input.split()
        if len(numbers) < 2:
            print("Die Eingabe muss mindestens 2 Ganzzahlen enthalten!")
        elif len(numbers) > 5:
            print("Die Eingabe darf nicht mehr als 5 Ganzzahlen enthalten!")
        else:
            try:
                list(map(int, numbers))
            except ValueError:
                print("Bitte nur Zahlen eingeben!")
            else:
                if not all(len(number) == 1 for number in numbers):
                    print("Bitte nur einstellige Zahlen eingeben!")
                return numbers


def find_calculations():
    target_number = get_target_input()
    digits = get_number_input()
    operators = ["+", "-", "*", "/"]
    for digit_permutation, operator_permutation in product(
        permutations(digits), permutations(operators, r=len(digits) - 1)
    ):
        str_expr = " ".join(
            f"{digit} {operator}"
            for digit, operator in zip_longest(
                digit_permutation,
                operator_permutation,
                fillvalue="",
            )
        )
        expr = sympify(str_expr)
        result = expr.evalf()

        if isclose(result, target_number, abs_tol=0):
            output_expr = " ".join(
                f"{digit} {OPERATOR_MAPPING[operator]}"
                for digit, operator in zip(
                    digit_permutation[:-1],
                    operator_permutation,
                )
            )
            yield f"{output_expr} {digit_permutation[-1]} = {result:0.0f}"


def main():
    for calculation in find_calculations():
        print(calculation)


if __name__ == "__main__":
    main()


"""
Beispiel Ausgabe:

Welche Zahl soll errechnet werden? 10 
Durch Leerzeichen getrennte Ganzzahlen von 1 bis 9 eingeben: 1 2 3 4 5

2 ÷ 1 · 4 - 3 + 5 = 10
2 ÷ 1 · 4 + 5 - 3 = 10
2 · 4 ÷ 1 - 3 + 5 = 10
2 · 4 ÷ 1 + 5 - 3 = 10
2 · 4 - 3 ÷ 1 + 5 = 10
2 · 4 - 3 + 5 ÷ 1 = 10
2 · 4 + 5 ÷ 1 - 3 = 10
2 · 4 + 5 - 3 ÷ 1 = 10
3 ÷ 2 · 4 - 1 + 5 = 10
3 ÷ 2 · 4 + 5 - 1 = 10
3 · 4 ÷ 2 - 1 + 5 = 10
3 · 4 ÷ 2 + 5 - 1 = 10
4 ÷ 1 · 2 - 3 + 5 = 10
4 ÷ 1 · 2 + 5 - 3 = 10
4 · 2 ÷ 1 - 3 + 5 = 10
4 · 2 ÷ 1 + 5 - 3 = 10
4 · 2 - 3 ÷ 1 + 5 = 10
4 ÷ 2 · 3 - 1 + 5 = 10
4 · 2 - 3 + 5 ÷ 1 = 10
4 ÷ 2 · 3 + 5 - 1 = 10
4 · 2 + 5 ÷ 1 - 3 = 10
4 · 2 + 5 - 3 ÷ 1 = 10
4 · 3 ÷ 2 - 1 + 5 = 10
4 · 3 ÷ 2 + 5 - 1 = 10
5 ÷ 1 + 2 · 4 - 3 = 10
5 - 1 + 3 ÷ 2 · 4 = 10
5 ÷ 1 - 3 + 2 · 4 = 10
5 - 1 + 3 · 4 ÷ 2 = 10
5 ÷ 1 - 3 + 4 · 2 = 10
5 - 1 + 4 ÷ 2 · 3 = 10
5 ÷ 1 + 4 · 2 - 3 = 10
5 - 1 + 4 · 3 ÷ 2 = 10
5 + 2 ÷ 1 · 4 - 3 = 10
5 + 2 · 4 ÷ 1 - 3 = 10
5 + 2 · 4 - 3 ÷ 1 = 10
5 - 3 ÷ 1 + 2 · 4 = 10
5 - 3 ÷ 1 + 4 · 2 = 10
5 - 3 + 2 ÷ 1 · 4 = 10
5 + 3 ÷ 2 · 4 - 1 = 10
5 - 3 + 2 · 4 ÷ 1 = 10
5 - 3 + 4 ÷ 1 · 2 = 10
5 + 3 · 4 ÷ 2 - 1 = 10
5 - 3 + 4 · 2 ÷ 1 = 10
5 + 4 ÷ 1 · 2 - 3 = 10
5 + 4 · 2 ÷ 1 - 3 = 10
5 + 4 · 2 - 3 ÷ 1 = 10
5 + 4 ÷ 2 · 3 - 1 = 10
5 + 4 · 3 ÷ 2 - 1 = 10
"""
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

@rogerb: Jetzt kann man immer noch mehrstellige Zahlen eingeben. Und dass da eine Liste erzeugt wird, mit der dann nichts gemacht wird, ist IMHO auch nicht so schön. Ich würde das so schreiben:

Code: Alles auswählen

def get_number_input():
    while True:
        number_input = input(
            "Durch Leerzeichen getrennte Ganzzahlen von 1 bis 9 eingeben: "
        )
        numbers = number_input.split()
        if len(numbers) < 2:
            print("Die Eingabe muss mindestens 2 Ganzzahlen enthalten!")
        elif len(numbers) > 5:
            print("Die Eingabe darf nicht mehr als 5 Ganzzahlen enthalten!")
        else:
            try:
                numbers = [int(number) for number in numbers]
            except ValueError:
                print("Bitte nur Zahlen eingeben!")
            else:
                if not all(number in range(10) for number in numbers):
                    print("Bitte nur einstellige Zahlen eingeben!")
                else:
                    return numbers
Und `get_target_input` kann man auch einfacher schreiben:

Code: Alles auswählen

def get_target_input():
    while True:
        try:
            return int(input("Welche Zahl soll errechnet werden? "))
        except ValueError:
            print("Bitte nur Zahlen eingeben!")
Antworten