MahmoudFawzyKhalil's implementation (#438)
* Initial commit trying out multiple things * Clean up code * Fix rounding error to fix failing test
This commit is contained in:
parent
27b9232b7d
commit
08541525cd
19
calculate_average_MahmoudFawzyKhalil.sh
Executable file
19
calculate_average_MahmoudFawzyKhalil.sh
Executable file
@ -0,0 +1,19 @@
|
||||
#!/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="--enable-preview"
|
||||
java $JAVA_OPTS --class-path target/average-1.0.0-SNAPSHOT.jar dev.morling.onebrc.CalculateAverage_MahmoudFawzyKhalil
|
@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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.lang.foreign.Arena;
|
||||
import java.lang.foreign.MemorySegment;
|
||||
import java.lang.foreign.ValueLayout;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
|
||||
// Solution using project Panama and Map Reduce
|
||||
public class CalculateAverage_MahmoudFawzyKhalil {
|
||||
|
||||
private static final String FILE = "./measurements.txt";
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
mapReduce();
|
||||
}
|
||||
|
||||
private static void mapReduce() throws IOException {
|
||||
var f = new File(FILE);
|
||||
try (var raf = new RandomAccessFile(f, "r")) {
|
||||
FileChannel channel = raf.getChannel();
|
||||
long fileSize = channel.size();
|
||||
MemorySegment ms = channel.map(FileChannel.MapMode.READ_ONLY, 0, fileSize, Arena.global());
|
||||
long chunkSize = fileSize / ForkJoinPool.commonPool().getParallelism();
|
||||
List<Chunk> chunks = getChunks(ms, chunkSize);
|
||||
Map<String, MeasurementAggregate> result = chunks.stream()
|
||||
.parallel()
|
||||
.map(c -> readChunkToMap(c, ms))
|
||||
.reduce(Collections.emptyMap(), (a, b) -> combine(a, b));
|
||||
System.out.println(new TreeMap<>(result));
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Chunk> getChunks(MemorySegment ms, long chunkSize) {
|
||||
List<Chunk> chunks = new ArrayList<>(32);
|
||||
long start = 0;
|
||||
long fileSize = ms.byteSize();
|
||||
long end = chunkSize;
|
||||
|
||||
while (start < fileSize) {
|
||||
byte b = ms.get(ValueLayout.JAVA_BYTE, end);
|
||||
if (b == '\n') {
|
||||
chunks.add(new Chunk(start, end));
|
||||
start = end + 1;
|
||||
end = Math.min(end + chunkSize, fileSize - 2);
|
||||
}
|
||||
end++;
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
private static Map<String, MeasurementAggregate> readChunkToMap(Chunk chunk, MemorySegment ms) {
|
||||
Map<String, MeasurementAggregate> map = new HashMap<>();
|
||||
|
||||
long start = chunk.start();
|
||||
while (start < chunk.end()) {
|
||||
long cityNameSize = 0;
|
||||
while (ms.get(ValueLayout.JAVA_BYTE, start + cityNameSize) != ';') {
|
||||
cityNameSize++;
|
||||
}
|
||||
|
||||
String cityName = readString(ms, start, cityNameSize);
|
||||
start = start + cityNameSize + 1;
|
||||
|
||||
long temperatureSize = 0;
|
||||
while (ms.get(ValueLayout.JAVA_BYTE, start + temperatureSize) != '\n') {
|
||||
temperatureSize++;
|
||||
}
|
||||
|
||||
String temperature = readString(ms, start, temperatureSize);
|
||||
start = start + temperatureSize + 1;
|
||||
|
||||
// System.out.println(STR."\{cityName};\{temperature}");
|
||||
addMeasurement(map, cityName, temperature);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
// Credit goes to imrafaelmerino for combine function
|
||||
private static Map<String, MeasurementAggregate> combine(Map<String, MeasurementAggregate> xs, Map<String, MeasurementAggregate> ys) {
|
||||
Map<String, MeasurementAggregate> result = new HashMap<>();
|
||||
|
||||
for (var key : xs.keySet()) {
|
||||
var m1 = xs.get(key);
|
||||
var m2 = ys.get(key);
|
||||
var combined = (m2 == null) ? m1 : (m1 == null) ? m2 : m1.combine(m2);
|
||||
result.put(key, combined);
|
||||
}
|
||||
|
||||
for (var key : ys.keySet())
|
||||
result.putIfAbsent(key, ys.get(key));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String readString(MemorySegment ms, long start, long size) {
|
||||
byte[] stringBytes = ms.asSlice(start, size)
|
||||
.toArray(ValueLayout.JAVA_BYTE);
|
||||
return new String(stringBytes);
|
||||
}
|
||||
|
||||
private static void addMeasurement(Map<String, MeasurementAggregate> measurements, String station, String reading) {
|
||||
measurements.compute(station,
|
||||
(_, oldMeasurements) -> oldMeasurements == null ? MeasurementAggregate.of(reading) : oldMeasurements.update(reading));
|
||||
}
|
||||
|
||||
record Chunk(long start, long end) {
|
||||
}
|
||||
|
||||
private static final class MeasurementAggregate {
|
||||
private double min;
|
||||
private double max;
|
||||
private double sum;
|
||||
private long count;
|
||||
|
||||
private MeasurementAggregate(double min, double max, double sum, long count) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.sum = sum;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public static MeasurementAggregate of(String temperature) {
|
||||
double measurement = Double.parseDouble(temperature);
|
||||
return new MeasurementAggregate(measurement, measurement, measurement, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this)
|
||||
return true;
|
||||
if (obj == null || obj.getClass() != this.getClass())
|
||||
return false;
|
||||
var that = (MeasurementAggregate) obj;
|
||||
return Double.doubleToLongBits(this.min) == Double.doubleToLongBits(that.min) &&
|
||||
Double.doubleToLongBits(this.max) == Double.doubleToLongBits(that.max) &&
|
||||
Double.doubleToLongBits(this.sum) == Double.doubleToLongBits(that.sum) &&
|
||||
this.count == that.count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(min, max, sum, count);
|
||||
}
|
||||
|
||||
public MeasurementAggregate update(String part) {
|
||||
double measurement = Double.parseDouble(part);
|
||||
this.min = Math.min(this.min, measurement);
|
||||
this.max = Math.max(this.max, measurement);
|
||||
this.sum += measurement;
|
||||
this.count++;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return min + "/" + round(round(sum) / count) + "/" + max;
|
||||
}
|
||||
|
||||
private double round(double value) {
|
||||
return Math.round(value * 10.0) / 10.0;
|
||||
}
|
||||
|
||||
public MeasurementAggregate combine(MeasurementAggregate m2) {
|
||||
return new MeasurementAggregate(
|
||||
Math.min(this.min, m2.min),
|
||||
Math.max(this.max, m2.max),
|
||||
this.sum + m2.sum,
|
||||
this.count + m2.count);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user