Bootstrap

C#第四次作业(类型转换,常见集合类型)

1.        整数转换,整数和字符串,字符串和整数之间的转换怎么实现?

1.1整数到字符串的转换

  • 使用ToString()方法

    整数类型(如intlong等)都提供了ToString()方法,可以直接将整数转换为字符串。

    int number = 123; 
    string numberAsString = number.ToString(); 
    Console.WriteLine(numberAsString); // 输出:123
  • 拓:使用字符串插值或$前缀

    使用字符串插值或$前缀来简化字符串的构造,也可以用于将整数转换为字符串。

    int number = 123; 
    string numberAsString = $"{number}"; 
    Console.WriteLine(numberAsString); // 输出:123

1.2字符串到整数的转换

  • 使用int.Parse()方法

  int.Parse()方法尝试将字符串参数转换为int类型。如果字符串不是有效的整数表示,则会抛        出FormatException异常;如果转换的数字超出了int的范围,则会抛出OverflowException。 

string strNumber = "123"; 
int number = int.Parse(strNumber); 
Console.WriteLine(number); // 输出:123

  • 使用int.TryParse()方法

   与int.Parse()不同,int.TryParse()方法尝试转换字符串,但不会抛出异常。如果转换成功,     它会返回true,并将转换后的值存储在第二个参数(一个out参数)中;如果转换失败,它会         返回false,并且第二个参数不会被修改。


	string strNumber = "123"; 

	if (int.TryParse(strNumber, out int number)) 

	{ 

	Console.WriteLine(number); // 输出:123 

	} 

	else 

	{ 

	Console.WriteLine("转换失败"); 

	}
  • 使用Convert.ToInt32()方法

    Convert.ToInt32()方法也可以用于将字符串转换为int类型。如果转换失败,它会抛出FormatExceptionOverflowException

    string strNumber = "123"; 
    int number = Convert.ToInt32(strNumber); 
    Console.WriteLine(number); // 输出:123

对于其他整数类型(如longshort等),也有类似的ParseTryParseConvert方法,可以根据需要选择使用。

2.        日期转换,获取当前日期,字符串转日期,日期转字符串怎么实现?

处理日期和时间通常涉及到DateTime类,以及DateTime.ParseDateTime.TryParseToString等方法和格式化字符串。

2.1获取当前日期

使用DateTime.NowDateTime.UtcNow来获取当前日期和时间DateTime.Now返回本地时间,而DateTime.UtcNow返回协调世界时(UTC)。

DateTime now = DateTime.Now; // 获取当前本地日期和时间 
DateTime utcNow = DateTime.UtcNow; // 获取当前UTC日期和时间 


Console.WriteLine(now.ToString()); // 输出本地时间 
Console.WriteLine(utcNow.ToString()); // 输出UTC时间

2.2字符串转日期

将字符串转换为DateTime对象时,可以使用DateTime.ParseDateTime.TryParse方法。Parse方法在转换失败时会抛出异常,而TryParse方法则不会,它会返回一个布尔值来表示转换是否成功


	string dateString = "2023-04-01"; 

	

	// 使用DateTime.Parse(如果字符串格式不正确,将抛出异常) 

	DateTime date = DateTime.Parse(dateString); 



	// 使用DateTime.TryParse(更安全,不会抛出异常) 

	if (DateTime.TryParse(dateString, out DateTime parsedDate)) 

	{ 

	Console.WriteLine(parsedDate.ToString()); // 输出转换后的日期 

	} 

	else 

	{ 

	Console.WriteLine("日期格式无效"); 

	}

2.3日期转字符串

DateTime对象转换为字符串时,可以使用ToString方法,并可以传递一个格式化字符串来指定输出格式。

DateTime date = DateTime.Now; 


// 使用默认格式 
string defaultString = date.ToString(); 


// 使用自定义格式 
string customString = date.ToString("yyyy-MM-dd HH:mm:ss"); // 输出格式为年-月-日 时:分:秒 


Console.WriteLine(defaultString); 
Console.WriteLine(customString);

ToString方法的格式化字符串中,你可以使用各种自定义格式说明符来指定日期的各个部分如何显示。例如,yyyy代表四位数的年份,MM代表月份,dd代表日,HH代表小时(24小时制),mm代表分,ss代表秒。

注意:如果你知道字符串的确切格式,并且它可能与当前文化设置不匹配,你可以使用DateTime.ParseExactDateTime.TryParseExact方法,并指定格式字符串和CultureInfo对象。

3.        举例一维、二维、三维数组

数组是一种数据结构,用于在内存中连续存储相同类型的数据。

