5
\$\begingroup\$

Let's assume we have an array that contains the following elements:

-1, 0, 0, 1, 0, -1, 0, -1, 1

I would like to count the combinations of consecutive elements such that

-1,0 & 0,0 & 0,1 & 1,0 & 0,-1 & -1,0 & 0,-1 & -1&1

In this case:

The count of -1,0 = 2
The count of 0,1 = 1
The count of 1,0 = 1
and so on

I implemented the following:

List<Double> sixProbabilites = new ArrayList<Double>();

        int count_neg10 = 0, count_neg11 = 0, count_10 = 0, count_1neg1 = 0, count_0neg1 = 0, count_01 = 0;

        for(int i=0; i<slopeChanges.size(); i++) {
            if(i+1 != slopeChanges.size()) {
                if((slopeChanges.get(i) == -1) && (slopeChanges.get(i) == 0)) {count_neg10++;}
                else if((slopeChanges.get(i) == -1) && (slopeChanges.get(i+1) == 1)) {count_neg11++;}
                else if((slopeChanges.get(i) == 1) && (slopeChanges.get(i+1) == 0)) {count_10++;}
                else if((slopeChanges.get(i) == 1) && (slopeChanges.get(i+1) == -1)) {count_1neg1++;}
                else if((slopeChanges.get(i) == 0) && (slopeChanges.get(i+1) == -1)) {count_0neg1++;}
                else if((slopeChanges.get(i) == 0) && (slopeChanges.get(i+1) == 1)) {count_01++;}
            }
        }

There is no problem with the code but I am just wondering if there is a way to enhance the code especially the if statement. Is there any way to reduce the code size to few lines?

\$\endgroup\$
4
\$\begingroup\$

I would build a Map with a custom keying function and just iterate over the length (minus one) of the array of values.

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class GroupFrenquency {
    public static <T> Map<String, Integer> groupFrenquencies(T[] arr, Function<List<T>, String> keyFn) {
        Map<String, Integer> frequency = new HashMap<>();
        for (int i = 0; i < arr.length - 1; i++) {
            String key = keyFn.apply(Arrays.asList(arr[i], arr[i + 1]));
            frequency.put(key, frequency.containsKey(key) ? frequency.get(key) + 1 : 1);
        }
        return frequency;
    }

    public static void main(String[] args) {
        Integer[] sequence = { -1, 0, 0, 1, 0, -1, 0, -1, 1 };
        Function<List<Integer>, String> keyFn = (items) -> String.format("%s,%s", items.get(0), items.get(1));

        System.out.println(groupFrenquencies(sequence, keyFn).entrySet().stream()
                .map(e -> String.format("The count of %-4s = %d", e.getKey(), e.getValue()))
                .collect(Collectors.joining(System.lineSeparator())));
    }
}

Output

The count of 0,0  = 1
The count of 0,1  = 1
The count of 1,0  = 1
The count of 0,-1 = 2
The count of -1,1 = 1
The count of -1,0 = 2

Using reflection, you can create a copy of the array instead of creating an entire list object, when creating the key. The only downside is that you need to pass in the class type of the array, because you cannot initialize a generic array without knowing the type.

import java.lang.reflect.Array;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class GroupFrenquency {
    public static <T> Map<String, Integer> groupFrenquencies(Class<T> type, T[] arr, Function<T[], String> keyFn) {
        Map<String, Integer> frequency = new HashMap<>();
        for (int i = 0; i < arr.length - 1; i++) {
            String key = keyFn.apply(copyArray(type, arr, i, 2));
            frequency.put(key, frequency.containsKey(key) ? frequency.get(key) + 1 : 1);
        }
        return frequency;
    }

    @SuppressWarnings("unchecked")
    private static <T> T[] copyArray(Class<T> type, T[] source, int startPos, int length) {
        T[] copy = (T[]) Array.newInstance(type, length);
        System.arraycopy(source, startPos, copy, 0, length);
        return copy;
    }

    public static void main(String[] args) {
        Class<Integer> type = Integer.class;
        Integer[] sequence = { -1, 0, 0, 1, 0, -1, 0, -1, 1 };
        Function<Integer[], String> keyFn = (items) -> String.format("%s,%s", items[0], items[1]);

        System.out.println(groupFrenquencies(type, sequence, keyFn).entrySet().stream()
                .map(e -> String.format("The count of %-4s = %d", e.getKey(), e.getValue()))
                .collect(Collectors.joining(System.lineSeparator())));
    }
}
\$\endgroup\$
  • \$\begingroup\$ Why do you use a String as a key for the map and not the List itself? I understand that you can't use arrays as keys because arrays are compared by identity and not by content, but in the List version, creating a String from a List only to use it as a key for the map makes the code unnecessarily complicated. Anyway, enlisting the functionality of reflection just to be able to use an array instead of a List seems overkill to me. If you don't want to use a List, why not create a class Pair that stores the two values and overrides equals and hashCode based on these two values? \$\endgroup\$ – Stingy Apr 14 '18 at 11:03
2
\$\begingroup\$

You could emulate array with negative indexes:

private void test() {
    List<Integer> slopeChanges = ...
    int counts[] = new int[9];
    for(int i=1; i<slopeChanges.size(); i++) {
        Integer current = slopeChanges.get(i-1);
        Integer next = slopeChanges.get(i);
        counts[index(current, next)]++;
    }

    System.out.println("count_neg10="+ counts[index(-1, 0)]);
}

private int index(int first, int second) {
    return (first+1)*3+(second+1);
}

but it doesn't really make it shorter.

\$\endgroup\$
0
\$\begingroup\$

This is all just a question of knowing your data structures. You already represent the input as a List<Double> (which is good), now the next step is simply to represent the match pattern as a List<Double> too, an then use the equals function of lists.

Naturally, you will have to iterate over the list to create 2-element sublists to check the equality.

Thus, setting up the combinations (may be done differently, the important thing is the structure as List:

List<List<Double>> combinations = new ArrayList<>();
for(double a = -1; a <= 1; a++) {
    for(double b = -1; b <= 1; b++) {
        combinations.add(Arrays.asList(a, b));
    }
}

When you have this structure, you can simply check slopeChanges.sublist(i, i + 2).equals(combination) for each legal i and combination created. For counting, using a mutable integer object (which the base libraries lack) is the esiest thing, thus here I use an AtomicInteger to count:

Map<List<Double>, AtomicInteger> counts = new HashMap<>();
for(int i = 0; i < slopeChanges.size() - 1; i++) {
    for(List<Double> combination : combinations) {
        if(slopeChanges.subList(i, i + 2).equals(combination))
            counts.computeIfAbsent(combination, k -> new AtomicInteger(0)).incrementAndGet();
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.