views:

208

answers:

6

Assuming I have

final Iterable<String> unsorted = asList("FOO", "BAR", "PREFA", "ZOO", "PREFZ", "PREFOO");

What can I do to transform this unsorted list into this:

[PREFZ, PREFA, BAR, FOO, PREFOO, ZOO]

(a list which begin with known values that must appears first (here "PREFA" and "PREFZ") and the rest is alphabetically sorted)

I think there are some usefull classes in guava that can make the job (Ordering, Predicates...), but I have not yet found a solution...

+2  A: 

Note: This is not the most efficient solution. It is just a simple, straightforward solution that gets the job done.

I would first use Collections.sort(list) to sort the list.

Then, I would remove the known items, and add them to the front.

String special = "PREFA";
if (list.remove(special)
    list.add(0, special);

Or, if you have a list of array of these values you need in the front you could do:

String[] knownValues = {};
for (String s: knownValues) {
    if (list.remove(s))
        list.add(0, s);
}
jjnguy
list.remove() returns a boolean, not the object what you remove. But I think it could work: for (final String s : asList("PREFA", "PREFZ")) { if (list.remove(s)) { list.add(0, s); } }
Sylvain M
@Syl Oops, I got the two removes mixed up. I will fix it, thanks.
jjnguy
except you can't sort an Iterable and dumping it to a list first makes it ugly, as you can separate unknowns already when dumping.
unbeli
@unbeli: I agree with you, but now, it seems the simpliest way to do the job
Sylvain M
@Sylvain if you want the simplest way, don't tag it as 'puzzle' and 'excercise' ;)
unbeli
@unbeli: I'm new to stackoverflow, I must have missed something in the faq.
Sylvain M
@Syl Naw, you are fine. This was a perfect question.
jjnguy
A: 

I would also use Collections.sort(list) but I think I would use a Comparator and within the comparator you could define your own rules, e.g.

class MyComparator implements Comparator<String> {

    public int compare(String o1, String o2) {
        // Now you can define the behaviour for your sorting.
        // For example your special cases should always come first, 
        // but if it is not a special case then just use the normal string comparison.

        if (o1.equals(SPECIAL_CASE)) {
            // Do something special
        }
        // etc.
        return o1.compareTo(o2);
    }

}

Then sort by doing:

Collections.sort(list, new MyComparator());
DaveJohnston
+3  A: 

I would keep separate lists.

One for known values and unknown values. And sort them separately, when you need them in a one list you can just concatenate them.

knownUnsorted.addAll(unsorted.size - 1, unknonwUnsorted);
Ankiov Spetsnaz
I think it's fine, but I have to separate the list in entry.
Sylvain M
+3  A: 

I suggest filling List with your values and using Collections.sort(...).

Something like

Collections.sort(myList, new FunkyComparator());

using this:

class FunkyComparator implements Comparator {

    private static Map<String,Integer> orderedExceptions =
        new HashMap<String,Integer>(){{ 
            put("PREFZ", Integer.valueOf(1));
            put("PREFA", Integer.valueOf(2));
        }};

    public int compare(Object o1, Object o2) {
        String s1 = (String) o1;
        String s2 = (String) o2;
        Integer i1 = orderedExceptions.get(s1);
        Integer i2 = orderedExceptions.get(s2);

        if (i1 != null && i2 != null) {
            return i1 - i2;
        }
        if (i1 != null) {
            return -1;
        }
        if (i2 != null) {
            return +1;
        }
        return s1.compareTo(s2);
    }
}
Chadwick
not bad, but Set will kill duplicates. Also, no need for dummy equals()
unbeli
Hum, well, how can I transform this if I have more than two known values ?
Sylvain M
@unbeli both good points. Since switching from SortedSet to List doesn't change the gist of my answer, I've updated the 'usage' of the Comparator for a List.
Chadwick
@Sylvain_M, simply add as many 'known values' as you need in the `orderedExceptions` map. Be sure that the integers used are unique and ordered as you want them - the map keeps the lookup fast, but the integers are what dictate the final ordering.
Chadwick
Oh, yes, so it's not too hard to maintain. Thanks
Sylvain M
and switching to a list makes it the same as the other answer here (Justin 'jjnguy' Nelson), which is not nice, because you need to populate the list first. This population step could already take care of separation and then there is no need for Comparator.
unbeli
+2  A: 

Since I'm a fan of the guava lib, I wanted to find a solution using it. I don't know if it's efficient, neither if you find it as simple as others solution, but it's here:

final Iterable<String> all = asList("FOO", "BAR", "PREFA", "ZOO", "PREFOO", "PREFZ");
final List<String> mustAppearFirst = asList("PREFZ", "PREFA");
final Iterable<String> sorted = 
      concat(
            Ordering.explicit(mustAppearFirst).sortedCopy(filter(all, in(mustAppearFirst))),
            Ordering.<String>natural().sortedCopy(filter(all, not(in(mustAppearFirst)))));
Sylvain M
Nice approach. Had another go at a 'guavaey' approach in my answer, if you're curious
Cowan
+1  A: 

You specifically mentioned guava; along with Sylvain M's answer, here's another way (more as an academic exercise and demonstration of guava's flexibility than anything else)

// List is not efficient here; for large problems, something like SkipList 
// is more suitable
private static final List<String> KNOWN_INDEXES = asList("PREFZ", "PREFA");

private static final Function<Object, Integer> POSITION_IN_KNOWN_INDEXES 
    = new Function<Object, Integer>() {
  public Integer apply(Object in) {
     int index = KNOWN_INDEXES.indexOf(in);
     return index == -1 ? null : index;
  }     
};


...


List<String> values = asList("FOO", "BAR", "PREFA", "ZOO", "PREFZ", "PREFOO");

Collections.sort(values,
  Ordering.natural().nullsLast().onResultOf(POSITION_IN_KNOWN_INDEXES).compound(Ordering.natural())
);

So, in other words, sort on natural order of the Integer returned by List.indexOf(), then break ties with natural order of the object itself.

Messy, perhaps, but fun.

Cowan
Hi Cowan, I try your solution, but it does not compile. And when I modify it to compile (by adding asList() on the ordering and helping type inference), I got an NPE...
Sylvain M
Sorry Sylvain, wasn't near a compiler when I wrote that. Have fixed it. For some reason I thought `Ordering.compound()` was a static method; also you need `nullsLast().onResultOf()` rather than `onResultOf().nullsLast()` otherwise it's chaining things in the wrong order. Works for me now as-is.
Cowan
Now it works :) Thanks Cowan
Sylvain M
As I'm recommended to accept an answer, I accept this one because it's the most concise I've read. Well, it's subjective, but it's my preffered answer (concision + use of guava)
Sylvain M