3.1一维数组

一维数组是最简单的数组形式,它代表了一个线性序列的数据。


// 初始化时直接赋值 
int[] oneArray= { 1, 2, 3, 4, 5 }; 


// 访问数组元素 
Console.WriteLine(oneArray[2]); // 输出:3

3.2 二维数组

二维数组是数组的数组,可以看作是表格或矩阵。每个元素都是一个一维数组

int[,] twoDimensionalArray = new int[3, 4] {
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
}; // 创建一个3行4列的二维数组, 初始化时直接赋值

// 访问数组元素 
Console.WriteLine(twoDimensionalArray[1, 2]); // 输出:7

3.3三维数组

三维数组可以看作是数组的数组的数组,通常用于表示三维空间中的数据,如立方体中的值。

using System;  
  
class Program  
{  
    static void Main()  
    {  
        // 声明并初始化一个三维数组   
        int[,,] threeDimArray = new int[3, 3, 3]  
        {  
            {  
                {1, 2, 3},  
                {4, 5, 6},  
                {7, 8, 9}  
            },  
            {  
                {10, 11, 12},  
                {13, 14, 15},  
                {16, 17, 18}  
            },  
            {  
                {19, 20, 21},  
                {22, 23, 24},  
                {25, 26, 27}  
            }  
        };  
  
        // 访问三维数组中的元素  
        // 例如,访问第一层的第二个元素(一个二维数组),然后从这个二维数组中访问第二行第三列的元素  
        // 这将是数字15  
        Console.WriteLine(threeDimArray[1, 1, 2]); // 输出: 15  
  
        // 遍历三维数组  
        for (int i = 0; i < 3; i++)  
        {  
            for (int j = 0; j < 3; j++)  
            {  
                for (int k = 0; k < 3; k++)  
                {  
                    Console.Write(threeDimArray[i, j, k] + " ");  
                }  
                Console.WriteLine(); // 每完成一层二维数组的遍历后换行  
                if (j < 2) // 为了更好的格式,在两层二维数组之间添加两个空行  
                {  
                    Console.WriteLine();  
                    Console.WriteLine();  
                }  
            }  
        }  
    }  
}

4.        需求:有个88笔费用记录,总额3亿,金额在300万~800万之间,随机数如何实现?并记录总耗时。

思路:

初始化:

初始化一个Stopwatch来记录操作所需的时间。

定义常量,包括总交易数(totalTransactions)、目标总金额(totalAmountTarget)、每笔交易的最小金额(minAmount)和最大金额(maxAmount)。

创建一个List<double>来存储每笔交易的金额,并初始化一个变量sum来累加这些交易的金额。

初步分配:

计算每笔交易的平均金额(average),即目标总金额除以总交易数。

通过循环,为每笔交易分配一个初始金额。这个初始金额是平均金额加上一个随机调整值(adjustment),该调整值在-10% * (maxAmount - minAmount)+10% * (maxAmount - minAmount)之间随机选择,以确保每笔交易的金额都在最小值和最大值之间。

将每笔交易的金额添加到transactions列表中,并累加到sum变量中。

检查并调整总额:

计算实际总金额(sum)与目标总金额(totalAmountTarget)之间的差异(difference)。

如果这个差异的绝对值超过了目标总金额的5%,则需要进行调整。

计算需要调整的交易数(adjustCount),根据差异大小和每笔交易可调整的范围来计算。乘以了1.2来向上取整,确保有足够的空间进行调整。然后,将adjustCount限制在总交易数以内。

计算每笔需要调整的金额(adjustAmountPerItem),即差异除以需要调整的交易数。

通过循环,将调整金额均分到最近的几笔交易中。如果差异为正,则增加这些交易的金额;如果差异为负,则减少这些交易的金额。同时,确保每笔交易的金额不会超出最小值和最大值的限制。

在循环中,还加入了一个条件检查,如果差异已经缩小到目标总金额的5%以内,则提前退出循环。

using System;
using System.Collections.Generic;
using System.Diagnostics;

