Descending sort of substrings by occurence - Java - java

Lest's say I have string:
String test= "AA BB CC BB BB CC BB";
What I would like to do is create String array like this:
String[]{"BB", "CC", "AA"}
Since B occurred 4 times C did 2 times and A only 1 time.
What would solution for this problem look like?

String test = "AA BB CC BB BB CC BB";
System.out.println(Arrays.deepToString(sort(test)));
Output: [BB, CC, AA]
Code:
public static String[] sort(String test) {
String[] strings = test.split(" ");
HashMap<String,Integer> map = new HashMap<String,Integer>();
for (String s : strings) {
Integer i = map.get(s);
if (i != null) {
map.put(s, i+1);
} else {
map.put(s, 1);
}
}
TreeMap<Integer,String> sort = new TreeMap<Integer,String>(Collections.reverseOrder());
for (Entry<String,Integer> e : map.entrySet()) {
sort.put(e.getValue(), e.getKey());
}
return sort.values().toArray(new String[0]);
}

What you could do is something like this (rough code):
String[] myOccurences = test.split(" ");
Then:
HashMap<String,Integer> occurencesMap = new HashMap<String,Integer>()
for( String s : myOccurences ){
if( occurencesMap.get( s ) == null ){
occurencesMap.put(s, 1);
} else {
occurencesMap.put(s, occurencesMap.get(s)++ );
}
}
Edit: The actual sorting (again rough code and unchecked):
List<String> mapKeys = new ArrayList<String>(occurencesMap.keySet()); // Keys
List<Integer> mapValues = new ArrayList<Integer>(occurencesMap.values()); // Values
TreeSet<Integer> sortedSet = new TreeSet( mapValues ); // Sorted according to natural order
Integer[] sortedValuesArray = sortedSet.toArray();
HashMap<String,Integer> lhMap = new LinkedHashMap<String,Integer>(); // LinkedHashMaps conserve order
for (int i=0; i<size; i++){
lhMap.put(mapKeys.get(mapValues.indexOf(sortedArray[i])), sortedValuesArray[i]);
}
mapKeys = new ArrayList<String>(occurencesMap.keySet()); // Keys again, this time sorted
Collections.sort(mapKeys, Collections.reverseOrder()); // Reverse since original ascending
String[] occurencesSortedByDescendingArray = mapKeys.toArray();
Feel free to comment.

If you want to use Guava:
Lists.transform(
Ordering
.natural()
.onResultOf(new Function<Multiset.Entry<String>, Integer>() {
public Integer apply(Multiset.Entry<String> entry) {
return entry.getCount();
}
})
.reverse()
.sortedCopy(
ImmutableMultiset.copyOf( Splitter.onPattern("\\s+").split(test) ).entrySet()
),
new Function<Multiset.Entry<String>, String>() {
public String apply(Multiset.Entry<String> entry) {
return entry.getElement();
}
}
);

I am not sure if a method exists for this exact purpose.
However, you could use the String.split() method to split the single string into an array of strings. From there, you could locate unique strings (either by manually checking or adding them all to a set, which would check for duplicates). Track (and increment a counter unique to each unique String) each time you add an element and it is not part of the collection. Then create an array that is sorted based on this count.
A map would be ideal for holding the String/count, as it would maintain the set of unique Strings as keys, and the count for each String as the value.

Related

Push values to a map when key is already exisitng in a map

