Forum: PC-Programmierung Clevere Lösung zur Festellung der Anzahl verschiedener Zahlen in einem Array


von Christian J. (Gast)


Lesenswert?

Hallo,

ich habe gestern eine Lösung programmiert, die mir in einem kleinen 
Array (512 Elemente) die Anzahl verschiedener Zahlen ausgeben soll. Ziel 
war es wiederkehrende Muster zu erkennen. Ich bin immer noch dabei mein 
"Game of Life" zu optimieren und eine statistische Auswertung des Feldes 
zu machen.

Typischer Fall:

16....20....45...5...16....20....45...5  als 4er Wiederhol Muster was 
immer weiter läuft aber die zahlen können auch deutlich größer werden.

Wertebereich der Zahlen 16 Bit

Mir fiel nur die Lösung ein einen verschwenderischen 64k grossen Array 
aufzumachen und die Vorkommnisse der Zahlen dann in dem jeweiligen 
Tabellenfeld zu addieren.

for .....
   zaehlung[array[zahl]]++;

In einem zweiten Lauf schaue ich wieviele Felder > 0 sind und habe damit 
die Anzahl verschiedener Zahlen. Bei wiederkehrenden Muster sinkt diese 
ab bis sie schliesslich konstant bleibt.

Wie würden es die Profis machen?

512 Elemente, davon jedes beliebig zwischen 0 - 0xffff.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Christian J. schrieb:
> Mir fiel nur die Lösung ein einen verschwenderischen 64k grossen Array
> aufzumachen

Das ist sicher nicht die schlechteste Idee. 64k sind auf einem PC nicht
viel.

> und die Vorkommnisse der Zahlen dann in dem jeweiligen
> Tabellenfeld zu addieren.

Statt eines Zählers genügt ein Bool-Wert: Zahl kommt vor / kommt nicht
vor.

> In einem zweiten Lauf schaue ich wieviele Felder > 0 sind und habe damit
> die Anzahl verschiedener Zahlen. Bei wiederkehrenden Muster sinkt diese

Das kannst du schon im ersten Durchlauf machen: Jedesmal, wenn der
Bool-Wert für eine Zahl von False auf True wechselt, wird die Anzahl
verschiedener Zahlen inkrementiert.

Speichersparender, aber nicht ganz so laufzeiteffizient wäre die
Verwendung einer auf einem Binärbaum basierende Mengendatenstruktur
(Set), wie sie in der Standardbibliothek der meisten Programmiersprachen
enthalten ist. Das funktioniert auch dann noch gut, wenn du es statt
16-Bit- mit 32-Bit-Zahlen zu tun hast.

von Christian J. (Gast)


Lesenswert?

Yalu X. schrieb:

> Speichersparender, aber nicht ganz so laufzeiteffizient wäre die
> Verwendung einer auf einem Binärbaum basierende Mengendatenstruktur
> (Set), wie sie in der Standardbibliothek der meisten Programmiersprachen
> enthalten ist. Das funktioniert auch dann noch gut, wenn du es statt
> 16-Bit- mit 32-Bit-Zahlen zu tun hast.

Die Idee ist gut, muss ich mir mal anschauen. Ist allerdings kein PC 
sondern meine Z80 "Kiste". Ich dachte mir, dass es vielleicht Lösungen 
gibt, die pfiffig sind und speichersparend aber ums Zaehlen kommt man 
wohl nicht herum.
Ob Bool "BITSET" oder "INC" ist eigentlich egal, beides kostet das 
Gleiche an Zeit.

Mir schwebt da noch vor den Array zu kopieren, da dieser nicht verändert 
werden darf und von außen als Ringpuffer gefüttert wird. und dann von 0 
an durchzulaufen, 0 als referenz, dann Feld 1,2,3... und die 
Wiederholungen "abzustreichen". Die 0 kommt nie vor, daher könnte man 
die als Marke nehmen.
Jedes Abstreichen würde mitgezaehlt für die Referenzzahl auf Feld 0. Mit 
fortschreitendem Durchlaufen entstehen immer mehr 0 Felder, die 
übergangen werden.


Stift, Papier.... ich muss mal eben ein "UML" machen :-)

von K.M. (Gast)


Lesenswert?

Sortieren (qsort etc.) und danach in einem linearen Lauf über den sort. 
Vektor gehen: Ist Zahl != vohergehender Zahl, Zähler um eins erhöhen.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Christian J. schrieb:
> Ob Bool "BITSET" oder "INC" ist eigentlich egal, beides kostet das
> Gleiche an Zeit.

... aber unterschiedlich viel Speicher. Bei 512 Eingabewerten braucht
man mindestens 10 Bits (d.h. 2 Bytes) für die Zähler, während man bei
Bool-Werte mit 1 Byte oder sogar 1 Bit auskommt.

