It is currently Wed Jun 19, 2013 1:34 pm

All times are UTC + 1 hour




Post new topic Reply to topic  [ 7 posts ] 
Author Message
 Post subject: Multi-field sorting for a structured array
PostPosted: Thu Mar 08, 2012 4:05 am 
Offline
Addict
Addict
User avatar

Joined: Mon Jul 25, 2005 3:51 pm
Posts: 2427
Location: Utah, USA
Here is some code I created for sorting a structured array on multiple fields. This code is needed because sorting an array in PB is not performed with a stable sort. The procedures I created below will allow you to sort on multiple fields at once (so to speak). It uses PureBasic's SortStructuredArray() function to do the main work but selectively calls it only over certain ranges and with selected parameters to accomplish the desired multi-field sort.

Because PB's SortStructuredArray() function requires a specific structured array parameter and not a pointer to an array I have written a macro that creates the needed procedures for an array of a specific structure. For instance to create the procedures to be used for arrays with the structure 'point' you would call the macro mcr_multiSort(point). This would create the procedure multiSort_point() that would be called with parameters as multiSort_point(a.point(), sortSpecs()).

The sortSpecs() array contains the details of the sorting for each field being sorted. Its dimensions must be one less than the number of fields being sorted. It is structured with the structure sortFieldSpecs which contains entries for the offset of the field, it's type, and the option for sorting. Each of those fields correspond to one of the parameters in PB's SortStructuredArray() function.

I've arranged the code as an include:

Code:
;Program: Multi-Field Sort for Structured Arrays.
;Name:  Array_MultiFieldSort.pbi
;Author: Demivec
;Date: 3/12/2012
;Written for PureBasic v4.61

EnableExplicit

;-sort routines
Enumeration   
  ;The standard equality functions return one of the following comparison
  ;results.
  #isNotEqualTo = 0
  #isEqualTo
EndEnumeration

;=================================================================================
; Description: Each of the equality functions compares two elements that are a
;              PureBasic standard data type for equality and returns a boolean
;              result.  The elements are at addresses [*element_1] and [*element_2].
;
; Returns: Returns '#isEqualTo' if the first element is equal to
;          element two, otherwise it returns '#isNotEqualTo'.
;
; Note: A given equality function can only operate on compatible data and so care
;       must be taken in using the correct function.  These functions are only
;       used by the subSort() function.
;=================================================================================
Macro mcr_equality_standardTypes(type, subStructure)
  Procedure _isEqual_#type(*element_1.type, *element_2.type)
    If *element_1\subStructure = *element_2\subStructure
      ProcedureReturn #isEqualTo
    EndIf
    ProcedureReturn #isNotEqualTo
  EndProcedure
EndMacro

mcr_equality_standardTypes(byte, b)
mcr_equality_standardTypes(word, w)
mcr_equality_standardTypes(long, l)
mcr_equality_standardTypes(integer, i)
mcr_equality_standardTypes(quad, q)
mcr_equality_standardTypes(float, f)
mcr_equality_standardTypes(double, d)
mcr_equality_standardTypes(ascii, a)
mcr_equality_standardTypes(character, c)
mcr_equality_standardTypes(unicode, u)
mcr_equality_standardTypes(string, s)

DataSection
  _standardEqualityFunctions:
  ;Equality functions for each of the standard element types.
  ;Data must be present for each of PB's standard sort compare types.
  ;A line for each parameter-constant followed by the address of its equality function.
  Data.i 11 ;1 based count of equality functions
  Data.i #PB_Sort_Byte     , @_isEqual_byte()     
  Data.i #PB_Sort_Word     , @_isEqual_word()     
  Data.i #PB_Sort_Long     , @_isEqual_long()     
  Data.i #PB_Sort_integer  , @_isEqual_Integer()  ;same as either long or quad, technically this entry is an unnecessary duplicate
  Data.i #PB_Sort_Quad     , @_isEqual_quad()     
  Data.i #PB_Sort_Float    , @_isEqual_float()   
  Data.i #PB_Sort_Double   , @_isEqual_double()   
  Data.i #PB_Sort_Ascii    , @_isEqual_ascii()   
  Data.i #PB_Sort_Character, @_isEqual_character()
  Data.i #PB_Sort_Unicode  , @_isEqual_unicode() 
  Data.i #PB_Sort_String   , @_isEqual_string()   
