Browse Groups

• Hello, I want to write tables of values/codes. It is given the number of digits of a code and the number of possible values for each digit (base of the code).
Message 1 of 11 , Jan 3, 2003
View Source
Hello,

I want to write tables of values/codes.
It is given the number of digits of a code and
the number of possible values for each
digit (base of the code).

And I want to know, how the problem can be solved
elegant and efficient.

let create_code len base = (* ... best approach ... *)

create_code 3 2;; (* will be able to create this list: *)
[ "000"; "001"; "010"; "011"; "100"; "101"; "110"; "111" ]

But it is also intended to have the possibility of creating
a gray-code:

create_gray_code 3 2;; (* will be able to create this list: *)
[ "000"; "001"; "011"; "010"; "110"; "111"; "101"; "100"]

I thought about creating a list for each digit
with a for-loop and the "::"-operator, to create
the list for one digit out of strings (and the strings out of int's).

Then I thought about using List.map2 with (^) to put the
created lists of digit's together. I have to use this approach
(len - 1) times (for three digits, as in the example
above, I have to use it twice.)

How would you solve this problem of code-generation?

How can the type-system help in solving such a problem?
Or is it a good idea to do it OO-like?

What about codes with more than base 10 (e.g. hexcode)?
And what's about creating lists of non-strings (e.g.
lists of functions or objects, so that calling these
objects/functions will create the intended result (e.g.
printing LaTeX-code, or doing other actions))?

Ciao,
Oliver
• I haven t spent a lot of time thinking about your problem but the first thing that popped into my mind was why use a list when you can define your own type
Message 1 of 11 , Jan 11, 2003
View Source
I haven't spent a lot of time thinking about your problem but the
first thing that popped into my mind was why use a list when you can
define your own type (recursively if need be)?

# type 'a code =
End of 'a
| Code of 'a * 'a code;;
type 'a code = End of 'a | Code of 'a * 'a code
# Code(0, Code(1, Code(0, End 1)));;
- : int code = Code (0, Code (1, Code (0, End 1)))

This is essentially a linked-list style data structure, but really
it's better to call it a recursive type. This data type is also
parameterized so that it will work for codes whose individual digits
could be of any type (char, int representing binary, int representing
hex, etc). To decode a code you could do something like:

# let rec decode code value =
match code with
Code (digit, rest) -> decode rest (value + digit)
| End c -> value + c;;
- : val decode : int code -> int -> int = <fun>

Of course this will only work with "integer codes" due to the +
operator. It is a harder problem to write an encoding/creation
function, I think (it's not obvious to me how it'd be done, but I
think it can be done, although a bit clumsily).

I think your specification of a create_code x y function is too
general as far as code generation goes. The first parameter is the
length of the code, which is fine, but the second parameter seems
rather ambiguous (unclear) when you talk about making an "elegant"
code generator. You need to know what are the members of the code
alphabet (if you always use binary, why do you need a y parameter that
will always be 2, etc.).

For Huffman codes, the data type I illustrated above could be modified
to represent a huffman code as tree like so:

# type 'a symbol = int * 'a;;
type 'a symbol = int * 'a
# type 'a huffman_code =
Symbol of 'a symbol
| Code of 'a huffman_code * 'a symbol * 'a huffman_code;;
type 'a huffman_code = Symbol of 'a symbol | Code of 'a huffman_code
* 'a symbol * 'a huffman_code

Given the symbols and their frequencies you could write a function to
build a tree by which a simple traversal (left=0 or right=1) will
produce the encoding...