class Program
{
    static void Main(string[] args)
    {
        Stopwatch stopwatch = Stopwatch.StartNew();

        const int totalTransactions = 88;
        const double totalAmountTarget = 300000000; // 3亿  
        const double minAmount = 3000000; // 300万  
        const double maxAmount = 8000000; // 800万  

        List<double> transactions = new List<double>();
        double sum = 0;

        double average = totalAmountTarget / totalTransactions;
        for (int i = 0; i < totalTransactions; i++)
        {
            double adjustment = (new Random().NextDouble() - 0.5) * (maxAmount - minAmount) * 0.1; 
            double amount = average + adjustment;
            amount = Math.Max(amount, minAmount); // 确保不小于最小值  
            amount = Math.Min(amount, maxAmount); 

            transactions.Add(amount);
            sum += amount;
        }
        double difference = totalAmountTarget - sum;
        if (Math.Abs(difference) > totalAmountTarget * 0.05) // 误差超过5%  
        {
            int adjustCount = (int)(Math.Abs(difference) / (maxAmount - minAmount) * 1.2); // 向上取整,确保有足够空间进行调整  
            adjustCount = Math.Min(adjustCount, totalTransactions); // 不超过总交易数  

            double adjustAmountPerItem = difference / adjustCount;

            for (int i = 0; i < adjustCount && i < totalTransactions; i++)
            {
                if (difference > 0)
                {
                    transactions[i] += adjustAmountPerItem;
                    transactions[i] = Math.Min(transactions[i], maxAmount); // 防止超出上限  
                }
                else
                {
                    transactions[i] -= adjustAmountPerItem;
                    transactions[i] = Math.Max(transactions[i], minAmount); // 防止低于下限  
                }
                difference -= adjustAmountPerItem;
                if (Math.Abs(difference) < totalAmountTarget * 0.005)
                {
                    break;
                }
            }
        } 
        Console.WriteLine("Total Transactions: " + totalTransactions);
        Console.WriteLine("Total Amount: " + sum.ToString("N0"));
        Console.WriteLine("Average Amount: " + (sum / totalTransactions).ToString("N0"));
        Console.WriteLine("Elapsed Time: " + stopwatch.ElapsedMilliseconds + "ms"); 
        foreach (var transaction in transactions)
        {
            Console.WriteLine(transaction.ToString("N0"));
        }
    }
}


 

5.        简述常见的集合类型的存储结构和它们的作用以及优缺点,并写出实现案例

集合类型是一种非常强大且常用的数据结构,用于存储和管理一系列的对象或值

线性表

5.1 List<T>(泛型集合,可以被视为动态数组)

  • 存储结构基于数组的动态数组。
  • 作用:存储和操作对象的序列,允许根据索引快速访问元素,支持动态扩容。
  • 优点随机访问速度快,支持动态添加和删除元素
  • 缺点:在列表中间插入或删除元素时性能较低,因为需要移动其他元素。

实现案例


	List<int> numbers = new List<int> { 1, 2, 3, 4, 5 }; 

	numbers.Add(6); // 添加元素 

	numbers.Remove(3); // 删除元素 

	Console.WriteLine(numbers[2]); // 访问索引为2的元素

5.2 Array(数组)

  • 存储结构固定大小的连续内存块。
  • 作用存储固定数量的元素,类型相同。
  • 优点内存连续,访问速度快,固定大小可以提高内存效率。
  • 缺点大小不可变,灵活性差。

实现案例

int[] numbers = { 1, 2, 3, 4, 5 }; 
Console.WriteLine(numbers[0]); // 访问索引为0的元素

5.3 LinkedList<T>(链表)

  • 存储结构双向链表。
  • 作用:存储和操作对象的序列,支持在序列的任何位置快速添加和删除元素。
  • 优点:在列表头部和尾部添加、删除元素时性能高。
  • 缺点:随机访问性能低,需要遍历链表。

实现案例

LinkedList<int> numbers = new LinkedList<int>(); 
numbers.AddLast(1); 
numbers.AddFirst(0); 
numbers.RemoveFirst(); 
foreach (var num in numbers) 
{ 
Console.WriteLine(num); 
}

5.4 Queue(队列,泛型集合)

队列是一种先进先出(FIFO, First-In-First-Out)的数据结构。它允许在队列的一端(队尾)添加元素,在另一端(队头)移除元素。

优点
  1. 先进先出:队列按照元素被添加的顺序进行处理,确保了元素的顺序性。
  2. 高效性:在大多数情况下,队列的插入和删除操作(在队尾插入和在队头删除)具有较高的效率。
  3. 适用性广:队列广泛应用于各种场景,如任务调度、消息传递、广度优先搜索等。
缺点
  1. 空间限制:基于数组实现的队列在扩容时可能会受到内存空间的限制。
  2. 并发性能:在并发环境下,普通的队列实现(如Queue<T>)可能不是线程安全的,需要额外的同步机制来保护数据一致性,这可能会影响性能。
  3. 访问特定元素:队列不支持通过索引直接访问特定位置的元素,这限制了其在某些场景下的使用。

