Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
LostMy2010Accnt
Dec 13, 2018

So I am completely stumped with what I'm doing wrong with my Builder pattern. I've been staring at it for a week and combing SO hasn't found me any explanation on why it's occuring. I'm using Apache Commons for parsing a CSV file and creating a map of maps that looks like this Map<String, Map<Enum, Interface>>. The problem occuring is that after the map is populated, when I check the objects in the map; they're all pointing to the same object.

code:
Los Angeles Kings={Standard=stats.teams.StandardTeamStats@485966cc}
Montreal Canadiens={Standard=stats.teams.StandardTeamStats@485966cc}
Calgary Flames={Standard=stats.teams.StandardTeamStats@485966cc}
New York Islanders={Standard=stats.teams.StandardTeamStats@485966cc}
Toronto Maple Leafs={Standard=stats.teams.StandardTeamStats@485966cc}
Philadelphia Flyers={Standard=stats.teams.StandardTeamStats@485966cc}
New York Rangers={Standard=stats.teams.StandardTeamStats@485966cc}
Winnipeg Jets={Standard=stats.teams.StandardTeamStats@485966cc}
Washington Capitals={Standard=stats.teams.StandardTeamStats@485966cc}
Colorado Avalanche={Standard=stats.teams.StandardTeamStats@485966cc}
Columbus Blue Jackets={Standard=stats.teams.StandardTeamStats@485966cc}
New Jersey Devils={Standard=stats.teams.StandardTeamStats@485966cc}
Buffalo Sabres={Standard=stats.teams.StandardTeamStats@485966cc}
St Louis Blues={Standard=stats.teams.StandardTeamStats@485966cc}
Dallas Stars={Standard=stats.teams.StandardTeamStats@485966cc}
Anaheim Ducks={Standard=stats.teams.StandardTeamStats@485966cc}
Ottawa Senators={Standard=stats.teams.StandardTeamStats@485966cc}
Detroit Red Wings={Standard=stats.teams.StandardTeamStats@485966cc}
Nashville Predators={Standard=stats.teams.StandardTeamStats@485966cc}
Pittsburgh Penguins={Standard=stats.teams.StandardTeamStats@485966cc}
Seattle Kraken={Standard=stats.teams.StandardTeamStats@485966cc}
Edmonton Oilers={Standard=stats.teams.StandardTeamStats@485966cc}
San Jose Sharks={Standard=stats.teams.StandardTeamStats@485966cc}
Carolina Hurricanes={Standard=stats.teams.StandardTeamStats@485966cc}
Vancouver Canucks={Standard=stats.teams.StandardTeamStats@485966cc}
Arizona Coyotes={Standard=stats.teams.StandardTeamStats@485966cc}
Minnesota Wild={Standard=stats.teams.StandardTeamStats@485966cc}
Florida Panthers={Standard=stats.teams.StandardTeamStats@485966cc}
Chicago Blackhawks={Standard=stats.teams.StandardTeamStats@485966cc}
Vegas Golden Knights={Standard=stats.teams.StandardTeamStats@485966cc}
Boston Bruins={Standard=stats.teams.StandardTeamStats@485966cc}
Tampa Bay Lightning={Standard=stats.teams.StandardTeamStats@485966cc}
So here's the Class with nested builder class:
Java code:
/**
 * Standard related team stats class.
 */
public class StandardTeamStats implements IStats {
    private String teamName;
    private int gamesPlayed;
    private BigDecimal timeOnIce;

    private int wins;
    private int losses;
    private int overtimeLosses;
    private int nonShootoutWins;

    private int points;
    private BigDecimal pointPercentage;

    /**
     * Constructor for standard related team stats.
     * 
     * @param builder object for builder design pattern
     */
    private StandardTeamStats(Builder builder) {
        teamName = builder.teamName;
        gamesPlayed = builder.gamesPlayed;
        timeOnIce = builder.timeOnIce;
        wins = builder.wins;
        losses = builder.losses;

        overtimeLosses = builder.overtimeLosses;
        nonShootoutWins = builder.nonShootoutWins;
        points = builder.points;
        pointPercentage = builder.pointPercentage;
    }

    public String getTeamName() {
        return teamName;
    }

