2D-Liste mit dynamischen Max-Werten zum Trennen in neue Liste

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
pyBlub
User
Beiträge: 2
Registriert: Samstag 27. Mai 2023, 08:55

Hallo an alle Forenuser,

ich selber bin erst vor kurzem zur Sprache Python gekommen, hab aber schon etwas Erfahrung in Programmierung - wenn auch eher im Bereich Hobby im Solo-Team.
Wie auch immer. Zur Zeit hänge ich an folgenden Projekt / Problem:


Ich habe eine 2D-Liste, die wie folgt aussieht:

Code: Alles auswählen

MAX = 5
LIST = []
LIST.append(
	{
	'Name'	:'Ordner First',
	'FPI'	:2,
	'File':
		[
		'first random file 01',
		'first random file 02',
		'first random file 03',
		'first random file 04',
		'first random file 05',
		'first random file 06',
		'first random file 07',
		'first random file 08',
		'first random file 09',
		'first random file 10',
		]
	}
	)
LIST.append(
	{
	'Name'	:'Ordner dumdidum',
	'FPI'	:2,
	'File':
		[
		'dumdidum random file 01',
		'dumdidum random file 02',
		'dumdidum random file 03',
		'dumdidum random file 04',
		'dumdidum random file 05',
		]
	}
	)
LIST.append(
	{
	'Name'	:'Ordner random',
	'FPI'	:2,
	'File':
		[
		'random random file 01',
		'random random file 02',
		'random random file 03',
		'random random file 04',
		'random random file 05',
		]
	}
	)

Ganz oben im Code ist eine Variable, die als Max-Wert für die neue Liste dient.
Jede Liste selber hat nochmal einen eigenen Max-Wert (FPI = File Per Index).

Grob:
Es soll eine neue Liste erstellt werden, die Maximal aus 5 Einträgen pro Index besteht. Die 5 Einträge sollen jeweils mit den Einträgen von 'File' gefüllt werden - aber maximal den 'FPI'-Anzahl.


Am Beispiel mit MAX-Wert '5', und jeder 'FPI'-Wert als '2'

Code: Alles auswählen

(
	[
	'first random file 01',
	'first random file 02',
	'dumdidum random file 01',
	'dumdidum random file 02',
	'random random file 01',
	],
	[
	'first random file 03',
	'first random file 04',
	'dumdidum random file 03',
	'dumdidum random file 04',
	'random random file 02',
	],
	[
	'first random file 05',
	'first random file 06',
	'dumdidum random file 05',
	'random random file 03',
	],
	[
	'first random file 07',
	'first random file 08',
	'random random file 04',
	'random random file 05',
	],
	[
	'first random file 09',
	'first random file 10',
	],
)

Ein weiteres Beispiel mit MAX-Wert '3', 'FPI'-Wert bleibt '2':

Code: Alles auswählen

(
	[
	'first random file 01',
	'first random file 02',
	'dumdidum random file 01',
	],
	[
	'first random file 03',
	'first random file 04',
	'dumdidum random file 02',
	],
	[
	'first random file 05',
	'first random file 06',
	'dumdidum random file 03',
	],
	[
	'first random file 07',
	'first random file 08',
	'dumdidum random file 04',
	],
	[
	'first random file 09',
	'first random file 10',
	'dumdidum random file 05',
	],
	[
	'random random file 01',
	'random random file 02',
	],
	[
	'random random file 03',
	'random random file 04',
	],
	[
	'random random file 05',
	],
)

Wenn noch unklar ist, oder weitere Beispiele benötigt werden - einfach sagen.


Mein bisheriges Vorgehen war, das ich erstmal die Liste durchgehe und anhand des 'FPI'-Wertes trenne:

Code: Alles auswählen

LIST = []
LIST.append(
	{
	'Name'	:'Ordner First',
	'FPI'	:2,
	'File':
		[
		'first random file 01',
		'first random file 02',
		'first random file 03',
		'first random file 04',
		'first random file 05',
		'first random file 06',
		'first random file 07',
		'first random file 08',
		'first random file 09',
		'first random file 10',
		]
	}
	)
LIST.append(
	{
	'Name'	:'Ordner dumdidum',
	'FPI'	:2,
	'File':
		[
		'dumdidum random file 01',
		'dumdidum random file 02',
		'dumdidum random file 03',
		'dumdidum random file 04',
		'dumdidum random file 05',
		]
	}
	)
LIST.append(
	{
	'Name'	:'Ordner random',
	'FPI'	:2,
	'File':
		[
		'random random file 01',
		'random random file 02',
		'random random file 03',
		'random random file 04',
		'random random file 05',
		]
	}
	)



nID		= 0
MAX		= 5


