LocalValueSummary.java

1
/*******************************************************************************
2
 * Copyright (C) 2026, Leo Galambos
3
 * All rights reserved.
4
 * 
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 * 
8
 * 1. Redistributions of source code must retain the above copyright notice,
9
 *    this list of conditions and the following disclaimer.
10
 * 
11
 * 2. Redistributions in binary form must reproduce the above copyright notice,
12
 *    this list of conditions and the following disclaimer in the documentation
13
 *    and/or other materials provided with the distribution.
14
 * 
15
 * 3. All advertising materials mentioning features or use of this software must
16
 *    display the following acknowledgement:
17
 *    This product includes software developed by the Egothor project.
18
 * 
19
 * 4. Neither the name of the copyright holder nor the names of its contributors
20
 *    may be used to endorse or promote products derived from this software
21
 *    without specific prior written permission.
22
 * 
23
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
27
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33
 * POSSIBILITY OF SUCH DAMAGE.
34
 ******************************************************************************/
35
package org.egothor.stemmer.trie;
36
37
import java.util.ArrayList;
38
import java.util.List;
39
import java.util.Map;
40
import java.util.function.IntFunction;
41
42
import org.egothor.stemmer.ReductionSettings;
43
44
/**
45
 * Local terminal value summary of a node.
46
 *
47
 * @param <V> value type
48
 */