--- In ocaml_beginners@yahoogroups.com, oliver@f... wrote:
> I want to write tables of values/codes.
> It is given the number of digits of a code and
> the number of possible values for each
> digit (base of the code).
>
> let create_code len base = (* ... best approach ... *)
>
> create_code 3 2;; (* will be able to create this list: *)
> [ "000"; "001"; "010"; "011"; "100"; "101"; "110"; "111" ]
>
>
> But it is also intended to have the possibility of creating
> a gray-code:
>
> create_gray_code 3 2;; (* will be able to create this list: *)
> [ "000"; "001"; "011"; "010"; "110"; "111"; "101"; "100"]
>
>
> I thought about creating a list for each digit
> with a for-loop and the "::"-operator, to create
> the list for one digit out of strings (and the strings out of int's).
>
> Then I thought about using List.map2 with (^) to put the
> created lists of digit's together. I have to use this approach
> (len - 1) times (for three digits, as in the example
> above, I have to use it twice.)
>
>
> How would you solve this problem of code-generation?
>
> How can the type-system help in solving such a problem?
> Or is it a good idea to do it OO-like?
>
> What about codes with more than base 10 (e.g. hexcode)?
> And what's about creating lists of non-strings (e.g.
> lists of functions or objects, so that calling these
> objects/functions will create the intended result (e.g.
> printing LaTeX-code, or doing other actions))?
• ... because, generally speaking, it s better to use already define data-structure when you can : it s more readable and there are some function in the stdlib
Message 1 of 11 , Jan 13, 2003
View Source
"spooky_jesse <spooky_jesse@...>" <spooky_jesse@...> writes:

> I haven't spent a lot of time thinking about your problem but the
> first thing that popped into my mind was why use a list when you can
> define your own type (recursively if need be)?

because, generally speaking, it's better to use already define
data-structure when you can : it's more readable and there are some
function in the stdlib for them.
--
Rémi Vanicat
vanicat@labri.u-bordeaux.fr
http://dept-info.labri.u-bordeaux.fr/~vanicat
• ... Yes, but it s surely better to define a tree as type a tree = Node of a tree * a * a tree ... Rather than mess with lists and 2n/2n+1 formula... what
Message 1 of 11 , Jan 15, 2003
View Source
--- In ocaml_beginners@yahoogroups.com, Remi VANICAT
<vanicat+egroups@l...> wrote:
> "spooky_jesse <spooky_jesse@y...>" <spooky_jesse@y...> writes:
>
> because, generally speaking, it's better to use already define
> data-structure when you can : it's more readable and there are some
> function in the stdlib for them.

Yes, but it's surely better to define a tree as
type 'a tree =
Node of 'a tree * 'a * 'a tree
| Empty

Rather than mess with lists and 2n/2n+1 formula... what would be the
disadvantage? Other than writing manipulation functions, which are
easy...

let rec dfs t e =
match t with
Node (l, n, r) -> n = e || if dfs l e then true else dfs r e
| Empty -> false
• ... Well, of course, when you need a tree, you have to define it, but, when you need list, you should use those of the stdlib. the type : type a code = End of
Message 1 of 11 , Jan 15, 2003
View Source
"spooky_jesse <spooky_jesse@...>" <spooky_jesse@...> writes:

> --- In ocaml_beginners@yahoogroups.com, Remi VANICAT
> <vanicat+egroups@l...> wrote:
>> "spooky_jesse <spooky_jesse@y...>" <spooky_jesse@y...> writes:
>>
>> because, generally speaking, it's better to use already define
>> data-structure when you can : it's more readable and there are some
>> function in the stdlib for them.
>
> Yes, but it's surely better to define a tree as
> type 'a tree =
> Node of 'a tree * 'a * 'a tree
> | Empty

Well, of course, when you need a tree, you have to define it, but,
when you need list, you should use those of the stdlib.

the type :

type 'a code =
End of 'a
| Code of 'a * 'a code;;

is not a tree, but a list.

--
Rémi Vanicat
vanicat@labri.u-bordeaux.fr
http://dept-info.labri.u-bordeaux.fr/~vanicat
• ... With compilation units/modules I may internally use my own data type, but give back a list of lists. :) Are self defined types a perfromance issue? (It
Message 1 of 11 , Jan 19, 2003
View Source
On Mon, Jan 13, 2003 at 11:23:29AM +0100, Remi VANICAT wrote:
> "spooky_jesse <spooky_jesse@...>" <spooky_jesse@...> writes:
>
> > I haven't spent a lot of time thinking about your problem but the
> > first thing that popped into my mind was why use a list when you can
> > define your own type (recursively if need be)?
>
> because, generally speaking, it's better to use already define
> data-structure when you can : it's more readable and there are some
> function in the stdlib for them.

With compilation units/modules I may internally use
my own data type, but give back a list of lists. :)

Are self defined types a perfromance issue?
(It will not be critically in my application,
but interests me in genral.)

But back to my question about how to create such a list:
When doing that job in functional manner, it seems
to me the best, to first create all partial lists,
and then put them together.

Or maybe I can create these lists on the fly?

