Since Java doesn't allow passing methods as parameters, what trick do you use to implement Python like list comprehension in Java ?
I have a list (ArrayList) of Strings. I need to transform each element by using a function so that I get another list. I have several functions which take a String as input and return another String as output. How do I make a generic method which can be given the list and the function as parameters so that I can get a list back with each element processed. It is not possible in the literal sense, but what trick should I use ?
The other option is to write a new function for each smaller String-processing function which simply loops over the entire list, which is kinda not so cool.
In Java 8 you can use method references:
List<String> list = ...;
list.replaceAll(String::toUpperCase);
Or, if you want to create a new list instance:
List<String> upper = list.stream().map(String::toUpperCase).collect(Collectors.toList());
Basically, you create a Function interface:
public interface Func<In, Out> {
public Out apply(In in);
}
and then pass in an anonymous subclass to your method.
Your method could either apply the function to each element in-place:
public static <T> void applyToListInPlace(List<T> list, Func<T, T> f) {
ListIterator<T> itr = list.listIterator();
while (itr.hasNext()) {
T output = f.apply(itr.next());
itr.set(output);
}
}
// ...
List<String> myList = ...;
applyToListInPlace(myList, new Func<String, String>() {
public String apply(String in) {
return in.toLowerCase();
}
});
or create a new List (basically creating a mapping from the input list to the output list):
public static <In, Out> List<Out> map(List<In> in, Func<In, Out> f) {
List<Out> out = new ArrayList<Out>(in.size());
for (In inObj : in) {
out.add(f.apply(inObj));
}
return out;
}
// ...
List<String> myList = ...;
List<String> lowerCased = map(myList, new Func<String, String>() {
public String apply(String in) {
return in.toLowerCase();
}
});
Which one is preferable depends on your use case. If your list is extremely large, the in-place solution may be the only viable one; if you wish to apply many different functions to the same original list to make many derivative lists, you will want the map version.
The Google Collections library has lots of classes for working with collections and iterators at a much higher level than plain Java supports, and in a functional manner (filter, map, fold, etc.). It defines Function and Predicate interfaces and methods that use them to process collections so that you don't have to. It also has convenience functions that make dealing with Java generics less arduous.
I also use Hamcrest** for filtering collections.
The two libraries are easy to combine with adapter classes.
** Declaration of interest: I co-wrote Hamcrest
Apache Commons CollectionsUtil.transform(Collection, Transformer) is another option.
I'm building this project to write list comprehension in Java, now is a proof of concept in https://github.com/farolfo/list-comprehension-in-java
Examples
// { x | x E {1,2,3,4} ^ x is even }
// gives {2,4}
Predicate<Integer> even = x -> x % 2 == 0;
List<Integer> evens = new ListComprehension<Integer>()
.suchThat(x -> {
x.belongsTo(Arrays.asList(1, 2, 3, 4));
x.is(even);
});
// evens = {2,4};
And if we want to transform the output expression in some way like
// { x * 2 | x E {1,2,3,4} ^ x is even }
// gives {4,8}
List<Integer> duplicated = new ListComprehension<Integer>()
.giveMeAll((Integer x) -> x * 2)
.suchThat(x -> {
x.belongsTo(Arrays.asList(1, 2, 3, 4));
x.is(even);
});
// duplicated = {4,8}
You can use lambdas for the function, like so:
class Comprehension<T> {
/**
*in: List int
*func: Function to do to each entry
*/
public List<T> comp(List<T> in, Function<T, T> func) {
List<T> out = new ArrayList<T>();
for(T o: in) {
out.add(func.apply(o));
}
return out;
}
}
the usage:
List<String> stuff = new ArrayList<String>();
stuff.add("a");
stuff.add("b");
stuff.add("c");
stuff.add("d");
stuff.add("cheese");
List<String> newStuff = new Comprehension<String>().comp(stuff, (a) -> { //The <String> tells the comprehension to return an ArrayList<String>
a.equals("a")? "1":
(a.equals("b")? "2":
(a.equals("c")? "3":
(a.equals("d")? "4": a
)))
});
will return:
["1", "2", "3", "4", "cheese"]
import java.util.Arrays;
class Soft{
public static void main(String[] args){
int[] nums=range(9, 12);
System.out.println(Arrays.toString(nums));
}
static int[] range(int low, int high){
int[] a=new int[high-low];
for(int i=0,j=low;i<high-low;i++,j++){
a[i]=j;
}
return a;
}
}
Hope, that I help you :)
Related
The expectation is derive 3 lists itemIsBoth, aItems, bItems from the input list items.
How to convert code like below to functional style? (I understand this code is clear enough in an imperative style, but I want to know does declarative style really fail to deal with such a simple example). Thanks.
for (Item item: items) {
if (item.isA() && item.isB()) {
itemIsBoth.add(item);
} else if (item.isA()) {
aItems.add(item);
} else if (item.isB()){
bItems.add(item)
}
}
The question title is quite broad (convert if-else ladder), but since the actual question asks about a specific scenario, let me offer a sample that can at least illustrate what can be done.
Because the if-else structure creates three distinct lists based on a predicate applied to the item, we can express this behavior more declaratively as a grouping operation. The only extra needed to make this work out of the box would be to collapse the multiple Boolean predicates using a tagging object. For example:
class Item {
enum Category {A, B, AB}
public Category getCategory() {
return /* ... */;
}
}
Then the logic can be expressed simply as:
Map<Item.Category, List<Item>> categorized =
items.stream().collect(Collectors.groupingBy(Item::getCategory));
where each list can be retrieved from the map given its category.
If it's not possible to change class Item, the same effect can be achieved by moving the enum declaration and the categorization method outsize the Item class (the method would become a static method).
Another solution using Vavr and doing only one iteration over a list of items might be achieved using foldLeft:
list.foldLeft(
Tuple.of(List.empty(), List.empty(), List.empty()), //we declare 3 lists for results
(lists, item) -> Match(item).of(
//both predicates pass, add to first list
Case($(allOf(Item::isA, Item::isB)), lists.map1(l -> l.append(item))),
//is a, add to second list
Case($(Item::isA), lists.map2(l -> l.append(item))),
//is b, add to third list
Case($(Item::isB), lists.map3(l -> l.append(item)))
))
);
It will return a tuple containing three lists with results.
Of course, you can. The functional way is to use declarative ways.
Mathematically you are setting an Equivalence relation, then, you can write
Map<String, List<Item>> ys = xs
.stream()
.collect(groupingBy(x -> here your equivalence relation))
A simple example show this
public class Main {
static class Item {
private final boolean a;
private final boolean b;
Item(boolean a, boolean b) {
this.a = a;
this.b = b;
}
public boolean isB() {
return b;
}
public boolean isA() {
return a;
}
}
public static void main(String[] args) {
List<Item> xs = asList(new Item(true, true), new Item(true, true), new Item(false, true));
Map<String, List<Item>> ys = xs.stream().collect(groupingBy(x -> x.isA() + "," + x.isB()));
ys.entrySet().forEach(System.out::println);
}
}
With output
true,true=[com.foo.Main$Item#64616ca2, com.foo.Main$Item#13fee20c]
false,true=[com.foo.Main$Item#4e04a765]
Another way you can get rid of the if-else is to to replace them with Predicate and Consumer:
Map<Predicate<Item>, Consumer<Item>> actions =
Map.of(item.predicateA(), aItems::add, item.predicateB(), bItems::add);
actions.forEach((key, value) -> items.stream().filter(key).forEach(value));
Therefore you need to enhace your Item with the both mehods predicateA() and predicateB() using the logic you have implemented in your isA() and isB()
Btw I would still suggest to use your if-else logic.
Since you've mentioned vavr as a tag, I'm gonna provide a solution using vavr collections.
import static io.vavr.Predicates.allOf;
import static io.vavr.Predicates.not;
...
final Array<Item> itemIsBoth = items.filter(allOf(Item::isA, Item::isB));
final Array<Item> aItems = items.filter(allOf(Item::isA, not(Item::isB)));
final Array<Item> bItems = items.filter(allOf(Item::isB, not(Item::isA)));
The advantage of this solution that it's simple to understand at a glance and it's as functional as you can get with Java. The drawback is that it will iterate over the original collections three times instead of once. That's still an O(n), but with a constant multiplier factor of 3. On non-critical code paths and with small collections it might be worth to trade a few CPU cycles for code clarity.
Of course, this works with all the other vavr collections too, so you can replace Array with List, Vector, Stream, etc.
Not (functional in the sense of) using lambda's or so, but quite functional in the sense of using only functions (as per mathematics) and no local state/variabels anywhere :
/* returns 0, 1, 2 or 3 according to isA/isB */
int getCategory(Item item) {
return item.isA() ? 1 : 0 + 2 * (item.isB() ? 1 : 0)
}
LinkedList<Item>[] lists = new LinkedList<Item> { initializer for 4-element array here };
{
for (Item item: items) {
lists[getCategory(item)].addLast(item);
}
}
The question is somewhat controversial, as it seems (+5/-3 at the time of writing this).
As you mentioned, the imperative solution here is most likely the most simple, appropriate and readable one.
The functional or declarative style does not really "fail". It's rather raising questions about the exact goals, conditions and context, and maybe even philosophical questions about language details (like why there is no standard Pair class in core Java).
You can apply a functional solution here. One simple, technical question is then whether you really want to fill the existing lists, or whether it's OK to create new lists. In both cases, you can use the Collectors#groupingBy method.
The grouping criterion is the same in both cases: Namely, any "representation" of the specific combination of isA and isB of one item. There are different possible solutions for that. In the examples below, I used an Entry<Boolean, Boolean> as the key.
(If you had further conditions, like isC and isD, then you could in fact also use a List<Boolean>).
The example shows how you can either add the item to existing lists (as in your question), or create new lists (which is a tad simpler and cleaner).
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
public class FunctionalIfElse
{
public static void main(String[] args)
{
List<Item> items = new ArrayList<Item>();
items.add(new Item(false, false));
items.add(new Item(false, true));
items.add(new Item(true, false));
items.add(new Item(true, true));
fillExistingLists(items);
createNewLists(items);
}
private static void fillExistingLists(List<Item> items)
{
System.out.println("Filling existing lists:");
List<Item> itemIsBoth = new ArrayList<Item>();
List<Item> aItems = new ArrayList<Item>();
List<Item> bItems = new ArrayList<Item>();
Map<Entry<Boolean, Boolean>, List<Item>> map =
new LinkedHashMap<Entry<Boolean, Boolean>, List<Item>>();
map.put(entryWith(true, true), itemIsBoth);
map.put(entryWith(true, false), aItems);
map.put(entryWith(false, true), bItems);
items.stream().collect(Collectors.groupingBy(
item -> entryWith(item.isA(), item.isB()),
() -> map, Collectors.toList()));
System.out.println("Both");
itemIsBoth.forEach(System.out::println);
System.out.println("A");
aItems.forEach(System.out::println);
System.out.println("B");
bItems.forEach(System.out::println);
}
private static void createNewLists(List<Item> items)
{
System.out.println("Creating new lists:");
Map<Entry<Boolean, Boolean>, List<Item>> map =
items.stream().collect(Collectors.groupingBy(
item -> entryWith(item.isA(), item.isB()),
LinkedHashMap::new, Collectors.toList()));
List<Item> itemIsBoth = map.get(entryWith(true, true));
List<Item> aItems = map.get(entryWith(true, false));
List<Item> bItems = map.get(entryWith(false, true));
System.out.println("Both");
itemIsBoth.forEach(System.out::println);
System.out.println("A");
aItems.forEach(System.out::println);
System.out.println("B");
bItems.forEach(System.out::println);
}
private static <K, V> Entry<K, V> entryWith(K k, V v)
{
return new SimpleEntry<K, V>(k, v);
}
static class Item
{
private boolean a;
private boolean b;
public Item(boolean a, boolean b)
{
this.a = a;
this.b = b;
}
public boolean isA()
{
return a;
}
public boolean isB()
{
return b;
}
#Override
public String toString()
{
return "(" + a + ", " + b + ")";
}
}
}
I'm trying to understand how to use a method that uses generic. And I want to create a generic method that can passing data to it. for example I want to create an array and add it with a variable in that Method. Byetheway I'm using Java
this is the code that i was Trying:
public class test {
static int d;
public static <E> void AdditionArray (E[] arr, int var) {
for (E element : arr) {
d = element + var; //this is the big problem, I have no idea whether this is possible or not
System.out.println(d);
}
}
public static void main(String[] args) {
Integer [] arr1 = {1,4,5,7};
AdditionArray(arr1, 2);
}
}
so one line of my codes definitely was wrong, but how can i do that ?
a Method that using Generic and passing data to it ?
If you want use generic to add array element consider extending Number type
for instance:
public class Main {
static int d;
public static <E extends Number> void AdditionArray(E[] arr, int var) {
for (E element : arr) {
d = element.intValue() + var; //this is the big problem, I have no idea whether this is possible or not
System.out.println(d);
}
}
static String z;
public static <E extends CharSequence> void AdditionArray2(E[] arr, int var) {
for (E element : arr) {
z += element.toString() + var; //this is the big problem, I have no idea whether this is possible or not
System.out.println(z);
}
}
public static void main(String[] args) {
Integer[] arr1 = {1, 4, 5, 7};
AdditionArray(arr1, 2);
Double[] arr2 = {1.0, 4.0, 5.0, 7.0};
AdditionArray(arr2, 2);
String[] arr3 = {"a", "b", "c", "d"};
AdditionArray2(arr3, 2);
}
}
If you are familiar with what a Lambda is, you can do this very succinctly in Java 8+ using lambdas.
Here's an example
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
public class GenericAddition {
public static <E> List<E> map(List<E> elements, Function<E, E> mapper) {
List<E> result = new ArrayList<>();
for (E element : elements) {
result.add(mapper.apply(element));
}
return result;
}
public static void main(String...args) {
List<Integer> addedThree = map(Arrays.asList(5, 3, 4), (elem) -> elem + 3);
System.out.println(addedThree);
List<String> concatted = map(Arrays.asList("good", "guten", "baz"), (elem) -> elem + " day");
System.out.println(concatted);
}
}
This will print out:
[8, 6, 7]
[good day, guten day, baz day]
One catch is you have to use a List type because using E[] could be an array of primitives but your mapper function needs to take in objects (since type parameters must be of object types). You could easily code around this restriction by adding a wrapper function that converts an array to a list, Java will be smart and perform auto-boxing for you in that case.
I personally think this is very readable and also very flexible since you can define any kind of logic for performing the mapping i.e. if I wanted to do something like add 5 if its odd, or *2 if its even, I can define my own lambda function for doing this. That way, I can get more complex behavior without having to write separate methods for each different type that I want.
If you are using an older Java version, you could do the same thing with anonymous inner classes that implement a particular interface.
I have a List of Objects, lets say List<Example> and the class Example has a member a which is a String:
class Example {
String a;
String b;
}
Now I would like to get from List<Example> to List<String> by using only the a elements from each member of the list.
Of course this is easy to do with a loop, but I was trying to find something similar to the algorithms in C++ which could do this directly.
Question: What is the simplest way to project from List to List, in which the values are the field a of Example?
Edit: this is what I meant by for loop:
List<String> result = new ArrayList<String>();
for(Example e : myList)
result.add(e.a);
return result;
Here's a simple solution using Java 8's declarative stream mapping:
class Example {
String a;
String b;
// methods below for testing
public Example(String a) {
this.a = a;
}
public String getA() {
return a;
}
#Override
public String toString() {
return String.format("Example with a = %s", a);
}
}
// initializing test list of Examples
List<Example> list = Arrays.asList(new Example("a"), new Example("b"));
// printing original list
System.out.println(list);
// initializing projected list by mapping
// this is where the real work is
List<String> newList = list
// streaming list
.stream()
// mapping to String through method reference
.map(Example::getA)
// collecting to list
.collect(Collectors.toList());
// printing projected list
System.out.println(newList);
Output
[Example with a = a, Example with a = b]
[a, b]
Docs
General package API on Java 8 streams here
Specific API on Stream#map method here
You can use streams in java8
List<Example> l = Arrays.asList(new Example("a", "a"), new Example("b", "b"), new Example("c", "c"),
new Example("d", "d"));
List<String> names = l.stream().map(Example::getA).collect(Collectors.toList());
System.out.println(names);
I have a list of Strings. I want to evaluate each string based on a function that returns a double. Then I want the first 5 strings, based on their calculated values. If there are fewer than 5, I want all of them (in order). Let's say the strings are chemical compounds and the function computes the mass. The function is computationally expensive; I need to evaluate it once per string. (I'm just making up data here, though.)
H2O => 18.5
C12H11O22 => 109.1
HeNe => 32.0
H2SO4 => 54.37
HCl => 19.11
4FeO3 => 82.39
Xe6 => 281.9
The program should return the first five strings arranged in order by their respective values. For this sample data: H20, HCl, HeNe, H2SO4, 4FeO3. Actually, I don't really care about the order; I just need the five lowest in any order.
I thought about how I'd do this in Perl. It's just a few lines:
foreach $s (#str) {
$strmap{$s} = f($s);
}
#sorted = sort { $strmap{$a} <=> $strmap{$b} } keys %strmap;
return #sorted[0, 4]
But I need to do it in Java. And it's driving me crazy.
First I tried populating a HashMap<String, Double>, then using Collections.sort with a custom comparator, just like the Perl version. But scoping on the Comparator prevented it from referring to the HashMap to look up the values.
Then I tried a TreeMap<String, Double>, but it only sorts by key and no amount of coercing could get it to order the entries by value.
So I tried a TreeMap<Double, String>. It will discard entries with the same Double. However, the likelihood of having Strings that map to the same Double is low, so I pressed forward. Adding the entries to the TreeMap is no problem, but I ran into issues trying to extract the values from it.
TreeMap supplies a method called subMap, but its parameters are the keys that delimit the subset. I don't know what they are; I just want the first five of them. So I tried using the values method to get all the values out of the TreeMap, hoping they'd be in order. Then I can just get the first ten.
ArrayList<String> strs = (ArrayList<String>)(treemap.values());
return new ArrayList<String>(strs.subList(0, 5));
Nope. Runtime error: cannot cast TreeMap$Values to ArrayList.
List<String> strs = (List<String>)(treemap.values());
return new ArrayList<String>(strs.subList(0, 5));
Same. Runtime error trying to do the cast. OK, let's just assign to a Collection...
Collection<String> strs = treemap.values();
return new ArrayList<String>(strs.subList(0, 5));
Sorry, subList isn't a method of Collection.
Collection<String> strs = treemap.values();
ArrayList<String> a = new ArrayList<String>(strs);
return new ArrayList<String>(a.subList(0, 5));
Finally, something that works! But two extra data structures just to get the first five elements? And I'm not too wild about using Double as the key for TreeMap.
Is there a better solution?
I don't think you'll get more compact than the three lines above, not in Java.
Apart from that, I have the impression that a Map as a data structure is the wrong choice in the first place, since you do not seem to need by-string lookups (UNLESS you want in some way deal with multiple occurences of strings, but you didn't say so). An alternative approach would be to declare your own comparable data record class:
private static class Record implements Comparable<Record> {
// public final fields ok for this small example
public final String string;
public final double value;
public Record(String string, double value) {
this.string = string;
this.value = value;
}
#Override
public int compareTo(Record other) {
// define sorting according to double fields
return Double.compare(value, other.value);
}
}
// provide size to avoid reallocations
List<Record> records = new ArrayList<Record>(stringList.size());
for(String s : stringList)
records.add(new Record(s, calculateFitness(s));
Collections.sort(records); // sort according to compareTo method
int max = Math.min(10, records.size()); // maximum index
List<String> result = new ArrayList<String>(max);
for(int i = 0; i < max; i++)
result.add(records.get(i).string);
return result;
This is now much more verbose than the three lines above (this is Java, after all), but also includes the code that would be required to insert the key/value pairs into the map.
Would something like the following work for you?
Note that I've assumed you don't require the double value other than to sort the data.
public static void main(String[] args) throws Exception {
List<String> data = new ArrayList<>(Arrays.asList("t", "h", "i", "s", "i", "s", "t", "e", "s", "t", "d", "a", "t", "a"));
Collections.sort(data, new Comparator<String>() {
#Override
public int compare(String o1, String o2) {
double o1Value = evaluate(o1);
double o2Value = evaluate(o2);
return Double.compare(o1Value, o2Value);
}
});
List<String> result = data.subList(0, 10); // Note the end point is exclusive
for (String s : result) {
System.out.println(s);
}
}
private static double evaluate(String s) {
return s.codePointAt(0); // Nonsense, I know
}
This example prints:
a
a
d
e
h
i
i
s
s
s
Why don't you just create a class to combine the String, Double and function that does the calculation - something like:
public Thing implements Comparable<Thing>
{
private String s;
private Double d;
public Thing(String s)
{
this.s = s;
this.d = calculateDouble(s);
}
public String getString()
{
return this.s;
}
public Double getDouble()
{
return this.d;
}
public int compareTo(Thing other)
{
return getDouble().compareTo(other.getDouble());
}
public Double calculateDouble(String s)
{
...
}
}
Then all you need is a List<Thing>, Collections.sort and List.subList.
I prototype in python and I'm used the zip function for this, I'm not sure how to do this in Java. Basically I have two lists (one is names and one is data) and want them sorted in relation to each other. My program only processes a list (data, in this case) but I use the names as a reference to what data I'm processing and I want to try to experiment with processing my data in a different order. Here's an example of the structure (in reality my data is not given to me stored but I would do either a basic sort or a reverse sort on it, nothing fancy).
String[] names = new String[]{"Monkey1", "Dog2", "Horse3", "Cow4", "Spider5"};
int[] data = new int[]{1, 2, 3, 4, 5};
so the inverse would be
name = Spider5, Cow4, Horse3, Dog2, Monkey1
data = 5, 4, 3, 2, 1
I found this question: Is there an accepted Java equivalent to Python's zip(), but I would rather (if possible and for the faint of heart) do this using libraries I already have (Java commons, apache commons, etc). If there's no other way then I'll give functional java a shot. Any suggestions?
If you really don't want to redo your data-structures to combine the infos, you can use a Multimap to do it.
This example utilizes the excellent Google-Guava Library, which you should be using anyway :) https://code.google.com/p/guava-libraries/
String[] names = new String[] {"Monkey1", "Dog2", "Horse3", "Cow4", "Spider5"};
int[] data = new int[] {1,2,3,4,5};
/* guava, throws an IllegalStateException if your array aren't of the same length */
Preconditions.checkState(names.length == data.length, "data and names must be of equal length");
/* put your values in a MultiMap */
Multimap<String, Integer> multiMap = LinkedListMultimap.create();
for (int i=0; i<names.length; i++) {
mmap.put(names[i], data[i]);
}
/* our output, 'newArrayList()' is just a guava convenience function */
List<String> sortedNames = Lists.newArrayList();
List<Integer> sortedData = Lists.newArrayList();
/* cycle through a sorted copy of the MultiMap's keys... */
for (String name : Ordering.natural().sortedCopy(mmap.keys())) {
/* ...and add all of the associated values to the lists */
for (Integer value : mmap.get(name)) {
sortedNames.add(name);
sortedData.add(value);
}
}
Here's complete code:
StringIntTuple.java:
public class StringIntTuple{
public final int intValue;
public final String stringValue;
public StringIntTuple(int intValue, String stringValue){
this.intValue = intValue;
this.stringValue = stringValue;
}
public String toString(){
return "(" + this.intValue + ", " + this.stringValue + ")";
}
}
StringIntTupleStringComparator.java:
import java.util.Comparator;
public class StringIntTupleStringComparator implements
Comparator<StringIntTuple> {
#Override
public int compare(StringIntTuple a, StringIntTuple b) {
// TODO Auto-generated method stub
return a.stringValue.compareTo(b.stringValue);
}
}
StringIntTupleIntComparator.java:
import java.util.Comparator;
public class StringIntTupleIntComparator implements Comparator<StringIntTuple> {
#Override
public int compare(StringIntTuple a,
StringIntTuple b) {
return ((Integer)a.intValue).compareTo((Integer)b.intValue);
}
}
Driver.java:
import java.util.ArrayList;
import java.util.Collections;
public class Driver {
/**
* #param args
*/
public static String[] names = new String[] {"Monkey1", "Dog2", "Horse3", "Cow4", "Spider5"};
public static int[] data = new int[] {1,2,3,4,5};
public static void main(String[] args) {
ArrayList<StringIntTuple> list = new ArrayList<StringIntTuple>();
for(int i =0; i<names.length; i++){
list.add(new StringIntTuple(data[i],names[i]));
}
Collections.sort(list, new StringIntTupleIntComparator());
System.out.println(list.toString());
Collections.sort(list, new StringIntTupleStringComparator());
System.out.println(list.toString());
}
}
Output (sorted first by int field, then by String field):
[(1, Monkey1), (2, Dog2), (3, Horse3), (4, Cow4), (5, Spider5)]
[(4, Cow4), (2, Dog2), (3, Horse3), (1, Monkey1), (5, Spider5)]
EDIT 1 (extra info):
If you want to make this work for any Tuple, i.e. which doesn't constrain the field types to int, String, you can simply do the same operation with generics, i.e.:
public class Tuple<A,B>{
public Tuple(A aValue, B bValue){
this.aValue = aValue;
this.bValue = bValue;
}
public final A aValue;
public final B bValue;
}
Then, just tweak the Comparators accordingly, and you have a generic solution.
EDIT 2(After lunch): Here it is.
public class TupleAComparator<A extends Comparable<A>,B extends Comparable<B>> implements Comparator<Tuple<A,B>> {
#Override
public int compare(Tuple<A, B> t1, Tuple<A, B> t2) {
return t1.aValue.compareTo(t2.aValue);
}
}
EDIT 3: Code supplement as answer to Comment #1 (augmenting comment #2)
TupleArrayList.java:
import java.util.ArrayList;
import java.util.List;
public class TupleArrayList<A,B> extends ArrayList<Tuple<A,B>> {
/**
* An ArrayList for tuples that can generate a List of tuples' elements from a specific position within each tuple
*/
private static final long serialVersionUID = -6931669375802967253L;
public List<A> GetAValues(){
ArrayList<A> aArr = new ArrayList<A>(this.size());
for(Tuple<A,B> tuple : this){
aArr.add(tuple.aValue);
}
return aArr;
}
public List<B> GetBValues(){
ArrayList<B> bArr = new ArrayList<B>(this.size());
for(Tuple<A,B> tuple : this){
bArr.add(tuple.bValue);
}
return bArr;
}
}
So the obvious answer here is to wrap the name and data values in a class. Then maintain a List of that class. The class should implement equals, hashCode and Comparable which then then allow sorting the list using Collections.sort.
Maintain related data in two different lists is anti-OOP.
Something like this.
class MyWrapper implements Comparable<MyWrapper>{
private String name;
private int data;
}
List<MyWrapper> listToBeSorted;
The "right" way to do this in Java is to create a combined object that holds the corresponding elements, and to sort that.
Example:
class NameAndData {
private final String name;
private final int data;
}
List<NameAndData> toBeSorted;
and then you create a list of the combined elements and sort that. Basically, you're writing your own specific Pair class. (I, and many Java developers, think that adding a Pair class to Java would just lead to more obfuscated code -- a LatLong class, for example, is much less ambiguous about what it means than a Pair<Double, Double>.)
In some cases it doesn't make much sense to create a new class just to do concurrent sorting.
Here, is a function that can be used to sort an arbitrary number of Lists with arbitrary types based on a key that implement Comparable (Ideone Example here).
Usage
Here is an example of how you can use the function to sort multiple lists of arbitrary types:
// Can be any type that implements Comparable, Dupes are allowed
List<Integer> key = Arrays.asList(4, 3, 1, 2, 1);
// List Types do not need to be the same
List<String> list1 = Arrays.asList("Four", "Three", "One", "Two", "One");
List<Character> list2 = Arrays.asList('d', 'c', 'a', 'b', 'a');
// Sorts key, list1, list2
// Remove second key if you don't want to sort key.
multiSort(key, key, list1, list2);
Output:
key: [1, 1, 2, 3, 4]
list1: [One, One, Two, Three, Four]
list2: [a, a, b, c, d]
Code
An Ideone Example can be found here which includes validation of parameters and a test case.
public static <T extends Comparable<T>> void multiSort(
final List<T> key, List<?>... lists){
// Create a List of indices
List<Integer> indices = new ArrayList<Integer>();
for(int i = 0; i < key.size(); i++) {
indices.add(i);
}
// Sort the indices list based on the key
Collections.sort(indices, new Comparator<Integer>() {
#Override public int compare(Integer i, Integer j) {
return key.get(i).compareTo(key.get(j));
}
});
// Create a mapping that allows sorting of the List by N swaps.
// Only swaps can be used since we do not know the type of the lists
Map<Integer,Integer> swapMap = new HashMap<Integer, Integer>(indices.size());
List<Integer> swapFrom = new ArrayList<Integer>(indices.size()),
swapTo = new ArrayList<Integer>(indices.size());
for (int i = 0; i < key.size(); i++) {
int k = indices.get(i);
while (i != k && swapMap.containsKey(k)) {
k = swapMap.get(k);
}
swapFrom.add(i);
swapTo.add(k);
swapMap.put(i, k);
}
// use the swap order to sort each list by swapping elements
for (List<?> list : lists)
for (int i = 0; i < list.size(); i++)
Collections.swap(list, swapFrom.get(i), swapTo.get(i));
}
You could use a ConcurrentSkipListMap which can provide forward and reverse iterators over the keys. If you are looking for arbitrary re-orderings besides a fixed forward and reverse ordering, you'll have to go to something else. Or you can always keep a simple HashMap or whatever to maintain parallel item associations, and then construct a SortedMap (Treemap or ConcurrentSkipListMap) as needed by providing an appropriate Comparator.
The disadvantage of this approach is that the associations between keys/values are much more transient, and can be more easily and accidentally broken by updates to the map. All of the other answers that create Tuples, Pairs, or other explicit 1-1 relationships address that better. Of course, if you intend for the associations to be more fluid, then just using a map adds a bit of an advantage.
Assuming that the lengths of these two arrays are the same, you can create a list of map entries containing pairs of elements from these arrays, and sort this list in reverse order by key as follows:
String[] names = new String[]{"Monkey1", "Dog2", "Horse3", "Cow4", "Spider5"};
int[] data = new int[]{1, 2, 3, 4, 5};
List<Map.Entry<Integer, String>> entryList = IntStream
.range(0, names.length)
.mapToObj(i -> Map.entry(data[i], names[i]))
.sorted(Map.Entry.<Integer, String>comparingByKey().reversed())
.collect(Collectors.toList());
System.out.println(entryList);
// [5=Spider5, 4=Cow4, 3=Horse3, 2=Dog2, 1=Monkey1]
If you want to replace the contents of the arrays:
IntStream.range(0, entryList.size()).forEach(i -> {
data[i] = entryList.get(i).getKey();
names[i] = entryList.get(i).getValue();
});
System.out.println(Arrays.toString(data));
// [5, 4, 3, 2, 1]
System.out.println(Arrays.toString(names));
// [Spider5, Cow4, Horse3, Dog2, Monkey1]
See also: Sorting two parallel arrays