    public int getGamesPlayed() {
        return gamesPlayed;
    }

    public BigDecimal getTimeOnIce() {
        return timeOnIce;
    }

    public int getWins() {
        return wins;
    }

    public int getLosses() {
        return losses;
    }

    public int getOvertimeLosses() {
        return overtimeLosses;
    }

    public int getNonShootoutWins() {
        return nonShootoutWins;
    }

    public int getPoints() {
        return points;
    }

    public BigDecimal getPointPercentage() {
        return pointPercentage;
    }

    /**
     * Nested class to be used in a Builder Design Pattern.
     */
    public static class Builder {
        private String teamName;
        private int gamesPlayed;
        private BigDecimal timeOnIce;

        private int wins;
        private int losses;
        private int overtimeLosses;
        private int nonShootoutWins;

        private int points;
        private BigDecimal pointPercentage;

        public Builder() {
            // Empty constructor
        }

        public Builder teamName(String argTeamName) {
            teamName = argTeamName;
            return this;
        }

        public Builder gamesPlayed(String argGamesPlayed) {
            gamesPlayed = StatConverterHelper.generateIntegerStat(argGamesPlayed);
            return this;
        }

        public Builder timeOnIce(String argTimeOnIce) {
            timeOnIce = StatConverterHelper.generateBigDecimalStat(argTimeOnIce);
            return this;
        }

        public Builder wins(String argWins) {
            wins = StatConverterHelper.generateIntegerStat(argWins);
            return this;
        }

        public Builder losses(String argLosses) {
            losses = StatConverterHelper.generateIntegerStat(argLosses);
            return this;
        }

        public Builder overtimeLosses(String argOvertimeLosses) {
            overtimeLosses = StatConverterHelper.generateIntegerStat(argOvertimeLosses);
            return this;
        }

        public Builder nonShootoutWins(String argNonShootoutWins) {
            nonShootoutWins = StatConverterHelper.generateIntegerStat(argNonShootoutWins);
            return this;
        }

        public Builder points(String argPoints) {
            points = StatConverterHelper.generateIntegerStat(argPoints);
            return this;
        }

        public Builder pointPercentage(String argPointPercentage) {
            pointPercentage = StatConverterHelper.generateBigDecimalStat(argPointPercentage);
            return this;
        }

        public StandardTeamStats build() {
            return new StandardTeamStats(this);
        }
    }
}
Here's the class that's utilizing the Commons library to parse my csv file and create the stat object:
Java code:
/**
 * Class for creating the stat objects and compiling them into a Map of Maps.
 */
public class HockeyStatConsumer {
    private static final Logger LOG = LogManager.getLogger(HockeyStatConsumer.class);

    /**
     * Private constructor to maintain static behavior.
     */
    private HockeyStatConsumer() {
        // Private constructor to protect from having it instantiated.
    }

    /**
     * Leverage Apache Commons library for parsing a CSV file and returning the
     * results in a Map with stat objects.
     * 
     * @param  argFileName file name to ascertain flow of construction process
     * @return             Map of maps with stat objects associated with unique
     *                     identifier
     * @throws IOException if file name is incorrect or non-existent
     */
    public static Map<String, Map<EStatTypes, IStats>> generateStats(String argFileName)
            throws IOException {
        Map<String, Map<EStatTypes, IStats>> teamStats = new HashMap<>();

        try (BufferedReader input = new BufferedReader(new FileReader(argFileName))) {
            CSVFormat csvRaw = CSVFormat.DEFAULT.builder()
                    .setHeader()
                    .setSkipHeaderRecord(true)
                    .setAllowMissingColumnNames(true)
                    .build();

            Map<EStatTypes, IStats> elementStats = new EnumMap<>(EStatTypes.class);

            Iterable<CSVRecord> records = csvRaw.parse(input);

            for (CSVRecord team : records) {

                elementStats.put(EStatTypes.STANDARD,
                        new StandardTeamStats.Builder().teamName(team.get("Team"))
                                .gamesPlayed(team.get("GP"))
                                .timeOnIce(team.get("TOI"))
                                .wins(team.get("W"))
                                .losses(team.get("L"))
                                .overtimeLosses(team.get("OTL"))
                                .nonShootoutWins(team.get("ROW"))
                                .points(team.get("Points"))
                                .pointPercentage(team.get("Point %"))
                                .build());

                teamStats.put(team.get("Team"), elementStats);
            }

        }
        catch (IOException e) {
            LOG.fatal("File not found, incorrect name, or not exceptable formatting.");
            e.printStackTrace();
        }
        return teamStats;
    }
}
Details:
  • I'm using an Enum for my key and the value is the stat object
  • I'm using an interface so that I can create different stat classes that will be stored in the map as well. So the goal is the first key is the team name, then in the inner map the key is the stat type and the value is the stat object
  • The CSVFormat object is also using a builder design pattern as well
  • I have a helper class being used in the StandardTeamStats class that handles a few of the stats that need to be converted to an int or BigDecimal
  • To the best of my understanding, my mistake is that I'm pointing to the builder object in all elements in the map; hence the printed results at the top of my post