for nList in LIST:
	nID		= nID + 1
	nName	= nList['Name']
	nFPI	= nList['FPI']	# = File Per Index
	nFile	= nList['File']


	if nID > 1:
		print()
	print('Name:\t' + str(nName))
	print('FPI:\t' + str(nFPI))
	print('ID:\t\t' + str(nID))


	vFileCount	= 0
	vFileIndex	= 0


	if nFPI > MAX:	vFPI = MAX
	else:			vFPI = nFPI


	for vName in nFile:
		vFileCount		= vFileCount + 1


		if vFileCount > vFPI:
			vFileCount	= 1
			vFileIndex	= vFileIndex + 1


		print(
			'\t\t'
			+ '\t' + str(vFileIndex)
			+ '\t' + str(vFileCount)
			+ '\t' + str(vName)
			)

Das klappt ja auch soweit ganz gut.
Dann hab ich versucht den MAX-Wert mit einzubeziehen.


Hatte das eine oder andere Versucht. Bspw. eine temporäre Liste, wo dann anhang des Indexes ein Zähler drin ist, um dann zu zählen, ob MAX für Index X erreicht wurde.
Aber irgendwie hatte das nie so richtig funktioniert - oder ich bin zu doof dafür ô.O


Meine Frage wäre halt, ob man da mal gucken kann und einen Tipp oder sowas hätte.
Eine "fertige" Lösung wäre aber auch nicht schlecht ;o


Vielleicht mach ich mir ja auch viel zu viel Arbeit und in Python gibt es "relativ" einfache Methoden.
Oder sowas ist ohne weiteres gar nicht möglich - keine Ahnung.


Wenn noch irgendetwas fehlt oder eine Frage besteht - einfach sagen.


Ich danke schon mal allen für die Hilfe.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Eingerückt wird immer mit 4 Leerzeichen pro Ebene, keine Tabs.
Variablennamen werden komplett klein geschrieben und sind sprechend. Sie enthalten keine kryptischen Präfixe wie n oder v. Zwischen Operatoren ist maximal ein Leerzeichen. Dein Code ist so zerstückelt, dass man die einzelnen Teile erst mühsam zusammensuchen muß.
Strings stückelt man nicht mit + zusammen, sondern benutzt f-Strings.
Wenn man einen Index zusätzlich in einer for-Schleife braucht, dann benutzt man enumerate.

Code: Alles auswählen

for index, entry in enumerate(LIST):
    if index:
        print()
    print(f'Name:\t{entry["Name"]}')
    print(f'FPI:\t{entry["FPI"]}')
    print(f'ID:\t{index}')
    for fileindex, name in enumerate(entry["File"]):
        fileindex, filecount = divmod(fileindex, entry["FPI"])
        print(f'\t\t\t{fileindex}\t{filecount + 1}\t{name}')
Da Du Deine File-Listen in mehreren Schritten durchgehen willst, verwende einfach Iteratoren, denn die kann man nach und nach abarbeiten. Dann kannst Du eine Funktion schreiben, die unter berücksichtigung von Deinem FPI aus jedem Iterator die passende Anzahl an Einträgen holt, bis Du insgesamt MAX Einträge hast.
Dann mußt Du diese Funktion nur noch so oft aufrufen, bis alle Einträge aller Listen abgearbeitet sind:

Code: Alles auswählen

from itertools import islice, chain
from functools import partial

LIST = [
    {
    'Name': 'Ordner First',
    'FPI': 2,
    'File':
        [
        'first random file 01',
        'first random file 02',
        'first random file 03',
        'first random file 04',
        'first random file 05',
        'first random file 06',
        'first random file 07',
        'first random file 08',
        'first random file 09',
        'first random file 10',
        ]
    },
    {
    'Name': 'Ordner dumdidum',
    'FPI': 2,
    'File':
        [
        'dumdidum random file 01',
        'dumdidum random file 02',
        'dumdidum random file 03',
        'dumdidum random file 04',
        'dumdidum random file 05',
        ]
    },
    {
    'Name': 'Ordner random',
    'FPI': 2,
    'File':
        [
        'random random file 01',
        'random random file 02',
        'random random file 03',
        'random random file 04',
        'random random file 05',
        ]
    },
]

def get_block(iterators, amount):
    return list(islice(
        chain.from_iterable(
            islice(iterator, amount)
            for amount, iterator in iterators
        ),
        amount
    ))

def main():
    amount = 5
    iterators = [
        (entry['FPI'], iter(entry['File']))
        for entry in LIST
    ]
    for block in iter(partial(get_block, iterators, amount), []):
        print(block)

