LesezeichenAbonnierenRSS-Feed abonnieren
AndreasMenrath
Pyrite | Level 9

Hallo zusammen,

zur Abwechslung einmal eine kleine Aufgabe an alle CoDe SAS Mitglieder.

Auf Spiegel Online fand ich ein spannendes Rätsel unter: http://www.spiegel.de/wissenschaft/mensch/raetsel-der-woche-wie-viele-schliessfaecher-stehen-offen-a...

Dort wurde eine mathematische Lösung vorgestellt, aber es wäre durchaus auch eine algorithmische Lösung des Problems denkbar.

Ich würde daher das Problem als kleine Code Kata für alle interessierten SAS Programmierer vorschlagen. Hierbei handelt es sich um eine kleine Übungsaufgabe, um neue Techniken oder Programmiermethoden auszuprobieren und zu üben.

Aus eigener Erfahrung kann ich bestätigen, dass man bereits durch eine kleine Code Kata schon einige interessante Einsichten gewinnen kann. Den größten Vorteil kann man jedoch aus einer Code Kata ziehen, wenn man seine Ergebnisse mit anderen Anwendern austauscht.

Mein Vorschlag wäre daher ein SAS Makro zu erstellen, dass die Anzahl der offenen Schließfächer und die Nummern der geöffneten Fächer als Bericht erzeugt.

Die Anzahl der Schließfächer soll per Parameter konfigurierbar sein.

Somit ergibt sich die folgende Makrosignatur:

%macro reportOpenLockers(countOfLockerDoors);

  /* ... */

%mend reportOpenLockers;

Aufgerufen werden kann das Makro dann beispielsweise für 100 Schließfächern so:

%reportOpenLockers(100);

Sie können sich für diese Aufgabe selbst ein Ziel setzen und beispielsweise das Makro besonders elegant und lesbar implementieren oder die Businesslogik in unterschiedlichen SAS Technologien wie Arrays, Hashtables oder PROC DS2 umzusetzen.

Ich werde dieses Makro selbst implementieren und nächsten Freitag (17.7.) an dieser Stelle meine Lösung vorstellen.

Alle interessierten Programmierer können aber ebenso hier ihre Lösung vorstellen und diskutieren.

Ich bin auf die Ergebnisse und Dikussionen gespannt.

Viele Grüße,

Andreas Menrath

8 ANTWORTEN 8
CKothenschulte
Obsidian | Level 7

Hallo Herr Menrath,

sehr gute Idee mit dem Rätsel!

Anbei meine Lösung mit einem Data-Step. Ein Report kommt nicht heraus.

Die zum Schluss geöffneten Schließfächer werden ins Log ausgegeben.

Der Stand nach jedem "Rundgang" wird in einer Tabelle gespeichert und kann von dort aus ausgegeben werden.

Entschieden habe ich mich für die Speicherung des Zustands in einer(!) alphanumerischen Variablen, um den Befehl (und nicht die Funktion) substr nutzen zu können.

Die Umsetzung hat Spaß gemacht. Ich konnte mal wieder sehen, wie schnell und kurz man Fragestellungen in SAS in einem einzigen Data-Step lösen kann.

Sicherlich kann man das Makro noch schöner, ausführlicher und abgesicherter umsetzen...

Schönen Abend!

%macro reportOpenLockers(countOfLockerDoors,zu=X,auf=O);