Maybe a recursive function with two parameters: one for the
state of the current digit, I work on, and one for the number
of the digiut I work on?

Or is it a better approach to use arrays instead of lists,
and maybe switch back to imperative programming for such a problem?

Ciao,
Oliver
• ... [...] Well, ok, I have experimented with writing recursive functions and using only lists. it seems to be a good approach. I can strengthen my facilities
Message 1 of 11 , Jan 20, 2003
View Source
On Sun, Jan 19, 2003 at 05:53:40PM +0100, oliver@...-berlin.de wrote:
> On Mon, Jan 13, 2003 at 11:23:29AM +0100, Remi VANICAT wrote:
> > "spooky_jesse <spooky_jesse@...>" <spooky_jesse@...> writes:
> >
> > > I haven't spent a lot of time thinking about your problem but the
> > > first thing that popped into my mind was why use a list when you can
> > > define your own type (recursively if need be)?
> >
> > because, generally speaking, it's better to use already define
> > data-structure when you can : it's more readable and there are some
> > function in the stdlib for them.
>
> With compilation units/modules I may internally use
> my own data type, but give back a list of lists. :)
>
> Are self defined types a perfromance issue?
> (It will not be critically in my application,
> but interests me in genral.)
>
> But back to my question about how to create such a list:
> When doing that job in functional manner, it seems
> to me the best, to first create all partial lists,
> and then put them together.
[...]

Well, ok, I have experimented with writing recursive functions
and using only lists.

it seems to be a good approach. I can strengthen my facilities
in writing tail-recursive code, and the code of most functions
is about two or three lines...

I only have one imperative function, and I maybe will rewrite it
to be a recursive one.

So, it seems to me, that for my problem I can completely
work without using the type-definition for own datatypes.

It's more a problem of how to split the problem into
smaller ones and maybe a question of style, if I use
local let-statements or let all the functions compilation-unit-global.