if __name__ == '__main__':
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@pyBlub: Die Formatierung ist komisch. In Python richtet man Code nicht vertikal so aus, dass er an Zuweisungen und ähnlichem ausgerichtet ist. Das führt früher oder später nur dazu das man Änderungen aus rein optischen Gründen vornehmen muss, obwohl sich inhaltlich an den betroffenen Zeilen gar nichts geändert hat. Die tatsächlichen Änderungen gehen dann in Diffs und ähnlichem in diesem Rauschen unter.

Blöcke stehen grundsätzlich eingerückt in einer neuen Zeile nach dem ``:``, auch wenn sie nur eine Zeile lang sind.

Lesestoff zu solchen Fragen: Style Guide for Python Code.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Namen sollten keine kryptischen Abkürzungen enthalten oder gar nur daraus bestehen. Der Name soll dem Leser vermitteln was der Wert dahinter im Programm bedeutet, nicht zum rätseln zwingen. Mir ist weder klar wofür der Präfix `n` stehen soll, noch wofür `v` stehen soll. Spätestens bei `nFPI` und `vFPI` verwirrt das dann doch.

Grunddatentypen haben nichts in Namen verloren. Den Typen ändert man gar nicht so selten mal während der Programmentwicklung und dann muss man überall im Programm die betroffenen Namen ändern, oder man hat falsche, irreführende Namen im Quelltext. `LIST` ist als Name deutlich zu generisch.

Eine leere Liste dann mit festen Werten mit mehreren `append()`-Aufrufen zu füllen macht keinen Sinn. Die Werte schreibt man da gleich in die literale Liste.

Wenn man einen Kommentar braucht um einen Namen zu erklären, sollte der Name in der Regel besser sein. Statt zu `fpi` zu kommentieren, dass das ``File Per Index`` heissen soll, nimmt man besser `file_per_index` als Namen. Schon kann man sich den Kommentar sparen, und man weiss *an jeder Stelle* wo der Name verwendet wird, wofür er steht.

`nFile` sollte besser `filenames` heissen. Dateien sind Objekte die Methoden wie `read()` oder `write()` und `close()` haben. Dateien und Dateinamen sind unterschiedliche Dinge.

Statt `nID` manuell hoch zu zählen, verwendet man `enumerate()`. Das gilt auch für `vFileIndex`.

Das zusammenstückeln von Zeichenketten und Werten mittels ``+`` und `str()` ist eher BASIC als Python. Dafür gibt es die `format()`-Methode auf Zeichenketten und f-Zeichenkettenliterale.

Das ``if``/``else`` bei `vFPI` (was eigentlich auch einfach `nFPI` beziehungsweise `files_per_index` heissen sollte) ist unnötig, weil es die `min()`-Funktion gibt.

