Как бы вы написали этот фрагмент Clojure на Ruby и / или Haskell?
Я работал над шаблоном Rails и пытался написать немного кода, который позволил бы мне заполнить таблицу или несколько столбцов тегов ul "сверху вниз" и "слева направо" в любом количестве столбцов, которые я указал, Я просто знакомлюсь с Руби, так что я не могу понять это. Мне также интересно узнать идиоматическую версию Haskell для этого полезного фрагмента. Улучшения к версии Clojure приветствуются:
(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)))))
С этим битом кода я могу сделать следующее:
(table (range 10) :cols 3)
Распечатано это будет выглядеть так:
0 1 2
3 4 5
6 7 8
9
И самый хитрый:
(table (range 10) :cols 3 :direction 'down)
Выглядит так:
0 4 8
1 5 9
2 6
3 7
6 ответов
Я, вероятно, написал бы что-то подобное в Хаскеле, используя 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] ]
Некоторые из них, такие как Direction
тип и transpose
трюк, был получен из ответа Jkramer. Я бы не использовал ключевые аргументы для чего-то подобного в Haskell (на самом деле таких вещей нет, но вы можете эмулировать их, используя записи, как в ответе Эдварда Кметта), но я поставил эти аргументы на первое место, потому что это более полезно при частичном применении (defaultTable = table Horizontal 1
). splitEvery
функция просто разбивает список на списки соответствующего размера; остальная часть кода должна быть простой. table
функция возвращает список списков; чтобы получить строку, showTable
Функция вставляет вкладки и новые строки. (The intercalate
Функция объединяет список списков, разделяя их с заданным списком. Это аналог Perl / Python / Ruby's join
, только для списков, а не только строки.)
Я не могу прочитать код clojure (я никогда не использовал язык), но, основываясь на примерах, вот как я это сделаю в 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
Вот что я быстро взломал в Хаскеле. Я уверен, что он глючит и может быть оптимизирован, но это то, с чего нужно начать:
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
Выход:
1 2 3
4 5 6
7 8 9
---
1 4 7
2 5 8
3 6 9
Нарезка и архивирование дают простое решение 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 ' ' }
Мое рубиновое решение
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
Использование и вывод:
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
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
Это дает нам довольно идиоматическое приближение вашего исходного запроса с необязательными аргументами:
*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]
Я оставил промежуточные результаты в виде массива, так как вы указали, что планируете их форматировать как таблицу или теги ul.