49
public final class LocalValueSummary<V> {
50
51
    /**
52
     * Locally stored values ordered by descending frequency.
53
     */
54
    private final V[] orderedValues;
55
56
    /**
57
     * Frequencies aligned with {@link #orderedValues}.
58
     */
59
    private final int[] orderedCounts;
60
61
    /**
62
     * Total local frequency.
63
     */
64
    private final int totalCount;
65
66
    /**
67
     * Winning value, or {@code null} if the node has no local value.
68
     */
69
    /* default */ final V dominantValue;
70
71
    /**
72
     * Winning value frequency.
73
     */
74
    private final int dominantCount;
75
76
    /**
77
     * Second best value frequency.
78
     */
79
    private final int secondCount;
80
81
    /**
82
     * Creates a summary.
83
     *
84
     * @param orderedValues ordered values
85
     * @param orderedCounts ordered counts
86
     * @param totalCount    total count
87
     * @param dominantValue dominant value
88
     * @param dominantCount dominant count
89
     * @param secondCount   second count
90
     */
91
    public LocalValueSummary(final V[] orderedValues, final int[] orderedCounts, final int totalCount,
92
            final V dominantValue, final int dominantCount, final int secondCount) {
93
        this.orderedValues = orderedValues;
94
        this.orderedCounts = orderedCounts;
95
        this.totalCount = totalCount;
96
        this.dominantValue = dominantValue;
97
        this.dominantCount = dominantCount;
98
        this.secondCount = secondCount;
99
    }
100
101
    /**
102
     * Builds a summary from local counts.
103
     *
104
     * @param counts       local counts
105
     * @param arrayFactory array factory
106
     * @param <V>          value type
107
     * @return summary
108
     */
109
    public static <V> LocalValueSummary<V> of(final Map<V, Integer> counts, final IntFunction<V[]> arrayFactory) {
110
        final List<SortableValue<V>> entries = new ArrayList<>(counts.size());
111
        int insertionOrder = 0;
112
        for (Map.Entry<V, Integer> entry : counts.entrySet()) {
113 1 1. of : Changed increment from 1 to -1 → KILLED
            entries.add(new SortableValue<>(entry.getKey(), entry.getValue(), String.valueOf(entry.getKey()),
114
                    insertionOrder++));
115
        }
116
117 1 1. of : removed call to java/util/List::sort → KILLED
        entries.sort((left, right) -> {
118
            final int frequencyCompare = Integer.compare(right.count(), left.count());
119 1 1. lambda$of$0 : negated conditional → KILLED
            if (frequencyCompare != 0) {
120 1 1. lambda$of$0 : replaced int return with 0 for org/egothor/stemmer/trie/LocalValueSummary::lambda$of$0 → KILLED
                return frequencyCompare;
121
            }
122
123
            final int lengthCompare = Integer.compare(left.textLength(), right.textLength());
124 1 1. lambda$of$0 : negated conditional → KILLED
            if (lengthCompare != 0) {
125 1 1. lambda$of$0 : replaced int return with 0 for org/egothor/stemmer/trie/LocalValueSummary::lambda$of$0 → KILLED
                return lengthCompare;
126
            }
127
128
            final int textCompare = left.text().compareTo(right.text());
129 1 1. lambda$of$0 : negated conditional → KILLED
            if (textCompare != 0) {
130 1 1. lambda$of$0 : replaced int return with 0 for org/egothor/stemmer/trie/LocalValueSummary::lambda$of$0 → KILLED
                return textCompare;
131
            }
132
133 1 1. lambda$of$0 : replaced int return with 0 for org/egothor/stemmer/trie/LocalValueSummary::lambda$of$0 → SURVIVED
            return Integer.compare(left.insertionOrder(), right.insertionOrder());
134
        });
135
136
        final V[] orderedValues = arrayFactory.apply(entries.size());
137
        final int[] orderedCounts = new int[entries.size()];
138
139
        int totalCount = 0;
140 2 1. of : negated conditional → KILLED
2. of : changed conditional boundary → KILLED
        for (int index = 0; index < entries.size(); index++) {
141
            final SortableValue<V> entry = entries.get(index);
142
            orderedValues[index] = entry.value();
143
            orderedCounts[index] = entry.count();
144 1 1. of : Replaced integer addition with subtraction → SURVIVED
            totalCount += orderedCounts[index];
145
        }
146
147 1 1. of : negated conditional → KILLED
        final V dominantValue = orderedValues.length == 0 ? null : orderedValues[0];
148 1 1. of : negated conditional → KILLED
        final int dominantCount = orderedCounts.length == 0 ? 0 : orderedCounts[0];
149 2 1. of : changed conditional boundary → SURVIVED
2. of : negated conditional → KILLED
        final int secondCount = orderedCounts.length < 2 ? 0 : orderedCounts[1];
150
151 1 1. of : replaced return value with null for org/egothor/stemmer/trie/LocalValueSummary::of → KILLED
        return new LocalValueSummary<>(orderedValues, orderedCounts, totalCount, dominantValue, dominantCount,
152
                secondCount);
153
    }
154
155
    /**
156
     * Returns ordered values.
157
     *
158
     * @return ordered values
159
     */
160
    @SuppressWarnings("PMD.MethodReturnsInternalArray")
161
    public V[] orderedValues() {
162 1 1. orderedValues : replaced return value with null for org/egothor/stemmer/trie/LocalValueSummary::orderedValues → KILLED
        return this.orderedValues;
163
    }
164
165
    /**
166
     * Returns ordered counts.
167
     *
168
     * @return ordered counts
169
     */
170
    @SuppressWarnings("PMD.MethodReturnsInternalArray")
171
    public int[] orderedCounts() {
172 1 1. orderedCounts : replaced return value with null for org/egothor/stemmer/trie/LocalValueSummary::orderedCounts → KILLED
        return this.orderedCounts;
173
    }
174
175
    /**
176
     * Indicates whether the dominant value satisfies the configured dominance
177
     * constraints.
178
     *
179
     * @param settings reduction settings
180
     * @return {@code true} if dominant, otherwise {@code false}
181
     */
182
    /* default */ boolean hasQualifiedDominantWinner(final ReductionSettings settings) {
183 1 1. hasQualifiedDominantWinner : negated conditional → KILLED
        if (this.dominantValue == null) {
184 1 1. hasQualifiedDominantWinner : replaced boolean return with true for org/egothor/stemmer/trie/LocalValueSummary::hasQualifiedDominantWinner → KILLED
            return false;
185
        }
186
187
        final int thresholdPercent = settings.dominantWinnerMinPercent();
188
        final int ratio = settings.dominantWinnerOverSecondRatio();
189
190 4 1. hasQualifiedDominantWinner : Replaced long multiplication with division → KILLED
2. hasQualifiedDominantWinner : Replaced long multiplication with division → KILLED
3. hasQualifiedDominantWinner : negated conditional → KILLED
4. hasQualifiedDominantWinner : changed conditional boundary → KILLED
        final boolean percentSatisfied = this.dominantCount * 100L >= (long) this.totalCount * thresholdPercent;
191
192
        final boolean ratioSatisfied;
193 1 1. hasQualifiedDominantWinner : negated conditional → KILLED
        if (this.secondCount == 0) {
194
            ratioSatisfied = true;
195
        } else {
196 3 1. hasQualifiedDominantWinner : Replaced long multiplication with division → KILLED
2. hasQualifiedDominantWinner : negated conditional → KILLED
3. hasQualifiedDominantWinner : changed conditional boundary → KILLED
            ratioSatisfied = this.dominantCount >= (long) this.secondCount * ratio;
197
        }
198
199 3 1. hasQualifiedDominantWinner : replaced boolean return with true for org/egothor/stemmer/trie/LocalValueSummary::hasQualifiedDominantWinner → KILLED
2. hasQualifiedDominantWinner : negated conditional → KILLED
3. hasQualifiedDominantWinner : negated conditional → KILLED
        return percentSatisfied && ratioSatisfied;
200
    }
201
}

Mutations

113

1.1
Location : of
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldUseInsertionOrderAsFinalTieBreaker()]
Changed increment from 1 to -1 → KILLED

117

1.1
Location : of
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldOrderByFrequencyLengthAndLexicographicalValue()]
removed call to java/util/List::sort → KILLED

119

1.1
Location : lambda$of$0
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldOrderByFrequencyLengthAndLexicographicalValue()]
negated conditional → KILLED

120

1.1
Location : lambda$of$0
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:dominantReductionFallsBackWhenWinnerIsNotDominantEnough()]
replaced int return with 0 for org/egothor/stemmer/trie/LocalValueSummary::lambda$of$0 → KILLED

