Java: Tricky sorting of prefixed Strings (ArrayLists) - java

(No networking knowledge required whatsoever. This is purely String and Lists).
Say I have a function in place, one that accepts a list of String IPv4 dotted address, and sorts them in ascending order. (Not alphabetical, true ip long format sorting). Let's call this:
public static ArrayList<String> sortListOfIpv4s(ArrayList<String> unsortedIPv4s);
This function already works correctly. Given an input:
192.168.1.1, 8.8.8.8, 4.5.6.7, 244.244.244.244, 146.144.111.6
It will output the list:
4.5.6.7, 8.8.8.8, 146.144.111.6, 192.168.1.1, 244.244.244.244
(Let's not get into a debate on whether it should modify the list in place or return a new list. It just returns a new list. Also, the function cannot be modified because of numerous reasons.)
However, my input list looks like this:
e192.168.1.1, f8.8.8.8, e4.5.6.7, f244.244.244.244, e146.144.111.6
When I remove the prefixes (only one of e or f, NOT NECESSARILY alternating) and create a clean array to pass to the sorting function, I lose the prefix information. What I would like is an output of the type:
e4.5.6.7, f8.8.8.8, e146.144.111.6, e192.168.1.1, f244.244.244.244
Basically, prior to sorting, whatever prefix was present for each element in the unsorted list, the same prefix needs to be added back to the elements in the sorted list.
Caveats:
An IP Address can repeat in the original list, a maximum of two times
When repeating twice, each of the two elements will have the same prefix, guaranteed
Sorting algorithm will not remove duplicates.
A little algorithmic help please? (Remember, we already have a function that can sort clean IPv4 String arraylists).

Don't remove the prefixes prior to passing it to the sorting function. Instead, in the sortListOfIpv4s method, always compare Strings using s.substring(1), which will give you the entire string without the prefix, and add s to the resulting sorted array.
If sortListOfIpv4s is a black box and you are required to pass the prefix-free Strings, then you could cache the prefixes beforehand in a Map from prefix-free IP -> prefix:
Map<String, String> prefixMap = new HashMap<String, String>();
for (String ip : unsortedIPv4s) {
prefixMap.put(ip.substring(1), ip.substring(0, 1));
}
Then sort and recover the prefixes from the Map:
List<String> sortedIPV4s = sortListOfIpv4s(unsortedIPv4s);
for (String ip : sortedIPV4s) {
String prefix = prefixMap.get(ip);
String originalIp = prefix + ip;
}

Your method could move any prefix to the end of the String, sort the list, and then go through the Strings again and move the prefixes from the end back to the start.

You could implement Comparator:
public class IpComparator implements Comparator<String> {
#Override
public int compare(String ipA, String ipB) {
return doComparison( ipA.substring(1), ipB.substring(1) );
}
}
Then you can use it:
return Collections.sort(unsortedIPv4s, new IpComparator());

Related

TreeSet not arranging value in ascending order

I am trying to create a TreeSet to sort the strings which are inserted to be in an ascending order. I am using below code for entering values in TreeSet.
TreeSet<String> ts = new TreeSet<String>();
ts.add("#Test0");
ts.add("#Test1");
ts.add("#Test2");
ts.add("#Test3");
ts.add("#Test10");
ts.add("#Test4");
System.out.println("Tree set :: "+ts);
Output:
Tree set :: [#Test0, #Test1, #Test10, #Test2, #Test3, #Test4]
You've used the no-args TreeSet constructor. This means TreeSet will order its elements based on natural order. It's the way the objects compare themselves: It means the things you add must be of a type that implements Comparable<Self>. String does that: The String class is defined to implement Comparable<String>. However, the way strings compare themselves is lexicographically. 10 comes before 2 for the same reason that aa comes before b.
You have two routes available to fix this:
Don't put strings in there but some other object that implements Comparable and does it right. Perhaps a class Thingie {String name; int idx;}.
Pass a Comparator as first and only argument to your TreeSet class. Write code that determines that #Test10 comes before #Test2. Then, TreeSet uses this comparator to determine ordering and won't use the one built into strings.
Specify the Comparator to sort on the number part only. This removes all but the number portion, converts that to an integer and sorts on that.
TreeSet<String> ts = new TreeSet<String>(Comparator.comparing(
s -> Integer.valueOf(s.replace("#Test", ""))));
ts.add("#Test0");
ts.add("#Test1");
ts.add("#Test2");
ts.add("#Test3");
ts.add("#Test10");
ts.add("#Test4");
System.out.println(ts);
prints
[#Test0, #Test1, #Test2, #Test3, #Test4, #Test10]
This works for the shown example. You may need to modify it somewhat for more varied data. But it demonstrates the idea.
#Test10 comes before #Test2 because 1 comes before 2. That's how the default ordering of String works (String implements the interface Comparable to do this sorting).
To solve your issue you need to provide a custom Comparator to the TreeSet, and do the comparison by parsing the integer within the string:
TreeSet<String> ts = new TreeSet<String>(new Comparator<String>() {
#Override
public int compare(String s1, String s2) {
return Integer.parseInt(s1.substring(5)) - Integer.parseInt(s2.substring(5));
}
});
The comparator can be constructed using the static convenience method:
TreeSet<String> ts = new TreeSet<>(Comparator.comparing(s -> Integer.parseInt(s.substring(5))));
As #Jems noted in the comment, strings are sorted lexichographically, so "#Test10" will come before "#Test2". If could however, supply a custom Comparator to define the order you need. E.g., if you know all the strings will have the form of "#Test" followed by a number, you could extract this number and sort accordingly:
TreeSet<String> ts =
new TreeSet<>(Comparator.comparingInt(s -> Integer.parseInt(s.substring(5))));

Getting individual entries of a Set in Java?

So I am currently having a problem understanding how to access a set, is that even allowed? So I understand my set of names contains a set of Character objects. I also understand that my toString() method call converts these Character objects into a String, but not a conventional string -- which is why I have [s,a] rather than [sa]. So my question is, is if there is a way to make me have a list of individual strings. So I want my list to be = [s, a] rather than [ [s,a] ]. Is this even possible? I apologize if this makes no sense; nevertheless, if you do understand my fumbled explanation thank you for your time and help. If you need for me to explain more, I will.
//this all works
Set<Character> names = find(prefix).getRollCall().keySet();
//[s,a]
String lists = names.toString();
//[s,a]
List<String> sloop = Arrays.asList(lists);
//[[s,a]]
If you want to convert a Set<Character> to a List<Character> you can do
List<Character> list = new ArrayList<Character>(set);
If you want to convert a Set<Character> to a List<String> you can do
List<String> list = new ArrayList<String>();
for (char c : set)
list.add("" + c);
Don't use toString() at all. Iterate over the elements of the Set and build up whatever string you like.
Set<Character> names = find(prefix).getRollCall().keySet();
for (Character c : names)
{
// whatever you like
}
Let me explain what's happening in your code, in case you aren't clear:
String lists = names.toString();
This calls the standard toString method for a collection which converts your set to an ordinary (conventional) string in a standard format (i.e. comma delimited, in brackets). There's nothing special about the string that is created: "[s, a]".
List<String> sloop = Arrays.asList(lists);
The asList method takes one or more arguments and converts them into a list. Because you've given it only a single argument lists it creates a list with a single string element: ("[s, a]")
Then, later, I suspect you are doing something like:
System.out.println(sloop);
This again calls the standard toString method for a collection (in this case the List sloop) and again creates a comma delimited, bracket enclosed standard string: "[[s, a]]"
So, most of that is probably not what you want. Your lists variable isn't a List, it's a String which I assume isn't what you want.
If you are just looking to convert your set of character to a list of strings, then this is pretty trivial in Java 8:
List<String> lists = names.stream().map(Character::toString).collect(Collectors.toList());

Collections.sort(String), doesn't work for me?

ok. Rookie question.
I have a scanned string that i would like to sort using Collections.sort(string).
The string comes from a scanned file that has a bunch of "asdfasdfasdf" in it.
I have scanned the file (.txt) and the scannermethod returns a String called scannedBok;
The string is then added to an ArrayList called skapaArrayBok();
here is the code:
public ArrayList<String> skapaArrayBok() {
ArrayList<String> strengar = new ArrayList<String>();
strengar.add(scanner());
Collections.sort(strengar);
return (strengar);
}
in my humble rookie brain the output would be "aaadddfffsss" but no.
This is a schoolasigment and the whole purpose of the project is to find the 10 most frequent words in a book. But i can't get it to sort. But i just would like to know why it won't sort the scanned string?
You are sorting the list, not the String. The list has only one element, so sorting it doesn't change anything.
In order to sort the content of the String, convert it to an array of characters, and sort the array.
Collections.sort() sorts the items in a list.
You have exactly one item in your list: the string "aaadddfffsss". There's nothing to sort.
SUGGESTIONS:
1) Read more strings into your collection
public ArrayList<String> skapaArrayBok() {
ArrayList<String> strengar = new ArrayList<String>();
// Input three strings
strengar.add(scanner());
strengar.add(scanner());
strengar.add(scanner());
// Now sort
Collections.sort(strengar);
... or ...
2) Split the string into characters, and sort the characters.
public ArrayList<String> skapaArrayBok() {
// Get string
String s = scanner());
char[] charArray = s.toCharArray();
Arrays.sort(charArray );
// Return sorted string
return (new String(charArray);
It is correct to sort "strengar", the ArrayList. However, it would not change the ordering of the ArrayList if you've only added one String to it. A list with one element is already sorted. If you want to sort the ArrayList, you should call add() on the ArrayList with each String you need to add, then sort.
You want to sort the characters within the String which is completely different and you need to re-organize the characters, a possible solution (especially knowing that Strings are immutable) is the following Sort a single String in Java

Howto transform each set of two elements of a source list into a transformed list?

I have a List<String> with elements like:
"<prefix-1>/A",
"<prefix-1>/B",
"<prefix-2>/A",
"<prefix-2>/B",
"<prefix-3>/A",
"<prefix-3>/B",
that is, for every <prefix>, there are two entries: <prefix>/A, <prefix>/B. (My list is already sorted, the prefixes might have different length.)
I want the list of prefixes:
"<prefix-1>",
"<prefix-2>",
"<prefix-3>",
What is a good way to transform a source list, when multiple (but always a constant amount of elements) correspond to one element in the transformed list?
Thank you for your consideration
If the prefixes are always a constant length, you can trim them out and put them into a Set:
List<String> elements = // initialize here
Set<String> prefixes = new HashSet<String>();
for( String element : elements) {
String prefix = element.substring(0,"<prefix-n>".length());
prefixes.add(prefix);
}
// Prefixes now has a unique set of prefixes.
You can do the same thing with regular expressions if you have a variable length prefix, or if you have more complex conditions.
Here is a solution that does not change the order of prefixes in the result. Since the elements are pre-sorted, you can take elements until you find a prefix that differs from the last taken element, and add new elements to the result, like this:
List<String> res = new ArrayList<String>();
String last = null;
for (String s : src) {
String cand = s.substring(0, s.lastIndexOf('/'));
// initially, last is null, so the first item will always be taken
if (!cand.equals(last)) {
// The assignment of last happens together with addition.
// If you think it's not overly readable, you can move it out.
res.add(last = cand);
}
}
Here is a demo on ideone.
If the number if structurally similar elements is always the same, then you cam just loop over the beginning of the list to find out this number, and then skip elements to construct the rest.
public List<String> getMyList(prefix){
List<String> selected= new ArrayList<String>();
for(String s:mainList){
if(s.endsWith(prefix.toLower())) // or .contains(), depending on
selected.add(s); // what you want exactly
}
return selected;
}

Filter out items from Set in Java

I have a list of items(i.e Strings) which I need to sort/filter.
The end result should not contain any duplicate (easy), I'll put them all in the Set. So I have a Set of strings now.
more explanation..
I also have a method x that calculates the amount of difference between two Strings (using levenstein distance).
Question:
Before inserting new String string into my Set set I want to check for levenstein distance using method x between string and any other String in the set and if x returns >=3 than I should not add it.
What is my best shot of doing this? Except iterate trough set for each string to be inserted?
Iterating through the Set is going to be your best bet, since there isn't any built-in Set implementation that would help you narrow the possibilities.
I have played with my idea of how to do it. I cannot think of a way to do this without any amount of iteration.
Supposing you have method named distance(String,String):int that returns the given distance between two Strings.
String x = "Obi-wan"; //this is the item subject to eval addition
List<String> items = new ArrayList<String>(asList("Luke","Yoda","Anakin"));
if (items.filter(s -> distance(s, x) >= 3).getFirst() == null) {
items.add(x);
}
If you use JDK8 Preview you can do this in no time using exactly the code above. The Iterables.getFirst() method would not iterate the whole collection, but only until the first element satisfying the criteria is found.
Otherwise you will probably have to implement a Predicate interface and filtering method.
interface Predicate<T> {
public boolean eval(T o);
}
public static void main(String[] args) {
final String x = "Obi-wan"; //this is the item subject to eval addition
List<String> items = new ArrayList<String>(asList("Luke","Yoda","Anakin"));
Predicate<String> p = new Predicate<String>() {
public boolean eval(String s){
return distance(s, x) >= 3;
}
};
if(filter(items, p).isEmpty()){
items.add(x);
}
}
public static <T> List<T> filter(List<? extends T> items, Predicate<? super T> predicate){
List<T> destiny = new ArrayList<T>();
for(T item : items){
if(predicate.eval(item){
destiny.add(item);
}
}
return destiny;
}
Alternatively, you could stop filtering once you find the first item that satisfies your criteria.
You can use a custom comparator when creating the set. In your comparator you return that two strings are the same if they are the same (as per regular string comparison rules) or if their Levenstein distance fulfills your criteria.
When your comaprator says two strings are the same, the new string is not inserted into the set. (Note that this means the end result of the string might depend on the order of insertion)
Update: Addressing comments about total ordering:
Using a comparator like the one suggested above would make the endresult dependent on the order of insertion (as noted above), as would any other solution as the Levenstein distance criteria used does not define total ordering.
OTOH, once a string passes the not-equal test and is inserted into the set, no other string in the set will compare equal to this one, so the strings in the set will use their natural string ordering, which does define total ordering, so no further inconsistencies arise within the set's internal operations (e.g. sorting).

Categories