Building a String array from a text file without collection classes - arrays

I am trying to build an array from a buffered in text file. This class is used by another class with a main method. What I have only prints the file... what I need is to have an array of strings, built line by line, mirroring the text file. I need to be able to then search against that array using a String from user input (that part will be in main method too) that will name a product, and find the corresponding price. I can't use things like ArrayList, Maps, Vectors, etc. This is in Java8.
/**
* A class that reads in inventory from vmMix1 text file using BufferedReader
* # author Michelle Merritt
*/
import java.io.*;
public class VendingMachine1
{
BufferedReader inInvFile1 = new BufferedReader(
new FileReader("vmMix1.txt"))
/**
* A method to print vending machine 1 inventory
*/
public void printVM1()
{
try
{
String vm1Line;
while((vm1Line = inInvFile1.readLine()) != null)
{
// This is what I was using for now to simply print my file
System.out.println(vm1Line);
}
}
catch(IOException e)
{
System.out.println("I/O Error: " + e);
}
}
}
This is the code that created my text file, since I can't seem to see how I attach the text file instead.
/**
* A class that creates the inventory found in vending machine #1, using
* a PrintWriter stream.
* # author Michelle Merritt
*/
import java.io.*;
public class VMMix1
{
public static void main(String[] args)
{
String [] product = {"Coke", "Dr. Pepper", "Sprite", "RedBull",
"Cool Ranch Doritos", "Lay's Potato Chips",
"Pretzels", "Almonds", "Snickers", "Gummi Bears",
"Milky Way", "KitKat"};
String [] packaging = {"bottle", "can", "can", "can", "bag", "bag",
"bag", "bag", "wrapper", "bag", "wrapper",
"wrapper"};
float [] price = {2.25f, 1.75f, 1.75f, 2.00f, 1.00f, 1.00f, 0.75f, 1.50f,
1.25f, 1.00f, 1.25f, 1.25f};
int [] quantity = {10, 10, 10, 12, 8, 10, 12, 9, 7, 11, 10, 8};
try(PrintWriter outFile = new PrintWriter("vmMix1.txt"))
{
for (int index = 0; index < product.length; index++)
{
outFile.printf("%-18s %-10s: $%.2f qty: %3d\n", product[index],
packaging[index], price[index], quantity[index]);
}
}
catch (IOException except)
{
System.out.println("IOException: " + except.getMessage());
}
}
}
I need for this thing to be dynamic. As the program runs, and something is purchased, I will have to account for losing inventory and changing the amount of money in the vending machine (there's another class for currency that houses quantities of denominations of money). I have to maintain the values in the array and reprint the updated array. Any help is much appreciated.

You may use Java8 stream API
String[] array = reader.lines().toArray(String[]::new);
You could even skip the buffer creation using
try (Stream<String> stream = Files.lines(Paths.get("vmMix1.txt"))) {
String [] array = stream.toArray(String[]::new);
}
Pre-Java8, probably one of the shortest way is to read the entire file into a string, and split it (ways to read a reader into string can be found here):
String[] array = fileAsString.split('\\n');
Of course you could also built the array in your loop and increase it for every line using System.arraycopy (which can be quite slow in that case).
String[] array = new String[0];
while((vm1Line = inInvFile1.readLine()) != null) {
String[] newArray = new String[array.length + 1];
System.arraycopy(array, 0, newArray, 0, array.length);
newArray[array.length] = vm1Line;
array = newArray;
}
You may optimize this approach by creating a larger array first, fill in the lines, increase size of the array as needed (using arraycopy), and finally shrink the array to the number of written lines.
Thats more or less what an array list does. So in case you may use the collections api, you could simply do
List<String> list = new ArrayList<>();
while((vm1Line = inInvFile1.readLine()) != null) {
list.add(vm1Line);
}
String[] array = list.toArray(new String[list.size()]);

Hope it helps.
Stream<String> lines = Files.lines(Paths.get("C:/SelfStudy/Input.txt"));
String[] array = lines.toArray(String[]::new);

Related

Stream of Char to Stream of Byte/Byte Array

The following code takes a String s, converts into char array, filters digits from it, then converts it to string, then converts into byte array.
char charArray[] = s.toCharArray();
StringBuffer sb = new StringBuffer(charArray.length);
for(int i=0; i<=charArray.length-1; i++) {
if (Character.isDigit(charArray[i]))
sb.append(charArray[i]);
}
byte[] bytes = sb.toString().getBytes(Charset.forName("UTF-8"));
I'm trying to change the above code to streams approach. Following is working.
s.chars()
.sequential()
.mapToObj(ch -> (char) ch)
.filter(Character::isDigit)
.collect(StringBuilder::new,
StringBuilder::append, StringBuilder::append)
.toString()
.getBytes(Charset.forName("UTF-8"));
I think there could be a better way to do it.
Can we directly convert Stream<Character> to byte[] & skip the conversion to String in between?
First, both of your variants have the problem of not handling characters outside the BMP correctly.
To support these characters, there is codePoints() as an alternative to chars(). You can use appendCodePoint on the target StringBuilder to consistently use codepoints throughout the entire operation. For this, you have to remove the unnecessary .mapToObj(ch -> (char) ch) step, whose removal also eliminates the overhead of creating a Stream<Character>.
Then, you can avoid the conversion to a String in both cases, by encoding the StringBuilder using the Charset directly. In the case of the stream variant:
StringBuilder sb = s.codePoints()
.filter(Character::isDigit)
.collect(StringBuilder::new,
StringBuilder::appendCodePoint, StringBuilder::append);
ByteBuffer bb = StandardCharsets.UTF_8.encode(CharBuffer.wrap(sb));
byte[] utf8Bytes = new byte[bb.remaining()];
bb.get(utf8Bytes);
Performing the conversion directly with the stream of codepoints is not easy. Not only is there no such support in the Charset API, there is no straight-forward way to collect a Stream into a byte[] array.
One possibility is
byte[] utf8Bytes = s.codePoints()
.filter(Character::isDigit)
.flatMap(c -> c<128? IntStream.of(c):
c<0x800? IntStream.of((c>>>6)|0xC0, c&0x3f|0x80):
c<0x10000? IntStream.of((c>>>12)|0xE0, (c>>>6)&0x3f|0x80, c&0x3f|0x80):
IntStream.of((c>>>18)|0xF0, (c>>>12)&0x3f|0x80, (c>>>6)&0x3f|0x80, c&0x3f|0x80))
.collect(
() -> new Object() { byte[] array = new byte[8]; int size;
byte[] result(){ return array.length==size? array: Arrays.copyOf(array,size); }
},
(b,i) -> {
if(b.array.length == b.size) b.array=Arrays.copyOf(b.array, b.size*2);
b.array[b.size++] = (byte)i;
},
(a,b) -> {
if(a.array.length<a.size+b.size) a.array=Arrays.copyOf(a.array,a.size+b.size);
System.arraycopy(b.array, 0, a.array, a.size, b.size);
a.size+=b.size;
}).result();
The flatMap step converts the stream of codepoints to a stream of UTF-8 unit. (Compare with the UTF-8 description on Wikipedia) The collect step collects int values into a byte[] array.
It’s possible to eliminate the flatMap step by creating a dedicate collector which collects a stream of codepoints directly into a byte[] array
byte[] utf8Bytes = s.codePoints()
.filter(Character::isDigit)
.collect(
() -> new Object() { byte[] array = new byte[8]; int size;
byte[] result(){ return array.length==size? array: Arrays.copyOf(array,size); }
void put(int c) {
if(array.length == size) array=Arrays.copyOf(array, size*2);
array[size++] = (byte)c;
}
},
(b,c) -> {
if(c < 128) b.put(c);
else {
if(c<0x800) b.put((c>>>6)|0xC0);
else {
if(c<0x10000) b.put((c>>>12)|0xE0);
else {
b.put((c>>>18)|0xF0);
b.put((c>>>12)&0x3f|0x80);
}
b.put((c>>>6)&0x3f|0x80);
}
b.put(c&0x3f|0x80);
}
},
(a,b) -> {
if(a.array.length<a.size+b.size) a.array=Arrays.copyOf(a.array,a.size+b.size);
System.arraycopy(b.array, 0, a.array, a.size, b.size);
a.size+=b.size;
}).result();
but it doesn’t add to readability.
You can test the solutions using a String like
String s = "some test text 1234 ✔ 3 𝟝";
and printing the result as
System.out.println(Arrays.toString(utf8Bytes));
System.out.println(new String(utf8Bytes, StandardCharsets.UTF_8));
which should produce
[49, 50, 51, 52, -17, -68, -109, -16, -99, -97, -99]
12343𝟝
It should be obvious that the first variant is the simplest, and it will have reasonable performance, even if it doesn’t create a byte[] array directly. Further, it’s the only variant which can be adapted for getting other result charsets.
But even the
byte[] utf8Bytes = s.codePoints()
.filter(Character::isDigit)
.collect(StringBuilder::new,
StringBuilder::appendCodePoint, StringBuilder::append)
.toString().getBytes(StandardCharsets.UTF_8);
is not so bad, regardless of whether the toString() operation bears a copying operation.

Splitting a text file where the information are separated in different lines

So, I have a text file where the information are separated by the enter key (I don't know how to explain, I will paste the code and some stuff).
cha-cha
Fruzsina
Ede
salsa
Szilvia
Imre
Here's how the text file looks like, and I need to split it into three parts, the first being the type of the dance, and then dancer 1 and dancer 2.
using System;
using System.Collections.Generic;
using System.IO;
namespace tanciskola
{
struct tanc
{
public string tancnev;
public string tancos1;
public string tancos2;
}
class Program
{
static void Main(string[] args)
{
#region 1.feladat
StreamReader sr = new StreamReader("tancrend.txt");
tanc[] tanc = new tanc[140];
string[] elv;
int i = 0;
while (sr.Peek() != 0)
{
elv = sr.ReadLine().Split('I don't know what goes here');
tanc[i].tancnev = elv[0];
tanc[i].tancos1 = elv[1];
tanc[i].tancos2 = elv[2];
i++;
}
#endregion
Console.ReadKey();
}
}
}
Here is how I tried to solve it, although I don't really get how I should do it. The task is would be to display the first dance and the last dance, but for that I need to split it somehow.
As mentioned in my comments, you seem to have a text file where each item is on a new line, and a set of 3 lines constitutes a single 'record'. In that case, you can simply read all the lines of the file, and then create your records, like so:
var v = File.ReadLines("file path");
tancr[] tanc = new tancr[140];
for (int i = 0; i < v.Count(); i += 3)
{
tanc[i/3].tancnev= v.ElementAt(i);
tanc[i/3].tancos1 = v.ElementAt(i + 1);
tanc[i/3].tancos2 = v.ElementAt(i + 2);
}
Note: ReadLines() is better when the file size is large. If your file is small, you could use ReadAllLines() instead.
To split by the "enter character" you can use Environment.NewLine in .NET:
https://msdn.microsoft.com/en-us/library/system.environment.newline(v=vs.110).aspx
elv = sr.ReadAllText().Split(new string[] {Environment.NewLine}, StringSplitOptions.None);
This constant will contain the sequence that is specific to your OS (I'm guessing Windows).
You should be aware that the characters used for newlines is different for Windows vs. Linux/Unix. So in the rare event that someone edits your file on a different OS, you can run into problems.
On Windows, newline is a two character sequence: carriage-return + line-feed (ASCII 13 + 10). On Linux it is just line-feed. So if you wanted to be extra clever, you could first check for CRLF and if you only get one element back from Split() then try just LF.

Simple ArrayIndexOutOfBoundsException 1 in loop

I have a .data file containing lines of values. I partition them into separate values using the split method, then I initialize an ArrayList where I add the model items to the list. I had a while loop for this specific code here which looked like this:
while (inFile.hasNextLine() {
// Do something
}
That didn't seem to work so I switched it to a for loop.
public MachineLearningInstance(File f) {
try {
int noOfRowsInData = 0;
LineNumberReader lnr = new LineNumberReader(new FileReader(f));
try {
lnr.skip(Long.MAX_VALUE);
noOfRowsInData = lnr.getLineNumber();
//System.out.println(noOfRowsInData);
lnr.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
irisData = new ArrayList<Iris>();
// While there is another line in inFile.
Scanner inFile = new Scanner(f);
for (int i = 0; i < noOfRowsInData; i++) {
if (inFile.hasNextLine()) {
// Store line into String
String line = inFile.nextLine();
// Partition values into separate elements in array
String[] numbers = line.split(comma);
// Grab values from that line and store it into a Iris ArrayList Item
irisData.add(i, new Iris(i, numbers[0], numbers[1], numbers[2], numbers[3], numbers[4]));
}
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
For some weird reason (and I bet it is a really simply reason I just can't see it) I keep getting the ArrayListIndexOutOfBoundsException 1 when I run this piece of code. I'm guessing that my while loop keeps looping? I don't understand what the problem seems to be.
Is it possible that my LineNumberReader is not reading the number of lines properly? I don't think that is the case though. Most likely I am not declaring something correctly.
So this happened to solve my problem, I'm really tired and have no interest right now in finding out why but it fixed the issue lol:
for (int i = 0; i < noOfRowsInData - 1; i++) {
if (inFile.hasNextLine()) {
// Store line into String
String line = inFile.nextLine();
// Partition values into separate elements in array
String[] numbers = line.split(comma);
// Grab values from that line and store it into a Iris ArrayList Item
irisData.add(i, new Iris(i, numbers[0], numbers[1], numbers[2], numbers[3], numbers[4]));
}
}
Supposedly noOfRowsInData - 1 fixes it.

Array throwing exception

I can't seem to put my finger on this and why the array is not being initialized.
Basically I am coding a 2d top down spaceship game and the ship is going to be fully customizable. The ship has several allocated slots for certain "Modules" (ie weapons, electronic systems) and these are stored in an array as follows:
protected Array<Weapon> weaponMount;
Upon creating the ship none of the module arrays are initialized, since some ships might have 1 weapon slot, while others have 4.
So when I code new ships, like this example:
public RookieShip(World world, Vector2 position) {
this.width = 35;
this.height = 15;
// Setup ships model
bodyDef.type = BodyType.DynamicBody;
bodyDef.position.set(position);
body = world.createBody(bodyDef);
chassis.setAsBox(width / GameScreen.WORLD_TO_BOX_WIDTH, height / GameScreen.WORLD_TO_BOX_HEIGHT);
fixtureDef.shape = chassis;
fixtureDef.friction = 0.225f;
fixtureDef.density = 0.85f;
fixture = body.createFixture(fixtureDef);
sprite = new Sprite(new Texture(Gdx.files.internal("img/TestShip.png")));
body.setUserData(sprite);
chassis.dispose();
// Ship module properties
setShipName("Rookie Ship");
setCpu(50);
setPower(25);
setFuel(500);
setWeaponMounts(2, world);
setDefenseSlots(1);
addModule(new BasicEngine(), this);
addModule(new BasicBlaster(), this);
// Add hp
setHullHP(50);
setArmorHP(125);
setShieldHP(125);
}
#Override
public void addModule(Module module, Ship currentShip) {
// TODO Auto-generated method stub
super.addModule(module, currentShip);
}
#Override
public void setWeaponMounts(int weaponMounts, World world) {
weaponMount = new Array<Weapon>(weaponMounts);
// super.setWeaponMounts(weaponMounts, world);
}
#Override
public String displayInfo() {
String info = "Everyones first ship, sturdy, reliable and only a little bit shit";
return info;
}
When I set the number of weapon mounts the following method is called:
public void setWeaponMounts(int weaponMounts, World world) {
weaponMount = new Array<Weapon>(weaponMounts);
}
This basically initializes the array with a size (weapon mounts available) to whatever the argument is. Now to me this seems fine but I have setup a hotkey to output the size of the Array, which reports zero. If I try to reference any objects in the array, it throws an outofbounds exception.
The addModule method adds to the array as follows:
public void addModule(Module module, Ship currentShip) {
currentShip.cpu -= module.getCpuUsage();
currentShip.power -= module.getPowerUsage();
if(module instanceof Engine){
engine = (Engine) module;
}else if(module instanceof Weapon){
if(maxWeaponMounts == weaponMount.size){
System.out.println("No more room for weapons!");
}else{
maxWeaponMounts += 1;
weaponMount.add((Weapon)module);
}
}
}
My coding ain't great but heh, better than what I was 2 month ago....
Any ideas?
First of all, You should avoid instanceof. It's not a really big deal performance-wise, but it always points to problems with your general architecture. Implement two different addModule methods. One that takes a Weapon, and one that takes an Engine.
Now back to topic:
else if(module instanceof Weapon){
if (maxWeaponMounts == weaponMount.size) {
System.out.println("No more room for weapons!");
} else{
maxWeaponMounts += 1;
weaponMount.add((Weapon)module);
}
}
It looks like you use maxWeaponMounts as a counter instead of a limit. That's why I assume that it will initially be 0. The same holds for Array.size. It is not the limit, but size also counts how many elements the Array currently holds. Thus you will always have (maxWeaponMounts == weaponMount.size) as 0 == 0 and you will not add the weapon to the array. It will always stay empty and trying to reference any index will end in an OutOfBoundsException.
What you should actually do is using maxWeaponMounts as a fixed limit and not the counter.
public void setWeaponMounts(int weaponMounts, World world) {
weaponMount = new Array<Weapon>(weaponMounts);
maxWeaponMounts = weaponMounts;
}
else if(module instanceof Weapon){
if (weaponMount.size >= maxWeaponMounts) {
System.out.println("No more room for weapons!");
} else{
weaponMount.add((Weapon)module);
}
}

How to read and add only numbers to array from a text file

I'm learning java and I have a question regarding reading from file
i want to read only numbers from file that contains strings as well.
here is an example of my file:
66.56
"3
JAVA
3-43
5-42
2.1
1
and here is my coding:
public class test {
public static void main (String [] args){
if (0 < args.length) {
File x = new File(args[0]);
try{
Scanner in = new Scanner( new FileInputStream(x));
ArrayList<Double> test = new ArrayList<>();
while(in.hasNext()){
if(in.hasNextDouble()){
Double f=in.nextDouble();
test.add(f);}
else
{in.next();}
}
catch(IOException e) { System.err.println("Exception during reading: " + e); }
}
my problem is it only add 66.56,2.1 and 1
it doesn't add 3 after "3 or it ignores 3-43 and 5-42
can you tell me how to skip Strings and only add doubles here?
thanks
All the said three ie; "3, 3-43 and 4-42 are strings
Either u read a string and split it and check for number at " and - or you put in a space between characters and integers.
The JVM after compilation would treat it all as string if it cannot be converted to a double.
And the File reader wont stop reading till at least a space or a newline.
Hence your code would never work the way you intend it to unless you do as I said above.
Solution 1:
Change your input file to something like this:
66.56
" 3
JAVA
3 - 43
5 - 42
2.1
1
Solution 2:
Considering the highly variable nature of your input file I am posting a solution only made for your current input. If the input changes a more versatile algorithm would need to be implemented.
public static void main(String[] args) {
File x = new File(args[0]);
try {
Scanner in = new Scanner(new FileInputStream(x));
ArrayList<Double> test = new ArrayList<>();
while (in.hasNext()) {
if (in.hasNextDouble()) {
Double f = in.nextDouble();
test.add(f);
} else {
String s=in.next();
if(s.contains("\"")){
String splits[]=s.split("\"");
test.add(Double.parseDouble(splits[1]));
}
else if (s.contains("-")){
String splits[]=s.split("-");
test.add(Double.parseDouble(splits[0]));
test.add(Double.parseDouble(splits[1]));
}
}
}
System.out.println(test);
} catch (IOException e) {
System.err.println("Exception during reading: " + e);
}
}
You can write custom Type Sensor Utility class to check whether the object can be converted to Integer or not. I would approach to this problem like this.
Moreover I can see that you have values like 2.1 and " 3 to handle these scenarios write additional methods like isDoubleType() or isLongType() etc.
Also you need to write some custom logic to solve this problem.
public class TypeSensor {
public String inferType(String value) throws NullValueException {
int formatIndex = -1;
if (null == value) {
throw new NullValueException("Value provided for type inference was null");
}else if (this.isIntegerType(value)) {
return "Integer";
}else{
LOGGER.info("Value " + value + " doesnt fit to any predefined types. Defaulting to String.");
return "String";
}
}
}
private boolean isIntegerType(String value) {
boolean isParseable = false;
try {
Integer.parseInt(value);
LOGGER.info("Parsing successful for " + value + " to Integer.");
isParseable = true;
} catch (NumberFormatException e) {
LOGGER.error("Value " + value + " doesn't seem to be of type Integer. This is not fatal. Exception message is->"
+ e.getMessage());
}
return isParseable;
}
}

Resources