fork download
  1. object Main extends App {
  2.  
  3. // -------- auxilliary functions --------
  4. def printLayout(layout: List[List[String]]): Unit =
  5. println((layout ++ List(List())).map(_.mkString).mkString("|\n").init)
  6. def renderCentered(txt: String, width: Int): String = {
  7. val rem = width - txt.size
  8. val (lSpan, rSpan) = (rem / 2, (rem + 1) / 2)
  9. "|" + " " * (lSpan - 1) + txt + " " * rSpan
  10. }
  11. // -------- data model --------
  12. trait Renderable {
  13. def render(width: Int): String
  14. }
  15. case class Product(id: String,
  16. name: String,
  17. price: BigDecimal) extends Renderable {
  18. def render(width: Int): String = renderCentered(s"$id,$name,$price", width)
  19. }
  20. trait Placeholder extends Renderable {
  21. def phType: String
  22. def render(width: Int): String = renderCentered(phType, width)
  23. }
  24. case object NeutralPH extends Placeholder { val phType = "neutral" }
  25. case object ProductInfoPH extends Placeholder { val phType = "product info" }
  26. case object SocialMediaPH extends Placeholder { val phType = "sm" }
  27. case object SellingDriverPH extends Placeholder { val phType = "selling" }
  28. case class LayoutRules(columnPattern: List[Int],
  29. placeholderPlacement: Map[String, PlaceholderRule],
  30. placeholdersDensity: Double = 1.0)
  31. case class PlaceholderRule(maxPerRow: Int,
  32. minSkipRows: Int)
  33.  
  34. // -------- rendering implementation --------
  35. val rand = new scala.util.Random
  36.  
  37. def renderLayout(products: List[Product],
  38. placeholders: List[Placeholder],
  39. rules: LayoutRules,
  40. maxWidth: Int = 60): List[List[String]] = {
  41.  
  42. val itemsPerCycle = rules.columnPattern.sum
  43.  
  44.  
  45. def renderAux(products: List[Product],
  46. groupedPHs: Map[String, List[Placeholder]],
  47. columnPattern: List[Int],
  48. placeholderStats: Map[String, Int],
  49. acc: List[List[String]]): List[List[String]] = {
  50.  
  51. val remainingPlaceholders = groupedPHs.map(_._2.size).sum
  52. val remainingItems = products.size + remainingPlaceholders
  53. val estimatedFullCycles = remainingItems / itemsPerCycle
  54. val lastCycleItems = remainingItems - estimatedFullCycles * itemsPerCycle
  55. val lastCycleRows = if(lastCycleItems == 0) 0 else rules.columnPattern
  56. .scanLeft(0)(_ + _).tail
  57. .dropWhile(_ < lastCycleItems).size + 1
  58. val estimatedRemainingRows = estimatedFullCycles / columnPattern.size + lastCycleRows
  59.  
  60. products match {
  61. case Nil =>
  62. // TODO: if some PHs are available and last row does not match to the column
  63. // TODO: pattern, then we can add remaining PHs
  64. acc
  65. case prods if rand.nextBoolean() => // putting placeholder in this row, if possible
  66. val curRowCols = columnPattern.head
  67. val (prodsRow, prodsRest) = prods.splitAt(curRowCols)
  68. val nextColPattern = columnPattern.tail :+ curRowCols
  69. val curRow = prodsRow.map(_.render(maxWidth / prodsRow.size))
  70. renderAux(prodsRest, groupedPHs, nextColPattern, placeholderStats, curRow :: acc)
  71. case prods => // no placeholder in this row
  72. val curRowCols = columnPattern.head
  73. val (prodsRow, prodsRest) = prods.splitAt(curRowCols)
  74. val nextColPattern = columnPattern.tail :+ curRowCols
  75. val curRow = prodsRow.map(_.render(maxWidth / prodsRow.size))
  76. renderAux(prodsRest, groupedPHs, nextColPattern, placeholderStats, curRow :: acc)
  77. }
  78. }
  79.  
  80. val groupedByType = placeholders.groupBy(_.phType)
  81.  
  82. renderAux(products, groupedByType, rules.columnPattern, Map(), Nil).reverse
  83. }
  84. // -------- example usage --------
  85. val products = (1 to 28).map(i=>Product(s"$i", s"name $i", i * 42)).toList
  86. val placeholders = (
  87. (1 to 3).map(_ => NeutralPH)
  88. ++ (1 to 2).map(_ => ProductInfoPH)
  89. ++ (1 to 4).map(_ => SocialMediaPH)
  90. ++ (1 to 1).map(_ => SellingDriverPH)
  91. ).toList
  92. val rules = LayoutRules(
  93. columnPattern = List(1,2,3),
  94. placeholderPlacement = Map(
  95. NeutralPH.phType -> PlaceholderRule(maxPerRow = 1, minSkipRows = 4),
  96. ProductInfoPH.phType -> PlaceholderRule(maxPerRow = 2, minSkipRows = 3),
  97. SocialMediaPH.phType -> PlaceholderRule(maxPerRow = 1, minSkipRows = 5),
  98. SellingDriverPH.phType -> PlaceholderRule(maxPerRow = 1, minSkipRows = 4)
  99. )
  100. )
  101.  
  102. printLayout(renderLayout(products, placeholders, rules))
  103.  
  104.  
  105.  
  106.  
  107. // your code goes here
  108. }
Success #stdin #stdout 0.46s 382080KB
stdin
Standard input is empty
stdout
|                       1,name 1,42                         |
|        2,name 2,84          |        3,name 3,126         |
|   4,name 4,168    |   5,name 5,210    |   6,name 6,252    |
|                       7,name 7,294                        |
|        8,name 8,336         |        9,name 9,378         |
|  10,name 10,420   |  11,name 11,462   |  12,name 12,504   |
|                      13,name 13,546                       |
|       14,name 14,588        |       15,name 15,630        |
|  16,name 16,672   |  17,name 17,714   |  18,name 18,756   |
|                      19,name 19,798                       |
|       20,name 20,840        |       21,name 21,882        |
|  22,name 22,924   |  23,name 23,966   | 24,name 24,1008   |
|                     25,name 25,1050                       |
|      26,name 26,1092        |      27,name 27,1134        |
|                     28,name 28,1176                       |