从列表中获取符合条件的子列表

3

我很新手Scala,但从我读到的内容来看,它似乎是处理我正在开发的项目的理想语言。

我有一个非常大的CSV文件,它看起来像这样:

INDEX, CITY, COST
  7  ,  London, 500
  7  ,  Paris, 200
  11  ,  Rome, 300
  11  ,  New York, 100
  11  ,  Madrid, 7

我希望能够读取CSV文件,并一次一个索引地生成所有具有相同索引的元素列表。从上面的示例中,我想要得到一个包含以下行的列表:
7, London, 500
7, Paris, 200

并且第二个列表包含行:

11, Rome, 300
11, New York, 100
11, Madrid, 7

在CSV文件中读取数据非常简单:

val iter = src.getLines().drop(1).map(_.split(",")) //from SO :) 

然而,我正在苦苦寻找一种干净的方法来生成我的子列表。在我看来,应该有一种漂亮、简洁的方式来使用Scala实现这一点。我特别希望数据能够延迟加载,因为数据量很大。你能建议我如何实现吗?

所有的数据都按索引顺序排列(尽管索引不是连续的),我正在处理的CSV文件不包含任何嵌套逗号或转义符。

3个回答

3

Source.getLines已经是懒加载的。它返回一个Iterator,该迭代器将根据需要从底层文件中获取每一行数据。大多数迭代器操作也会返回迭代器,因此在下面的代码中:

val iter = src.getLines.tail map {_ split ","}

你已经正确地命名了该值。它将成为一个Iterator [Array [String]],每个字符串数组都将按需生成。
你遇到过任何特定问题吗,表明数据没有被惰性加载吗? 更新 要从此迭代器产生其中一个子列表,可以执行以下操作:
val id7 = iter filter {_(0) == 7)

再次提醒,这仍然是懒加载模式。

或者...你可以将它们分组:

val grouped = iter.toStream groupBy {_(0)}

不幸的是,这并不完全是懒惰的。最后一行的第一列可能有一个唯一的值,因此您需要从输入中读取每个元素以知道需要多少个子分区。在REPL中,强制子流也更容易,因此您可以看到它们包含的内容:

val grouped = iter.toStream groupBy {_(0)} mapValues {_.toList}

抱歉,我并没有遇到问题,只是我想让代码实现某个目标。我无法弄清如何按索引对行进行分组,基本上我想要一个Array [Array [String]] ,将具有相同索引列值的每一行分组。 - user130076

2
scala> List(Array(1,"a"),Array(2,"b"),Array(1,"c")).groupBy(_(0))
res1: scala.collection.immutable.Map[Any,List[Array[Any]]] = Map(1 -> List(Array(1, a), Array(1, c)), 2 -> List(Array(2, b)))

所以你需要做的是添加.groupBy(_(0))来按照数组中的第一个元素进行分组。

+1。不保留顺序,但是OP也没有要求这个。 - The Archetypal Paul
1
然而,OP确实要求进行延迟加载,而这个答案完全规避了这个问题。 - Kevin Wright

1

当你拥有大量数据时,你必须更加小心地选择要进行的操作。

假设你的文件太大,无法全部加载到内存中,而你可以(被迫)通过将其读取 ~N 次来获取 N 个不同的子集。

首先,你应该弄清楚需要多少个子集。让我们创建一个具有虚构 getLines 方法的东西:

val src = new { def getLines() = Iterator("#", "1,a", "2,b", "2,c") }

现在我们需要找到所有的初始索引。你可以使用split,但是由于你正在处理大量的数据并且不需要将其全部拆分,所以让我们只找到第一个逗号(这里假设总能找到逗号):
val idx = Set() ++ src.getLines().drop(1).map(s => s.substring(0, s.indexOf(',')))

好的,现在我们知道我们要找什么了。然后我们通过一个类来获取它,这个类将帮助我们懒加载数据:

class OneIndex(index: String) {
  lazy val data = src.getLines().drop(1).filter(
    s => index == s.substring(0,s.indexOf(','))
  ).toArray
}
val everything = idx.map(i => (i,new OneIndex(i))).toMap

scala> everything("2").data.foreach(println)
2,b
2,c

还有许多其他的东西可以添加进去 - 也许在某些时候使用 .toInt.trim.toInt 可以帮助将索引值从字符串转换为整数。你可能还会想知道是否真的需要懒加载,因为它会强制你多次读取整个文件。但这至少是一个基本框架。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接