Archiv verlassen und diese Seite im Standarddesign anzeigen : Problem beim Umgang mit Array
Hallo, ich habe hier folgende Funktion, die leider nicht das macht, was sie machen soll.
Sinn ist folgender: Manche Waffen haben einen eigenen Parameter im Name (gekennzeichnet durch !...) und durch den bekommt der Kämpfer neue Skills. Alle Angriffe sind so quasi von Waffen abhängig.
Unterschiedliche Waffen, unterschiedliche Parameter, unterschiedliche Angriffe.
Funktioniert auch alles soweit, Problem ist die for-Schleife zum Schluss:
Diese löscht das 0. Element aus dem Array skills_neu UND aus @skills. dadurch wird das eigentlich 1. Element zum 0. und dann übersprungen.
Das ist aber Quatsch, der soll das Element doch nur aus skills_neu löschen...
Warum löscht der das auch aus @skills?
def angriffe_setzen(waffe)
skills_neu = @skills
if waffe.include?("!M")
# Machtwaffen
skills_neu.push(3) # Angriff
elsif waffe.include?("!S")
# Schusswaffen
skills_neu.push(2) # Angriff
skills_neu.push(5) # Gezielter Angriff
elsif !waffe.include?("")
# Normale Waffen
skills_neu.push(1) # Angriff
if waffe.include?("!r") or waffe.include?("!m")
skills_neu.push(4) # Gezielter Angriff
end
if waffe.include?("!m")
skills_neu.push(24) # Meucheln
end
end
for i in 0..@skills.size
if @skills[i] < 25
skills_neu.delete_at(i)
else
break
end
end
@skills = skills_neu
@skills.sort!
end
Außerdem bin ich für Optimierungsvorschläge dankbar ^_^"
Arrays werden nicht mit dem Call-By-Value sondern mit dem Call-By-Reference Prinzip übergeben.
Wenn du schreibst:
skills_neu = @skills
Dann bedeutet dies, dass das Array welches die Fähigkeiten enthält nicht nur durch die Variable "@skills" sondern auch durch "skills_neu" referenziert wird.
Du musst dir das so vorstellen als ob es in deinem Speicher irgendwo einen Abschnitt gibt welcher dieses Array darstellt. Irgendein Ding mit Daten.
Und die Variablen "@skills" und "skills_neu" sind nur Zeiger welche dem Programm sagen an welcher Stelle diese Daten liegen.
Wenn du den Code
skills_neu = @skills schreibst dann sagst du dem Programm er soll den Zeiger in "skills_neu" auf "@skills" setzen. Er wird keine Kopie von den Inhalten anlegen.
Für dein Vorhaben solltest du entweder
skills_neu = @skills.dup benutzen oder die Hilfsvariable "skills_neu" direkt vergessen und auf dem Array "@skills" arbeiten so wie es ist.
Was mich im Moment noch stutzig macht ist die eigenartige If-Bediengung
if waffe.include?("") Das macht nicht sonderlich viel Sinn den leeren String hier zu verwenden, jeder String enthält den leeren String. Das ist als ob du " if true " schreiben würdest nur aufwendiger...
Was ich ebenfalls nicht ganz verstehe ist folgender Part:
for i in 0..@skills.size
if @skills[i] < 25
skills_neu.delete_at(i)
else
break
end
end
Erstens muss die Signatur der For-Schleife eher so aussehen:
for i in 0...@skills.size
Da 2 Punkte ("..") und 3 Punkte ("...") eine andere Semantik haben.
Dies hier: "for i in 0..4" würde die Zahlen 0, 1, 2, 3 und 4 iterieren, wobei "for i in 0...4" nur über 0, 1, 2 und 3 laufen würde.
Zweitens ist die gesamte Bedingung innerhalb nicht ganz schlüssig.
Ich habe den Verdacht, dass du hier eher folgendes Beabsichtigst:
for i in 0...@skills.size
if @skills[i] < 25
skills_neu.delete_at(i)
else
next
end
end
Was mich im Moment noch stutzig macht ist die eigenartige If-Bediengung
if waffe.include?("") Das macht nicht sonderlich viel Sinn den leeren String hier zu verwenden, jeder String enthält den leeren String. Das ist als ob du " if true " schreiben würdest nur aufwendiger...
ich vermute mal, waffe.include?(val) ist Array#include?, laut Dokumentation hat String nichtmal eine include?-Funktion. Und ein leerer String als Element eines Array macht wieder Sinn.
String besitzt eine include? (http://www.ruby-doc.org/core-1.9.3/String.html#method-i-include-3F)-Methode.
Waffe wird wohl einfach auf $data_weapons[@weapon_id].name verweisen oder so.
Okay, erstmal vielen Dank.
Das die Arrays hier genauso funktionieren, wie in C hatte ich bereits vermutet... leider ist das bei dem komischen Ruby kaum zu erkennen, dass es dort auch klassische Zeiger gibt...
Die Variante mit @skills.dup hat übrigens auch nicht funktioniert. Eigentlich hat gar nichts funktioniert, der Effekt war bei jeder Änderung der selbe. Jetzt hab ich es auf die ganz grobe Art gemacht. (Siehe ganz unten.)
Was mich im Moment noch stutzig macht ist die eigenartige If-Bediengung
Code:
if waffe.include?("")
Das macht nicht sonderlich viel Sinn den leeren String hier zu verwenden, jeder String enthält den leeren String. Das ist als ob du " if true " schreiben würdest nur aufwendiger...
Das war ein Fehler aus einer alten Variante, den ich leider mitkopiert hatte. Ich hab den kurz, nachdem ich hier gepostet hatte, noch entfernt. Leider warst du da schon am schreiben ^_^" Das machte an der Stelle wirklich kein Sinn.
Am Ende steht allerding "if !waffe.include?("")", das macht tatsächlich Sinn. Shisu hat schon Recht. Die !x-Parameter stehen tatsächlich im Namen der Waffe und werden mit einer Funktion aus dem Namen raus genommen und in ein Array gespeichert. Dieses wird dann hier wieder an die Funktion übergeben.
Wenn kein Parameter im Namen steht, dann ist steht in dem Array nur der leere String. Und dann sollen nur alle Fertigkeiten entfernt werden. Passt also schon.
Das mit .. oder ... bei for-Schleifen war mir allerdings neu. Hab mich da schon in anderen Skripts öfter mal gewundert, warum da manchmal von 0 bis .size und manchmal 1 bis .size drin war und beides funktioniert hat. Dass es dann an dem . mehr oder weniger liegt, okay.
Das break war ebenfalls beabsichtigt, weil nur Skills mit einer ID < 25 entfernt werden. Alle anderen bleiben drin.
Wie gesagt, ich hab es jetzt auf die harte Tour gemacht, weil alles andere nicht mehr funktioniert hat und ich auch langsam frustriert war.
def angriffe_setzen(waffe)
if waffe.include?("!M")
# Machtwaffen
@skills.push(3) # Angriff
elsif waffe.include?("!S")
# Schusswaffen
@skills.push(2) # Angriff
@skills.push(5) # Gezielter Angriff
elsif !waffe.include?("")
# Normale Waffen
@skills.push(1) # Angriff
if waffe.include?("!r") or waffe.include?("!m")
@skills.push(4) # Gezielter Angriff
end
if waffe.include?("!m")
@skills.push(24) # Meucheln
end
end
i = 0
while i < @skills.size
if @skills[i] < 25
@skills.delete_at(i)
i -= 1
else
break
end
i += 1
end
@skills.sort!
end
Ich sage euch vielen Dank, soweit wieder alles schön.
Dann ist das Break keinesfalls richtig.
Hier ein Beispiel mit deinem Code:
@skills[0] = 4
@skills[1] = 27
@skills[2] = 9
Was würde nach der Ausführung von diesem Code-Abschnitt passieren:
i = 0
while i < @skills.size
if @skills[i] < 25
@skills.delete_at(i)
i -= 1
else
break
end
i += 1
end
Ganz einfach:
Schritt 1:
i = 0
@skills[0] == 4 < 25 => true
-> @skills.delete_at(0)
Schritt 2:
i = 1
@skills[1] == 27 < 25 => false
-> break
Schritt 3:
Wird nichtmehr weiter ausgeführt, da die While-Schleife hier bereits beendet worden ist.
Soweit ich dich verstanden habe soll dem aber nicht der Fall sein.
Es würde nämlich sonst nur bis zu dem ersten Vorkommen eines Skills mit einer ID < 25 gesucht werden und danach abgebrochen werden ohne Acht darauf zu geben wie viele weitere Skills noch kommen würden und welche ID diese haben.
Tatsächlich müsste dein Code an der Stelle wie folgt aussehen:
for i in 0...@skills.size
if @skills[i] < 25
@skills.delete_at(i)
end
end
Um alle Skills mit den ID's < 25 heraus zu filtern.
Alternativ könntest du auch zuerst @skills.sort! ausführen und danach iterieren bis du eine ID >= 25 gefunden hast.
Außerdem sollte der Code
elsif !waffe.include?("")
Soweit ich deine Absicht damit richtig verstanden habe vielleicht lieber folgendermaßen geschrieben werden:
elsif not waffe.empty?
Es ist definitiv leichter verständlich, ich nehme an auch effektiver von der Performance her aber das ist nicht empirisch von mir überprüft worden sondern lediglich eine Vermutung.
Noch einfacher kann man sich das Leben mit select machen. Select filtert ein Array nach einer bedingung, in deinem Beispiel wäre das:
@skills = @skills.select{|s| s >= 25}
Sollte eigentlich einfach verständlich sein, im Grunde gibst du in den { } select eine Funktion mit, nach der gefiltert werden soll. Mit |s| gibst du an, in welche Variable die einzelnen Werte gespeichert werden sollen, damit du damit irgendwas machen kannst (könnte auch ein beliebiger anderer Bezeichner sein), danach gibst du die Bedingung an. Alle Elemente die die Bedingung erfüllen bleiben drin, der Rest fliegt raus.
Ein Aufruf [1,26,5,30,2].select{|s| s >= 25} würde beispielsweise [26,30] zurückgeben. Dabei ist zu beachten, dass select ein neues Array zurückgibt und das Originalarray unverändert lässt. Ich hoffe, dass war jetzt etwas hilfreich und nicht mehr verwirrend ^^
Es gibt eigentlich recht viele hilfreiche Methoden auf Array von Ruby, es lohnt sich, da mal die Dokumentation (http://www.ruby-doc.org/core-1.9.3/Array.html) durchzulese - Da findet man einige Methoden, die einem das leben oft einfacher machen :)
Was ich noch etwas merkwürdig finde ist der Gesamteffekt deiner Methode. Du tust neue Werte in das Array, allerdings sind alle möglichen neuen Werte kleiner als 25, und werden dann wieder rausgefiltert? Oder sehe ich das falsch?
leider ist das bei dem komischen Ruby kaum zu erkennen, dass es dort auch klassische Zeiger gibt...Es gibt in Ruby auch keine klassischen Zeiger. Im Prinzip musst du dir das so vorstellen, dass alle Objekte in Ruby nur über "Zeiger" referenziert werden. Call-by-Value gibt es in Ruby nicht (wobei es bei konstanten wie z.B. Integer-Literalen keine Rolle spielt ob sie referenziert oder kopiert werden). Das Zeiger setze ich mal in Anführungszeichen, weil die Ruby-Variablen eigentlich nicht wie richtige Zeiger funktionieren. Du kannst zwar Referenzen auf ein Objekt halten, aber z.B. nicht auf eine Variable (so irrsinnige Sachen wie Pointer auf einen Pointer auf eine Variable, wie in C Gang und Gäbe, gibt es in Ruby nicht).
Wenn du von C kommst, dürfte das Konzept von Ruby erstmal gewöhnungsbedürftig sein. Aber ich denke es sollte klar sein, dass solche Konzepte wie Zeiger bei einer Scriptsprache einfach keinen Sinn machen, da sie viel zu fehleranfällig sind.
Zum Problem: Es wurde ja eigentlich schon alles gesagt. Schleifen (sowohl while als auch for) würde ich in Ruby nicht verwenden. Es gibt stattdessen die Iteratoren, die das viel einfacher und besser können. Statt select kannst du auch delete_if nehmen. Das wirkt in-place (gibt also keine Kopie des Arrays zurück).
@skills.delete_if {|id| id < 25}
Statt mehrfaches aufrufen von push kannst du auch << verwenden (also array << element1 << element2).
Oder sehe ich das falsch?
Ja, tust du und Cornix in der Hinsicht wohl ^_^ Das @skills-Array ist eigentlich immer sortiert, bevor meine Funktion aufgerufen wird. Deshalb sind die kleinen Werte am Anfang. Wenn ich dann neue pushe, kommen die ans Ende. Dadurch werden auch doppelte aussortiert, weil im Grunde ja nur die kleinen am Anfang verschwinden, am Ende aber bleiben.
Zugegeben, das ist alles andere als schön, aber solange es funktioniert.
Allerdings werde ich trotzdem mal .select oder .delete_if ausprobieren.
Dank auch an KD, das bringt mein Verständnis von Ruby nochmal ein Stückchen weiter. Scheint so, als wäre das wohl alles mehr Umgewöherei als ich vermutet habe. Aber solange man Hilfe bekommt ist ja alles in Ordnung.
Danke euch allen.
Ja, tust du und Cornix in der Hinsicht wohl ^_^ Das @skills-Array ist eigentlich immer sortiert, bevor meine Funktion aufgerufen wird. Deshalb sind die kleinen Werte am Anfang. Wenn ich dann neue pushe, kommen die ans Ende. Dadurch werden auch doppelte aussortiert, weil im Grunde ja nur die kleinen am Anfang verschwinden, am Ende aber bleiben.
Zugegeben, das ist alles andere als schön, aber solange es funktioniert.
Ah okay, dachte mir dass da etwas mehr dahintersteckt ;)
In dem Fall kommst du mit select oder delete_if auch nicht viel weiter, da diese Methoden dann auch deine neuen Werte, die du gerade gepusht hast, rausfiltern würden. Dann müsstest du select / delete_if aufrufen, bevor du die neuen Werte reinpusht.
Würde also folgenden Code geben:
def angriffe_setzen(waffe)
@skills.delete_if{|s| s<25}
if waffe.include?("!M")
@skills << 3
....
Potentielles Problem mit deinem Codeschnipsel mit while seh ich dann noch, wenn kein Skill mit einer Nummer größer als 25 drin ist würde er die neuen Werte trotzdem löschen. Also bei [1,25,1] würde [25,1] rauskommen, bei [1,24,2] aber ein leeres Array.
Und mal nebenbei, um sicher zu gehen, dass ich deine Absicht jetzt richtig verstanden habe:
Skills >= 25 sind permanente Skills, Skills < 25 sind waffenabhängig und wenn man die Waffe wechselt, vergisst man alle Skills (<25) der alten Waffe und kann nur noch die Skills der neuen Waffe.
Wird aus deiner Eingangsfrage wirklich nicht ganz klar, deshalb denke ich haben auch viele den Nutzen der Schleife am Ende missverstanden ;)
Potentielles Problem mit deinem Codeschnipsel mit while seh ich dann noch, wenn kein Skill mit einer Nummer größer als 25 drin ist würde er die neuen Werte trotzdem löschen. Also bei [1,25,1] würde [25,1] rauskommen, bei [1,24,2] aber ein leeres Array.
Stimmt eigentlich, aber das Problem war sogar schon unbewusst dadurch gelöst, dass es einen Standard-Skill gibt den jeder Charakter von Anfang an hatte.
Und mal nebenbei, um sicher zu gehen, dass ich deine Absicht jetzt richtig verstanden habe:
Skills >= 25 sind permanente Skills, Skills < 25 sind waffenabhängig und wenn man die Waffe wechselt, vergisst man alle Skills (<25) der alten Waffe und kann nur noch die Skills der neuen Waffe.
Wird aus deiner Eingangsfrage wirklich nicht ganz klar, deshalb denke ich haben auch viele den Nutzen der Schleife am Ende missverstanden ;)
Bingo ^_^
Ich war beim erklären von Problemen noch nie sehr gut, weil mir beim erklären meist potenzielle Lösungen in den Kopf komen, die ich dann entweder direkt ausprobieren oder drüber nachdenken muss. Daher komm ich beim erklären oft durcheinander oder bekomme verkorkste Erklärungen hin.
Aber auf lang oder kurz wusstet ihr ja worum es geht.
Und da ich jetzt auch endlich mal Zeit habe, werde ich es jetzt auch mal umsetzen...
The_Burrito
12.12.2011, 19:36
Ich finde dein Konstrukt weder sinnhaft noch sonderlich einfach gestaltet.
Warum speicherst du permanente Skills und Waffenskills nicht in zwei verschiedenen Arrays? Dann musst du dich weniger damit rumplagen wo was steht und welche ID ein Skill hat.
@Rettan @Cornix
Dann ist das Break keinesfalls richtig.
Hier ein Beispiel mit deinem Code:
@skills[0] = 4
@skills[1] = 27
@skills[2] = 9
Was würde nach der Ausführung von diesem Code-Abschnitt passieren:
i = 0
while i < @skills.size
if @skills[i] < 25
@skills.delete_at(i)
i -= 1
else
break
end
i += 1
end
Ganz einfach:
Schritt 1:
i = 0
@skills[0] == 4 < 25 => true
-> @skills.delete_at(0)
Schritt 2:
i = 1
@skills[1] == 27 < 25 => false
-> break
Die Schleife so zu benutzen ist eh semantisch falsch, wenn man keinen iterator benutzt.
Schritt 1 löscht @skills[0]
Ruby unshiftet das Array jetzt - was vorher @skills[1] war, rutscht an die freigewordene Stelle von @skills[0]
Schritt 2 würde jetzt also mit @skills[1]=9 arbeiten - das ursprüngliche @skills[1]=27 würde ignoriert werden. In dem Fall der gewünschte Effekt, aber vertausche 9 und 27 in dem Beispiel, und du hast einen unerwünschten Effekt. Schritte dagegen sind i in dem Fall einer Löschung runterzusetzen oder gleich iteratoren zu nehmen
@KD
Du magst recht haben, aber je nachdem wie eingearbeitet man ist und woher man kommt, ist man halt gewohnt mit while und for zu arbeiten - seit dem ich sie kenne nutze ich gerne Iteratoren, aber nehm auch gern hier und da eine for - aus gewohnheit, weil ich es dann besser lesen kann und weil ich auch nicht alle iteratoren kenne. Gerader der lesbarkeithalber bevorzuge ich for-Schleifen sogar vor Array#each und Numeric#times - solange es hinterher funktioniert^^.
Zumindest solange es bei RGSS bleibt, wenn man vernünftige Programme mit Ruby schreiben will ist es durchaus korrekt, sich das erst garnicht anzugewöhnen. Aber ein Großteil der User hier gehört wohl eher zu den Leuten, die damit nicht ausserhalb des RGSS arbeiten wollen.
@include?("")-Thema
Hm, okay, ich hatte es vermutet dass String die Funktion hat, ich konnte es mir nur nicht bestätigen. Wenn es also wirklich um String#include? geht, dann,Rettan, ergibt weder .include?("") noch !.include?("") Sinn - wenn das Element wirklich ein String ist, ist der leere String immer enthalten - da würde sich eher ein Codezeichen für Anbieten, sei es irgendein sonst unbenutztes Zeichen inkl. dem Leerzeichen
Powered by vBulletin® Version 4.2.3 Copyright ©2025 Adduco Digital e.K. und vBulletin Solutions, Inc. Alle Rechte vorbehalten.