EndDataSection

Prototype.i equalityFunction(*element_1, *element_2)

Global Dim *equalityFunctionAdr(0)

Procedure setupEqualityFunctions()
  Shared *equalityFunctionAdr()
  Protected i, equalityFunctionCount, dataType

  Restore _standardEqualityFunctions
  Read.i equalityFunctionCount
  For i = 1 To equalityFunctionCount
    Read.i dataType
    If dataType > ArraySize(*equalityFunctionAdr())
      Redim *equalityFunctionAdr(dataType)
    EndIf
    Read.i *equalityFunctionAdr(dataType)
  Next
EndProcedure

setupEqualityFunctions()

Structure sortFieldSpecs
  offset.i
  type.i
  option.i
EndStructure

;The multiSort procedure created ends with the structure name i.e. multiSort_struc()
;The multiSort procedure has parameters {arr(), sortSpecs() [, rangeStart, rangeEnd]}.
;rangeStart and rangeEnd are optional and if either of them is specified they both
;must be specified.
Macro mcr_multiSort(struc)
  ;multiSort() and subSort() only are set up for use with the given structure
  Procedure subSort_#struc(Array x.struc(1), Array sortFieldSpecs.sortFieldSpecs(1), sortLevel, rangeStart, rangeEnd)
    Protected i, firstElement = rangeStart, prevSortLevel = sortLevel - 1
    Protected equalityFunc.equalityFunction = *equalityFunctionAdr(sortFieldSpecs(prevSortLevel)\type)
   
    For i = rangeStart + 1 To rangeEnd
      If equalityFunc(@x(firstElement) + sortFieldSpecs(prevSortLevel)\offset, @x(i) + sortFieldSpecs(prevSortLevel)\offset) <> #isEqualTo
        If i - firstElement > 1
          SortStructuredArray(x(), sortFieldSpecs(sortLevel)\option, sortFieldSpecs(sortLevel)\offset, sortFieldSpecs(sortLevel)\type, firstElement, i - 1)
          If sortLevel < ArraySize(sortFieldSpecs())
            subSort_#struc(x(), sortFieldSpecs(), sortLevel + 1, firstElement, i - 1)
          EndIf
        EndIf
        firstElement = i
      EndIf
    Next
   
    If rangeEnd - firstElement > 0
      SortStructuredArray(x(), sortFieldSpecs(sortLevel)\option, sortFieldSpecs(sortLevel)\offset, sortFieldSpecs(sortLevel)\type, firstElement, rangeEnd)
      If sortLevel < ArraySize(sortFieldSpecs())
        subSort_#struc(x(), sortFieldSpecs(), sortLevel + 1, firstElement, rangeEnd)
      EndIf
    EndIf
  EndProcedure
 
  Procedure multiSort_#struc(Array x.struc(1), Array sortFieldSpecs.sortFieldSpecs(1), rangeStart = -1, rangeEnd = -1)
    If rangeStart = -1 Or rangeEnd = -1
      rangeStart = 0: rangeEnd = ArraySize(x())
    EndIf
    SortStructuredArray(x(), sortFieldSpecs(0)\option, sortFieldSpecs(0)\offset, sortFieldSpecs(0)\type, rangeStart, rangeEnd)
    If ArraySize(sortFieldSpecs()) > 0 And ArraySize(x()) > 1
      subSort_#struc(x(), sortFieldSpecs(), 1, rangeStart, rangeEnd)
    EndIf
  EndProcedure
EndMacro

