【问题标题】:Java - parsing a 2nd csv file to override a valueJava - 解析第二个 csv 文件以覆盖一个值
【发布时间】:2021-10-17 00:02:29
【问题描述】:

我有两个 csv 文件。一个显示所有犯罪数据,包括城市、州、人口等。另一个显示州和缩写。我想将状态设置为缩写,目前我有一些很长的代码,我认为肯定有更好的方法来根据缩写 csv 文件设置它。

我的主要课程:

    public class StartApp {

public static ArrayList<CityCrime> crimes = new ArrayList<CityCrime>();
public static ArrayList<String> cities = new ArrayList<String>(); 

/**
 * Start point for app. Directs the reads from file and shows the menu
 * @param args
 */
public static void main(String[] args) {

    try {
        readCrimeData("crimeUSA.csv");
        System.out.println("Total cities read: " + getTotalCities());
        showMenu();
    } catch (Exception e) {

        e.printStackTrace();
    }
}

/**
 * Reads the crime data for each city from entered file
 * Adds the CityCrime objects to the crimes ArrayList
 */
public static void readCrimeData(String fromFile) {

    File file = new File(fromFile);

    FileReader fileReader;
    BufferedReader bufferedReader;
    String crimeInfo;
    String[] stats;

    try {
        fileReader = new FileReader(file);
        bufferedReader = new BufferedReader(fileReader);

        crimeInfo = bufferedReader.readLine();
        crimeInfo = bufferedReader.readLine();

        do {
            CityCrime crime = new CityCrime(); // Default constructor
            stats = crimeInfo.split(",");
            {
                if (stats[0] != null) {
                    crime.setCity(stats[0]);
                }
                if (stats[1] != null) {
                    crime.setState(stats[1]);
                }
                if (stats[2] != null) {
                    if (Integer.parseInt(stats[2]) >= 0) {
                        crime.setPopulation(Integer.parseInt(stats[2]));
                    }
                }
                if (stats[3] != null) {
                    if (Integer.parseInt(stats[3]) >= 0) {
                        crime.setMurder(Integer.parseInt(stats[3]));
                    }
                }

                if (stats[4] != null) {
                    if (Integer.parseInt(stats[4]) >= 0) {
                        crime.setRobbery(Integer.parseInt(stats[4]));
                    }
                }

                if (stats[5] != null) {
                    if (Integer.parseInt(stats[5]) >= 0) {
                        crime.setAssault(Integer.parseInt(stats[5]));
                    }
                }

                if (stats[6] != null) {
                    if (Integer.parseInt(stats[6]) >= 0) {
                        crime.setBurglary(Integer.parseInt(stats[6]));
                    }
                }

                if (stats[7] != null) {
                    if (Integer.parseInt(stats[7]) >= 0) {
                        crime.setLarceny(Integer.parseInt(stats[7]));
                    }
                }

                if (stats[8] != null) {
                    if (Integer.parseInt(stats[8]) >= 0) {
                        crime.setMotorTheft(Integer.parseInt(stats[8]));
                    }
                }
                crime.setTotalCrimes(Integer.parseInt(stats[3]), Integer.parseInt(stats[4]), Integer.parseInt(stats[5]), Integer.parseInt(stats[6]), Integer.parseInt(stats[7]), Integer.parseInt(stats[8]));
            }
            crimes.add(crime);
            System.out.println(crime);

            crimeInfo = bufferedReader.readLine();

        } while (crimeInfo != null);

        fileReader.close();
        bufferedReader.close();

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (NumberFormatException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    }

}

/**
 * For each crime, add the city to the cities ArrayList and return the count
 */
public static int getTotalCities() {
    for(CityCrime crime : crimes) {
        cities.add(crime.getCity());
    }
    int cityCount = cities.size();
    return cityCount;
}

/**
 * Displays the menu
 * User can select which function they want to run
 * @throws IOException 
 */
@SuppressWarnings("unlikely-arg-type")
public static void showMenu() throws IOException {

    @SuppressWarnings("resource")
    Scanner menuSelect = new java.util.Scanner(System.in);
    System.out.println("1. Display all crime stats by city");
    System.out.println("2. Display all crime stats by selected city");
    System.out.println("3. Display the murder stats by selected state ");
    System.out.println("4. Display  highest crime city - all crimes");
    System.out.println("5. Display each state (in alphabetical order with the number of car thefts ");
    System.out.println("6. Write / export all cities in descending order of Robbery rate ");
    System.out.println("7. Quit");
    System.out.println("Enter option 1-7");
    
    @SuppressWarnings("resource")
    Scanner scanner = new Scanner(System.in);
    int option = Integer.parseInt(menuSelect.next());
    if(option<1 || option>7 ) {
        System.out.println("Invalid input.");
        return;
    }
    switch (option) {
    case 1:
        displayAllCityCrimeStats();
        break;
    case 2:
        System.out.println("Enter city");
        String cityOption = menuSelect.next();
        displayCrimeStatsByCity(cityOption);
        break;
    case 3:
        System.out.println("Enter state");
        String stateOption = menuSelect.next();
        displayMurdersByState(stateOption);
        break;
    case 4:
        displayHighestCrimeStats();
        break;
    case 5:
        displayStateCarThefts();
        break;
    case 6:
        writeToFile("Robbery.csv");
        break;
    case 7:
        return; 
    default:
        option = Integer.parseInt(scanner.next());
    }

}

我的 CityCrime 文件。现在一团糟,因为我不知道正确的方向是什么。我已经减少了 setStates,实际上有 52 个,所以它很长:

    public class CityCrime {


//Instance variables
private String city;
private String state;
private int population;
private int murder;
private int robbery;
private int assault;
private int burglary;
private int larceny;
private int motorTheft;
public int totalCrimes;
public static ArrayList<CityState> abbreviations = new ArrayList<CityState>();
public String fromFile = ("C:/Users/ebeck/Downloads/StateAbbreviations.csv");

public static void main(String[] args) {
    
}

public static void readAbbrevData(String fromFile) {

    File file = new File(fromFile);

    FileReader fileReader;
    BufferedReader bufferedReader;
    String abbrevInfo;
    String[] stats;

    try {
        fileReader = new FileReader(file);
        bufferedReader = new BufferedReader(fileReader);

        abbrevInfo = bufferedReader.readLine();
        abbrevInfo = bufferedReader.readLine();

        do {
            CityState abbrev = new CityState(); // Default constructor
            stats = abbrevInfo.split(",");
            {
                if (stats[0] != null) {
                    abbrev.setState(stats[0]);
                }
                if (stats[1] != null) {
                    abbrev.setAbbreviation(stats[1]);
                }
            }
            abbreviations.add(abbrev);
            System.out.println(abbrev);

            abbrevInfo = bufferedReader.readLine();

        } while (abbrevInfo != null);

        fileReader.close();
        bufferedReader.close();

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (NumberFormatException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    }

}

public String getCity() {
    return city;
}

public void setCity(String city) {
    this.city = city;
}

public String getState() {
    return state;
}

public void setState(String state) {
    if(state.equalsIgnoreCase("ALABAMA")) {
        this.state = "AL";
    }
    else if(state.equalsIgnoreCase("ALASKA")) {
        this.state = "AK";
    }
    else if(state.equalsIgnoreCase("ARIZONA")) {
        this.state = "AR";
    }
    else if(state.equalsIgnoreCase("ARKANSAS")) {
        this.state = "AR";
    }
    else if(state.equalsIgnoreCase("CALIFORNIA")) {
        this.state = "CA";
    }
    else if(state.equalsIgnoreCase("COLORADO")) {
        this.state = "CO";
    }
    else if(state.equalsIgnoreCase("CONNECTICUT")) {
        this.state = "CT";
    }
    //etc
}

public int getPopulation() {
    return population;
}


public void setPopulation(int population) {
    this.population = population;

}


public int getMurder() {
    return murder;
}
    //etc

}

我创建了一个 CityState 文件,但可能没有必要: 公共类 CityState {

private String state;
private String abbreviation;



public static void main(String[] args) {
    
}

public String getState() {
    return state;
}

public String getAbbreviation() {
    return abbreviation;
}
public void setAbbreviation(String abbreviation) {
    this.abbreviation = abbreviation;
}

public void setState(String state) {
    this.state = state;
    
}

}

我想更改设置获取状态的方式的几个原因是 1. 用于状态的 Junit 测试:

        @Test
public void testValidState() {
    CityCrime crimes = new CityCrime();
    crimes.setState(state);
    assertEquals(crimes.getState(), state);     
}

我收到了错误:

预期:

然后,如果我将其设置为“AK”,则会出现错误:

预期:但实际是:

还有 2. 看起来也不是很好,我想学习更好的方法

感谢您的帮助我很感激

编辑: 对于每个犯罪,如果 CityCrimes csv 文件中的状态与 StatesAbbreviations 文件中的状态相同,则将状态设置为 StatesAbbreviations 文件中的缩写并返回

【问题讨论】:

  • 首先,你能举一个你的第二个CSV文件的例子吗?我不明白为什么你在 setState 函数中对它进行了硬编码,而你有这个文件。
  • 第二个 CSV 文件是标头 State , Abbreviation, 然后是 Alaska , AL ... New York, NY 等。然后它说要显示缩写 csv 文件中的状态。因此,我不知道如何对其进行硬编码,或读取该 csv 文件。我在开始时选择了更简单的选项,但现在想改进我的代码并知道我还能怎么做:)
  • @Oromis ,我在最后添加了一个小评论,希望它更能说明我想要做什么
  • 与问题无关,但由于您提到您希望看到更好的方法,我建议您使用静态 Map 来获取缩写。想象一下,您想去怀俄明州,然后您必须检查它之前的 50 种可能性,而地图可以立即为您提供结果。 if(option&lt;1 || option&gt;7 ) 我会进入 switch-case 的 default 案例。另请注意,do while 将始终至少运行一次,因此如果犯罪信息为空,则无论如何您都会有 NPE,do while 根本不能用于避免 NPE(除非您再次检查循环内部)

标签: java csv arraylist


【解决方案1】:

好的,首先,我认为最好将数据从数据解析中拆分出来。 一个 class= 一个表示。这里CityCrime有两个目标:表示一个城市的犯罪数量(目标由类名表示)和解析缩写列表。所以我认为最好创建一个新类,其目标是解析你的缩写。

接下来,存在一致性问题:您有一个解析缩写 CSV 的函数,但您还有一个 if 列表,可以将状态名称“转换”为状态缩写(setState 函数)。根据我的说法,有两种方法可以做到这一点:

  • 将 State 定义为类型
  • 将状态定义为资源

作为类型的状态

这种方法的优点是受益于强类型。实际上,使用您的setState 解决方案,您将状态定义为字符串,并且您必须检查传递的值是否正确(if 的后续)。此外,由于它仍然是一个字符串,因此您无法保证 getState 返回的值格式正确(您必须信任写入 state 变量的所有函数)。
所以这里的解决方案是将 State 定义为一种类型,例如使用 Enumeration。

public enum State {
    ALABAMA("AL"),
    ALASKA("AK"),
    ARIZONA("AR"),
    ARKANSAS("AR"),
    CALIFORNIA("CA"),
    COLORADO("CO"),
    CONNECTICUT("CT");

    private String abbreviation;

    State(String abbreviation) {
        this.abbreviation = abbreviation;
    }

    public String getAbbreviation() {
        return abbreviation;
    }
}

枚举是一种特殊类型的类。确实,这不是直接可实例化的(你不能这样做new State("foo"))。此类型的可接受实例在类的开头定义为枚举(ALABAMAALASKA...)。所以State.ALABAMA 具有类型StateState.ALABAMA.getAbbrevation() 返回AL。 此解决方案仅在您的所有状态集都关闭时才有效,因此如果您有固定数量的状态。

在这种情况下,您的 state 变量将具有 State 类型,而您的 setState 函数将采用 State 变量。这是强类型,因为您不能将无效的值传递给函数(null 除外)。

如何将状态名称转换为状态变量?

枚举类有一个valueOf(String name) 函数。参数为String形式的枚举常量(ALABAMA,ALASKA)的标识符,返回值为State形式的枚举常量。如果找到任何枚举常量,则抛出 IllegalArgumentException。此解决方案效果很好,但字符串必须与标识符完全对应(包括大小写)。 如果你想做一个类似的函数,但忽略大小写,你可以使用values 函数,它返回一个包含所有状态值的数组(State[])。例如:

    public static State valueOfIgnoreCase(String name) {
        for(var state: State.values())
            if(state.name.equals(name)) // name function return the exact identifier of the state
                return state;
        return null; // Or throw IllegalArgumentException
    }

这个函数可以在State类中创建。

状态为资源

另一种解决方案是将State定义为资源,即在外部文件(或资源文件)中。优点是您可以动态添加新状态,而无需修改程序代码。这是您与解析器一起使用的解决方案。 对于此解决方案,解析器位于一个单独的类中。 您可以将所有州都放入列表中,而不是将其放入地图中:

public class AbbreviationParser {

 public static Map<String, State> parseState(InputStream stream) throws IOException { // InputStream is better than String or File, because it abstract the type of input (can works with a simple file, a network stream, a text downloaded from Internet...)
        final var map = new HashMap<String, State>();

        final var reader = new BufferedReader(new InputStreamReader(stream)); // Define a reader on the stream then bufferize it for better performance
        reader.readLine();

        String line;
        while((line = reader.readLine()) != null) { // As long as there is an unread line
            var array = line.split(",");
            if(array.length == 2) {// Important, because if your line does not contain a comma, your actual code will crash at "array[1]"
                var state = new State();
                state.setState(array[0]);
                state.setAbbreviation(array[1]);
                map.put(state.getState(), state);
            } else {
                //TODO define a behavior if the line is not valid
            }
        }

        reader.close();
        stream.close();
        return map;
 }

为文件调用此函数:

parseState(new FileInputStream(fromFile)); // FileInputStream is an implementation of InputStream for File

为了保护您的 State 免受外部实例化,您可以将 State 构造函数放在仅包范围内,并将此类和 AbbreviationParser 放在同一个包中。像这样,只有同一个包上的类(所以 AbbreviationParser)可以实例化 State,因此您可以确保不会将错误的值传递给您的 setState(State state)

public class State {

    private String state;
    private String abbreviation;

    State() {}

    public String getState() {
        return state;
    }

    public String getAbbreviation() {
        return abbreviation;
    }
    public void setAbbreviation(String abbreviation) {
        this.abbreviation = abbreviation;
    }

    public void setState(String state) {
        this.state = state;

    }

}

即使您更喜欢您所在州的第一种方法,我也建议您从我的解析器中获取灵感,用于您的第一个 CSV 解析器,并将其隔离在一个单独的类中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-08-05
    • 2014-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-16
    • 1970-01-01
    相关资源
    最近更新 更多