My program calculates the digit sums of all values entered into a text file. The entered values and their according digit sums are stored in two seperate ArrayLists.
Both ArrayLists are combined into a LinkedHashMap in the end which should be ordered by the digit sum in descending order.
If you enter multiple values with the same digit sum, it's supposed to order those (and only those) in descending order by their original value, not the digit sum this time (the rest stays the same as before).
How do I achieve this with Comparators?
My lists and the map:
String filePath = args[0];
LineNumberReader br = new LineNumberReader(new FileReader(filePath));
LinkedHashMap<BigInteger, BigInteger> unsortedMap = new LinkedHashMap<BigInteger, BigInteger>();
List<BigInteger> inputList = new ArrayList<>();
List<BigInteger> DSList = new ArrayList<>();
if(br.ready()){
while (true) {
String line = br.readLine();
if (line == null) {
break;
}
BigInteger input = new BigInteger(line);
inputList.add(input);
DSList.add(methods.digitSum(input));
}
}
for(int i = 0; i < inputList.size(); i++){
unsortedMap.put(inputList.get(i), DSList.get(i));
}
for(BigInteger key : unsortedMap.keySet()){
System.out.println(new BigDecimal(key).toPlainString() + " (Digit Sum: " + unsortedMap.get(key) + (")"));
}
methods.digitSum:
public static BigInteger digitSum(BigInteger number) {
String digits = number.toString();
int sum = 0;
for(int i = 0; i < digits.length(); i++) {
int digit = (int) (digits.charAt(i) - '0');
sum = sum + digit;
}
return BigInteger.valueOf(sum);
}
Output has to look like this:
x (digit sum: y)
x (digit sum: y)
...
x = entered value
y = digit sum of x
If you need any further information, feel free to ask.
Here is a solution with a simple class and a Comparator
class Values {
BigInteger number;
BigInteger digitSum;
Values(BigInteger number, BigInteger sum) {
this.number = number;
this.digitSum = sum;
}
#Override
public String toString() {
return number + " (digit sun:" + digitSum + ")";
}
}
And then create a list with this class
List<Values> inputList = new ArrayList<>();
and add objects of Values to the list using the constructor when reading the file
For sorting you can create a Comparator object like this
Comparator<Values> compareSum = (Values v1, Values v2) -> {
int result = v1.digitSum.compareTo(v2.digitSum);
return result != 0 ? result : v1.number.compareTo(v2.number);
};
and sort your list in descending order
inputList.sort(compareSum.reversed());
As there are duplicate keys with different values, you cannot use LinkedHashMap or any other Java Map implementations to store the same key with different value.
You can either create your custom Map or use Apache's Common Map Implementations. The following code shows a solution using MultiValueMap and a custom comparator that sorts by key and then by value in descending order:
MultiValuedMap<BigInteger, BigInteger> unsortedMap = new ArrayListValuedHashMap<>();
if(br.ready()){
while (true) {
String line = br.readLine();
if (line == null) {
break;
}
//Use a single map to put both values
unsortedMap.put(new BigInteger(line), new BigInteger(methods.digitSum(line)));
}
}
//Do the sorting using custom comparator
List<Map.Entry<BigInteger, BigInteger>> list = new LinkedList<>(unsortedMap.entries());
// Sort the list
Collections.sort(list, new Comparator<Map.Entry<BigInteger, BigInteger> >() {
public int compare(Map.Entry<BigInteger, BigInteger> o1,
Map.Entry<BigInteger, BigInteger> o2) {
int c = o1.getKey().compareTo(o2.getKey());
if (c==0){
c = o1.getValue().compareTo(o2.getValue());
}
return c * -1; //descending
}
});
//Place the sorted List into another Map that retains the insert order
Multimap<BigInteger, BigInteger> sortedMap = LinkedHashMultimap.create();
for (Map.Entry<BigInteger, BigInteger> entry : list) {
sortedMap.put(entry.getKey(), entry.getValue());
}
//Print
for(BigInteger key : sortedMap.keySet()){
for (BigInteger bi: sortedMap.get(key)) {
System.out.println("x: " + new BigDecimal(key).toPlainString() + " (Digit Sum: " + bi + (")"));
}
}
To use the above Maps you should include the apache commons collections dependency
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
You can refer to below code to sort your map by value in descending order (in case of same values, sort in descending order of keys). In your case, the keys of the map (passed in parameter) are the inputs from text file and respective values in map are the digit sum.
public static LinkedHashMap<BigInteger, BigInteger> sortByValue(LinkedHashMap<BigInteger, BigInteger> hm)
{
List<Map.Entry<BigInteger, BigInteger> > list =
new LinkedList<Map.Entry<BigInteger, BigInteger> >(hm.entrySet());
Collections.sort(list, new Comparator<Map.Entry<BigInteger, BigInteger> >() {
public int compare(Map.Entry<BigInteger, BigInteger> o1,
Map.Entry<BigInteger, BigInteger> o2)
{
// To handle same value case
if(o1.getValue().compareTo(o2.getValue()) == 0) {
return (o2.getKey()).compareTo(o1.getKey());
}
return (o2.getValue()).compareTo(o1.getValue());
}
});
LinkedHashMap<BigInteger, BigInteger> temp = new LinkedHashMap<BigInteger, BigInteger>();
for (Map.Entry<BigInteger, BigInteger> aa : list) {
temp.put(aa.getKey(), aa.getValue());
}
return temp;
}
The compareTo method is called on object o2 rather than object o1 to get the result in descending order. As mentioned in comment of above code snippet, the if condition will handle your requirement of comparing the keys and sort in descending order of keys when the values are same.
The list of Entry objects is sorted as as per your requirement and these sorted Entry objects are added to LinkedHashMap to maintain the insertion order into the map.
user can create as many instances of Thing as they please. a user inputs a string into the object with a number.
eg of created objects
Thing thing1 = new Thing("input1", 3);
Thing thing2 = new Thing("input2", 1);
Thing thing3 = new Thing("input2", 3000);
Thing thing4 = new Thing("input1", 4);
Thing thing5 = new Thing("input4", 200");
ArrayList<Thing> ThingList= new ArrayList<Thing>();
ThingList.add(thing1);
.....
.....
I need to have the the program search through an ArrayList of Things and output the inputed String with the combined total of all integers with the same inputed string
output example
name count
input1 7
input2 3001
input4 200
Im not sure how I can do this without doubling up on inputs with the same name. unless I compare to a name entered by me
what I have done so far (note it can only find and total what I have inputed for it to search.)
for( i= 0; i< ThingList.size(); i++){
inputedThingCheck = ThingList.get(i).getInputedName();
//testInput is the input I know for a fact is inside arraylist
if(inputedThingCheck.equals(testInput)){
thingTotal = ThingList.get(i).getCount() + thingTotal;
}
}
I want to know how to have the program search through each Thing object and add to a total the count of all things with the same name, while skipping the Thing that has already been done
You can try something like this with Collectors.groupingBy
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Thing {
private String key;
private int value;
public Thing(String key, int value) {
super();
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
#Override
public String toString() {
return "Thing [key=" + key + ", value=" + value + "]";
}
public static void main(String[] args) {
List<Thing> source = Arrays.asList(
new Thing("input1", 3),
new Thing("input2", 1),
new Thing("input2", 3000),
new Thing("input1", 4),
new Thing("input4", 200));
List<Thing> target = source.stream().collect(Collectors.groupingBy(thing -> thing.key)).entrySet().stream()
.map(e -> e.getValue().stream().reduce((f1, f2) -> new Thing(f1.key, f1.value + f2.value)))
.map(f -> f.get()).collect(Collectors.toList());
System.out.println(target);
}
}
Thank you!
you can also implement the groupby function for example java 8
Map<String, Integer> sum = list.stream().collect(
Collectors.groupingBy(Thing::getkey, Collectors.summingInt(Thing::getValue)));
Instead of using ArrayList to contain the thing object, I guess it would be easier if we store it as a key, value pair. The key can be the name of the thing, and the value can an array containing the count of that particular key.
ArrayList<Thing> foo = new ArrayList<Thing>();
foo.add(thing1);
foo.add(thing2);
foo.add(thing3);
foo.add(thing4);
foo.add(thing5);
Map<String, ArrayList<Integer>> ThingList = new HashMap<String, ArrayList<Integer>>();
for (Thing x : foo){
if (ThingList.containsKey(x.getName())){
ArrayList value = ThingList.get(x.getName());
value.add(x.getValue());
}
else{
container = new ArrayList<Integer>();
container.add(x.getValue());
ThingList.put(x.getName(), container);
}
}
// Debug to check the key, value pair of ThingList
// System.out.println(Arrays.asList(ThingList));
// Loop through ThingList and get the key + sum of its related values
for (Map.Entry<String, ArrayList<Integer>> entry : ThingList.entrySet()) {
String key = entry.getKey();
ArrayList value = entry.getValue();
int valueSum = 0;
for (int i=0 ; i < value.size() ; i++){
valueSum += (Integer)value.get(i);
}
// Print the output
System.out.println(key + " " + String.valueOf(valueSum));
}
}
I created a small code example in order to demonstrate the difference of HashMap and TreeMap.
public class HashMapSimpleValueAutosort {
private static final char[] alphabet = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
public static void main(String[] args) {
Map<Character, Integer> map = new HashMap<>();
inverseAbc(map, "HashMap");
map = new TreeMap<>();
inverseAbc(map, "TreeMap");
}
private static void inverseAbc(Map<Character, Integer> map, String desc ) {
System.out.println(desc);
for (int i=25; i>=0; --i) {
map.put(alphabet[i], 26 - i);
}
System.out.println(map);
}
}
What it does, is to assign the alphabet letters in the reverse order inside the map as keys and their position in the alphabet as the corresponding value, using a HashMap and a TreeMap approach.
Althought the keys are inserted in the inversed order, HashMap toString() outputs them in ascending order, just like TreeMap does.
So the question that arises here is:
Does toString() method of HashMap, sorts the keys internally before returning the string represention of the map ?
EDIT:
It seams that this might be a JDK or IDE based symptom and not restricted only to toString().
public class HashMapSimpleValueAutosort {
private static final char[] alphabet = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
public static void main(String[] args) {
Map<Character, Integer> map = new HashMap<>();
printEntries(map, "HashMap");
map = new TreeMap<>();
printEntries(map, "TreeMap");
}
private static void printEntries(Map<Character, Integer> map, String desc) {
System.out.println(desc);
for (int i=25; i>=0; --i) {
map.put(alphabet[i], 26 - i);
}
System.out.print("{ ");
for (Map.Entry<Character, Integer> entry : map.entrySet()) {
System.out.printf("%c=%d,", entry.getKey(), entry.getValue());
}
System.out.println(" }");
}
}
In the example above I print the key-value pairs as entries.
This is a curious result of the implementation of HashMap.
A HashMap has to decide in which of the hash buckets to place the entry. It decides this based on the hashCode() of the key object. Now, hashCode() can be any integer at all. So it first does this to the hash code:
(h = key.hashCode()) ^ (h >>> 16)
Now, your key in this case is of type Character. The hashCode for Character is the value of the character itself. Java char is 16-bits wide. So shifting it 16 bits to the right will give you zero. Xoring it with that zero gives you the original value - the char value!
The values you have chosen are consecutive. This means that they will happen to be stored in the hash table buckets at indexes i, i+1, i+2...
This also happens to be the order at which the entry set iterator, on which toString is based, traverses that table: it goes through the table consecutively. So as long as you don't have collisions, for Character keys that happen to be consecutive, you'll see the result "sorted".
It is coincidence that HashMap appears sorted, which only occurs because the values are very simple.
If you change to String, and double the letters, such that hashCode() returns more complex values, the HashMap will appear more like what is usually expected: Order appears random (it's isn't, but might as well be).
private static final String[] alphabet = {"aa","bb","cc","dd","ee","ff","gg","hh","ii","jj","kk","ll","mm",
"nn","oo","pp","qq","rr","ss","tt","uu","vv","ww","xx","yy","zz"};
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
inverseAbc(map, "HashMap");
map = new TreeMap<>();
inverseAbc(map, "TreeMap");
}
private static void inverseAbc(Map<String, Integer> map, String desc ) {
System.out.println(desc);
for (int i=alphabet.length-1; i>=0; --i) {
map.put(alphabet[i], alphabet.length - i);
}
System.out.println(map);
}
Output
HashMap
{tt=7, zz=1, xx=3, vv=5, rr=9, pp=11, nn=13, ll=15, jj=17, hh=19, ff=21, dd=23, bb=25, ss=8, yy=2, ww=4, uu=6, qq=10, oo=12, mm=14, kk=16, ii=18, gg=20, ee=22, cc=24, aa=26}
TreeMap
{aa=26, bb=25, cc=24, dd=23, ee=22, ff=21, gg=20, hh=19, ii=18, jj=17, kk=16, ll=15, mm=14, nn=13, oo=12, pp=11, qq=10, rr=9, ss=8, tt=7, uu=6, vv=5, ww=4, xx=3, yy=2, zz=1}
First and foremost -
HashMap is not ordered collection.
TreeMap is ordered collection.
So, if you are expecting that you will put values in certain order in your HashMap and it will remain so then it is wrong. It is unordered collection. Also, I ran it my local and HashMap output was unordered and TreeMap was ordered
Like said above, TreeMap is an ordered collection and it works because when you put values in TreeMap then a Comparator is sorting them. See below TreeMap.put implementation.
While when you put values in HashMap then there is no such sorting.
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
Pre-Java8 v/s Java8:
Implementation of put method of HashMap in Java8 is different than Java7 or before, and it internally uses TreeNode and all, and is logic looks pretty different ... That could be the reason. Check Java8 HashMap.put here
HashMap, LinkedHashMap and TreeMap are the three most popular Map types.
LinkedHashMap maintains insertion order i.e elements are stored as they are inserted.
TreeMap stores the elements according to supplied Comparator or in the natural order, if you don’t supply any Comparator.
HashMap doesn’t guarantee any order.
So if you want the map to retain the insertion order, use LinkedHashMap
class Solution {
public int firstUniqChar(String s) {
char ch ;
Map<Character,Integer> map = new LinkedHashMap<Character,Integer>() ;
for(int i = 0; i < s.length(); i++)
{
ch = s.charAt(i) ;
map.put(ch,map.getOrDefault(ch,0)+1) ;
}
for(char key : map.keySet())
{
if(map.get(key) == 1)
return s.indexOf(key+"") ;
}
return -1 ;
}}
Ive created a map with keys of type integer and values are Sets of Strings. I have populated the map with some test data, and now need to write a method that prints out the contents of the map like "key: value, value, value"
Im assuming iterating through the map, and assigning the keys to a int variable and printing these out is how to start, but how would I then go about printing the values in the set of strings?
public class HandicapRecords {
private Map<Integer, Set<String>> handicapMap;
public HandicapRecords() {
handicapMap = new HashMap<>();
}
public void handicapMap() {
Set<String> players = new HashSet<>();
players.add("Michael");
players.add("Roger");
players.add("Toby");
handicapMap.put(10, players);
players = new HashSet<>();
players.add("Bethany");
players.add("Martin");
handicapMap.put(16, players);
players = new HashSet<>();
players.add("Megan");
players.add("Declan");
handicapMap.put(4, players);
}
public void printMap() {
//code for method to go here
}
}
You can iterate on a Set data structure just as you could in a list (well, actually list preserves the order, whereas the set does not but I presume that that would go beyond the scope of this question).
To print the data, you could do the following:
for (Integer num : handicapMap.keySet()) {
System.out.print("Key : " + String.valueOf(num) + " Values:");
for (String player : handicapMap.get(num)) {
System.out.print(" " + player + " ");
}
System.out.println();
}
You gave use nested for-each loop. We cant directly iterate through HashMao, take the keySet and print.
Example:
public void printMap()
{
Set<Integer> keys=handicapMap.keySet();
for(Integer k:keys)
{
Set<String> players=handicapMap.get(k);
System.out.print(" "+k+":");
int i=0;
for(String p:players)
{
i++;
System.out.print(p);
if(i!=players.size())
System.out.print(",");
}
System.out.println();
}
}
I guess you won't know keys, so you have to iterate over all entries in hash map:
for (Map.Entry<Integer, Set<String>> entry : handicapMap.entrySet())
{
Integer key = entry.getKey();
HashSet<String> values = entry.getValue();
for (String s : values) {
// and now do what you need with your collection values
}
}
Really struggling to work this out...
I have a text file with data like this (17000 lines of it)
45226 1
45226 1
45226 1
45226 3
45226 5
23470 1
45226 5
45226 5
29610 4
37417 2
37417 3
37948 1
What I want to do is sort the text file (using java) so all the left numbers are grouped if the right value is 1.
or the left value is group if the right is not equal to 1 (so any other number).
for example (but doesn't have to be like this)
3 x 45226 1
4 x 45226 MIXED
1 x 23470 1
1 x 29610 MIXED
2 x 37417 MIXED
1 x 37948 1
I know I may need to use array? or some sort of sort? but I just can't work it out :'(
Any help, code or suggestions - greatly appreciated!
Thank you!
I'd generate two maps, one for when the right is 1 and one for all other right-hand values. Each map maps left-hand values to count of occurrences. You can then populate the maps by looping through the data. In pseudo-Java:
Map<Integer, Integer> onesMap = new HashMap<Integer, Integer>();
Map(Integer, Integer> otherMap = new HashMap<Integer, Integer>();
for (each left/right pair) {
Map<Integer, Integer> map = right == 1 ? onesMap : otherMap;
Integer count = map.get(left);
map.put(left, count == null ? 1 : (1 + count));
}
At the end, the key/value pairs give you the counts you need for each left value.
My code:
public class Sort {
private static class Counter {
private int one;
private int mixed;
public void incrementOne() {
one++;
}
public void incrementMixed() {
mixed++;
}
}
public static void main(String[] args) throws IOException {
Map<String, Counter> map = new LinkedHashMap<String, Counter>();
String fileName = "input.txt";
BufferedReader reader = new BufferedReader(new FileReader(fileName));
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
StringTokenizer tokenizer = new StringTokenizer(line);
String key = tokenizer.nextToken();
String value = tokenizer.nextToken();
Counter counter = map.get(key);
if (counter == null) {
counter = new Counter();
map.put(key, counter);
}
if (value.equals("1")) {
counter.incrementOne();
} else {
counter.incrementMixed();
}
}
reader.close();
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"));
for(Map.Entry<String, Counter>entry:map.entrySet()){
Counter counter = entry.getValue();
if(counter.one>0){
writer.write(String.valueOf(counter.one));
writer.write(" x ");
writer.write(entry.getKey());
writer.write(" 1");
writer.newLine();
}
if(counter.mixed>0){
writer.write(String.valueOf(counter.mixed));
writer.write(" x ");
writer.write(entry.getKey());
writer.write(" MIXED");
writer.newLine();
}
}
writer.close();
}
}
I'd use a map to store the data:
Map<Integer, Set<Integer>> map = new TreeSet<Integer, Set<Integer>>();
The key is the value of the first line and, as it is not unique, the maps value is a set which holds the various values from the second column.
private void addLine(String line) {
String[] fields = line.split("\\s*");
Integer key = Integer.parseInt(fields[0]);
Integer value = Integer.parseInt(fields[1]);
Set<Integer> set = map.get(value);
if (set == null) {
set = new HashSet<Integer>();
map.put(key, set);
}
set.add(value);
}
Once the file is read, you can get the result for each number like this:
private String getResult(Integer key) {
Set<Integer> values = map.get(key);
if (values == null) {
return null; // key not in map (unknown number)
} else if (values.size() == 1) {
return values.iterator().next().toString();
} else {
return "MIXED";
}
}
Map<Integer, Integer> map1=new HashMap<Integer, Integer>();
Map<Integer, Integer> mapMixed=new HashMap<Integer, Integer>();
void addValues(int value, int num){
if (num==1){
if (map1.get(value)==null) map1.put(value,0);
map1.put(value,map1.get(value)+1);
} else {
if (mapMixed.get(value)==null) mapMixed.put(value,0);
mapMixed.put(value,mapMixed.get(value)+1);
}
}
and after it you have to print maps:
void printIt(){
Set<Integer> keys=map1.keySet();
keys.AddAll(mapMixed.keySet());
for (int key:keys){
if (map1.get(key)!=null) println(map1.get(key)+"x"+key+" 1");
if (mapMixed.get(key)!=null) println(mapMixed.get(key)+"x"+key+" MIXED");
}
}
I would suggest you use two Multiset instances from Guava - one for "mixed" values and one for "1" values. All you need is a count of the number of values, after all - and that's exactly what Multiset gives you.
So you'd use something like:
final Multiset<Integer> mixed = new HashMultiset<Integer>();
final Multiset<Integer> one = new HashMultiset<Integer>();
Files.readLines(new File(...), Charsets.UTF8,
new LineProcessor<Void>() {
#Override Void getResult() { return null; }
#Override void processLine(String line) {
// TODO: Split line into two integers
int value = ...;
int type = ...;
Multiset<Integer> set = type == 1 ? one : mixed;
set.add(value);
}
});
You can then iterate over those two sets, and find the value and the count of each entry.