Ciao,
Oliver
• Hi, ok, I have now some code, which works. But when compiling with ocamlc, the part with let _ = xxx() will create a warning (because of types). This is a (the
Message 1 of 11 , Jan 22, 2003
View Source
Hi,

ok, I have now some code, which works.

But when compiling with ocamlc, the part with let _ = xxx()
will create a warning (because of types).

This is a (the newest) part of my code-generator program.
It's not completely ready, but the main functionality
is working. It is the "innerst" part of the program.
There is some stuff around it, which feeds the list
and number of repeats (in germen wiederholungen) in
as parameters.

Can I shrink the amount of code by some "tricks"?

How can I redesign it, so that I can set the initialization
of the counter, depending on the direction type?

Even if the code is much better than what it looks like in perl,
I think it can be optimized.

Here is the code:

#########################################################################
type direction = BACKWARD | FORWARD | TOGGLE of direction | RESET;;

exception Parameter;;

(* =================================================================== *)

let generate list elem_wdh dirtype =
let wdh wdh_c =
let c = ref 0 in
let w () =
c := !c + 1;
if !c = 1 + abs wdh_c then (c := 0; false) else true
in w
in

let w = wdh (elem_wdh -1) in
let dir = ref dirtype in
let c = ref 0 in
let pwe () =
let p() = print_string (List.nth list !c) in
let ret = List.nth list !c in
let inc() = c := !c + 1 in (* incrementor *)
let dec() = c := !c - 1 in (* decrementor *)
let res() = c := 0 in (* resetter *)

(* toggle directions *)
let toggledir() = match !dir with
| TOGGLE(BACKWARD) -> dir := TOGGLE(FORWARD)
| _ -> dir := TOGGLE(BACKWARD)

in

let mainfunc () = (* main functionality of the "ringbuffer" *)
match !dir with
FORWARD -> inc(); if !c = List.length list then res()
| BACKWARD -> dec(); if !c < 0 then c := List.length list - 1
| TOGGLE(FORWARD) -> inc(); if !c = List.length list then (toggledir(); dec())
| TOGGLE(BACKWARD) -> dec(); if !c < 0 then toggledir(); inc()
| RESET -> c := 0
| _ -> raise Parameter
in
if ( w () = true ) then ret else (mainfunc (); ret)
in
pwe

;;

let xxx = generate ["a";"b";"c";"d"] 3 FORWARD;;

let _ = xxx(); xxx();;

#########################################################################

Any hint is welcome.

Oliver
• ... well, its very, very imperative. You might want to do it on a more functional way... ... it seem strange here, because the ret is computed before the
Message 1 of 11 , Jan 22, 2003
View Source
oliver@...-berlin.de writes:

> Hi,
>
>
> ok, I have now some code, which works.
>
> But when compiling with ocamlc, the part with let _ = xxx()
> will create a warning (because of types).
>
> This is a (the newest) part of my code-generator program.
> It's not completely ready, but the main functionality
> is working. It is the "innerst" part of the program.
> There is some stuff around it, which feeds the list
> and number of repeats (in germen wiederholungen) in
> as parameters.
>
>
> Can I shrink the amount of code by some "tricks"?
>
> How can I redesign it, so that I can set the initialization
> of the counter, depending on the direction type?
>
> Even if the code is much better than what it looks like in perl,
> I think it can be optimized.
>
>
> Here is the code:
>
> #########################################################################
> type direction = BACKWARD | FORWARD | TOGGLE of direction | RESET;;
>
> exception Parameter;;
>
> (* =================================================================== *)
>
> let generate list elem_wdh dirtype =
> let wdh wdh_c =
> let c = ref 0 in
> let w () =
> c := !c + 1;
> if !c = 1 + abs wdh_c then (c := 0; false) else true
> in w
> in
>
> let w = wdh (elem_wdh -1) in
> let dir = ref dirtype in
> let c = ref 0 in
> let pwe () =
> let p() = print_string (List.nth list !c) in
> let ret = List.nth list !c in
> let inc() = c := !c + 1 in (* incrementor *)
> let dec() = c := !c - 1 in (* decrementor *)
> let res() = c := 0 in (* resetter *)
>
> (* toggle directions *)
> let toggledir() = match !dir with
> | TOGGLE(BACKWARD) -> dir := TOGGLE(FORWARD)
> | _ -> dir := TOGGLE(BACKWARD)
>

well, its very, very imperative. You might want to do it on a more
functional way...

>
> in
>
> let mainfunc () = (* main functionality of the "ringbuffer" *)
> match !dir with
> FORWARD -> inc(); if !c = List.length list then res()
> | BACKWARD -> dec(); if !c < 0 then c := List.length list - 1
> | TOGGLE(FORWARD) -> inc(); if !c = List.length list then (toggledir(); dec())
> | TOGGLE(BACKWARD) -> dec(); if !c < 0 then toggledir(); inc()
> | RESET -> c := 0
> | _ -> raise Parameter
> in
> if ( w () = true ) then ret else (mainfunc (); ret)

it seem strange here, because the ret is computed before the mainfunc
is run, and then return, so its seem that the work of the mainfunc is
discarded (well, I know it isn't, because of the side effect of
mainfunc, but the ret is still the result before the computation).
--
Rémi Vanicat
vanicat@labri.u-bordeaux.fr
http://dept-info.labri.u-bordeaux.fr/~vanicat
• ... [...] ... Yes, it is. Maybe that s, we I like it... it s very used to me to do it imperative. ;-) ... I first had a very functional approach. It was
Message 1 of 11 , Jan 22, 2003
View Source
On Wed, Jan 22, 2003 at 02:58:42PM +0100, Remi VANICAT wrote:
> oliver@...-berlin.de writes:
[...]
> > let p() = print_string (List.nth list !c) in
> > let ret = List.nth list !c in
> > let inc() = c := !c + 1 in (* incrementor *)
> > let dec() = c := !c - 1 in (* decrementor *)
> > let res() = c := 0 in (* resetter *)
> >
> > (* toggle directions *)
> > let toggledir() = match !dir with
> > | TOGGLE(BACKWARD) -> dir := TOGGLE(FORWARD)
> > | _ -> dir := TOGGLE(BACKWARD)
> >
>
> well, its very, very imperative.

Yes, it is.

Maybe that's, we I like it... it's very used to me
to do it imperative. ;-)

> You might want to do it on a more
> functional way...