data SCHLIESSFACH (drop=i j);

      attrib SCHLIESSFACH format=$&countOfLockerDoors..;

      /* Schleife i: Rundgänge des Hausmeisters */

      do i=1 to &countOfLockerDoors.;

           /* Schleife j: Je Rundgang alle Schließfächer */

           do j=i by i to &countOfLockerDoors.;

                if mod(j,i)=0 then

                /* jedes i-te Schließfach ändern... */

                /* ...wenn zu (oder beim 1. Mal unbekannt) dann auf */

                     if substr(SCHLIESSFACH,j,1) in (' ',"&ZU.") then do;

                          substr(SCHLIESSFACH,j,1) = "&AUF.";

                     end;

                     else do;

                     /* ...wenn auf dann zu */

                          substr(SCHLIESSFACH,j,1) = "&ZU.";

                     end;

           end; /* Ende Schleife j */

           /* Ausgabe des aktuellen Stands in die Tabelle */

           output;

      end; /* Ende Schleife i */

      /* Ausgabe der noch offenen Schließfächer ins Log */

      do i=1 to &countOfLockerDoors.;

           if substr(SCHLIESSFACH,i,1) in ("&AUF.") then do;

                put i=;

           end;

      end;

run;


%mend reportOpenLockers;


%reportOpenLockers(100);

Sven_
Fluorite | Level 6

Hallo,

auch ich habe das gerade eben mal ausprobiert, weil ich recht neu in SAS bin. Ist auf jeden Fall eine gute Übung. Hat Spass gemacht.

*1 ist offen;

*0 ist geschlossen;

%macro ReportOpenLockers(count);

data tueren (drop=i j);

array lockers[&count.] ;

*alle array variablen auf eins setzen;

do i=1 to &count.;

  lockers = 1;

end;

*zwei Scheifen durchlaufen;

do j=2 to &count.;

do i=j to &count. by j;

  if lockers = 1 then lockers = 0;

  else if lockers = 0 then lockers = 1;

end;

end;

run;

proc transpose data=tueren out=tueren;

run;

proc print data=tueren;

  where Col1 = 1;

run;

%mend ReportOpenLockers;

%ReportOpenLockers(100);

Viele Grüße

Sven

AndreasMenrath
Pyrite | Level 9

Hallo zusammen,

wie versprochen möchte ich auch meine Lösung vorstellen:

%macro reportOpenLockers(countOfLockerDoors);

     proc format;

          value open 0 = "closed"

                                                 1 = "open";

     run;

     data lockerinfo;

          /* initialize */

          length lockerNumber status i 8;

          format status open.;

          array lockerDoor {&countOfLockerDoors.} (&countOfLockerDoors. * 0) ;

          /* business logic to calculate locker status */

          do i = 1 to &countOfLockerDoors.;

              lockerDoor2flip = i;

              /* inner loop flips each i-th lockerdoor */

              do while (lockerDoor2flip <= &countOfLockerDoors.);       

                   lockerDoor[lockerDoor2flip] = NOT lockerDoor[lockerDoor2flip];

                   lockerDoor2flip+i;

              end;

          end;

          /* pivot */

          do i=1 to &countOfLockerDoors.;

              lockerNumber = i;

              status = lockerDoor;

              output;

          end;

          keep lockerNumber status;

     run;

     /* reporting */

     proc sql noprint;

          select count(*) into :countOpen

              from lockerinfo

              where status = 1;

     quit;

     title "&countOpen. open locker doors found";

     proc print data=lockerinfo(where=(status=1)) noobs;

          var lockerNumber status;

     run;

     /* cleanup */

     proc delete data=lockerinfo;

     run;

%mend reportOpenLockers;

%reportOpenLockers(100);

Ich muss sagen, dass auch mir das Implementieren Spaß gemacht hat und ich auch wieder etwas gelernt habe.

Beim Anspruch einen möglichst eleganten Code abzuliefern, habe ich u.a. ein längst vergessenes Feature wiederentdeckt:

beim Initialisieren des Arrays kann man mit der Syntax (n * Wert) n-fach einen Initialisierungswert angeben. Das spart mir eine Schleife, in der überall der Wert 0 gesetzt wird.

Den Status der Schließfachtür habe ich als Boolean implementiert. Anfangs hatte ich auch noch das folgende Codeschnipsel im Code stehen:

if(lockerDoor[locker2flip]) then do;

  lockerDoor[locker2flip] = 0;

end;

else do;

     lockerDoor[locker2flip] = 1;

end;

