将数组排序为“直方图”

时间:2018-11-24 13:50:51

标签: scala

我是一名相当新的软件工程专业的学生,​​所以我的知识有限。 我已经获得了对数组进行排序的任务,在这种情况下,这是一组彩票号码,可以在控制台中提供类似输出的图形,我可以使用一大堆match语句来做到这一点,但是感觉必须有更好的方法

这是给出的确切摘要:


假设下面的声明定义了在指定时期内抽出每个国家彩票球(1 ..49)的次数。

  var lottery = Array(23,16,18,19,26,13,22,    /*  1 ..  7 */
                20,14,22,18,21,15,17,    /*  8 .. 14 */
                24,15,18,20,13,14,20,    /* 15 .. 21 */
                18,22,20,16,19,11,20,    /* 22 .. 28 */
                16,28,22,20,15,17,17,    /* 29 .. 35 */
                21,21,19,20,14,22,25,    /* 36 .. 42 */
                19,17,26,18,20,23,12);   /* 43 .. 49 */

编写程序来打印直方图,从而使用星形图形显示信息,如下所示:

1(23)| **********************
2(16)| ************

以此类推。


关于如何完成此操作的任何提示/建议将不胜感激,因为到目前为止,它使我感到困惑。我并不是在寻求确切的解决方案,而只是寻求一些指导,说明我可以使用哪些方法来实现这些目标,因为我可能没有碰到它们。 谢谢,祝你有美好的一天。


编辑-我完成任务的第一种方式。

object lottery {
  def main(args: Array[String]): Unit = {

    var lotteryIndex = 1

    var lottery = Array(23,16,18,19,26,13,22,    /*  1 ..  7 */
                    20,14,22,18,21,15,17,    /*  8 .. 14 */
                    24,15,18,20,13,14,20,    /* 15 .. 21 */
                    18,22,20,16,19,11,20,    /* 22 .. 28 */
                    16,28,22,20,15,17,17,    /* 29 .. 35 */
                    21,21,19,20,14,22,25,    /* 36 .. 42 */
                    19,17,26,18,20,23,12);   /* 43 .. 49 */

    scala.util.Sorting.quickSort(lottery)

    var i = 1

    while(i < lottery.length){
      if(lottery(i) != lottery(i-1)){
        print("\n" + lotteryIndex + " (" + lottery(i) + ")  |  " + "*")
        i += 1
        lotteryIndex += 1
      }else{
        print("*")
        i += 1
      }
    }
  }
}

1 个答案:

答案 0 :(得分:3)

这很简单。数组中的每个值都是索引的频率计数,因此您需要能够创建一个星号('*')字符串,其长度与频率计数相同。这可以通过多种方法来实现,但是也许最简单的方法是定义一个函数,如下所示:

def ast(fc: Int) = "*" * fc

其中fc是频率计数和所需的星号数。因此,例如,如果fc为5,则此表达式的结果将为"*****"

接下来,我们需要一个函数来创建输出行,给定一个球号(bn)和相应的频率计数(fc):

def line(bn: Int, fc: Int) = f"$bn%2d ($fc%2d) | ${ast(fc)}%s"

让我们解释一下这里发生了什么。字符串的f前缀告诉 Scala 它包含字符串格式信息,必须进行插值。 (有关更多信息,请参阅this explanation。)

无论何时遇到$,以下内容都将被视为需要在输出中转换为字符串的表达式。如果表达式不是简单的表达式,例如变量或值名称(例如,如果是函数调用),则表达式必须用大括号括起来(例如${...})。如果%跟随表达式,则它以类似于C language std::printf function的方式标识表达式的格式。

因此:$bn%2d将整数值bn转换为两位十进制字符串; $fc%2d将整数值fc转换为两位十进制字符串;并且${ast(fc)}%s调用函数ast,并传递fc作为参数,然后将结果字符串插入输出。 (在后一种情况下,%s是多余的。)

(注意:您对原始问题的评论之一指定了%2s的格式。虽然此格式将输出正确地格式化为两个字符,但它不会检查参数类型是否为整数,因此是,不推荐。)

现在,我们需要遍历数组以为数组的每个成员调用后一个函数...

首先,请注意,我们需要索引和值(球数是数组索引+ 1,因为数组是从0开始的)。第一步是通过lottery方法将Array[Int]Array[(Int, Int)]转换成zipWithIndex。这将获取lottery数组中的每个索引值对,并将它们组合为一个 tuple (值先出现,然后是索引),然后将结果放入另一个数组。

然后我们可以将结果中的每个元组值map移至输出行,如下所示:

val lines = lottery.zipWithIndex.map {
  case (fc, idx) => line(idx + 1, fc)
}

以上是使用部分函数断开元组的一种方法。如果您不介意使用元组访问成员,也可以使用以下方法更简洁(但也更加混淆):

val lines = lottery.zipWithIndex.map(p => line(p._2 + 1, p._1))

其中p是数组索引和频率计数的元组。选择您喜欢的方法。

