从输入1:
fruit, apple, cider
animal, beef, burger
然后输入2:
animal, beef, 5kg
fruit, apple, 2liter
fish, tuna, 1kg
我需要生产:
fruit, apple, cider, 2liter
animal, beef, burger, 5kg
我能得到的最接近的例子是:
object FileMerger {
def main(args : Array[String]) {
import scala.io._
val f1 = (Source fromFile "file1.csv" getLines) map (_.split(", *")(1))
val f2 = Source fromFile "file2.csv" getLines
val out = new java.io.FileWriter("output.csv")
f1 zip f2 foreach { x => out.write(x._1 + ", " + x._2 + "\n") }
out.close
}
}
问题在于该示例假定两个CSV文件包含相同数量的元素且顺序相同。我的合并结果必须只包含第一个和第二个文件中的元素。我是Scala的新手,任何帮助将不胜感激。
最佳答案
您需要两个文件的intersection:来自file1和file2的行,它们共享一些条件。从集合论的角度考虑这一点:您有两个集合,其中一些元素有共同点,而您需要一个包含这些元素的新集合。好吧,除此之外,还有更多的东西,因为线并不完全相等...
因此,假设您读取了file1,其类型为List[Input1]
。我们可以这样编写代码,而无需深入了解Input1
是什么的任何细节:
case class Input1(line: String)
val f1: List[Input1] = (Source fromFile "file1.csv" getLines () map Input1).toList
我们可以对file2和
List[Input2]
做同样的事情:case class Input2(line: String)
val f2: List[Input2] = (Source fromFile "file2.csv" getLines () map Input2).toList
您可能想知道为什么我创建两个不同的类,如果它们具有完全相同的定义。好吧,如果您正在读取结构化数据,则将有两种不同的类型,因此让我们看一下如何处理更复杂的情况。
好的,既然
Input1
和Input2
是不同的类型,那么我们如何匹配它们呢?好吧,这些行由键匹配,根据您的代码,键是每行的第一列。因此,让我们创建一个Key
类,并转换Input1 => Key
和Input2 => Key
:case class Key(key: String)
def Input1IsKey(input: Input1): Key = Key(input.line split "," head) // using regex would be better
def Input2IsKey(input: Input2): Key = Key(input.line split "," head)
好的,现在我们可以从
Key
和Input1
生成一个通用的Input2
,让我们得到它们的交集:val intersection = (f1 map Input1IsKey).toSet intersect (f2 map Input2IsKey).toSet
因此,我们可以构建所需的线的交点,但没有线!问题在于,对于每个键,我们需要知道它来自哪一行。考虑一下我们有一组键,对于每个键,我们都希望跟踪一个值-这就是
Map
的含义!因此,我们可以构建以下代码:val m1 = (f1 map (input => Input1IsKey(input) -> input)).toMap
val m2 = (f2 map (input => Input2IsKey(input) -> input)).toMap
因此可以产生如下输出:
val output = intersection map (key => m1(key).line + ", " + m2(key).line)
现在您要做的就是输出。
让我们考虑对该代码进行一些改进。首先,请注意,上面产生的输出重复了该键-这正是您的代码所做的,而不是您在示例中想要的。然后,我们更改
Input1
和Input2
,以将密钥与其余args分开:case class Input1(key: String, rest: String)
case class Input2(key: String, rest: String)
现在要初始化f1和f2有点困难。而不是使用
split
,它会不必要地破坏所有行(并且以极高的性能代价),我们将在第一个逗号处将行右对齐:之前的所有内容都是关键,之后的所有内容都是休息。方法span
可以做到:def breakLine(line: String): (String, String) = line span (',' !=)
在REPL上使用
span
方法稍作尝试,可以更好地理解它。至于(',' !=)
,这只是(x => ',' != x)
的缩写形式。接下来,我们需要一种从元组(
Input1
的结果)创建Input2
和breakLine
的方法:def TupleIsInput1(tuple: (String, String)) = Input1(tuple._1, tuple._2)
def TupleIsInput2(tuple: (String, String)) = Input2(tuple._1, tuple._2)
现在,我们可以读取文件:
val f1: List[Input1] = (Source fromFile "file1.csv" getLines () map breakLine map TupleIsInput1).toList
val f2: List[Input2] = (Source fromFile "file2.csv" getLines () map breakLine map TupleIsInput2).toList
我们可以简化的另一件事是交集。创建
Map
时,其键是集合,因此我们可以先创建 map ,然后使用其键来计算交集:case class Key(key: String)
def Input1IsKey(input: Input1): Key = Key(input.key)
def Input2IsKey(input: Input2): Key = Key(input.key)
// We now only keep the "rest" as the map value
val m1 = (f1 map (input => Input1IsKey(input) -> input.rest)).toMap
val m2 = (f2 map (input => Input2IsKey(input) -> input.rest)).toMap
val intersection = m1.keySet intersect m2.keySet
输出是这样计算的:
val output = intersection map (key => key + m1(key) + m2(key))
请注意,我不再添加逗号-f1和f2的其余部分都已经以逗号开头。
关于scala - 将两个CSV文件的交集与Scala合并,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/6576360/