Combinaciones de tamaño K de una lista en Ocaml -- recursion campo con functional-programming campo con ocaml camp codereview Relacionados El problema

Combinations of size k from a list in OCaml


8
vote

problema

Español

Quería escribir una función pequeña pero no trivial en OcamL para probar mi comprensión. Solo tengo una comprensión básica de las bibliotecas, y ninguna idea de todo lo que se considera un buen estilo. La función calcula todas las combinaciones de tamaño k de una lista, donde 9988776655544335 .

¿Alguien le importaría comentar

  1. Si la función está generalmente escrita (he cubierto todos los casos, ¿hay oportunidades para la recursión de la cola que he perdido?)

  2. Han hecho un buen uso de las bibliotecas (por ejemplo, definido is_empty y tails porque no pude encontrarlos en el List Módulo, pero tal vez están en otro lugar?)

  3. es el estilo de acuerdo, en particular el uso de let declaraciones e indentación?

El código es:

   !function(){  $("#nav ul.level0 ul.level1").each(function(){   var menuLength = $(this).find("li.level2").length;   if (menuLength > 10){    var detachedElements = $(this).find("li.level2").slice(10);    detachedElements.detach();    $(this).parent().append("<ul class='level1 second-menu'></ul>");    detachedElements.appendTo($(this).siblings(".second-menu"));   }   $(this).siblings("a").hover(    function(){     $(this).siblings(".second-menu").addClass("shown-sub");    },     function(){     $(this).siblings(".second-menu").removeClass("shown-sub");    }   );   $(this).children(".level2").hover(    function(){     $(this).parent().siblings(".second-menu").addClass("shown-sub");    },     function(){     $(this).parent().siblings(".second-menu").removeClass("shown-sub");    }   );   $(this).siblings(".second-menu").children(".level2").hover(    function(){     $(this).parent().addClass("shown-sub");    },     function(){     $(this).parent().removeClass("shown-sub");    }   );  })  }();0  

Basado en los excelentes comentarios de Amon (ver más abajo) He escrito a lo que creo que es la versión más legible de esta función, que es la que inicia la definición de !function(){ $("#nav ul.level0 ul.level1").each(function(){ var menuLength = $(this).find("li.level2").length; if (menuLength > 10){ var detachedElements = $(this).find("li.level2").slice(10); detachedElements.detach(); $(this).parent().append("<ul class='level1 second-menu'></ul>"); detachedElements.appendTo($(this).siblings(".second-menu")); } $(this).siblings("a").hover( function(){ $(this).siblings(".second-menu").addClass("shown-sub"); }, function(){ $(this).siblings(".second-menu").removeClass("shown-sub"); } ); $(this).children(".level2").hover( function(){ $(this).parent().siblings(".second-menu").addClass("shown-sub"); }, function(){ $(this).parent().siblings(".second-menu").removeClass("shown-sub"); } ); $(this).siblings(".second-menu").children(".level2").hover( function(){ $(this).parent().addClass("shown-sub"); }, function(){ $(this).parent().removeClass("shown-sub"); } ); }) }();1 y se deshace de !function(){ $("#nav ul.level0 ul.level1").each(function(){ var menuLength = $(this).find("li.level2").length; if (menuLength > 10){ var detachedElements = $(this).find("li.level2").slice(10); detachedElements.detach(); $(this).parent().append("<ul class='level1 second-menu'></ul>"); detachedElements.appendTo($(this).siblings(".second-menu")); } $(this).siblings("a").hover( function(){ $(this).siblings(".second-menu").addClass("shown-sub"); }, function(){ $(this).siblings(".second-menu").removeClass("shown-sub"); } ); $(this).children(".level2").hover( function(){ $(this).parent().siblings(".second-menu").addClass("shown-sub"); }, function(){ $(this).parent().siblings(".second-menu").removeClass("shown-sub"); } ); $(this).siblings(".second-menu").children(".level2").hover( function(){ $(this).parent().addClass("shown-sub"); }, function(){ $(this).parent().removeClass("shown-sub"); } ); }) }();2 Completamente, pero no va hasta la eliminación del uso de !function(){ $("#nav ul.level0 ul.level1").each(function(){ var menuLength = $(this).find("li.level2").length; if (menuLength > 10){ var detachedElements = $(this).find("li.level2").slice(10); detachedElements.detach(); $(this).parent().append("<ul class='level1 second-menu'></ul>"); detachedElements.appendTo($(this).siblings(".second-menu")); } $(this).siblings("a").hover( function(){ $(this).siblings(".second-menu").addClass("shown-sub"); }, function(){ $(this).siblings(".second-menu").removeClass("shown-sub"); } ); $(this).children(".level2").hover( function(){ $(this).parent().siblings(".second-menu").addClass("shown-sub"); }, function(){ $(this).parent().siblings(".second-menu").removeClass("shown-sub"); } ); $(this).siblings(".second-menu").children(".level2").hover( function(){ $(this).parent().addClass("shown-sub"); }, function(){ $(this).parent().removeClass("shown-sub"); } ); }) }();3 y 99887766555443314 , porque creo en el uso de funciones de la biblioteca para Simplifica el código siempre que sea posible.

