Movatterモバイル変換


[0]ホーム

URL:


Exercise Categories


Lists Tail of a List Last Two Elements of a List N'th Element of a List Length of a List Reverse a List Palindrome Flatten a List Eliminate Duplicates Pack Consecutive Duplicates Run-Length Encoding Modified Run-Length Encoding Decode a Run-Length Encoded List Run-Length Encoding of a List (Direct Solution) Duplicate the Elements of a List Replicate the Elements of a List a Given Number of Times Drop Every N'th Element From a List Split a List Into Two Parts; The Length of the First Part Is Given Extract a Slice From a List Rotate a List N Places to the Left Remove the K'th Element From a List Insert an Element at a Given Position Into a List Create a List Containing All Integers Within a Given Range Extract a Given Number of Randomly Selected Elements From a List Lotto: Draw N Different Random Numbers From the Set 1..M Generate a Random Permutation of the Elements of a List Generate the Combinations of K Distinct Objects Chosen From the N Elements of a List Group the Elements of a Set Into Disjoint Subsets Sorting a List of Lists According to Length of Sublists

Arithmetic Determine Whether a Given Integer Number Is Prime Determine the Greatest Common Divisor of Two Positive Integer Numbers Determine Whether Two Positive Integer Numbers Are Coprime Calculate Euler's Totient Function Φ(m) Determine the Prime Factors of a Given Positive Integer Determine the Prime Factors of a Given Positive Integer (2) Calculate Euler's Totient Function Φ(m) (Improved) Compare the Two Methods of Calculating Euler's Totient Function A List of Prime Numbers Goldbach's Conjecture A List of Goldbach Compositions

Logic and Codes Truth Tables for Logical Expressions (2 Variables) Truth Tables for Logical Expressions Gray Code Huffman Code

Binary Trees Construct Completely Balanced Binary Trees Symmetric Binary Trees Binary Search Trees (Dictionaries) Generate-and-Test Paradigm Construct Height-Balanced Binary Trees Construct Height-Balanced Binary Trees With a Given Number of Nodes Count the Leaves of a Binary Tree Collect the Leaves of a Binary Tree in a List Collect the Internal Nodes of a Binary Tree in a List Collect the Nodes at a Given Level in a List Construct a Complete Binary Tree Layout a Binary Tree (1) Layout a Binary Tree (2) Layout a Binary Tree (3) A String Representation of Binary Trees Preorder and Inorder Sequences of Binary Trees Dotstring Representation of Binary Trees

Multiway Trees Tree Construction From a Node String Count the Nodes of a Multiway Tree Determine the Internal Path Length of a Tree Construct the Bottom-Up Order Sequence of the Tree Nodes Lisp-Like Tree Representation

Graphs Conversions Path From One Node to Another One Cycle From a Given Node Construct All Spanning Trees Construct the Minimal Spanning Tree Graph Isomorphism Node Degree and Graph Coloration Depth-First Order Graph Traversal Connected Components Bipartite Graphs Generate K-Regular Simple Graphs With N Nodes

Miscellaneous Eight Queens Problem Knight's Tour Von Koch's Conjecture An Arithmetic Puzzle English Number Words Syntax Checker Sudoku Nonograms Crossword Puzzle

Exercises

This section is inspired by Ninety-Nine Lisp Problems which in turn was based on “Prolog problem list” by Werner Hett. For each of these questions, some simple tests are shown—they may also serve to make the question clearer if needed. To work on these problems, we recommend you firstinstall OCaml or use itinside your browser. The source of the following problems is available onGitHub.

Every exercise has a difficulty level, ranging from beginner to advanced.

Tail of a List

Beginner

Write a functionlast : 'a list -> 'a option that returns the last element of a list

#last["a";"b";"c";"d"];;-:stringoption=Some"d"#last[];;-:'aoption=None
#letreclast=function|[]->None|[x]->Somex|_::t->lastt;;vallast:'alist->'aoption=<fun>

Last Two Elements of a List

Beginner

Find the last two (last and penultimate) elements of a list.

#last_two["a";"b";"c";"d"];;-:(string*string)option=Some("c","d")#last_two["a"];;-:(string*string)option=None
#letreclast_two=function|[]|[_]->None|[x;y]->Some(x,y)|_::t->last_twot;;vallast_two:'alist->('a*'a)option=<fun>

N'th Element of a List

Beginner

Find the N'th element of a list.

#at2["a";"b";"c";"d";"e"];;-:stringoption=Some"c"#at2["a"];;-:stringoption=None

Remark: OCaml hasList.nth which numbers elements from0 andraises an exception if the index is out of bounds.

#List.nth["a";"b";"c";"d";"e"]2;;-:string="c"#List.nth["a"]2;;Exception:Failure"nth".
#letrecatk=function|[]->None|h::t->ifk=0thenSomehelseat(k-1)t;;valat:int->'alist->'aoption=<fun>

Length of a List

Beginner

Find the number of elements of a list.

OCaml standard library hasList.length but we ask that you reimplementit. Bonus for atail recursivesolution.

#length["a";"b";"c"];;-:int=3#length[];;-:int=0

This function is tail-recursive: it uses a constant amount of stack memory regardless of list size.

#letlengthlist=letrecauxn=function|[]->n|_::t->aux(n+1)tinaux0list;;vallength:'alist->int=<fun>

Reverse a List

Beginner

Reverse a list.

OCaml standard library hasList.rev but we ask that you reimplementit.

#rev["a";"b";"c"];;-:stringlist=["c";"b";"a"]
#letrevlist=letrecauxacc=function|[]->acc|h::t->aux(h::acc)tinaux[]list;;valrev:'alist->'alist=<fun>

Palindrome

Beginner

Find out whether a list is a palindrome.

Hint: A palindrome is its own reverse.

#is_palindrome["x";"a";"m";"a";"x"];;-:bool=true#not(is_palindrome["a";"b"]);;-:bool=true
#letis_palindromelist=(* One can use either the rev function from the previous problem, or the built-in List.rev*)list=List.revlist;;valis_palindrome:'alist->bool=<fun>

Flatten a List

Intermediate

Flatten a nested list structure.

type'anode=|Oneof'a|Manyof'anodelist
#flatten[One"a";Many[One"b";Many[One"c";One"d"];One"e"]];;-:stringlist=["a";"b";"c";"d";"e"]
#type'anode=|Oneof'a|Manyof'anodelist;;type'anode=Oneof'a|Manyof'anodelist#(* This function traverses the list, prepending any encountered elements    to an accumulator, which flattens the list in inverse order. It can    then be reversed to obtain the actual flattened list.*);;#letflattenlist=letrecauxacc=function|[]->acc|Onex::t->aux(x::acc)t|Manyl::t->aux(auxaccl)tinList.rev(aux[]list);;valflatten:'anodelist->'alist=<fun>

Eliminate Duplicates

Intermediate

Eliminate consecutive duplicates of list elements.

#compress["a";"a";"a";"a";"b";"c";"c";"a";"a";"d";"e";"e";"e";"e"];;-:stringlist=["a";"b";"c";"a";"d";"e"]
#letreccompress=function|a::(b::_ast)->ifa=bthencompresstelsea::compresst|smaller->smaller;;valcompress:'alist->'alist=<fun>

Pack Consecutive Duplicates

Intermediate

Pack consecutive duplicates of list elements into sublists.

#pack["a";"a";"a";"a";"b";"c";"c";"a";"a";"d";"d";"e";"e";"e";"e"];;-:stringlistlist=[["a";"a";"a";"a"];["b"];["c";"c"];["a";"a"];["d";"d"];["e";"e";"e";"e"]]
#letpacklist=letrecauxcurrentacc=function|[]->[](* Can only be reached if original list is empty*)|[x]->(x::current)::acc|a::(b::_ast)->ifa=bthenaux(a::current)acctelseaux[]((a::current)::acc)tinList.rev(aux[][]list);;valpack:'alist->'alistlist=<fun>

Run-Length Encoding

Beginner

If you need so, refresh your memory aboutrun-length encoding.

Here is an example:

#encode["a";"a";"a";"a";"b";"c";"c";"a";"a";"d";"e";"e";"e";"e"];;-:(int*string)list=[(4,"a");(1,"b");(2,"c");(2,"a");(1,"d");(4,"e")]
#letencodelist=letrecauxcountacc=function|[]->[](* Can only be reached if original list is empty*)|[x]->(count+1,x)::acc|a::(b::_ast)->ifa=bthenaux(count+1)acctelseaux0((count+1,a)::acc)tinList.rev(aux0[]list);;valencode:'alist->(int*'a)list=<fun>

An alternative solution, which is shorter but requires more memory, is to usethepack function declared in problem 9:

#letpacklist=letrecauxcurrentacc=function|[]->[](* Can only be reached if original list is empty*)|[x]->(x::current)::acc|a::(b::_ast)->ifa=bthenaux(a::current)acctelseaux[]((a::current)::acc)tinList.rev(aux[][]list);;valpack:'alist->'alistlist=<fun>#letencodelist=List.map(funl->(List.lengthl,List.hdl))(packlist);;valencode:'alist->(int*'a)list=<fun>

Modified Run-Length Encoding

Beginner

Modify the result of the previous problem in such a way that if anelement has no duplicates it is simply copied into the result list. Onlyelements with duplicates are transferred as (N E) lists.

Since OCaml lists are homogeneous, one needs to define a type to holdboth single elements and sub-lists.

type'arle=|Oneof'a|Manyofint*'a
#encode["a";"a";"a";"a";"b";"c";"c";"a";"a";"d";"e";"e";"e";"e"];;-:stringrlelist=[Many(4,"a");One"b";Many(2,"c");Many(2,"a");One"d";Many(4,"e")]
#type'arle=|Oneof'a|Manyofint*'a;;type'arle=Oneof'a|Manyofint*'a#letencodel=letcreate_tuplecntelem=ifcnt=1thenOneelemelseMany(cnt,elem)inletrecauxcountacc=function|[]->[]|[x]->(create_tuple(count+1)x)::acc|hd::(snd::_astl)->ifhd=sndthenaux(count+1)acctlelseaux0((create_tuple(count+1)hd)::acc)tlinList.rev(aux0[]l);;valencode:'alist->'arlelist=<fun>

Decode a Run-Length Encoded List

Intermediate

Given a run-length code list generated as specified in the previousproblem, construct its uncompressed version.

#decode[Many(4,"a");One"b";Many(2,"c");Many(2,"a");One"d";Many(4,"e")];;-:stringlist=["a";"a";"a";"a";"b";"c";"c";"a";"a";"d";"e";"e";"e";"e"]
#letdecodelist=letrecmanyaccnx=ifn=0thenaccelsemany(x::acc)(n-1)xinletrecauxacc=function|[]->acc|Onex::t->aux(x::acc)t|Many(n,x)::t->aux(manyaccnx)tinaux[](List.revlist);;valdecode:'arlelist->'alist=<fun>

Run-Length Encoding of a List (Direct Solution)

Intermediate

Implement the so-called run-length encoding data compression methoddirectly. I.e. don't explicitly create the sublists containing theduplicates, as in problem "Pack consecutive duplicates of list elements into sublists", but only count them. As in problem"Modified run-length encoding", simplify the result list by replacing the singleton lists (1 X) by X.

#encode["a";"a";"a";"a";"b";"c";"c";"a";"a";"d";"e";"e";"e";"e"];;-:stringrlelist=[Many(4,"a");One"b";Many(2,"c");Many(2,"a");One"d";Many(4,"e")]
#letencodelist=letrlecountx=ifcount=0thenOnexelseMany(count+1,x)inletrecauxcountacc=function|[]->[](* Can only be reached if original list is empty*)|[x]->rlecountx::acc|a::(b::_ast)->ifa=bthenaux(count+1)acctelseaux0(rlecounta::acc)tinList.rev(aux0[]list);;valencode:'alist->'arlelist=<fun>

Duplicate the Elements of a List

Beginner

Duplicate the elements of a list.

#duplicate["a";"b";"c";"c";"d"];;-:stringlist=["a";"a";"b";"b";"c";"c";"c";"c";"d";"d"]
#letrecduplicate=function|[]->[]|h::t->h::h::duplicatet;;valduplicate:'alist->'alist=<fun>

Remark: this function is not tail recursive. Can you modify it soit becomes so?

Replicate the Elements of a List a Given Number of Times

Intermediate

Replicate the elements of a list a given number of times.

#replicate["a";"b";"c"]3;;-:stringlist=["a";"a";"a";"b";"b";"b";"c";"c";"c"]
#letreplicatelistn=letrecprependnaccx=ifn=0thenaccelseprepend(n-1)(x::acc)xinletrecauxacc=function|[]->acc|h::t->aux(prependnacch)tin(* This could also be written as:       List.fold_left (prepend n) [] (List.rev list)*)aux[](List.revlist);;valreplicate:'alist->int->'alist=<fun>

Note thatList.rev list is needed only because we wantaux to betail recursive.

Drop Every N'th Element From a List

Intermediate

Drop every N'th element from a list.

#drop["a";"b";"c";"d";"e";"f";"g";"h";"i";"j"]3;;-:stringlist=["a";"b";"d";"e";"g";"h";"j"]
#letdroplistn=letrecauxi=function|[]->[]|h::t->ifi=nthenaux1telseh::aux(i+1)tinaux1list;;valdrop:'alist->int->'alist=<fun>

Split a List Into Two Parts; The Length of the First Part Is Given

Beginner

Split a list into two parts; the length of the first part is given.

If the length of the first part is longer than the entire list, then thefirst part is the list and the second part is empty.

#split["a";"b";"c";"d";"e";"f";"g";"h";"i";"j"]3;;-:stringlist*stringlist=(["a";"b";"c"],["d";"e";"f";"g";"h";"i";"j"])#split["a";"b";"c";"d"]5;;-:stringlist*stringlist=(["a";"b";"c";"d"],[])
#letsplitlistn=letrecauxiacc=function|[]->List.revacc,[]|h::tasl->ifi=0thenList.revacc,lelseaux(i-1)(h::acc)tinauxn[]list;;valsplit:'alist->int->'alist*'alist=<fun>

Extract a Slice From a List

Intermediate

Given two indices,i andk, the slice is the list containing theelements between thei'th andk'th element of the original list(both limits included). Start counting the elements with 0 (this is theway theList module numbers elements).

#slice["a";"b";"c";"d";"e";"f";"g";"h";"i";"j"]26;;-:stringlist=["c";"d";"e";"f";"g"]
#letslicelistik=letrectaken=function|[]->[]|h::t->ifn=0then[]elseh::take(n-1)tinletrecdropn=function|[]->[]|h::tasl->ifn=0thenlelsedrop(n-1)tintake(k-i+1)(dropilist);;valslice:'alist->int->int->'alist=<fun>

This solution has a drawback, namely that thetake function is nottail recursive so it mayexhaust the stack when given a very long list. You may also notice thatthe structure oftake anddrop is similar and you may want toabstract their common skeleton in a single function. Here is a solution.

#letrecfold_untilfaccn=function|[]->(acc,[])|h::tasl->ifn=0then(acc,l)elsefold_untilf(facch)(n-1)tletslicelistik=let_,list=fold_until(fun__->[])[]ilistinlettaken,_=fold_until(funacch->h::acc)[](k-i+1)listinList.revtaken;;valfold_until:('a->'b->'a)->'a->int->'blist->'a*'blist=<fun>valslice:'alist->int->int->'alist=<fun>

Rotate a List N Places to the Left

Intermediate

Rotate a list N places to the left.

#rotate["a";"b";"c";"d";"e";"f";"g";"h"]3;;-:stringlist=["d";"e";"f";"g";"h";"a";"b";"c"]
#letsplitlistn=letrecauxiacc=function|[]->List.revacc,[]|h::tasl->ifi=0thenList.revacc,lelseaux(i-1)(h::acc)tinauxn[]listletrotatelistn=letlen=List.lengthlistin(* Compute a rotation value between 0 and len - 1*)letn=iflen=0then0else(nmodlen+len)modleninifn=0thenlistelseleta,b=splitlistninb@a;;valsplit:'alist->int->'alist*'alist=<fun>valrotate:'alist->int->'alist=<fun>

Remove the K'th Element From a List

Beginner

Remove the K'th element from a list.

The first element of the list is numbered 0, the second 1,...

#remove_at1["a";"b";"c";"d"];;-:stringlist=["a";"c";"d"]
#letrecremove_atn=function|[]->[]|h::t->ifn=0thentelseh::remove_at(n-1)t;;valremove_at:int->'alist->'alist=<fun>

Insert an Element at a Given Position Into a List

Beginner

Start counting list elements with 0. If the position is larger orequal to the length of the list, insert the element at the end. (Thebehavior is unspecified if the position is negative.)

#insert_at"alfa"1["a";"b";"c";"d"];;-:stringlist=["a";"alfa";"b";"c";"d"]
#letrecinsert_atxn=function|[]->[x]|h::tasl->ifn=0thenx::lelseh::insert_atx(n-1)t;;valinsert_at:'a->int->'alist->'alist=<fun>

Create a List Containing All Integers Within a Given Range

Beginner

If first argument is greater than second, produce a list in decreasingorder.

#range49;;-:intlist=[4;5;6;7;8;9]
#letrangeab=letrecauxab=ifa>bthen[]elsea::aux(a+1)binifa>bthenList.rev(auxba)elseauxab;;valrange:int->int->intlist=<fun>

A tail recursive implementation:

#letrangeab=letrecauxacchighlow=ifhigh>=lowthenaux(high::acc)(high-1)lowelseaccinifa<bthenaux[]baelseList.rev(aux[]ab);;valrange:int->int->intlist=<fun>

Extract a Given Number of Randomly Selected Elements From a List

Intermediate

The selected items shall be returned in a list. We use theRandommodule but and initialise it withRandom.init 0 at the start ofthe function for reproducibility and validate the solution. To make the function truly random, however,one should remove the call toRandom.init 0

#rand_select["a";"b";"c";"d";"e";"f";"g";"h"]3;;-:stringlist=["e";"c";"g"]
#letrand_selectlistn=Random.init0;letrecextractaccn=function|[]->raiseNot_found|h::t->ifn=0then(h,acc@t)elseextract(h::acc)(n-1)tinletextract_randlistlen=extract[](Random.intlen)listinletrecauxnacclistlen=ifn=0thenaccelseletpicked,rest=extract_randlistleninaux(n-1)(picked::acc)rest(len-1)inletlen=List.lengthlistinaux(minnlen)[]listlen;;valrand_select:'alist->int->'alist=<fun>

Lotto: Draw N Different Random Numbers From the Set 1..M

Beginner

Draw N different random numbers from the set1..M.

The selected numbers shall be returned in a list.

#lotto_select649;;-:intlist=[20;28;45;16;24;38]
#(* [range] and [rand_select] defined in problems above*)letlotto_selectnm=rand_select(range1m)n;;vallotto_select:int->int->intlist=<fun>

Generate a Random Permutation of the Elements of a List

Beginner

Generate a random permutation of the elements of a list.

#permutation["a";"b";"c";"d";"e";"f"];;-:stringlist=["c";"d";"f";"e";"b";"a"]
#letpermutationlist=letrecextractaccn=function|[]->raiseNot_found|h::t->ifn=0then(h,acc@t)elseextract(h::acc)(n-1)tinletextract_randlistlen=extract[](Random.intlen)listinletrecauxacclistlen=iflen=0thenaccelseletpicked,rest=extract_randlistleninaux(picked::acc)rest(len-1)inaux[]list(List.lengthlist);;valpermutation:'alist->'alist=<fun>

Generate the Combinations of K Distinct Objects Chosen From the N Elements of a List

Intermediate

Generate the combinations of K distinct objects chosen from the N elements of a list.

In how many ways can a committee of 3 be chosen from a group of 12people? We all know that there are C(12,3) = 220 possibilities (C(N,K)denotes the well-known binomial coefficients). For pure mathematicians,this result may be great. But we want to really generate all thepossibilities in a list.

#extract2["a";"b";"c";"d"];;-:stringlistlist=[["a";"b"];["a";"c"];["a";"d"];["b";"c"];["b";"d"];["c";"d"]]
#letrecextractklist=ifk<=0then[[]]elsematchlistwith|[]->[]|h::tl->letwith_h=List.map(funl->h::l)(extract(k-1)tl)inletwithout_h=extractktlinwith_h@without_h;;valextract:int->'alist->'alistlist=<fun>

Group the Elements of a Set Into Disjoint Subsets

Intermediate

Group the elements of a set into disjoint subsets

  1. In how many ways can a group of 9 people work in 3 disjoint subgroupsof 2, 3 and 4 persons? Write a function that generates all thepossibilities and returns them in a list.
  2. Generalize the above function in a way that we can specify a list ofgroup sizes and the function will return a list of groups.
#group["a";"b";"c";"d"][2;1];;-:stringlistlistlist=[[["a";"b"];["c"]];[["a";"c"];["b"]];[["b";"c"];["a"]];[["a";"b"];["d"]];[["a";"c"];["d"]];[["b";"c"];["d"]];[["a";"d"];["b"]];[["b";"d"];["a"]];[["a";"d"];["c"]];[["b";"d"];["c"]];[["c";"d"];["a"]];[["c";"d"];["b"]]]
#(* This implementation is less streamlined than the one-extraction  version, because more work is done on the lists after each  transform to prepend the actual items. The end result is cleaner  in terms of code, though.*)letgrouplistsizes=letinitial=List.map(funsize->size,[])sizesin(* The core of the function. Prepend accepts a list of groups,        each with the number of items that should be added, and        prepends the item to every group that can support it, thus        turning [1,a ; 2,b ; 0,c] into [ [0,x::a ; 2,b ; 0,c ];        [1,a ; 1,x::b ; 0,c]; [ 1,a ; 2,b ; 0,c ]]        Again, in the prolog language (for which these questions are        originally intended), this function is a whole lot simpler.*)letprependplist=letemitlacc=l::accinletrecauxemitacc=function|[]->emit[]acc|(n,l)ash::t->letacc=ifn>0thenemit((n-1,p::l)::t)accelseaccinaux(funlacc->emit(h::l)acc)acctinauxemit[]listinletrecaux=function|[]->[initial]|h::t->List.concat_map(prependh)(auxt)inletall=auxlistin(* Don't forget to eliminate all group sets that have non-full     groups*)letcomplete=List.filter(List.for_all(fun(x,_)->x=0))allinList.map(List.mapsnd)complete;;valgroup:'alist->intlist->'alistlistlist=<fun>

Sorting a List of Lists According to Length of Sublists

Intermediate

Sorting a list of lists according to length of sublists.

  1. We suppose that a list contains elements that are lists themselves.The objective is to sort the elements of this list according to theirlength. E.g. short lists first, longer lists later, or vice versa.

  2. Again, we suppose that a list contains elements that are liststhemselves. But this time the objective is to sort the elements of thislist according to theirlength frequency; i.e., in the default,where sorting is done ascendingly, lists with rare lengths are placedfirst, others with a more frequent length come later.

#length_sort[["a";"b";"c"];["d";"e"];["f";"g";"h"];["d";"e"];["i";"j";"k";"l"];["m";"n"];["o"]];;-:stringlistlist=[["o"];["d";"e"];["d";"e"];["m";"n"];["a";"b";"c"];["f";"g";"h"];["i";"j";"k";"l"]]#frequency_sort[["a";"b";"c"];["d";"e"];["f";"g";"h"];["d";"e"];["i";"j";"k";"l"];["m";"n"];["o"]];;-:stringlistlist=[["i";"j";"k";"l"];["o"];["a";"b";"c"];["f";"g";"h"];["d";"e"];["d";"e"];["m";"n"]]
(* We might not be allowed to use built-in List.sort, so here's an   eight-line implementation of insertion sort — O(n²) time   complexity.*)letrecinsertcmpe=function|[]->[e]|h::tasl->ifcmpeh<=0thene::lelseh::insertcmpetletrecsortcmp=function|[]->[]|h::t->insertcmph(sortcmpt)(* Sorting according to length : prepend length, sort, remove length*)letlength_sortlists=letlists=List.map(funlist->List.lengthlist,list)listsinletlists=sort(funab->compare(fsta)(fstb))listsinList.mapsndlists;;(* Sorting according to length frequency : prepend frequency, sort,   remove frequency. Frequencies are extracted by sorting lengths   and applying RLE to count occurrences of each length (see problem"Run-length encoding of a list.")*)letrlelist=letrecauxcountacc=function|[]->[](* Can only be reached if original list is empty*)|[x]->(x,count+1)::acc|a::(b::_ast)->ifa=bthenaux(count+1)acctelseaux0((a,count+1)::acc)tinaux0[]listletfrequency_sortlists=letlengths=List.mapList.lengthlistsinletfreq=rle(sortcomparelengths)inletby_freq=List.map(funlist->List.assoc(List.lengthlist)freq,list)listsinletsorted=sort(funab->compare(fsta)(fstb))by_freqinList.mapsndsorted

Determine Whether a Given Integer Number Is Prime

Intermediate

Determine whether a given integer number is prime.

#not(is_prime1);;-:bool=true#is_prime7;;-:bool=true#not(is_prime12);;-:bool=true

Recall thatd dividesn if and only ifn mod d = 0. This is a naivesolution. See theSieve ofEratosthenes for amore clever one.

#letis_primen=letn=absninletrecis_not_divisord=d*d>n||(nmodd<>0&&is_not_divisor(d+1))inn>1&&is_not_divisor2;;valis_prime:int->bool=<fun>

Determine the Greatest Common Divisor of Two Positive Integer Numbers

Intermediate

Determine the greatest common divisor of two positive integer numbers.

Use Euclid's algorithm.

#gcd1327;;-:int=1#gcd205367826;;-:int=2
#letrecgcdab=ifb=0thenaelsegcdb(amodb);;valgcd:int->int->int=<fun>

Determine Whether Two Positive Integer Numbers Are Coprime

Beginner

Determine whether two positive integer numbers are coprime.

Two numbers are coprime if their greatest common divisor equals 1.

#coprime1327;;-:bool=true#not(coprime205367826);;-:bool=true
#(* [gcd] is defined in the previous question*)letcoprimeab=gcdab=1;;valcoprime:int->int->bool=<fun>

Calculate Euler's Totient Function Φ(m)

Intermediate

Euler's so-called totient function φ(m) is defined as the number ofpositive integers r (1 ≤ r < m) that are coprime to m. We let φ(1) = 1.

Find out what the value of φ(m) is if m is a prime number. Euler'stotient function plays an important role in one of the most widely usedpublic key cryptography methods (RSA). In this exercise you should usethe most primitive method to calculate this function (there are smarterways that we shall discuss later).

#phi10;;-:int=4
#(* [coprime] is defined in the previous question*)letphin=letreccount_coprimeaccd=ifd<nthencount_coprime(ifcoprimendthenacc+1elseacc)(d+1)elseaccinifn=1then1elsecount_coprime01;;valphi:int->int=<fun>

Determine the Prime Factors of a Given Positive Integer

Intermediate

Construct a flat list containing the prime factors in ascending order.

#factors315;;-:intlist=[3;3;5;7]
#(* Recall that d divides n iff [n mod d = 0]*)letfactorsn=letrecauxdn=ifn=1then[]elseifnmodd=0thend::auxd(n/d)elseaux(d+1)ninaux2n;;valfactors:int->intlist=<fun>

Determine the Prime Factors of a Given Positive Integer (2)

Intermediate

Construct a list containing the prime factors and their multiplicity.

Hint: The problem is similar to problemRun-length encoding of a list (direct solution).

#factors315;;-:(int*int)list=[(3,2);(5,1);(7,1)]
#letfactorsn=letrecauxdn=ifn=1then[]elseifnmodd=0thenmatchauxd(n/d)with|(h,n)::twhenh=d->(h,n+1)::t|l->(d,1)::lelseaux(d+1)ninaux2n;;valfactors:int->(int*int)list=<fun>

Calculate Euler's Totient Function Φ(m) (Improved)

Intermediate

See problem "Calculate Euler's totient function φ(m)" forthe definition of Euler's totient function. If the list of the primefactors of a number m is known in the form of the previous problem thenthe function phi(m) can be efficiently calculated as follows: Let[(p1, m1); (p2, m2); (p3, m3); ...] be the list of prime factors(and their multiplicities) of a given number m. Then φ(m) can becalculated with the following formula:

φ(m) = (p1 - 1) × p1m1 - 1 × (p2 - 1) ×p2m2 - 1 × (p3 - 1) × p3m3 - 1 × ⋯

#phi_improved10;;-:int=4#phi_improved13;;-:int=12
(* Naive power function.*)letrecpownp=ifp<1then1elsen*pown(p-1)(* [factors] is defined in the previous question.*)letphi_improvedn=letrecauxacc=function|[]->acc|(p,m)::t->aux((p-1)*powp(m-1)*acc)tinaux1(factorsn)

Compare the Two Methods of Calculating Euler's Totient Function

Beginner

Use the solutions of problems"Calculate Euler's totient function φ(m)" and"Calculate Euler's totient function φ(m) (improved)"to compare the algorithms. Take the number of logical inferences as a measure for efficiency. Try to calculate φ(10090) as an example.

timeitphi10090
#(* Naive [timeit] function.  It requires the [Unix] module to be loaded.*)lettimeitfa=lett0=Unix.gettimeofday()inignore(fa);lett1=Unix.gettimeofday()int1-.t0;;valtimeit:('a->'b)->'a->float=<fun>

A List of Prime Numbers

Beginner

Given a range of integers by its lower and upper limit, construct a listof all prime numbers in that range.

#List.length(all_primes27920);;-:int=1000
#letis_primen=letn=maxn(-n)inletrecis_not_divisord=d*d>n||(nmodd<>0&&is_not_divisor(d+1))inis_not_divisor2letrecall_primesab=ifa>bthen[]elseletrest=all_primes(a+1)binifis_primeathena::restelserest;;valis_prime:int->bool=<fun>valall_primes:int->int->intlist=<fun>

Goldbach's Conjecture

Intermediate

Goldbach's conjecture says that every positive even number greater than2 is the sum of two prime numbers. Example: 28 = 5 + 23. It is one ofthe most famous facts in number theory that has not been proved to becorrect in the general case. It has beennumerically confirmed up tovery large numbers. Write a function to find the two prime numbers thatsum up to a given even integer.

#goldbach28;;-:int*int=(5,23)
#(* [is_prime] is defined in the previous solution*)letgoldbachn=letrecauxd=ifis_primed&&is_prime(n-d)then(d,n-d)elseaux(d+1)inaux2;;valgoldbach:int->int*int=<fun>

A List of Goldbach Compositions

Intermediate

Given a range of integers by its lower and upper limit, print a list ofall even numbers and their Goldbach composition.

In most cases, if an even number is written as the sum of two primenumbers, one of them is very small. Very rarely, the primes are bothbigger than say 50. Try to find out how many such cases there are in therange 2..3000.

#goldbach_list920;;-:(int*(int*int))list=[(10,(3,7));(12,(5,7));(14,(3,11));(16,(3,13));(18,(5,13));(20,(3,17))]
#(* [goldbach] is defined in the previous question.*)letrecgoldbach_listab=ifa>bthen[]elseifamod2=1thengoldbach_list(a+1)belse(a,goldbacha)::goldbach_list(a+2)bletgoldbach_limitablim=List.filter(fun(_,(a,b))->a>lim&&b>lim)(goldbach_listab);;valgoldbach_list:int->int->(int*(int*int))list=<fun>valgoldbach_limit:int->int->int->(int*(int*int))list=<fun>

Truth Tables for Logical Expressions (2 Variables)

Intermediate

Let us define a small "language" for boolean expressions containingvariables:

#typebool_expr=|Varofstring|Notofbool_expr|Andofbool_expr*bool_expr|Orofbool_expr*bool_expr;;typebool_expr=Varofstring|Notofbool_expr|Andofbool_expr*bool_expr|Orofbool_expr*bool_expr

A logical expression in two variables can then be written in prefixnotation. For example,(a ∨ b) ∧ (a ∧ b) is written:

#And(Or(Var"a",Var"b"),And(Var"a",Var"b"));;-:bool_expr=And(Or(Var"a",Var"b"),And(Var"a",Var"b"))

Define a function,table2 which returns the truth table of a givenlogical expression in two variables (specified as arguments). The returnvalue must be a list of triples containing(value_of_a, value_of_b, value_of_expr).

#table2"a""b"(And(Var"a",Or(Var"a",Var"b")));;-:(bool*bool*bool)list=[(true,true,true);(true,false,true);(false,true,false);(false,false,false)]
#letreceval2aval_abval_b=function|Varx->ifx=athenval_aelseifx=bthenval_belsefailwith"The expression contains an invalid variable"|Note->not(eval2aval_abval_be)|And(e1,e2)->eval2aval_abval_be1&&eval2aval_abval_be2|Or(e1,e2)->eval2aval_abval_be1||eval2aval_abval_be2lettable2abexpr=[(true,true,eval2atruebtrueexpr);(true,false,eval2atruebfalseexpr);(false,true,eval2afalsebtrueexpr);(false,false,eval2afalsebfalseexpr)];;valeval2:string->bool->string->bool->bool_expr->bool=<fun>valtable2:string->string->bool_expr->(bool*bool*bool)list=<fun>

Truth Tables for Logical Expressions

Intermediate

Generalize the previous problem in such a way that the logicalexpression may contain any number of logical variables. Definetablein a way thattable variables expr returns the truth table for theexpressionexpr, which contains the logical variables enumerated invariables.

#table["a";"b"](And(Var"a",Or(Var"a",Var"b")));;-:((string*bool)list*bool)list=[([("a",true);("b",true)],true);([("a",true);("b",false)],true);([("a",false);("b",true)],false);([("a",false);("b",false)],false)]
#(* [val_vars] is an associative list containing the truth value of     each variable.  For efficiency, a Map or a Hashtlb should be     preferred.*)letrecevalval_vars=function|Varx->List.assocxval_vars|Note->not(evalval_varse)|And(e1,e2)->evalval_varse1&&evalval_varse2|Or(e1,e2)->evalval_varse1||evalval_varse2(* Again, this is an easy and short implementation rather than an     efficient one.*)letrectable_makeval_varsvarsexpr=matchvarswith|[]->[(List.revval_vars,evalval_varsexpr)]|v::tl->table_make((v,true)::val_vars)tlexpr@table_make((v,false)::val_vars)tlexprlettablevarsexpr=table_make[]varsexpr;;valeval:(string*bool)list->bool_expr->bool=<fun>valtable_make:(string*bool)list->stringlist->bool_expr->((string*bool)list*bool)list=<fun>valtable:stringlist->bool_expr->((string*bool)list*bool)list=<fun>

Gray Code

Intermediate

An n-bit Gray code is a sequence of n-bit strings constructed accordingto certain rules. For example,

n = 1: C(1) = ['0', '1'].n = 2: C(2) = ['00', '01', '11', '10'].n = 3: C(3) = ['000', '001', '011', '010', '110', '111', '101', '100'].

Find out the construction rules and write a function with the followingspecification:gray n returns then-bit Gray code.

#gray1;;-:stringlist=["0";"1"]#gray2;;-:stringlist=["00";"01";"11";"10"]#gray3;;-:stringlist=["000";"001";"011";"010";"110";"111";"101";"100"]
#letgrayn=letrecgray_next_levelkl=ifk<nthen(* This is the core part of the Gray code construction.         * first_half is reversed and has a"0" attached to every element.         * Second part is reversed (it must be reversed for correct gray code).         * Every element has"1" attached to the front.*)let(first_half,second_half)=List.fold_left(fun(acc1,acc2)x->(("0"^x)::acc1,("1"^x)::acc2))([],[])lin(* List.rev_append turns first_half around and attaches it to second_half.         * The result is the modified first_half in correct order attached to         * the second_half modified in reversed order.*)gray_next_level(k+1)(List.rev_appendfirst_halfsecond_half)elselingray_next_level1["0";"1"];;valgray:int->stringlist=<fun>

Huffman Code

Advanced

First of all, consult a good book on discrete mathematics or algorithmsfor a detailed description of Huffman codes (you can start with theWikipedia page)!

We consider a set of symbols with their frequencies.For example, if the alphabet is"a",...,"f"(represented as the positions 0,...5) andrespective frequencies are 45, 13, 12, 16, 9, 5:

#letfs=[("a",45);("b",13);("c",12);("d",16);("e",9);("f",5)];;valfs:(string*int)list=[("a",45);("b",13);("c",12);("d",16);("e",9);("f",5)]

Our objective is to construct theHuffman codec word for all symbolss. In our example, the result couldbehs = [("a", "0"); ("b", "101"); ("c", "100"); ("d", "111"); ("e", "1101"); ("f", "1100")](orhs = [("a", "1");...]). The task shall be performed by the functionhuffman defined as follows:huffman(fs) returns the Huffman codetable for the frequency tablefs

#huffmanfs;;-:(string*string)list=[("a","0");("c","100");("b","101");("f","1100");("e","1101");("d","111")]
#(* Simple priority queue where the priorities are integers 0..100.     The node with the lowest probability comes first.*)modulePq=structtype'at={data:'alistarray;mutablefirst:int}letmake()={data=Array.make101[];first=101}letaddqpx=q.data.(p)<-x::q.data.(p);q.first<-minpq.firstletget_minq=ifq.first=101thenNoneelsematchq.data.(q.first)with|[]->assertfalse|x::tl->letp=q.firstinq.data.(q.first)<-tl;whileq.first<101&&q.data.(q.first)=[]doq.first<-q.first+1done;Some(p,x)endtypetree=|Leafofstring|Nodeoftree*treeletrechuffman_treeq=matchPq.get_minq,Pq.get_minqwith|Some(p1,t1),Some(p2,t2)->Pq.addq(p1+p2)(Node(t1,t2));huffman_treeq|Some(_,t),None|None,Some(_,t)->t|None,None->assertfalse(* Build the prefix-free binary code from the tree*)letrecprefixes_of_treeprefix=function|Leafs->[(s,prefix)]|Node(t0,t1)->prefixes_of_tree(prefix^"0")t0@prefixes_of_tree(prefix^"1")t1lethuffmanfs=ifList.fold_left(funs(_,p)->s+p)0fs<>100thenfailwith"huffman: sum of weights must be 100";letq=Pq.make()inList.iter(fun(s,f)->Pq.addqf(Leafs))fs;prefixes_of_tree""(huffman_treeq);;modulePq:sigtype'at = { data : 'a list array;mutable first : int; }valmake : unit -> 'a tvaladd : 'a t -> int -> 'a -> unitvalget_min : 'a t -> (int * 'a) optionendtypetree=Leafofstring|Nodeoftree*treevalhuffman_tree:treePq.t->tree=<fun>valprefixes_of_tree:string->tree->(string*string)list=<fun>valhuffman:(string*int)list->(string*string)list=<fun>

Construct Completely Balanced Binary Trees

Intermediate

Binary Tree

A binary tree is either empty or it is composed of a root element andtwo successors, which are binary trees themselves.

In OCaml, one can define a new typebinary_tree that carries anarbitrary value of type'a (thus is polymorphic) at each node.

#type'abinary_tree=|Empty|Nodeof'a*'abinary_tree*'abinary_tree;;type'abinary_tree=Empty|Nodeof'a*'abinary_tree*'abinary_tree

An example of tree carryingchar data is:

#letexample_tree=Node('a',Node('b',Node('d',Empty,Empty),Node('e',Empty,Empty)),Node('c',Empty,Node('f',Node('g',Empty,Empty),Empty)));;valexample_tree:charbinary_tree=Node('a',Node('b',Node('d',Empty,Empty),Node('e',Empty,Empty)),Node('c',Empty,Node('f',Node('g',Empty,Empty),Empty)))

In OCaml, the strict type disciplineguarantees that, if you get avalue of typebinary_tree, then it must have been created with the twoconstructorsEmpty andNode.

In a completely balanced binary tree, the following property holds forevery node: The number of nodes in its left subtree and the number ofnodes in its right subtree are almost equal, which means theirdifference is not greater than one.

Write a functioncbal_tree to construct completely balanced binarytrees for a given number of nodes. The function should generate allsolutions via backtracking. Put the letter'x' as information into allnodes of the tree.

#cbal_tree4;;-:charbinary_tree/2list=[Node('x',Node('x',Empty,Empty),Node('x',Node('x',Empty,Empty),Empty));Node('x',Node('x',Empty,Empty),Node('x',Empty,Node('x',Empty,Empty)));Node('x',Node('x',Node('x',Empty,Empty),Empty),Node('x',Empty,Empty));Node('x',Node('x',Empty,Node('x',Empty,Empty)),Node('x',Empty,Empty))]
#(* Build all trees with given [left] and [right] subtrees.*)letadd_trees_withleftrightall=letadd_right_treealll=List.fold_left(funar->Node('x',l,r)::a)allrightinList.fold_leftadd_right_treeallleftletreccbal_treen=ifn=0then[Empty]elseifnmod2=1thenlett=cbal_tree(n/2)inadd_trees_withtt[]else(* n even: n-1 nodes for the left & right subtrees altogether.*)lett1=cbal_tree(n/2-1)inlett2=cbal_tree(n/2)inadd_trees_witht1t2(add_trees_witht2t1[]);;valadd_trees_with:charbinary_treelist->charbinary_treelist->charbinary_treelist->charbinary_treelist=<fun>valcbal_tree:int->charbinary_treelist=<fun>

Symmetric Binary Trees

Intermediate

Let us call a binary tree symmetric if you can draw a vertical linethrough the root node and then the right subtree is the mirror image ofthe left subtree. Write a functionis_symmetric to check whether agiven binary tree is symmetric.

Hint: Write a functionis_mirror first to check whether one treeis the mirror image of another. We are only interested in thestructure, not in the contents of the nodes.

#letrecis_mirrort1t2=matcht1,t2with|Empty,Empty->true|Node(_,l1,r1),Node(_,l2,r2)->is_mirrorl1r2&&is_mirrorr1l2|_->falseletis_symmetric=function|Empty->true|Node(_,l,r)->is_mirrorlr;;valis_mirror:'abinary_tree->'bbinary_tree->bool=<fun>valis_symmetric:'abinary_tree->bool=<fun>

Binary Search Trees (Dictionaries)

Intermediate

Construct abinary search treefrom a list of integer numbers.

#construct[3;2;5;7;1];;-:intbinary_tree=Node(3,Node(2,Node(1,Empty,Empty),Empty),Node(5,Empty,Node(7,Empty,Empty)))

Then use this function to test the solution of the previous problem.

#is_symmetric(construct[5;3;18;1;4;12;21]);;-:bool=true#not(is_symmetric(construct[3;2;5;7;4]));;-:bool=true
#letrecinserttreex=matchtreewith|Empty->Node(x,Empty,Empty)|Node(y,l,r)->ifx=ythentreeelseifx<ythenNode(y,insertlx,r)elseNode(y,l,insertrx)letconstructl=List.fold_leftinsertEmptyl;;valinsert:'abinary_tree->'a->'abinary_tree=<fun>valconstruct:'alist->'abinary_tree=<fun>

Generate-and-Test Paradigm

Intermediate

Apply the generate-and-test paradigm to construct all symmetric,completely balanced binary trees with a given number of nodes.

#sym_cbal_trees5;;-:charbinary_treelist=[Node('x',Node('x',Node('x',Empty,Empty),Empty),Node('x',Empty,Node('x',Empty,Empty)));Node('x',Node('x',Empty,Node('x',Empty,Empty)),Node('x',Node('x',Empty,Empty),Empty))]

How many such trees are there with 57 nodes? Investigate about how manysolutions there are for a given number of nodes? What if the number iseven? Write an appropriate function.

#List.length(sym_cbal_trees57);;-:int=256
#letsym_cbal_treesn=List.filteris_symmetric(cbal_treen);;valsym_cbal_trees:int->charbinary_treelist=<fun>

Construct Height-Balanced Binary Trees

Intermediate

In a height-balanced binary tree, the following property holds for everynode: The height of its left subtree and the height of its right subtreeare almost equal, which means their difference is not greater than one.

Write a functionhbal_tree to construct height-balanced binary treesfor a given height. The function should generate all solutions viabacktracking. Put the letter'x' as information into all nodes of thetree.

#lett=hbal_tree3;;valt:charbinary_treelist=[Node('x',Node('x',Empty,Node('x',Empty,Empty)),Node('x',Empty,Node('x',Empty,Empty)));Node('x',Node('x',Empty,Node('x',Empty,Empty)),Node('x',Node('x',Empty,Empty),Empty));Node('x',Node('x',Empty,Node('x',Empty,Empty)),Node('x',Node('x',Empty,Empty),Node('x',Empty,Empty)));Node('x',Node('x',Node('x',Empty,Empty),Empty),Node('x',Empty,Node('x',Empty,Empty)));Node('x',Node('x',Node('x',Empty,Empty),Empty),Node('x',Node('x',Empty,Empty),Empty));Node('x',Node('x',Node('x',Empty,Empty),Empty),Node('x',Node('x',Empty,Empty),Node('x',Empty,Empty)));Node('x',Node('x',Node('x',Empty,Empty),Node('x',Empty,Empty)),Node('x',Empty,Node('x',Empty,Empty)));Node('x',Node('x',Node('x',Empty,Empty),Node('x',Empty,Empty)),Node('x',Node('x',Empty,Empty),Empty));Node('x',Node('x',Node('x',Empty,Empty),Node('x',Empty,Empty)),Node('x',Node('x',Empty,Empty),Node('x',Empty,Empty)));Node('x',Node('x',Empty,Node('x',Empty,Empty)),Node('x',Empty,Empty));Node('x',Node('x',Node('x',Empty,Empty),Empty),Node('x',Empty,Empty));Node('x',Node('x',Node('x',Empty,Empty),Node('x',Empty,Empty)),Node('x',Empty,Empty));Node('x',Node('x',Empty,Empty),Node('x',Empty,Node('x',Empty,Empty)));Node('x',Node('x',Empty,Empty),Node('x',Node('x',Empty,Empty),Empty));Node('x',Node('x',Empty,Empty),Node('x',Node('x',Empty,Empty),Node('x',Empty,Empty)))]

The functionadd_trees_with is defined in the solution ofConstruct completely balanced binary trees.

#letrechbal_treen=ifn=0then[Empty]elseifn=1then[Node('x',Empty,Empty)]else(* [add_trees_with left right trees] is defined in a question above.*)lett1=hbal_tree(n-1)andt2=hbal_tree(n-2)inadd_trees_witht1t1(add_trees_witht1t2(add_trees_witht2t1[]));;valhbal_tree:int->charbinary_treelist=<fun>

Construct Height-Balanced Binary Trees With a Given Number of Nodes

Intermediate

Consider a height-balanced binary tree of heighth. What is themaximum number of nodes it can contain? Clearly,max_nodes = 2h - 1.

#letmax_nodesh=1lslh-1;;valmax_nodes:int->int=<fun>

Minimum of nodes

However, what is the minimum number min_nodes? This question is moredifficult. Try to find a recursive statement and turn it into a functionmin_nodes defined as follows:min_nodes h returns the minimum numberof nodes in a height-balanced binary tree of heighth.

Minimum height

On the other hand, we might ask: what are the minimum (resp. maximum)height H aheight-balanced binary tree with N nodes can have?min_height (resp.max_height n) returnsthe minimum (resp. maximum) height of a height-balanced binary treewithn nodes.

Constructing trees

Now, we can attack the main problem: construct all the height-balancedbinary trees with a given number of nodes.hbal_tree_nodes n returns alist of all height-balanced binary tree withn nodes.

Find out how many height-balanced trees exist forn = 15.

#List.length(hbal_tree_nodes15);;-:int=1553

Minimum of nodes

The following solution comes directly from translating the question.

#letrecmin_nodesh=ifh<=0then0elseifh=1then1elsemin_nodes(h-1)+min_nodes(h-2)+1;;valmin_nodes:int->int=<fun>

It is not the more efficient one however. One should use the lasttwo values as the state to avoid the double recursion.

#letrecmin_nodes_loopm0m1h=ifh<=1thenm1elsemin_nodes_loopm1(m1+m0+1)(h-1)letmin_nodesh=ifh<=0then0elsemin_nodes_loop01h;;valmin_nodes_loop:int->int->int->int=<fun>valmin_nodes:int->int=<fun>

It is not difficult to show thatmin_nodes h = Fh+2‌ - 1,where (Fn) is theFibonacci sequence.

Minimum height

Inverting the formula max_nodes = 2h - 1, one directlyfind that Hₘᵢₙ(n) = ⌈log₂(n+1)⌉ which is readily implemented:

#letmin_heightn=int_of_float(ceil(log(float(n+1))/.log2.));;valmin_height:int->int=<fun>

Let us give a proof that the formula for Hₘᵢₙ is valid. First, if h=min_height n, there exists a height-balanced tree of height hwith n nodes. Thus 2ʰ - 1 =max_nodes h ≥ n i.e., h ≥ log₂(n+1).To establish equality for Hₘᵢₙ(n), one has to show that, for any n,there exists a height-balanced tree with height Hₘᵢₙ(n). This isdue to the relation Hₘᵢₙ(n) = 1 + Hₘᵢₙ(n/2) where n/2 is the integerdivision. For n odd, this is readily proved — so one can build atree with a top node and two sub-trees with n/2 nodes of heightHₘᵢₙ(n) - 1. For n even, the same proof works if one first remarksthat, in that case, ⌈log₂(n+2)⌉ = ⌈log₂(n+1)⌉ — use log₂(n+1) ≤ h ∈ℕ ⇔ 2ʰ ≥ n + 1 and the fact that 2ʰ is even for that. This allowsto have a sub-tree with n/2 nodes. For the other sub-tree withn/2-1 nodes, one has to establish that Hₘᵢₙ(n/2-1) ≥ Hₘᵢₙ(n) - 2which is easy because, if h = Hₘᵢₙ(n/2-1), then h+2 ≥ log₂(2n) ≥log₂(n+1).

The above function is not the best one however. Indeed, not every64 bits integer can be represented exactly as a floating pointnumber. Here is one that only uses integer operations:

#letrecceil_log2_looplogplus1n=ifn=1thenifplus1thenlog+1elselogelseceil_log2_loop(log+1)(plus1||nland1<>0)(n/2)letceil_log2n=ceil_log2_loop0falsen;;valceil_log2_loop:int->bool->int->int=<fun>valceil_log2:int->int=<fun>

This algorithm is still not the fastest however. See for exampletheHacker's Delight, section 5-3(and 11-4).

Following the same idea as above, if h =max_height n, then oneeasily deduces thatmin_nodes h ≤ n <min_nodes(h+1). Thisyields the following code:

#letrecmax_height_searchhn=ifmin_nodesh<=nthenmax_height_search(h+1)nelseh-1letmax_heightn=max_height_search0n;;valmax_height_search:int->int->int=<fun>valmax_height:int->int=<fun>

Of course, sincemin_nodes is computed recursively, there is noneed to recompute everything to go frommin_nodes h tomin_nodes(h+1):

#letrecmax_height_searchhm_hm_h1n=ifm_h<=nthenmax_height_search(h+1)m_h1(m_h1+m_h+1)nelseh-1letmax_heightn=max_height_search001n;;valmax_height_search:int->int->int->int->int=<fun>valmax_height:int->int=<fun>

Constructing trees

First, we define some convenience functionsfold_range that foldsa functionf on the rangen0...n1 i.e., it computesf (... f (f (f init n0) (n0+1)) (n0+2) ...) n1. You can think itas performing the assignmentinit ← f init n forn = n0,..., n1except that there is no mutable variable in the code.

#letrecfold_range~f ~initn0n1=ifn0>n1theninitelsefold_range ~f ~init:(finitn0)(n0+1)n1;;valfold_range:f:('a->int->'a)->init:'a->int->int->'a=<fun>

When constructing trees, there is an obvious symmetry: if one swapsthe left and right sub-trees of a balanced tree, we still have abalanced tree. The following function returns all trees intreestogether with their permutation.

#letrecadd_swap_left_righttrees=List.fold_left(funan->matchnwith|Node(v,t1,t2)->Node(v,t2,t1)::a|Empty->a)treestrees;;valadd_swap_left_right:'abinary_treelist->'abinary_treelist=<fun>

Finally we generate all trees recursively, using a priori the boundscomputed above. It could be further optimized but our aim is tostraightforwardly express the idea.

#letrechbal_tree_nodes_heighthn=assert(min_nodesh<=n&&n<=max_nodesh);ifh=0then[Empty]elseletacc=add_hbal_tree_node[](h-1)(h-2)ninletacc=add_swap_left_rightaccinadd_hbal_tree_nodeacc(h-1)(h-1)nandadd_hbal_tree_nodelh1h2n=letmin_n1=max(min_nodesh1)(n-1-max_nodesh2)inletmax_n1=min(max_nodesh1)(n-1-min_nodesh2)infold_rangemin_n1max_n1 ~init:l ~f:(funln1->lett1=hbal_tree_nodes_heighth1n1inlett2=hbal_tree_nodes_heighth2(n-1-n1)inList.fold_left(funlt1->List.fold_left(funlt2->Node('x',t1,t2)::l)lt2)lt1)lethbal_tree_nodesn=fold_range(min_heightn)(max_heightn) ~init:[] ~f:(funlh->List.rev_append(hbal_tree_nodes_heighthn)l);;valhbal_tree_nodes_height:int->int->charbinary_treelist=<fun>valadd_hbal_tree_node:charbinary_treelist->int->int->int->charbinary_treelist=<fun>valhbal_tree_nodes:int->charbinary_treelist=<fun>

Count the Leaves of a Binary Tree

Beginner

A leaf is a node with no successors. Write a functioncount_leaves tocount them.

#count_leavesEmpty;;-:int=0
#letreccount_leaves=function|Empty->0|Node(_,Empty,Empty)->1|Node(_,l,r)->count_leavesl+count_leavesr;;valcount_leaves:'abinary_tree->int=<fun>

Collect the Leaves of a Binary Tree in a List

Beginner

A leaf is a node with no successors. Write a functionleaves tocollect them in a list.

#leavesEmpty;;-:'alist=[]
#(* Having an accumulator acc prevents using inefficient List.append.   * Every Leaf will be pushed directly into accumulator.   * Not tail-recursive, but that is no problem since we have a binary tree and   * and stack depth is logarithmic.*)letleavest=letrecleaves_auxtacc=matchtwith|Empty->acc|Node(x,Empty,Empty)->x::acc|Node(x,l,r)->leaves_auxl(leaves_auxracc)inleaves_auxt[];;valleaves:'abinary_tree->'alist=<fun>

Collect the Internal Nodes of a Binary Tree in a List

Beginner

An internal node of a binary tree has either one or two non-emptysuccessors. Write a functioninternals to collect them in a list.

#internals(Node('a',Empty,Empty));;-:charlist=[]
#(* Having an accumulator acc prevents using inefficient List.append.   * Every internal node will be pushed directly into accumulator.   * Not tail-recursive, but that is no problem since we have a binary tree and   * and stack depth is logarithmic.*)letinternalst=letrecinternals_auxtacc=matchtwith|Empty->acc|Node(x,Empty,Empty)->acc|Node(x,l,r)->internals_auxl(x::internals_auxracc)ininternals_auxt[];;valinternals:'abinary_tree->'alist=<fun>

Collect the Nodes at a Given Level in a List

Beginner

A node of a binary tree is at level N if the path from the root to thenode has length N-1. The root node is at level 1. Write a functionat_level t l to collect all nodes of the treet at levell in alist.

#letexample_tree=Node('a',Node('b',Node('d',Empty,Empty),Node('e',Empty,Empty)),Node('c',Empty,Node('f',Node('g',Empty,Empty),Empty)));;valexample_tree:charbinary_tree=Node('a',Node('b',Node('d',Empty,Empty),Node('e',Empty,Empty)),Node('c',Empty,Node('f',Node('g',Empty,Empty),Empty)))#at_levelexample_tree2;;-:charlist=['b';'c']

Usingat_level it is easy to construct a functionlevelorder whichcreates the level-order sequence of the nodes. However, there are moreefficient ways to do that.

#(* Having an accumulator acc prevents using inefficient List.append.   * Every node at level N will be pushed directly into accumulator.   * Not tail-recursive, but that is no problem since we have a binary tree and   * and stack depth is logarithmic.*)letat_leveltlevel=letrecat_level_auxtacccounter=matchtwith|Empty->acc|Node(x,l,r)->ifcounter=levelthenx::accelseat_level_auxl(at_level_auxracc(counter+1))(counter+1)inat_level_auxt[]1;;valat_level:'abinary_tree->int->'alist=<fun>

Construct a Complete Binary Tree

Intermediate

Acomplete binary tree with height H is defined as follows: The levels1,2,3,...,H-1 contain the maximum number of nodes (i.e 2i-1at the level i, note that we start counting the levels from 1 at theroot). In level H, which may contain less than the maximum possiblenumber of nodes, all the nodes are "left-adjusted". This means that in alevelorder tree traversal all internal nodes come first, the leaves comesecond, and empty successors (the nil's which are not really nodes!)come last.

Particularly, complete binary trees are used as data structures (oraddressing schemes) for heaps.

We can assign an address number to each node in a complete binary treeby enumerating the nodes in levelorder, starting at the root withnumber 1. In doing so, we realize that for every node X with address Athe following property holds: The address of X's left and rightsuccessors are 2*A and 2*A+1, respectively, supposed the successors doexist. This fact can be used to elegantly construct a complete binarytree structure. Write a functionis_complete_binary_tree with thefollowing specification:is_complete_binary_tree n t returnstrueifft is a complete binary tree withn nodes.

#complete_binary_tree[1;2;3;4;5;6];;-:intbinary_tree=Node(1,Node(2,Node(4,Empty,Empty),Node(5,Empty,Empty)),Node(3,Node(6,Empty,Empty),Empty))
#letrecsplit_nlstaccn=match(n,lst)with|(0,_)->(List.revacc,lst)|(_,[])->(List.revacc,[])|(_,h::t)->split_nt(h::acc)(n-1)letrecmyflattenpc=match(p,c)with|(p,[])->List.map(funx->Node(x,Empty,Empty))p|(x::t,[y])->Node(x,y,Empty)::myflattent[]|(ph::pt,x::y::t)->(Node(ph,x,y))::myflattenptt|_->invalid_arg"myflatten"letcomplete_binary_tree=function|[]->Empty|lst->letrecauxl=function|[]->[]|lst->letp,c=split_nlst[](1lsll)inmyflattenp(aux(l+1)c)inList.hd(aux0lst);;valsplit_n:'alist->'alist->int->'alist*'alist=<fun>valmyflatten:'alist->'abinary_treelist->'abinary_treelist=<fun>valcomplete_binary_tree:'alist->'abinary_tree=<fun>

Layout a Binary Tree (1)

Intermediate

As a preparation for drawing the tree, a layout algorithm is required todetermine the position of each node in a rectangular grid. Severallayout methods are conceivable, one of them is shown in the illustration.

Binary Tree Grid

In this layout strategy, the position of a node v is obtained by thefollowing two rules:

  • x(v) is equal to the position of the node v in theinordersequence;
  • y(v) is equal to the depth of the nodev in the tree.

In order to store the position of the nodes, we will enrich the valueat each node with the position(x,y).

The tree pictured above is

#letexample_layout_tree=letleafx=Node(x,Empty,Empty)inNode('n',Node('k',Node('c',leaf'a',Node('h',Node('g',leaf'e',Empty),Empty)),leaf'm'),Node('u',Node('p',Empty,Node('s',leaf'q',Empty)),Empty));;valexample_layout_tree:charbinary_tree=Node('n',Node('k',Node('c',Node('a',Empty,Empty),Node('h',Node('g',Node('e',Empty,Empty),Empty),Empty)),Node('m',Empty,Empty)),Node('u',Node('p',Empty,Node('s',Node('q',Empty,Empty),Empty)),Empty))
#layout_binary_tree_1example_layout_tree;;-:(char*int*int)binary_tree=Node(('n',8,1),Node(('k',6,2),Node(('c',2,3),Node(('a',1,4),Empty,Empty),Node(('h',5,4),Node(('g',4,5),Node(('e',3,6),Empty,Empty),Empty),Empty)),Node(('m',7,3),Empty,Empty)),Node(('u',12,2),Node(('p',9,3),Empty,Node(('s',11,4),Node(('q',10,5),Empty,Empty),Empty)),Empty))
#letlayout_binary_tree_1t=letreclayoutdepthx_left=function(* This function returns a pair: the laid out tree and the first       * free x location*)|Empty->(Empty,x_left)|Node(v,l,r)->let(l',l_x_max)=layout(depth+1)x_leftlinlet(r',r_x_max)=layout(depth+1)(l_x_max+1)rin(Node((v,l_x_max,depth),l',r'),r_x_max)infst(layout11t);;vallayout_binary_tree_1:'abinary_tree->('a*int*int)binary_tree=<fun>

Layout a Binary Tree (2)

Intermediate

Binary Tree Grid

An alternative layout method is depicted in this illustration. Findout the rules and write the corresponding OCaml function.

Hint: On a given level, the horizontal distance betweenneighbouring nodes is constant.

The tree shown is

#letexample_layout_tree=letleafx=Node(x,Empty,Empty)inNode('n',Node('k',Node('c',leaf'a',Node('e',leaf'd',leaf'g')),leaf'm'),Node('u',Node('p',Empty,leaf'q'),Empty));;valexample_layout_tree:charbinary_tree=Node('n',Node('k',Node('c',Node('a',Empty,Empty),Node('e',Node('d',Empty,Empty),Node('g',Empty,Empty))),Node('m',Empty,Empty)),Node('u',Node('p',Empty,Node('q',Empty,Empty)),Empty))
#layout_binary_tree_2example_layout_tree;;-:(char*int*int)binary_tree=Node(('n',15,1),Node(('k',7,2),Node(('c',3,3),Node(('a',1,4),Empty,Empty),Node(('e',5,4),Node(('d',4,5),Empty,Empty),Node(('g',6,5),Empty,Empty))),Node(('m',11,3),Empty,Empty)),Node(('u',23,2),Node(('p',19,3),Empty,Node(('q',21,4),Empty,Empty)),Empty))
#letlayout_binary_tree_2t=letrecheight=function|Empty->0|Node(_,l,r)->1+max(heightl)(heightr)inlettree_height=heighttinletrecfind_missing_leftdepth=function|Empty->tree_height-depth|Node(_,l,_)->find_missing_left(depth+1)linlettranslate_dst=1lsl(find_missing_left0t)-1in(* remember than 1 lsl a = 2ᵃ*)letreclayoutdepthx_root=function|Empty->Empty|Node(x,l,r)->letspacing=1lsl(tree_height-depth-1)inletl'=layout(depth+1)(x_root-spacing)landr'=layout(depth+1)(x_root+spacing)rinNode((x,x_root,depth),l',r')inlayout1((1lsl(tree_height-1))-translate_dst)t;;vallayout_binary_tree_2:'abinary_tree->('a*int*int)binary_tree=<fun>

Layout a Binary Tree (3)

Advanced

Binary Tree Grid

Yet another layout strategy is shown in the above illustration. Themethod yields a very compact layout while maintaining a certain symmetryin every node. Find out the rules and write the correspondingpredicate.

Hint: Consider the horizontal distance between a node and its successornodes. How tight can you pack together two subtrees to construct thecombined binary tree? This is a difficult problem. Don't give up tooearly!

#letexample_layout_tree=letleafx=Node(x,Empty,Empty)inNode('n',Node('k',Node('c',leaf'a',Node('h',Node('g',leaf'e',Empty),Empty)),leaf'm'),Node('u',Node('p',Empty,Node('s',leaf'q',Empty)),Empty));;valexample_layout_tree:charbinary_tree=Node('n',Node('k',Node('c',Node('a',Empty,Empty),Node('h',Node('g',Node('e',Empty,Empty),Empty),Empty)),Node('m',Empty,Empty)),Node('u',Node('p',Empty,Node('s',Node('q',Empty,Empty),Empty)),Empty))#layout_binary_tree_3example_layout_tree;;-:(char*int*int)binary_tree=Node(('n',5,1),Node(('k',3,2),Node(('c',2,3),Node(('a',1,4),Empty,Empty),Node(('h',3,4),Node(('g',2,5),Node(('e',1,6),Empty,Empty),Empty),Empty)),Node(('m',4,3),Empty,Empty)),Node(('u',7,2),Node(('p',6,3),Empty,Node(('s',7,4),Node(('q',6,5),Empty,Empty),Empty)),Empty))

Which layout do you like most?

In order to pack the tree tightly, the layout function will returnin addition to the layout of the tree the left and right profiles ofthe tree, that is lists of offsets relative to the position of theroot node of the tree.

#letlayout_binary_tree_3=letrectranslate_xd=function|Empty->Empty|Node((v,x,y),l,r)->Node((v,x+d,y),translate_xdl,translate_xdr)in(* Distance between a left subtree given by its right profile [lr]       and a right subtree given by its left profile [rl].*)letrecdistlrrl=matchlr,rlwith|lrx::ltl,rlx::rtl->max(lrx-rlx)(distltlrtl)|[],_|_,[]->0inletrecmerge_profilesp1p2=matchp1,p2with|x1::tl1,_::tl2->x1::merge_profilestl1tl2|[],_->p2|_,[]->p1inletreclayoutdepth=function|Empty->([],Empty,[])|Node(v,l,r)->let(ll,l',lr)=layout(depth+1)linlet(rl,r',rr)=layout(depth+1)rinletd=1+distlrrl/2inletll=List.map(funx->x-d)llandlr=List.map(funx->x-d)lrandrl=List.map((+)d)rlandrr=List.map((+)d)rrin(0::merge_profilesllrl,Node((v,0,depth),translate_x(-d)l',translate_xdr'),0::merge_profilesrrlr)infunt->let(l,t',_)=layout1tinletx_min=List.fold_leftmin0lintranslate_x(1-x_min)t';;vallayout_binary_tree_3:'abinary_tree->('a*int*int)binary_tree=<fun>

A String Representation of Binary Trees

Intermediate

Binary Tree

Somebody represents binary trees as strings of the following type (seeexample):"a(b(d,e),c(,f(g,)))".

  • Write an OCaml functionstring_of_tree which generates thisstring representation,if the tree is given as usual (asEmpty orNode(x,l,r) term).Then write a functiontree_of_string which does this inverse;i.e. given the stringrepresentation, construct the tree in the usual form. Finally,combine the two predicates in a single functiontree_string whichcan be used in both directions.
  • Write the same predicatetree_string using difference lists and asingle predicatetree_dlist which does the conversion between atree and a difference list in both directions.

For simplicity, suppose the information in the nodes is a single letterand there are no spaces in the string.

#letexample_layout_tree=letleafx=Node(x,Empty,Empty)in(Node('a',Node('b',leaf'd',leaf'e'),Node('c',Empty,Node('f',leaf'g',Empty))));;valexample_layout_tree:charbinary_tree=Node('a',Node('b',Node('d',Empty,Empty),Node('e',Empty,Empty)),Node('c',Empty,Node('f',Node('g',Empty,Empty),Empty)))

A simple solution is:

#letrecstring_of_tree=function|Empty->""|Node(data,l,r)->letdata=String.make1datainmatchl,rwith|Empty,Empty->data|_,_->data^"("^(string_of_treel)^","^(string_of_treer)^")";;valstring_of_tree:charbinary_tree->string=<fun>

One can also use a buffer to allocate a lot less memory:

#letrecbuffer_add_treebuf=function|Empty->()|Node(data,l,r)->Buffer.add_charbufdata;matchl,rwith|Empty,Empty->()|_,_->Buffer.add_charbuf'(';buffer_add_treebufl;Buffer.add_charbuf',';buffer_add_treebufr;Buffer.add_charbuf')'letstring_of_treet=letbuf=Buffer.create128inbuffer_add_treebuft;Buffer.contentsbuf;;valbuffer_add_tree:Buffer.t->charbinary_tree->unit=<fun>valstring_of_tree:charbinary_tree->string=<fun>

For the reverse conversion, we assume that the string is well formedand do not deal with error reporting.

#lettree_of_string=letrecmakeofss=ifofs>=String.lengths||s.[ofs]=','||s.[ofs]=')'then(Empty,ofs)elseletv=s.[ofs]inifofs+1<String.lengths&&s.[ofs+1]='('thenletl,ofs=make(ofs+2)sin(* skip"v("*)letr,ofs=make(ofs+1)sin(* skip","*)(Node(v,l,r),ofs+1)(* skip")"*)else(Node(v,Empty,Empty),ofs+1)infuns->fst(make0s);;valtree_of_string:string->charbinary_tree=<fun>

Preorder and Inorder Sequences of Binary Trees

Intermediate

We consider binary trees with nodes that are identified by singlelower-case letters, as in the example of the previous problem.

  1. Write functionspreorder andinorderthat construct thepreorderandinordersequence of a given binary tree, respectively. Theresults should be atoms, e.g. 'abdecfg' for the preorder sequence ofthe example in the previous problem.
  2. Can you usepreorder from problem part 1 in the reversedirection; i.e. given a preorder sequence, construct a correspondingtree? If not, make the necessary arrangements.
  3. If both the preorder sequence and the inorder sequence of the nodesof a binary tree are given, then the tree is determinedunambiguously. Write a functionpre_in_tree that does the job.
  4. Solve problems 1 to 3 usingdifference lists.Cool! Use thefunctiontimeit (defined in problem “Compare the two methods ofcalculating Euler's totient function.”) to compare thesolutions.

What happens if the same character appears in more than one node. Tryfor instancepre_in_tree "aba" "baa".

#preorder(Node(1,Node(2,Empty,Empty),Empty));;-:intlist=[1;2]

We use lists to represent the result. Note thatpreorder andinorder can be made more efficient by avoiding list concatenations.

#letrecpreorder=function|Empty->[]|Node(v,l,r)->v::(preorderl@preorderr)letrecinorder=function|Empty->[]|Node(v,l,r)->inorderl@(v::inorderr)letrecsplit_pre_inpixaccpacci=match(p,i)with|[],[]->(List.revaccp,List.revacci),([],[])|h1::t1,h2::t2->ifx=h2then(List.tl(List.rev(h1::accp)),t1),(List.rev(List.tl(h2::acci)),t2)elsesplit_pre_int1t2x(h1::accp)(h2::acci)|_->assertfalseletrecpre_in_treepi=match(p,i)with|[],[]->Empty|(h1::t1),(h2::t2)->let(lp,rp),(li,ri)=split_pre_inpih1[][]inNode(h1,pre_in_treelpli,pre_in_treerpri)|_->invalid_arg"pre_in_tree";;valpreorder:'abinary_tree->'alist=<fun>valinorder:'abinary_tree->'alist=<fun>valsplit_pre_in:'alist->'alist->'a->'alist->'alist->('alist*'alist)*('alist*'alist)=<fun>valpre_in_tree:'alist->'alist->'abinary_tree=<fun>

Solution usingdifference lists.

(* solution pending*)

Dotstring Representation of Binary Trees

Intermediate

We consider again binary trees with nodes that are identified by singlelower-case letters, as in the example of problem “A stringrepresentation of binary trees”. Such a tree can berepresented by the preorder sequence of its nodes in which dots (.) areinserted where an empty subtree (nil) is encountered during the treetraversal. For example, the tree shown in problem “A stringrepresentation of binary trees” is represented as'abd..e..c.fg...'. First, try to establish a syntax (BNF or syntaxdiagrams) and then write a functiontree_dotstring which does theconversion in both directions. Use difference lists.

(* solution pending*)

Tree Construction From a Node String

Intermediate

Multiway Tree

A multiway tree is composed of a root element and a (possibly empty)set of successors which are multiway trees themselves. A multiway treeis never empty. The set of successor trees is sometimes called aforest.

To represent multiway trees, we will use the following type which is adirect translation of the definition:

#type'amult_tree=Tof'a*'amult_treelist;;type'amult_tree=Tof'a*'amult_treelist

The example tree depicted opposite is therefore represented by thefollowing OCaml expression:

#T('a',[T('f',[T('g',[])]);T('c',[]);T('b',[T('d',[]);T('e',[])])]);;-:charmult_tree=T('a',[T('f',[T('g',[])]);T('c',[]);T('b',[T('d',[]);T('e',[])])])

We suppose that the nodes of a multiway tree contain single characters.In the depth-first order sequence of its nodes, a special character^has been inserted whenever, during the tree traversal, the move is abacktrack to the previous level.

By this rule, the tree in the figure opposite is represented as:afg^^c^bd^e^^^.

Write functionsstring_of_tree : char mult_tree -> string to constructthe string representing the tree andtree_of_string : string -> char mult_tree to construct the tree whenthe string is given.

#lett=T('a',[T('f',[T('g',[])]);T('c',[]);T('b',[T('d',[]);T('e',[])])]);;valt:charmult_tree=T('a',[T('f',[T('g',[])]);T('c',[]);T('b',[T('d',[]);T('e',[])])])
#(* We could build the final string by string concatenation but     this is expensive due to the number of operations.  We use a     buffer instead.*)letrecadd_string_of_treebuf(T(c,sub))=Buffer.add_charbufc;List.iter(add_string_of_treebuf)sub;Buffer.add_charbuf'^';;valadd_string_of_tree:Buffer.t->charmult_tree->unit=<fun>#letstring_of_treet=letbuf=Buffer.create128inadd_string_of_treebuft;Buffer.contentsbuf;;valstring_of_tree:charmult_tree->string=<fun>#lettree_of_strings=letrecparse_nodechars=matchcharswith|[]->failwith"Unexpected end of input (expecting node)"|c::rest->let(children,rest')=parse_childrenrestin(T(c,children),rest')andparse_childrenchars=matchcharswith|[]->failwith"Unexpected end of input (expecting ^)"|'^'::rest->([],rest)|_->let(child,rest')=parse_nodecharsinlet(siblings,rest'')=parse_childrenrest'in(child::siblings,rest'')inlet(tree,remaining)=parse_node(List.of_seq(String.to_seqs))inmatchremainingwith|[]->tree|_->failwith"Extra input after tree";;valtree_of_string:string->charmult_tree=<fun>

Count the Nodes of a Multiway Tree

Beginner
#count_nodes(T('a',[T('f',[])]));;-:int=2
#letreccount_nodes(T(_,sub))=List.fold_left(funnt->n+count_nodest)1sub;;valcount_nodes:'amult_tree->int=<fun>

Determine the Internal Path Length of a Tree

Beginner

We define the internal path length of a multiway tree as the total sumof the path lengths from the root to all nodes of the tree. By thisdefinition, the treet in the figure of the previous problem has aninternal path length of 9. Write a functionipl tree that returns theinternal path length oftree.

#iplt;;-:int=9
#letrecipl_sublen(T(_,sub))=(* [len] is the distance of the current node to the root.  Add the       distance of all sub-nodes.*)List.fold_left(funsumt->sum+ipl_sub(len+1)t)lensubletiplt=ipl_sub0t;;valipl_sub:int->'amult_tree->int=<fun>valipl:'amult_tree->int=<fun>

Construct the Bottom-Up Order Sequence of the Tree Nodes

Beginner

Write a functionbottom_up t which constructs the bottom-up sequenceof the nodes of the multiway treet.

#bottom_up(T('a',[T('b',[])]));;-:charlist=['b';'a']#bottom_upt;;-:charlist=['g';'f';'c';'d';'e';'b';'a']
#letrecprepend_bottom_up(T(c,sub))l=List.fold_right(funtl->prepend_bottom_uptl)sub(c::l)letbottom_upt=prepend_bottom_upt[];;valprepend_bottom_up:'amult_tree->'alist->'alist=<fun>valbottom_up:'amult_tree->'alist=<fun>

Lisp-Like Tree Representation

Intermediate

There is a particular notation for multiway trees in Lisp. Thepicture shows how multiway tree structures are represented in Lisp.

Lisp representation of trees

Note that in the "lispy" notation a node with successors (children) inthe tree is always the first element in a list, followed by itschildren. The "lispy" representation of a multiway tree is a sequence ofatoms and parentheses '(' and ')'. This is very close to the way treesare represented in OCaml, except that no constructorT is used. Writea functionlispy : char mult_tree -> string that returns thelispy notation of the tree.

#lispy(T('a',[]));;-:string="a"#lispy(T('a',[T('b',[])]));;-:string="(a b)"#lispyt;;-:string="(a (f g) c (b d e))"
#letrecadd_lispybuf=function|T(c,[])->Buffer.add_charbufc|T(c,sub)->Buffer.add_charbuf'(';Buffer.add_charbufc;List.iter(funt->Buffer.add_charbuf' ';add_lispybuft)sub;Buffer.add_charbuf')'letlispyt=letbuf=Buffer.create128inadd_lispybuft;Buffer.contentsbuf;;valadd_lispy:Buffer.t->charmult_tree->unit=<fun>vallispy:charmult_tree->string=<fun>

Conversions

Beginner

A graph

A graph is defined as a set of nodes and a set of edges, where eachedge is a pair of different nodes.

There are several ways to represent graphs in OCaml.

  • One method is to list all edges, an edge being a pair of nodes. Inthis form, the graph depicted above is represented as thefollowing expression:
#[('h','g');('k','f');('f','b');('f','c');('c','b')];;-:(char*char)list=[('h','g');('k','f');('f','b');('f','c');('c','b')]

We call thisedge-clause form. Obviously, isolated nodes cannotbe represented.

  • Another method is to represent the whole graph as one data object.According to the definition of the graph as a pair of two sets(nodes and edges), we may use the following OCaml type:
#type'agraph_term={nodes:'alist;edges:('a*'a)list};;type'agraph_term={nodes:'alist;edges:('a*'a)list;}

Then, the above example graph is represented by:

#letexample_graph={nodes=['b';'c';'d';'f';'g';'h';'k'];edges=[('h','g');('k','f');('f','b');('f','c');('c','b')]};;valexample_graph:chargraph_term={nodes=['b';'c';'d';'f';'g';'h';'k'];edges=[('h','g');('k','f');('f','b');('f','c');('c','b')]}

We call thisgraph-term form. Note, that the lists are keptsorted, they are really sets, without duplicated elements. Each edgeappears only once in the edge list; i.e. an edge from a node x toanother node y is represented as(x, y), the couple(y, x) is notpresent. Thegraph-term form is our default representation. Youmay want to define a similar type using sets instead of lists.

  • A third representation method is to associate with each node the setof nodes that are adjacent to that node. We call this theadjacency-list form. In our example:
letadjacency_example=['b',['c';'f'];'c',['b';'f'];'d',[];'f',['b';'c';'k'];'g',['h'];'h',['g'];'k',['f']];;valadjacency_example:(char*charlist)list=[('b',['c';'f']);('c',['b';'f']);('d',[]);('f',['b';'c';'k']);('g',['h']);('h',['g']);('k',['f'])]
  • The representations we introduced so far are well suited for automatedprocessing, but their syntax is not very user-friendly. Typing theterms by hand is cumbersome and error-prone. We can define a morecompact and "human-friendly" notation as follows: A graph (with charlabelled nodes) is represented by a string of atoms and terms of thetype X-Y. The atoms stand for isolated nodes, the X-Y terms describeedges. If an X appears as an endpoint of an edge, it isautomatically defined as a node. Our example could be written as:
#"b-c f-c g-h d f-b k-f h-g";;-:string="b-c f-c g-h d f-b k-f h-g"

We call this thehuman-friendly form. As the example shows, thelist does not have to be sorted and may even contain the same edgemultiple times. Notice the isolated noded.

Write functions to convert between the different graph representations.With these functions, all representations are equivalent; i.e. for thefollowing problems you can always pick freely the most convenient form.This problem is not particularly difficult, but it's a lot of work todeal with all the special cases.

(* example pending*)

Path From One Node to Another One

Intermediate

Write a functionpaths g a b that returns all acyclic pathp fromnodea to nodeb ≠ a in the graphg. The function should returnthe list of all paths via backtracking.

#letexample_graph={nodes=['b';'c';'d';'f';'g';'h';'k'];edges=[('h','g');('k','f');('f','b');('f','c');('c','b')]};;valexample_graph:chargraph_term={nodes=['b';'c';'d';'f';'g';'h';'k'];edges=[('h','g');('k','f');('f','b');('f','c');('c','b')]}#pathsexample_graph'f''b';;-:charlistlist=[['f';'c';'b'];['f';'b']]
#(* The datastructures used here are far from the most efficient ones     but allow for a straightforward implementation.*)(* Returns all neighbors satisfying the condition.*)letneighborsgacond=letedgel(b,c)=ifb=a&&condcthenc::lelseifc=a&&condbthenb::lelselinList.fold_leftedge[]g.edgesletreclist_pathgato_b=matchto_bwith|[]->assertfalse(* [to_b] contains the path to [b].*)|a'::_->ifa'=athen[to_b]elseletn=neighborsga'(func->not(List.memcto_b))inList.concat_map(func->list_pathga(c::to_b))nletpathsgab=assert(a<>b);list_pathga[b];;valneighbors:'agraph_term->'a->('a->bool)->'alist=<fun>vallist_path:'agraph_term->'a->'alist->'alistlist=<fun>valpaths:'agraph_term->'a->'a->'alistlist=<fun>

Cycle From a Given Node

Beginner

Write a functionscycle g a that returns a closed path (cycle)pstarting at a given nodea in the graphg. The predicate shouldreturn the list of all cycles via backtracking.

#letexample_graph={nodes=['b';'c';'d';'f';'g';'h';'k'];edges=[('h','g');('k','f');('f','b');('f','c');('c','b')]};;valexample_graph:chargraph_term={nodes=['b';'c';'d';'f';'g';'h';'k'];edges=[('h','g');('k','f');('f','b');('f','c');('c','b')]}#cyclesexample_graph'f';;-:charlistlist=[['f';'b';'c';'f'];['f';'c';'f'];['f';'c';'b';'f'];['f';'b';'f'];['f';'k';'f']]
#letcyclesga=letn=neighborsga(fun_->true)inletp=List.concat_map(func->list_pathga[c])ninList.map(funp->p@[a])p;;valcycles:'agraph_term->'a->'alistlist=<fun>

Construct All Spanning Trees

Intermediate

Spanning tree graph

Write a functions_tree g to construct (by backtracking) allspanningtrees of a given graphg.With this predicate, find out how many spanning trees there are for thegraph depicted to the left. The data of this example graph can be foundin the test below. When you have a correct solution for thes_treefunction, use it to define two other useful functions:is_tree graphandis_connected Graph. Both are five-minutes tasks!

#letg={nodes=['a';'b';'c';'d';'e';'f';'g';'h'];edges=[('a','b');('a','d');('b','c');('b','e');('c','e');('d','e');('d','f');('d','g');('e','h');('f','g');('g','h')]};;valg:chargraph_term={nodes=['a';'b';'c';'d';'e';'f';'g';'h'];edges=[('a','b');('a','d');('b','c');('b','e');('c','e');('d','e');('d','f');('d','g');('e','h');('f','g');('g','h')]}
(* solution pending*);;

Construct the Minimal Spanning Tree

Intermediate

Spanning tree graph

Write a functionms_tree graph to construct the minimal spanning treeof a given labelled graph. A labelled graph will be represented asfollows:

#type('a,'b)labeled_graph={nodes:'alist;labeled_edges:('a*'a*'b)list};;type('a,'b)labeled_graph={nodes:'alist;labeled_edges:('a*'a*'b)list;}

(Beware that from now onnodes andedges mask the previous fields ofthe same name.)

Hint: Use thealgorithm of Prim.A small modification of the solution of P83 does the trick. The data of theexample graph to the right can be found below.

#letg={nodes=['a';'b';'c';'d';'e';'f';'g';'h'];labeled_edges=[('a','b',5);('a','d',3);('b','c',2);('b','e',4);('c','e',6);('d','e',7);('d','f',4);('d','g',3);('e','h',5);('f','g',4);('g','h',1)]};;valg:(char,int)labeled_graph={nodes=['a';'b';'c';'d';'e';'f';'g';'h'];labeled_edges=[('a','b',5);('a','d',3);('b','c',2);('b','e',4);('c','e',6);('d','e',7);('d','f',4);('d','g',3);('e','h',5);('f','g',4);('g','h',1)]}
(* solution pending*);;

Graph Isomorphism

Intermediate

Two graphs G1(N1,E1) and G2(N2,E2) are isomorphic if there is abijection f: N1 → N2 such that for any nodes X,Y of N1, X and Y areadjacent if and only if f(X) and f(Y) are adjacent.

Write a function that determines whether two graphs are isomorphic.

Hint: Use an open-ended list to represent the function f.

#letg={nodes=[1;2;3;4;5;6;7;8];edges=[(1,5);(1,6);(1,7);(2,5);(2,6);(2,8);(3,5);(3,7);(3,8);(4,6);(4,7);(4,8)]};;valg:intgraph_term={nodes=[1;2;3;4;5;6;7;8];edges=[(1,5);(1,6);(1,7);(2,5);(2,6);(2,8);(3,5);(3,7);(3,8);(4,6);(4,7);(4,8)]}
(* solution pending*);;

Node Degree and Graph Coloration

Intermediate
  • Write a functiondegree graph node that determines the degree of agiven node.
  • Write a function that generates a list of all nodes of a graphsorted according to decreasing degree.
  • UseWelsh-Powell'salgorithmto paint the nodes of a graph in such a way that adjacent nodes havedifferent colors.
(* example pending*);;

Depth-First Order Graph Traversal

Intermediate

Write a function that generates adepth-first order graph traversalsequence. The starting point should be specified, and the output shouldbe a list of nodes that are reachable from this starting point (indepth-first order).

Specifically, the graph will be provided by itsadjacency-list representationand you must create a moduleM with the following signature:

#moduletypeGRAPH=sigtypenode = chartypetvalof_adjacency : (node * node list) list -> tvaldfs_fold : t -> node -> ('a -> node -> 'a) -> 'a -> 'aend;;moduletypeGRAPH=sigtypenode = chartypetvalof_adjacency : (node * node list) list -> tvaldfs_fold : t -> node -> ('a -> node -> 'a) -> 'a -> 'aend

whereM.dfs_fold g n f a appliesf on the nodes of the graphg in depth first order, starting with noden.

#letg=M.of_adjacency['u',['v';'x'];'v',['y'];'w',['z';'y'];'x',['v'];'y',['x'];'z',['z'];];;valg:M.t=<abstr>

In a depth-first search you fully explore the edges of the mostrecently discovered nodev before 'backtracking' to explore edgesleaving the node from whichv was discovered. To do a depth-firstsearch means keeping careful track of what vertices have been visitedand when.

We compute timestamps for each vertex discovered in the search. Adiscovered vertex has two timestamps associated with it : itsdiscovery time (in mapd) and its finishing time (in mapf) (avertex is finished when its adjacency list has been completelyexamined). These timestamps are often useful in graph algorithms andaid in reasoning about the behavior of depth-first search.

We color nodes during the search to help in the bookkeeping (mapcolor). All vertices of the graph are initiallyWhite. When avertex is discovered it is markedGray and when it is finished, itis markedBlack.

If vertexv is discovered in the adjacency list of previouslydiscovered nodeu, this fact is recorded in the predecessor subgraph(mappred).

#moduleM:GRAPH=structmoduleChar_map=Map.Make(Char)typenode=chartypet=(nodelist)Char_map.tletof_adjacencyl=List.fold_right(fun(x,y)->Char_map.addxy)lChar_map.emptytypecolors=White|Gray|Blacktype'astate={d:intChar_map.t;(*discovery time*)f:intChar_map.t;(*finishing time*)pred:charChar_map.t;(*predecessor*)color:colorsChar_map.t;(*vertex colors*)acc:'a;(*user specified type used by 'fold'*)}letdfs_foldgcfnacc=letrecdfs_visittu{d;f;pred;color;acc}=letedge(t,state)v=ifChar_map.findvstate.color=Whitethendfs_visittv{statewithpred=Char_map.addvustate.pred}else(t,state)inlett,{d;f;pred;color;acc}=lett=t+1inList.fold_leftedge(t,{d=Char_map.addutd;f;pred;color=Char_map.adduGraycolor;acc=fnaccu})(Char_map.findug)inlett=t+1int,{d;f=Char_map.addutf;pred;color=Char_map.adduBlackcolor;acc}inletv=List.fold_left(funk(x,_)->x::k)[](Char_map.bindingsg)inletinitial_state={d=Char_map.empty;f=Char_map.empty;pred=Char_map.empty;color=List.fold_right(funx->Char_map.addxWhite)vChar_map.empty;acc}in(snd(dfs_visit0cinitial_state)).accend;;moduleM:GRAPH

Connected Components

Intermediate

Write a predicate that splits a graph into itsconnectedcomponents.

(* example pending*);;

Bipartite Graphs

Intermediate

Write a predicate that finds out whether a given graph isbipartite.

(* example pending*);;

Generate K-Regular Simple Graphs With N Nodes

Advanced

In aK-regular graph allnodes have a degree of K; i.e. the number of edges incident in each nodeis K. How many (non-isomorphic!) 3-regular graphs with 6 nodes arethere?

See also thetable of results.

(* example pending*);;

Eight Queens Problem

Intermediate

This is a classical problem in computer science. The objective is toplace eight queens on a chessboard so that no two queens are attackingeach other; i.e., no two queens are in the same row, the same column, oron the same diagonal.

Hint: Represent the positions of the queens as a list of numbers 1..N.Example:[4; 2; 7; 3; 6; 8; 5; 1] means that the queen in the first column isin row 4, the queen in the second column is in row 2, etc. Use thegenerate-and-test paradigm.

#queens_positions4;;-:intlistlist=[[3;1;4;2];[2;4;1;3]]

This is a brute force algorithm enumerating all possible solutions.For a deeper analysis, look for example toWikipedia.

#letpossiblerowcolused_rowsusedD1usedD2=not(List.memrowused_rows||List.mem(row+col)usedD1||List.mem(row-col)usedD2)letqueens_positionsn=letrecauxrowcolused_rowsusedD1usedD2=ifcol>nthen[List.revused_rows]else(ifrow<nthenaux(row+1)colused_rowsusedD1usedD2else[])@(ifpossiblerowcolused_rowsusedD1usedD2thenaux1(col+1)(row::used_rows)(row+col::usedD1)(row-col::usedD2)else[])inaux11[][][];;valpossible:int->int->intlist->intlist->intlist->bool=<fun>valqueens_positions:int->intlistlist=<fun>

Knight's Tour

Intermediate

Another famous problem is this one: How can a knight jump on an N×Nchessboard in such a way that it visits every square exactly once?

Hint: Represent the squares by pairs of their coordinates(x,y),where bothx andy are integers between 1 and N. Define the functionjump n (x,y) that returns all coordinates(u,v) to which aknight can jump from(x,y) to on an×n chessboard. And finally,represent the solution of our problem as a list knight positions (theknight's tour).

(* example pending*);;

Von Koch's Conjecture

Advanced

Several years ago I met a mathematician who was intrigued by a problemfor which he didn't know a solution. His name was Von Koch, and I don'tknow whether the problem has been solved since.

Tree numbering

Anyway, the puzzle goes like this: Given a tree with N nodes (and henceN-1 edges). Find a way to enumerate the nodes from 1 to N and,accordingly, the edges from 1 to N-1 in such a way, that for each edge Kthe difference of its node numbers equals to K. The conjecture is thatthis is always possible.

For small trees the problem is easy to solve by hand. However, forlarger trees, and 14 is already very large, it is extremely difficult tofind a solution. And remember, we don't know for sure whether there isalways a solution!

Larger tree

Write a function that calculates a numbering scheme for a given tree.What is the solution for the larger tree pictured here?

(* example pending*);;

An Arithmetic Puzzle

Advanced

Given a list of integer numbers, find a correct way of insertingarithmetic signs (operators) such that the result is a correct equation.Example: With the list of numbers[2; 3; 5; 7; 11] we can form theequations 2 - 3 + 5 + 7 = 11 or 2 = (3 * 5 + 7) / 11 (and ten others!).

(* example pending*);;

English Number Words

Intermediate

On financial documents, like cheques, numbers must sometimes be writtenin full words. Example: 175 must be written as one-seven-five. Write afunctionfull_words to print (non-negative) integer numbers in fullwords.

#full_words175;;-:string="one-seven-five"
#letfull_words=letdigit=[|"zero";"one";"two";"three";"four";"five";"six";"seven";"eight";"nine"|]inletrecwordswn=ifn=0then(matchwwith[]->[digit.(0)]|_->w)elsewords(digit.(nmod10)::w)(n/10)infunn->String.concat"-"(words[]n);;valfull_words:int->string=<fun>

Syntax Checker

Intermediate

Syntax graph

In a certain programming language (Ada) identifiers are defined by thesyntax diagram (railroad chart) opposite. Transform the syntax diagraminto a system of syntax diagrams which do not contain loops; i.e. whichare purely recursive. Using these modified diagrams, write a functionidentifier : string -> bool that can check whether or not a givenstring is a legal identifier.

#identifier"this-is-a-long-identifier";;-:bool=true
#letidentifier=letis_letterc='a'<=c&&c<='z'inletis_letter_or_digitc=is_letterc||('0'<=c&&c<='9')inletrecis_validsinot_after_dash=ifi<0thennot_after_dashelseifis_letter_or_digits.[i]thenis_valids(i-1)trueelseifs.[i]='-'&&not_after_dashthenis_valids(i-1)falseelsefalseinfuns->(letn=String.lengthsinn>0&&is_letters.[n-1]&&is_valids(n-2)true);;validentifier:string->bool=<fun>

Sudoku

Intermediate

Sudoku puzzles go like this:

   Problem statement                 Solution    .  .  4 | 8  .  . | .  1  7      9  3  4 | 8  2  5 | 6  1  7            |         |                      |         |    6  7  . | 9  .  . | .  .  .      6  7  2 | 9  1  4 | 8  5  3            |         |                      |         |    5  .  8 | .  3  . | .  .  4      5  1  8 | 6  3  7 | 9  2  4    --------+---------+--------      --------+---------+--------    3  .  . | 7  4  . | 1  .  .      3  2  5 | 7  4  8 | 1  6  9            |         |                      |         |    .  6  9 | .  .  . | 7  8  .      4  6  9 | 1  5  3 | 7  8  2            |         |                      |         |    .  .  1 | .  6  9 | .  .  5      7  8  1 | 2  6  9 | 4  3  5    --------+---------+--------      --------+---------+--------    1  .  . | .  8  . | 3  .  6      1  9  7 | 5  8  2 | 3  4  6            |         |                      |         |    .  .  . | .  .  6 | .  9  1      8  5  3 | 4  7  6 | 2  9  1            |         |                      |         |    2  4  . | .  .  1 | 5  .  .      2  4  6 | 3  9  1 | 5  7  8

Every spot in the puzzle belongs to a (horizontal) row and a (vertical)column, as well as to one single 3x3 square (which we call "square" forshort). At the beginning, some of the spots carry a single-digit numberbetween 1 and 9. The problem is to fill the missing spots with digits insuch a way that every number between 1 and 9 appears exactly once ineach row, in each column, and in each square.

#(* The board representation is not imposed.  Here"0" stands for"."*);;

A simple way of resolving this is to use brute force.The idea is to start filling with available values in each case andtest if it works. When there is no available values, it means wemade a mistake so we go back to the last choice we made, and try adifferent choice.

#openPrintfmoduleBoard=structtypet=intarray(* 9×9, row-major representation.  A value of 0                          means undecided.*)letis_validc=c>=1letget(b:t)(x,y)=b.(x+y*9)letget_as_string(b:t)pos=leti=getbposinifis_validithenstring_of_intielse"."letwith_val(b:t)(x,y)v=letb=Array.copybinb.(x+y*9)<-v;bletof_listl:t=letb=Array.make810inList.iteri(funyr->List.iteri(funxe->b.(x+y*9)<-ife>=0&&e<=9theneelse0)r)l;bletprintb=fory=0to8doforx=0to8doprintf(ifx=0then"%s"elseifxmod3=0then" |%s"else"%s")(get_as_stringb(x,y))done;ify<8thenifymod3=2thenprintf"\n--------+---------+--------\n"elseprintf"\n        |         |\n"elseprintf"\n"doneletavailableb(x,y)=letavail=Array.make10trueinfori=0to8doavail.(getb(x,i))<-false;avail.(getb(i,y))<-false;done;letsq_x=x-xmod3andsq_y=y-ymod3inforx=sq_xtosq_x+2dofory=sq_ytosq_y+2doavail.(getb(x,y))<-false;done;done;letav=ref[]infori=1(* not 0*)to9doifavail.(i)thenav:=i::!avdone;!avletnext(x,y)=ifx<8then(x+1,y)else(0,y+1)(** Try to fill the undecided entries.*)letrecfillb((x,y)aspos)=ify>8thenSomeb(* filled all entries*)elseifis_valid(getbpos)thenfillb(nextpos)elsematchavailablebposwith|[]->None(* no solution*)|l->try_valuesbposlandtry_valuesbpos=function|v::l->(matchfill(with_valbposv)(nextpos)with|Some_asres->res|None->try_valuesbposl)|[]->Noneendletsudokub=matchBoard.fillb(0,0)with|Someb->b|None->failwith"sudoku: no solution";;moduleBoard:sigtypet = int arrayvalis_valid : int -> boolvalget : t -> int * int -> intvalget_as_string : t -> int * int -> stringvalwith_val : t -> int * int -> int -> int arrayvalof_list : int list list -> tvalprint : t -> unitvalavailable : t -> int * int -> int listvalnext : int * int -> int * intvalfill : t -> int * int -> t optionvaltry_values : t -> int * int -> int list -> t optionendvalsudoku:Board.t->Board.t=<fun>

Nonograms

Advanced

Around 1994, a certain kind of puzzles was very popular in England. The"Sunday Telegraph" newspaper wrote: "Nonograms are puzzles from Japanand are currently published each week only in The Sunday Telegraph.Simply use your logic and skill to complete the grid and reveal apicture or diagram." As an OCaml programmer, you are in a bettersituation: you can have your computer do the work!

The puzzle goes like this: Essentially, each row and column of arectangular bitmap is annotated with the respective lengths of itsdistinct strings of occupied cells. The person who solves the puzzlemust complete the bitmap given only these lengths.

          Problem statement:          Solution:          |_|_|_|_|_|_|_|_| 3         |_|X|X|X|_|_|_|_| 3          |_|_|_|_|_|_|_|_| 2 1       |X|X|_|X|_|_|_|_| 2 1          |_|_|_|_|_|_|_|_| 3 2       |_|X|X|X|_|_|X|X| 3 2          |_|_|_|_|_|_|_|_| 2 2       |_|_|X|X|_|_|X|X| 2 2          |_|_|_|_|_|_|_|_| 6         |_|_|X|X|X|X|X|X| 6          |_|_|_|_|_|_|_|_| 1 5       |X|_|X|X|X|X|X|_| 1 5          |_|_|_|_|_|_|_|_| 6         |X|X|X|X|X|X|_|_| 6          |_|_|_|_|_|_|_|_| 1         |_|_|_|_|X|_|_|_| 1          |_|_|_|_|_|_|_|_| 2         |_|_|_|X|X|_|_|_| 2           1 3 1 7 5 3 4 3             1 3 1 7 5 3 4 3           2 1 5 1                     2 1 5 1

For the example above, the problem can be stated as the two lists[[3]; [2; 1]; [3; 2]; [2; 2]; [6]; [1; 5]; [6]; [1]; [2]] and[[1; 2]; [3; 1]; [1; 5]; [7; 1]; [5]; [3]; [4]; [3]] which give the "solid"lengths of the rows and columns, top-to-bottom and left-to-right,respectively. Published puzzles are larger than this example, e.g.25×20, and apparently always have unique solutions.

#solve[[3];[2;1];[3;2];[2;2];[6];[1;5];[6];[1];[2]][[1;2];[3;1];[1;5];[7;1];[5];[3];[4];[3]];;

Brute force solution: construct boards trying all the fillpossibilities for the columns given the prescribed patterns for themand reject the solution if it does not satisfy the row patterns.

#typeelement=Empty|X(* ensure we do not miss cases in patterns*);;typeelement=Empty|X

You may want to look atmore efficient algorithmsand implement them so you can solve the following within reasonable time:

solve[[14];[1;1];[7;1];[3;3];[2;3;2];[2;3;2];[1;3;6;1;1];[1;8;2;1];[1;4;6;1];[1;3;2;5;1;1];[1;5;1];[2;2];[2;1;1;1;2];[6;5;3];[12]][[7];[2;2];[2;2];[2;1;1;1;1];[1;2;4;2];[1;1;4;2];[1;1;2;3];[1;1;3;2];[1;1;1;2;2;1];[1;1;5;1;2];[1;1;7;2];[1;6;3];[1;1;3;2];[1;4;3];[1;3;1];[1;2;2];[2;1;1;1;1];[2;2];[2;2];[7]]

Crossword Puzzle

Advanced

Crossword

Given an empty (or almost empty) framework of a crossword puzzle and aset of words. The problem is to place the words into the framework.

The particular crossword puzzle is specified in a text file which firstlists the words (one word per line) in an arbitrary order. Then, afteran empty line, the crossword framework is defined. In this frameworkspecification, an empty character location is represented by a dot (.).In order to make the solution easier, character locations can alsocontain predefined character values. The puzzle above is defined in thefilep7_09a.dat,other examples arep7_09b.datandp7_09d.dat.There is also an example of a puzzle(p7_09c.dat)which does not have a solution.

Words are strings (character lists) of at least two characters. Ahorizontal or vertical sequence of character places in the crosswordpuzzle framework is called a site. Our problem is to find a compatibleway of placing words onto sites.

Hints:

  1. The problem is not easy. You will need some time to thoroughlyunderstand it. So, don't give up too early! And remember that theobjective is a clean solution, not just a quick-and-dirty hack!
  2. For efficiency reasons it is important, at least for larger puzzles,to sort the words and the sites in a particular order.
(* example pending*);;

Never-Ending Sequences

Beginner

Lists are finite, meaning they always contain a finite number of elements. Sequences maybe finite or infinite.

The goal of this exercise is to define a type'a stream which only containsinfinite sequences. Using this type, define the following functions:

valhd:'astream->'a(** Returns the first element of a stream*)valtl:'astream->'astream(** Removes the first element of a stream*)valtake:int->'astream->'alist(** [take n seq] returns the n first values of [seq]*)valunfold:('a->'b*'a)->'a->'bstream(** Similar to Seq.unfold*)valbang:'a->'astream(** [bang x] produces an infinitely repeating sequence of [x] values.*)valints:int->intstream(* Similar to Seq.ints*)valmap:('a->'b)->'astream->'bstream(** Similar to List.map and Seq.map*)valfilter:('a->bool)->'astream->'astream(** Similar to List.filter and Seq.filter*)valiter:('a->unit)->'astream->'b(** Similar to List.iter and Seq.iter*)valto_seq:'astream->'aSeq.t(** Translates an ['a stream] into an ['a Seq.t]*)valof_seq:'aSeq.t->'astream(** Translates an ['a Seq.t] into an ['a stream]    @raise Failure if the input sequence is finite.*)

Tip: Uselet ... = patterns.

type'acons=Consof'a*'astreamand'astream=unit->'aconslethd(seq:'astream)=let(Cons(x,_))=seq()inxlettl(seq:'astream)=let(Cons(_,seq))=seq()inseqletrectakenseq=ifn=0then[]elselet(Cons(x,seq))=seq()inx::take(n-1)seqletrecunfoldfx()=let(y,x)=fxinCons(y,unfoldfx)letbangx=unfold(funx->(x,x))xletintsx=unfold(funx->(x,x+1))xletrecmapfseq()=let(Cons(x,seq))=seq()inCons(fx,mapfseq)letrecfilterpseq()=let(Cons(x,seq))=seq()inletseq=filterpseqinifpxthenCons(x,seq)elseseq()letreciterfseq=let(Cons(x,seq))=seq()infx;iterfseqletto_seqseq=Seq.unfold(funseq->Some(hdseq,tlseq))seqletrecof_seqseq()=matchseq()with|Seq.Nil->failwith"Not a infinite sequence"|Seq.Cons(x,seq)->Cons(x,of_seqseq)

Diagonal of a Sequence of Sequences

Intermediate

Write a functiondiag : 'a Seq.t Seq.t -> 'a Seq that returns thediagonalof a sequence of sequences. The returned sequence is formed as follows:The first element of the returned sequence is the first element of the firstsequence; the second element of the returned sequence is the second element ofthe second sequence; the third element of the returned sequence is the thirdelement of the third sequence; and so on.

letrecdiagseq_seq()=lethds,tls=Seq.filter_mapSeq.unconsseq_seq|>Seq.splitinlethd,tl=Seq.unconshds|>Option.mapfst,Seq.unconstls|>Option.mapsndinletd=Option.fold ~none:Seq.empty ~some:diagtlinOption.fold ~none:Fun.id ~some:Seq.conshdd()


[8]ページ先頭

©2009-2026 Movatter.jp