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

Mutations

109

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

113

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

115

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

116

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

120

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

121

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

125

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

126

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

129

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

136

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

140

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

143

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

144

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

145

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

147

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

158

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

168

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

179

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

180

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

186

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

189

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

192

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

195

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