> Mir schwebt da noch vor den Array zu kopieren, da dieser nicht verändert
> werden darf und von außen als Ringpuffer gefüttert wird. und dann von 0
> an durchzulaufen, 0 als referenz, dann Feld 1,2,3... und die
> Wiederholungen "abzustreichen".

Das hat die Zeitkomplexität O(n^2), wenn n die Anzahl der Eingabewerte
ist. Die Lösung mit dem Binärbaum geht in O(n·log n). Man könnte das
Array auch kopieren, sortieren und anschließend die Sprünge in der
sortierten Folge zählen, was ebenfalls O(n·log n) braucht.

Da n nach oben (auf 512) begrenzt ist, kann es aber duchaus sein, dass
sich in der Praxis ein O(n²)-Algorithmus besser schlägt als ein anderer
mit O(n·log n).

Edit:

Die Idee mit dem Sortieren hatte auch schon K.M.

: Bearbeitet durch Moderator
von Yalu X. (yalu) (Moderator)


Lesenswert?


von Christian J. (Gast)


Lesenswert?

K.M. schrieb:

> Sortieren (qsort etc.) und danach in einem linearen Lauf über den sort.
> Vektor gehen: Ist Zahl != vohergehender Zahl, Zähler um eins erhöhen.

Super Idee! Da ich Code sparen muss (max 16kb EPROM, 11kb sind schon 
voll) würde ich qs aber nicht nehmen, sondern ein Bubble Sort was 
einfacher ist. Geschwindigkeit ist egal.

von Christian J. (Gast)


Lesenswert?

Yalu X. schrieb:
> Das hier sieht sehr interessant aus:
>
>   http://stackoverflow.com/questions/1532819/algorit...

Interesante Doktorarbeit aber man müsste erst herausfinden, was davon 
richtig ist was fehlerhaft. Die Coddes sind teilweise nicht trivial.

Element in array[1] will be compared with array[0]. If they are 
different, then value in array[NewLength] will be modified with array[1] 
and increment NewLength. If they are same, NewLength will not be modifie

Das scheint mir ganz clever zu sein. NewLength zb auf 512 setzen und für 
jeden erfolgreichen Vergleich um 1 erniedrigen. Am Schluss entspricht 
NewLenght der Anzahl verschiedener Werte.

von Possetitjel (Gast)


Lesenswert?

Christian J. schrieb:

> Wie würden es die Profis machen?

Keine Ahnung - bin keiner.

Bisher habe ich Dein Problem noch nicht verstanden. Irgendwo
fallen 16bit-Ganzzahlen an, und Du möchtest jetzt wissen,
wie viele unterschiedliche Werte vorkommen. Ist das richtig?

> 512 Elemente, davon jedes beliebig zwischen 0 - 0xffff.

Prinzipskizze:
- Liste mit allen auftretenden Zahlen anlegen
- Liste sortieren
- Liste durchmustern, bei jeder Änderung Zähler erhöhen

von Possetitjel (Gast)


Lesenswert?

Christian J. schrieb:

> K.M. schrieb:
>
>> Sortieren (qsort etc.)

Ahh!
Wurde schon zweimal vorgeschlagen. Zu spät gesehen

von Christian J. (Gast)


Lesenswert?

Den Code hier verstehe ich nicht ganz, so eine for Schleife habe ich 
noch nie gesehen, C macht es wohl möglich, dass da alles drin geht.

current = array + 1

Was bewirkt das? array ist für sich selbst ein Pointer die array[0]. 
current zeigt dann auf array[1] ?


void rmdup(int *array, int length)
{
    int *current , *end = array + length - 1;

    for ( current = array + 1; array < end; array++, current = array + 1 
)
    {
        while ( current <= end )
        {
            if ( *current == *array )
            {
                *current = *end--;
            }
            else
            {
                current++;
            }
        }
    }
}

von Christian J. (Gast)


Lesenswert?

Zum Verständnis:

Manche machen Kreuzworträtsel, ich knoble lieber Software Sachen aus. Es 
gehgt um die Abbruchbedingung wann das "Game of Live" einen Zustand 
erreicht hat, wo es entweder statisch ist oder nur noch "Blinker" 
auftreten oder "Gleiter". All diese End-Muster haben eines gemeinsam, 
nämlich dass sich die Anzahl der Veränderungen (das delta) von einem 
Zustand N nach N+1 stetig wiederholt. Ich habe den Z80 tagelang rechnen 
lassen und Tabellen auf dem PC abspeichern, wo diese Werte drin sind. Es 
gibt Muster, die nur 2 Zyklen haben oder solche mit 3 oder 4.

