aboutsummaryrefslogtreecommitdiff
path: root/challenge-094/paulo-custodio/forth/ch-1.fs
blob: 22475142a51cb0efbb73ac09a6c034758660735c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
#! /usr/bin/env gforth

\ Challenge 094
\
\ TASK #1 � Group Anagrams
\ Submitted by: Mohammad S Anwar
\ You are given an array of strings @S.
\
\ Write a script to group Anagrams together in any random order.
\
\ An Anagram is a word or phrase formed by rearranging the letters of a
\ different word or phrase, typically using all the original letters exactly
\ once.
\
\ Example 1:
\     Input: ("opt", "bat", "saw", "tab", "pot", "top", "was")
\     Output: [ ("bat", "tab"),
\               ("saw", "was"),
\               ("top", "pot", "opt") ]
\ Example 2:
\     Input: ("x")
\     Output: [ ("x") ]

\ -----------------------------------------------------------------------------
\ counted strings

\ compare two counted strings
: cstr=         ( c-addr1 c-addr2 -- f )
    >R COUNT R> COUNT STR=
;

\ copy string as counted string to pad
: str>pad       ( addr len -- )
    DUP PAD C!
    PAD 1+ SWAP CMOVE
;

\ allot string as counted string
: cstr,         ( addr len -- )
    DUP C, >R                   \ store length
    HERE R@ CMOVE               \ copy string
    R> ALLOT                    \ advance HERE
    ALIGN
;

\ -----------------------------------------------------------------------------
\ Linked list of strings

\ create a new string list
: new_strlist           ( -- list-addr )
    HERE  0 , 0 ,
;

: strlist.head          ( list-addr -- head-addr )
;

: strlist.tail          ( list-addr -- tail-addr )
    1 CELLS +
;

\ create a new node for a string list
: new_strnode           ( addr len -- node-addr )
    HERE >R             ( R:node-addr )
    0 ,                 \ store next pointer
    cstr,               \ store string
    R>                  ( node-addr )
;

: strnode.next          ( node-addr -- next-addr )
;

: strnode.cstring       ( node-addr -- cstring-addr )
    1 CELLS +
;

\ push a string to the end of the list
: strlist.push          { addr len list-addr -- }
    addr len new_strnode    ( node-addr )
    list-addr strlist.head @ 0= IF  \ list is empty
        DUP list-addr strlist.head !
        list-addr strlist.tail !
    ELSE
        DUP
        list-addr strlist.tail @        ( new-node last-node )
        strnode.next !                  \ store node in next of last node
        list-addr strlist.tail !        \ point tail to node
    THEN
;

: strlist.print-sep         ( first -- first )
    DUP IF
        DROP 0
    ELSE
        ." , "
    THEN
;


: strlist.print         ( list-addr -- )
    1 SWAP              ( first list-addr )
    '(' EMIT
    strlist.head @      ( first node-addr )
    BEGIN DUP 0<> WHILE \ while pointer not null
        SWAP strlist.print-sep SWAP
        '"' EMIT
        DUP strnode.cstring COUNT TYPE
        '"' EMIT
        strnode.next @
    REPEAT
    ')' EMIT
    2DROP
;

\ -----------------------------------------------------------------------------
\ map of string to linked list of strings

\ create new map
: new_map               ( -- map-addr )
    HERE  0 , 0 ,
;

: map.head              ( map-addr -- head-addr )
;

: map.tail              ( map-addr -- tail-addr )
    1 CELLS +
;

\ create a new node for a map
: new_mapnode           ( addr len -- node-addr )
    HERE >R             ( R:node-addr )
    0 ,                 \ store next pointer
    new_strlist DROP    \ store list of string values
    cstr,               \ store key string
    R>                  ( node-addr )
;

: mapnode.next          ( node-addr -- next-addr )
;

: mapnode.strlist       ( node-addr -- strlist-addr )
    1 CELLS +
;

: mapnode.key           ( node-addr -- cstring-addr )
    3 CELLS +
;

: map.add_first         ( node-addr map-addr -- )
    2DUP
    map.head !
    map.tail !
;

: map.append            { node-addr map-addr -- }
    node-addr map-addr map.tail @ mapnode.next !    \ point next of last to node
    node-addr map-addr map.tail !                   \ point tail to node
;

: map.find_node         ( map-addr -- node-addr|0 ) \ find node with key in PAD
    map.head @                      ( nope-addr )
    BEGIN DUP 0<> WHILE
        DUP mapnode.key PAD cstr= IF \ same key?
            EXIT                    ( node-addr ) \ of found key
        THEN
        mapnode.next @              \ point to next
    REPEAT
                                    ( 0 ) \ entry not found
;

\ add/search a key to a map, return strlist
: map.add_key           { addr len map-addr -- node-strlist }
    addr len str>pad                    \ save key in PAD
    map-addr map.head @ 0= IF           \ map is empty
        PAD COUNT new_mapnode           ( node-addr )
        DUP map-addr map.add_first      \ add first entry
        mapnode.strlist                 ( node-strlist )
    ELSE
        map-addr map.find_node          ( node-addr|0 )
        ?DUP IF
            mapnode.strlist             ( node-strlist )
        ELSE
            PAD COUNT new_mapnode       ( node-addr )
            DUP map-addr map.append     \ append node
            mapnode.strlist             ( node-strlist )
        THEN
    THEN
;

: map.print-sep         ( first -- first )
    DUP IF
        DROP 0
    ELSE
        ',' EMIT CR 2 SPACES
    THEN
;

: map.print         ( map-addr -- )
    1 SWAP                      ( first map-addr )
    ." [ "
    map.head @                  ( first node-addr )
    BEGIN DUP 0<> WHILE
        SWAP map.print-sep SWAP ( node-addr )
        DUP mapnode.strlist strlist.print   \ print values
        mapnode.next @          \ move to next
    REPEAT
    2DROP
    ."  ]" CR
;

\ -----------------------------------------------------------------------------
\ sort a string

\ selection sort
: str-sort          { addr len }
                            \ outer loop
    len 1- 0 ?DO            \ i1: 0..len-1
        I                   ( min-idx=i1 )

                            \ inner loop: search minimum value
        len I 1+ ?DO        \ i2: i1+1..len

            I addr + C@     ( min-idx c[i2] )
            OVER addr + C@  ( min-idx c[i2] c[min-idx])
            < IF
                DROP I      ( min-idx=i2 )
            THEN
        LOOP

                            \ swap values
        I addr + C@         ( min-idx c[i1] )
        OVER addr + C@      ( min-idx c[i1] c[min-idx] )
        I addr + C!         ( min-idx c[i] )
        SWAP addr + C!      ( )
    LOOP
;

\ -----------------------------------------------------------------------------
\ main program
\ read strings from args, built map
\ print map

: build-map         { map -- }
    BEGIN NEXT-ARG ?DUP WHILE   \ read each argument
        2DUP str>pad            \ copy string to PAD
        PAD COUNT str-sort      \ build key (sorted chars of string) in PAD
                                ( arg-addr arg-size )
        PAD COUNT map map.add_key   ( arg-addr arg-size node-strlist )
        strlist.push            \ add string to values list
    REPEAT DROP
;

new_map value map
map build-map
map map.print
bye