2010-05-12 11 views
10

Estaba trabajando en una plantilla de Rails y estaba tratando de escribir un código que me permite completar una tabla o varias columnas de etiquetas ul "de arriba a abajo" y "de izquierda a derecha" en todas las columnas Lo especifico Solo estoy entendiendo a Ruby, así que no pude resolver esto. También tengo curiosidad acerca de una versión idiomática de Haskell para este fragmento útil. Mejoras en la versión Clojure apreciadas:¿Cómo escribirías este fragmento de Clojure en Ruby y/o Haskell?

(defn table [xs & {:keys [cols direction] 
        :or {cols 1 direction 'right}}] 
    (into [] 
     (condp = direction 
      'down (let [c (count xs) 
         q (int (/ c cols)) 
         n (if (> (mod c q) 0) (inc q) q)] 
        (apply map vector (partition n n (repeat nil) xs))) 
      'right (map vec (partition cols cols (repeat nil) xs))))) 

Con este fragmento de código que se puede hacer lo siguiente:

(table (range 10) :cols 3) 

imprime este se vería así:

0 1 2 
3 4 5 
6 7 8 
9 

Y el más complicado uno:

(table (range 10) :cols 3 :direction 'down) 

Parece que sí:

0 4 8  
1 5 9  
2 6   
3 7   
+0

tres cosas que tal vez podría cambiar en la versión clojure 1 . Utilice defnk (clojure.contrib.def) es un poco agradable de leer. 2. palabras clave en lugar de símbolos 3. en lugar de hacer la conversión a vectores de dos maneras unificarlo con (vec (map vec (condp .....))) – nickik

Respuesta

4

que probablemente escribir algo como esto en Haskell, utilizando el paquete de Data.List.Split Hackage:

import Data.List  (intercalate, transpose) 
import Data.List.Split (splitEvery) 

data Direction = Horizontal | Vertical deriving (Eq, Read, Show) 

table :: Direction -> Int -> [a] -> [[a]] 
table Horizontal cols xs = splitEvery cols xs 
table Vertical cols xs = let (q,r) = length xs `divMod` cols 
           q' = if r == 0 then q else q+1 
          in transpose $ table Horizontal q' xs 

showTable :: Show a => [[a]] -> String 
showTable = intercalate "\n" . map (intercalate "\t" . map show) 

main :: IO() 
main = mapM_ putStrLn [ showTable $ table Horizontal 3 [0..9] 
         , "---" 
         , showTable $ table Vertical 3 [0..9] ] 

Algo de esto, al igual que el tipo Direction y la transpose truco, se deriva de la respuesta de jkramer. No usaría argumentos de palabras clave para algo como esto en Haskell (en realidad no tiene tales cosas, pero puede emularlas usando registros como en la respuesta de Edward Kmett), pero puse esos argumentos primero porque es más útil con la aplicación parcial (defaultTable = table Horizontal 1). La función splitEvery simplemente corta una lista en listas del tamaño apropiado; el resto del código debería ser sencillo. La función table devuelve una lista de listas; para obtener una cadena, la función showTable inserta pestañas y nuevas líneas. (La función intercalate concatena una lista de listas, separándolas con la lista dada.Es análogo a Perl/Python/Ruby join, sólo para las listas en lugar de sólo cuerdas)

2

He aquí algo que pirateé rápidamente en Haskell. Estoy seguro de que es defectuoso y se puede optimizar, pero es algo para empezar:

import System.IO 
import Data.List 

data Direction = Horizontal | Vertical 

main = do 
    putStrLn $ table [1..9] 3 Horizontal 
    putStrLn "---" 
    putStrLn $ table [1..9] 3 Vertical 


table xs ncol direction = 
    case direction of 
     Horizontal -> format (rows strings ncol) 
     Vertical -> format (columns strings ncol) 
    where 
     format = intercalate "\n" . map (intercalate " ") 

     strings = map show xs 

     rows xs ncol = 
      if length xs > ncol 
       then take ncol xs : rows (drop ncol xs) ncol 
       else [xs] 

     columns xs = transpose . rows xs 

Salida:

1 2 3 
4 5 6 
7 8 9 
--- 
1 4 7 
2 5 8 
3 6 9 
+0

Esto no da la respuesta correcta para non-square sin embargo, el número de columnas va en sentido vertical; en lugar de imprimir algo que es '_' por' ncol', imprime algo que es 'ncol' por' _' ('_' indica" el número que sea necesario "). En general, creo que podría ser más estricto; ver mi respuesta –

+0

Ahh, estaba buscando algo así como splitEvery, pero no lo encontré. – jkramer

4

no puedo leer el código clojure (nunca he utilizado el lenguaje), pero basado en los ejemplos, así es como lo haría en Ruby.

def table array, cols, direction 
    if direction==:down 
     if array.size%cols != 0 
     array[(array.size/cols+1)*cols-1]=nil 
     #putting nil in the last space in the array 
     #also fills all of the spaces before it 
     end 
     newarray=array.each_slice(array.size/cols).to_a 
     table newarray.transpose.flatten(1), cols, :across 
    elsif direction==:across 
     array.each_slice(cols) do |row| 
     puts row.join(" ") 
     end 
    else 
     raise ArgumentError 
    end 
end 
+0

Buena solución. Es genial ver variaciones en un problema práctico en Ruby y Haskell. – dnolen

2

Mi solución rubí

def table(values) 
    elements = values[:elements] 
    cols = values[:cols] 
    rows = (elements.count/cols.to_f).ceil 

    erg = [] 

    rows.times do |i| 
    cols.times do |j| 
     erg << elements[values[:direction] == 'down' ? i+(rows*j) : j+i*(rows-1)] 
     if erg.length == cols 
     yield erg 
     erg = [] 
     end   
    end 
    end 
    yield erg 
end 

Uso y salida:

table(:elements => [0,1,2,3,4,5,6,7,8,9], :cols => 3) do |h,i,j| 
    puts h.to_s << " " << i.to_s << " " << j.to_s 
end 

puts "---" 

table(:elements => [0,1,2,3,4,5,6,7,8,9], :cols => 3, :direction => "down") do |h,i,j| 
    puts h.to_s << " " << i.to_s << " " << j.to_s 
end 

0 1 2 
3 4 5 
6 7 8 
9 
--- 
0 4 8 
1 5 9 
2 6 
3 7 
1
import Data.Array 

stride :: Int -> Int -> Int 
stride count cols = ceiling (fromIntegral count/fromIntegral cols) 

type Direction = Int -> Int -> Int -> Int -> Int 

right :: Direction 
right count cols x y = y * cols + x 

down :: Direction 
down count cols x y = x * stride count cols + y 

data Options = Options { cols :: Int, direction :: Direction } 

options :: Options 
options = Options 1 right 

table :: Options -> [a] -> Array (Int,Int) (Maybe a) 
table (Options cols dir) xs 
    = listArray newRange (map f (range newRange)) 
    where count = length xs 
      rows = stride count cols 
      newRange = ((0,0),(rows-1,cols-1)) 
      f (y, x) 
       | ix < count = Just (xs !! ix) 
       | otherwise = Nothing 
       where ix = dir count cols x y 

Esto nos da una aproximación bastante idiomática de su búsqueda original completo con argumentos opcionales:

*Main> table options { cols = 3 } [1..10] 
listArray ((0,0),(3,2)) [Just 1, Just 2, Just 3 
         ,Just 4, Just 5, Just 6 
         ,Just 7, Just 8, Just 9 
         ,Just 10,Nothing,Nothing] 

*Main> table options { direction = down, cols = 3 } [1..10] 
listArray ((0,0),(3,2)) [Just 1,Just 5,Just 9 
         ,Just 2,Just 6,Just 10 
         ,Just 3,Just 7,Nothing 
         ,Just 4,Just 8,Nothing] 

Dejé los resultados intermedios en forma de matriz, ya que usted indicó que los planificó formateándolos como una tabla o etiquetas ul.

2

rebanar y comprimir da una solución sencilla Ruby:.

def table(range, cols, direction=:right) 
    if direction == :right 
    range.each_slice cols 
    else 
    columns = range.each_slice((range.to_a.length - 1)/cols + 1).to_a 
    columns[0].zip *columns[1..-1] 
    end 
end 


puts table(0..9, 3, :down).map { |line| line.join ' ' } 
Cuestiones relacionadas