C# 提供了 Queue<T> 泛型类来实现队列。

Queue<int> queue = new Queue<int>(); 


// 入队 
queue.Enqueue(1); 
queue.Enqueue(2); 


// 出队 
Console.WriteLine(queue.Dequeue()); // 输出 1 


// 查看队首元素 
Console.WriteLine(queue.Peek()); // 输出 2,但不移除 


// 遍历队列 
foreach (int item in queue) 
{ 
Console.WriteLine(item); 
}

Enqueue 方法是 Queue<T> 类中的一个成员方法,用于在队列的末尾(队尾)插入一个新元素。

5.5 Stack(栈)

栈是一种后进先出(LIFO, Last-In-First-Out)的数据结构。它只允许在栈顶进行添加(push)或删除(pop)元素的操作。

Stack<int> stack = new Stack<int>(); 


// 入栈 
stack.Push(1); 
stack.Push(2); 


// 出栈 
Console.WriteLine(stack.Pop()); // 输出 2 


// 查看栈顶元素 
Console.WriteLine(stack.Peek()); // 输出 1,但不移除 


// 遍历栈(注意:栈通常不推荐遍历,因为这会破坏LIFO的原则) 
foreach (int item in stack.ToArray()) // 需要转换为数组或其他集合才能遍历 
{ 
Console.WriteLine(item); 
}

压栈(push,添加元素到栈顶)、弹栈(pop,移除栈顶元素并返回其值)、查看栈顶元素(peek或top,不移除栈顶元素但返回其值)等

哈希表

5.6 Hashtable<TKey, TValue>)(非泛型)

  • 键值对存储:存储的元素是键值对。
  • 非泛型集合:可以接受任何类型的键和值。
  • 键的唯一性:每个键在Hashtable中都是唯一的。
  • 无序:Hashtable中的元素是无序的,即它们不按照任何特定的顺序存储。
using System; 
using System.Collections; 


class Program 
{ 
static void Main() 
{ 
// 创建一个Hashtable实例 
Hashtable hashtable = new Hashtable(); 


// 添加键值对 
hashtable.Add("key1", "value1"); 
hashtable.Add("key2", 2); 
hashtable.Add("key3", true); 


// 访问Hashtable中的元素 
Console.WriteLine("key1对应的值: " + hashtable["key1"]); 
Console.WriteLine("key2对应的值: " + hashtable["key2"]); 
Console.WriteLine("key3对应的值: " + hashtable["key3"]); 


// 检查键是否存在 
if (hashtable.ContainsKey("key1")) 
{ 
Console.WriteLine("key1存在于Hashtable中。"); 
} 


// 遍历Hashtable 
foreach (DictionaryEntry entry in hashtable) 
{ 
Console.WriteLine("键: " + entry.Key + ", 值: " + entry.Value); 
} 
} 
}

在这个示例中,我们创建了一个Hashtable实例,并向其中添加了一些键值对。然后,我们通过键访问了这些值,并检查了某个键是否存在。最后,我们遍历了Hashtable中的所有元素,并打印了它们的键和值。

5.7 HashSet<T>(泛型集合)

  • 存储结构:哈希表。
  • 作用存储不重复的元素集合。
  • 优点:添加、删除和查找元素的速度快,自动去重
  • 缺点不支持索引访问,元素顺序不固定。

实现案例

HashSet<int> numbers = new HashSet<int> { 1, 2, 3, 4, 5, 5 }; // 自动去重 
numbers.Add(6); 
numbers.Remove(3); 
foreach (var num in numbers) 
{ 
Console.WriteLine(num); 
}

概念
HashSet<T> 是一个泛型集合,它存储不重复的元素。HashSet 使用哈希表来存储元素,因此它可以提供非常快速的元素查找、插入和删除操作。

用途
HashSet<T> 常用于需要快速检查一个元素是否存在于集合中,或者需要从集合中移除重复元素的场景。

优点

  • 快速查找:由于使用了哈希表,HashSet 提供了平均时间复杂度为O(1)的查找、插入和删除操作。
  • 不重复元素:HashSet 自动确保集合中不包含重复的元素。
  • 灵活性:可以存储任何类型的对象,只要它们支持相等性比较(IEquatable<T> 或 object.Equals)。

缺点

  • 无序:HashSet 不保证元素的顺序,特别是当元素被添加或删除时。
  • 不支持索引访问:由于HashSet是一个集合,它不支持通过索引来访问元素。
  • 内存占用:与字典类似,HashSet 由于需要存储元素以及它们的哈希码和内部状态信息,可能会占用比数组或列表更多的内存。

;