这种特定的练习是否适合“功能风格”的设计模式?

时间:2016-10-20 08:16:39

标签: scala functional-programming java-8 java-stream

假设我们有一个包含在文件Array.json中的一维javascript对象数组,其中的密钥模式是未知的,即在读取文件之前不知道密钥。

然后我们希望输出一个带有标题或第一个条目的CSV文件,该条目是来自所有对象的逗号分隔的一组键。

文件的每一行都应包含逗号分隔值,这些值对应于文件中的每个键。

Array.json

[
    abc:123,
    xy:"yz",
    s12:13,
],
    ...
[
    abc:1
    s:133,
]

有效输出:

abc,xy,s12,s

123,yz,13,
1,,,133

我正在教自己'功能风格'编程,但我认为这个问题不适合功能性解决方案。 我相信这个问题需要为输出头保留一些状态,然后每行依赖于该头。

我想在一次通过中解决问题。我的目标是大数据集的效率,最小的遍历,以及可能的并行化。如果这是不可能的,那么你可以提供证据或推理来解释原因吗?

编辑:有没有办法在功能上解决这个问题?:

  

假设您按特定顺序传递一次数组。然后   从头开始,标头集对于第一个看起来像abc,xy,s12   宾语。使用CSV条目123,yz,13。然后在下一个对象上添加一个   标头集的附加键,因此abc,xy,s12,s将成为标头   并且CSV条目为1,,,133。最后我们不需要   第二次传递数据集。我们可以追加额外的   逗号到结果集。这是我们接近单一的一种方式   通过....

是否有用于解决此类问题的功能工具(功能),我应该考虑什么? [通过功能工具我的意思是Monads,FlatMap,Filters等]。 另外,我应该考虑期货这样的事情吗?

目前我一直在尝试使用Java8来解决这个问题,但我对Scala等解决方案持开放态度。理想情况下,我能够确定Java8s的功能方法是否可以解决问题,因为这是我目前正在使用的语言英寸

4 个答案:

答案 0 :(得分:0)

完全可以在函数式编程中使用状态。但是在函数式编程中不允许具有可变状态或变异状态。

功能编程主张创建新的更改状态,而不是改变状态。

因此,在程序中创建的读取和访问状态为Ok,直到并且除非您发生变异或副作用。

即将到来。

val list = List(List("abc" -> "123", "xy" -> "yz"), List("abc" -> "1"))

list.map { inner => inner.map { case (k, v) => k}}.flatten

list.map { inner => inner.map { case (k, v) => v}}.flatten

<强> REPL

scala> val list = List(List("abc" -> "123", "xy" -> "yz"), List("abc" -> "1"))
list: List[List[(String, String)]] = List(List((abc,123), (xy,yz)), List((abc,1)))


scala> list.map { inner => inner.map { case (k, v) => k}}.flatten
res1: List[String] = List(abc, xy, abc)

scala> list.map { inner => inner.map { case (k, v) => v}}.flatten
res2: List[String] = List(123, yz, 1)

或使用flatMap而不是map并展平

val list = List(List("abc" -> "123", "xy" -> "yz"), List("abc" -> "1"))

list.flatMap { inner => inner.map { case (k, v) => k}}

list.flatMap { inner => inner.map { case (k, v) => v}}

答案 1 :(得分:0)

这可能是一种方法:

val arr = Array(Map("abc" -> 123, "xy" -> "yz", "s12" -> 13), Map("abc" -> 1, "s" -> 133)) 
    val keys = arr.flatMap(_.keys).distinct  // get the distinct keys for header
    arr.map(x => keys.map(y => x.getOrElse(y,""))) // get an array of rows

答案 2 :(得分:0)

在函数式编程中,不允许使用可变状态。但不可改变的状态/价值观都很好。

假设您已将json文件读入值输入:List [Map [String,String]],以下代码将解决您的问题:

val input = List(Map("abc"->"123", "xy"->"yz" , "s12"->"13"), Map("abc"->"1", "s"->"33"))
val keys = input.map(_.keys).flatten.toSet
val keyvalues = input.map(kvs => keys.map(k => (k->kvs.getOrElse(k,""))).toMap)
val values = keyvalues.map(_.values)
val result = keys.mkString(",") + "\n" + values.map(_.mkString(",")).mkString("\n")

答案 3 :(得分:0)

由于csv输出会随着每一个新的输入行而改变,所以在写出之前必须将其保存在内存中。 如果您考虑从csv文件的内部表示创建输出文本格式 数据上的另一个“传递”(csv的内部表示实际上是{{1您必须遍历以将其转换为文本)然后单次传递无法执行此操作。

但是,如果这是可以接受的,那么您可以使用Map[String,List[String]]从json文件中读取单个项目,将其合并到csv文件中,并执行此操作直到流为空。

假设csv文件的内部表示是

Stream

您可以将单个项目表示为

trait CsvFile {
  def merge(line: Map[String, String]): CsvFile
}

您可以使用trait Item { def asMap: Map[String, String] }

来实现它
foldLeft

或使用递归来获得相同的结果

def toCsv(items: Stream[Item]): CsvFile = 
  items.foldLeft(CsvFile(Map()))((csv, item) => csv.merge(item.asMap))

注意:当然你不必为CsvFile或Item创建类型,你可以分别使用Map [String,List [String]]和Map [String,String]

更新:

随着@tailrec def toCsv(items: Stream[Item], prevCsv: CsvFile): CsvFile = items match { case Stream.Empty => prevCsv case item #:: rest => val newCsv = prevCsv.merge(item.asMap) toCsv(rest, newCsv) } 特征/类的更多细节请求,这是一个示例实现:

CsvFile