Shuffling groups of elements in an arraylist - java

I am working on a group generator and currently I am making an ArrayList from this txt file.
So that, the ArrayList is in the form of [PedroA, Brazil, Male, 10G, Saadia...]
I want to shuffle 4 elements at a time, to randomize this arraylist.
I am storing the info in
ArrayList<String> studentInfo = info.readEachWord(className);

This is very hard to do. It's possible, of course, but difficult.
It is being made difficult because what you want to do is bizarre.
The normal way to do this would be to:
Make a class representing a single entry, let's call it class Person.
Read this data by parsing each line into a single Person instance, and add them all to a list.
Just call Collections.shuffle(list); to shuffle them.
If we have the above, we could do what you want, by then converting your List<Person> back into a List<String>. In many ways this is the simplest way to do the task you ask for, but then you start wondering why you want this data in the form of a list of strings in the first place.
enum Gender {
MALE, FEMALE, OTHER;
public static Gender parse(String in) {
switch (in.toLowerCase()) {
case "male": return MALE;
case "female": return FEMALE;
default: return OTHER;
}
}
class Person {
String name;
String location;
Gender gender;
[some type that properly represents whatever 10G and 10W means];
public static Person readLine(String line) {
String[] parts = line.split("\\s+", 4);
Person p = new Person();
p.name = parts[0];
p.location = parts[1];
p.gender = Gender.parse(parts[2]);
...;
return p;
}
}
you get the idea.

Related

Java 9 processing list of objects with nullable fields into another list

I have a list of objects like
#Getter
#Setter
public class Person {
private String name;
private boolean value_bool;
private String value_string;
private Integer value_integer;
private String value_text;
}
The problem is, only ONE of these fields (except for name) is actually initiated (e.g. non-null) for one Person, every other one is null, so for 4 of these values there are 4 Person instances each having a name and one of the values. How do I transform this list of Person objects into a list of value objects, preferably using streamAPI?
The boolean value_bool cannot be null.
To merge this data, first you'd want to group by name, and then collapse a List<Person> (which would represent the separate Person objects all with the same name) into a single merged Person.
To group:
Map<String, List<Person>> grouped = persons.stream()
.collect(Collectors.groupingBy(Person::getName));
Merging a List<Person> into a single Person is not particularly suitable to stream-based code. I would just make a method, it's a little complicated as you have lots of non-obvious choices to make. For example, let's say you have 2 with value_bool is false, and 1 with true. And 2 Person objects both have a non-null value_string, and they aren't the same string; which one wins, or should an exception be thrown, or should the strings be concatenated?
The fact that such questions exist, and have no obvious answers, should strongly indicate that there is no simple one-liner that could possibly do this.
Thus, something like:
public class Person {
// ....
public static Person merge(#NonNull Collection<? extends Person> persons) {
if (persons.isEmpty()) throw new IllegalArgumentException(
"Cannot merge an empty list of persons");
if (persons.size() == 1) return persons.iterator().next();
Person out = null;
for (Person person : persons) {
if (out == null) {
out = person;
continue;
}
// If any part-person is 'valueBool', the output is also.
if (person.isValueBool()) out.setValueBool(true);
// For the rest, the 'last' non-null value 'wins':
if (person.getValueString() != null) out.setValueString(person.getValueString());
// etc
}
return out;
}
}
armed with that method:
persons.stream()
.collect(Collectors.groupingBy(Person::getName))
.values()
.stream()
.map(Person::merge)
.collect(Collectors.toList());
Where that last line can be toList(); if you have JDK16.

Parsing file and sort lines by property

So basically someone gave me a text file for me to read through Java and I have to print out certain parts of it.
So what I did was I put all of the text file into a String and between every word there's a ":". So i split all of the text with ":" using split function. At first every line looks like this
firstName:Surname:Age:Country
firstName:Surname:Age:Country
firstName:Surname:Age:Country
firstName:Surname:Age:Country
firstName:Surname:Age:Country
firstName:Surname:Age:Country
firstName:Surname:Age:Country
firstName:Surname:Age:Country
After it would be the same thing without the colons.
So now if I say all[0], I would get all the firstNames only.
What I'm trying to get is get the top 3 highest ages but I do not know how to do that.
Explanation
Suppose you have a file with lines like
John:Doe:20:USA
Jane:Doe:35:Germany
Robert:Moe:14:Japan
Larry:Loe:25:China
Richard:Roe:27:India
and you want the 3 lines with the highest age, that would be
Jane:Doe:35:Germany
Richard:Roe:27:India
Larry:Loe:25:China
The procedure is straightforward. First read all lines, split by : and parse the data into a wrapper class like Person. Collect them into some collection like List<Person> and sort them using a Comparator that compares the age. Alternatively you could let Person implement Comparable and then use their natural order.
If efficiency matters you can also do partial sort since you are only interested in the top 3 hits. For this you could use a PriorityQueue, insert all elements and call poll three times.
Solution
First the Person wrapper class
public class Person {
private String mFirstName;
private String mSurname;
private int mAge;
private String mCountry;
public Person(String firstName, String surname, int age, String country) {
this.mFirstName = firstName;
this.mSurname = surname;
this.mAge = age;
this.mCountry = country;
}
// TODO Some getters
public String toString() {
return this.mFirstName + ":" + this.mSurname
+ ":" + this.mAge + ":" + this.mCountry;
}
public static Person parse(String[] data) {
String firstName = data[0];
String surname = data[1];
int age = Integer.parseInt(data[2]);
String country = data[3];
return new Person(firstName, surname, age, country);
}
}
Next we read all lines, split the data and parse them into Person. After that we sort and limit the result to 3. Finally we collect to a List and print the results.
Path file = Paths.get(...);
Pattern separator = Pattern.compile(":");
List<Person> persons = Files.lines(file) // Stream<String>
.map(separator::splitAsStream) // Stream<String[]>
.map(Person::parse) // Stream<Person>
.sorted(Comparator.comparingInt(Person::getAge).reversed())
.limit(3)
.collect(Collectors.toList());
persons.forEach(System.out::println);
Or if you want to use the PriorityQueue as suggested, which will improve runtime:
Path file = Paths.get(...);
Pattern separator = Pattern.compile(":");
PriorityQueue<Person> personQueue = Files.lines(file)
.map(separator::splitAsStream)
.map(Person::parse)
.collect(Collectors.toCollection(() -> {
return new PriorityQueue<>(
Comparator.comparingInt(Person::getAge).reversed());
}));
List<Person> persons = new ArrayList<>(3);
persons.add(personQueue.poll());
persons.add(personQueue.poll());
persons.add(personQueue.poll());
persons.forEach(System.out::println);

Finding combinations of a HashSet of Strings

I have a HashMap<String, HashSet>. The String stores the name of the person, and the HashSet stores the list of people that are friends with the person.
KEY<STRING> VALUE<HASHSET>
Dave Steve
Steve Dave
Bob Dalton
Dalton Bob, Sue
Anne Sue
Sue Dalton, Anne
In the above data, Dave is friends with Steve (line 1 and 2). From line 4, Dalton is friends with Bob and Sue. However, Bob and Sue are not necessarily friends. The program needs to input Bob and Sue as friends. In other words, Bob should be added to Sue's friend list and Sue should be added to Bob's friends list. However, Dalton's friends list may have an infinite amount of people. I am also not allowed to store the friend list data into an Array or an ArrayList.
One solution I was considering (but haven't tried) was to edit my read(String name1, String name2) method. (Note: In my runner class, whenever this method is called, it is called as read(name1, name2) and read(name2, name1)) In short, this method reads in two friendships and adds in the friendship into the map. In the else block (if name1 is already a key in the HashMap), I was thinking to add in code to take the existing friendlist (which will only have one value) of name1 and call read again.
Here's the read method, if you need it
private Map<String, Set> friends;
// Adds two friends, name1 and name2, to the HashMap of friendships
public void read(String name1, String name2)
{
// Temporary HashSet in case a person has more than one friend
Set<String> strSet = new HashSet<String>();
if (!friends.containsKey(name1))
{
strSet.add(name2);
friends.put(name1, strSet);
}
else
{
strSet.clear();
// Set strSet to the current friendlist of name1
strSet = friends.get(name1);
strSet.add(name2);
// Make a new entry in the HashMap with name1 and the updated friend list
friends.put(name1, strSet);
}
}
Another solution (going off of the title of this thread) is to find all the possible combinations of the friendlist. e.g. if Dalton has Bob, Sue, and Dave in his friend list, I could have a method that finds all possible combinations of two way friendships (remember, order doesn't matter):
Bob Sue
Bob Dave
Sue Dave
However, I don't know how to code this. Any suggestions?
The second solution you described is equivalent to a disjoint-set data structure. Your friends end up being in sets, where everyone in each set is friends with everyone else in that set and no one else.
The tricky part of implementing this data structure is merging two sets when you discover that two people in different sets are friends.
This is a naive implementation:
public class DisjointFriendSet {
private final Map<String, Set<String>> personToFriends = new HashMap<>();
/**
* Includes the person themselves in their group of friends.
*
* If no friendships have been registered for this person, then returns a set
* containing just themselves.
*
* #param person
* #return
*/
public Set<String> getFriends(String person) {
if(personToFriends.containsKey(person)) {
return personToFriends.get(person);
} else {
final Set<String> result = new HashSet<>();
result.add(person);
return result;
}
}
public void addFriendship(String person1, String person2) {
final Set<String> friends1 = getFriends(person1);
final Set<String> friends2 = getFriends(person2);
if(friends1 == friends2) {
return;
} else {
personToFriends.put(person1, friends1);
friends1.addAll(friends2);
for(String person: friends2) {
personToFriends.put(person, friends1);
}
}
}
/**
*
* #return All unique friendship groups
*/
public Collection<Set<String>> getAllFriendshipGroups() {
return new HashSet<>(personToFriends.values());
}
public static void main(String[] args) {
final DisjointFriendSet disjointFriendSet = new DisjointFriendSet();
disjointFriendSet.addFriendship("Alice","Beowulf");
disjointFriendSet.addFriendship("Charity","Donald");
disjointFriendSet.addFriendship("Eduardo","Frank");
disjointFriendSet.addFriendship("Grendel","Harriet");
System.out.println("Friendship groups: "+disjointFriendSet.getAllFriendshipGroups());
System.out.println("Adding friendship between Grendel and Beowulf");
disjointFriendSet.addFriendship("Grendel","Beowulf");
System.out.println("Friendship groups: "+disjointFriendSet.getAllFriendshipGroups());
System.out.println();
for(String person: new String[]{"Alice","Beowulf","Charity","Donald","Eduardo","Frank","Grendel","Harriet","Zod"}) {
System.out.println(person+"'s friends: "+disjointFriendSet.getFriends(person));
}
}
}
Here's a pseudo code approach (I can look up the Java later)
Assume at each addition that all friends of friends are properly matched.
Take the two new inputs, and create a temporary collection of all of their friends as well as the input values.
For every value in the temporary collection, add every other value as a friend. (A set should only maintain unique values, but you can explicitly check if need be).
This may not be the most efficient solution (at every step, half of the additions would be duplicates), but it should be a starting point.
Func (friend1, friend2)
tempSet = masterSet(friend1).Hashset
UNION
masterSet(friend2).Hashset
UNION
friend1, friend2
foreach (friend in tempSet)
foreach(otherFriend in tempSet - friend)
friend.AddFriend(otherFriend)
otherFriend.AddFriend(friend)

Which collections to use?

Suppose I want to store phone numbers of persons. Which kind of collection should I use for key value pairs? And it should be helpful for searching. The name may get repeated, so there may be the same name having different phone numbers.
In case you want to use key value pair. Good choice is to use Map instead of collection.
So what should that map store ?
As far it goes for key. First thing you want to assure is that your key is unique to avoid collisions.
class Person {
long uniqueID;
String name;
String lastname;
}
So we will use the uniqueID of Person for key.
What about value ?
In this case is harder. As the single Person can have many phone numbers. But for simple task lest assume that a person can have only one phone number. Then what you look is
class PhoneNumberRegistry {
Map<Long,String> phoneRegistry = new HashMap<>();
}
Where the long is taken from person. When you deal with Maps, you should implement the hashCode and equals methods.
Then your registry could look like
class PhoneNumberRegistry {
Map<Person,String> phoneRegistry = new HashMap<>();
}
In case when you want to store more then one number for person, you will need to change the type of value in the map.
You can use Set<String> to store multiple numbers that will not duplicate. But to have full control you should introduce new type that not only store the number but also what king of that number is.
class PhoneNumberRegistry {
Map<Person,HashSet<String>> phoneRegistry = new HashMap<>();
}
But then you will have to solve various problems like, what phone number should i return ?
Your problem has different solutions. For example, I'll go with a LIST: List<Person>, where Person is a class like this:
public class Person{
private String name;
private List<String> phoneNumbers;
// ...
}
For collections searching/filtering I suggest Guava Collections2.filter method.
You should use this:
Hashtable<String, ArrayList<String>> addressbook = new Hashtable<>();
ArrayList<String> persons = new ArrayList<String>()
persons.add("Tom Butterfly");
persons.add("Maria Wanderlust");
addressbook.put("+0490301234567", persons);
addressbook.put("+0490301234560", persons);
Hashtable are save to not have empty elements, the ArrayList is fast in collect small elements. Know that multiple persons with different names may have same numbers.
Know that 2 persons can have the same number and the same Name!
String name = "Tom Butterfly";
String[] array = addressbook.keySet().toArray(new String[] {});
int firstElement = Collections.binarySearch(Arrays.asList(array),
name, new Comparator<String>() {
#Override
public int compare(String top, String bottom) {
if (addressbook.get(top).contains(bottom)) {
return 0;
}
return -1;
}
});
System.out.println("Number is " + array[firstElement]);
Maybe
List<Pair<String, String> (for one number per person)
or
List<Pair<String, String[]> (for multiple numbers per person)
will fit your needs.

Comparing and deleting similar strings within a vector

JAVA: First off, Thanks so much for taking the time to look at my question; your help is EXTREMELY appreciated!
So, the problem is i have a Vector of Objects in Java and each object has a name(String). But, i have tons of objects that are repeated, and the ones that are repeated are always directly after the Object they repeat. Also the number of repeats ranges from 1-10(So frustrating) its completely random.
How would i go about deleting the repeats, I thought about comparing each objects name with the next in the vector and deleting all of the ones that match but that gave me tons of problems. Thank you SO much for your help in advance!
-Dylan
EDIT: Just to make sure you understand the kind of repetition i'm talking about ive added this.
Vector
---Object1(String name = "hi") --> Remove This one.
---Object2(String name = "hi")
---Object3(string name = "bob")
End Vector
Edit 2: add Code
public class Vector
{
public static void main(String args[])
{
Person person1 = new Person("jane");
Person person2 = new Person("jane");
Person person3 = new Person("bob");
Person person4 = new Person("shelly");
Vector<Person> vectorObject = new Vector<Person>
vectorObject.add(person1);
vectorObject.add(person2);
vectorObject.add(person3);
vectorObject.add(person4);
}
}
class Person
{
String name = null;
String bDay = null;
String color = null;
public Person(String name)
{
this.name = name;
}
}
It seems you should use a different data structure.
You may want to use a Set instead of a Vector. Sets do not contain duplicate elements. You've to override equals(Object) method.
Or use a Map with the name property as key value and store the corresponding Person object as value.
In both cases you prevent duplicates rather then deleting them afterwards.
Person person1 = new Person("jane");
Person person2 = new Person("jane");
Person person3 = new Person("bob");
Person person4 = new Person("shelly");
Map<String, Person> nameToPerson = new HashMap<>();
nameToPerson.add(person1.name, person1);
nameToPerson.add(person2.name, person2);
nameToPerson.add(person3.name, person3);
nameToPerson.add(person4.name, person4);
Collection<Person> noDuplicatesHere = map.values();
Well, I don't know which language are you using, so I will give you an algorithm in JavaScript:
var newvector=new Array();
var lastName;
for(var i=0;i<vector.length;i++){
if(!lastName || vector[i].name!=lastName){
lastName=vector[i].name;
newvector.push(vector[i]);
}
}
The problem is that this way a new vector is created, and if the original one is huge maybe you will have memory problems.
Here is your first problem:
class Person
{
String name = null;
String bDay = null;
String color = null;
public Person(String name)
{
name = this.name;
}
}
it should be:
class Person
{
String name = null;
String bDay = null;
String color = null;
public Person(String name)
{
this.name = name;
}
}
Here is more info on the this keyword:
http://docs.oracle.com/javase/tutorial/java/javaOO/thiskey.html
Your second problem is: I'm guessing you are trying to create a Vector, java.util.Vector to be exact. If you create a new instance of Vector inside of a class called vector, it will create a new instance of itself, not of java.util.Vector. You can either rename the class or you can just do:
java.util.Vector<Person> vector = new java.util.Vector<Person>();
if you want to compare 2 string values use:
String name = "John";
String name2 = "Joe";
if(name.equalsIgnoreCase(name2))
System.out.println("They match!");
You can also just use equals() if you want an exact match.
Hope this helped!

Categories

Resources