views:

87

answers:

3

Not sure how to word this algorithm question, actually. I have toggle buttons in an android app, each corresponding to a numbered channel. Channels are (1,n) but the toggle button ids are some unknown, unsequential (to the buttons) integer. I need to collect the channel numbers and build a command string. My 1995-based Java skills have given me this:

String ch;
int i = 1;
for (ToggleButton toggle : toggles) {
    if (toggle.isChecked()) {
        ch = String.format("+%d", i+1);
        channels = channels.concat(ch);
    }
} 

If toggle buttons 1,2,4,5,6,7,11,13,21,22,23,24,25 are checked, this code snippet successfully gives me the string "+1+2+4+5+6+7+11+13+21+22+23+24+25"

However, what I would more like to do is have the string "+1/2, +4/7, +11, +13, +21/25"

I wonder if there's an easier way to do this than multiple if statements:

String ch;
int it = 0;
int last = 1;
for (ToggleButton toggle : toggles ) {
    if (toggle.isChecked()) {
    if (it == last + 1) {
            // somehow continue or note that we're only adding the "/"
        } else {
            // If we're not the next one, then build the last string
            // of "n/last" and then add ", +" to start building next string
        }
    }
    last++;
}

That seems like a bit of brute force type of algorithm, so I don't know if there's a more elegant solution that Java might have in it's bag of tricks (Or, more likely, that I should have)

Thanks.

A: 

Yours won't work, there is an end-case problem where you will have to duplicate your code to clean up after a range ends (try it on your dataset)

I know you were looking for short/terse, but my preference is to make the business logic code shorter and cleaner by doing something like this:

Counter counter=new Counter();

for(int i=1;i<=toggles.length;i++)
    if(toggles.get(i).isChecked())
        counter.append(i);

System.out.println(counter.getResult());

so that portion of your code is shorter, but you need a new class. This class is reusable, any time you want to re-create the same kind of range list, you've got it!. Both sections of code should be more understandable because each is only doing it's own job. It's a little longer, but honestly I'm not a fan of terse in any way, shape or form. As long as you are DRY, the more explicit the better.

PS. I'm going brace-free today.

So:

public class counter
    int a=-1;
    int b=-1;
    String result="";

    public append(int i)
        if(a == -1)
            a=i;
            b=i;
        else if(b == i-1)
            b++;
        else
            finished();

    public finished()
        if(result.length != 0 && a != -1)
            result+="+";
        if(a != -1)
            result.append(a);
            if(a != b)
                result.append("/"+b);
        a=-1;
        b=-1;

    public String getResult()
        finished();
        return result;
Bill K
This had a few bugs, probably still does. Be sure check back--I've updated it at least 3 times now, but I think it'll work at this point.
Bill K
+1  A: 

On java.util.BitSet

I'd use java.util.BitSet both for the representation and the span search algorithm. Here's the basic idea (see on ideone.com):

    import java.util.*;
    //...

    BitSet bs = new BitSet();
    int[] onBits = new int[] { 1,2,4,5,6,7,11,13,21,22,23,24,25 };
    for (int onBit : onBits) {
        bs.set(onBit);
    }
    System.out.println(bs);
    // {1, 2, 4, 5, 6, 7, 11, 13, 21, 22, 23, 24, 25}

    StringBuilder sb = new StringBuilder();
    for (int begin, end = -1; (begin = bs.nextSetBit(end + 1)) != -1; ) {
        end = bs.nextClearBit(begin) - 1;
        if (sb.length() > 0) sb.append(", ");
        sb.append(
            (begin == end)
                ? String.format("+%d", begin)
                : String.format("+%d/%d", begin, end)
        );
    }
    System.out.println(sb);
    // +1/2, +4/7, +11, +13, +21/25

The nextSetBit and nextClearBit methods are really handy in finding the spans.


On StringBuilder instead of String with +=/concat

The StringBuilder joining algorithm is a standard one. Here it is when refactored out:

    StringBuilder sb = new StringBuilder();
    for (Element e : elements) {
        if (sb.length() > 0) sb.append(SEPARATOR);
        sb.append(e);
    }

You should use a StringBuilder or StringBuffer instead of String with +=/concat for building long strings.

polygenelubricants
By the way, `BitSet` has a `flip`/"toggle" method, plus set-like operations like `or` (union) and `and` (intersection).
polygenelubricants
You should use StringBuffer *only if* you require thread-safety or you're targeting a version of Java that doesn't support it (pre-1.5 or some versions of MIDP).
tc.
When using StringBuilder, you have to initialize the capacity first, or else you are not gaining any performance here compared to String "+" concat.
limc
A: 

This one sounds like fun, maybe I should join the fun too. :)

public void run() {
    List<Integer> orderNums = Arrays.asList(new Integer[] {1,2,4,5,6,7,11,13,21,22,23,24,25});

    Iterator<Integer> ite = orderNums.iterator();

    // I'm assuming the list is greater than 1, so, I'm not doing the
    // ite.hasNext() check here.
    Integer start = ite.next();
    Integer end = start;
    StringBuilder sb = new StringBuilder(1000);

    while (ite.hasNext()) {
        Integer current = ite.next();
        Integer diff = current - end;

        if (diff == 1) {
            end = current;
        }
        else if (diff > 1) {
            sb.append(getValue(start, end));

            end = current;
            start = current;
        }
    }

    sb.append(getValue(start, end));

    System.out.println(sb.toString());
}

private String getValue(Integer start, Integer end) {
    return (end - start > 0) ? "+" + start + "/" + end + "," : "+" + start + ",";
}
limc