Welcome to the LimeSurvey Community Forum

Ask the community, share ideas, and connect with other LimeSurvey users!

Urnenziehung ohne Zurücklegen (ohne javascript, ohne plugin) mit Zusatzbedingung

  • Joffm
  • Joffm's Avatar Topic Author
  • Away
  • LimeSurvey Community Team
  • LimeSurvey Community Team
More
2 years 2 weeks ago - 2 years 2 weeks ago #227422 by Joffm
In diesem Text möchte ich einmal folgendes Szenario behandeln:
Aus einer großen Anzahl von Objekten soll eine bestimmte Anzahl per Zufall ausgewählt werden.
Im speziellen Fall handelt es sich um zufällige Satzpaare, die angezeigt werden sollen.
Als zusätzliche Bedingung soll gelten: Insgesamt soll jedes Objekt nicht mehr als x-Mal angezeigt werden.

Geben wir einmal konkrete Werte an:
Seien es 700 Satzpaare, 5 sollen zufällig ausgewählt werden, und ein Satzpaar soll nicht mehr als dreimal gezeigt werden.

Hier eine Lösung, die nur auf eingebaute Funktionen von LimeSurvey zurückgreift, weder javascript noch irgendwelche plugins benötigt.
Dies könnte eine Reihe von Studenten betreffen, da viele admins in den Uni-Installationen dies verboten haben.

Wer javascript benutzen kann (aber auch keine plugins), lese bitte den "Schwester"-Thread darüber.

Über die eingebauten Funktionen lese man im Handbuch bitte nach:
[url] manual.limesurvey.org/ExpressionScript_-...mplemented_functions [/url]

Man könnte sich zunächst denken: "Gut, machen wir 5 Zufallszahlen zwischen 1 und 700".
Im Normalfall würden wohl 5 verschiedene Zahlen herauskommen, aber eben nicht zwangsläufig; es kann durchaus passieren, dass ein Zahl dabei zweimal auftaucht.

Erinnern wir uns an die Schule, haben wir es mit "Mehrmaligem Ziehen aus einer Urne ohne Zurücklegen" zu tun.

1. Vorbereitung der "Urne"
Schaffen wir uns zunächst einmal eine gefüllte Urne.
Dazu bietet sich eine Frage vom Typ "kurzer Text" (QWerte) an. Und als Vorgabe-Antwort tragen wir ein:
001002003004005006007008009010...697698699700
Also 700 "Kugeln" , jede mit einer Zahl beschriftet, links mit Nullen aufgefüllt, dass jede Zahl dreistellig ist.
Nun geht die Zieherei los.
Da im Folgenden viele Fragen vom Typ "Gleichung" benutzt werden, benenne ich sie eqxxx, an "Equation" erinnern.
Als Erstes bestimmen wir die Anzahl der "Kugeln".
Das ist nicht schwer, das ist einfach "Länge des Textes" geteilt durch 3"
eqLaenge: {strlen(QWerte)/3}

