i am trying to write a program, and the rest of the code so far works but i am getting a incompatible types found : double required :Grocery Item in line 38. Can anyone help me in explaining why I am receiving this error and how to correct it? Thank you. here is my code:
import java.util.Scanner;
public class GroceryList {
private GroceryItem[]groceryArr; //ARRAY HOLDS GROCERY ITEM OBJECTS
private int numItems;
private String date;
private String storeName;
public GroceryList(String inputDate, String inputName) {
//FILL IN CODE HERE
// CREATE ARRAY, INITIALIZE FIELDS
groceryArr = new GroceryItem[10];
numItems = 0;
}
public void load() {
Scanner keyboard = new Scanner(System.in);
double sum = 0;
System.out.println ("Enter the trip date and then hit return:");
date = keyboard.next();
keyboard.nextLine();
System.out.println("Enter the store name and then hit return:");
storeName = keyboard.next();
keyboard.nextLine();
double number = keyboard.nextDouble();
//NEED TO PROMPT USER FOR, AND READ IN THE DATE AND STORE NAME.
System.out.println("Enter each item bought and the price (then return).");
System.out.println("Terminate with an item with a negative price.");
number = keyboard.nextDouble();
while (number >= 0 && numItems < groceryArr.length) {
groceryArr[numItems] = number;
numItems++;
sum += number;
System.out.println("Enter each item bought and the price (then return).");
System.out.println("Terminate with an item with a negative price.");
number = keyboard.nextDouble();
}
/*
//READ IN AND STORE EACH ITEM. STORE NUMBER OF ITEMS
}
private GroceryItem computeTotalCost() {
//add code here
}
public void print() {
\\call computeTOtalCost
}
*/
}
}
"groceryArr[numItems] = number;"
groceryArr[numItems] is an instance of GroceryItem() - 'number' is a double
You need a double variable in your GroceryItem() object to store the 'number' value.
Related
I have dataset (from JSON source) with cumulative values. It looks like this:
Could I extract from this dataset delta from last hour or last day (for example, count from 0 since last midnight?)
What you are asking about falls squarely in the realm of process data as it usually comes from control systems aka process controls systems. There may be DCS (Distributed Control Systems) or SCADA out in the field that act as a focal point on receiving data. And there may be a process historian or time-series database for accessing that data, if not on an enterprise level at least not within the process controls network.
Much of the engineering associated with process data has been established for many, many decades. For my examples, I did not want to write too many custom classes so I will use some everyday .NET objects. However, I am adhering to 2 such well-regarded principles about process data:
All times will be in UTC. Usually one does not show the UtcTime until the very last moment when displaying to a local user.
Process Data acknowledges the Quality of a value. While there can be dozens of bad states associated with such Quality, I will use a simple binary approach of good or bad. Since I use double, a value is good as long as it is not double.NaN.
That said, I assume you have a class that looks similar to:
public class JsonDto
{
public string Id { get; set; }
public DateTime Time { get; set; }
public double value { get; set; }
}
Granted your class name may be different, but the main thing is this class holds an individual instance of process data. When you read a JSON file, it will produce a List<jsonDto> instance.
You will need lots of methods to transform the data to something a wee bit more useable in order to get to where the rubber finally meets the road: producing hourly differences. But that requires producing hourly values because there is no guarantee that your recorded values occur exactly on each hour.
ProcessData Class - lots of methods
public static class ProcessData
{
public enum CalculationTimeBasis { Auto = 0, EarliestTime, MostRecentTime, MidpointTime }
public static Dictionary<string, SortedList<DateTime, double>> GetTagTimedValuesMap(IEnumerable<JsonDto> jsonDto)
{
var map = new Dictionary<string, SortedList<DateTime, double>>();
var tagnames = jsonDto.Select(x => x.Id).Distinct().OrderBy(x => x);
foreach (var tagname in tagnames)
{
map.Add(tagname, new SortedList<DateTime, double>());
}
var orderedValues = jsonDto.OrderBy(x => x.Id).ThenBy(x => x.Time.ToUtcTime());
foreach (var item in orderedValues)
{
map[item.Id].Add(item.Time.ToUtcTime(), item.value);
}
return map;
}
public static DateTimeKind UnspecifiedDefaultsTo { get; set; } = DateTimeKind.Utc;
public static DateTime ToUtcTime(this DateTime value)
{
// Unlike ToUniversalTime(), this method assumes any Unspecified Kind may be Utc or Local.
if (value.Kind == DateTimeKind.Unspecified)
{
if (UnspecifiedDefaultsTo == DateTimeKind.Utc)
{
value = DateTime.SpecifyKind(value, DateTimeKind.Utc);
}
else if (UnspecifiedDefaultsTo == DateTimeKind.Local)
{
value = DateTime.SpecifyKind(value, DateTimeKind.Local);
}
}
return value.ToUniversalTime();
}
private static DateTime TruncateTime(this DateTime value, TimeSpan interval) => new DateTime(TruncateTicks(value.Ticks, interval.Ticks)).ToUtcTime();
private static long TruncateTicks(long ticks, long interval) => (interval == 0) ? ticks : (ticks / interval) * interval;
public static SortedList<DateTime, double> GetInterpolatedValues(SortedList<DateTime, double> recordedValues, TimeSpan interval)
{
if (interval <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException($"{nameof(interval)} TimeSpan must be greater than zero");
}
var interpolatedValues = new SortedList<DateTime, double>();
var previous = recordedValues.First();
var intervalTimestamp = previous.Key.TruncateTime(interval);
foreach (var current in recordedValues)
{
if (current.Key == intervalTimestamp)
{
// It's easy when the current recorded value aligns perfectly on the desired interval.
interpolatedValues.Add(current.Key, current.Value);
intervalTimestamp += interval;
}
else if (current.Key > intervalTimestamp)
{
// We do not exactly align at the desired time, so we must interpolate
// between the "last recorded data" BEFORE the desired time (i.e. previous)
// and the "first recorded data" AFTER the desired time (i.e. current).
var interpolatedValue = GetInterpolatedValue(intervalTimestamp, previous, current);
interpolatedValues.Add(interpolatedValue.Key, interpolatedValue.Value);
intervalTimestamp += interval;
}
previous = current;
}
return interpolatedValues;
}
private static KeyValuePair<DateTime, double> GetInterpolatedValue(DateTime interpolatedTime, KeyValuePair<DateTime, double> left, KeyValuePair<DateTime, double> right)
{
if (!double.IsNaN(left.Value) && !double.IsNaN(right.Value))
{
double totalDuration = (right.Key - left.Key).TotalSeconds;
if (Math.Abs(totalDuration) > double.Epsilon)
{
double partialDuration = (interpolatedTime - left.Key).TotalSeconds;
double factor = partialDuration / totalDuration;
double calculation = left.Value + ((right.Value - left.Value) * factor);
return new KeyValuePair<DateTime, double>(interpolatedTime, calculation);
}
}
return new KeyValuePair<DateTime, double>(interpolatedTime, double.NaN);
}
public static SortedList<DateTime, double> GetDeltaValues(SortedList<DateTime, double> values, CalculationTimeBasis timeBasis = CalculationTimeBasis.Auto)
{
const CalculationTimeBasis autoDefaultsTo = CalculationTimeBasis.MostRecentTime;
var deltas = new SortedList<DateTime, double>(capacity: values.Count);
var previous = values.First();
foreach (var current in values.Skip(1))
{
var time = GetTimeForBasis(timeBasis, previous.Key, current.Key, autoDefaultsTo);
var diff = current.Value - previous.Value;
deltas.Add(time, diff);
previous = current;
}
return deltas;
}
private static DateTime GetTimeForBasis(CalculationTimeBasis timeBasis, DateTime earliestTime, DateTime mostRecentTime, CalculationTimeBasis autoDefaultsTo)
{
if (timeBasis == CalculationTimeBasis.Auto)
{
// Different (future) methods calling this may require different interpretations of Auto.
// Thus we leave it to the calling method to declare what Auto means to it.
timeBasis = autoDefaultsTo;
}
switch (timeBasis)
{
case CalculationTimeBasis.EarliestTime:
return earliestTime;
case CalculationTimeBasis.MidpointTime:
return new DateTime((earliestTime.Ticks + mostRecentTime.Ticks) / 2L).ToUtcTime();
case CalculationTimeBasis.MostRecentTime:
return mostRecentTime;
case CalculationTimeBasis.Auto:
default:
return earliestTime;
}
}
}
Usage Example
var inputValues = new List<JsonDto>();
// TODO: Magically populate inputValues
var tagDataMap = ProcessData.GetTagTimedValuesMap(inputValues);
foreach (var item in tagDataMap)
{
// Following would generate hourly differences for the one Tag Id (item.Key)
// by first generating hourly data, and then finding the delta of that.
var hourlyValues = ProcessData.GetInterpolatedValues(item.Value, TimeSpan.FromHours(1));
// Consider the difference between Hour(1) and Hour(2).
// That is, 2 input values will create 1 output value.
// Now you must decide which of the 2 input times you use for the 1 output time.
// This is what I call the CalculationTimeBasis.
// The time basis used will be Auto, which defaults to the most recent for this particular method, e.g. Hour(2)
var deltaValues = ProcessData.GetDeltaValues(hourlyValues);
// Same as above except we explicitly state we want the most recent time, e.g. also Hour(2)
var deltaValues2 = ProcessData.GetDeltaValues(hourlyValues, ProcessData.CalculationTimeBasis.MostRecentTime);
// Here the calculated differences are the same except the now
// timestamp now reflects the earliest time, e.g. Hour(1)
var deltaValues3 = ProcessData.GetDeltaValues(hourlyValues, ProcessData.CalculationTimeBasis.EarliestTime);
I am trying to do geofence monitoring/analytics using KSQLDB. I want to get a message whenever a vehicle ENTERS/LEAVES a geofence. Taking inspiration from the [https://github.com/gschmutz/various-demos/tree/master/kafka-geofencing] I have created a UDF named as GEOFENCE, below is the code for the same.
Below is my query to perform join on geofence stream and live vehicle position stream
CREATE stream join_live_pos_geofence_status_1 AS SELECT lp1.vehicleid,
lp1.lat,
lp1.lon,
s1p.geofencecoordinates,
Geofence(lp1.lat, lp1.lon, 'POLYGON(('+s1p.geofencecoordinates+'))') AS geofence_status
FROM live_position_1 LP1
LEFT JOIN stream_1_processed S1P within 72 hours
ON kmdlp1.clusterid = kmds1p.clusterid emit changes;
I am taking into account all the geofences created in last 3 days.
I have created another query to use the geofence status from previous query to calculate whether the vehicle is ENTERING/LEAVING geofence.
CREATE stream join_geofence_monitoring_1 AS SELECT *,
Geofence(jlpgs1.lat, jlpgs1.lon, 'POLYGON(('+jlpgs1.geofencecoordinates+'))', jlpgs1.geofence_status) geofence_monitoring_status
FROM join_live_pos_geofence_status_1 JLPGS1 emit changes;
The above query give me the output as 'INSIDE', 'INSIDE' for geofence_status and geofence_monitoring_status columns, respectively or the output is 'OUTSIDE', 'OUTSIDE' for geofence_status and geofence_monitoring_status columns, respectively. I know I am not taking into account the time aspect, like these 2 queries should never be executed at same time say 't0' but I am not able to think the correct way of doing this.
public class Geofence
{
private static final String OUTSIDE = "OUTSIDE";
private static final String INSIDE = "INSIDE";
private static GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
private static WKTReader wktReader = new WKTReader(geometryFactory);
#Udf(description = "Returns whether a coordinate lies within a polygon or not")
public static String geofence(final double latitude, final double longitude, String geometryWKT) {
boolean status = false;
String result = "";
Polygon polygon = null;
try {
polygon = (Polygon) wktReader.read(geometryWKT);
// However, an important point to note is that the longitude is the X value
// and the latitude the Y value. So we say "lat/long",
// but JTS will expect it in the order "long/lat".
Coordinate coord = new Coordinate(longitude, latitude);
Point point = geometryFactory.createPoint(coord);
status = point.within(polygon);
if(status)
{
result = INSIDE;
}
else
{
result = OUTSIDE;
}
} catch (ParseException e) {
throw new RuntimeException(e.getMessage());
}
return result;
}
#Udf(description = "Returns whether a coordinate moved in or out of a polygon")
public static String geofence(final double latitude, final double longitude, String geometryWKT, final String statusBefore) {
String status = geofence(latitude, longitude, geometryWKT);
if (statusBefore.equals("INSIDE") && status.equals("OUTSIDE")) {
//status = "LEAVING";
return "LEAVING";
} else if (statusBefore.equals("OUTSIDE") && status.equals("INSIDE")) {
//status = "ENTERING";
return "ENTERING";
}
return status;
}
}
My question is how can I calculate correctly that a vehicle is ENTERING/LEAVING a geofence? Is it even possible to do with KSQLDB?
Would it be correct to say that the join_live_pos_geofence_status_1 stream can have rows that go from INSIDE -> OUTSIDE and then from OUTSIDE -> INSIDE for some key value?
And what you're wanting to do is to output LEAVING and ENTERING events for these transitions?
You can likely do what you want using a custom UDAF. Custom UDAFs take and input and calculate an output, via some intermediate state. For example, an AVG udaf would take some numbers as input, its intermediate state would be the number of inputs and the sum of inputs, and the output would be count/sum.
In your case, the input would be the current state, e.g. either INSIDE or OUTSIDE. The UDAF would need to store the last two states in its intermediate state, and then the output state can be calculated from this. E.g.
Input Intermediate Output
INSIDE INSIDE <only single in intermediate - your choice what you output>
INSIDE INSIDE,INSIDE no-change
OUTSIDE INSIDE,OUTSIDE LEAVING
OUTSIDE OUTSIDE,OUTSIDE no-change
INSIDE OUTSIDE,INSIDE ENTERING
You'll need to decide what to output when there is only a single entry in the intermediate state, i.e. the first time a key is seen.
You can then filter the output to remove any rows that have no-change.
You may also need to set cache.max.bytes.buffering to zero to stop any results being conflated.
UPDATE: suggested code.
Not tested, but something like the following code may do what you want:
#UdafDescription(name = "my_geofence", description = "Computes the geofence status.")
public final class GoeFenceUdaf {
private static final String STATUS_1 = "STATUS_1";
private static final String STATUS_2 = "STATUS_2";
#UdafFactory(description = "Computes the geofence status.",
aggregateSchema = "STRUCT<" + STATUS_1 + " STRING, " + STATUS_2 + " STRING>")
public static Udaf<String, Struct, String> calcGeoFenceStatus() {
final Schema STRUCT_SCHEMA = SchemaBuilder.struct().optional()
.field(STATUS_1, Schema.OPTIONAL_STRING_SCHEMA)
.field(STATUS_2, Schema.OPTIONAL_STRING_SCHEMA)
.build();
return new Udaf<String, Struct, String>() {
#Override
public Struct initialize() {
return new Struct(STRUCT_SCHEMA);
}
#Override
public Struct aggregate(
final String newValue,
final Struct aggregate
) {
if (newValue == null) {
return aggregate;
}
if (aggregate.getString(STATUS_1) == null) {
// First status for this key:
return aggregate
.put(STATUS_1, newValue);
}
final String lastStatus = aggregate.getString(STATUS_2);
if (lastStatus == null) {
// Second status for this key:
return aggregate
.put(STATUS_2, newValue);
}
// Third and subsequent status for this key:
return aggregate
.put(STATUS_1, lastStatus)
.put(STATUS_2, newValue);
}
#Override
public String map(final Struct aggregate) {
final String previousStatus = aggregate.getString(STATUS_1);
final String currentStatus = aggregate.getString(STATUS_2);
if (currentStatus == null) {
// Only have single status, i.e. first status for this key
// What to do? Probably want to do:
return previousStatus.equalsIgnoreCase("OUTSIDE")
? "LEAVING"
: "ENTERING";
}
// Two statuses ...
if (currentStatus.equals(previousStatus)) {
return "NO CHANGE";
}
return previousStatus.equalsIgnoreCase("OUTSIDE")
? "ENTERING"
: "LEAVING";
}
#Override
public Struct merge(final Struct agg1, final Struct agg2) {
throw new RuntimeException("Function does not support session windows");
}
};
}
}
I am trying to change the value of a TextField for display only. Ie - when users attempt to enter phone number, they only enter the digits and when they leave the field, it displays formatted without changing the data in the field.
Let's say I have a TextField for a phone number and it should allow digits only, maximum of 10 characters:
2085551212
I can handle that with a TextFormatter using a UnaryOperator.
UnaryOperator<TextFormatter.Change> filter = new UnaryOperator<TextFormatter.Change>() {
#Override
public TextFormatter.Change apply(TextFormatter.Change change) {
int maxlength = 14;
if(change.getControlText().indexOf('(') == -1) {
maxlength = 10;
}
System.out.println(change);
if (change.getControlText().length() + change.getText().length() >= maxlength) {
int maxPos = maxlength - change.getControlText().length();
change.setText(change.getText().substring(0, maxPos));
}
String text = change.getText();
for (int i = 0; i < text.length(); i++)
if (!Character.isDigit(text.charAt(i)))
return null;
return change;
}
};
However I would like to format the value to be when it's 10 characters long (likely unformatted when != 10):
(208) 555-1212
When I use a TextFormatter to format it, it changes the value of the string to (208) 555-1212. We store only the digits in the database 2085551212.
I attempted this with a StringConverter. But I couldn't make it work. In the toString() method I strip out the formatting, however when I do that my TextField doesn't display.
StringConverter<String> formatter = new StringConverter<String>() {
#Override
public String fromString(String string) {
System.out.println("fromString(): before = " + string);
if (string.length() == 14) {
System.out.println("fromString(): after = " + string);
return string;
} else if (string.length() == 10 && string.indexOf('-') == -1) {
String result = String.format("(%s) %s-%s", string.substring(0, 3), string.substring(3, 6),
string.substring(6, 10));
System.out.println("fromString(): after = " + result);
return result;
} else {
return null;
}
}
#Override
public String toString(String object) {
System.out.println("toString(): before = " + object);
if(object == null) {
return "";
}
Pattern p = Pattern.compile("[\\p{Punct}\\p{Blank}]", Pattern.UNICODE_CHARACTER_CLASS);
Matcher m = p.matcher(object);
object = m.replaceAll("");
System.out.println("toString(): after = " + object);
return object;
}
};
I bound to a TextField like this which I assumed would work:
txtPhone.setTextFormatter(new TextFormatter<String>(formatter, null, filter));
t2.textProperty().bindBidirectional(foo.fooPropertyProperty(), formatter); //I was just testing to see the results in another textfield to see if it would work.
So I am at a loss. I essentially want to allow only digits and then when the user leaves the field present the value in a formatted way - without actually changing the string value that goes to the database.
You are confusing the purpose of toString() and fromString() methods with each other in your converter. toString() converts the value property of your text editor to the displayed text, not the other way around. Try switching the code in these methods and it should work.
The reason why your text field does not display anything after loosing focus is because fromString() method is called and returns null (from the else clause). This commits null to the value property of your editor. The change in value property updates the displayed text (textProperty) by calling toString(null) which changes the text property of your editor to an empty string.
EDIT
Below is my test code that is a follow-up to the discussion in the comments. I reused a large amount of your original code. I created an FXML JavaFX project and defined TextField and Label in FXML file. The TextField accepts user's input and formats it. The Label displays value of the text formatter (only digits) that should go to the database. The value is accessible by calling formatter.valueProperty().get(). I hope it helps.
import java.net.URL;
import java.util.ResourceBundle;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.util.StringConverter;
public class FXMLDocumentController implements Initializable {
// label displays phone number containing only digits (for database)
#FXML private Label label;
/* field displays formatted text (XXX)-XXX-XXXX after user types
10 digits and presses Enter or if the field looses focus */
#FXML private TextField field;
#Override
public void initialize(URL url, ResourceBundle rb) {
UnaryOperator<TextFormatter.Change> filter = new UnaryOperator<TextFormatter.Change>() {
#Override
public TextFormatter.Change apply(TextFormatter.Change change) {
if (!change.isContentChange()) {
/* nothing is added or deleted but change must be returned
* as it contains selection info and caret position
*/
return change;
}
int maxlength = 14;
if (change.getControlText().indexOf('(') == -1) {
maxlength = 10;
}
if (change.getControlNewText().length() > maxlength
|| change.getText().matches("\\D+")) {
// invalid input. Cancel the change
return null;
}
return change;
}
};
StringConverter<String> converter = new StringConverter<String>() {
// updates displayed text from commited value
#Override
public String toString(String commitedText) {
if (commitedText == null) {
// don't change displayed text
return field.getText();
}
if (commitedText.length() == 10 && !commitedText.matches("\\D+")) {
return String.format("(%s) %s-%s", commitedText.substring(0, 3), commitedText.substring(3, 6),
commitedText.substring(6, 10));
} else {
/* Commited text can be either null or 10 digits.
* Nothing else is allowed by fromString() method unless changed directly
*/
throw new IllegalStateException(
"Unexpected or incomplete phone number value: " + commitedText);
}
}
// commits displayed text to value
#Override
public String fromString(String displayedText) {
// remove formatting characters
Pattern p = Pattern.compile("[\\p{Punct}\\p{Blank}]", Pattern.UNICODE_CHARACTER_CLASS);
Matcher m = p.matcher(displayedText);
displayedText = m.replaceAll("");
if (displayedText.length() != 10) {
// user is not done typing the number. Don't commit
return null;
}
return displayedText;
}
};
TextFormatter<String> formatter = new TextFormatter<String>(converter, "1234567890", filter);
field.setTextFormatter(formatter);
label.textProperty().bind(formatter.valueProperty());
}
}
I have been looking for a while but people seem to be waaaaay ahead of me on on the chess front. All i want to do is have a method in a class to resolve the colour of a tile but my colour keeps coming up as "null".
import java.util.Scanner;
public class ChessTileTest {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String tileColour;
chessTile test = new chessTile();
System.out.print(" Enter chess move : ");
String move = in.next();
tileColour = test.setColour(move);
System.out.println(tileColour);
}
}
public class chessTile {
private String colour;
private String address;
public chessTile(){
}
public String setColour(String move){
char letter;
int number;
letter = move.charAt(0);
number = move.charAt(1);
if((letter=='a'||letter=='c'||letter=='e'||letter=='g')&&(number/2==1)){
colour = "Black";
}
else if((letter=='a'||letter=='c'||letter=='e'||letter=='g')&&(number/2==0)){
colour = "white";
}
else if((letter=='b'||letter=='d'||letter=='f'||letter=='h')&&(number/2==1)){
colour = "white";
}
else if((letter=='b'||letter=='d'||letter=='f'||letter=='h')&&(number/2==0)){
colour = "Black";
}
return colour;
}
}
First lines of setColour(...) are
char letter = move.charAt(0); // gets ASCII character at index 0
int number = move.charAt(1); // gets **int value of** ASCII character at index 1
So, for exmaple, if your string is "a1", then letter = a but number = 49 because the integer ASCII value of the character "1" is 49. See this ASCII chart for more . . . http://www.asciitable.com/index/asciifull.gif
You will need to convert the character into a proper int. You can do that with the following . . .
int number = Character.getNumericValue( move.charAt(1) );
Since you are probably getting a bad value, none of the if-statements are satisfied and a null value is returned
The code that is giving me trouble is converting my content in the Text(), to an int so that I could use some arithmetic. I know how to convert a string to an int, but for some reason Hadoop needs to read items in a file as a TExt(). I can't find a method to do this conversion. ANy ideas on how this could be done?
package BaseballStats;
import java.io.IOException;
import java.util.Iterator;
import mrtools.CountMap;
import mrtools.NBest;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.MapReduceBase;
import org.apache.hadoop.mapred.OutputCollector;
import org.apache.hadoop.mapred.Reducer;
import org.apache.hadoop.mapred.Reporter;
public class StatReducer extends MapReduceBase
implements Reducer<Text, Text, Text, Text[]> {
private Text Hits = new Text();
private Text AtBats = new Text();
//private Text Year = new Text();
int totalAtBats = 0;
int totalHits = 0;
float average = 0;
Text [] Pair = {AtBats, Hits};
public void reduce(Text key, Iterator<Text> values,
OutputCollector<Text, Text[]> output,
Reporter reporter) throws IOException {
//Need to pull values from the Array and assign to AtBats
//THen total all at bats and hits per year (key)
//Divide hits by at Bats to get batting average
//Output batting average per year
for (Text s: Pair) {
//Do your stuff here
Pair[0] = AtBats;
Pair[1] = Hits;
}
//Get values to key to sum up and get a total per year.
while (values.hasNext()){
int AB = Integer.parseInt(AtBats);
int i = Integer.valueOf(AtBats);
totalAtBats = AB + totalAtBats;
}
}
}
You could first convert the Text to a string using the .toString() method and then convert the result to an int.