Simplify haskell function

8

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)]

    
infotoni91 03.09.2017, 16:37
quelle

4 Antworten

8

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:

%Vor%

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.

    
Code-Apprentice 03.09.2017, 16:53
quelle
5

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:

  • tue so, als ob wir echten Code schreiben würden und sorge sich über Asymptotik, Listen als Arrays ( !! , und length ) und Sets ( elem ) zu behandeln und stattdessen richtige Array- und Set-Datenstrukturen zu verwenden
  • verschiebt die Überprüfung Ihrer Grenzen (und mögliche Nullwertüberprüfung) in eine neue Nachschlagefunktion (das Ziel besteht darin, wenn möglich nur eine einzige ... = area -Klausel zu haben
  • )
  • erwägen Verbesserungen des Algorithmus: Können wir vermeiden, rekursiv die Zelle zu überprüfen, von der wir gerade algorithmisch kommen? können wir vermeiden, area ganz zu übergeben (was unsere Suche angenehm faul / "produktiv" macht)?
jberryman 03.09.2017 22:15
quelle
2

Hier ist meine Meinung:

%Vor%

Was ich getan habe

  • foldr über den Nachbarn, wie einige Kommentatoren vorgeschlagen haben.
  • Da die Reihenfolge der Punkte keine Rolle spielt, verwende ich eine Set anstelle einer Liste, damit sie semantisch besser passt und schneller startet.
  • Benannte einige hilfreiche Zwischenabstraktionen wie Point und neighbors .
  • Eine bessere Datenstruktur für den Lebensraum wäre auch gut, da Listen linearer Zeitaufwand sind, vielleicht ein 2D 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

%Vor%

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%     
luqui 04.09.2017 04:16
quelle
1

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.

    
Redu 05.09.2017 19:58
quelle

Tags und Links