I first had a very functional approach.
It was completely based on list-processing
(should I use lisp then? ;-)).
I had not used type-definitions for the data.
Maybe that was the reason,
that it seemed to be not very convenient.
(But aleways was better than using C's pointer-stuff.)

Now, when I changed to going the imperative way,

Maybe I try it again more functionally, but using
type definitions then.

The "pure/basic datatypes only" approach was - at a certain
level of nestedness of the datastructures - not very convenient.

But I have solved the "outer" computations with that approach.
I thought that imperative way would be better for creating
the codes.

[...]
> >
> > in
> >
> > let mainfunc () = (* main functionality of the "ringbuffer" *)
> > match !dir with
> > FORWARD -> inc(); if !c = List.length list then res()
> > | BACKWARD -> dec(); if !c < 0 then c := List.length list - 1
> > | TOGGLE(FORWARD) -> inc(); if !c = List.length list then (toggledir(); dec())
> > | TOGGLE(BACKWARD) -> dec(); if !c < 0 then toggledir(); inc()
> > | RESET -> c := 0
> > | _ -> raise Parameter
> > in
> > if ( w () = true ) then ret else (mainfunc (); ret)
>
> it seem strange here, because the ret is computed before the mainfunc
> is run, and then return, so its seem that the work of the mainfunc is
> discarded (well, I know it isn't, because of the side effect of
> mainfunc, but the ret is still the result before the computation).

Well mainfunc is the shifting of the "pointers" to the
"ringbuffer". It does not necessarily have to be computed
first.

functionality of the codegeneration-part.

I may rename it. The wdh() is necessary. Well I could
create the basic codes first and then duplicate the
"digits" after this.

And this is, how I have done it before.

I append the old code, where I have worked.
maybe it can be more clear, what is going on there,
when I introduce type-definitions for the elements of
the lists. I may add parametrized datatypes for
the elements of my digits.

I think this makes reading the code more easy.
So I come back to the hint of one of the answers

Here comes my older (more functional) code:

(sorry, the comments are mostly held in german)

################################################################

(* ------------------------------------------------------------------------- *)
let listrest_product liste = List.fold_left ( * ) 1 liste
(* ------------------------------------------------------------------------- *)

(* ------------------------------------------------------------------------- *)
(* ------------------------------------------------------------------------- *)
(* ------------------------------------------------------------------------- *)
(* ------------------------------------------------------------------------- *)
(* ------------------------------------------------------------------------- *)

type listlength = int
type multiplikator = int
type worklist_t = (string list * listlength * multiplikator ) list
type inputlists_t = (string list) list

type werteliste = { lis: string list; mutable mult : int }

type wl = ( string list * int array )

(* ---------------------------------------------------------------- *)
(* append length of a list to list-of-lists (list of list-length's) *)
(* ---------------------------------------------------------------- *)
let apll2l lol liste = match liste with
[] -> lol
| _ -> (List.length liste) :: lol
(* ---------------------------------------------------------------- *)

(* ---------------------------------------------------------------- *)
let rec listlengen listoflists (lengthlist: int list) = match listoflists with
[] -> lengthlist
| l::lrest -> List.length l :: listlengen lrest lengthlist
(* ---------------------------------------------------------------- *)
(*
# listlengen lol [];;
- : int list = [4; 0; 2; 3]

# listlengen lol [22;33;44;55];;
- : int list = [4; 0; 2; 3; 22; 33; 44; 55]
*)

(* ---------------------------------------------------------------- *)
(* create_worklist creates a datatype, which contains list-lengths *)
(* and the multiplicators. *)
(* ---------------------------------------------------------------- *)
let rec create_worklist (listoflists: inputlists_t) lengthlist =
match listoflists with
[] -> lengthlist
| l::lrest -> (l, List.length l) :: create_worklist lrest lengthlist
(* ---------------------------------------------------------------- *)

(*
let create_multlist wl (ml: int list) = match wl with
[] -> ml
| l::lrest -> third l :: create_multlist lrest []
*)

(* ---------------------------------------------------------------- *)
(* ---------------------------------------------------------------- *)
let rec calc_multipliers (worklist: worklist_t) =
let rwl = List.rev worklist in
rwl
(* --- matche auf drittes Element des Triplets *)
(* multipliziere diese Elemente miteinander *)

(*

let rec tr_calc_multipliers (worklist: worklist_t) curr_mult =

| (a,b,m) -> (a,b, curr_mult * m) :: tr_calc_multipliers worklist curr_mult * m

tr_calc_multipliers worklist 1;;

*)

(* ---------------------------------------------------------------- *)

(* ---------------------------------------------------------------- *)
let sublistlength lol = listlengen lol []
(* ---------------------------------------------------------------- *)
(*
# sublistlength lol;;
- : int list = [4; 0; 2; 3]
*)

(* test data *)
let lol = [[3;5;22;66];[];[2;4];[2;4;5]]

(* real world data: combinating the Effort-Elements *)
let rwlol = [
["sudden";"sustained"]; (* Time *)
["direct"; "indirect"]; (* Space *)
["light";"strong"]; (* Weight *)
["free";"bound"] (* Flow *)
]

let _ = print_int(List.hd (sublistlength lol)) (* Ergebnis: [4; 0; 2; 3] *)

(* ------------------------------------------------------------------------- *)
(* Ein Listenelement wird num mal vervielfältigt und al Liste zurück gegeben *)
(* ------------------------------------------------------------------------- *)
let multel num el =
let x = ref [] in
for i = 0 to num - 1
do
x := el :: !x
done; !x
(* ------------------------------------------------------------------------- *)
(*
# multel 4 "xh ";;
- : string list = ["xh "; "xh "; "xh "; "xh "]
*)

(* ------------------------------------------------------------------------- *)
(* multlist: foreach element in list use multel and concat the lists *)
(* ===> that means: for all elements in the list duplicate them "num" times *)
(* ------------------------------------------------------------------------- *)
let rec multlist num list = match list with
[] -> list
| l::lrest -> multel num l @ multlist num lrest
(* ------------------------------------------------------------------------- *)
(*
# multlist 3 [0;1;2];;
- : int list = [0; 0; 0; 1; 1; 1; 2; 2; 2]

# multlist 3 ["xh "; "jkhbhj"; "guik"];;
- : string list =
["xh "; "xh "; "xh "; "jkhbhj"; "jkhbhj"; "jkhbhj"; "guik"; "guik"; "guik"]
*)

(* ------------------------------------------------------------------------- *)
(* . *)
(* ------------------------------------------------------------------------- *)
(*
jetzt nur noch für alle Eingangselemente aller Eingangslisten alle Ergebnis-
Listen erstellen: multlist für alle Digits anwenden, mit
dem Multiplikator "num" entsprechend des Produkts der Längen aller
bisher bearbeiteten Listen (allerdings muß das tatsächlich mit dem
letzten Element anfangen => eingangsliste ggf. mit List.reverse umkehren!
*)
(* ------------------------------------------------------------------------- *)

let codegen (list_of_digit_lists: string list list) =
[["fgh"; "ou";"uoih"]; ["0";"1";"2";"3";"4";"5";"6";"7";"8"]; ["fgh"; "zh"]];;

################################################################

Ciao,
Oliver

P.S.: Is it a problem to have names for functions and parameters,
which are used as language identifiers (e.g. "list", "string",
"rec")? I have not had problems until now, but I'm not shure
if there can be problems created by using such words as
identifiers.

P.P.S.: Yes, it's interesting to explore the different ways, how
a problem can be solved using OCaml. But sometimes I
think, that may be a problem of decision more. (?!)
• oliver@first.in-berlin.de writes: [...] well, there is a lot of code, and I ve not the time to read. Just try. ... There is two thing : keyword, and things
Message 1 of 11 , Jan 22, 2003
View Source
oliver@...-berlin.de writes:

[...]

well, there is a lot of code, and I've not the time to read. Just try.

> P.S.: Is it a problem to have names for functions and parameters,
> which are used as language identifiers (e.g. "list", "string",
> "rec")?

There is two thing : keyword, and things that are in the core
library. rec is a keyword, you can't use it otherwise. list, string
are just identifier use in the core library, yon can use them. Beware,
in this case they are both type, you should not use them for type name
(because you will then hide the previous type declaration). But there
is absolutely no problem for using them as name for value.

[...]

--
Rémi Vanicat
vanicat@labri.u-bordeaux.fr
http://dept-info.labri.u-bordeaux.fr/~vanicat
Your message has been successfully submitted and would be delivered to recipients shortly.
• Changes have not been saved
Press OK to abandon changes or Cancel to continue editing
• Your browser is not supported
Kindly note that Groups does not support 7.0 or earlier versions of Internet Explorer. We recommend upgrading to the latest Internet Explorer, Google Chrome, or Firefox. If you are using IE 9 or later, make sure you turn off Compatibility View.