124

1.1
Location : lambda$of$0
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:equalFrequenciesPreferShorterStringRepresentation()]
negated conditional → KILLED

125

1.1
Location : lambda$of$0
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldOrderByFrequencyLengthAndLexicographicalValue()]
replaced int return with 0 for org/egothor/stemmer/trie/LocalValueSummary::lambda$of$0 → KILLED

129

1.1
Location : lambda$of$0
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:equalFrequenciesAndLengthsPreferLexicographicallyLowerString()]
negated conditional → KILLED

130

1.1
Location : lambda$of$0
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:equalFrequenciesAndLengthsPreferLexicographicallyLowerString()]
replaced int return with 0 for org/egothor/stemmer/trie/LocalValueSummary::lambda$of$0 → KILLED

133

1.1
Location : lambda$of$0
Killed by : none
replaced int return with 0 for org/egothor/stemmer/trie/LocalValueSummary::lambda$of$0 → SURVIVED
Covering tests

140

1.1
Location : of
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldCreateEmptySummaryForEmptyCounts()]
negated conditional → KILLED

2.2
Location : of
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldCreateEmptySummaryForEmptyCounts()]
changed conditional boundary → KILLED

144

1.1
Location : of
Killed by : none
Replaced integer addition with subtraction → SURVIVED
Covering tests

147

1.1
Location : of
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldCreateEmptySummaryForEmptyCounts()]
negated conditional → KILLED

148

1.1
Location : of
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldCreateEmptySummaryForEmptyCounts()]
negated conditional → KILLED

149

1.1
Location : of
Killed by : none
changed conditional boundary → SURVIVED
Covering tests

2.2
Location : of
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldCreateEmptySummaryForEmptyCounts()]
negated conditional → KILLED

151

1.1
Location : of
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldCreateEmptySummaryForEmptyCounts()]
replaced return value with null for org/egothor/stemmer/trie/LocalValueSummary::of → KILLED

162

1.1
Location : orderedValues
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldCreateEmptySummaryForEmptyCounts()]
replaced return value with null for org/egothor/stemmer/trie/LocalValueSummary::orderedValues → KILLED

172

1.1
Location : orderedCounts
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldCreateEmptySummaryForEmptyCounts()]
replaced return value with null for org/egothor/stemmer/trie/LocalValueSummary::orderedCounts → KILLED

183

1.1
Location : hasQualifiedDominantWinner
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldReturnFalseWhenNoDominantValueExists()]
negated conditional → KILLED

184

1.1
Location : hasQualifiedDominantWinner
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldReturnFalseWhenNoDominantValueExists()]
replaced boolean return with true for org/egothor/stemmer/trie/LocalValueSummary::hasQualifiedDominantWinner → KILLED

190

1.1
Location : hasQualifiedDominantWinner
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldRejectWinnerWhenPercentageThresholdIsNotSatisfied()]
Replaced long multiplication with division → KILLED

2.2
Location : hasQualifiedDominantWinner
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldAcceptSingleWinnerWhenSecondCountIsAbsent()]
Replaced long multiplication with division → KILLED

3.3
Location : hasQualifiedDominantWinner
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldAcceptSingleWinnerWhenSecondCountIsAbsent()]
negated conditional → KILLED

4.4
Location : hasQualifiedDominantWinner
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldAcceptSingleWinnerWhenSecondCountIsAbsent()]
changed conditional boundary → KILLED

193

1.1
Location : hasQualifiedDominantWinner
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldRejectWinnerWhenOverSecondRatioIsNotSatisfied()]
negated conditional → KILLED

196

1.1
Location : hasQualifiedDominantWinner
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldRejectWinnerWhenOverSecondRatioIsNotSatisfied()]
Replaced long multiplication with division → KILLED

2.2
Location : hasQualifiedDominantWinner
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldAcceptQualifiedDominantWinner()]
negated conditional → KILLED

3.3
Location : hasQualifiedDominantWinner
Killed by : org.egothor.stemmer.FrequencyTrieTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.FrequencyTrieTest]/[method:dominantReductionMergesQualifiedDominantWinnerNodes()]
changed conditional boundary → KILLED

199

1.1
Location : hasQualifiedDominantWinner
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldRejectWinnerWhenPercentageThresholdIsNotSatisfied()]
replaced boolean return with true for org/egothor/stemmer/trie/LocalValueSummary::hasQualifiedDominantWinner → KILLED

2.2
Location : hasQualifiedDominantWinner
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldAcceptSingleWinnerWhenSecondCountIsAbsent()]
negated conditional → KILLED

3.3
Location : hasQualifiedDominantWinner
Killed by : org.egothor.stemmer.trie.LocalValueSummaryTest.[engine:junit-jupiter]/[class:org.egothor.stemmer.trie.LocalValueSummaryTest]/[method:shouldAcceptSingleWinnerWhenSecondCountIsAbsent()]
negated conditional → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1