Adding Peter Lawrey's submission
This commit is contained in:
parent
5d885165ba
commit
a09fa928db
20
calculate_average_lawrey.sh
Executable file
20
calculate_average_lawrey.sh
Executable file
@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright 2023 The original authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
JAVA_OPTS=""
|
||||
time java $JAVA_OPTS -verbose:gc -Xmx99m --class-path target/average-1.0.0-SNAPSHOT.jar dev.morling.onebrc.CalculateAverage_lawrey
|
215
src/main/java/dev/morling/onebrc/CalculateAverage_lawrey.java
Normal file
215
src/main/java/dev/morling/onebrc/CalculateAverage_lawrey.java
Normal file
@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright 2023 The original authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dev.morling.onebrc;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.stream.LongStream;
|
||||
|
||||
public class CalculateAverage_lawrey {
|
||||
|
||||
// Path to the file containing temperature measurements.
|
||||
private static final String FILE = "./measurements.txt";
|
||||
|
||||
// Inner class representing a measurement with min, max, and average calculations.
|
||||
static class Measurement {
|
||||
double min = Double.POSITIVE_INFINITY;
|
||||
double max = Double.NEGATIVE_INFINITY;
|
||||
double sum = 0.0;
|
||||
long count = 0;
|
||||
|
||||
// Default constructor for Measurement.
|
||||
public Measurement() {
|
||||
}
|
||||
|
||||
// Adds a new temperature sample and updates min, max, and average.
|
||||
public void sample(double temp) {
|
||||
min = Math.min(min, temp);
|
||||
max = Math.max(max, temp);
|
||||
sum += temp;
|
||||
count++;
|
||||
}
|
||||
|
||||
// Returns a formatted string representing min, average, and max.
|
||||
public String toString() {
|
||||
return round(min) + "/" + round(sum / count) + "/" + round(max);
|
||||
}
|
||||
|
||||
// Helper method to round a double value to one decimal place.
|
||||
private double round(double value) {
|
||||
return Math.round(value * 10.0) / 10.0;
|
||||
}
|
||||
|
||||
// Merges this Measurement with another Measurement.
|
||||
public Measurement merge(Measurement m2) {
|
||||
min = Math.min(min, m2.min);
|
||||
max = Math.max(max, m2.max);
|
||||
sum += m2.sum;
|
||||
count += m2.count;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
// Inner class representing a key for measurements.
|
||||
static class Key {
|
||||
final byte[] data = new byte[32];
|
||||
int hash = 0, length = 0;
|
||||
|
||||
// Override of hashCode using the hash field.
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Custom equals method to compare two Key objects.
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof Key k) {
|
||||
if (length != k.length || hash != k.hash)
|
||||
return false;
|
||||
return Arrays.equals(data, 0, length, k.data, 0, length);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converts the key's data to a String.
|
||||
@Override
|
||||
public String toString() {
|
||||
return new String(data, 0, length, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
// Appends a byte to the key and updates its hash.
|
||||
public void append(byte b) {
|
||||
data[length++] = b;
|
||||
hash = hash * 10191 + b;
|
||||
}
|
||||
|
||||
// Resets the key to its initial state.
|
||||
public void clear() {
|
||||
length = hash = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
// Open the file for reading.
|
||||
File file = new File(FILE);
|
||||
long length = file.length();
|
||||
long chunk = 1 << 28; // Size of the chunk to be processed.
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
|
||||
// Process the file in chunks and merge the results.
|
||||
Map<Key, Measurement> allMeasurementsMap = LongStream.range(0, length / chunk + 1)
|
||||
.parallel()
|
||||
.mapToObj(i -> extractMeasurementsFromChunk(i, chunk, length, raf))
|
||||
.reduce((a, b) -> mergeMeasurementMaps(a, b))
|
||||
.orElseGet(Collections::emptyMap);
|
||||
|
||||
// Sort the measurements and print them.
|
||||
Map<String, Measurement> sortedMeasurementsMap = new TreeMap<>();
|
||||
allMeasurementsMap.forEach((k, m) -> sortedMeasurementsMap.put(k.toString(), m));
|
||||
System.out.println(sortedMeasurementsMap);
|
||||
}
|
||||
|
||||
// Merges two measurement maps.
|
||||
private static Map<Key, Measurement> mergeMeasurementMaps(Map<Key, Measurement> a, Map<Key, Measurement> b) {
|
||||
a.forEach((k, m) -> {
|
||||
Measurement m2 = b.get(k);
|
||||
if (m2 != null)
|
||||
m.merge(m2);
|
||||
});
|
||||
b.forEach(a::putIfAbsent);
|
||||
return a;
|
||||
}
|
||||
|
||||
// Extracts measurements from a chunk of the file.
|
||||
private static Map<Key, Measurement> extractMeasurementsFromChunk(long i, long chunk, long length, RandomAccessFile raf) {
|
||||
long start = i * chunk;
|
||||
long size = Math.min(length, start + chunk + 64 * 1024) - start;
|
||||
Map<Key, Measurement> map = new HashMap<>(1024);
|
||||
try {
|
||||
MappedByteBuffer mbb = raf.getChannel().map(FileChannel.MapMode.READ_ONLY, start, size);
|
||||
if (i > 0)
|
||||
skipToFirstLine(mbb);
|
||||
Key key = new Key();
|
||||
while (mbb.remaining() > 0 && mbb.position() <= chunk) {
|
||||
key.clear();
|
||||
readKey(mbb, key);
|
||||
int temp = readTemperatureFromBuffer(mbb);
|
||||
Measurement m = map.computeIfAbsent(key, n -> new Measurement());
|
||||
if (m.count == 0)
|
||||
key = new Key();
|
||||
m.sample(temp / 10.0);
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// Reads a temperature value from the buffer.
|
||||
private static int readTemperatureFromBuffer(MappedByteBuffer mbb) {
|
||||
int temp = 0;
|
||||
boolean negative = false;
|
||||
outer:
|
||||
while (mbb.remaining() > 0) {
|
||||
byte b = mbb.get();
|
||||
switch (b) {
|
||||
case '-':
|
||||
negative = true;
|
||||
break;
|
||||
case '.':
|
||||
break;
|
||||
case '\r':
|
||||
case '\n':
|
||||
break outer;
|
||||
default:
|
||||
temp = 10 * temp + (b - '0');
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mbb.remaining() > 0) {
|
||||
byte b = mbb.get(mbb.position());
|
||||
if (b == '\n')
|
||||
mbb.get();
|
||||
}
|
||||
if (negative)
|
||||
temp = -temp;
|
||||
return temp;
|
||||
}
|
||||
|
||||
// Reads a key from the buffer.
|
||||
private static void readKey(MappedByteBuffer mbb, Key key) {
|
||||
while (mbb.remaining() > 0) {
|
||||
byte b = mbb.get();
|
||||
if (b == ';' || b == '\r' || b == '\n')
|
||||
break;
|
||||
key.append(b);
|
||||
}
|
||||
}
|
||||
|
||||
// Skips to the first line in the buffer, used for chunk processing.
|
||||
private static void skipToFirstLine(MappedByteBuffer mbb) {
|
||||
while ((mbb.get() & 0xFF) >= ' ') {
|
||||
// Skip bytes until reaching the start of a line.
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user