使用 Java 流从 csv 文件中过滤
Filtering from csv files using Java stream
我有一个包含 SW 字符的 csv 文件,我想使用 java 流找到最重的字符。这是文件示例:
name;height;mass;hair_color;skin_color;eye_color;birth_year;gender
Luke Skywalker;172;77;blond;fair;blue;19BBY;male
C-3PO;167;75;n/a;gold;yellow;112BBY;n/a
R2-D2;96;32;n/a;white, blue;red;33BBY;n/a
Darth Vader;202;136;none;white;yellow;41.9BBY;male
Leia Organa;150;49;brown;light;brown;19BBY;female
Owen Lars;178;120;brown, grey;light;blue;52BBY;male
Beru Whitesun lars;165;75;brown;light;blue;47BBY;female
Grievous;216;159;none;brown, white;green, yellow;unknown;male
Finn;unknown;unknown;black;dark;dark;unknown;male
Rey;unknown;unknown;brown;light;hazel;unknown;female
Poe Dameron;unknown;unknown;brown;light;brown;unknown;male
预期输出是字符串“Grievous”。
最初我想创建一个字符 class,我可以在其中存储数据并在拆分行后使用对象而不是字符串数组。但是,每个值都可能是未知的或 n/a,因此不太确定如何解决它。有没有办法只使用流来实现这一点?
这是我的初步尝试,将每一行映射到具有字段 name
和 height
的新 Person
对象,但是这种方法不能正确处理未知输入。
public static String getHeaviestCharacter(String file) throws IOException {
return Files.lines(Paths.get(file))
.map(line -> line.split(";"))
.map(part -> new Person(part[0], part[2]))
.max((p1, p2) -> Integer.compare(p1.getWeight(), p2.getWeight()))
.map(p1.getName());
}
我不建议使用 Streams 执行此操作,而是使用一些 CSV 库,因为它更安全。
public static void main(String[] args) {
try {
BufferedReader reader = new BufferedReader(new FileReader(new File("characters.csv")));
// Skip first line
reader.readLine();
Optional<String> optionalHeaviestCharacter = getHeaviestCharactersName(reader.lines());
System.out.println(optionalHeaviestCharacter);
} catch (IOException e) {
e.printStackTrace();
}
}
public static Optional<String> getHeaviestCharactersName(Stream<String> lineStream) {
return lineStream
.map(lineString -> lineString.split(";")) // map every line string to an array with all values
.filter(values -> values[2].matches("[0-9]+")) // filter out characters with a non-number value as a mass
.max((values1, values2) -> Integer.compare(Integer.parseInt(values1[2]), Integer.parseInt(values2[2]))) // get element with maximum mass
.map(heaviestValues -> heaviestValues[0]); // map values array of heaviest character to its name
}
首先我们读取文件,我将其命名为 characters.csv
。您可能需要编辑文件路径以指向您的文件。
BufferedReader reader = new BufferedReader(new FileReader(new File("characters.csv")));
然后我们通过调用reader.lines()
方法
从文件中读取所有行,每一行作为Stream<String>
中的一个字符串
函数 getHeaviestCharactersName
将 return 和 Optional<String>
。 Optional 将为空,例如当所有字符都具有 unknown/invalid 质量或根本没有字符时。
如果您认为总会有至少一个字符具有有效质量,那么您只会得到具有 optionalHeaviestCharacter.get()
的最重字符的名称。
否则你必须先检查 Optional 是否为空:
if (optionalHeaviestCharacter.isEmpty()) {
System.out.println("Could not find a character with the heaviest mass");
} else {
System.out.println("Heaviest character is " + optionalHeaviestCharacter.get());
}
您可以通过电话获取姓名
流
正如其他人指出的那样,我怀疑流是解决您的特定问题的最佳方法。但既然你问了,只是为了好玩,我试了一下。经过web-searching和trial-and-error之后,我似乎找到了使用流的解决方案。
我们使用NIO.2 classes Path
& Files
打开数据文件
我们通过调用Files.lines
来定义一个流。
我们通过调用 Stream#skip
.
省略了 header 行
您的某些输入行在我们的目标第三字段中具有 non-numeric 值“未知”。所以我们调用 Stream#filter
来忽略这些行。我们通过使用 String#split
提取第三个字段,同时传递烦人的 zero-based 索引号 2
.
要获得第三列中的最高数字,我们需要进行排序。为了排序,我们提取了 Comparator
created via Comparator.comparingInt
. To get the needed int
value, we parse the text of the third field using Integer.parseInt
.
中的第三个字段
排序后,我们需要访问流中的最后一个元素,因为它应该具有最大权重的角色。这对我来说似乎很笨拙,但显然获取流的最后一个元素的方法是 .reduce( ( first , second ) -> second ).orElse( null )
。我真希望我们有一个 Stream#last
方法!
最后一个元素是 String
object,输入文件中的一行文本。所以我们需要再次拆分字符串。但是这次我们拆分时,我们采用 first 元素而不是第三个元素,因为我们的目标是报告角色的名字。第一个元素由烦人的 zero-based 索引号 0
.
标识
瞧,我们得到 Grievous
作为我们的最终结果。
Path path = Paths.get( "/Users/basil_dot_work/inputs.csv" );
if ( Files.notExists( path ) ) { throw new IllegalStateException( "Failed to find file at path: " + path ); }
Stream < String > lines;
try { lines = Files.lines( path , StandardCharsets.UTF_8 ); } catch ( IOException e ) { throw new RuntimeException( e ); }
String result =
lines
.skip( 1L ) // Skip the header row, with column names.
.filter( // Filter out lines whose targeted value is "unknown". We need text made up only of digits.
line -> ! line.split( ";" )[ 2 ].equalsIgnoreCase( "unknown" )
)
.sorted( // Sort by extracting third field’s text, then parse to get an `int` value.
Comparator.comparingInt( ( String line ) -> Integer.parseInt( line.split( ";" )[ 2 ] ) )
)
.reduce( ( first , second ) -> second ).orElse( null ) // Get last element.
.split( ";" )[ 0 ]; // Extract name of character from first field of our one and only line of input left remaining after processing.
System.out.println( "result = " + result );
result = Grievous
请务必将我的方法与其他人的方法进行比较 Answer, by Florian Hartung。另一个可能更好;还没仔细研究
没有流
为了进行比较,这里是更传统的代码,很少或根本没有使用流。
我们以与上面相同的方式从文件中读取行。
我们需要跳过第一行,即列标题的 header 行。但是 Files.lines
编辑的 List
object return 是不可修改的。所以我们不能简单地删除该列表的第一个元素。所以我们通过调用 lines.subList( 1 , lines.size() )
有效地跳过了第一行。 subList
命令 return 是一个作为视图映射回原始列表的列表,而不是实际创建一个新的单独列表。这很有效,适合我们在这里使用。
我们将 class 定义为 record 来保存每个人的详细信息。我们使用 Integer
而不是 int
以便我们可以为带有 unknown
文本而不是数字的行保留 null
。
对于每一行,我们直接将文本项传输到 String
个成员字段。但是对于高度和质量,我们使用三元运算符来 return null
或实例化 Integer
object.
我们通过添加到列表来收集我们的 Person
object。
为了得到最大的Person
object,其中mass
最大,我们需要忽略那些null
。所以我们在这里使用一个简单的流来制作 Person
object 的新列表,质量为 non-null。此流可以替换为常规循环,但会更冗长。
通过过滤列表,我们调用 Collections.max
while passing a Comparator
object 来比较 mass
成员字段。
我们最终得到一个 Person
object。所以我们查询它的 name
成员字段。
瞧,我们得到 Grievous
作为我们的最终结果。
Path path = Paths.get( "/Users/basil_dot_work/inputs.csv" );
if ( Files.notExists( path ) ) { throw new IllegalStateException( "Failed to find file at path: " + path ); }
List < String > lines;
try { lines = Files.lines( path , StandardCharsets.UTF_8 ).toList(); } catch ( IOException e ) { throw new RuntimeException( e ); }
lines = lines.subList( 1 , lines.size() ); // Skip over first line.
record Person( String name , Integer height , Integer mass , String hair_color , String skin_color , String eye_color , String birth_year , String gender ) { }
List < Person > persons = new ArrayList <>();
for ( String line : lines )
{
String[] parts = line.split( ";" );
Integer height = ( parts[ 1 ].equalsIgnoreCase( "unknown" ) ) ? null : Integer.valueOf( parts[ 1 ] );
Integer mass = ( parts[ 2 ].equalsIgnoreCase( "unknown" ) ) ? null : Integer.valueOf( parts[ 2 ] );
Person person = new Person( parts[ 0 ] , height , mass , parts[ 3 ] , parts[ 4 ] , parts[ 5 ] , parts[ 6 ] , parts[ 7 ] );
persons.add( person );
}
System.out.println( "persons = " + persons );
List < Person > personsWithMass = persons.stream().filter( person -> Objects.nonNull( person.mass ) ).toList();
Person heaviestPerson = Collections.max( personsWithMass , Comparator.comparing( person -> person.mass ) );
System.out.println( "heaviest Person’s name = " + heaviestPerson.name );
heaviest Person’s name = Grievous
我有一个包含 SW 字符的 csv 文件,我想使用 java 流找到最重的字符。这是文件示例:
name;height;mass;hair_color;skin_color;eye_color;birth_year;gender
Luke Skywalker;172;77;blond;fair;blue;19BBY;male
C-3PO;167;75;n/a;gold;yellow;112BBY;n/a
R2-D2;96;32;n/a;white, blue;red;33BBY;n/a
Darth Vader;202;136;none;white;yellow;41.9BBY;male
Leia Organa;150;49;brown;light;brown;19BBY;female
Owen Lars;178;120;brown, grey;light;blue;52BBY;male
Beru Whitesun lars;165;75;brown;light;blue;47BBY;female
Grievous;216;159;none;brown, white;green, yellow;unknown;male
Finn;unknown;unknown;black;dark;dark;unknown;male
Rey;unknown;unknown;brown;light;hazel;unknown;female
Poe Dameron;unknown;unknown;brown;light;brown;unknown;male
预期输出是字符串“Grievous”。
最初我想创建一个字符 class,我可以在其中存储数据并在拆分行后使用对象而不是字符串数组。但是,每个值都可能是未知的或 n/a,因此不太确定如何解决它。有没有办法只使用流来实现这一点?
这是我的初步尝试,将每一行映射到具有字段 name
和 height
的新 Person
对象,但是这种方法不能正确处理未知输入。
public static String getHeaviestCharacter(String file) throws IOException {
return Files.lines(Paths.get(file))
.map(line -> line.split(";"))
.map(part -> new Person(part[0], part[2]))
.max((p1, p2) -> Integer.compare(p1.getWeight(), p2.getWeight()))
.map(p1.getName());
}
我不建议使用 Streams 执行此操作,而是使用一些 CSV 库,因为它更安全。
public static void main(String[] args) {
try {
BufferedReader reader = new BufferedReader(new FileReader(new File("characters.csv")));
// Skip first line
reader.readLine();
Optional<String> optionalHeaviestCharacter = getHeaviestCharactersName(reader.lines());
System.out.println(optionalHeaviestCharacter);
} catch (IOException e) {
e.printStackTrace();
}
}
public static Optional<String> getHeaviestCharactersName(Stream<String> lineStream) {
return lineStream
.map(lineString -> lineString.split(";")) // map every line string to an array with all values
.filter(values -> values[2].matches("[0-9]+")) // filter out characters with a non-number value as a mass
.max((values1, values2) -> Integer.compare(Integer.parseInt(values1[2]), Integer.parseInt(values2[2]))) // get element with maximum mass
.map(heaviestValues -> heaviestValues[0]); // map values array of heaviest character to its name
}
首先我们读取文件,我将其命名为 characters.csv
。您可能需要编辑文件路径以指向您的文件。
BufferedReader reader = new BufferedReader(new FileReader(new File("characters.csv")));
然后我们通过调用reader.lines()
方法
Stream<String>
中的一个字符串
函数 getHeaviestCharactersName
将 return 和 Optional<String>
。 Optional 将为空,例如当所有字符都具有 unknown/invalid 质量或根本没有字符时。
如果您认为总会有至少一个字符具有有效质量,那么您只会得到具有 optionalHeaviestCharacter.get()
的最重字符的名称。
否则你必须先检查 Optional 是否为空:
if (optionalHeaviestCharacter.isEmpty()) {
System.out.println("Could not find a character with the heaviest mass");
} else {
System.out.println("Heaviest character is " + optionalHeaviestCharacter.get());
}
您可以通过电话获取姓名
流
正如其他人指出的那样,我怀疑流是解决您的特定问题的最佳方法。但既然你问了,只是为了好玩,我试了一下。经过web-searching和trial-and-error之后,我似乎找到了使用流的解决方案。
我们使用NIO.2 classes Path
& Files
打开数据文件
我们通过调用Files.lines
来定义一个流。
我们通过调用 Stream#skip
.
您的某些输入行在我们的目标第三字段中具有 non-numeric 值“未知”。所以我们调用 Stream#filter
来忽略这些行。我们通过使用 String#split
提取第三个字段,同时传递烦人的 zero-based 索引号 2
.
要获得第三列中的最高数字,我们需要进行排序。为了排序,我们提取了 Comparator
created via Comparator.comparingInt
. To get the needed int
value, we parse the text of the third field using Integer.parseInt
.
排序后,我们需要访问流中的最后一个元素,因为它应该具有最大权重的角色。这对我来说似乎很笨拙,但显然获取流的最后一个元素的方法是 .reduce( ( first , second ) -> second ).orElse( null )
。我真希望我们有一个 Stream#last
方法!
最后一个元素是 String
object,输入文件中的一行文本。所以我们需要再次拆分字符串。但是这次我们拆分时,我们采用 first 元素而不是第三个元素,因为我们的目标是报告角色的名字。第一个元素由烦人的 zero-based 索引号 0
.
瞧,我们得到 Grievous
作为我们的最终结果。
Path path = Paths.get( "/Users/basil_dot_work/inputs.csv" );
if ( Files.notExists( path ) ) { throw new IllegalStateException( "Failed to find file at path: " + path ); }
Stream < String > lines;
try { lines = Files.lines( path , StandardCharsets.UTF_8 ); } catch ( IOException e ) { throw new RuntimeException( e ); }
String result =
lines
.skip( 1L ) // Skip the header row, with column names.
.filter( // Filter out lines whose targeted value is "unknown". We need text made up only of digits.
line -> ! line.split( ";" )[ 2 ].equalsIgnoreCase( "unknown" )
)
.sorted( // Sort by extracting third field’s text, then parse to get an `int` value.
Comparator.comparingInt( ( String line ) -> Integer.parseInt( line.split( ";" )[ 2 ] ) )
)
.reduce( ( first , second ) -> second ).orElse( null ) // Get last element.
.split( ";" )[ 0 ]; // Extract name of character from first field of our one and only line of input left remaining after processing.
System.out.println( "result = " + result );
result = Grievous
请务必将我的方法与其他人的方法进行比较 Answer, by Florian Hartung。另一个可能更好;还没仔细研究
没有流
为了进行比较,这里是更传统的代码,很少或根本没有使用流。
我们以与上面相同的方式从文件中读取行。
我们需要跳过第一行,即列标题的 header 行。但是 Files.lines
编辑的 List
object return 是不可修改的。所以我们不能简单地删除该列表的第一个元素。所以我们通过调用 lines.subList( 1 , lines.size() )
有效地跳过了第一行。 subList
命令 return 是一个作为视图映射回原始列表的列表,而不是实际创建一个新的单独列表。这很有效,适合我们在这里使用。
我们将 class 定义为 record 来保存每个人的详细信息。我们使用 Integer
而不是 int
以便我们可以为带有 unknown
文本而不是数字的行保留 null
。
对于每一行,我们直接将文本项传输到 String
个成员字段。但是对于高度和质量,我们使用三元运算符来 return null
或实例化 Integer
object.
我们通过添加到列表来收集我们的 Person
object。
为了得到最大的Person
object,其中mass
最大,我们需要忽略那些null
。所以我们在这里使用一个简单的流来制作 Person
object 的新列表,质量为 non-null。此流可以替换为常规循环,但会更冗长。
通过过滤列表,我们调用 Collections.max
while passing a Comparator
object 来比较 mass
成员字段。
我们最终得到一个 Person
object。所以我们查询它的 name
成员字段。
瞧,我们得到 Grievous
作为我们的最终结果。
Path path = Paths.get( "/Users/basil_dot_work/inputs.csv" );
if ( Files.notExists( path ) ) { throw new IllegalStateException( "Failed to find file at path: " + path ); }
List < String > lines;
try { lines = Files.lines( path , StandardCharsets.UTF_8 ).toList(); } catch ( IOException e ) { throw new RuntimeException( e ); }
lines = lines.subList( 1 , lines.size() ); // Skip over first line.
record Person( String name , Integer height , Integer mass , String hair_color , String skin_color , String eye_color , String birth_year , String gender ) { }
List < Person > persons = new ArrayList <>();
for ( String line : lines )
{
String[] parts = line.split( ";" );
Integer height = ( parts[ 1 ].equalsIgnoreCase( "unknown" ) ) ? null : Integer.valueOf( parts[ 1 ] );
Integer mass = ( parts[ 2 ].equalsIgnoreCase( "unknown" ) ) ? null : Integer.valueOf( parts[ 2 ] );
Person person = new Person( parts[ 0 ] , height , mass , parts[ 3 ] , parts[ 4 ] , parts[ 5 ] , parts[ 6 ] , parts[ 7 ] );
persons.add( person );
}
System.out.println( "persons = " + persons );
List < Person > personsWithMass = persons.stream().filter( person -> Objects.nonNull( person.mass ) ).toList();
Person heaviestPerson = Collections.max( personsWithMass , Comparator.comparing( person -> person.mass ) );
System.out.println( "heaviest Person’s name = " + heaviestPerson.name );
heaviest Person’s name = Grievous