Сжать массив с дубликатами

Есть класс Дерево, которое содержит массив геномов. При его росте массив растёт очень быстро. Как его сжать? Подскажите алгоритм действий.

Код дерева:

import java.util.List;
import java.util.ArrayList;

public class Tree {

    private Gene[] genes;
    
    public Tree(Gene[] genes) {
        this.genes = genes;
    }
    
    public Gene[] getGen() {
        return genes;
    }
    
    public void development() {
        List<Gene> newGens = new ArrayList<>();
        for(Gene g : genes) {
            Gene[] arrG = g.newInstructions();
            if(arrG == null) {
                continue;
            }
            for(Gene j : arrG) {
                newGens.add(j);
            }
        }
        int l = newGens.size();
        genes = new Gene[l];
        for(int i = 0; i < l; i++) {
            genes[i] = newGens.get(i);
        }
    }
    
}

Код базового генома:

import java.awt.Color;

public abstract class Gene {
    
    public final String NAME;
    public final Color COLOR;
    public final FormGene FORM;
    public final Direction DIRECTION;
    public final int STEP;
    
    protected Gene(String name, Color color, FormGene form, Direction direction, int step) {
        NAME = name;
        COLOR = color;
        FORM = form;
        DIRECTION = direction;
        STEP = step;
    }
    
    public abstract Gene[] newInstructions();

}

У меня реализованы потомки генома, которые выдают такие результаты:

A
AB
ABABC
ABABCABABCAB  //Повторы есть ABABC ABABC AB -> 2*ABABC 1*AB
ABABCABABCABABABCABABCABABABC //Повторы ABABC ABABC AB ABABC ABABC AB ABABC -> 2*ABABC 1*AB 2*ABABC 1*AB

Сжать нужно массив genes в Tree.


Ответы (1 шт):

Автор решения: Solt

Если речь об эффективной упаковке данных, то вообще не торопитесь с повторами, не важно, массив это, строка, или что-то ещё.

Вначале хорошо бы проанализировать "ёмкость" данных и не пытаться ужать бесполезно потраченное место.

У вас видно, что используется всего 3 символа. Для генов, наверное их будет больше (основных нуклеотидов же вроде 5 и их комбинаций штук 6 всего).

Но в любом случае 3-4 символа можно пронумеровать и поместить в 2 бита (для 5-8 символов понадобится 3 бита):

  1. A - 00
  2. B - 01
  3. С - 10
  4. D - 11

В байт поместится целых четыре таких числа, например BBCA=01011000, оно же 58 в шестнадцатеричном представлении, оно же десятичное 88, и даже представимо в текстовом виде 'X' (будут и непечатаемые, так что текст не стоит рассматривать).

В случае трёхбитного кодирования для упаковки будет удобно использовать три байта - туда уложится 8 штук.

Если хочется, можете считать это словарём, состоящим из 4-буквенных комбинаций.

Так вот, ваша строка ABABCABABCABABABCABABCABABABC без всяких словарей станет таким массивом:

ABAB 0x11
CABA 0x84
BCAB 0x61
ABAB 0x11
CABA 0x84
BCAB 0x61
ABAB 0x11
CAAA 0x80 (добиваем до 4 букв, при известной общей длине это проблемой не будет)

То есть [0x11,0x84,0x61,0x11,0x84,0x61,0x11,0x80]

Как видно, результирующие данные займут в 4 раза меньше места, причем закодировать можно 4, а не 3 символа.

Если же их всегда будет только 3 - можно применить троичную систему счисления и запаковать максимально компактно. Даже в байт помещается уже 5 букв, а не 4. А в четырехбайтовую переменную влезет уже 21 буква.

При кодировании 5 нуклеотидов оптимально использовать пятеричную систему счисления и, например, в 8 байтов уложится 27 букв.

Об этом в более математических терминах изложил Stanislav Volodarskiy в топике, указанном maestro в комментариях.

А уже следующим этапом можно задуматься над формированием словаря. Только и там надо помнить, что если заменять, например 3 байта на 3-байтовое положение в словаре - хрен редьки не слаще. Словарь должен состоять из достаточно длинных слов и их должно быть не сильно много. Другими словами, ссылки на словарь должны быть несоизмеримо короче самих слов, иначе нет смысла. Но это уже лучше почитать про алгоритмы сжатия.

И простите, что это вообще ни разу не ответ на поставленный в топике вопрос ))

→ Ссылка