Dann wurde mir bewusst, dass es auch einfacher geht, indem der Boolsche Wert mit dem NOT Operator einfach negiert wird.

Insgesamt fand ich es auch spannend die anderen Lösungen zu sehen und zu vergleichen. Das man die Lösung auch ohne Arrays angehen kann, hätte ich z.B. als ich die Aufgabe gestellt habe, nicht erwartet.

Vielen Dank an alle Mutigen, die ihre Lösung mit der CoDe SAS Community geteilt haben! Weitere Lösungen können weiterhin gerne gepostet werden.

Viele Grüße,

Andreas Menrath

CKothenschulte
Obsidian | Level 7

Die Übung eignet sich wirklich gut für den Einstieg in Programmierung (mit SAS)

 

Ein Praktikant in unserer Abteilung hatte den interessanten Ansatz, die Schliessfächer nicht waagerecht zu speichern, sondern senkrecht, also ein Schliessfach entspricht einer Zeile in einer SAS-Tabelle.

 

Das wollte ich dann mal ausprobieren und habe folgende Lösung erstellt, bei der für jeden Durchgang eine neue Spalte angefügt wird.

%macro reportOpenLockers(ANZAHL=,AUF=,ZU=);
data SCHLIESSFAECHER;
  do SCHLIESSFACH=1 to &ANZAHL;
    DURCHGANG0 = "&AUF."; 
    output;
  end;
run;

%do I=1 %to &ANZAHL;
%let J=%sysfunc(sum(&I,-1));
data SCHLIESSFAECHER;
  set SCHLIESSFAECHER;
  DURCHGANG&I. = DURCHGANG&J.;
  if mod(_n_,&I)= 0 then do;
    if DURCHGANG&J. = "&AUF." then
      DURCHGANG&I. = "&ZU";
    else 
      DURCHGANG&I. = "&AUF.";
  end;
run;
%end;

%mend reportOpenLockers;
%reportOpenLockers(ANZAHL=100,AUF=auf,ZU=zu);

Dann wollte ich noch ändern, dass es nur eine Spalte gibt, die den aktuellen Zustand enthält. Diese Spalte wird also bei jedem Durchgang überschrieben.

%macro reportOpenLockers(ANZAHL=,AUF=,ZU=);
data SCHLIESSFAECHER;
  do SCHLIESSFACH=1 to &ANZAHL;
    DURCHGANG = "&AUF."; 
    output;
  end;
run;

%do I=1 %to &ANZAHL;
data SCHLIESSFAECHER;
  set SCHLIESSFAECHER;
    if mod(_n_,&I)= 0 then do;
      if DURCHGANG = "&AUF." then
        DURCHGANG = "&ZU";
      else 
        DURCHGANG = "&AUF.";
    end;
run;
%end;

%mend reportOpenLockers;
%reportOpenLockers(ANZAHL=100,AUF=auf,ZU=zu);

Schönes Wochenende allerseits!

Christine2
Fluorite | Level 6

Nettes Rätsel, meine Lösung ist vielleicht noch nicht richtig hübsch, man könnte bestimmt bei der Ausgabe noch einiges hübscher machen, aber mit der Berechnung bin ich recht zufrieden 🙂

 

%macro reportOpenLockers(countOfLockerDoors);
	data Ausgabe;
		length Zustand 3;
		label Zustand = "Schließfach offen (0/1)";
		do _schliessfachnr = 1 to &countOfLockerDoors;
			Zustand = 0;
			do _durchlauf = 1 to &countOfLockerDoors;
				if mod(_schliessfachnr, _durchlauf) = 0 then do;
					Zustand = not(Zustand);
				end;
			end; /* _durchlauf = 1 to &countOfLockerDoors */
			output;
		end; /*_schliessfachnr = 1 to &countOfLockerDoors */
	run;

	proc sql;
		select sum(Zustand) into: _AnzahlOffen
		from Ausgabe;
	quit;

	Title "Aufteilung der %sysfunc(strip(&_Anzahloffen)) offenen Schließfächer";

	proc print data=ausgabe(keep= Zustand) noobs;
	run;