2. Ziehen der "Kugeln"
Nun ziehen wir die erste Kugel.
Dazu erzeugen wir eine Zufallszahl zwischen 1 und dieser Länge:
eqRand1: {if(is_empty(rand1,(rand(1,eqLaenge),rand1)}
Mit dieser Zufallszahl können wir nun die "Kugel" herausgreifen, die an dieser Stelle liegt.
eqZahl1: {substr(QWerte,(eqRand1-1)*3,3)}
Übersetzt: Nimm die folgenden drei Stellen des Textes, der an der Stelle "(eqRand1-1)*3" anfängt. 
Jetzt ist diese Kugel "verbraucht" und muss entfernt werden.
eqRemove1: {str_replace(eqZahl1,"",QWerte)}
Übersetzt: Ersetze die soeben "gezogenen" drei Stellen des Textes durch einen leeren Text.

Und jetzt geht es analog weiter
1. Zufallszahl erzeugen (der Text ist jetzt um eine Stelle verkürzt)
eqRand2: {if(is_empty(rand2,(rand(1,eqLaenge-1),rand2)}
2. Wert an der Stelle bestimmen
eqZahl2: {substr(QWerte,(eqRand2-1)*3,3)}
3. Aus dem Text entfernen
eqRemove2: {str_replace(eqZahl2,"",QWerte)}

Usw.
eqRand3: {if(is_empty(rand3,(rand(1,eqLaenge-2),rand3)}
eqZahl3: {substr(QWerte,(eqRand3-1)*3,3)}
eqRemove3: {str_replace(eqZahl3,"",QWerte)}

Nach 5 Runden haben wir also 5 zufällige und voneinander verschiedene Zahlen in "eqZahl1,..., eqZahl5"

3. Herausfiltern der zugehörigen Objekte
Bis jetzt haben wir uns noch nicht mit den eigentlich auszuwählenden Objekten beschäftigt.
Wir benötigen also einen Container, in welchem diese Objekte gespeichert sind, und auch wohl definiert aufgrund meiner 5 Zahlen herausgesucht werden können.
Auch hierzu bietet sich eine Frage vom Typ "Text" an, nehmen wir "ausführlicher Text" (QPool)
Hier tragen wir die Satzpaare wieder als Vorgabeantwort ein, ein Satzpaar pro Zeile.
Da wir später diese Paare wieder trennen müssen, um jeden Teil des Paares getrennt anzeigen zu können, benötigen wir entweder ein Trennzeichen (dann kann man später mit den Funktionen "strpos" das Zeichen suchen, und mit "substr" den Teil bis zum Zeichen als Teil 1 und ebenfalls mit "substr" den Teil nach dem Trennzeichen als Teil 2 nehmen).

Hier möchte ich aber beschreiben, wie man dies mit Sätzen fester Länge durchführt. Im Folgenden gehe ich von einer Länge jedes Textes von 125 Zeichen aus, also 250 pro Satzpaar.
Jeder Teil des Satzpaares wird rechts mit Leerzeichen aufgefüllt und dann einfach hintereinander gesetzt.
Sieht dann so aus.
 
Dies kann man übrigens in EXCEL mit den Funktionen "LINKS", "VERKETTEN", "WIEDERHOLEN" einfach und schnell durchführen.

Um nun den Text, der an der Stelle steht, der durch unsere oben gezogene Zahl "eqZahl1" steht, herauszufischen, benutzen wir wieder die Funktion "substr"
Diese Gleichung wird nun direkt in die Frage eingesetzt, in welcher der Text eingeblendet werden soll.
Erste Hälfte des Paares: {trim(substr(QPool,(eqZahl1-1)*252,125))}
Übersetzt: Nimm die folgenden 125 Stellen des Textes aus "QPool", der an der Stelle "(eqZahl1-1)*252" beginnt.
Übrigens "252", weil zusätzlich zu den 250 Zeichen des eigentlichen Textes noch zwei Zeichen hinzugekommen sind durch den Zeilenumbruch.
Zweite Hälfte des Paares: {trim(substr(QPool,(eqZahl1-1)*252+125,125))}
Analog, nur fange ich eben 125 Zeichen später an, um den zweiten Teil des Paares zu erwischen.

Vorsicht: Wenn man die Paare frisch aus einem Texteditor oder aus EXCEL hineinkopiert, gibt es zwei Zeichen für den Zeilenumbruch.
Wird die Studie, z.B. die lss-Datei importiert, gibt es nur ein Zeichen dort; dann muss geändert werden in "251"


Das ist schön; leider zu schön, um wahr zu sein.
Ein Fragetyp vom Typ "Text", egal welcher, kann bis zu 64kB speichern, wird also bei langen Texten an seine Grenzen stoßen.
Oben war von 700 Satzpaaren die Rede. Nehmen wir jeden Satz mit 125 Zeichen an, so ergibt dies 175000 Zeichen, also zu viele.
Dies ist aber überhaupt kein Problem, da wir in diesem Fall einfach mehrere Pools anlegen.
Zum Beispiel kommen dann 200 Satzpaare in "QPool1", 200 in "QPool2", 200 in "QPool3" und die letzten 100 in "QPool4"
Jetzt müssen wir nur noch bestimmen, in welchem Container unser gesuchter Text nun ist, und an welcher Stelle.
Den Text mit dem Index 532 würden wir jetzt also an Stelle 132 in Container "QPool3" finden.
Da gehen wir wieder zurück in die Schule, und erinnern uns an DIV und MODULO.
Also benötigen wir noch pro Satzpaar diese beiden Werte.
Also, noch ein paar Fragen vom Typ "Gleichung" dazu.
Ich benenne einmal:
eqCox: die Nummer des Containers (x=1,2,3,4,5)
eqInx: der Index des Textes innerhalb des Containers (x=1,2,3,4,5)

Dann haben wir also:
eqCo1: {1+floor((eqZahl1-1)/200)}
eqIn1: {eqZahl1-(eqCo1-1)*200}
Anmerkung zur Subtraktion bzw. Addition von "1" in eqCo1:
Im ersten Container sind die Satzpaare 1-200. Wenn man nun einfach "floor(eqZahl1/200)" rechnet, was ja einer Ganzzahldivision entspricht, so ergibt sich für 200 als Ergebnis 1, für alle übrigen 0.
Daher wird 1 abgezogen, so dass die Berechnung für 0-199 stattfindet, und immer 0 ergibt; damit dies aber Container 1 sein soll, wird dann wieder 1 addiert.

Nun müssen wir aber in der eigentlichen Frage eine Fallunterscheidung machen, nämlich
für den ersten Teil des Paares:
{if(eqCo1==1,trim(substr(QPool1,(eqIn1-1)*252,125)),if(eqCo1==2,trim(substr(QPool2,(eqIn1-1)*252,125)),if(eqCo1==3,trim(substr(QPool3,(eqIn1-1)*252,125)),trim(substr(QPool4,(eqIn1-1)*252,125)))))}
(Also einfach ein verschachteltes IF)
und für den zweiten Teil des Paares:
{if(eqCo1==1,trim(substr(QPool1,(eqIn1-1)*252+125,125)),if(eqCo1==2,trim(substr(QPool2,(eqIn1-1)*252+125,125)),if(eqCo1==3,trim(substr(QPool3,(eqIn1-1)*252+125,125)),trim(substr(QPool4,(eqIn1-1)*252+125,125)))))}


4. Einbau der Zusatzbedingung
Jetzt haben wir also die erste Bedingung erfüllt, fünf Satzpaare aus den 700 anzuzeigen.
Kommen wir zur zweiten.
Ein Satzpaar soll maximal dreimal angezeigt werden.
Wir müssen also auf alle vorigen Beantwortungen der Umfrage zugreifen. Dies geht nur mithilfe der plugins "getStatInSurvey" bzw. seit Vewrsion 5.x. "statFunctions".
Da wir davon ausgehen, dass keine plugins erlaubt sind, und auch in der Version 5.x. das core-plugin "statFunctions" aus irgendeinem Grund nicht aktiviert ist, müssen wir hier händisch zu Werke gehen.

Dies beruht darauf, dass man in den Vorgabewert der "Urne" die "Kugeln", die bereits dreimal gezogen wurden, nicht mehr hineinlegt.
Beispiel:
Ursprünglich sieht die Urne so aus:
001002003004005006007...
Nun wurde Satzpaar 6 bereits dreimal gezeigt.
Dann füllen wir die Urne eben ohne sie
001002003004005007...
Was nicht da ist kann auch nicht gezogen werden.

Und jetzt wird auch klar, warum ganz oben als Erstes die Länge dieses Textes bestimmt wurde.

Wie geht man praktisch vor.
Nur als Idee:
Jeden Abend exportiert man die Daten nach EXCEL (hier würden die 5 Spalten "eqZahl1", "eqZahl2", ... ausreichen)
Mit einem Makro geht man nun durch alle Zeilen und Spalten und zählt die Häufigkeit, die in einem array gespeichert werden.
Dann erzeugt man einen String mit nur den Zahlen, die noch nicht dreimal vorgekommen sind
Das Makro könnte - ganz rudimentär - so aussehen

Sub Pool()
  Dim i, arow, acol, s, A() As Integer, erg

' Dimensioniere das array auf die Gesamtanzahl der Paare
' Hier als Beispiel 50
  ReDim A(50)
  
' Vorbesetze alle Zellen mit 0
  For i = 1 To 50
    A(i) = 0
  Next
  
' Gehe durch die Daten und zähle das array entsprechend hoch
  arow = 2
  s = Cells(arow, 2)
  While s <> ""
    For acol = 0 To 4
      s = Cells(arow, 2 + acol)
      s=int(s)
      A(s) = A(s) + 1
    Next
    arow = arow + 1
    s = Cells(arow, 2)
  Wend
  
' Initialisiere den Ergebnisstring
  erg = "'"
  
' Schreibe den Ergebnisstring
' Falls der Wert <3 füge zum String hinzu
  For arow = 1 To 50
    If A(arow) < 3 Then
      erg = erg + Right(("'00" & arow), 3)
    End If
  Next
  
  ' Schreibe den String in das EXCEL sheet
  Cells(1, 7) = erg

End Sub 


 

Diesen neuen Text nimmt man nun und fügt ihn als neue Vorgabeantwort in QWerte ein.

Als Schlusswort: Sollten es mehr als 999 Satzpaare sein, so muss eben überall, wo von "3" die Rede ist, dies in 4 geändert werden.

Joffm

Und hier ein Beispiel
 

File Attachment:

File Name: limesurvey...7184.lss
File Size:240 KB

Volunteers are not paid.
Not because they are worthless, but because they are priceless
Last edit: 2 years 2 weeks ago by Joffm.

Please Log in to join the conversation.

Moderators: Joffm

Lime-years ahead

Online-surveys for every purse and purpose