I am currently working on one of the usecases where you are given 6 strings which has 3 oldValues and 3 newValues like given below:
String oldFirstName = "Yogend"
String oldLastName = "Jos"
String oldUserName = "YNJos"
String newFirstName = "Yogendra"
String newLastName ="Joshi"
String newUserName = "YNJoshi"
now what I basically want to do is compare each of the oldValue with its corresponding new value and return true if they are not equal i.e
if(!oldFirstName.equalsIgnoreCase(newFirstName)) {
return true;
}
Now, since I am having 3 fields and it could very well happen that in future we might have more Strings with old and new value I am looking for an optimum solution which could work in all cases no matter how many old and new values are added and without having gazillions of if else clauses.
One possibility I thought was of having Old values as OldArrayList and new values as newArraylist and then use removeAll where it would remove the duplicate values but that is not working in some cases.
Can anyone on stack help me out with some pointers on how to optimum way get this done.
Thanks,
Yogendra N Joshi
you can use lambdaj (download here,website) and hamcrest (download here,website), this libraries are very powerfull for managing collections, the following code is very simple and works perfectly:
import static ch.lambdaj.Lambda.filter;
import static ch.lambdaj.Lambda.having;
import static ch.lambdaj.Lambda.on;
import static org.hamcrest.Matchers.isIn;
import java.util.Arrays;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<String> oldNames = Arrays.asList("nameA","nameE","nameC","namec","NameC");
List<String> newNames = Arrays.asList("nameB","nameD","nameC","nameE");
List<String> newList = filter(having(on(String.class), isIn(oldNames)),newNames);
System.out.print(newList);
//print nameC, nameE
}
}
With this libraries you can solve your problem in one line. You must add to your project: hamcrest-all-1.3.jar and lambdaj-2.4.jar Hope this help serve.
NOTE: This will help you assuming you can have alternatives to your code.
You can use two HashMap<yourFieldName, yourFieldValue> instead of two Arrays / Lists / Sets of Strings (or multiple random Strings);
Then you need a method to compare each value of both maps by their keys;
The result will be an HashMap<String,Boolean> containing the name of each field key, and true if the value is equal in both maps, while false if it is different.
No matter how many fields you will add in the future, the method won't change, while the result will.
Running Example: https://ideone.com/dIaYsK
Code
private static Map<String,Boolean> scanForDifferences(Map<String,Object> mapOne,
Map<String,Object> mapTwo){
Map<String,Boolean> retMap = new HashMap<String,Boolean>();
Iterator<Map.Entry<String, Object>> it = mapOne.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String,Object> entry = (Map.Entry<String,Object>)it.next();
if (mapTwo.get(entry.getKey()).equals(entry.getValue()))
retMap.put(entry.getKey(), new Boolean(Boolean.TRUE));
else
retMap.put(entry.getKey(), new Boolean(Boolean.FALSE));
it.remove(); // prevent ConcurrentModificationException
}
return retMap;
}
Test Case Input
Map<String,Object> oldMap = new HashMap<String,Object>();
Map<String,Object> newMap = new HashMap<String,Object>();
oldMap.put("initials","Y. J.");
oldMap.put("firstName","Yogend");
oldMap.put("lastName","Jos");
oldMap.put("userName","YNJos");
oldMap.put("age","33");
newMap.put("initials","Y. J.");
newMap.put("firstName","Yogendra");
newMap.put("lastName","Joshi");
newMap.put("userName","YNJoshi");
newMap.put("age","33");
Test Case Run
Map<String,Boolean> diffMap = Main.scanForDifferences(oldMap, newMap);
Iterator<Map.Entry<String, Boolean>> it = diffMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String,Boolean> entry = (Map.Entry<String,Boolean>)it.next();
System.out.println("Field [" + entry.getKey() +"] is " +
(entry.getValue()?"NOT ":"") + "different" );
}
You should check too if a value is present in one map and not in another one.
You could return an ENUM instead of a Boolean with something like EQUAL, DIFFERENT, NOT PRESENT ...
You should convert your String to some Set.
One set for OLD and another for NEW. And your goal of varity number of elements will also be resolved using same.
As it's set order of it will be same.
Related
I receive the following error when attempting to run my java program
Exception in thread "main" java.lang.ClassCastException: class java.util.TreeMap cannot be cast to class java.lang.Comparable (java.util.TreeMap and java.lang.Comparable are in module java.base of loader 'bootstrap')
at java.base/java.util.TreeMap.compare(TreeMap.java:1569)
at java.base/java.util.TreeMap.addEntryToEmptyMap(TreeMap.java:776)
at java.base/java.util.TreeMap.put(TreeMap.java:785)
at java.base/java.util.TreeMap.put(TreeMap.java:534)
at exportsParser.exportsMap(exportsParser.java:53)
at exportsParser.main(exportsParser.java:28)
The applicable code:
import edu.duke.*;
import java.io.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.regex.*;
import java.util.*;
import java.util.TreeMap;
import java.util.Iterator;
public class exportsParser{
void println(Object obj){
System.out.println(obj);
}
/* The rather involved pattern used to match CSV's consists of three
* alternations: the first matches aquoted field, the second unquoted,
* the third a null field.
*/
private final static Pattern csv_pattern = Pattern.compile("\"([^\"]+?)\",?|([^,]+),?|,");
public static void main(String[] argv) throws IOException {
//println(csv_pattern);
exportsParser parser = new exportsParser();
BufferedReader reader = new BufferedReader(new FileReader("./exports_small.csv"));
parser.exportsMap(reader);
}
public TreeMap<String, TreeMap<TreeMap<String,String> ,TreeMap<String, String>>> exportsMap(BufferedReader reader) throws IOException{
if(reader.readLine() == null) return null;
TreeMap<String, TreeMap<TreeMap<String,String>, TreeMap<String,String>>> exportsTable = new TreeMap<>();
TreeMap<String, String> products = new TreeMap<>();
TreeMap<String, String> value = new TreeMap<>();
TreeMap<TreeMap<String,String>,TreeMap<String,String>> exportsData = new TreeMap<>();
int countryIndex = 0;
ArrayList<String> exportsList = new ArrayList<String>();
String line;
try{
while((line = reader.readLine()) != null){
exportsList = parse(line);
String countryName = exportsList.get(0);
products.put("items", exportsList.get(1));
value.put("total", exportsList.get(2));
println(products);
println(value);
exportsData.put(products, value);
println(exportsData);
// exportsTable.put(countryName,exportsData);
println(exportsTable);
}
}
catch(IOException e)
{
e.printStackTrace();
}
reader.close();
return exportsTable;
}
/* Parse one line.
* #return List of Strings, minus their double quotes
*/
public ArrayList<String> parse(String line) {
ArrayList<String> list = new ArrayList<String>();
Matcher mat = csv_pattern.matcher(line);
// For each field
while (mat.find()) {
String match = mat.group();
if (match == null)
break;
if (match.endsWith(",")) { // trim trailing ,
match = match.substring(0, match.length() - 1);
}
/*if (match.startsWith("\"")) { // assume also ends with
match = match.substring(1, match.length() - 1);
}*/
if (match.length() == 0)
match = null;
list.add(match);
}
return list;
}
}
To clarify, the issue arises when attempting to put the TreeMap data of products and value in exportsData. Same is applicable when attempting to add exportsData to the exportsTable correlating its key (Country) to the exportsData (Value). I understand what the errors means, I just have no idea as to how to fix it. Additionally libraries are not allowed (Purpose is to understand the flow of input data into "rows/columns" and experiment with Trees, HashMaps, etc)
Additionally, I cannot use a database for this as this is a requirement to manually do this. However what is not a requirement is using TreeMaps of course. We are allowed to experiment with the various Collection classes.
I have spent a while trying to get this to work but I have run out of thoughts and forum pages to read now. Eventually, this would be ideal to make it cater towards larger CSV files of unknown columns. However, for the practice run, we have been given the information before hand, hence the indexing in the code above.
CSV data:
Country,Exports,Value (dollars)
Germany,"motor vehicles, machinery, chemicals","$1,547,000,000,000"
Macedonia,"tobacco, textiles","$3,421,000,000"
Madagascar,"coffee, vanilla, shellfish","$864,800,000"
Malawi,"tea, sugar, cotton, coffee","$1,332,000,000"
Malaysia,"semiconductors, wood","$231,300,000,000"
Namibia,"diamonds, copper, gold, zinc, lead","$4,597,000,000"
Peru,"copper, gold, lead, zinc, tin, coffee","$36,430,000,000"
Rwanda,"coffee, tea, hides, tin ore","$720,000,000"
South Africa,"gold, diamonds, platinum","$97,900,000,000"
United States,"corn, computers, automobiles, medicines","$1,610,000,000,000"
This is my first time using the above so it is prone to beginner errors.
Check javadoc. To cite - The map is sorted according to the natural ordering of its keys, or by a Comparator provided at map creation time, depending on which constructor is used. Natural ordering of keys means key must implement Comparable. Your keys are of type TreeMap, which does not have natural ordering - it does not implement Comparable. Naturally this leads to ClassCastException.
If you really must use TreeMap as key for your TreeMap, you must provide a Comparator to TreeMap constructor:
Comparator<TreeMap<String,String>> comparator = implement it;
TreeMap<TreeMap<String,String>,TreeMap<String,String>> exportsData = new TreeMap<>(comparator);
Seeing that your data is coming from a csv file, i would suggest to parse it to some custom class. It would be much more readable, and it will be easier to implement Comparable, or Comparator if needed.
Edit: You don't actually need 2 maps - for exports and value, it complicates thing more that needed. Those can be put in a single map. Keys are values from the first line in csv(or other keys, as in your case) and values are corresponding values from the parsed line. So you have:
Map<String, String> lineData;
Country may also be part of this map(if you need it). Normally it's this map, which will be rerpesented by your custom class, but it looks like your task is to work with collections, so i won't delve into that.
Since you want to map country names to data, now you need another map - keys will be string(country name) and values the map containing line data from above.
All of that can be stored in a list(you can store anything in a list). I'm leaving to you figuring out the exact way to implement it.
Is there a way to get or set an array element stored in a Java Map?
Example:
If we have a map like this:
{
name: "Blah",
friends: ["Foo", "Bar"]
}
Map<String, Object> myMap = new HashMap<>();
List<String> friends = new ArrayList<>();
myMap.put("name", "Blah");
myMap.put("friends", friends);
Is it possible to use Reflection to get or set the first element in the friends array in the "myMap" from the string: "myMap.friends[0]"
Your question is not very clearly written and I believe that's why you are not getting the answer you expect but, If I understood your question correctly, you need to parse the following input string at runtime that you don't know beforehand:
myMap.friends[0]
And this should be parsed into components like:
mapName = "myMap"
mapKey = "friends"
valueIndex = 0
And with this information, you need to manipulate data in a Map at runtime through reflection.
Note: This only makes sense if you could potentially have more complex expressions, using different sort of objects and accessing nested properties of retrieved objects, otherwise you wouldn't need reflection at all.
Note 2: You may want to have a look at JXPath which already does a lot of this for you based on a XPath-like syntax for navigating object graphs.
That said, if my assumptions are correct and you still want to do it yourself, consider the following example.
For the sake of demonstration, let's consider our map is returned by a method myMap inside a Context.
private static class Context {
public Map<String, Object> myMap() {
Map<String, Object> myMap = new HashMap<>();
List<String> friends = new ArrayList<>();
friends.add("Foo");
friends.add("Bar");
myMap.put("name", "Blah");
myMap.put("friends", friends);
return myMap;
}
}
I'm assuming you are already parsing the input string into the different components. If not, for this simple string you could do it with simple regular expressions. If you already have the components, let's consider the following method:
public static Object readContextMap(Context context,
String mapName, String mapKey, Integer mapValueIndex) throws Exception {
// gets Context class for inspection
Class<?> cls = context.getClass();
// search for a method based on supplied mapName
Method mapMethod = cls.getDeclaredMethod(mapName);
// get a value from the retrieved map based on mapKey
Object mapValue = mapMethod.getReturnType()
.getDeclaredMethod("get", Object.class)
.invoke(mapMethod.invoke(context), mapKey);
// if the result is of type list, use the index to return the indexed element
if (List.class.isAssignableFrom(mapValue.getClass())) {
return ((List<?>)mapValue).get(mapValueIndex);
}
// otherwise return the object itself
return mapValue;
}
For testing purposes, consider the following main method:
public static void main(String[] args) throws Exception {
Context context = new Context();
String input = "myMap.friends[0]";
// parse input into...
String mapName = "myMap";
String mapKey = "friends";
Integer valueIndex = 0;
Object firstFriend = readContextMap(context, mapName, mapKey, valueIndex);
System.out.println(firstFriend);
// prints Foo
Object name = readContextMap(context, "myMap", "name", null);
System.out.println(name);
// prints Blah
}
This should be approximately what you want. You can easily create variations of this to set values as well. Please bear in mind that this code is just for demo purposes and needs a better error handling (e.g. verify if the context is really returning a map and nothing else).
This should be something along the lines you are looking for.
There's no need to use reflection here. You can simply cast it (which is also unsafe, but less so).
You can just do this:
List<String> friends = (List<String>) myMap.get("friends");
friends.set(0, "Bob");
I have list that has alphanumeric elements. I want to find the maximum number of each elements individually.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class Collect {
public static void main(String[] args) {
List<String> alphaNumericList = new ArrayList<String>();
alphaNumericList.add("Demo.23");
alphaNumericList.add("Demo.1000");
alphaNumericList.add("Demo.12");
alphaNumericList.add("Demo.12");
alphaNumericList.add("Test.01");
alphaNumericList.add("Test.02");
alphaNumericList.add("Test.100");
alphaNumericList.add("Test.99");
Collections.sort(alphaNumericList);
System.out.println("Output "+Arrays.asList(alphaNumericList));
}
I need filter only below values. For that I am sorting the list but it filters based on the string rather than int value. I want to achieve in an efficient way. Please suggest on this.
Demo.1000
Test.100
Output [[Demo.1000, Demo.12, Demo.12, Demo.23, Test.01, Test.02, Test.100, Test.99]]
You can either create a special AlphaNumericList type, wrapping the array list or whatever collection(s) you want to use internally, giving it a nice public interface to work with, or for the simplest case if you want to stick to the ArrayList<String>, just use a Comparator for sort(..):
package de.scrum_master.stackoverflow.q60482676;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static java.lang.Integer.parseInt;
public class Collect {
public static void main(String[] args) {
List<String> alphaNumericList = Arrays.asList(
"Demo.23", "Demo.1000", "Demo.12", "Demo.12",
"Test.01", "Test.02", "Test.100", "Test.99"
);
Collections.sort(
alphaNumericList,
(o1, o2) ->
((Integer) parseInt(o1.split("[.]")[1])).compareTo(parseInt(o2.split("[.]")[1]))
);
System.out.println("Output " + alphaNumericList);
}
}
This will yield the following console log:
Output [Test.01, Test.02, Demo.12, Demo.12, Demo.23, Test.99, Test.100, Demo.1000]
Please let me know if you don't understand lambda syntax. You can also use an anonymous class instead like in pre-8 versions of Java.
Update 1: If you want to refactor the one-line lambda for better readability, maybe you prefer this:
Collections.sort(
alphaNumericList,
(text1, text2) -> {
Integer number1 = parseInt(text1.split("[.]")[1]);
int number2 = parseInt(text2.split("[.]")[1]);
return number1.compareTo(number2);
}
);
Update 2: If more than one dot "." character can occur in your strings, you need to get the numeric substring in a different way via regex match, still not complicated:
Collections.sort(
alphaNumericList,
(text1, text2) -> {
Integer number1 = parseInt(text1.replaceFirst(".*[.]", ""));
int number2 = parseInt(text2.replaceFirst(".*[.]", ""));
return number1.compareTo(number2);
}
);
Update 3: I just noticed that for some weird reason you put the sorted list into another list via Arrays.asList(alphaNumericList) when printing. I have replaced that by just alphaNumericList in the code above and also updated the console log. Before the output was like [[foo, bar, zot]], i.e. a nested list with one element.
Check below answer:
public static void main(String[] args) {
List<String> alphaNumericList = new ArrayList<String>();
alphaNumericList.add("Demo.23");
alphaNumericList.add("Demo.1000");
alphaNumericList.add("Demo.12");
alphaNumericList.add("Demo.12");
alphaNumericList.add("Test.01");
alphaNumericList.add("Test.02");
alphaNumericList.add("Test.100");
alphaNumericList.add("Test.99");
Map<String, List<Integer>> map = new HashMap<>();
for (String val : alphaNumericList) {
String key = val.split("\\.")[0];
Integer value = Integer.valueOf(val.split("\\.")[1]);
if (map.containsKey(key)) {
map.get(key).add(value);
} else {
List<Integer> intList = new ArrayList<>();
intList.add(value);
map.put(key, intList);
}
}
for (Map.Entry<String, List<Integer>> entry : map.entrySet()) {
List<Integer> valueList = entry.getValue();
Collections.sort(valueList, Collections.reverseOrder());
System.out.print(entry.getKey() + "." + valueList.get(0) + " ");
}
}
Using stream and toMap() collector.
Map<String, Long> result = alphaNumericList.stream().collect(
toMap(k -> k.split("\\.")[0], v -> Long.parseLong(v.split("\\.")[1]), maxBy(Long::compare)));
The result map will contain word part as a key and maximum number as a value of the map(in your example the map will contain {Demo=1000, Test=100})
a. Assuming there are string of type Demo. and Test. in your arraylist.
b. It should be trivial to filter out elements with String Demo. and then extract the max integer for same.
c. Same should be applicable for extracting out max number associated with Test.
Please check the following snippet of code to achieve the same.
Set<String> uniqueString = alphaNumericList.stream().map(c->c.replaceAll("\\.[0-9]*","")).collect(Collectors.toSet());
Map<String,Integer> map = new HashMap<>();
for(String s:uniqueString){
int max= alphaNumericList.stream().filter(c -> c.startsWith(s+".")).map(c -> c.replaceAll(s+"\\.","")).map(c-> Integer.parseInt(c)).max(Integer::compare).get();
map.put(s,max);
}
I have narrowed down my problem to the following, I need to convert a String to a Item Identifyer.
Example:
String str = "pickaxe";
Item pick = str;
That would resolve to Identifying pick as pickaxe. How can you do this?
I think what you want to use is a Hash Map
In your code you can create your objects
Item pickaxe = new Pickaxe();
Hashmap<String, Item> items = new HashMap<String, Item>();
items.add("pickaxe", pickaxe);
Then later you can retrieve your item with
items.get("pickaxe");
Usually I would recommend an enum for this sort of string-to-Object lookup, but I also know that there's a huge list of items in minecraft that will change from game update to the next. That means that putting those around 370 items into an enum is a lot of work.
My suggestion: store them in a database or something else that is not your sourcecode. Then load it into a structure such as a HashMap or a List, the first one being easier for lookup up the short name, just as joey.enfield suggested. With a list, you would have to iterate through the whole thing to find an item with a matching name.
EDIT:
public enum Item {
PICKAXE("pickaxe"),
SHOVEL("shovel"),
BOW("bow");
private String m_shortID;
Item(String shortID){
m_shortID = shortID;
)
static Item lookupByShortID(String shortID){
for(Item i:values()){
if(i.m_shortID.equals(shortID)){
return i;
}
}
return null;
}
}
Here is a quick and dirty class I cooked up. You should be aware that this code will most likely produce a hashmap that does not contain EVERY item as there are most likely items that have the same String value for getItemDisplayName(), so the last one in Item.itemsList[] will be the one in the hashmap. Also note that it will produce a different list if you use a different MC language since getItemDisplayName() gets the localized String.
import java.util.HashMap;
import java.util.Map;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
public class ItemListByString
{
private Map<String, Item> itemMap;
private static ItemListByString instance = new ItemListByString();
private ItemListByString()
{
itemMap = new HashMap<String, Item>();
for (Item item : Item.itemsList)
if (item != null)
itemMap.put(item.getItemDisplayName(new ItemStack(item, 1, 0)), item);
}
public Item getItemByString(String s)
{
return instance.itemMap.get(s);
}
}
I have a list of objects which I want to aggregate one of the values of this object grouped by other values of the objects in this list.
I'm currently using the properties I want to group by as a Hash key and I'm traversing the object so:
ArrayList<MyObject> raw = Some Data;
Map<String, MyObject> map = new HashMap<String, MyObject>();
for (MyObject ungrouped : raw) {
String key = ungrouped.getStringOne().getName() + ungrouped.getStringTwo() + ungrouped.getStringThree();
if (map.containsKey(key)){
MyObject holder = map.get(key);
holder.setNumericProp(holder.getNumericProp() + ungrouped.getNumericProp());
// map.put(key, holder); //Edited after comments
}
else{
map.put(key, ungrouped);
}
}
return map.values().toArray(new MyObject[map.values().size()]);
Is there a more elegant way to do this without using the concatenated strings as a key?
If this was SQL (from which I'm several application layers away) it would be:
SELECT SUM(numericvalue) FROM sometable GROUP BY stringone, stringtwo , stringthree
Apart from some problems I see with the code, one solution would be to use (if you can afford it) Guava's Equivalence (or replicate it in your code). You'd implement an Equivalence<MyObject> and use a Map<Equivalence.Wrapper<MyObject>, MyObject> as a container; you'd make the equivalence on your three string members.
That would allow it not to break in this situation:
// Oops! Same key...
s1 = "foo", s2 = "bar", s3 = "baz"
s1 = "fooba", s2 = "rb", s3 = "az"
Also, you could use the return value of the map's .put() method (the old value):
MyObject holder = map.put(key, ungrouped);
if (holder != null)
holder.setNumericProp(etc);
If your looking elegantly solve this you can use lambdja libraries (Download Here, Website). For example you can SUM a column with the following code (look at this link):
double sum = sumFrom(select(sales,
having(on(Sale.class).getBuyer().isMale())
.and( having(on(Sale.class).getSeller().isMale())))).getCost();
You also can group with this (look at this link):
Group<Person> group = group(meAndMyFriends, by(on(Person.class).getAge()));
With this libraries you can solve your problem in few lines.