最后,我们可以迭代结果以打印出结果的每一行:

lines.foreach(println)

后者是以下内容的简写:

lines.foreach(l => println(l))

因此,将所有这些放在一起,我们得到以下结果:

object Lottery
extends App {

  // Lottery data
  val lottery = Array(23,16,18,19,26,13,22,    /*  1 ..  7 */
                      20,14,22,18,21,15,17,    /*  8 .. 14 */
                      24,15,18,20,13,14,20,    /* 15 .. 21 */
                      18,22,20,16,19,11,20,    /* 22 .. 28 */
                      16,28,22,20,15,17,17,    /* 29 .. 35 */
                      21,21,19,20,14,22,25,    /* 36 .. 42 */
                      19,17,26,18,20,23,12)    /* 43 .. 49 */

  // Get a string of asterisks of the required length.
  def ast(fc: Int) = "*" * fc

  // Get a line of the histogram from a ball number and frequency count.
  def line(bn: Int, fc: Int) = f"$bn%2d ($fc%2d) | ${ast(fc)}%s"

  // Get each line of output for the histogram.
  val lines = lottery.zipWithIndex.map {
    case (fc, idx) => line(idx + 1, fc)
  }

  // Print each line of the histogram
  lines.foreach(println)
}

请注意,我们尚未对直方图进行排序(目前的代码与您摘要中指定的输出匹配)。如果您需要按频率计数的降序列出球,只需使用频率计数对压缩数组进行排序即可:

val lines = lottery.zipWithIndex.sortBy(-_._1).map {
  case (fc, idx) => line(idx + 1, fc)
}

sortBy将一个函数用作排序值。在这里,我们将频率计数取为负,以反转排序的顺序(如果您希望以频率计数的升序进行排序,请删除-号)。

请注意,我们必须在对带有索引的压缩后的数组进行排序,否则我们将失去球号关联。

一些进一步的观察结果

  • Scala 中,强烈建议不要使用var。请改用val。 (可以编写从不完全不使用var的代码。)
  • object(除package object之外)通常应按惯例大写首字母。
  • 扩展scala.App可使对象的构造函数成为程序的main方法,并简化事务。

未排序的输出(与您的摘要匹配):

1 (23) | ***********************
2 (16) | ****************
3 (18) | ******************
4 (19) | *******************
5 (26) | **************************
...

排序输出:

30 (28) | ****************************
 5 (26) | **************************
45 (26) | **************************
42 (25) | *************************
15 (24) | ************************
...

更新

为回应您有关需要频率计数中的频率计数的评论(如果我理解正确的话),请按以下步骤操作:

要获得每个值的频率,有(再次)多种方法可以执行此操作。在这种情况下,我将使用foldLeft操作。

您可以将foldLeft视为累加操作:第一个参数标识值- accumulator 的初始值,而第二个参数是应用于容器的每个成员(Array)的函数。后一个函数本身带有两个参数:当前累加器值和当前元素的值,并返回新的累加器值。

在这种情况下,我将使用foldLeft来建立一个关联数组(一个SortedMap,它的键会以升序排列)将每个频率计数链接到该次数它发生。 (在此SortedMap中,_key_s是频率计数,与每个键关联的是该频率计数的频率。)

这是下面的样子(我将其分解为多个步骤以使其更易于理解):

// Required import.
import scala.collection.immutable.SortedMap

// Our initial, empty map, which reports a frequency of zero if a key is not present.
// Note that a `SortedMap` keeps its keys sorted.
val zero = SortedMap.empty[Int, Int].withDefaultValue(0)

// Iterate through the frequency counts (`fc`) in the `lottery` array. `fm` is the current
// status of our map.
val freq = lottery.foldLeft(zero) {(fm, fc) =>

  // Determine the new count for this frequency count.
  val newCount = fm(fc) + 1

  // Create an association from the frequency count to this new count.
  val assoc = fc -> newCount

  // Add this association to the map, resulting in a new map. Any existing association
  // will be replaced.
  fm + assoc
}

如果您已遵循此操作,则为简短版本:

val freq = lottery.foldLeft(SortedMap.empty[Int, Int].withDefaultValue(0)) {(fm, fc) =>
  fm + (fc -> (fm(fc) + 1))
}

现在剩下的就是创建直方图并打印它们:

val lines = freq.map {
  case (k, v) => line(k, v)
}

lines.foreach(println)

(注意:line方法的参数定义需要根据更改进行调整,但是行为是相同的。)

输出:

11 ( 1) | *
12 ( 1) | *
13 ( 2) | **
14 ( 3) | ***
15 ( 3) | ***
16 ( 3) | ***
17 ( 4) | ****
18 ( 5) | *****
19 ( 4) | ****
20 ( 8) | ********
21 ( 3) | ***
22 ( 5) | *****
23 ( 2) | **
24 ( 1) | *
25 ( 1) | *
26 ( 2) | **
28 ( 1) | *