I have a problem related to "dynamic ArrayLists". I have a List that contains usernames and their data. I want for every distinct username to create a single list that contains all data of this user. For example, I have an arraylist (username,tweet) that has: lefteris,"Plays ball", Kostas, "Plays basketball", lefteris, "Nice weather". And I want after that to create two lists. One list with kostas and his tweets and another with lefteris and its tweets (2 tweets). The parent arraylist may have 20 distinct usernames or more. How can I do that ?
I recommend you to use hashmap or hashset instead because if you need to store something in pairs, hashing is a perfect solution......
I'd go with the following data structure:
HashMap<String, ArrayList<String>>
Then you could manipulate a "dynamic" list of properties keyed to each name, if the properties are single items:
Lefteris->("Plays ball", "Nice weather",...)
Kostas->("Plays basketball",...)
If the properties are key-value pairs, do:
HashMap<String, HashMap<String, Object>>
Data looking like:
Lefteris->(Sport->"Plays ball", Weather->"Nice",...)
Kostas->(Sport->"basketball",...)
Since you parse the items from a file, you can do the following.
Create a map that contains the tweets associated to a particular username
Map<String,List<String>> userTweets = new HashMap<String,List<String>>();
Then, have a method to associate a tweet to certain user, verifying that it is already added in the map and adding it if it isn't.
public void addTweetToUser(String user, String tweet) {
if(userTweets.containsKey(user))
userTweets.get(user).add(tweet);
else {
List<String> newUserTweets = new LinkedList<String>();
newUserTweets.add(tweet);
userTweets.put(user, newUserTweets);
}
}
As a plus, you can improve this by creating an object UserTweet that contains:
public class UserTweet {
private String user;
private String tweet;
//Constructor, Setters & Getters or all of them
}
Then your addTweetToUser method can have an UserTweet parameter instead.
When you want to know the tweets for a certain user, you just obtain the corresponding list from the userTweets map. I alsomethods to remove tweets and/or remove users, just in case.
Several libraries add excellent collection-processing functionality to Java along the lines of what functional languages provide. One such library is Google Guava. Guava provides a MultiMap suitable for grouping things the way you want. There are also many utility methods, like MultiMaps.index(), which collects items from a list into a map by applying some function to the elements of the list to calculate a key. With such support, it only takes a few lines of code and one Function implementation (a closure in any other language) to solve your problem:
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import java.util.Arrays;
import java.util.List;
public class Tweets {
public static final int NAME = 0;
public static final int TWEET = 1;
public static void main(String[] args) {
List<String> namesAndTweets = Arrays.asList(
"lefteris", "Plays ball",
"Kostas", "Plays basketball",
"lefteris", "Nice weather");
List<List<String>> nameTweetPairs =
Lists.partition(namesAndTweets, 2);
Multimap<String, List<String>> namesAndTweetsByName =
Multimaps.index(nameTweetPairs, get(NAME));
Multimap<String, String> tweetsByName =
Multimaps.transformValues(namesAndTweetsByName, get(TWEET));
System.out.println(tweetsByName);
}
private static Function<List<String>, String> get(final int n) {
return new Function<List<String>, String>() {
#Override
public String apply(List<String> nameAndTweet) {
return nameAndTweet.get(n);
}
};
}
}
Outputs:
{lefteris=[Plays ball, Nice weather], Kostas=[Plays basketball]}
Update: To explain the code a bit more, there are three basic steps:
Take the list that has names and tweets all mixed together and use Lists.partition() to break it into pairs of (name, tweet).
Use MultiMaps.index() to build a MultiMap from the pairs, taking the name as the map key. This gives you a map where map keys are names and map values are the (name, tweet) pairs.
Use MultiMaps.transformValues() to reduce the map values from (name, tweet) pairs to just the tweets.
P.S. does anyone know if there's a built-in Function that does what my get() does? It seems like a useful Function that should be provided, but I can't find it anywhere.
Related
I am wondering how to build a nested Map and then retrieve the nested key:value pairs. This is how I created the map to be nested.
//Create List of Nested HashMaps
List<Map> nestedMap = new ArrayList<>();
nestedMap.add(building3Map);
nestedMap.add(building31Map);
nestedMap.add(buildingHFTFMap);
System.out.println("The nested map is: " + nestedMap);
This is the system output for the following code:
The nested map is: [{buildingname=[Building 3], buildingid=[3]}, {buildingname=[Building 31], buildingid=[1]}, {buildingname=[HFTF], buildingid=[4]}]
This is correct as I want a list of maps. But the next step is what is confusing to me. When I try to build the outer layer Map as below:
HashMap<String, List<Map>> queryMap = new HashMap<>();
queryMap.put("buildings", nestedMap);
System.out.println(queryMap.get("buildings.buildingid"));
I get a system output of null when attempting the .get("buildings.buildingid") method. Ideally, I need the output to look like this:
[[3, 1, 4]]
Where it returns all values with a key of "buildings.buildingid" in an array. I am new to coding so please let me know if I'm making any fundamental errors and how I can create a nested Map where I can access the inner layer or if there is another method I should be using.
I think you are making it way too complicated than it should be. you can store your data in a simple map where the ids are the keys for example and the names are the values. So you only need to read the keys or the values from the map to get your result.
Map<Integer, String> myMap = new HashMap<>();
myMap.put(3, "Building 3");
myMap.put(31, "Building 31");
myMap.put(4, "HFTF");
System.out.println(myMap.keySet());
System.out.println(myMap.values());
However, Java is an object-oriented language. If it makes sense for your use case you might want to create a custom class "Building" instead of frantically trying to store your data in nested data structures. See below for an example of how it might look using a custom class:
import java.util.ArrayList;
import java.util.List;
public class Example {
public static void main(String args[]) {
List<Building> buildings = new ArrayList<>();
buildings.add(new Building(3, "Building 3"));
buildings.add(new Building(31, "Building 31"));
buildings.add(new Building(4, "HFTF"));
List<Integer> buildingIds = new ArrayList<>();
buildings.forEach(building -> buildingIds.add(building.getBuildingId()));
List<String> buildingNames = new ArrayList<>();
buildings.forEach(building -> buildingNames.add(building.getBuildingName()));
System.out.println(buildingIds);
System.out.println(buildingNames);
}
public static class Building {
int buildingId;
String buildingName;
public Building(final int buildingId, final String buildingName) {
this.buildingId = buildingId;
this.buildingName = buildingName;
}
public int getBuildingId() {
return buildingId;
}
public void setBuildingId(final int buildingId) {
this.buildingId = buildingId;
}
public String getBuildingName() {
return buildingName;
}
public void setBuildingName(final String buildingName) {
this.buildingName = buildingName;
}
}
}
queryMap.get("buildings.buildingid") returns null, because queryMap only contains a value under the key
buildings. A HashMap can only access the value using the same key it was stored under. The key and the value is not processed in any further way.
A simple alternative could be
queryMap.get("buildings").stream() // Create a Stream<Map>
.map(building -> building.get("buildingid")) // Create Stream<String> by extracting the buildingid values.
.collect(Collectors.toList()); // Collect Stream into a List<String> which contains the buildingid's
If you don't like this solution you could take a deeper look into property expressions, and write your own map implementation that can resolve these expressions. But it will be a lot of work to get it working correctly...
I'm attempting to consolidate multiple unnecessary web requests into a map, with the key connected to a location's ID, and the value being a list of products at that location.
The idea is to reduce the amount of requests to my flask server by creating a single request for each location, with a list of required products mapped to it.
I have tried to find others who has faced a similar problem using Java 8's streaming functionality, but I cannot find anyone who is trying to append to a list within a map.
Example;
public class Product {
public Integer productNumber();
public Integer locationNumber();
}
List<Product> products = ... (imagine many products in this list)
Map<Integer, List<Integer>> results = products.stream()
.collect(Collectors.toMap(p -> p.locationNumber, p -> Arrays.asList(p.productNumber));
Also, the second p parameter cannot access the current product in stream.
Because of this, I have been unable to test if I can append to a List when the location number matches a pre-existing list. I don't believe I can use Arrays.asList(), as I believe its immutable.
At the end, the map should have many product numbers in a list per location. Is it possible to append Integers to a pre-existing list within a map?
You may do it like so,
Map<Integer, List<Integer>> res = products.stream()
.collect(Collectors.groupingBy(Product::locationNumber,
Collectors.mapping(Product::productNumber, Collectors.toList())));
The java collectors API is pretty powerful and have lots of nice utility method to solve this.
public class Learn {
static class Product {
final Integer productNumber;
final Integer locationNumber;
Product(Integer productNumber, Integer locationNumber) {
this.productNumber = productNumber;
this.locationNumber = locationNumber;
}
Integer getProductNumber() {
return productNumber;
}
Integer getLocationNumber() {
return locationNumber;
}
}
public static Product of(int i, int j){
return new Product(i,j);
}
public static void main(String[] args) {
List productList = Arrays.asList(of(1,1),of(2,1),of(3,1),
of(7,2),of(8,2),of(9,2));
Map> results = productList.stream().collect(Collectors.groupingBy(Product::getLocationNumber,
Collectors.collectingAndThen(Collectors.toList(), pl->pl.stream().map(Product::getProductNumber).collect(Collectors.toList()))));
System.out.println(results);
}
}
So, what we are doing here is we are streaming the product list and grouping the stream by the location attribute but with the twist that we want to transform the collected list of products to list of product numbers.
Collectors.collectingAndThen is precisely the method for this which will let you specify a main collector toList() and a transformer function which is nothing but again a stream to map product to product numbers. IN java API doc the main collector and transformer are labeled as downstream collector and finisher.
Please go through the Collectors source code to have a complete understanding as to how all these different collectors are defined.
I am trying to create a method that, when given a key and a value, can access the map via the key and then either add or replace that specific value.
My Hash Map is created like so:
public Band(){
musicians = new HashMap<>();
}
And I can add new entries like so, with band acting as the key:
public void addMapEntry(String band, String name, String instrument, int experience){
musicians.put(band, new Musician(name, instrument, experience));
}
My new method header look like this:
public void addValue(){ }
I have tried using the put method but I can't get it work as I'd like.
I have also tried iterating through the map, but I've only ever used that method to return map objects in a list, whereas I don't want to return anything in this method. I want to be able to send it two arguments (the key and the value) and have it only replace that specific value.
I hope I have been clear in my explanation.
Java Map is single value for each key.
If you need multiple values for a single key, you should make the type a collection of the appropriate type and add your own logic for adding a new value. Your Band class should have methods to add/remove a Musician and handle the details in the private implementation.
public class Band {
private Map<String, List<Musician>> members = new HashMap<String, List<Musician>>();
public void addMusician(String key, Musician musician) {
if (this.members.containsKey(key) {
List<Musician> musicians = this.members.get(key);
if (musician != null) {
musicians.add(musician);
this.members.put(key, musicians);
}
}
}
public void removeMusician(String key, Musician musician) {
// should be clear enough from the add method.
}
}
I think the most suitable for you is to use Guava Multimap
ListMultimap<String, String> musicianMap = ArrayListMultimap.create();
Then add as many musicians to your band
musicianMap.put("Beatles", new Musician("Jhon Lennon"));
musicianMap.put("Beatles", new Musician("Paul McCartney"));
musicianMap.put("Beatles", new Musician("Ringo Starr"));
musicianMap.put("Beatles", new Musician("George Harrison"));
And you can pull them all using just key.
musicianMap.get("Beatles")
This will return a list of ["John Lenon", "Paul McCartney", "Ringo Starr"] of course these will objects of class Musician.
This question already has answers here:
Sort a Map<Key, Value> by values
(64 answers)
Closed 8 years ago.
I saw this thread sorting a List of Map<String, String> and I know mine could sound a duplicate, but it is slight differen.
My example is:
List<Map<String, String>> myList = new ArrayList<Map<String, String>>();
...
for(MyClass1 c1 : c1)
{
...
for(MyClass2 c2 : c12)
{
SimpleBindings myBindindings= new SimpleBindings();
myBindindings.put(c1.getName(), c2.getName());
myList.add(myBindindings);
}
}
...
Concretely I can have
{
(John, Mike)
(John, Jack)
(Sam, Jack)
(Gloria, Anna)
(Jane, Carla)
...
}
and would like that my list is sorted by the maps key:
{
(Gloria, Anna)
(Jane, Carla)
(John, Mike)
(John, Jack)
(Sam, Jack)
...
}
Are you sure that
List<Map<String, String>>
is the approriate data type you want?
To me it looks like you are in fact looking simplify for
TreeMap<String, String>
i.e. a sorted map key -> value?
Or do you mean to use a List<StringPair> (for that, please choose a more appropriate name than StringPair, and implement that class to your needs)? I have the impression that in lack of an obvious Pair<String, String> class in Java you have been abusing SimpleBinding as a pair class. The proper way to have pairs in Java is to implement a new class, with a proper class name - "pair" is technical, not semantic.
You could also do
List<String[]>
and implement a Comparator<String[]> for sorting. But that doesn't save you any work over implementing a NamePair class and making it comparable yourself.
You need to implement Comparator to accomplish this...
Collections.sort(myList, new Comparator<ObjectBeingCompared>() {
#Override
public int compare(ObjectBeingCompared obj1, ObjectBeingCompared obj2) {
//Of course you will want to return 1, 0, -1 based on whatever you like
//this is just a simple example
//return 1 if obj1 should be ordered first
//return 0 if obj1 and obj2 are the same
//return -1 if obj1 should be ordered after obj2
return obj1.compareTo(obj2);
}
});
The HashMap data structure is used to allow access to its elements in O(1) time.
Because it is a container of data its pool or keys can vary in time. This mean that you can not assure in long therm an order for list of maps.
In your example you match two Strings and create Pair of data called SimpleBindings.
In case of your simple example you should not use Map<String,String> data structure to represent a Pair of data.
If you SimpleBindings really consist of two string, everything you must do is only implement a Comparable in SimpleBindings class like this:
class SimpleBinding implements Comparable<SimpleBinding> {
private final String key;
private final String value;
public SimpleBinding(String key, String value) {
Objects.nonNull(key);
Objects.nonNull(value);
this.key = key;
this.value = value;
}
#Override
public int compareTo(SimpleBinding that) {
return this.key.compareTo(that.key);
}
}
And the you just use the Collections.sort(bindings ) to have sorted result.
In case you do not have access to the class you should use the Comparator interface like this
enum SimpleBindingComparator implements Comparator<SimpleBinding> {
DEFUALT {
#Override
public int compare(SimpleBinding fist, SimpleBinding second) {
return fist.key.compareTo(second.key);
}
};
Then you sort your bindings like this Collections.sort(bindings ,SimpleBindingComparator.DEFAULT);
But if your case is more complex than this and your store a Map in the list you should define a logic that represent the order. In your case it can be sad that the order must maintained by c1.getName()
One choice is that you should not create a List but a map of list Map<String>,List<String>> this is so called multi map where a single key matches to multiple values. See MultiMap of guava and if you want it to be sorted then i propose to read about TreeMultiMap
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.