;This helper macro makes it possible to specify on one line the sort options for an array of
;sortFieldSpecs at a specified index.
Macro mcr_setSortSpec(arrName, index, _offset, _type, _option = #PB_Sort_Ascending)
  arrName(index)\offset = _offset
  arrName(index)\type = _type
  arrName(index)\option = _option
EndMacro


The macro mcr_setSortSpec() is provided so that the sortSpecs for an index can be specified on a single line.

Here's some demonstration code that uses the include file:

Code:
;Demonstration code for Multi-Field Sort of a Structured Array.
;Author: Demivec
;Written for PureBasic v4.61
;Date: 3/7/2012

EnableExplicit

IncludeFile "Array_MultiFieldSort.pbi"

Structure myOwn
  a.w
  b$
  c.i
  d$
EndStructure

Procedure display_myOwn(Array x.myOwn(1), header.s = "", footer.s = "")
  Protected i, tw = 8
  Debug header
  Debug LSet("a", tw) + LSet("b$", tw) + LSet("c", tw) + LSet("d$", tw)
  Debug LSet("---", tw) + LSet("---", tw) + LSet("---", tw) + LSet("---", tw)
  For i = 0 To ArraySize(x())
    Debug LSet(Str(x(i)\a), tw) + LSet(x(i)\b$, tw) + LSet(Str(x(i)\c), tw) + LSet(x(i)\d$, tw)
  Next
  Debug footer
EndProcedure

;-sort routines
mcr_multiSort(myOwn) ;create procedure multiSort_myOwn()

;-setup random data
Define i, entryCount = 500000
Dim myOwn.myOwn(entryCount)
For i = 0 To entryCount
  myOwn(i)\a = Random(10)
  myOwn(i)\b$ = Chr(Random(10) + 65)
  myOwn(i)\c = (Random(10) + 1) * 20000
  myOwn(i)\d$ = Chr(Random(10) + 65)
Next

display_myOwn(myOwn(), "-- Before Sort --")

;now for some heavy duty sorting
Dim myOwn_sortSpecs.sortFieldSpecs(3) ;Array should be sized to be one less than the number of fields to be sorted

mcr_setSortSpec(myOwn_sortSpecs, 0, OffsetOf(myOwn\d$), #PB_Sort_String, #PB_Sort_Ascending)
mcr_setSortSpec(myOwn_sortSpecs, 1, OffsetOf(myOwn\b$), #PB_Sort_String, #PB_Sort_Descending)
mcr_setSortSpec(myOwn_sortSpecs, 2, OffsetOf(myOwn\a), #PB_Sort_Word, #PB_Sort_Descending)
mcr_setSortSpec(myOwn_sortSpecs, 3, OffsetOf(myOwn\c), #PB_Sort_integer, #PB_Sort_Ascending)

multiSort_myOwn(myOwn(), myOwn_sortSpecs())

display_myOwn(myOwn(), "-- Sort by: d$ A, b$ D, a.w D, c.i A --") ;'A' and 'D' represent ascending and descending order


To resort with different fields, options, or the number of fields just redimension the sorSpecs() array and fill it in with the new options.


@Edit: separated the previous code into an include file and demonstration code.
Created a procedure for the initialization code so that the variables used for setup could be kept local to the procedure, leaving only one global variable defined.

Modified the multiSort() procedure to allow an optional range to be specified.

@Edit: corrected a small bug that skipped sorts of only 2 elements if they occurred at the end of a sub-sort range. Made a very minor change to cleanup code.

Added a default option of #PB_Sort_Ascending for the mcr_setSortSpec() macro. Thanks for skywalk for reminding me of this possibility.

_________________
Image


Last edited by Demivec on Mon Mar 12, 2012 6:30 pm, edited 2 times in total.

Top
 Profile  
 
 Post subject: Re: Multi-field sorting for a structured array
PostPosted: Thu Mar 08, 2012 4:31 am 
Offline
Addict
Addict
User avatar

Joined: Wed Dec 23, 2009 10:14 pm
Posts: 1410
Location: Boston, MA
Very cool Demivec!
I wish this was natively supported as in SortStructuredList().

_________________
To understand recursion, you must first understand recursion. ~ unknown
I never make stupid mistakes. Only very, very clever ones. ~ John Peel


Top
 Profile  
 
 Post subject: Re: Multi-field sorting for a structured array
PostPosted: Thu Mar 08, 2012 2:20 pm 
Offline
Enthusiast
Enthusiast
User avatar

Joined: Thu Jun 26, 2003 2:09 am
Posts: 731
Location: Spain (Galicia)
Thanks Demivec. Great code and very useful!


Top
 Profile  
 
 Post subject: Re: Multi-field sorting for a structured array
PostPosted: Thu Mar 08, 2012 4:29 pm 
Offline
Addict
Addict
User avatar

Joined: Mon Jul 25, 2005 3:51 pm
Posts: 2427
Location: Utah, USA
@skywalk, @einander: Thanks.

I've made some slight modifications to the first post that includes some useful structuring of the code with a slight improvement as well. It should be ready for primetime now. :)

_________________
Image


Top
 Profile  
 
 Post subject: Re: Multi-field sorting for a structured array
PostPosted: Mon Mar 12, 2012 1:38 am 
Offline
Addict
Addict
User avatar

Joined: Wed Dec 23, 2009 10:14 pm
Posts: 1410
Location: Boston, MA
Hi Demivec.
Trying to use this I found a weird case where #PB_Sort_NoCase doesn't affect the outcome?
Am I doing something wrong or is there a bug in the SortStructuredArray() when Or'ing options?
So many macros it's tough to debug... :(
Where are you Macro Expander :evil:

Code:
;Program: Multi-Field Sort for Structured Arrays.
;Name:  Array_MultiFieldSort.pbi
;Author: Demivec
;Date: 3/7/2012
;Written for PureBasic v4.61

EnableExplicit

;-sort routines
Enumeration   
  ;The standard equality functions return one of the following comparison
  ;results.
  #isNotEqualTo = 0
  #isEqualTo
EndEnumeration

;=================================================================================
; Description: Each of the equality functions compares two elements that are a
;              PureBasic standard data type for equality and returns a boolean
;              result.  The elements are at addresses [*element_1] and [*element_2].
;
; Returns: Returns '#isEqualTo' if the first element is equal to
;          element two, otherwise it returns '#isNotEqualTo'.
;
; Note: A given equality function can only operate on compatible data and so care
;       must be taken in using the correct function.  These functions are only
;       used by the subSort() function.
;=================================================================================
Macro mcr_equality_standardTypes(type, subStructure)
  Procedure _isEqual_#type(*element_1.type, *element_2.type)
    If *element_1\subStructure = *element_2\subStructure
      ProcedureReturn #isEqualTo
    EndIf
    ProcedureReturn #isNotEqualTo
  EndProcedure
EndMacro

mcr_equality_standardTypes(byte, b)
mcr_equality_standardTypes(word, w)
mcr_equality_standardTypes(long, l)
mcr_equality_standardTypes(integer, i)
mcr_equality_standardTypes(quad, q)
mcr_equality_standardTypes(float, f)
mcr_equality_standardTypes(double, d)
mcr_equality_standardTypes(ascii, a)
mcr_equality_standardTypes(character, c)
mcr_equality_standardTypes(unicode, u)
mcr_equality_standardTypes(string, s)

DataSection
  _standardEqualityFunctions:
  ;Equality functions for each of the standard element types.
  ;Data must be present for each of PB's standard sort compare types.
  ;A line for each parameter-constant followed by the address of its equality function.
  Data.i 11 ;1 based count of equality functions
  Data.i #PB_Sort_Byte     , @_isEqual_byte()     
  Data.i #PB_Sort_Word     , @_isEqual_word()     
  Data.i #PB_Sort_Long     , @_isEqual_long()     
  Data.i #PB_Sort_Integer  , @_isEqual_Integer()  ;same as either long or quad, technically this entry is an unnecessary duplicate
  Data.i #PB_Sort_Quad     , @_isEqual_quad()     
  Data.i #PB_Sort_Float    , @_isEqual_float()   
  Data.i #PB_Sort_Double   , @_isEqual_double()   
  Data.i #PB_Sort_Ascii    , @_isEqual_ascii()   
  Data.i #PB_Sort_Character, @_isEqual_character()
  Data.i #PB_Sort_Unicode  , @_isEqual_unicode() 
  Data.i #PB_Sort_String   , @_isEqual_string()   
EndDataSection

Prototype.i equalityFunction(*element_1, *element_2)

Global Dim *equalityFunctionAdr(0)

Procedure setupEqualityFunctions()
  Shared *equalityFunctionAdr()
  Protected i, equalityFunctionCount, dataType

  Restore _standardEqualityFunctions
  Read.i equalityFunctionCount
  For i = 1 To equalityFunctionCount
    Read.i dataType
    If dataType > ArraySize(*equalityFunctionAdr())
      ReDim *equalityFunctionAdr(dataType)
    EndIf
    Read.i *equalityFunctionAdr(dataType)
  Next
EndProcedure

setupEqualityFunctions()

Structure sortFieldSpecs
  offset.i
  type.i
  option.i
EndStructure

;The multiSort procedure created ends with the structure name i.e. multiSort_struc()
;The multiSort procedure has parameters {arr(), sortSpecs() [, rangeStart, rangeEnd]}.
;rangeStart and rangeEnd are optional and if either of them is specified they both
;must be specified.
Macro mcr_multiSort(struc)
  ; multiSort() and subSort() only are set up for use with the given structure
  Procedure subSort_#struc(Array x.struc(1), Array sortFieldSpecs.sortFieldSpecs(1), sortLevel, rangeStart, rangeEnd)
    Protected i = rangeStart, firstElement, prevSortLevel = sortLevel - 1
    Protected equalityFunc.equalityFunction = *equalityFunctionAdr(sortFieldSpecs(prevSortLevel)\type)
    firstElement = rangeStart
    For i = rangeStart + 1 To rangeEnd
      If equalityFunc(@x(firstElement) + sortFieldSpecs(prevSortLevel)\offset, @x(i) + sortFieldSpecs(prevSortLevel)\offset) <> #isEqualTo
        If i - firstElement > 1
          SortStructuredArray(x(), sortFieldSpecs(sortLevel)\option, sortFieldSpecs(sortLevel)\offset, sortFieldSpecs(sortLevel)\type, firstElement, i - 1)
          If sortLevel < ArraySize(sortFieldSpecs())
            subSort_#struc(x(), sortFieldSpecs(), sortLevel + 1, firstElement, i - 1)
          EndIf
        EndIf
        firstElement = i
      EndIf
    Next
    If rangeEnd - firstElement > 1
      SortStructuredArray(x(), sortFieldSpecs(sortLevel)\option, sortFieldSpecs(sortLevel)\offset, sortFieldSpecs(sortLevel)\type, firstElement, rangeEnd)
      If sortLevel < ArraySize(sortFieldSpecs())
        subSort_#struc(x(), sortFieldSpecs(), sortLevel + 1, firstElement, rangeEnd)
      EndIf
    EndIf
  EndProcedure
 
  Procedure multiSort_#struc(Array x.struc(1), Array sortFieldSpecs.sortFieldSpecs(1), rangeStart = -1, rangeEnd = -1)
    If rangeStart = -1 Or rangeEnd = -1
      rangeStart = 0: rangeEnd = ArraySize(x())
    EndIf
    SortStructuredArray(x(), sortFieldSpecs(0)\option, sortFieldSpecs(0)\offset, sortFieldSpecs(0)\type, rangeStart, rangeEnd)
    If ArraySize(sortFieldSpecs()) > 0 And ArraySize(x()) > 1
      subSort_#struc(x(), sortFieldSpecs(), 1, rangeStart, rangeEnd)
    EndIf
  EndProcedure
EndMacro

; This helper macro makes it possible to specify on one line the sort options for an array of
; sortFieldSpecs at a specified index.
Macro mcr_setSortSpec(arrName, index, _offset, _type, _option=#PB_Sort_Ascending)
  arrName(index)\offset = _offset
  arrName(index)\type = _type
  arrName(index)\option = _option
EndMacro

;-{ TEST
EnableExplicit
;IncludeFile "Array_MultiFieldSort.pbi"
Structure myOwn
  a.i
  b.d
  c$
EndStructure

Procedure display_myOwn(Array x.myOwn(1), header.s = "", footer.s = "")
  Protected i, tw = 8
  Debug header
  Debug LSet("a", tw) + LSet("b", tw) + LSet("c$", tw)
  Debug LSet("---", tw) + LSet("---", tw) + LSet("---", tw)
  For i = 0 To ArraySize(x())
    Debug LSet(Str(x(i)\a), tw) + LSet(Str(x(i)\b), tw) + LSet(x(i)\c$, tw)
  Next
  Debug footer
EndProcedure

mcr_multiSort(myOwn) ; creates procedure multiSort_myOwn()

Define.i i, entryCount
Define.s r$
Restore SortThis
Read.i entryCount
Dim myOwn.myOwn(entryCount-1)
For i = 0 To entryCount-1
  Read.s r$: myOwn(i)\a = Val(r$)
  Read.s r$: myOwn(i)\b = ValD(r$)
  Read.s myOwn(i)\c$
Next
display_myOwn(myOwn(), "-- Before Sort --")

Dim myOwn_sortSpecs.sortFieldSpecs(2) ; Array should be sized to be one less than the number of fields to be sorted
mcr_setSortSpec(myOwn_sortSpecs, 0, OffsetOf(myOwn\a), #PB_Sort_Integer)
mcr_setSortSpec(myOwn_sortSpecs, 1, OffsetOf(myOwn\b), #PB_Sort_Double)
;mcr_setSortSpec(myOwn_sortSpecs, 2, OffsetOf(myOwn\c$), #PB_Sort_String)
mcr_setSortSpec(myOwn_sortSpecs, 2, OffsetOf(myOwn\c$), #PB_Sort_String,#PB_Sort_Ascending|#PB_Sort_NoCase)

multiSort_myOwn(myOwn(), myOwn_sortSpecs())
display_myOwn(myOwn(), "-- Sort by: a.i+, b.d+, c$+ --")
DataSection
  SortThis:
  Data.i 12
  ;       a,   b,   c$
  Data.s "1", "9", "1"
  Data.s "2", "2", "5"
  Data.s "3", "1", "b"
  Data.s "4", "1", "a"
  Data.s "5", "3", "z"
  Data.s "7", "3", "y"
  Data.s "6", "3", "3"
  Data.s "8", "3", "2"
  Data.s "9", "3", "x"
  Data.s "7", "5", "a"  ;<- The problem of a 3rd sort appears when both Field1 and Field2 are duplicates
  Data.s "7", "5", "W"  ;<- Field1 = primary, Field2 = secondary, etc.
  Data.s "7", "4", "x"
EndDataSection
; mcr_setSortSpec(myOwn_sortSpecs, 2, OffsetOf(myOwn\c$), #PB_Sort_String)
; -- Sort by: a.i+, b.d+, c$+ --
; a       b       c$     
; ---     ---     ---     
; 1       9       1       
; 2       2       5       
; 3       1       b       
; 4       1       a       
; 5       3       z       
; 6       3       3       
; 7       3       y       
; 7       4       x       
; 7       5       W       ;<-- 3rd Column Sort OK
; 7       5       a       ;<-- because 'W' < 'a'
; 8       3       2       
; 9       3       x       

; mcr_setSortSpec(myOwn_sortSpecs, 2, OffsetOf(myOwn\c$), #PB_Sort_String,#PB_Sort_Ascending|#PB_Sort_NoCase)
; -- Sort by: a.i+, b.d+, c$+ --
; a       b       c$     
; ---     ---     ---     
; 1       9       1       
; 2       2       5       
; 3       1       b       
; 4       1       a       
; 5       3       z       
; 6       3       3       
; 7       3       y       
; 7       4       x       
; 7       5       W       ;<-- 3rd Column Sort FAIL using '#PB_Sort_NoCase'
; 7       5       a       ;<-- c$ should be 'a' then 'W'
; 8       3       2       
; 9       3       x       

_________________
To understand recursion, you must first understand recursion. ~ unknown
I never make stupid mistakes. Only very, very clever ones. ~ John Peel


Top
 Profile  
 
 Post subject: Re: Multi-field sorting for a structured array
PostPosted: Mon Mar 12, 2012 6:35 pm 
Offline
Addict
Addict
User avatar

Joined: Mon Jul 25, 2005 3:51 pm
Posts: 2427
Location: Utah, USA
skywalk wrote:
Trying to use this I found a weird case where #PB_Sort_NoCase doesn't affect the outcome?
Am I doing something wrong or is there a bug in the SortStructuredArray() when Or'ing options?

Thanks for reporting your results.

I have corrected a bug that skipped sorts of only 2 elements if they occurred at the end of a sub-sort range.

I also added a default option of #PB_Sort_Ascending for the mcr_setSortSpec(). Thanks for reminding me of this possibility.

Everything should be okeedokee now. :)

_________________
Image


Top
 Profile  
 
 Post subject: Re: Multi-field sorting for a structured array
PostPosted: Mon Mar 12, 2012 7:14 pm 
Offline
Addict
Addict
User avatar

Joined: Wed Dec 23, 2009 10:14 pm
Posts: 1410
Location: Boston, MA
Great, that fixed it. :!:
Ha!, Sorry I couldn't find this: If rangeEnd - firstElement > 0 ; Change 1 to 0

_________________
To understand recursion, you must first understand recursion. ~ unknown
I never make stupid mistakes. Only very, very clever ones. ~ John Peel


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 7 posts ] 

All times are UTC + 1 hour


Who is online

Users browsing this forum: No registered users and 3 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
Jump to:  

 


Powered by phpBB © 2008 phpBB Group
subSilver+ theme by Canver Software, sponsor Sanal Modifiye