I have declared a map as below:
Map<String, String[]> test = new HashMap<String, String[]>();
I have a variable empnames which is an array and deptname is a String and i have declared the deptname and empnames as below:
String deptname = ['Department']
String empnames = [['Test1']['Test2']]
if (deptname != null)
{
if (test.containsKey(deptname))
{
///
}
else
{
test.put(deptname, new String[]{empnames});
}
}
If the test map already contains deptname key then what condition i should write in if condition to append new values to department?
Since you tagged for [grails], I assume a Groovy answer is appropriate too. You can use a Map with .withDefault{ ... } to provide the content in case the key is missing. E.g.
def data = [["x", ["a", "b"]], ["x", ["c", "d"]]]
def test = [:].withDefault{[]} // XXX
data.each{ k, vs ->
test[k].addAll(vs) // if there is no key `k`, create an empty array, so `.addAll` just works
}
println(test.inspect())
// => ['x':['a', 'b', 'c', 'd']]
You can use the new methods in Java 8 like putIfAbsent to add new entry if key is not present and computeIfPresent to append values to an existing key of a map.
An example would be:
public static void main(String[] args) {
Map<String, String[]> test = new HashMap<>();
String deptname = "Department";
String[] empnames = {"Test1", "Test2"};
if (deptname != null){
test.putIfAbsent(deptname, empnames);
test.computeIfPresent(deptname, (dept, value) -> {
List<String> list = new ArrayList<>(Arrays.asList(value));
list.add("Test3");
value = list.toArray(value);
return value;
});
}
for(String s : test.get("Department")){
System.out.println(s);
}
}
Here the putIfAbsent tests if the key is present, if not adds a new key-value entry. The computeIfAbsent on the other hand tests if the key is present, if yes it computes the new value for the existing key-value entry.
The output of the above code is:
Test1
Test2
Test3
This is because initially the key Department was not present in the map test, so it was added to it along with the value empnames as an array.
In the second operation the method computeIfPresent checked that the key Department was already in the map so it appended the new String Test3 to existing value array of [Test1, Test2].
The same can be done for an List instead of an array:
public static void main(String[] args) {
Map<String, List<String>> test = new HashMap<>();
String deptname = "Department";
List<String> empnames = new ArrayList(Arrays.asList("Test1", "Test2"));
if (deptname != null){
test.putIfAbsent(deptname, empnames);
test.computeIfPresent(deptname, (dept, value) -> {
value.add("Test3");
return value;
});
}
for(String s : test.get("Department")){
System.out.println(s);
}
}
ArrayList<String> departmentList;
if(test.containsKey(key)){
// if the key has already been used, then and add a new value to it
list = test.get(key);
list.add(value);
test.put(key, list);
} else {
// if the key hasn't been used yet, then create a new ArrayList<String> object, add the value
list = new ArrayList<String>();
list.add(value);
test.put(key, list);
}
As others have suggested, this would be easier if you had an ArrayList instead of String[].
But since you have a String[], you will have to make a new array of old_array's_size + list_to_add and copy over the values from the old array into the new one plus the new value you want to append.
So inside of your if statement:
String [] oldList = test.get(deptName);
String[] newList = new String[oldList.length + empnames.length]; //Make a new array with enough space for the previous values at deptname but also the new ones you want to add
//Put all of the values from the existing value at deptname into a new array
for (int i = 0; i < oldList.length; i++)
newList[i] = oldList[i];
//Put all of the values from the list of values you want to add into the new array
for (int i = 0; i < empnames.length; i++)
newList[oldList.length + i] = empnames[i];
test.put(deptname, newList); //Put the completed list back into the map
Again, if you used some kind of List this would be easier. One good reason besides being able to append is that you can easily sort it alphabetically using Collections.sort.

Sorting a String list with Integer values in Java

