从输入1:
fruit, apple, cider
animal, beef, burger
Run Code Online (Sandbox Code Playgroud)
和输入2:
animal, beef, 5kg
fruit, apple, 2liter
fish, tuna, 1kg
Run Code Online (Sandbox Code Playgroud)
我需要生产:
fruit, apple, cider, 2liter
animal, beef, burger, 5kg
Run Code Online (Sandbox Code Playgroud)
我能得到的最接近的例子是:
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
}
}
Run Code Online (Sandbox Code Playgroud)
问题是该示例假定两个CSV文件包含相同数量的元素并且顺序相同.我的合并结果必须只包含第一个和第二个文件中的元素.我是Scala的新手,非常感谢任何帮助.
您需要两个文件的交集:来自file1和file2的行共享一些条件.从集合理论的角度考虑这个问题:你有两个共同的元素集合,你需要一个带有这些元素的新集合.嗯,还有更多的东西,因为线条不是真的相等......
所以,假设您读取了file1,那就是类型List[Input1]
.我们可以像这样对它进行编码,而不会得到任何细节Input1
:
case class Input1(line: String)
val f1: List[Input1] = (Source fromFile "file1.csv" getLines () map Input1).toList
Run Code Online (Sandbox Code Playgroud)
我们可以为file2做同样的事情List[Input2]
:
case class Input2(line: String)
val f2: List[Input2] = (Source fromFile "file2.csv" getLines () map Input2).toList
Run Code Online (Sandbox Code Playgroud)
您可能想知道为什么我创建了两个不同的类,如果它们具有完全相同的定义.好吧,如果你正在阅读结构化数据,你将有两种不同的类型,所以让我们看看如何处理更复杂的情况.
好了,那么我们如何搭配它们,因为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)
Run Code Online (Sandbox Code Playgroud)
好了,现在我们就可以产生一个共同Key
的Input1
和Input2
,让我们让他们的交集:
val intersection = (f1 map Input1IsKey).toSet intersect (f2 map Input2IsKey).toSet
Run Code Online (Sandbox Code Playgroud)
所以我们可以建立我们想要的线路交叉点,但我们没有线路!问题是,对于每个密钥,我们需要知道它来自哪条线.考虑一下我们有一组键,对于每个键,我们想要跟踪一个值 - 这正是一个键Map
!所以我们可以建立这个:
val m1 = (f1 map (input => Input1IsKey(input) -> input)).toMap
val m2 = (f2 map (input => Input2IsKey(input) -> input)).toMap
Run Code Online (Sandbox Code Playgroud)
所以输出可以像这样产生:
val output = intersection map (key => m1(key).line + ", " + m2(key).line)
Run Code Online (Sandbox Code Playgroud)
你现在要做的只是输出.
让我们考虑一下这段代码的一些改进.首先,请注意上面生成的输出重复了密钥 - 这正是您的代码所做的,但不是您在示例中所需的内容.然后让我们改变,Input1
并Input2
从其余的args中分离出密钥:
case class Input1(key: String, rest: String)
case class Input2(key: String, rest: String)
Run Code Online (Sandbox Code Playgroud)
现在有点难以初始化f1和f2.我们不会使用split
会不必要地破坏所有线路(并且性能成本很高),而是将第一个逗号分隔为正确的行:前面的所有内容都是关键,之后的所有内容都是休息.该方法span
可以:
def breakLine(line: String): (String, String) = line span (',' !=)
Run Code Online (Sandbox Code Playgroud)
使用span
REPL 上的方法进行一些操作以更好地理解它.至于(',' !=)
,这只是一种缩写形式的说法(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)
Run Code Online (Sandbox Code Playgroud)
我们现在可以读取文件:
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
Run Code Online (Sandbox Code Playgroud)
我们可以简化的另一件事是交叉.当我们创建一个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
Run Code Online (Sandbox Code Playgroud)
输出的计算如下:
val output = intersection map (key => key + m1(key) + m2(key))
Run Code Online (Sandbox Code Playgroud)
请注意,我不再附加逗号 - f1和f2的其余部分都以逗号开头.