Und diese Zyklik will ich erkennen. Ihr Merkmal ist, dass immer wieder 
die gleiche "Anzahl Modifikationen" in einem Vektor auftritt, der 
mitschreibt. Im Rush-Hour Zustand kommt keine Zahl mehr als 1 Mal vor, 
erst der End Zustand erzeugt diese Zykliken oder Stillstand.

Es macht Spass sich darüber ein wenig Gedanken zu machen, wie ich das 
einem Computer beibringen kann. Uwe Beger hier, der die STM32 Seite 
betreut, war schon "angesteckt" dass er das auf dem STM32F429 Disco 
Board mit LCD sehr schön realisiert hat.

von Tilo R. (joey5337) Benutzerseite


Lesenswert?

Der verlinkte Stackoverflow ist für den Z80 denke ich zu teuer. (Für die 
Aufgabenstellung zu kompliziert vielleicht auch)

Eine andere Idee:
Einmal über dein großes Array laufen. Im Array enthaltene Zahlen werden 
einem zweiten (kleineren) Array hinzugefügt, wenn die Zahl dort noch 
nicht enthalten war.
Das zweite Array startet zu Beginn leer, es wird sortiert gehalten, 
Zahlen werden per Insert-Sort eingefügt (d.h. alle größeren Zahlen eins 
nach oben geschoben)
Das ist nicht so teuer wie es klingt:
- feststellen, ob eine Zahl enthalten ist geht per 
Intervallhalbierungsverfahren im kleinen (teilgefüllten) Array
- Zahlen hinzufügen muss man nicht so oft


Bei der Idee, eine mögliche Periodizität zu finden musste ich an die 
Autokorrelationsfunktion denken.

von Christian J. (Gast)


Lesenswert?

Tilo Renz schrieb:
> Einmal über dein großes Array laufen. Im Array enthaltene Zahlen werden
> einem zweiten (kleineren) Array hinzugefügt, wenn die Zahl dort noch
> nicht enthalten war.

Frage ist wieviel Schritte dazu nötig sind. Ein Computer sieht ja nicht 
das ganze Bild wie der Mensch sondern muss immer erst nachschauen. Das 
mit dem zweiten Array wo alle Zahlen reinkommen habe ich auch schon 
angedacht.

Ich brauche die Zahlen nachher nicht, nur wieviele es sind. Kleiner 4 
verschiedene ist Abruch. Der Z80 "rennt" mit 0,5 Mips (hust) bei bis zu 
20 Zyklen pro Befehl.

512 ist nicht viel, ich kann das auch kleiner machen. Bubblesort läuft 
da dann n*m drüber und sortiert mir das durch. BS ist einfach, langsam 
und verbraucht nicht viel Code. Zweiter Durchlauf "vergleiche N mit 
N+1".

Im absoluten Extrem (wozu es einige Dokotoren und Professoren brauchte) 
wird das im SETI Projekt durchgeführt, wo genau diese Musterkennung auf 
Milliarden von Werten angewendet wird. Das übesteigt aber mein problem 
bei weitem.

von Possetitjel (Gast)


Lesenswert?

Christian J. schrieb:

> Und diese Zyklik will ich erkennen. [...]

Das verstehe ich.

> Im Rush-Hour Zustand kommt keine Zahl mehr als 1 Mal vor,

Ja, eben.
Du hast das Problem meiner Meinung nach von einer ungünstigen
Seite her formuliert. Solange eine Liste mit N Zahlen nämlich
nur verschiedene Zahlen enthält, ist die Länge der Liste
(=Anzahl der Listenelemente) genau gleich der Anzahl
unterschiedlicher Werte. (Das ist ja eine Tautologie.)

Wenn Du jetzt eine weitere, N+1te Zahl betrachtest, können
zwei Fälle eintreten:
a) die neue Zahl steht noch nicht in der Liste.
   Dann kannst Du sie an die Liste anhängen, und die Liste
   enthält wieder nur unterschiedliche Werte.
b) die neue Zahl steht schon in der Liste.

Im Fall b) weisst Du folgendes:
- Du hast einen Zyklus gefunden.
- Die Kette vom 1. Listenelement bis zum ersten Auftreten der
  Zahl ist die Zustandsfolge, die nur einmal durchlaufen wird.
- Der Teil vom ersten Auftreten der Zahl bis zum Listenende ist
  der Zyklus.

> erst der End Zustand erzeugt diese Zykliken oder Stillstand.

Richtig.
Es ist nie erforderlich, zu zählen, wie oft irgendwas auftritt.
Im Gegenteil: Wenn eine Zahl das zweite Mal auftritt, bist Du
fertig - dann weisst Du alles, was sich wissen lässt.

von Christian J. (Gast)


Lesenswert?

