set day_num(MON) 1
set day_num(TUES) 2
set day_num(WED) 3
set day_num(THURS) 4
set day_num(FRI) 5
set day_num(SAT) 6
sat day_num(SUN) 7
I can map symbolic names to numbers using $day_num($day); but what is the
simplest, most elegant way to map in both directions e.g. convert THURS to 4
or convert 3 to WED? Do I have to declare a list [MON TUES WED THURS FRI
SAT SUN]? I don't like having to list the symbols twice in my code.
TIA
Andrew.
Why not use a list like:
set conversions [list MON 1 TUES 2 WED 3 ...]
Find the element and depending on whether it is even or odd, take the
element
before or after it:
set idx [lsearch $conversions $elem]
set conv [lindex [expr {$idx%2==0? $idx+1 : $idx-1}]
(Hm, this can be written effectively as only one statement, but I will
leave that
to you :))
Regards,
Arjen
> Why not use a list like:
>
> set conversions [list MON 1 TUES 2 WED 3 ...]
He can do this also with his original array with:
---
proc reverseLookup {array value} {
global $array
puts [lindex [set l [array get $array]] [expr [lsearch $l $value]-1]]
}
reverseLookup day_num 1 => MON
reverseLookup day_num 2 => TUES
...
---
It has the disadvantage, that it only gives one match if the array contains
a value more than once (but this can be fixed with "-all" for lsearch and
then collecting the results into a list) and that it is very slow... So I
would also recommend using another data structure, which better fits
biderectional lookups. Or simply use two arrays where the second is
constructed from the first one by getting all index-value-pairs, swapping
index and value and write that to another array. So you have the
symbolnames only once in the source, but two arrays with very fast lookup.
Regards
Stephan
uwe
Doesn't do reverse mappings for you, since there's no reason for
values to be distinct. This means that the inverse mapping is expected
to be non-trivial, and so best handled at the script level where
ambiguity can be resolved according to the script's policy.
As a side note to anyone fiddling around with inverses: do be sure to
remember to try keys and values with spaces in...
Donal.
For quick and dirty and highly readable I usually do it like:
foreach {day num} {
MON 1
TUES 2
WED 3
THURS 4
FRI 5
SAT 6
SUN 7
} {
set day_num($day) $num
set day($num) $day
}
I often have complex initialisation requirements like this and this
technique allows me to write the initial values once and have them
assigned to multiple things. For example you could also do something
like:
foreach {day num} {
MON 1
TUES 2
WED 3
THURS 4
FRI 5
SAT 6
SUN 7
} {
proc $day {} [list return $num]
proc $num {} [list return $day]
}
Always feel free to treat your code (variable names, function names,
values etc.) as data to be looped over. I believe it's called
metaprogramming. Tcl makes metaprogramming so painless that not many
people would recognise the above as an example of metaprogramming.
> Arjen Markus wrote:
>
> > Why not use a list like:
> >
> > set conversions [list MON 1 TUES 2 WED 3 ...]
>
> He can do this also with his original array with:
>
> ---
> proc reverseLookup {array value} {
> global $array
> puts [lindex [set l [array get $array]] [expr [lsearch $l $value]-1]]
> }
...
What about like?
array set day2num {MON 1 TUE 2 WED 3 THU 4 FRI 5 SAT 6 SUN 7}
foreach {day num} [array get day2num] {set num2day($num) $day}
2 arrays but it's "simple" ;-)
--
Gerhard Reithofer
Tech-EDV Support Forum - http://support.tech-edv.co.at
I like this.
I like this.
Schelte
--
set Reply-To [string map {nospam schelte} $header(From)]
You could even use the same array for both, since the keys and values
are all distinct in this special case.
set day($day) $num
set day($num) $day
R'
or make it a proc !
proc day_num { type key } {
foreach {day num} {
MON 1
TUES 2
WED 3
THURS 4
FRI 5
SAT 6
SUN 7
} {
set num($day) $num
set day($num) $day
}
return [set [set type]($key)]
}
day_num num WED
day_num day 3
or something alike
As an optimisation, you'd want to pull the array creation out of the
proc, so you don't have to do it every time you invoke the proc.
--
Glenn Jackman
"You can only be young once. But you can always be immature." -- Dave Barry
Or this which lacks speed of array but does not need any code for
error checking
as internal commands provide reasonable defaults.
proc dayConvert { day } {
set daylist {{} MON TUES WED THURS FRI SAT SUN }
if { [ string is digit $day ] } {
return [lindex $daylist $day ]
}
return [lsearch $daylist $day ]
}
puts "Day [dayConvert MON ] is [dayConvert 1 ]"
puts "Day [dayConvert X ] is [dayConvert 84 ]"
outputs:
Day 1 is MON
Day -1 is
Carl
Of course another way to attack the problem would be to use the msgcat
package, then the day "names" could be I18N localized.
--
+--------------------------------+---------------------------------------+
| Gerald W. Lester |
|"The man who fights for his ideals is the man who is alive." - Cervantes|
+------------------------------------------------------------------------+
There is this enumeration at http://wiki.tcl.tk/1308 which may be of
use
Derek
proc makeEnum {name values} {
interp alias {} $name: {} lsearch $values
interp alias {} $name@ {} lindex $values
}
% makeEnum fruit {apple blueberry cherry date elderberry}
fruit@
% fruit: date
3
% fruit@ 2
cherry
> * "Andrew Holme" <and...@nospam.com>
> | > set day_num($day) $num
> | > set day($num) $day
> You could even use the same array for both, since the keys and values
> are all distinct in this special case.
> set day($day) $num
> set day($num) $day
I've done that kind of thing on several occasions... It's quite neat
and tidy if you don't mind the duplication. (I usually use a list,
though, for the reason I'll mention below.)
But on occasions when I've done it this way, I usually end up doing
something more along the lines of this;
set pair [list $num $day]
set day_list($day) $pair
set day_list($num) $pair
That way, given either a day name or number, you can obtain either
also. Otherwise you'll end up having to check it with something like
[string is number].
It also lends itself to a pair of helper functions which simply vary
in which item of the list they retrieve, but at the same time allow
you to fiddle and swap implementations at will, and of course, allow
for error handling.
Another thing I like about the list approach, is that you can do a
search for "$input*", fetching all matches, and give much better error
messages; if it returns an empty list, then winge that you don't have a
freaking clue what they're on about. If it returns a list with more
than one element, spit out the ones it found, and ask them to be a
little more specific. If it returns a list with just the one element,
then pluck it out and continue on. Although in a paired case like this
it gets slightly more hairy when you need to return the matches as
indices, and then convert them back.
The list approach also, I would think, is somewhat more memory
efficient, though certainly a little slower, neither point however
would make that much difference with a set of values so small.
Fredderic
If you have the number, why would you need to get the number?
same with name?
> That way, given either a day name or number, you can obtain either
> also. Otherwise you'll end up having to check it with something like
> [string is number].
>
> It also lends itself to a pair of helper functions which simply vary
> in which item of the list they retrieve, but at the same time allow
> you to fiddle and swap implementations at will, and of course, allow
> for error handling.
>
>
> Another thing I like about the list approach, is that you can do a
> search for "$input*", fetching all matches, and give much better error
> messages; if it returns an empty list, then winge that you don't have a
> freaking clue what they're on about. If it returns a list with more
> than one element, spit out the ones it found, and ask them to be a
> little more specific. If it returns a list with just the one element,
> then pluck it out and continue on.
you can do the same with [array names $input*]
> Although in a paired case like this
> it gets slightly more hairy when you need to return the matches as
> indices, and then convert them back.
>
> The list approach also, I would think, is somewhat more memory
> efficient, though certainly a little slower, neither point however
> would make that much difference with a set of values so small.
>
I had a complicated display once, tons of different things going on.
This was way early in my tcl/tk carreer and for the backing store of
info on everything I use a keyed list (from tclx). once things were
working ok, i started looking at the slowness, and just changing the
list storage to an array sped things up by at least 1 or 2 orders of
magnitude. a hash lookup is significantly faster than searching a list.
Of course if you are talking 7 entries (days of week) it won't make
much difference, but I got in the habbit of lookup tables as hashes
so I don;t have to worry about on getting larger than originally planned
and then having to change it. To me it is also simpler to use code-wise.
Bruce
> Fredderic wrote:
>> On occasions when I've done it this way, I usually end up doing
>> something more along the lines of this;
>> set pair [list $num $day]
>> set day_list($day) $pair
>> set day_list($num) $pair
> If you have the number, why would you need to get the number?
> same with name?
Because you might not KNOW that you have the number. And even if you
do, you might not have a "correct" number. If you care which one you
were given, number or name, then you won't mind testing the input and
handling each case specifically. But if you don't care, which I would
hazard to guess is the most common situation, then this allows you to
plug in the input, and get not only itself back, but its pair to pick
from, plus simple validation by [catch] or {eq ""}, depending on how
the helper is formed. Hence my next paragraph;
>> That way, given either a day name or number, you can obtain either
>> also. Otherwise you'll end up having to check it with something
>> like [string is number].
An example is reading a configuration file, and processing a boolean
value. Most often you want to know whether the value is true or false,
not specifically whether it's a "1" vs. the string "true". So you
shove it through [string is boolean] for validation, and then if it's
all good, just use it in your [if] statements, or [eval] it into the
specific form you need.
>> Another thing I like about the list approach, is that you can do a
>> search for "$input*" .....
> you can do the same with [array names $input*]
Of course you can. But if your set up to handle lists from the start,
then you save on shimmering an array.
> I had a complicated display once, tons of different things going on.
> This was way early in my tcl/tk carreer and for the backing store of
> info on everything I use a keyed list (from tclx). once things were
> working ok, i started looking at the slowness, and just changing the
> list storage to an array sped things up by at least 1 or 2 orders of
> magnitude. a hash lookup is significantly faster than searching a
> list. Of course if you are talking 7 entries (days of week) it won't
> make much difference, but I got in the habbit of lookup tables as
> hashes so I don;t have to worry about on getting larger than
> originally planned and then having to change it. To me it is also
> simpler to use code-wise.
*nods* That's also why I suggested the use of helper functions...
Greatly simplifies the "change it" part if that ever comes about. When
I have used arrays for things like this, though, I've found that I
usually end up spending an awful lot of time using [array names], just
to [lsearch] it anyhow. Kind of detracts from the purpose of doing it
that way to begin with, and you lose the native bi-directional mapping
unless you double-up on the number of items in the array.
Looking back at some of my own code, the last time I did this
particular task I actually used something similar to what someone else
suggested; a flat list of day names, with their natural numeric
mapping. In any case, when I do this sort of thing, I almost always
implement a generic function that takes as arguments a list or array to
look up in, and the item being looked up. You can then [interp alias]
(or wrap with [proc], for example, to improve error handling) that into
a helper command specific to your purpose.
Not sure about the speed aspect of it, but it sure is easier. :)
Fredderic