存储和引用数百个值的有效方法是什么?
What is an efficient way to store and reference hundreds of values?
在我的游戏中,我有许多不同类型的单位 instantiated/destroyed 具有规律性。它们具有某些值,例如 MaxHP 或 Level 或 Defense,需要在实例化时引用这些值,并且可能会因某些因素而异。例如,如果 Level 增加,MaxHP 可能会更高,但不是基于严格的公式,它因单位而异。所以,例如:
- 士兵 1 级最大生命值:5
- 士兵 2 级最大生命值:6
士兵 3 级最大生命值:8
1 级法师最大生命值:3
- 2 级法师最大生命值:4
- 法师 3 级最大生命值:5
class 名称很容易引用,单位的级别将存储在其他地方,我需要知道我要查找的是哪个特征才能在第一时间查找地方。因此,对我来说有意义的是将它们存储为 key/value 对。我可以以编程方式检查类似 (className + lvlString + traitName) 的内容作为键,因此键最终将成为 Mage2MaxHP,值将为 4.
通常我在这方面的直觉很差,因为我从来没有学过很多数据结构。所以,我想知道是否有更好的方法来实现这一目标并组织这些数据。例如,对于一本字典来说,这似乎是非常大的。拆分成几个字典(一个用于 MaxHP,一个用于 Defense 等)也许会更易于管理,但我仍然觉得我忽略了一个更适合此目的的数据结构。有什么建议吗?
我通常使用如下代码:
public class Unit
{
public static Dictionary<string, Unit> units { get; set; } // string is unit name
public string name { get; set; }
public int level { get; set; }
public Dictionary<string, int> attributes { get; set; }
}
"Hundreds" 与现代的 CPU 相比确实不多,字典增加了很多复杂性并将您绑定到一种访问模式。通常最好从简单开始:使用 List<T>
并确定您的访问模式。您可以针对它使用 LINQ,通过 class 名称、生命值、级别或其任意组合进行查询。一旦你让它工作,如果你发现你需要提高某些查询的性能,那么,在那个时候继续并重构它可能使用 Dictionary
作为索引。
但永远不要低估现代编译器和现代 CPU 迭代连续内存块的能力,它以惊人的速度击败许多其他数据结构,而不是为非常大的 N 值提供更好的理论性能.
以下内容将同时使用继承和字典来解决您的问题,以避免必须 class-name-strings 等
public abstract class Unit
{
// This approach would use one Dictionary per Trait
protected abstract Dictionary<int, int> MaxHpByLevel { get; }
public int Level { get; set; } = 1;
public int MaxHp => this.MaxHpByLevel[this.Level];
}
public class Soldier : Unit
{
protected override Dictionary<int, int> MaxHpByLevel => new Dictionary<int, int>
{
[1] = 5,
[2] = 6,
[3] = 8
};
}
public class Mage : Unit
{
protected override Dictionary<int, int> MaxHpByLevel => new Dictionary<int, int>
{
[1] = 3,
[2] = 4,
[3] = 5
};
}
你会这样使用它:
var soldier = new Soldier { Level = 2 };
Console.WriteLine(soldier.MaxHp); // 6
和你一样,我也认为可以通过不同的方式解决这个问题。
我喜欢这种特殊方法的地方在于它基于 OOP-Principles 并且它减少了冗余的结构元素(例如避免 trait-types 等的枚举)。您可以通过 - 单元属性访问单元的属性。
这里使用的词典会比较小。
但是,在谈到 size-restrictions 时,我同意 comments/answers 的人的看法。无论您选择哪种方法,您的词典都不会接近其极限。
您可能希望将 类 和属性作为枚举,我希望它在很多其他地方也能派上用场。类似于:
public enum Class
{
Soldier,
Mage,
/*...*/
}
public enum Attribute
{
Health,
Magic,
/*...*/
}
然后将它们组合成一个字典的键,它应该比只是一个连接的字符串更有效 "useful"。类似于:
public struct AttributeKey : IEquatable<AttributeKey>
{
public AttributeKey(Class @class, Attribute attribute, int level)
{
Class = @class;
Attribute = attribute;
Level = level;
}
public readonly Class Class;
public readonly Attribute Attribute;
public readonly int Level;
public bool Equals(AttributeKey other)
{
return Class == other.Class && Attribute == other.Attribute && Level == other.Level;
}
public override bool Equals(object obj)
{
return obj is AttributeKey && Equals((AttributeKey)obj);
}
public override int GetHashCode()
{
unchecked
{
return (((Class.GetHashCode() * 397) ^ Attribute.GetHashCode()) * 397) ^ Level;
}
}
}
要用作字典中的键,最重要的是要非常慎重地使用 Equals
,尤其是 GetHashCode
方法。
例如,请参阅 Why is it important to override GetHashCode when Equals method is overridden? and MSDN 以了解更多相关信息。
没有特别的理由说明为什么这比任何其他方法都好。但是,您可以将 class 与 Indexer
一起使用,该 Indexer
具有内部 Dictionary
和 Tuple
键
Note : Ideally you would want to store and load this stuff from db, or a file, or maybe just generate it dynamically if there is
not too much. However, depending on the nature of the game, this maybe
a novel approach.
主要
private static readonly CharLookup _lookup = new CharLookup();
private static void Main()
{
_lookup[CharacterClass.Mage, CharacterTrait.SingingAbility, 2] = 123;
_lookup[CharacterClass.Mage, CharacterTrait.SingingAbility, 3] = 234;
_lookup[CharacterClass.Soilder, CharacterTrait.MaxBeers, 3] = 23423;
Console.WriteLine("Mage,SingingAbility,2 = " + _lookup[CharacterClass.Mage, CharacterTrait.SingingAbility, 2]);
Console.WriteLine("Soilder,MaxBeers,3 = " + _lookup[CharacterClass.Soilder, CharacterTrait.MaxBeers, 3]);
}
枚举
public enum CharacterClass
{
Soilder,
Mage,
SmellyCoder
}
public enum CharacterTrait
{
MaxHp,
MaxBeers,
SingingAbility
}
CharLookup
public class CharLookup
{
private Dictionary<Tuple<CharacterClass, CharacterTrait, int>, int> myDict = new Dictionary<Tuple<CharacterClass, CharacterTrait, int>, int>();
public int this[CharacterClass characterClass, CharacterTrait characterTrait, int level]
{
get => Check(characterClass, characterTrait, level);
set => Add(characterClass, characterTrait, level, value);
}
public void Add(CharacterClass characterClass, CharacterTrait characterTrait, int level, int value)
{
var key = new Tuple<CharacterClass, CharacterTrait, int>(characterClass, characterTrait, level);
if (myDict.ContainsKey(key))
myDict[key] = value;
else
myDict.Add(key, value);
}
public int Check(CharacterClass characterClass, CharacterTrait characterTrait, int level)
{
var key = new Tuple<CharacterClass, CharacterTrait, int>(characterClass, characterTrait, level);
if (myDict.TryGetValue(key, out var result))
return result;
throw new ArgumentOutOfRangeException("blah");
}
}
这仍然存储在字典中,但包括简单的枚举和存储和检索数据的方法。
public enum UnitType
{
Soldier,
Mage
}
public enum StatType
{
MaxHP,
MaxMP,
Attack,
Defense
}
// Where the unit initialisation data is stored
public static class UnitData
{
private static Dictionary<string, Dictionary<StatType, int>> Data = new Dictionary<UnitType, Dictionary<StatType, int>>();
private static string GetKey(UnitType unitType, int level)
{
return $"{unitType}:{level}";
}
public static AddUnit(UnitType unitType, int level, int maxHP, int maxMP, int attack, int defense)
{
Data.Add(GetKey(unitType, level),
new Dictionary<StatType, int>
{
{ StatType.MaxHP, maxHP },
{ StatType.MaxMP, maxMP },
{ StatType.Attack, attack },
{ StatType.Defense, defense }
});
}
public static int GetStat(UnitType unitType, int level, StatType statType)
{
return Data[GetKet(unitType, level][statType];
}
}
// The data is not stored against the unit but referenced from UnitData
public class Unit
{
public UnitType UnitType { get; private set; }
public int Level { get; private set; }
public Unit(UnitType unitType, int level)
{
UnitType = unitTypel
Level = level;
}
public int GetStat(StatType statType)
{
return UnitData.GetStat(UnitType, Level, statType);
}
}
// To initialise the data
public class StartClass
{
public void InitialiseData()
{
UnitData.Add(UnitType.Soldier, 1, 5, 0, 1, 1);
UnitData.Add(UnitType.Soldier, 2, 6, 0, 2, 2);
UnitData.Add(UnitType.Soldier, 3, 8, 0, 3, 3);
UnitData.Add(UnitType.Mage, 1, 3, 10, 1, 1);
UnitData.Add(UnitType.Mage, 2, 4, 15, 2, 2);
UnitData.Add(UnitType.Mage, 3, 5, 20, 3, 3);
}
}
// Use of units
public class Level1
{
public List<Unit> Units = new List<Unit>();
public void InitialiseUnits()
{
Units.Add(new Unit(UnitType.Soldier, 1));
Units.Add(new Unit(UnitType.Soldier, 1));
Units.Add(new Unit(UnitType.Mage, 1));
Units.Add(new Unit(UnitType.Mage, 1));
}
public void Something()
{
int maxHP = Units.First().GetStat(StatType.MaxHP);
// etc
}
}
在我的游戏中,我有许多不同类型的单位 instantiated/destroyed 具有规律性。它们具有某些值,例如 MaxHP 或 Level 或 Defense,需要在实例化时引用这些值,并且可能会因某些因素而异。例如,如果 Level 增加,MaxHP 可能会更高,但不是基于严格的公式,它因单位而异。所以,例如:
- 士兵 1 级最大生命值:5
- 士兵 2 级最大生命值:6
士兵 3 级最大生命值:8
1 级法师最大生命值:3
- 2 级法师最大生命值:4
- 法师 3 级最大生命值:5
class 名称很容易引用,单位的级别将存储在其他地方,我需要知道我要查找的是哪个特征才能在第一时间查找地方。因此,对我来说有意义的是将它们存储为 key/value 对。我可以以编程方式检查类似 (className + lvlString + traitName) 的内容作为键,因此键最终将成为 Mage2MaxHP,值将为 4.
通常我在这方面的直觉很差,因为我从来没有学过很多数据结构。所以,我想知道是否有更好的方法来实现这一目标并组织这些数据。例如,对于一本字典来说,这似乎是非常大的。拆分成几个字典(一个用于 MaxHP,一个用于 Defense 等)也许会更易于管理,但我仍然觉得我忽略了一个更适合此目的的数据结构。有什么建议吗?
我通常使用如下代码:
public class Unit
{
public static Dictionary<string, Unit> units { get; set; } // string is unit name
public string name { get; set; }
public int level { get; set; }
public Dictionary<string, int> attributes { get; set; }
}
"Hundreds" 与现代的 CPU 相比确实不多,字典增加了很多复杂性并将您绑定到一种访问模式。通常最好从简单开始:使用 List<T>
并确定您的访问模式。您可以针对它使用 LINQ,通过 class 名称、生命值、级别或其任意组合进行查询。一旦你让它工作,如果你发现你需要提高某些查询的性能,那么,在那个时候继续并重构它可能使用 Dictionary
作为索引。
但永远不要低估现代编译器和现代 CPU 迭代连续内存块的能力,它以惊人的速度击败许多其他数据结构,而不是为非常大的 N 值提供更好的理论性能.
以下内容将同时使用继承和字典来解决您的问题,以避免必须 class-name-strings 等
public abstract class Unit
{
// This approach would use one Dictionary per Trait
protected abstract Dictionary<int, int> MaxHpByLevel { get; }
public int Level { get; set; } = 1;
public int MaxHp => this.MaxHpByLevel[this.Level];
}
public class Soldier : Unit
{
protected override Dictionary<int, int> MaxHpByLevel => new Dictionary<int, int>
{
[1] = 5,
[2] = 6,
[3] = 8
};
}
public class Mage : Unit
{
protected override Dictionary<int, int> MaxHpByLevel => new Dictionary<int, int>
{
[1] = 3,
[2] = 4,
[3] = 5
};
}
你会这样使用它:
var soldier = new Soldier { Level = 2 };
Console.WriteLine(soldier.MaxHp); // 6
和你一样,我也认为可以通过不同的方式解决这个问题。
我喜欢这种特殊方法的地方在于它基于 OOP-Principles 并且它减少了冗余的结构元素(例如避免 trait-types 等的枚举)。您可以通过 - 单元属性访问单元的属性。
这里使用的词典会比较小。 但是,在谈到 size-restrictions 时,我同意 comments/answers 的人的看法。无论您选择哪种方法,您的词典都不会接近其极限。
您可能希望将 类 和属性作为枚举,我希望它在很多其他地方也能派上用场。类似于:
public enum Class
{
Soldier,
Mage,
/*...*/
}
public enum Attribute
{
Health,
Magic,
/*...*/
}
然后将它们组合成一个字典的键,它应该比只是一个连接的字符串更有效 "useful"。类似于:
public struct AttributeKey : IEquatable<AttributeKey>
{
public AttributeKey(Class @class, Attribute attribute, int level)
{
Class = @class;
Attribute = attribute;
Level = level;
}
public readonly Class Class;
public readonly Attribute Attribute;
public readonly int Level;
public bool Equals(AttributeKey other)
{
return Class == other.Class && Attribute == other.Attribute && Level == other.Level;
}
public override bool Equals(object obj)
{
return obj is AttributeKey && Equals((AttributeKey)obj);
}
public override int GetHashCode()
{
unchecked
{
return (((Class.GetHashCode() * 397) ^ Attribute.GetHashCode()) * 397) ^ Level;
}
}
}
要用作字典中的键,最重要的是要非常慎重地使用 Equals
,尤其是 GetHashCode
方法。
例如,请参阅 Why is it important to override GetHashCode when Equals method is overridden? and MSDN 以了解更多相关信息。
没有特别的理由说明为什么这比任何其他方法都好。但是,您可以将 class 与 Indexer
一起使用,该 Indexer
具有内部 Dictionary
和 Tuple
键
Note : Ideally you would want to store and load this stuff from db, or a file, or maybe just generate it dynamically if there is not too much. However, depending on the nature of the game, this maybe a novel approach.
主要
private static readonly CharLookup _lookup = new CharLookup();
private static void Main()
{
_lookup[CharacterClass.Mage, CharacterTrait.SingingAbility, 2] = 123;
_lookup[CharacterClass.Mage, CharacterTrait.SingingAbility, 3] = 234;
_lookup[CharacterClass.Soilder, CharacterTrait.MaxBeers, 3] = 23423;
Console.WriteLine("Mage,SingingAbility,2 = " + _lookup[CharacterClass.Mage, CharacterTrait.SingingAbility, 2]);
Console.WriteLine("Soilder,MaxBeers,3 = " + _lookup[CharacterClass.Soilder, CharacterTrait.MaxBeers, 3]);
}
枚举
public enum CharacterClass
{
Soilder,
Mage,
SmellyCoder
}
public enum CharacterTrait
{
MaxHp,
MaxBeers,
SingingAbility
}
CharLookup
public class CharLookup
{
private Dictionary<Tuple<CharacterClass, CharacterTrait, int>, int> myDict = new Dictionary<Tuple<CharacterClass, CharacterTrait, int>, int>();
public int this[CharacterClass characterClass, CharacterTrait characterTrait, int level]
{
get => Check(characterClass, characterTrait, level);
set => Add(characterClass, characterTrait, level, value);
}
public void Add(CharacterClass characterClass, CharacterTrait characterTrait, int level, int value)
{
var key = new Tuple<CharacterClass, CharacterTrait, int>(characterClass, characterTrait, level);
if (myDict.ContainsKey(key))
myDict[key] = value;
else
myDict.Add(key, value);
}
public int Check(CharacterClass characterClass, CharacterTrait characterTrait, int level)
{
var key = new Tuple<CharacterClass, CharacterTrait, int>(characterClass, characterTrait, level);
if (myDict.TryGetValue(key, out var result))
return result;
throw new ArgumentOutOfRangeException("blah");
}
}
这仍然存储在字典中,但包括简单的枚举和存储和检索数据的方法。
public enum UnitType
{
Soldier,
Mage
}
public enum StatType
{
MaxHP,
MaxMP,
Attack,
Defense
}
// Where the unit initialisation data is stored
public static class UnitData
{
private static Dictionary<string, Dictionary<StatType, int>> Data = new Dictionary<UnitType, Dictionary<StatType, int>>();
private static string GetKey(UnitType unitType, int level)
{
return $"{unitType}:{level}";
}
public static AddUnit(UnitType unitType, int level, int maxHP, int maxMP, int attack, int defense)
{
Data.Add(GetKey(unitType, level),
new Dictionary<StatType, int>
{
{ StatType.MaxHP, maxHP },
{ StatType.MaxMP, maxMP },
{ StatType.Attack, attack },
{ StatType.Defense, defense }
});
}
public static int GetStat(UnitType unitType, int level, StatType statType)
{
return Data[GetKet(unitType, level][statType];
}
}
// The data is not stored against the unit but referenced from UnitData
public class Unit
{
public UnitType UnitType { get; private set; }
public int Level { get; private set; }
public Unit(UnitType unitType, int level)
{
UnitType = unitTypel
Level = level;
}
public int GetStat(StatType statType)
{
return UnitData.GetStat(UnitType, Level, statType);
}
}
// To initialise the data
public class StartClass
{
public void InitialiseData()
{
UnitData.Add(UnitType.Soldier, 1, 5, 0, 1, 1);
UnitData.Add(UnitType.Soldier, 2, 6, 0, 2, 2);
UnitData.Add(UnitType.Soldier, 3, 8, 0, 3, 3);
UnitData.Add(UnitType.Mage, 1, 3, 10, 1, 1);
UnitData.Add(UnitType.Mage, 2, 4, 15, 2, 2);
UnitData.Add(UnitType.Mage, 3, 5, 20, 3, 3);
}
}
// Use of units
public class Level1
{
public List<Unit> Units = new List<Unit>();
public void InitialiseUnits()
{
Units.Add(new Unit(UnitType.Soldier, 1));
Units.Add(new Unit(UnitType.Soldier, 1));
Units.Add(new Unit(UnitType.Mage, 1));
Units.Add(new Unit(UnitType.Mage, 1));
}
public void Something()
{
int maxHP = Units.First().GetStat(StatType.MaxHP);
// etc
}
}