%mend reportOpenLockers;

%reportOpenLockers(100);
Christine2
Fluorite | Level 6

Beim Vergleich mit den anderen Lösungen fällt mir auf, dass ich die Aufgabe zwar gelöst habe, aber nicht wirklich die Aufgabenstellung. Bei mir geht der Mensch nicht x mal na den Schließfächern vorbei und öffnet/schließt beim n-ten Lauf das n-te Fach sondern er steht vor dem ersten Fach, führt alle x Läufe durch, geht dann zum zweiten und so weiter.

 

Das Ergebnis ist sicherlich das Gleiche, aber meine Lösung möchte glaube ich kein Mensch in der Wirklichkeit machen müssen 🙂

mfab
Quartz | Level 8

Hallo zusammen,

 

habe meine Lösung entworfen und festgestellt, dass diese (verständlicherweise) ähnlich zu schon geposteten Lösungen ist.

Abweichend zu den bisherigen Ansätzen wird allerdings nicht der Zustand der Fächer betrachtet. Stattdessen zähle ich nur die Anzahl der Veränderungen der Zustände und ermittle daraus abschliessend den Endzustand des Fachs.

Dabei gilt die Annahme, dass zu Beginn alle Fächer geschlossen sind.

 


%macro reportOpenLockers(countOfLockerDoors);

data want (keep=offen anz_offen);
length offen $300. anz_offen 8; /* Ergebnisfelder */

array fach{&countOfLockerDoors.}; /* Fächer anlegen */

 do i = 1 to &countOfLockerDoors.; /* Loop Durchgänge */
   do j = i to &countOfLockerDoors. by i; /* Veränderung Fächer */
      fach{j} = sum(fach{j}, 1); /* Zähle Anzahl Veränderungen pro Fach */
   end;
 end;

 do z = 1 to &countOfLockerDoors.; /* Zählung */
   if mod(fach{z}, 2) = 1 then /* bei ungerader Anzahl Änderungen => offen */
     do;
        offen = catx(' ', offen, put(z,best.)); 
        anz_offen = sum(anz_offen, 1);
	 end;
 end;
 output;
run;

%mend reportOpenLockers;

%reportOpenLockers(100);

Herzliche Grüsse

Michael

mfab
Quartz | Level 8

Als alternativer Ansatz hier noch eine Lösung mit Hash-Objekt:

 


%macro reportOpenLockers(countOfLockerDoors);

data want (keep=offen anz_offen);
length offen $300. anz_offen mysuminc sum 8; /* Ergebnisfelder */

  declare hash myhash( suminc: 'mysuminc');
  rc = myhash.definekey("fach");
  rc = myhash.definedone();

/* Fächer anlegen */
mysuminc = 0;
do fach = 1 to &countOfLockerDoors.;
  myhash.add();
end;

/* Durchläufe */
mysuminc = 1;
do i = 1 to &countOfLockerDoors.;
  do j = i to &countOfLockerDoors. by i;
    rc = myhash.find(key:j);
  end;
end;

/* Auswertung */
do z = 1 to &countOfLockerDoors.;
  rc = myhash.sum(key:z, sum: sum);
  if mod(sum, 2) = 1 then
     do;
        offen = catx(' ', offen, put(z,best.)); 
        anz_offen = sum(anz_offen, 1);
	 end; 
end;

output;
run;

%mend reportOpenLockers;

%reportOpenLockers(100);

sas-innovate-2024.png

Join us for SAS Innovate April 16-19 at the Aria in Las Vegas. Bring the team and save big with our group pricing for a limited time only.

Pre-conference courses and tutorials are filling up fast and are always a sellout. Register today to reserve your seat.

 

Register now!

Diskussionsstatistiken
  • 8 Antworten
  • 2249 Aufrufe
  • 3 Kudos
  • 5 in Unterhaltung