提问人:GayCriminal 提问时间:10/5/2023 最后编辑:Joel CoehoornGayCriminal 更新时间:10/5/2023 访问量:79
如何向上移动文件中的行以填充空白
How do i move lines in a file upwards to fill gaps
问:
我正在制作一个程序来存储存储在 4 个单独文件中的 4 组值;ID、名称、数量和价格。现在我对其进行了编码,因此您可以选择一次删除一行,但我无法填补代码运行时留下的空白,我尝试了 for 循环来遍历文件并将所有内容向上移动一,但它超出了界限。
static void Removestock()
{
StreamReader IDread = new StreamReader("IDTEXT.txt");
StreamReader NAMEread = new StreamReader("NAMETEXT.txt");
StreamReader QUANTITYread = new StreamReader("QUANTITYTEXT.txt");
StreamReader PRICEread = new StreamReader("PRICETEXT.txt");
int IDlines = System.IO.File.ReadAllLines("IDTEXT.txt").Length;
int NAMElines = System.IO.File.ReadAllLines("NAMETEXT.txt").Length;
int QUANTITYlines = System.IO.File.ReadAllLines("QUANTITYTEXT.txt").Length;
int PRICElines = System.IO.File.ReadAllLines("PRICETEXT.txt").Length;
bool finished = false;
bool del = false;
string ID = "";
bool found = false;
bool confirm = false;
string t1 = "";
string t2 = "";
string t3 = "";
string t4 = "";
int count = 0;
while (finished == false)
{
try
{
Console.WriteLine("Do you want to Delete a stock or return to the menu (true/false)");
del = Boolean.Parse(Console.ReadLine());
if (del == true)
{
Console.WriteLine("");
Console.WriteLine("Input the Stock's ID you wish to delete");
ID = Console.ReadLine();
while (found == false)
{
t1 = IDread.ReadLine();
t2 = NAMEread.ReadLine();
t3 = QUANTITYread.ReadLine();
t4= PRICEread.ReadLine();
if (t1 == ID)
{
Console.WriteLine("Found a match!");
Console.WriteLine();
Console.WriteLine(t1 + " " + t2 + " " + t3 + " " + t4);
Console.WriteLine();
confirm = Boolean.Parse(Console.ReadLine());
if (del == true)
{
Console.WriteLine("Deleting...");
List<string> ID2 = new List<string>();
List<string> NAME = new List<string>();
List<int> QUANTITY = new List<int>();
List<double> PRICE = new List<double>();
int temp = count;
while (temp != IDlines-1)
{
ID2.Add(IDread.ReadLine());
NAME.Add(NAMEread.ReadLine());
QUANTITY.Add(int.Parse(QUANTITYread.ReadLine()));
PRICE.Add(double.Parse(PRICEread.ReadLine()));
temp = temp + 1;
}
IDread.Close();
NAMEread.Close();
QUANTITYread.Close();
PRICEread.Close();
StreamWriter IDW = new StreamWriter("IDTEXT.txt");
StreamWriter NMW = new StreamWriter("NAMETEXT.txt");
StreamWriter QUW = new StreamWriter("QUANTITYTEXT.txt");
StreamWriter PRW = new StreamWriter("PRICETEXT.txt");
for (int i = count; i < IDlines; i++)
{
IDW.Write(ID2[i]);
NMW.Write(NAME[i]);
QUW.Write(QUANTITY[i]);
PRW.Write(PRICE[i]);
}
}
}
else
{
count = count + 1;
}
}
}
else
{
Console.WriteLine("Exiting System.Delete now...");
finished = true;
}
}
catch
{
}
}
IDread.Close();
NAMEread.Close();
QUANTITYread.Close();
PRICEread.Close();
}
我研究了其他一些方法,我看到了大多数答案的来源,但是我尽量不使用 System.IO.File 的东西,因为我专注于流读取和流写入 总的来说,我希望将文本移动到我创建的文件中的空白区域。 此外,对您认为可能导致问题的代码其他部分的任何帮助也表示赞赏
编辑:我已经研究过它,CSV似乎很复杂,所以我会把它们留给我的下一个项目,因为大多数人说你应该安装一个帮助系统,我绝对还没有准备好这样做。 此外,您可以忽略我在 4 个单独的文件而不是一个数据库上执行此操作的事实,想象一下我只将一个文件用于一组数据,并且只是实现了 4 次。
答:
您的代码缺少数据模型。至少创建一个类来包含库存中每个项目的数据。Item
public class Item
{
public string Id { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}
请注意,要对价格使用小数
,以避免愚蠢的事情,例如价格为 1.59999999999997 而不是 1.6
这是,语言主要是围绕类设计的,你应该利用这一点。C#
在类中,您可以编写方法来读取和写入数据,既可以从旧版 4 个单独的文本文件,也可以从单个 CSV 文件读取和写入数据。Item
下面是处理 4 个文本文件中的数据的示例方法,其中包含一些基本的验证和检查
public class Item
{
...
/// <summary>
/// Parse 4 lines from text files.
/// </summary>
/// <param name="IDline">The id text.</param>
/// <param name="NAMEline">The name text.</param>
/// <param name="QUANTITYline">The quantity text.</param>
/// <param name="PRICEline">The price text.</param>
/// <returns>An <see cref="Item"/> if successful, <code>null</code> otherwise.</returns>
public static Item FromTextItems(string IDline, string NAMEline, string QUANTITYline, string PRICEline)
{
string ID = IDline.Trim(); // need to convert to uppercase?
string NAME = NAMEline.Trim();
// Validation & avoid exception when converting
// This is the part it skips "gaps" (or empty lines)
// as `ID` or `NAME` cannot be just whitespace.
if (!string.IsNullOrWhiteSpace(ID)
&& !string.IsNullOrWhiteSpace(NAME)
&& int.TryParse(QUANTITYline, out int QTY)
&& decimal.TryParse(PRICEline, out decimal PRICE))
{
Item item = new Item()
{
Id = ID,
Name = NAME,
Quantity = QTY,
Price = PRICE
};
return item;
}
return null;
}
...
}
我还创建了另一个类,它包含多个 s,可以对数据进行整体读/写。我可以只使用 ,但下面的代码允许 be 不仅可以处理数据,还可以充当字典并从中查找项目,而无需筛选所有项目。Item
List<Item>
ID
public class Stock : System.Collections.ObjectModel.KeyedCollection<string, Item>
{
/// <summary>
/// Extracts the key from the specified element.
/// </summary>
/// <param name="item">The element from which to extract the key.</param>
/// <returns>The key for the specified element.</returns>
protected override string GetKeyForItem(Item item)
{
return item.Id;
}
/// <summary>
/// Read data from legacy 4 text files.
/// </summary>
/// <param name="idFilePath">Text file containing ids in each row.</param>
/// <param name="nameFilePath">Text file containing names in each row.</param>
/// <param name="priceFilePath">Text file containing prices in each row.</param>
/// <param name="qtyFilePath">Text file containing quantities in each row.</param>
/// <returns>A <see cref="Stock"/> object with data loaded.</returns>
public static Stock ReadLegacyData(string idFilePath, string nameFilePath, string qtyFilePath, string priceFilePath)
{
Stock stock = new Stock();
StreamReader IDread = new StreamReader(idFilePath);
StreamReader NAMEread = new StreamReader(nameFilePath);
StreamReader QUANTITYread = new StreamReader(qtyFilePath);
StreamReader PRICEread = new StreamReader(priceFilePath);
// Check ALL files for available data
while (!IDread.EndOfStream && !NAMEread.EndOfStream && !QUANTITYread.EndOfStream && !PRICEread.EndOfStream)
{
string IDline = IDread.ReadLine();
string NAMEline = NAMEread.ReadLine();
string QUANTITYline = QUANTITYread.ReadLine();
string PRICEline = PRICEread.ReadLine();
var item = Item.FromTextItems(IDline, NAMEline, QUANTITYline, PRICEline);
if (item != null)
{
stock.Add(item);
}
}
return stock;
}
}
这应该足以替换您现在拥有的功能。如果您想删除特定项目,只需调用 where 是要删除的项目的 id。stock.Remove("XYZ")
XYZ
本文的第二部分是实现文件处理,包括读取和写入(包括标题行)。CSV
CSV 文件中的每一行都由类中的这些方法处理Item
public class Item
{
...
/// <summary>
/// String representation of the item. Contains all data.
/// </summary>
public override string ToString() => $"({Id},{Name},{Quantity},{Price:c2})";
/// <summary>
/// Header line for CSV file.
/// </summary>
public static string ToCSVHead() => $"{"ID"},{"NAME"},{"QUANTITY"},{"PRICE"}";
/// <summary>
/// Item line for CSV file.
/// </summary>
public string ToCSVLine() => $"{Id},{Name},{Quantity},{Price}";
/// <summary>
/// Read a line from a CSV file and parse it.
/// </summary>
/// <param name="line">The csv file line.</param>
/// <returns>An <see cref="Item"/> if successful, <code>null</code> otherwise.</returns>
public static Item FromCSVLine(string line)
{
// This will skip any blank lines
if (!string.IsNullOrWhiteSpace(line))
{
// This will skip comments that start with #
if (!line.StartsWith("#"))
{
string[] parts = line.Split(',');
// Make sure all data is present
if (parts.Length == 4)
{
return FromTextItems(parts[0], parts[1], parts[2], parts[3]);
}
}
}
return null;
}
...
}
并且整个库存的物品可以处理文件操作
public class Stock : System.Collections.ObjectModel.KeyedCollection<string, Item>
{
...
/// <summary>
/// Saves to data to CSV file.
/// </summary>
/// <param name="fileName">Name of the file.</param>
public void SaveToCSV(string fileName)
{
FileStream fs = File.OpenWrite(fileName);
var csv = new StreamWriter(fs);
csv.WriteLine(Item.ToCSVHead());
foreach (var item in this)
{
csv.WriteLine(item.ToCSVLine());
}
fs.Close();
}
/// <summary>
/// Reads data from a CSV file.
/// </summary>
/// <param name="fileName">Name of the file.</param>
/// <returns>Stock.</returns>
public static Stock ReadFromCSV(string fileName)
{
Stock stock = new Stock();
FileStream fs = File.OpenRead(fileName);
var csv = new StreamReader(fs);
while (!csv.EndOfStream)
{
var item = Item.FromCSVLine(csv.ReadLine());
if (item != null)
{
stock.Add(item);
}
}
return stock;
}
...
}
用一个非常基本的测试代码来询问路径,读取 4 个文本文件,然后写出一个文件.csv.
static class Program
{
static void Main(string[] args)
{
string idFilePath = "IDTEXT.txt";
string nameFilePath = "NAMETEXT.txt";
string qtyFilePath = "QUANTITYTEXT.txt";
string priceFilePath = "PRICETEXT.txt";
var stock = Stock.ReadLegacyData(idFilePath, nameFilePath, qtyFilePath, priceFilePath);
string csvFileName = "INVENTORY.CSV";
stock.SaveToCSV(csvFileName);
Process.Start(csvFileName);
}
}
根据它们的价值实施和实施也是一个好主意。典型的实现是IComparable<Item>
IEquatable<Item>
Item
ID
public class Item : IComparable<Item>, IEquatable<Item>
{
public string Id { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
...
#region IEquatable Members
/// <summary>
/// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object.
/// </summary>
/// <param name="other">An object to compare with this instance.</param>
/// <returns>A value that indicates the relative order of the objects being compared. The return value has these meanings:
/// Value
/// Meaning
/// Less than zero
/// This instance precedes <paramref name="other" /> in the sort order.
/// Zero
/// This instance occurs in the same position in the sort order as <paramref name="other" />.
/// Greater than zero
/// This instance follows <paramref name="other" /> in the sort order.</returns>
public int CompareTo(Item other)
=> Id.CompareTo(other?.Id);
/// <summary>
/// Equality overrides from <see cref="System.Object"/>
/// </summary>
/// <param name="obj">The object to compare this with</param>
/// <returns>False if object is a different type, otherwise it calls <code>Equals(Item)</code></returns>
public override bool Equals(object obj) => obj is Item item && Equals(item);
/// <summary>
/// Checks for equality among <see cref="Item"/> classes
/// </summary>
/// <returns>True if equal</returns>
public bool Equals(Item other) => Id.Equals(other.Id);
/// <summary>
/// Calculates the hash code for the <see cref="Item"/>
/// </summary>
/// <returns>The int hash value</returns>
public override int GetHashCode()
{
unchecked
{
int hc = -1817952719;
hc = (-1521134295) * hc + Id.GetHashCode();
return hc;
}
}
#endregion
}
评论
Item
Stock
评论
CSV's seem quite complicated
这没什么意义 - CSV 只是一个文本文件,每行一条记录,值用逗号分隔。而已。你在这里尝试做的事情要复杂得多,因为你试图“匹配”它们之间可能完全没有关系的字符串。没有任何东西说一个文件中的第 5 行与另一个文件中的第 5 行相关。在同一行中只有一个包含您想要的 4 个值的文本文件要容易得多,用逗号分隔。