Ich habe wirklich Probleme mit Haskell atm.
Ich habe fast sechs Stunden gebraucht, um eine Funktion zu schreiben, die das macht, was ich will. Leider bin ich nicht zufrieden mit dem Aussehen.
Könnte mir bitte jemand Hinweise geben, wie ich es umschreiben kann?
%Vor%Die Funktion get ist ein [[generic_type]] (repräsentiert eine Landschaftskarte) und durchsucht den vollständig verbundenen Bereich um einen Punkt, der nicht dem angegebenen nullValue entspricht.
Beispiel:
Wenn die Funktion wie folgt aufgerufen wird:
get_connected_area [[0,1,0],[1,1,1],[0,1,0],[1,0,0]] (1,1) [] 0
Das bedeutet wörtlich
0 1 0
1 1 1
0 1 0
1 0 0
Repräsentiert eine Karte (wie Google Maps). Start vom Punkt (Koordinaten) (1,1) Ich möchte alle Koordinaten der Elemente erhalten, die einen verbundenen Bereich mit dem gegebenen Punkt bilden.
Das Ergebnis sollte daher lauten:
0 1 0
1 1 1
0 1 0
1 0 0
Und der entsprechende Rückgabewert (Liste der Koordinaten von fettgedruckten 1s):
[(2,1),(0,1),(1,2),(1,0),(1,1)]
Eine kleine Änderung ist, dass Sie Mustervergleiche für die Variable point
verwenden können. Dies bedeutet, dass Sie (x, y)
anstelle von point
in der Funktionsdeklaration verwenden können:
Jetzt überall wo du fst point
hast, setze einfach x
, und überall wo du snd point
hast, setze y
.
Eine weitere Modifikation besteht darin, mehr Variablen für Teilausdrücke zu verwenden. Dies kann bei den verschachtelten rekursiven Aufrufen hilfreich sein. Nehmen Sie beispielsweise eine Variable für den innersten verschachtelten Aufruf vor:
%Vor% Fügen Sie nun foo
anstelle des Aufrufs ein. Diese Technik kann nun für den "neuen" innersten Ruf wiederholt werden. (Beachten Sie, dass Sie einen aussagekräftigeren Namen als foo
wählen sollten. Vielleicht down
?)
Beachten Sie, dass not (x >= y)
dasselbe ist wie x < y
. Verwenden Sie dies, um alle Bedingungen zu vereinfachen. Da diese Bedingungen testen, ob ein Punkt innerhalb eines Begrenzungsrechtecks liegt, kann der größte Teil dieser Logik für eine Funktion isIn :: (Int, Int) -> (Int, Int) -> (Int, Int) -> Bool
berücksichtigt werden, wodurch get_connected_area
lesbarer wird.
Dies wäre mein erster schneller Durchlauf durch die Funktion und eine Art Minimum, die eine Codeüberprüfung (nur in Bezug auf den Stil) bestehen könnte:
%Vor% Wir binden habitat
und nullValue
einmal auf der obersten Ebene (was die rekursive Arbeit verdeutlicht), entfernen Indirection in den Prädikaten, benutzen camel-case (Underdashes obskure wo Funktion appliziert wird), ersetze generic_type
mit a
(eine laute Variable zu verwenden, hat tatsächlich den gegenteiligen Effekt von dem, den du beabsichtigst; ich versuche herauszufinden, welche spezielle Semantik du herausrufst, wenn das Interessante daran liegt, dass der Typ dies nicht tut wichtig (solange es für Gleichheit verglichen werden kann)).
An dieser Stelle gibt es viele Dinge, die wir tun können:
!!
, und length
) und Sets ( elem
) zu behandeln und stattdessen richtige Array- und Set-Datenstrukturen zu verwenden ... = area
-Klausel zu haben area
ganz zu übergeben (was unsere Suche angenehm faul / "produktiv" macht)? Hier ist meine Meinung:
%Vor%Was ich getan habe
foldr
über den Nachbarn, wie einige Kommentatoren vorgeschlagen haben. Set
anstelle einer Liste, damit sie semantisch besser passt und schneller startet. Point
und neighbors
. Data.Array
- aber soweit es diese Funktion interessiert, brauchen Sie nur eine Indexierungsfunktion Point -> Bool
(out Die Werte von bounds und null werden gleich behandelt. Daher habe ich den Datenstrukturparameter durch die Indexierungsfunktion selbst ersetzt (dies ist eine übliche Transformation in FP). Wir können sehen, dass es auch möglich wäre, die neighbors
-Funktion wegzuspalten, und dann würden wir zu einer sehr allgemeinen Graph-Traversal-Methode kommen
in dem Sie getConnectedArea
schreiben könnten. Ich empfehle das zu Schulungszwecken - links als Übung.
BEARBEITEN
Hier ist ein Beispiel dafür, wie man die Funktion in (fast) Ihrer alten Funktion aufruft:
%Vor%OK, ich werde versuchen, Ihren Code zu vereinfachen. Aber es gibt bereits gute Antworten und deshalb werde ich das mit einem etwas konzeptionelleren Ansatz angehen.
Ich glaube, Sie könnten bessere Datentypen wählen. Zum Beispiel scheint Data.Matrix
einen idealen Datentyp in der Ort Ihres [[generic_type]]
-Typs. Auch für Koordinaten würde ich keinen Tupel-Typ wählen, da der Tupel-Typ dazu da ist, verschiedene Typen zu packen. Es ist Funktor und Monad-Instanzen sind nicht sehr hilfreich, wenn es als ein Koordinatensystem gewählt wird. Aber da es scheint, dass Data.Matrix
nur mit Tupeln als Koordinaten glücklich ist, werde ich sie behalten.
OK, der umformulierte Code ist wie folgt;
%Vor% Zunächst ist zu beachten, dass der Matrix
-Datentyp Index 1
ist. Also haben wir unseren Mittelpunkt bei (2,2)
.
Das zweite ist ... wir haben eine Liste von Funktionen mit drei Elementen, die als [id, subtract 1, (+1)]
definiert sind. Die enthaltenen Funktionen sind alle Num a => a -> a
Typ und ich brauche sie, um die umgebenden Pixel der gegebenen Koordinate einschließlich der gegebenen Koordinate zu definieren. Also haben wir eine Linie wie wenn wir es getan hätten;
[1,2,3] >>= \x -> [1,2,3] >>= \y -> return [x,y]
würde [[1,1],[1,2],[1,3],[2,1],[2,2],[2,3],[3,1],[3,2],[3,3]]
ergeben, was in unserem Fall anstelle der Zahlen 1,2 und 3 2 Kombinationen aller Funktionen ergeben würde.
Was wir dann nacheinander mit einer kaskadierenden Anweisung auf unsere gegebene Koordinate anwenden
%Vor%liefert alle benachbarten Koordinaten.
Dann ist es nichts weiter als das Filtern der benachbarten Filter, indem überprüft wird, ob sie nicht gleich dem nil
-Wert innerhalb der Ausgangsmatrix sind.
Tags und Links haskell