I have a column VALUE in my table that contains:
`M_SYSCONFIG = 200600,2600000,700000600,110000600,150000600`
When I sort this list the result is:
110000600,150000600,110000600,200600,2600000,700000600
However, I need the list to be sorted as follows (treat the strings as integers):
200600,2600000,110000600,150000600,700000600
This is the code I have right now for sorting the list:
JSONArray jsArray = dbcon.callSelectRecords("SELECT CODE, VALUE FROM M_SYSCONFIG WHERE MODULE = 'LIMIT_CONFIG' AND CODE in (?,?,?,?) ORDER BY VALUE", ft_other_cn, ft_own_account, payment, purchase);
for (int i = 0; i< jsArray.size(); i++) {
JSONObject js = JSON.newJSONObject(jsArray.get(i).toString());
String trasactionType = JSON.get(js, "CODE");
String value = JSON.get(js, "VALUE");
List<String> data = Arrays.asList(value.split(","));
Collections.sort(data);
I need to obtain the results as strings because after sorting I want to apply the following code:
StringBuilder sbValue = new StringBuilder();
if(ft_other_cn.equals(trasactionType)) {
long limitOtherCimb = limit.getFtOtherCimbLimit();
sbValue.append(limitOtherCimb).append(",");
for(String values:data) {
Long limitSysConfig = null;
try {
limitSysConfig = Long.parseLong(values);
} catch (Exception e) {}
if(limitSysConfig == null) {
continue;
}
if(limitSysConfig > limitOtherCimb) {
continue;
}
sbValue.append(limitSysConfig).append(",");
}
customerLimit.setFtOtherCnLimit(StringUtils.removeEnd(sbValue.toString(), ","));
You need to convert you string values to integers like this and then need to sort.
JSONObject js = JSON.newJSONObject(jsArray.get(i).toString());
String trasactionType = JSON.get(js, "CODE");
String value = JSON.get(js, "VALUE");
List<String> data = Arrays.asList(value.split(","));
List<Integer> intList = new ArrayList<Integer>();
for(String s : data){
intList.add(Integer.valueOf(s));
}
Collections.sort(intList);
I suggest using biginteger because your numbers seems quite large. It's not the most efficient and optimized solution but yeah it will work
public static List<String> sortData(List<String> data){
List<BigInteger>convertedData=new ArrayList<BigInteger>();
for (String s : data)
{
//System.out.println(s);
convertedData.add(new BigInteger(s));
}
Collections.sort(convertedData);
List<String>sortedData=new ArrayList<String>();
for (BigInteger b : convertedData)
{
sortedData.add(String.valueOf(b));
}
return sortedData;
}
Your code:
JSONArray jsArray = dbcon.callSelectRecords("SELECT CODE, VALUE FROM M_SYSCONFIG WHERE MODULE = 'LIMIT_CONFIG' AND CODE in (?,?,?,?) ORDER BY VALUE", ft_other_cn, ft_own_account, payment, purchase);
for (int i = 0; i< jsArray.size(); i++) {
JSONObject js = JSON.newJSONObject(jsArray.get(i).toString());
String trasactionType = JSON.get(js, "CODE");
String value = JSON.get(js, "VALUE");
List<String> data = Arrays.asList(value.split(","));
List<String> sortedData=sortData(data); **<------**
Implement a Comparator like this:
Collections.sort(data, new Comparator<String>() {
#Override
public int compare(String o1, String o2) {
return new Long(Long.parseLong(o1)).compareTo(Long.parseLong(o2));
}
});
You can take help of streams introduced in java 8.
Just add the below line after creating the List and you would have sorted string list
List<String> data = Arrays.asList(value.split(","));
data=data.stream().mapToLong(Long::parseLong).sorted().mapToObj(String::valueOf).collect(Collectors.toList());
If you very large numbers you can use BigInteger
data=data.stream().map(BigInteger :: new ).sorted().map(String::valueOf).collect(Collectors.toList());
If you are using java 6,7 you would have to use a comparator as mentioned by Taher
Collections.sort(data, new Comparator<String>() {
#Override
public int compare(String o1, String o2) {
return new Long(Long.parseLong(o1)).compareTo(Long.parseLong(o2));
}
});
If you are not able to change your list, then the simplest way is to build a comparator using Java 8 and use the string values as bigintegers. You do not need to convert your string list to a number list.
List<String> list = Arrays.asList("200600,2600000,700000600,110000600,150000600".split(","));
list.sort(Comparator.comparing(item -> new BigInteger(item)));
System.out.println(list);
The magic happens within
Comparator.comparing(item -> new BigInteger(item))
With this you are constructing a Comparator (which is needed for sorting), that compares all items converted to BigIntegers.
You are sorting the numbers as Strings - as a string, 11 comes before 2. You need to first convert the array of strings to numbers, then sort them as numbers.
With the Streams API you can do that on one line:
String value = ...;
List<Long> data = Arrays.stream(value.split(",")).map(Long::new).sorted()
.collect(Collectors.toList());
Since you need them as Long later, I'm using Long as the numeric type.

write data from hashmap as a CSV file

Say I have a hashmap with String type as key and ArrayList type as value, example {"value1"=["one","three","five"], "value2"=["two","four","six"]} where "value1" and "value2" are keys. I want to write the above hashmap data in following format. (so that I can read the csv file in excel)
value1,value2
one,two
three,four
five,six
My idea was to write the first key and its values as follows
value1
one
three
five
Then I was thinking of using the seek method in RandomAccessFile class to back to line 1 and again write the second key and its values. However I am not able to accomplish this task since seek function takes in the length of strings in the entire file and writes the new string after it. While I wanted the pointer to jump to the first line and append the string. Is there a better way to do this?. A quick example would be much appreciated.
Thanks
Why can't you just use 4 Strings, one for each row? Something like this:
StringBuilder keyRow = new StringBuilder();
StringBuilder value1 = new StringBuilder();
StringBuilder value2 = new StringBuilder();
StringBuilder value3 = new StringBuilder();
Iterator keys = hashmap.keySet().iterator();
boolean notFirst = true;
while(keys.hasNext()) {
String key = (String)keys.next();
ArrayList list = (ArrayList)hashmap.get(key);
if(!notFirst) {
keyRow.append(",");
value1.append(",");
value2.append(",");
value3.append(",");
}
keyRow.append(key);
value1.append((String)list.get(0));
value2.append((String)list.get(1));
value3.append((String)list.get(2));
notFirst = false;
}
Then at the end, just take the 4 Strings
String csv = keyRow.toString()+"\n"+value1.toString()+"\n"+value2.toString()+"\n"+value3.toString();
Note that this example isn't really proper CSV. Strings with commas aren't wrapped in quotes.
Or you iterate through the HashMap a thousand times if you have thousands of these rows. To save a bit of time from looking up a key, you can put them all in an ArrayList:
StringBuilder csv = new StringBuilder();
int row = 0;
ArrayList<ArrayList> list = new ArrayList<ArrayList>();
// Write the keys row:
Iterator keys = hashmap.keySet().iterator();
boolean notFirst = true;
while(keys.hasNext()) {
String key = (String)keys.next();
ArrayList tmp = (ArrayList)hashmap.get(key);
if(!notFirst) {
csv.append(",");
}
csv.append(key);
// store list
list.add(tmp);
notFirst = false;
}
csv.append("\n");
// Write the rest of the rows
while(row<numberOfTotalRow) {
notFirst = true;
for(int x=0;x<list.size();x++) {
if(!notFirst) {
csv.append(",");
}
csv.append((String)list.get(x).get(row));
notFirst = false;
}
row++;
}
You can make a method that prints out the map as you wish:
public void toString(HashMap<String, ArrayList<String>> map) {
for(int i = 0; i < map.size(); i++) {
ArrayList<String> list = new ArrayList<String>(map.keySet());
String key = list.get(i);
System.out.println(key);
for(int j = 0; j < map.get(key).size(); j++)
System.out.println(map.get(key).get(j));
}
}
The way you have imagined is impossible. A file is a continuous stream of bytes. So after you write the first value, you have this in your file : "value1\none\nthree\nfive". If you then seek to position 6 (after "value") and insert new characters, you'll be overwiting the first value's second row. The following bytes won't be magically pushed away.
The only way to do this is to traverse the data you have in a way that allows you to output the bytes in the same order that they will be in the file. So: go to each value and write the first element, to each value again and write their second element and so on.
You don't need a RandomAccessFile file, better use this:
HashMap<String, ArrayList<String>> map = new HashMap<>();
map.put("a", new ArrayList<>(Arrays.asList(new String[]{"A1", "A2", "A3"})));
map.put("b", new ArrayList<>(Arrays.asList(new String[]{"B1", "B2", "B3"})));
map.put("c", new ArrayList<>(Arrays.asList(new String[]{"C1", "C2", "C3"})));
{
/**
* Set your file printstream. For testing System.out
*/
PrintStream ps = System.out;
boolean first = true;
/**
* Size of the array list. Let's asume that the arraylist are of the
* same lenght;
*/
int s = 0;
/**
* Create a ArrayList variable because, this map class makes no guarantees
* as to the order of the map; in particular, it does not guarantee that
* the order will remain constant over time.
*/
ArrayList<Entry<String, ArrayList<String>>> l =
new ArrayList<>(map.entrySet());
for (Entry<String, ArrayList<String>> e : l) {
if (first) {
first = false;
s = e.getValue().size();
} else {
ps.print(",");
}
ps.print(e.getKey());
}
ps.println();
for (int i = 0; i < s; i++) {
first = true;
for (Entry<String, ArrayList<String>> e : l) {
if (first) {
first = false;
} else {
ps.print(",");
}
ps.print(
e.getValue().get(i));
}
ps.println();
}
}
Output:
b,c,a
B1,C1,A1
B2,C2,A2
B3,C3,A3

Logic for Grouping number

I am having 1 -10 in different groups A,B and C.
For eg. A-1,A-2,A-3,B-4,C-5,B-6,A-7,C-8,A-9,A,10
I want to make group separately as A, B and C
A
1-3,
7,
9-10
B
4,
6
C
5,
8
can any one help me with logic..?
Guava will help you creating the Data Structure you need:
public static void main(final String[] args) {
String input = "A-1,A-2,A-3,B-4,C-5,B-6,A-7,C-8,A-9,A-10";
// create multimap
Map<String, Collection<Integer>> map=Maps.newTreeMap();
SortedSetMultimap<String, Integer> multimap = Multimaps.newSortedSetMultimap(
map, new Supplier<SortedSet<Integer>>() {
public SortedSet<Integer> get() {
return new TreeSet<Integer>();
}
});
//add data
Splitter entrySplitter = Splitter.on(',');
Splitter keyValueSplitter = Splitter.on('=');
for (String entry : entrySplitter.split(input)) {
Iterator<String> tokens = keyValueSplitter.split(entry).iterator();
multimap.put(tokens.next(), Integer.valueOf(tokens.next()));
}
// read data
for (Entry<String, Collection<Integer>> entry : map.entrySet()) {
System.out.println(entry.getKey()+":");
printMergedValues(entry.getValue());
}
}
private static void printMergedValues(Collection<Integer> value) {
// TODO implement this yourself
}
The only thing I left for you is to join the groups
Here is something to get you started:
String[] items = new String[] {
"A-1", "B-2", "A-5"
}
// This is the data structure that will receive the final data. The map key is the
// group name (e.g. "A" for item "A-15") and the map value is a list of numbers that
// have been found for that group. TreeMap is chosen because the groups will be sorted
// alphabetically. If you don't need that, you could also use HashMap.
Map<String, List<Integer>> groups = new TreeMap<String, List<Integer>>();
for (String item : items) {
// Split the item into the group and the number
String group = item.substring(0, 1);
String number = Integer.toString(item.substring(2));
// See if this group is already registered in our Map
List<Integer> groupData = groups.get(group);
if (groupData==null) {
groupData = new List<Integer>();
groups.put(group, groupData);
}
// Add the number to the data
groupData.add(number);
}
I assume here that your items are always in the form 1 letter dash number. If it is a bit more complicated than that, you'll want to have a look at regular expressions (see java class Pattern). This is not tested, I let you test it and handle the special cases.
This function will output for { "A-1", "A-2", "A-3", "B-2", "A-5" }:
A -> {1, 2, 3, 5}
B -> {2}
You'll need to process the resulting number lists if you want to merge consecutive numbers, but that should not be too difficult if you think about it a little while.
I'd go this way:
String[] input = {"A-1","A-2","A-3","B-4","C-5","B-6","A-7","C-8","A-9"};
Map<String, Set<Integer>> result = new HashMap<String, Set<Integer>>();
String[] inputSplit;
String group;
Integer groupNumber;
for (String item : input)
{
inputSplit = item.split("-");
group = inputSplit[0];
groupNumber = Integer.valueOf( inputSplit[1] );
if ( result.get(group) == null ) { result.put(group, new HashSet<Integer>()); }
result.get(group).add(groupNumber);
}
for (Map.Entry entry : result.entrySet())
{
System.out.println( entry.getKey() + ":" + entry.getValue() );
}

Efficient way to delete values from hashmap object

I have HashMap object contains a key x-y-z with corresponding value test-test1-test2.
Map<String,String> map = new HashMap<String,String>();
map.put("x-y-z","test-test1-test2");
map.put("x1-y1-z1","test-test2-test3");
Now I have an input string array that contains some piece of the key:
String[] rem={"x","x1"}
Based on this string array I want to remove HashMap values.
Can anyone give an efficient approach to do this operation?
List remList = Arrays.asList(rem);
for (Iterator it = map.keySet().iterator(); it.hasNext();) {
String key = (String) it.next();
String[] tokens = key.split("-");
for (int i = 0; i < tokens.length; i++) {
String token = tokens[i];
if (remList.contains(token)) {
it.remove();
break;
}
}
}
And an updated version with adding functionality based on your latest comment on this answer:
private static Map getMapWithDeletions(Map map, String[] rem) {
Map pairs = new HashMap();
for (int i = 0; i < rem.length; i++) {
String keyValue = rem[i];
String[] pair = keyValue.split("#", 2);
if (pair.length == 2) {
pairs.put(pair[0], pair[1]);
}
}
Set remList = pairs.keySet();
for (Iterator it = map.keySet().iterator(); it.hasNext();) {
String key = (String) it.next();
String[] tokens = key.split("-");
for (int i = 0; i < tokens.length; i++) {
String token = tokens[i];
if (remList.contains(token)) {
it.remove();
pairs.remove(token);
break;
}
}
}
map.putAll(pairs);
return map;
}
Edited based on edited question.
Loop through the keySet of the hashmap. When you find a key that starts with x you are looking for remove it from the map.
Something like:
for(String[] key: map.keySet()){
if(key.length>0 && x.equals(key[0])){
map.remove(key);
}
}
Assuming I understand you correctly, and you want to remove everything starting with 'x-' and 'x1-' from the map (but not 'x1111-', even though 'x1' is a prefix of 'x1111'), and efficiency is important, you might want to look at one of the implementations of NavigableMap, such as (for example) TreeMap.
NavigableMaps keep their entries in order (by natural key order, by default), and can be iterated over and searched very efficiently.
They also provide methods like subMap, which can produce another Map which contains those keys in a specified range. Importantly, this returned Map is a live view, which means operations on this map affect the original map too.
So:
NavigableMap<String,String> map = new TreeMap<String,String>();
// populate data
for (String prefixToDelete : rem) {
// e.g. prefixToDelete = "x"
String startOfRange = prefixToDelete + "-"; // e.g. x-
String endOfRange = prefixToDelete + "`"; // e.g. x`; ` comes after - in sort order
map.subMap(startOfRange, endOfRange).clear(); // MAGIC!
}
Assuming your map is large, .subMap() should be much faster than iterating over each Map entry (as a TreeMap uses a red-black tree for fast searching).
You can do the following:
Map<String,String> map = new HashMap<String,String>();
map.put("x-y-z","test-test1-test2");
map.put("x1-y1-z1","test-test2-test3");
String[] rem={"x","x1"};
for (String s : rem) {
map.keySet().removeIf(key -> key.contains(s));
}
This piece of code will remove all entries with "x" or "x1" in the map key.

Categories

Resources