2010-05-12 5 views
10

Ich arbeitete an einer Rails-Vorlage und versuchte, ein wenig Code zu schreiben, der es mir erlaubt, eine Tabelle oder mehrere Spalten von UL-Tags "von oben nach unten" und "von links nach rechts" über viele Spalten hinweg zu füllen Ich spezifiziere. Ich bekomme gerade Ruby in den Griff, damit ich das nicht herausfinden kann. Ich bin auch neugierig auf eine idiomatische Haskell-Version für dieses nützliche Snippet. Improvements to Clojure Version geschätzt:Wie würden Sie dieses Clojure-Snippet in Ruby und/oder Haskell schreiben?

(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))))) 

Mit diesem Stück Code kann ich dann folgendes tun:

(table (range 10) :cols 3) 

Gedruckt heraus dies wie so aussehen:

0 1 2 
3 4 5 
6 7 8 
9 

Und die trickier eins:

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

Sieht so aus:

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

Drei Dinge, die Sie vielleicht auf der clojure Version ändern könnte 1 Benutze defnk (clojure.contrib.def) es ist ein wenig nett zu lesen. 2. Schlüsselwörter anstelle von Symbolen 3. Anstatt die Konvertierung in Vektoren zu machen, vereinheitlichen Sie sie mit (vec (map vec (condp .....))) – nickik

Antwort

4

würde ich wahrscheinlich so etwas wie dies in Haskell schreiben, mit dem Data.List.Split Paket von 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] ] 

Einen Teil davon, wie der Direction Typ und den transpose Trick wurde von jkramer Antwort abgeleitet. Ich würde in Haskell keine Schlüsselwortargumente für so etwas verwenden (es gibt nicht wirklich solche Dinge, aber Sie können sie emulieren, indem Sie Datensätze wie in Edward Kmetts Antwort verwenden), aber ich lege diese Argumente an erster Stelle, weil es bei partieller Anwendung nützlicher ist (defaultTable = table Horizontal 1). Die splitEvery Funktion teilt nur eine Liste in Listen der entsprechenden Größe auf; Der Rest des Codes sollte einfach sein. Die table Funktion gibt eine Liste von Listen zurück; Um eine Zeichenfolge zu erhalten, fügt die Funktion showTable Tabs und Zeilenumbrüche ein. (Die intercalate Funktion verkettet eine Liste von Listen, die sie mit der angegebenen Liste trennt.Es ist analog zu Perl/Python/Ruby join, nur für Listen statt nur Strings)

2

Hier ist etwas, das ich schnell in Haskell gehackt habe. Ich bin sicher, dass es Buggy ist und optimiert werden kann, aber es ist etwas zu beginnen mit:

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 

Ausgang:

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

Dies gibt nicht die richtige Antwort für Nicht-Quadrat Anzahl der vertikal verlaufenden Spalten; anstatt etwas zu drucken, das '_' von 'ncol' ist, druckt es etwas, das' ncol' durch '_' ist (' _' zeigt an "welche Zahl auch immer notwendig ist"). Generell denke ich, dass es verschärft werden könnte; Sieh meine Antwort. –

+0

Ahh, ich suchte nach etwas wie splitEvery, aber habe es nicht gefunden. – jkramer

4

Ich kann den clojure Code gelesen (ich habe noch nie verwendet die Sprache), aber basierend auf den Beispielen, hier ist, wie ich es in Ruby machen würde.

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

Schöne Lösung. Es ist cool, Variationen über ein praktisches Problem in Ruby und Haskell zu sehen. – dnolen

2

Meine Rubin Lösung

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 

Verbrauch und Ausgang:

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 

Dies gibt uns eine ziemlich idiomatische Annäherung Ihrer ursprünglichen Abfrage komplett mit optionalen Argumenten:

*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] 

Ich habe die Zwischenergebnisse in Array-Form, da Sie angegeben haben, dass Sie sie als eine Tabelle oder UL-Tags formatiert haben.

2

Slicing und Zuziehen eine einfache Ruby-Lösung gibt.

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 ' ' }