Possetitjel schrieb:
> Richtig.
> Es ist nie erforderlich, zu zählen, wie oft irgendwas auftritt.
> Im Gegenteil: Wenn eine Zahl das zweite Mal auftritt, bist Du
> fertig - dann weisst Du alles, was sich wissen lässt.

Nein, nicht ganz. Es kommt immer mal wieder vor, dass zwei Bilder die 
gleiche Anzahl Modifikationen haben. Aktuell zaehle ich 512 Generationen 
mit. Eine Zyklik ist nicht so ganz einfach zu erkennen. Es kann ja auch 
eine unscharfe Zyklik geben, wo man einer daneben ist.

Ich bin hier grad unter Knoppix drin, weil ich ein Backup laufen lasse 
was noch 3h dauert, daher kkann ich kein Video nach youtube laden aber 
dann würde ich zeigen  können was ich meine.

Guck dir, wenn Interesse mal bei youtube ein GOL Bild an. Nicht diese 
Designer Dinger, sondern was normales. Das wuselt erst richtig rum, dann 
wird es immer weniger und irgendwann ist Ende. Nur noch Inseln und 
Blinker.

Auf dem STM32 LCD mit 160 Mhz sehr schön zu sehen, weil der deutlich 
fixer rechnet als der Z80.

von Christian J. (Gast)


Lesenswert?

Ich packs mal hier drunter, einen Thread ist es nicht wert.

Das ist alter Code von mir, 6 jahre oder so. Vermutlich habe ich da aber 
was von irgendwoher kopiert, da ich dieses Konstrukt nicht kenne.


  for (p = str,i = 0; i != zeit->wtag && *p; i++) {
    while(*p++ !=0 );
  }


Hier wird aus einem const Array ein String heraus gesucht. Die for 
Schleife ist als solche kaum mehr zu erkennen finde ich.

Reicht da nicht einfach ein

sprintf(lcdbuf[0],"Tag: %s", str[zeit->wtag]);

wenn man den str als 2D Feld auslegt und Kommas zwischen die Tage macht? 
Also str[8][]= {"Sonntag","Montag",....)


Oder ist da mal jemand ganz schlau gewesen und hat heraus gefunden dass 
diese Lösung effizienterb ist? 2D Arrays sind nicht grad code sparend 
wegen der Offsets..

????

Kann das nicht ausprobieren grad aber ist das überhaupt guter Coding 
Style, das mit der For Schleife?


Gruss,
Christian
1
// Dieser Struct liegt im Battery RAM und enthält die aktuelle Zeit
2
// Battery RAM ist nur 32 Bit ansprechbar.
3
typedef struct
4
{
5
  uint32_t  stunde,
6
        minute,
7
        sekunde,
8
        tag,
9
        monat,
10
        jahr,
11
        wtag,
12
        jahrtag;
13
} zeit_t;
14
15
16
17
// Anzeige einer Digitaluhr + Datum
18
// Def. zeit_t: rtc.c
19
void LCD_Uhrzeit_2(zeit_t *zeit)
20
{
21
    static const char str[] = "Sonntag\0" "Montag\0" "Dienstag\0" "Mittwoch\0" "Donnerstag\0" "Freitag\0" "Samstag\0" ;
22
23
  const char *p;
24
  uint32_t i;
25
26
  // Lege p-Pointer auf das i.te Element
27
28
  for (p = str,i = 0; i != zeit->wtag && *p; i++) {
29
    while(*p++ !=0 );
30
  }
31
32
  sprintf(lcdbuf[0],"Tag: %s", p);
33
        LCD_Write();
34
}

von Udo S. (urschmitt)


Lesenswert?

Eine Datenstruktur die nur unterschiedliche Werte enthält nennt man 
allgemein ein Set. Oft als Hashset implementiert, oder über einen Baum.
Eine Einfügeoperation in ein Set gibt normalerweise zurück ob der Wert 
schon vorhanden war oder neu ist.
Man könnte sich jetzt einen gleitenden Mittelwert über die letzten 5 / 
10 / 20 Werte vorstellen (Ringspeicher), der angibt wie viele dieser 
Werte Doubletten waren. Übersteigt dieser Wert einen Schwellwert, dann 
ist man in dem Bereich angekommen wo nichts neues mehr passiert.

: Bearbeitet durch User
von OldMan (Gast)


Lesenswert?

Christian J. schrieb:
> so eine for Schleife habe ich
> noch nie gesehen

Es wird mit Pointern gearbeitet.

current zeigt auf die Startadresse +1
und array auf Startadresse.
Bei jedem Durchlauf werden array und current um eins inkrementiert.
Wobei in der while-Schleife dies auch passiert und auch end 
dekrementiert wird.
Es werde Duplikate gesucht und gelöscht.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.