Überarbeitet könnte das dann so aussehen (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
MAX = 5
ITEMS = [
    {
        "Name": "Ordner First",
        "FPI": 2,
        "File": [
            "first random file 01",
            "first random file 02",
            "first random file 03",
            "first random file 04",
            "first random file 05",
            "first random file 06",
            "first random file 07",
            "first random file 08",
            "first random file 09",
            "first random file 10",
        ],
    },
    {
        "Name": "Ordner dumdidum",
        "FPI": 2,
        "File": [
            "dumdidum random file 01",
            "dumdidum random file 02",
            "dumdidum random file 03",
            "dumdidum random file 04",
            "dumdidum random file 05",
        ],
    },
    {
        "Name": "Ordner random",
        "FPI": 2,
        "File": [
            "random random file 01",
            "random random file 02",
            "random random file 03",
            "random random file 04",
            "random random file 05",
        ],
    },
]


def main():
    for item_number, item in enumerate(ITEMS, 1):
        name = item["Name"]
        files_per_index = item["FPI"]  # = File Per Index
        filenames = item["File"]

        print()
        print(f"Name:\t{name}")
        print(f"FPI:\t{files_per_index}")
        print(f"ID:\t\t{item_number}")

        files_per_index = min(files_per_index, MAX)
        file_counter = 0
        file_index = 0
        for file_index, filename in enumerate(filenames, 1):
            file_counter += 1
            if file_counter > files_per_index:
                file_counter = 1
                file_index += 1

            print(f"\t\t\t{file_index}\t{file_counter}\t{filename}")


if __name__ == "__main__":
    main()
Bevor Du da anfängst selbst etwas mit einem Haufen Zählvariablen zu basteln, würde ich erst einmal schauen ob sich das mit den Mitteln aus `itertools` und `more_itertools` ausdrücken lässt.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
pyBlub
User
Beiträge: 2
Registriert: Samstag 27. Mai 2023, 08:55

Guten Morgen euch beiden,

erstmal vielen Dank für die recht zügige Beantwortung zu meinen Problem. Ich bin als stiller Leser in Foren aus meiner Anfangszeit zu 90% anderes gewöhnt.
Auch der Code von dir Sirius3 funktioniert wunderbar - vielen Dank. Ich hätte etwas umfangreiches, komplexes erwartet, so mit 100-200 Zeilen Code, aber das ist ja, gefühlt 2 Zeilen Code ;o Ich versuch später mal in die Doku zu gucken was die einzelnen Funktionen so machen.


Was eure Anmerkungen zu meinen Code oben angeht:

Ich werd versuchen dies umzusetzen. Muss aber auch sagen, das sich das eine oder andere (z.B. Tabs/Leerzeichen [siehe unten], oder halt Variablennamen) einfach über die Jahre so eingestellt hat und nicht so von heute auf morgen geändert bekomme.
Und ja, ich weis. Mein Code-Stil schmeckt nicht jedem und entspricht nicht irgendwelchen Normen. Ich muss aber auch sagen, das ich über die Jahre (ich fing mit PHP an) auch nie wirklich brauchbare Artikel gefunden habe, die z.B. Wartbarkeit von Code, oder praktische Vorgehensweisen erläutern. Ich fand immer nur Artikel, die das Thema anreisen oder Konzepte andeuten.


Was die Tabs/Leerzeichen angeht:
Gibt es hierfür einen (guten) Grund warum man (4) Leerzeichen nimmt? Meine ersten Gehversuche mit Programmierung war mit PHP. Anfangs hab ich auch immer mit Leerzeichen die Einrückungen gemacht. Irgendwann hatte ich einen Artikel zum Thema "Einrückungen Tabs vs. Leerzeichen: Vor- und Nachteile" gelesen und mit der Zeit auf Tabs umgestellt. Der Hauptgrund war, das ja nicht jeder eine Einrückung von 4 Leerzeichen bevorzugt, und so jeder halt seinen empfinden die Einrückungen gestalten kann, ohne den ganzen Code anzufassen. Die meisten Text-Editoren / IDE's können den Tab-Wert ja nach belieben einstellen.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@pyBlub: Tabs vs. Leerzeichen bekommst Du von heute auf morgen geändert: Das lässt sich ja automatisieren Tabs in Leerzeichen umzuwandeln, zum Beispiel auch automatisiert beim speichern. Und so ziemlich alle Editoren/IDEs können so eingestellt werden, dass sie sich bezüglich Tab-Taste und Löschtaste auch mit Leerzeichen so verhalten als wären das Tabs.

Der gute Grund vier Leerzeichen zu nehmen ist dass das alle so machen (ist Teil vom Style-Guide) und Einrückung in Python relevanter Teil der Syntax ist. Wenn jeder was eigenes macht, kann man nicht zusammenarbeiten ohne das dadurch irgendwann Probleme/Fehler entstehen. Und zusammenarbeiten ist auch Code von anderen kopieren oder Code zum ausprobieren/fehler suchen für andere in ein Forum zu posten. Wenn man helfen will, ist das eine zusätzliche Hürde wenn man den Code erst mal an die eigenen Editoreinstellungen anpassen muss, oder die Editoreinstellungen an den Code, bevor man damit arbeiten kann um einen Fehler zu finden oder eine Lösung auszuprobieren.

Tabs können bei Python auch problematisch werden wenn man irgendwo Tabs und Leerzeichen gemischt hat. Ein Leerzeichen + Tab sieht optisch genau so aus wie ein Tab — für den Compiler ist das aber unterschiedlich eingerückt. So bekommt man entweder Syntaxfehler die nicht auf den ersten Blick offensichtlich sind, oder es kann passieren das man Code hat der sich anders verhält als es optisch aussieht.

Das ein Tab nicht einer festen Anzahl von Leerzeichen entspricht ist auch ein Problem. Klar kann man das in Editoren/IDEs einstellen, aber an vielen anderen Stellen kann man das eben nicht. Zum Beispiel im Web-Browser, im Terminalfenster, oder im E-Mail-Client. Auch dort landet Code oft, entweder direkt/komplett, oder in Diffs. Und selbst wenn man es einstellen kann, sobald es um Tabs *innerhalb* von Zeilen geht, statt nur am Anfang, nützt das nicht immer was. Da gibt es dann trotzdem Fälle wo es nur ordentlich aussieht wenn man die gleiche Einstellung wie der Autor des Codes verwendet.

Ich persönlich kümmere mich um Einrückung und Formatierung gar nicht selbst, das lasse ich machen. Befreit einen davon über Formatierungsfragen nachzudenken und es ist immer konsistent und hält sich an den Style-Guide und sieht auch so aus wie bei allen anderen die den wohl beliebtesten Formatierer für Python verwenden: Black. Der hat genau *zwei* Einstellungen die man als Benutzer machen kann. Wem das immer noch zu viel ist, kann Shed verwenden. Der baut auf Black auf und hat gar keine Einstellungen mehr. 😎
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Antworten