En particular, las pautas de diseño hacen que la estructura de la función sea mucho más clara, y creo que en esta versión es obvio lo que se está utilizando el algoritmo, mientras que está un tanto ofuscado en el original. Gracias, amos!

   !function(){  $("#nav ul.level0 ul.level1").each(function(){   var menuLength = $(this).find("li.level2").length;   if (menuLength > 10){    var detachedElements = $(this).find("li.level2").slice(10);    detachedElements.detach();    $(this).parent().append("<ul class='level1 second-menu'></ul>");    detachedElements.appendTo($(this).siblings(".second-menu"));   }   $(this).siblings("a").hover(    function(){     $(this).siblings(".second-menu").addClass("shown-sub");    },     function(){     $(this).siblings(".second-menu").removeClass("shown-sub");    }   );   $(this).children(".level2").hover(    function(){     $(this).parent().siblings(".second-menu").addClass("shown-sub");    },     function(){     $(this).parent().siblings(".second-menu").removeClass("shown-sub");    }   );   $(this).siblings(".second-menu").children(".level2").hover(    function(){     $(this).parent().addClass("shown-sub");    },     function(){     $(this).parent().removeClass("shown-sub");    }   );  })  }();5  
Original en ingles

I wanted to write a small but non-trivial function in OCaml to test my understanding. I have only a basic understanding of the libraries, and no idea at all of what is considered good style. The function computes all combinations of size k from a list, where 0 <= k <= length lst.