I haven't updated my repo recently, but if it would help I can cut a branch with this work in it and if that would help. Let me know if there's anything else that would help with figuring this out.

Also, critique on code and design is welcomed. This is early in my personal project so if I'm doing something ugly, let me know so I can work it. Not looking for you to solve it, just call it out and I'll gladly spend the time to learn how to do it.

Thank you.

LostMy2010Accnt fucked around with this message at 12:37 on Sep 13, 2023

Adbot
ADBOT LOVES YOU

LostMy2010Accnt
Dec 13, 2018

Tesseraction posted:

On my phone but from a glance it looks like here


Java code:
teamStats.put(team.get("Team"), elementStats); }
you're assigning the elementStats Map to every entry in teamStats, which is why the printed output is the same printed object's position in memory.

I might be misunderstanding but could you either comment or pseudocode what you're trying to do there and also what you expect it to print instead of what you showed in your post?

Thank you for looking into it, I'll try to explain better what I'm doing.

So I'm trying to parse a CSV file and then from it create a Map that consists of a key for the team name and then a value of another map. In the inner map I want to use an enum to classify the type or name of the object and then the value is the object itself. for the loop here's what I'm trying to do:

code:
create the map of maps

create a BufferedReader Object with the csv file as the input
	create CSVFormat object and set the fields to build
	
	create an EnumMap to store all the stat objects I intend to populate it with

	create the iterator based on the CSVRecord

	for every row (team) in the CSVRecord that I'm itterating over
		add Enum stat as name and key as object
		put that map into the teamStats with team name as key

return the populated mape of maps
When I debug it, I see the team object I'm trying to create consists of the last team/element/entry that was processed. So I'd have 32 entries all with the same StanderTeamStats object in them.

Again, apologies if I'm not describing it well; I'm terrible with programing venacular, it's a weak spot of mine that I'm really tryin to improve upon.

LostMy2010Accnt
Dec 13, 2018

VegasGoat posted:

Yeah probably just needs to move the elementStats = new… inside the records loop so each team has its own elementStats.

Thank you. I'll give that a try and report back what happens.

LostMy2010Accnt
Dec 13, 2018

Tesseraction posted:

Okay looking into this a little closer, first thing I'm gonna pull up isn't the solution, but


Builders have mandatory fields and non-mandatory; at the very least your team has to have a name or some other unique identifier, after all this is what's going to be the key to your Map.

As for the rest... if you could upload a sanitised version of your code/CSV file to your source repo it would help me at the very least dissect where the issue is. At the very least it'll let me run through the debugger to see where things start going south.

Thank you. I'm having issues with my project because I'm trying to learn Maven along the way so it's going to take me a little to get it going. I'll post back when I get it it squared away.

LostMy2010Accnt
Dec 13, 2018

Tesseraction posted:

...
As for the rest... if you could upload a sanitised version of your code/CSV file to your source repo it would help me at the very least dissect where the issue is. At the very least it'll let me run through the debugger to see where things start going south.

Okay I made the correction to my builder class and updated my branch with the changes.

https://github.com/sDriskell/HockeyStats/tree/build_csv_consumer

There's a csv file inside HockeyStatVisualizer that I'm using. It's only 32 rows but does have about 71 columns. Currently only working with the first 10 columns.

Still having issues with my maven pom file; it's new to me but I've not had luck building it, just importing dependencies for the project. Also working on why log4j2 isn't logging either; I think it might have something to do with where I have it in the project directory. If any of these are causing you issues, then don't sweat looking into this. Appreciate it, thanks.

LostMy2010Accnt
Dec 13, 2018

Pedestrian Xing posted:

Your log4j2.xml should be under src/main/resources. What errors are you seeing with the pom?

Ah I just had in source, thank you for catching that. Not seeing errors with pom more that: 1) I'm just trying to learn how to use it; 2) Eclipse is acting really odd whenever I try to modify it at times (Weird spacing, freezes, etc.).

Tesseraction posted:

Okay I've download your git repo and it seems rather different from the code you were asking a question about. I don't see a Map anywhere here? This code seems to work fine?

Sorry it should be the build_csv_consumer branch that I've commited the current work I have.

Please don't feel obligated to go too deep in this. I think this is really more of a byproduct of me trying to make multiple changes at a single time with this project. I hate wasting time so don't stress over this one.


franks posted:

I know it’s easy to try and do a lot at once, but I’d highly recommend trying to change one thing at a time so you can more easily identify where any problems are. I’m not a superdev by any means but that has been really helpful for my debugging.

Yeah, this was a learning lesson in that this week.

Thank you everyone for jumping in and helping. I think my lesson today is to not make so many changes concurrently so I'll take the L on that.

Adbot
ADBOT LOVES YOU

LostMy2010Accnt
Dec 13, 2018

Tesseraction posted:

Looking at the code (your Maven config seemed correct FWIW) it's doing what you want it to, just with the issue of you assigning the full Map<EStatTypes, IStats> elementStats to every Team entry in the Map<String, Map<EStatTypes, IStats>> teamStats.

Let's pick three teams, I'll take the first three in your CSV, the Anaheim Ducks, the Arizona Coyotes, and the Boston Bruins. As it goes over these three CSVRecord team elements:

elementStats creates a new entry for the Anaheim Ducks, with the stats you specified, effectively a key,value of ["Anaheim Ducks", StandardTeamStatsObj@1]

It then adds this to teamStats as ["Anaheim Ducks", [ ["Anaheim Ducks", StandardTeamStatsObj@1] ] ]

next it adds an entry of ["Arizona Coyotes",StandardTeamStatsObj@2] to elementStats, this now makes it [ ["Anaheim Ducks", StandardStatsObj@1] , ["Arizona Coyotes",StandardStatsObj@2] ]
now you're adding that to teamStats as ["Arizona Coyotes", [ ["Anaheim Ducks", StandardStatsObj@1] , ["Arizona Coyotes",StandardStatsObj@2] ] ]

And because Java passes by reference, the entry for Anaheim Ducks has now changed to ["Anaheim Ducks", [ ["Anaheim Ducks", StandardStatsObj@1] , ["Arizona Coyotes",StandardStatsObj@2] ] ]

So lastly the Boston Bruins will be added to teamStats as ["Boston Bruins", [ ["Anaheim Ducks", StandardStatsObj@1] , ["Arizona Coyotes",StandardStatsObj@2] , ["Boston Bruins",StandardStatsObj@3] ] ]

I hope this clarifies why your code isn't doing what you're expecting? I guess we can simplify this a bit: for the three loops, to save a bit of time I'm shortening the teams to AD, AC and BB:

loop 1 elementStats adds ["AD",STS@1]
elementStats is an object we will call object ES@1
loop 1 teamStats adds ["AD",ES@1]

loop 2 elementStats adds ["AC",STS@2]
elementStats is the same object, ES@1
loop 2 teamStats adds ["AC", ES@1]

loop 3 elementStats adds ["BB",STS@3]
elementStats is the same object, ES@1
loop 2 teamStats adds ["BB", ES@1]

Does that make sense?

poo poo. Okay I see what you're saying. Thank you for taking the time to look into this and explaining what I was missing. Much appreciated!


Pedestrian Xing posted:

Turn off any kind of auto-import or sync your IDE might be doing on pom.xml modification and trigger it manually when you make a change.

Oh that makes sense. Thank you.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply