2011-01-13 24 views
7

Estoy buscando una manera de crear una secuencia que consta de cada enésimo elemento de otra secuencia, pero no parece encontrar una manera de hacerlo de una manera elegante. Por supuesto, puedo hackear algo, pero me pregunto si hay una función de biblioteca que no estoy viendo.Obteniendo cada enésimo elemento de una secuencia

Las funciones de secuencia cuyos nombres terminan en -i parecen ser bastante buenas para determinar cuándo un elemento es el enésimo o (múltiplo de n) uno, pero solo puedo ver iteri y mapi, ninguno de lo cual realmente se presta a la tarea.

Ejemplo:

let someseq = [1;2;3;4;5;6] 
let partial = Seq.magicfunction 3 someseq 

Entonces partial debería ser [3;6]. ¿Hay algo así por ahí?

Editar:

Si no soy tan ambicioso y permitir la n a ser constante/conocido, entonces me acaba de encontrar que el siguiente debería funcionar:

let rec thirds lst = 
    match lst with 
    | _::_::x::t -> x::thirds t // corrected after Tomas' comment 
    | _ -> [] 

Would ¿Hay alguna forma de escribir esto más corto?

+3

Puede usar 'mapi' para convertir cada elemento de la lista en' Some' o 'None',' filtrar' fuera '' '' '' '' '' '' '' '', y luego 'map' de nuevo al tipo no decorado. –

+0

Su solución usando listas se ve bien (pero es probable que desee escribir '_ :: _ :: x :: t' (en lugar de' (_, _, x) :: t' que usa una lista de tuplas). La diferencia es que 'Seq' funcionará con otras colecciones que listas, pero puede que no sea un problema para ti. Tu versión con listas es un buen código funcional. –

+0

Sí, por supuesto, debe ser' _ :: _ :: x :: t', debería haberle preguntado al compilador antes de pegarlo aquí. –

Respuesta

8

se puede obtener el comportamiento mediante la composición de mapi con otras funciones:

let everyNth n seq = 
    seq |> Seq.mapi (fun i el -> el, i)    // Add index to element 
     |> Seq.filter (fun (el, i) -> i % n = n - 1) // Take every nth element 
     |> Seq.map fst        // Drop index from the result 

la solución usando opciones y choose según lo sugerido por Annon utilizaría sólo dos funciones, pero el cuerpo de la primera sería un poco más complicado (pero el principio es esencialmente el mismo).

Una versión más eficiente utilizando directamente el objeto IEnumerator no es demasiado difícil escribir:

let everyNth n (input:seq<_>) = 
    seq { use en = input.GetEnumerator() 
     // Call MoveNext at most 'n' times (or return false earlier) 
     let rec nextN n = 
      if n = 0 then true 
      else en.MoveNext() && (nextN (n - 1)) 
     // While we can move n elements forward... 
     while nextN n do 
      // Retrun each nth element 
      yield en.Current } 

EDIT: El fragmento también está disponible aquí: http://fssnip.net/1R

+0

No sé quién fue más rápido, usted o 'Anon.', pero ambas son la misma sugerencia, ¿no? No parece terriblemente eficiente, ¿o sí? –

+0

No es terriblemente eficiente (hay algunas llamadas a funciones adicionales e indirecciones, porque usa 3 iteradores debajo de la cubierta), pero puede que no sea tan malo (no hay listas intermedias que tendrían que asignarse). Para una versión más eficiente, necesitarás mutación (en la expresión de secuencia) o utilizar el subyacente 'IEnumerator' –

9

Seq.choose funciona muy bien en estas situaciones porque le permite hacer el trabajo filter dentro del mapi lambda.

let everyNth n elements = 
    elements 
    |> Seq.mapi (fun i e -> if i % n = n - 1 then Some(e) else None) 
    |> Seq.choose id 

Similar a here.

+0

¡Agradable y elegante! –

+0

Sí, ¡me gusta también! :) –

Cuestiones relacionadas