>source

데이터 포인트가있는 CSV 파일이 있습니다.

student, year, subject, score1, score2, score3, ..., score100
Alex, 2010, Math, 23, 56, 43, ..., 89
Alex, 2011, Science, 45, 32, 45, ..., 65
Matt, 2009, Art, 34, 56, 75, ..., 43
Matt, 2010, Math, 43, 54, 54, ..., 32

Java에서 Map과 같은 CSV를로드하는 가장 좋은 방법은 무엇입니까? 이 데이터는 조회 서비스에 사용되므로 선택한 맵 데이터 구조입니다. 키는 Tuple (학생, 연도)->주제 + 점수 목록을 반환합니다 (SubjectScore.class). 그래서 아이디어에는 학생의 이름과 학년이 주어지고 모든 과목과 점수를 얻습니다.

다음과 같은 정의 된 클래스 맵에서 CSV 파일을 읽으려고 검색하는 동안 우아한 솔루션을 찾지 못했습니다. Map<Tuple, List<SubjectScore>>

class Tuple {
  private String student;
  private int year;
}
class SubjectScore {
  private String subject;
  private int score1;
  private int score2;
  private int score3;
  // more fields here
  private int score100;
}

추가 세부 정보 : CSV 파일은 크기가 2GB 이하이지만 본질적으로 정적이므로 메모리에로드하기로 결정합니다.


  • 답변 # 1

    아래에서 시작점으로 사용할 수있는 첫 번째 예를 찾으십시오. 예제 입력 데이터에서 점을 제거하고 점수가 4 개인 간단한 예제를 가정합니다.

    student, year, subject, score1, score2, score3, ..., score100
    Alex, 2010, Math, 23, 56, 43, 89
    Alex, 2011, Science, 45, 32, 45, 65
    Matt, 2009, Art, 34, 56, 75, 43
    Matt, 2010, Math, 43, 54, 54, 32
    Alex, 2010, Art, 43, 54, 54, 32
    
    

    또한 튜플 클래스에서 equals 및 hashcode 메서드를 덮어 쓰고 적절한 생성자를 구현했다고 가정합니다.

    class Tuple {
        private String student;
        private int year;
        public Tuple(String student, int year) {
            this.student = student;
            this.year = year;
        }
        @Override
        public int hashCode() {
            int hash = 7;
            hash = 79 * hash + Objects.hashCode(this.student);
            hash = 79 * hash + this.year;
            return hash;
        }
        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final Tuple other = (Tuple) obj;
            if (this.year != other.year) {
                return false;
            }
            return Objects.equals(this.student, other.student);
        }   
        @Override
        public String toString() {
            return "Tuple{" + "student=" + student + ", year=" + year + '}';
        }
    }
    
    

    적절한 생성자가있는 SubjectScore 클래스

    class SubjectScore {
        private String subject;
        private int score1;
        private int score2;
        private int score3;
        // more fields here
        private int score4;
        public SubjectScore(String row) {
            String[] data = row.split(",");
            this.subject = data[0];
            this.score1 = Integer.parseInt(data[1].trim());
            this.score2 = Integer.parseInt(data[2].trim());
            this.score3 = Integer.parseInt(data[3].trim());
            this.score4 = Integer.parseInt(data[4].trim());
        }        
    }
    
    

    그런 다음 다음과 같이 원하는 맵을 만들 수 있습니다.

    import java.io.IOException;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.util.AbstractMap;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Objects;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
    public class Example {
        public static void main(String[] args)  {
            Map<Tuple, List<SubjectScore>> map = new HashMap<>();
            try (Stream<String> content = Files.lines(Paths.get("path to your csv file"))) {
                map = content.skip(1).map(line -> lineToEntry(line)) //skip header and map each line to a map entry
                        .collect(Collectors.groupingBy(
                                Map.Entry::getKey, 
                                Collectors.mapping(Map.Entry::getValue, Collectors.toList()))
                        );
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            map.forEach((k,v) -> {System.out.println(k + " : " + v);});
        }
        static Entry<Tuple, SubjectScore> lineToEntry(String line) {
            //split each line at the first and second comma producing an array with 3 columns
            // first column with the name and second with year to create a tuple object
            // evrything after the second comma as one column to create a SubjectScore object
            String[] data = line.split(",", 3);
            Tuple t = new Tuple(data[0].trim(), Integer.parseInt(data[1].trim()));
            SubjectScore s = new SubjectScore(data[2]);
            return new AbstractMap.SimpleEntry<>(t, s);
        }
    }
    
    

    각 점수마다 개별 필드가 정말로 필요한지 모르겠습니다. SubjectScore 수업. 내가 당신이라면 정수 목록을 선호합니다. 그렇게하려면 클래스를 다음과 같이 변경하십시오.

    class SubjectScore {
        private String subject;
        private List<Integer> scores;
        public SubjectScore(String row) {
            String[] data = row.split(",");
            this.subject = data[0];
            this.scores = Arrays.stream(data, 1, data.length)
                    .map(item -> Integer.parseInt(item.trim()))
                    .collect(Collectors.toList());
        }
    }
    
    

  • 답변 # 2

    I was wondering how to take the same approach but convert it into Map<String, Map<Integer, List<SubjectScore>>> .

    데이터 유형에 대한 요구 사항이 변경 되었기 때문에 다른 답변을 추가하기로 결정했습니다. 당신이 여전히 같다고 가정 SubjectScore 수업

    class SubjectScore {
        private String subject;
        private List<Integer> scores;
        public SubjectScore(String row) {
            String[] data = row.split(",");
            this.subject = data[0];
            this.scores = Arrays.stream(data, 1, data.length)
                    .map(item -> Integer.parseInt(item.trim()))
                    .collect(Collectors.toList());
        }
    }
    
    

    if-else 블록을 사용하여 키-값 쌍이 항상 존재하는지 확인하는 구식 방법 :

    public static void main(String[] args) throws IOException {
        List<String> allLines = Files.readAllLines(Paths.get("path to your file"));
        Map<String,Map<String, List<SubjectScore>>> mapOldWay = new HashMap<>();
        for(String line : allLines.subList(1, allLines.size())){
            //split each line in 3 parts, i.e  1st column, 2nd column and everything after 3rd column
            String data[] = line.split("\\s*,\\s*",3);
            if(mapOldWay.containsKey(data[0])){
                if(mapOldWay.get(data[0]).containsKey(data[1])){
                    mapOldWay.get(data[0]).get(data[1]).add(new SubjectScore(data[2]));
                }
                else{
                    mapOldWay.get(data[0]).put(data[1], new ArrayList<>());
                    mapOldWay.get(data[0]).get(data[1]).add(new SubjectScore(data[2]));
                }
            }
            else{
                mapOldWay.put(data[0], new HashMap<>());
                mapOldWay.get(data[0]).put(data[1], new ArrayList<>());
                mapOldWay.get(data[0]).get(data[1]).add(new SubjectScore(data[2]));
            }
        }
        printMap(mapOldWay);
    }
    public static void printMap(Map<String, Map<String, List<SubjectScore>>> map) {
        map.forEach((outerkey,outervalue) -> {
            System.out.println(outerkey);
            outervalue.forEach((innerkey,innervalue)-> {
                System.out.println("\t" + innerkey + " : " + innervalue);
            });
        });
    }
    
    

    동일한 로직이지만 Java 8 기능을 사용하면 더 짧습니다 ( Map#computeIfAbsent ) :

    public static void main(String[] args) throws IOException {
        List<String> allLines = Files.readAllLines(Paths.get("path to your file"));
        Map<String,Map<String, List<SubjectScore>>> mapJ8Features = new HashMap<>();
        for(String line : allLines.subList(1, allLines.size())){
            String data[] = line.split("\\s*,\\s*",3);
            mapJ8Features.computeIfAbsent(data[0], k -> new HashMap<>())
                    .computeIfAbsent(data[1], k -> new ArrayList<>())
                    .add(new SubjectScore(data[2]));
        }
    }
    
    

    스트림 및 중첩을 사용하는 또 다른 접근 Collectors#groupingBy

    public static void main(String[] args) throws IOException {
        Map<String,Map<String, List<SubjectScore>>> mapStreams = new HashMap<>();        
        try (Stream<String> content = Files.lines(Paths.get("path to your file"))) {
            mapStreams = content.skip(1).map(line -> line.split("\\s*,\\s*",3))
                    .collect(Collectors.groupingBy(splited -> splited[0],
                             Collectors.groupingBy(splited -> splited[1], 
                             Collectors.mapping(splited -> new SubjectScore(splited[2]),Collectors.toList()))));
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    
    

    참고 : 방금 연도를 정수로 표현하고 싶다는 사실을 깨달았습니다. 나는 그것을 끈으로 남겼다. 변경하려면 모든 곳을 교체하십시오. data[1] or splited[1]Integer.parseInt(data[1] or splited[1])

  • 이전 r - 반짝이는 틱과 백틱으로 group_by_ ()를 사용하는 방법>
  • 다음 javascript - React에서 이동 가능/드래그 가능