Would someone mind commenting on

  1. Whether the function is generally well written (have I covered all cases, are there opportunities for tail recursion that I've missed?)

  2. Have I made good use of libraries (e.g. I defined is_empty and tails because I couldn't find them in the List module, but maybe they are somewhere else?)

  3. Is the style okay, particularly the use of let statements and indentation?

The code is:

let rec tails = function   | []          -> []   | _ :: t as l -> l :: tails t  let is_empty = function   | [] -> true   | _  -> false  let rec combnk k lst =   if k = 0 then [[]]   else let f = function     | []      -> [] (* I think this is unnecessary, but I get a pattern match warning o/w *)     | x :: xs -> List.map (fun z -> x :: z) (combnk (k-1) xs)   in if is_empty lst then []      else List.concat (List.map f (tails lst)) 

Based on the excellent comments by amon (see below) I have written to what I think is the most readable version of this function, which is the one that inlines the definition of tails and gets rid of is_empty completely, but doesn't go all the way to removing the use of List.concat and List.map, because I believe in using library functions to simplify the code wherever possible.

In particular, the layout guidelines make the structure of the function much clearer, and I think that in this version it is obvious what algorithm is being used, whereas it is somewhat obfuscated in the original. Thanks, amos!

let rec combnk k lst =   if k = 0 then     [[]]   else     let rec inner = function       | []      -> []       | x :: xs -> List.map (fun z -> x :: z) (combnk (k - 1) xs) :: inner xs in     List.concat (inner lst) 
        
 
 

Lista de respuestas

4
 
vote
vote
La mejor respuesta
 

Su tails es muy hermoso, su is_empty1 inútil, y combnk2 un lío.

En combnk , su sangría ofusca la estructura real del código. Aquí hay una mejor sangría:

  let rec combnk k lst =   if k = 0 then     [[]]   else     let f = function       | []      -> []       | x :: xs -> List.map (fun z -> x :: z) (combnk (k - 1) xs)     in       if is_empty lst then         []       else         List.concat (List.map f (tails lst))   

Ahora hay algunas observaciones interesantes que se deben hacer aquí: la sucursal if is_empty lst then [] no accede a f , por lo que podríamos mover esta prueba fuera del let < / Código>:

  let rec combnk k lst =   if k = 0 then     [[]]   else if is_empty lst then     []   else     let f = function       | []      -> []       | x :: xs -> List.map (fun z -> x :: z) (combnk (k - 1) xs)     in       List.concat (List.map f (tails lst))   

¿Pero es la prueba combnk9 en realidad es necesario? is_empty0 Produce una lista vacía, is_empty1 Produce una lista vacía para cualquier función is_empty2 , y is_empty3 también produce un vacío lista. Ahora tenemos:

  is_empty4  

¿Cómo se puede mejorar esto? Podemos mover el is_empty5 DEFINICIONES EN EL INTERIOR ELSE-RAMPE is_empty6 para que esté restringido al único alcance donde se usa:

  is_empty7  

Con respecto a la pregunta, ya sea necesario el caso is_empty8 is_empty9 , excepto para el sistema de tipo: la respuesta es no , como la lista producida por combnk0 No puede contener otra lista vacía â € " 99887766555443321 es 99887776655443322 de nuevo. Esto cambiaría cuando cambia combnk3 en combnk4 para combnk5 , que sería posiblemente más correcto.

Ahora que combnk6 y combnk7 están muy juntos Puede notar algunas similitudes. De hecho, podemos combinar los dos directamente, deshacerse de un 99887766655443328 :

  combnk9  

Por supuesto, combnk0 podría ser recursivo parcialmente de la cola (pero esto invierte el orden de las combinaciones):

  combnk1  

Todavía hay una recursión indirecta a través de combnk2 , más obvio si le reescribimos así:

  combnk3  

Ahora todo lo que queda por hacer es escribir un combnk4 que realiza un acumulador externo, por lo tanto, eliminando la necesidad de combnk5 o combnk36 :

  combnk7  
 

Your tails is very beatiful, your is_empty useless, and combnk a mess.

In combnk, your indentation obfuscates the actual structure of the code. Here is a better indentation:

let rec combnk k lst =   if k = 0 then     [[]]   else     let f = function       | []      -> []       | x :: xs -> List.map (fun z -> x :: z) (combnk (k - 1) xs)     in       if is_empty lst then         []       else         List.concat (List.map f (tails lst)) 

Now there are some interesting observations to be made here: The branch if is_empty lst then [] does not access f, so we could move this test outside of the let:

let rec combnk k lst =   if k = 0 then     [[]]   else if is_empty lst then     []   else     let f = function       | []      -> []       | x :: xs -> List.map (fun z -> x :: z) (combnk (k - 1) xs)     in       List.concat (List.map f (tails lst)) 

But is the is_empty test actually necessary? tails [] produces an empty list, List.map f [] produces an empty list for any function f, and List.concat [] also produces an empty list. We now have:

let rec combnk k lst =   if k = 0 then     [[]]   else     let f = function       | []      -> []       | x :: xs -> List.map (fun z -> x :: z) (combnk (k - 1) xs)     in       List.concat (List.map f (tails lst)) 

How can this be improved? We can move the tails definitions inside the else-branch let so that it's restricted to the only scope where it is used:

let rec combnk k lst =   if k = 0 then     [[]]   else     let rec tails = function       | []          -> []       | _ :: t as l -> l :: tails t     and f = function       | []      -> []       | x :: xs -> List.map (fun z -> x :: z) (combnk (k - 1) xs)     in       List.concat (List.map f (tails lst)) 

Regarding the question whether the case [] -> [] in f is necessary except for the type system: The answer is no, as the list produced by tails cannot contain another empty list xe2x80x93 l :: [] is l again. This would change when you swap [] -> [] in tails for [] -> [[]], which would be arguably more correct.

Now that f and tails are so close together you may notice some similarities. Indeed, we can combine the two directly, thus getting rid of one map:

let rec combnk k lst =   if k = 0 then     [[]]   else     let rec inner = function       | []      -> []       | x :: xs -> (List.map (fun z -> x :: z) (combnk (k - 1) xs)) :: inner xs     in       List.concat (inner lst) 

Of course, inner could be made partially tail recursive (but this reverses the order of combinations):

let rec combnk k lst =   if k = 0 then     [[]]   else     let rec inner acc = function       | []      -> acc       | x :: xs ->         let this_length = List.map (fun z -> x :: z) (combnk (k - 1) xs)         in           inner (this_length :: acc) xs     in       List.concat (inner [] lst) 

There is still an indirect recursion through combnk, more obvious if we rewrite it like this:

let rec combnk k lst =   let rec inner acc k lst =     match k with     | 0 -> [[]]     | _ ->       match lst with       | []      -> List.flatten acc       | x :: xs ->         let this_length = List.map (fun z -> x :: z) (inner [] (k - 1) xs)         in           inner (this_length :: acc) k xs   in     inner [] k lst 

Now all that is left to do is to write a map that takes an external accumulator, thus also removing the need for flatten or concat:

let rec combnk k lst =   let rec inner acc k lst =     match k with     | 0 -> [[]]     | _ ->       match lst with       | []      -> acc       | x :: xs ->         let rec accmap acc f = function           | []      -> acc           | x :: xs -> accmap ((f x) :: acc) f xs         in           let newacc = accmap acc (fun z -> x :: z) (inner [] (k - 1) xs)           in             inner newacc k xs     in       inner [] k lst 
 
 
 
 

Relacionados problema

4  Eliminar un nodo del árbol binario en Ocaml  ( Deleting a node from binary tree in ocaml ) 
Actualmente estoy explorando OCaml y escribió la siguiente implementación de eliminar un nodo de un árbol binario let rec deleteNode tree' value = mat...

2  Evaluación de la EMPRESIÓN DE INFIX  ( Infix epression evaluation ) 
Estoy buscando algunos comentarios sobre mi implementación de OCAML de algunos métodos para traducir las expresiones de infijo a Postfix y luego evaluarlas. ...

2  Intercalle dos listas, preservando el orden de los elementos en su lista original  ( Interleave two lists preserving the order of elements in their original list ) 
Necesito escribir un programa OCAML que devuelva toda la posibilidad posible de dos listas, mientras conserva el orden del elemento en cada lista. Por ejemplo...

2  Imprimir el domingo pasado de cada mes en un año determinado  ( Print last sunday of each month in a given year ) 
Soy un programador experimentado tratando de aprender OCAML. Pensé que una buena manera de hacerlo sería elegir una tarea simple en el código de Rosetta que a...

18  Módulo de cálculo de tiempo de espera  ( Timeoutable computations module ) 
Define un módulo simple para cálculos de tiempo de espera, con la capacidad de devolver Los resultados intermedios arbitrarios en el tiempo de espera o el val...

2  Árbol de programación funcional de OCAML  ( Ocaml functional programming tree ) 
Estoy escribiendo una función llamada UNZIP que se vuelve: [(1,2);(3,4);(5,6)] a ([1;3;5],[2;4;6]) Tengo una implementación de trabajo aquí: ...

2  L-Systems en Ocaml  ( L systems in ocaml ) 
para referencia: l-system Estoy buscando en su mayoría cosas de estilo general aquí y en particular: Si mis parámetros de tipo son demasiado locos, que...

2  Detectar XOR de un solo carácter (OCAML)  ( Detect single character xor ocaml ) 
He estado trabajando a través de la criptopals desafíos en Ocaml. Mi interés en OCAML es aprender mejor las técnicas funcionales de la programación y el uso...

3  ¿Es esta búsqueda binaria cerca de Idiomatic OCAML?  ( Is this binary search close to idiomatic ocaml ) 
let binsearch ~arr ~cmp x = ( let cmpx = cmp x in (* Assuming arr is ordered according to cmp, then (bs min max) is an *) (* index of a value v...

5  Cuerda trie en ocaml  ( String trie in ocaml ) 
Soy bastante nuevo en Ocaml, por lo que se aprecia cualquier retroalimentación. El objetivo es implementar un Trie que almacena y clasifica de manera eficient...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos