Reputation: 1032
Imagine we are pulling data about people and their favourite foods.
The data would come to us in the format: "Name, FavFood1, FavFood2..FavFoodn".
e.g. "James, Beans, Chicken".
Notice how we do not know how many foods a person will favour.
From this data we create an instance of a Person object which captures the person's name and favourite foods.
After we have pulled data on every person, we want to create a spreadsheet whose columns would be: Name|Potato|Chicken|Beans|Curry
etc.
All of the values to the right of the person's name will be simple boolean values representing whether or not that food was one of the person's favourites.
The problem is: we do not know in advance; all the foods that someone could possibly favour, and as such cannot just set up boolean instance variables in the Person class.
I've given this some thought, implementing sets,hash-sets and hash-maps, however every solution I think of ends up being horribly inelegant and so I've turned to the genius of stackoverflow for help on this one.
My question is: What design pattern / approach can I use to cleanly achieve the outcome I desire?
Whilst this is a language-agnostic question I am programming this in Java, so if there's anything in the Java API or elsewhere built for this, do let me know.
Thanks in advance!
Upvotes: 0
Views: 118
Reputation: 24879
What is so inelegant about this pseudocode?
Set<String> allFoods = new TreeSet<String>();
List<Person> allPersons = new ArrayList<Person>();
while (hasMorePersons()) {
Person person = getNextPerson();
allPersons.add(person);
allFoods.addAll(person.getFoods());
}
spreadSheet.writeHeader("Name", allFoods);
for (Person person : allPersons) {
spreadSheet.writeName(person.getName());
for (String food : allFoods) {
// assume getFoods() return a Set<String>,
// not necessarily ordered (could be a HashSet)
boolean yourBooleanHere = person.getFoods().contains(food);
spreadSheet.writeBoolean(yourBooleanHere);
}
spreadSheet.nextLine();
}
If you need a table of booleans or whatever else, you can easily store them anywhere you want during the second loop.
Note: TreeSet orders foods according to the natural order (that is, alphabetically). To output them in the order they are encountered, use a LinkedHashSet
instead.
Upvotes: 1
Reputation: 62769
First of all, I highly recommend that whatever you do it in a separate class with methods like addFavoriteFood(String food) and boolean isFavoriteFood(String food) getFavorites(String food).
Personally I think the implementation of this class should contain both an instance HashSet (to hold the foods this person likes) and a SortedSet that is common to all the foods that can contain a list of ALL foods. (See notes at end)
Add would add it to both sets, getFavorites would return those in the first Hash set.
Hmm, it may also need a static getAllFavorites() method to return the SortedSet
Since your FavoiteFoods class knows the master list AND the person's favorites, you could even have it do most of the work by having a getFormattedRow() and static getFormattedHeaderRow() method. then your implementaiton is just:
System.out.println(FavoriteFoods.getFormattedHeaderRow());
for(Person person:people)
System.out.println(person.favoriteFood.getFormattedRow());
Again, the best thing here is that you can just use the Simplest Thing That Could Possibly Work for your implementation and re-do it later if need be since, being isolated in another class, it doesn't infect all your code with nasty implementation-specific sets, classes, booleans, etc.
Notes about the master list: This master list could naively be implemented as a Static but that's a bad idea--optimally the same masterList SortedSet would be passed into each instance on construction. Also since it is shared among all instances and is mutable it brings in issues if your solution is threaded!
Upvotes: 1
Reputation: 8139
Try this. It generates data in csv form.
class Person {
final String name;
final Set<String> foods;
Person(String name, Set<String> foods) {
this.name = name;
this.foods = foods;
}
Stream<Boolean> getBooleans(List<String> foods) {
return foods.stream().map(food -> this.foods.contains(food));
}
@Override
public String toString() {
return "Person(" + name + ", " + foods +")";
}
}
class Test {
public static void main(String[] args) throws Exception {
List<String> data = Arrays.asList(
"James, Beans, Chicken",
"Emily, Potato, Curry",
"Clara, Beans, Curry"
);
List<String> foodNames = Arrays.asList(
"Potato", "Chicken", "Beans", "Curry"
);
Stream<Person> persons = data.stream().map(d -> {
String[] split = d.split(",");
for(int i = 0; i < split.length; i++) {
split[i] = split[i].trim();
}
String name = split[0];
Set<String> foods = Stream.of(split).skip(1).collect(Collectors.toSet());
return new Person(name, foods);
});
Stream<String> csvData = persons.map(p ->
p.name + ", " + p.getBooleans(foodNames)
.map(b -> b.toString())
.collect(Collectors.joining(", "))
);
csvData.forEach(System.out::println);
}
}
Upvotes: 1