SortArray oder PureLVSORT und deutsche Umlaute

Anfängerfragen zum Programmieren mit PureBasic.
Benutzeravatar
dysti
Beiträge: 656
Registriert: 10.02.2006 18:34
Wohnort: Schlicktown

SortArray oder PureLVSORT und deutsche Umlaute

Beitrag von dysti »

Hallo,
habe ein ganz großes Problem mit Sortarray bzw. PureLVSORT.
Habe folgende Einträge in einem Array oder Listicon:

"Abben"
"Otten"
"Öztar"
"Oncken"
"Paulsen"
"Weiser"
"Zimmer"
und ich diese mit den obengenannten Befehlen sortiere, finde ich den Namen "Özgar" am Ende der Liste wieder.
Bei PureLVSORT ist es noch verrückter. da kann der Name überall sein.

Wie kann ich ein Array bzw. Listicon unter Berücksichtigung der deutschen Umlaute sortieren oder gibt es da keine Möglichkeit?

hier der Code zum probieren:
Macht mal einen rechten Doppelclick im Listicon und dann mehrmals ein Click auf dem Spaltenkopf und wundert euch.
Versuche das mit dem Befehl "Sortdaten"
Alles klar, oder?

Code: Alles auswählen

Procedure SortDaten(Ngadget.l,Column.l,ColumnCount.l)
 
  AnzahlPositionen = CountGadgetItems(Ngadget)
 
  Dim Feld.s(AnzahlPositionen-1)
 
  For i = 0 To AnzahlPositionen-1
    Eintrag.s=GetGadgetItemText(Ngadget,i,Column)
    If Eintrag<>" "
      For ii = 0 To ColumnCount-1
        text$=GetGadgetItemText(Ngadget,i,ii)
        Eintrag + "|" + text$
      Next
      Feld(i)= Eintrag
    EndIf
  Next
 
  SortArray(Feld(),2)
  ClearGadgetItemList(Ngadget)
 
  For i = 0 To AnzahlPositionen-1
 
    text$ = Feld(i)
    Datenneu.s=""
    For ii = 2 To ColumnCount+1
     
      Datentext.s = StringField(text$,ii,"|")
      Datenneu.s + Datentext+Chr(10)
     
    Next
    AddGadgetItem(Ngadget,-1,Datenneu)
  Next
   
EndProcedure

#Window_0 = 0
#ListIcon_0 = 0
Procedure Open_Window_0()
  If OpenWindow(#Window_0, 216, 0, 150, 302, "PureLVSORT Test",  #PB_Window_SystemMenu | #PB_Window_TitleBar | #PB_Window_ScreenCentered)
    If CreateGadgetList(WindowID(#Window_0))
      ListIconGadget(#ListIcon_0, 5, 5, 140, 285, "String", 136)
      AddGadgetItem(#ListIcon_0, -1, "Abben" )
      AddGadgetItem(#ListIcon_0, -1, "Otten" )
      AddGadgetItem(#ListIcon_0, -1, "Öztar" )
      AddGadgetItem(#ListIcon_0, -1, "Oncken")
      AddGadgetItem(#ListIcon_0, -1, "Paulsen")
      AddGadgetItem(#ListIcon_0, -1, "Weiser")
      AddGadgetItem(#ListIcon_0, -1, "Zimmer")
    EndIf
  EndIf
EndProcedure
Open_Window_0()
; ListIcon Sort Setup
  PureLVSORT_SelectGadgetToSort(#ListIcon_0, #PureLVSORT_ShowClickedHeader_No)
  PureLVSORT_SetColumnType(#ListIcon_0, 0, #PureLVSORT_String) 
;
Repeat
  Event = WaitWindowEvent()
  
  If EventType() = 2
    ;PureLVSORT_ClearGadget(#ListIcon_0)
  EndIf
  If EventType() = 3
    AddGadgetItem(#ListIcon_0, -1, "Bopiz" )
    PureLVSORT_SetColumnType(#ListIcon_0, 0, #PureLVSORT_String)
    ;SortDaten(#ListIcon_0,1,1)
  EndIf
Until Event = #PB_Event_CloseWindow
End
PB5 / Spiderbasic / WB14 / Win7 / Win8.1 / Win10 / Debian 9
Benutzeravatar
Shardik
Beiträge: 746
Registriert: 25.01.2005 12:19

Beitrag von Shardik »

Hallo dysti,

wie möchtest Du denn die Umlaute sortiert haben? Laut Wikipedia gibt es entsprechende DIN-Normen, die eine Lexikon-Sortierung oder Telefonbuch-Sortierung vorsehen:
http://de.wikipedia.org/wiki/Alphabetische_Sortierung

Die Lexikon-Sortierung ersetzt einen Umlaut lediglich durch die entsprechenden "normalen" Buchstaben, also z.B. das "ä" durch ein "a". Die Telefonbuch-Sortierung ersetzt ein "ä" durch die zwei Buchstaben "ae" und ist programmtechnisch etwas schwieriger umzusetzen, weil sich die Zeichenketten dadurch ja verlängern. Grundsätzlich arbeitet man in beiden Fällen mit einem Puffer, in den man vor dem Vergleich die Zeichenketten schreibt, da die Original-Texte ja nicht verändert werden dürfen. Ich habe zuletzt 1999 die "Telefonbuch"-Lösung für einen IBM-Großrechner in Assembler programmiert. Wenn Du keine andere Lösung findest, könnte ich versuchen, diese Routine für Dich zu portieren, was aber etwas dauern kann, weil ich momentan zeitlich etwas knapp bin...
Benutzeravatar
dysti
Beiträge: 656
Registriert: 10.02.2006 18:34
Wohnort: Schlicktown

Beitrag von dysti »

@Shardik, ich brauche die Telefonbuchsortierung.
Das wäre super wenn du das machen würdest.
Aber zum Verständnis:
1. ich durchsuche mein Array z.B. nach "ö" und ersetze es nach "oe".
2. dann sortiere ich.
3. dann das Array wieder umwandeln, also "oe" nach "ö".

Soweit kann ich gedanklich folgen.
Aber bei 3 weiß ich doch gar nicht, ob "oe" nun tatsächlich ein Umlaut ist oder nicht.
Bitte bringe Licht in das Dunkel meines Gehirns.
PB5 / Spiderbasic / WB14 / Win7 / Win8.1 / Win10 / Debian 9
Benutzeravatar
Shardik
Beiträge: 746
Registriert: 25.01.2005 12:19

Beitrag von Shardik »

@dysti,
bei meiner oben beschriebenen Methode verwende ich zwei Puffer, in die die zu vergleichenden Texte kopiert werden. Dann werden alle Umlaute in den Pufferbereichen in zwei Buchstaben umgesetzt (Telefonbuch-Methode, "ä" => "ae", "ß" => "ss", usw.). Erst dann erfolgt der Vergleich der Puffer. Umsortiert werden nach dem Vergleich aber nur die Original-Texte. Das heißt, daß es unerheblich ist, ob im Original-Text nun "ä" oder "ae" steht. Denn einige meiner Anwender (oder deren Kunden) würden sich schnell beschweren, wenn in einem Massenanschreiben ihre Namen verändert würden, indem Umlaute in den entsprechenden Doppelbuchstaben oder umgekehrt umgesetzt würden... :wink:
Benutzeravatar
dysti
Beiträge: 656
Registriert: 10.02.2006 18:34
Wohnort: Schlicktown

Beitrag von dysti »

Bis hierhin kann ich folgen:
Erst dann erfolgt der Vergleich der Puffer.
Hier wird "ä" zu ""ae"

und hier stehe ich im Dunkeln:
Umsortiert werden nach dem Vergleich aber nur die Original-Texte
und wie erfolgt die Einsortierung.
Ich blicke rein garnichts.

Lichtblitz beim Schreiben:
der Puffer hat die Struktur ->

Structure puffer
sortname.s
originalname.s
Endstructure

In puffer()\sortname stehen die umgewandelten Namen und
in puffer()\originalname natürlich die Originalnamen

Mit SortStructureArray() sortiere ich nach puffer()\sortname

und fülle danach aus puffer()\originalname das vorher geleerte Listicongadget.

Hast du dir das so gedacht?
PB5 / Spiderbasic / WB14 / Win7 / Win8.1 / Win10 / Debian 9
Num500
Beiträge: 21
Registriert: 23.12.2004 18:50
Wohnort: Buchholz

Beitrag von Num500 »

Hallo dysti,

vielleicht kommst Du mit folgendem klar:

Code: Alles auswählen

Enumeration
  #Window_0
  #ListIcon_0
  #Button_Auf
  #Button_Ab
  #Button_NameHinzu
EndEnumeration

Structure Namen
  Name.s
  SortName.s
EndStructure

Global NewList NamenListe.Namen()

Procedure DatenLaden()
ClearList(NamenListe())
AddElement(NamenListe.Namen())
NamenListe.Namen()\Name.s     = "Abben"
AddElement(NamenListe.Namen())
NamenListe.Namen()\Name.s     = "Otten"
AddElement(NamenListe.Namen())
NamenListe.Namen()\Name.s     = "Öztar"
AddElement(NamenListe.Namen())
NamenListe.Namen()\Name.s     = "Oncken"
AddElement(NamenListe.Namen())
NamenListe.Namen()\Name.s     = "Paulsen"
AddElement(NamenListe.Namen())
NamenListe.Namen()\Name.s     = "Weißer"
AddElement(NamenListe.Namen())
NamenListe.Namen()\Name.s     = "Milchmueller"
AddElement(NamenListe.Namen())
NamenListe.Namen()\Name.s     = "Weiser"
AddElement(NamenListe.Namen())
NamenListe.Namen()\Name.s     = "Zimmer"
AddElement(NamenListe.Namen())
NamenListe.Namen()\Name.s     = "Ändermich"
AddElement(NamenListe.Namen())
NamenListe.Namen()\Name.s     = "Milchmüller"
EndProcedure        

Procedure SortDaten(SortRichtung.l)
;hier
  PosAnzahl.l = CountList(NamenListe())
  ResetList(NamenListe())
  For i = 1 To PosAnzahl.l
    NextElement(NamenListe())
    NamenListe.Namen()\SortName.s = NamenListe.Namen()\Name.s
    If FindString(NamenListe.Namen()\SortName.s, "Ä",1) 
      NamenListe.Namen()\SortName.s = ReplaceString(NamenListe.Namen()\Name.s, "Ä", "Ae")
    EndIf
    If FindString(NamenListe.Namen()\SortName.s, "ä",1)     
      NamenListe.Namen()\SortName.s = ReplaceString(NamenListe.Namen()\Name.s, "ä", "ae")
    EndIf
    If FindString(NamenListe.Namen()\SortName.s, "Ö",1)     
      NamenListe.Namen()\SortName.s = ReplaceString(NamenListe.Namen()\Name.s, "Ö", "Oe")
    EndIf
    If FindString(NamenListe.Namen()\SortName.s, "ö",1) 
      NamenListe.Namen()\SortName.s = ReplaceString(NamenListe.Namen()\Name.s, "ö", "oe")
    EndIf
    If FindString(NamenListe.Namen()\SortName.s, "Ü",1)     
      NamenListe.Namen()\SortName.s = ReplaceString(NamenListe.Namen()\Name.s, "Ü", "Ue")
    EndIf
    If FindString(NamenListe.Namen()\SortName.s, "ü",1) 
      NamenListe.Namen()\SortName.s = ReplaceString(NamenListe.Namen()\Name.s, "ü", "ue")
    EndIf    
    If FindString(NamenListe.Namen()\SortName.s, "ß",1) 
      NamenListe.Namen()\SortName.s = ReplaceString(NamenListe.Namen()\Name.s, "ß", "ss")
    EndIf
  Next i
  
 If SortRichtung.l = 1
   SortStructuredList(NamenListe(), 2, OffsetOf(Namen\SortName.s), #PB_Sort_String)
 ElseIf SortRichtung.l = -1
   SortStructuredList(NamenListe(), 3, OffsetOf(Namen\SortName.s), #PB_Sort_String)
 EndIf
EndProcedure

Procedure ZeigeDaten()
  ClearGadgetItemList(#ListIcon_0) 
  PosAnzahl.l = CountList(NamenListe())
  ResetList(NamenListe())
  For i = 1 To PosAnzahl.l
    NextElement(NamenListe())
    AddGadgetItem(#ListIcon_0, -1, NamenListe.Namen()\Name.s )
  Next i
EndProcedure

Procedure.s ErzeugeNamen()
  Name.s=""
  For i = 1 To 7
    Buchstabe.l = Random(57)+66
    If Buchstabe.l = 91
      Buchstabe.l = 196
    ElseIf Buchstabe.l = 92
      Buchstabe.l = 228
    ElseIf Buchstabe.l = 93
      Buchstabe.l = 214
    ElseIf Buchstabe.l = 94
      Buchstabe.l = 246
    ElseIf Buchstabe.l = 95
      Buchstabe.l = 220
    ElseIf Buchstabe.l = 96
      Buchstabe.l = 252
    ElseIf Buchstabe.l = 123
      Buchstabe.l = 223
    EndIf  
    Name.s = Name.s+Chr(Buchstabe.l)
  Next i
  ProcedureReturn Name.s
EndProcedure

Procedure Open_Window_0()
  If OpenWindow(#Window_0, 216, 0, 150, 390, "PureSORT Test",  #PB_Window_SystemMenu | #PB_Window_TitleBar | #PB_Window_ScreenCentered)
    If CreateGadgetList(WindowID(#Window_0))
      ButtonGadget(#Button_Auf, 5, 300, 140, 20, "sortiere aufsteigend")
      ButtonGadget(#Button_Ab, 5, 330, 140, 20, "sortiere absteigend")
      ButtonGadget(#Button_NameHinzu, 5, 360, 140, 20, "Namen hinzufügen")
      ListIconGadget(#ListIcon_0, 5, 5, 140, 285, "Name", 136)
      PosAnzahl.l = CountList(NamenListe())
      ResetList(NamenListe())
      For i = 1 To PosAnzahl.l
        NextElement(NamenListe())
        AddGadgetItem(#ListIcon_0, -1, NamenListe.Namen()\Name.s )
      Next i
    EndIf
  EndIf
EndProcedure

DatenLaden()
Open_Window_0()

;
Repeat
  Event = WaitWindowEvent()
  WindowID = EventWindow()
  GadgetID = EventGadget()
  EventType = EventType()

  If Event = #PB_Event_Gadget 
    If GadgetID = #Button_Auf
      SortDaten(1)
      ZeigeDaten()
    ElseIf GadgetID = #Button_Ab
      SortDaten(-1)
      ZeigeDaten()
    ElseIf GadgetID = #Button_NameHinzu
      NeuerName.s = ErzeugeNamen()
      AddElement(NamenListe.Namen())
      NamenListe.Namen()\Name.s = NeuerName.s

      ZeigeDaten()
    EndIf  
  EndIf
  
Until Event = #PB_Event_CloseWindow 
Jürgen
Benutzeravatar
Kiffi
Beiträge: 10711
Registriert: 08.09.2004 08:21
Wohnort: Amphibios 9

Beitrag von Kiffi »

@Num500: Interessant, dass der Milchmüller beim mehrfachen Klick auf
'sortiere aufsteigend' die Position wechselt. Ist meines Erachtens nicht
Dein Fehler sondern eine Seltsamkeit von PB(?)

Grüße ... Kiffi

P.S.: Deinen Code kann man quantitativ optimieren. Wenn Du wissen
möchtest, welche Änderungen ich vorschlagen würde, dann lass es mich
wissen. ;-)
a²+b²=mc²
Num500
Beiträge: 21
Registriert: 23.12.2004 18:50
Wohnort: Buchholz

Beitrag von Num500 »

Hallo Kiffi,
P.S.: Deinen Code kann man quantitativ optimieren. Wenn Du wissen
möchtest, welche Änderungen ich vorschlagen würde, dann lass es mich
wissen.
ja gerne.

Jürgen
Benutzeravatar
Shardik
Beiträge: 746
Registriert: 25.01.2005 12:19

Beitrag von Shardik »

@dysti,
man kann es durchaus so machen, daß man in einer Struktur für jeden Namen zwei Strings definiert (einen unveränderten und einen mit expandierten Umlauten), wie es ja auch Num500 gezeigt hat. Allerdings ist diese Vorgehensweise nicht sehr ressourcensparend, vor allem bei großen Dateibeständen. Ich meinte bei meinen ersten Postings, daß man in zwei temporäre Puffer immer nur die Namen für den aktuellen Vergleich kopiert und dann nur dort die Umlaute expandiert bzw. abändert.

Hier ein Beispiel, das demonstriert, daß man bei Umlauten die PureBASIC Sort()-Funktionen für die Telefonbuch-Methode nicht verwenden kann und wie das Ganze bei Zuhilfenahme des einfachsten Sortieralgorithmus Bubble Sort und Benutzung von zwei Hilfsvariablen (Puffer) aussieht:

Code: Alles auswählen

Procedure.S UmlautConversion(Name.S)
  Name = ReplaceString(Name, "Ä", "Ae")
  Name = ReplaceString(Name, "Ö", "Oe")
  Name = ReplaceString(Name, "Ü", "Ue")
  Name = ReplaceString(Name, "ä", "ae")
  Name = ReplaceString(Name, "ö", "oe")
  Name = ReplaceString(Name, "ü", "ue")
  Name = ReplaceString(Name, "ß", "ss")
  ProcedureReturn Name
EndProcedure

TempName1.S
TempName2.S

Dim NameList.S(3)

NameList(0) = "Goldmann"
NameList(1) = "Götz"
NameList(2) = "Göbel"
NameList(3) = "Goethe"

SortArray(NameList(), 0)

Debug "Sortiert mit PB-Funktion SortArray():"

For i = 0 To 3
  Debug NameList(i)
Next i

Debug ""

NameList(0) = "Goldmann"
NameList(1) = "Götz"
NameList(2) = "Göbel"
NameList(3) = "Goethe"

; Bubble Sort

NumNames = 4

For j = NumNames - 2 To 0 Step -1
  For i = 0 To j
    TempName1 = UmlautConversion(NameList(i))
    TempName2 = UmlautConversion(NameList(i + 1))

    If TempName1 > TempName2
      Swap NameList(i), NameList(i + 1)
    EndIf
  Next i
Next j

Debug "Sortiert mit Bubble Sort Algorithmus und Umlaut-Umsetzung:"

For i = 0 To 3
  Debug NameList(i)
Next i
Benutzeravatar
Kiffi
Beiträge: 10711
Registriert: 08.09.2004 08:21
Wohnort: Amphibios 9

Beitrag von Kiffi »

Num500 hat geschrieben:ja gerne.
ok. Zum einen sind das Deine Variablen.

* Wenn Du Ihnen einmal einen Datentyp zugewiesen hast, dann brauchst
Du diesen bei der weiteren Verwendung nicht mehr gesondert angeben.

Z.B.:

Code: Alles auswählen

Define.s myVar   ; Hier wird 'myVar' als Variable vom Typ 'String' definiert
myVar.s = "Lala" ; das .s ist hier zwar nicht falsch, aber unnötig
myVar = "Lala"   ; besser
ziemlich deutlich wird das bei Deiner LinkedList:

Code: Alles auswählen

NamenListe.Namen()\Name.s = "Milchmüller"

... kann gekürzt werden zu ...

Code: Alles auswählen

NamenListe()\Name = "Milchmüller"
* Wenn Du alle Elemente einer LinkedList durchlaufen willst, kannst Du
ForEach verwenden. Also anstelle von:

Code: Alles auswählen

PosAnzahl.l = CountList(NamenListe())
ResetList(NamenListe())
For i = 1 To PosAnzahl.l
  NextElement(NamenListe())
  AddGadgetItem(#ListIcon_0, -1, NamenListe()\Name )
Next i

... kannst Du folgendes schreiben:

Code: Alles auswählen

ForEach NamenListe()
  AddGadgetItem(#ListIcon_0, -1, NamenListe()\Name )
Next
* Wenn Du eine Zeichenkette mit ReplaceString() ersetzen möchtest, dann
brauchst Du nicht vorher zu testen, ob diese darin vorhanden ist.
Entweder sie ist drin (dann wird sie ersetzt) oder sie ist nicht drin (dann
passiert nix). Also:

Code: Alles auswählen

If FindString(NamenListe()\SortName, "ß",1)
 NamenListe()\SortName = ReplaceString(NamenListe()\Name, "ß", "ss")
EndIf

... kann gekürzt werden zu:

Code: Alles auswählen

NamenListe()\SortName = ReplaceString(NamenListe()\Name, "ß", "ss")
* Wenn Du Variablen nur innerhalb einer Prozedur verwendest, solltest Du
diese als Protected deklarieren, damit sie nicht zufälligerweise mit globalen
Variablen kollidieren. Also nicht so:

Code: Alles auswählen

Procedure.s ErzeugeNamen()
  Name.s=""
  For i = 1 To 7 
    [...]
... sondern so:

Code: Alles auswählen

Procedure.s ErzeugeNamen()

 Protected Name.s
 Protected i.l

  For i = 1 To 7 
    [...]
So, das war's auf die Schnelle.

Grüße ... Kiffi
a²+b²=mc²
Antworten