如何处理c#中的大量变量

本文关键字:变量 何处理 处理 | 更新日期: 2023-09-27 18:02:43

我正在制作一款关于遗传学的游戏,我遇到了一个小"问题"。(事实上这更像是一个问题)

我有一个包含许多变量的DNA类,这些变量存储着关于游戏中每个"生物"的信息。

public class DNA {
    float size;
    float speed;
    float drag;
    float eyeSize;
    int numberOfEyes;
    float color_r;
    [...]
}

现在让我们假设我想平均两个dna。

I could do:

DNA AverageDNAs (DNA dna1, DNA dna2) {
    DNA newDNA = new DNA ();
    newDNA.size = (dna1.size+dna2.size)/2f;
    newDNA.speed = (dna1.speed+dna2.speed)/2f;
    [...]
}

但是它看起来很长,而且每次我要做一些计算时,我都需要一个一个地重写每个变量。

所以我写了一个函数,将所有变量(在0到1之间归一化)存储到一个列表

public class DNA {
    public float size;
    public float speed;
    [...]
    private List<float> tempList;
    public List<float> ToList() {
        if (tempList == null) {
        tempList = new List<float>();
        toReturn.Add (size/sizemax);
        toReturn.Add (speed/speedmax);
        [...]
        }
        return tempList
    }
    public static ADN fromList(List<float> list) {
        ADN toReturn = new ADN();
        toReturn.size = list[0]*sizemax;
        [...]
    }
}

现在,我可以做:

DNA AverageDNAs (DNA dna1, DNA dna2) {
    List<float> dnaList1 = dna1.ToList();
    List<float> dnaList2 = dna2.ToList();
    List<float> newDNA = new List<float>();
    for (int i = 0; i<dnaList1.Count; i++) {
        newDNA.Add ((dnaList1[i]+dnaList2[i])/2f);
    }
    return DNA.fromList(newDNA);
}

实现新的函数和计算更容易,但它相当沉重(部分原因是由于list的创建),而且不是很漂亮(我猜?)

为了可读性,我不喜欢只使用List<float>

我想知道有没有更好的方法来处理这种情况?

(请原谅我的英语不好,因为我不是母语)

如何处理c#中的大量变量

如果您总是对所有人应用相同的操作,您可以使用参数来指定要执行的操作:

public DNA Cross(DNA dna1, DNA dna2, Func<float, float, float> operation)
{
  var newDNA = new DNA();
  newDNA.size = operation(dna1.size, dna2.size);
  newDNA.speed = operation(dna1.speed, dna1.speed);
  // And all the rest
}

这允许您将此方法用于各种不同的操作-只要它们总是对相同数量的字段进行操作:

// Average
(d1, d2) => (d1 + d2) / 2f
// Sum
(d1, d2) => d1 + d2
// Random
(d1, d2) => RandomBool() ? d1 : d2

等。

如果您确实希望更有选择性地应用该操作,您还可以扩展Cross方法,使其接受指定的enum,其中字段正是您想要更新的。当然,您可能希望添加另一个Func(或Action,取决于您对变化参数的适应程度)以任何您想要的方式更新其余部分。

我认为更好的方法是在DNA类上定义自己的算术运算符。请看这个链接,看看怎么做。

public class DNA {
    float size;
    float speed;
    float drag;
    float eyeSize;
    int numberOfEyes;
    float color_r;
    [...]
   public static DNA operator +(DNA dna1, dna1 rhs) 
   {
       DNA newDNA = new DNA ();
       newDNA.size = (dna1.size+dna2.size);
       newDNA.speed = (dna1.speed+dna2.speed);
       [...]
       return newDNA;
   }
}

那么你可以这样做:

var newDna = dna1 + dna2;

我猜你可以在内部使用@Luaan建议来避免重复输入相同的东西,但使用操作符更整洁。

 public static DNA operator +(DNA dna1, dna1 rhs) 
 {
      return Cross(dna1, dna2, (d1, d2) => d1 + d2);  
 }

使用Dictionary代替字段。所以当你需要得到平均值时,你可以使用:

foreach(var item in this._dict)
{
    avgDNA._dict[item.Key] = (item.Value + other._dict[item.Key]) / 2;
}

这样你就不用关心新字段了。

您可以围绕Trait定义一个接口/类模型,它知道如何与类似的特性"结合"。例如,用浮点数表示的流,其组合是平均值,可以表示为:

public class FloatTrait 
{
    private float val;
    public FloatTrait(float val)
    {
        this.val = val;
    }
    public float Value{get { return this.val; }}
    public FloatTrait CombineWith(FloatTrait t)
    {
       return new FloatTrait((this.Value + t.Value)/2.0f);
    }
}

你可以在你的DNA对象中拥有代表特征的属性,而不是绝对值

public class DNA
{
    public FloatTrait Size{get;set;}
    public FloatTrait Speed{get;set;}
}

添加一个字典来引用它们,使得组合它们变得更简单(值得注意的是:我使用字符串字面值作为键,使用枚举会更合适!h/t @Luaan)

public class DNA
{
    private Dictionary<string, FloatTrait> traits = new Dictionary<string, FloatTrait>() {
        {"Size", new FloatTrait(5.0)},
        {"Speed", new FloatTrait(50.0)},
    }
    public DNA(Dictionary<string, FloatTrait> dict)
    {
        this.traits = dict;
    }
    public FloatTrait this[string key]{ get{ return traits[key]; } }
    public float Size{ get{ return traits["Size"].Value; }
    public float Speed{ get{ return traits["Speed"].Value; }
    public DNA CombineWith(DNA other)
    {
        var newDict = this.traits.ToDictionary(k => k.Key
                                               v => v.Value.CombineWith(other[v.Key]));
        return new DNA(newDict);
    }
}   

你可以扩展它来支持除了那些由float表示的"特征"。如果平均不合适,你也可以改变"组合"特征的机制。

工作的实例:http://rextester.com/MVH5197

我假设这里您并不总是执行相同的操作,操作取决于对象的类型。

首先要做的是在float中添加一个额外的抽象层来定义基本功能。我假设您想添加语义,例如:

public interface IDNAComputable
{
    IDNAComputable Grow(float length);
    // etc.
}

如果你最终发现大多数方法都是空的,那就把它变成一个抽象类,并定义一些什么都不做的基本实现。

接下来,添加一个类来包装float、int、string或任何你喜欢的类型。你可能想要像我一样创建语义类,比如重量、高度等。在这种情况下,我使用一个结构体,但如果你在那里塞了很多东西,你可能想使用一个类。这里最主要的是它们都共享相同的接口,我们可以使用它来做一些事情:

public struct Height : IDNAComputable
{
    // implement members
}

已更新似乎你也想剪东西等等。可能需要添加一个额外的枚举来描述属性。因为它们映射到整数,你可以用它们做很多有趣的事情,包括随机索引等。

public enum DNAProperty : int
{
    BodyWidth,
    BodyHeight,
    MouthWidth,
    MouthHeight,
    // ...
}

最后一件事是建立DNA本身:

public class DNA
{
    private Width bodyWidth;
    private Height bodyHeight;
    private Width mouthWidth;
    private Height mouthHeight;
    // etc.
    public void Update(Func<IDNAComputable, IDNAComputable, DNAProperty> op)
    {
        bodyHeight = (Height)op(bodyHeight, DNAProperty.BodyHeight);
        bodyWidth = (Width)op(bodyWidth, DNAProperty.BodyWidth);
        // etc for all other stuff.
    }
    public void Merge(Func<IDNAComputable, IDNAComputable, IDNAComputable, DNAProperty> op, DNA otherCreature)
    {
        bodyHeight = (Height)op(bodyHeight, otherCreature.bodyHeight, DNAProperty.BodyHeight);
        bodyWidth = (Width)op(bodyWidth, otherCreature.bodyWidth, DNAProperty.BodyWidth);
        // etc for all other stuff.
    }
}

请注意,开销并不是那么糟糕,它基本上使用了一些额外的指示和虚函数调用;对于您的用例,它应该足够快。

最后要做的是定义实际的功能。

public void GrowCreature(DNA creature, float height) 
{
   creature.Update((a)=>a.Grow(height));
}

update基于您的评论的一些实际示例:

现在我正在使用ToList()方法来比较两个dna(知道它们有多大差异),将突变应用于随机变量,并交叉两个dna(我不是平均,这只是一个例子)

使用这种方法计算差异很容易,即使你的"高度"不与"宽度"属性相同。如果你愿意,你甚至可以引入一个'body mass'属性等:

double diff = 0;
creature.Merge((lhs, rhs, prop) => { diff += lhs.Difference(rhs); return lhs; });

…或者如果你只想使用一组特定的属性,你也可以这样做:

double diff = 0;
creature.Merge((lhs, rhs, prop) => 
    { 
        if (prop == DNAProperty.BodyMass)
        {
            diff += lhs.Difference(rhs); 
        } 
    return lhs; });

List非常有用,因为我只需要选择交叉点,迭代列表,然后"剪切"

不需要列表:

double diff = 0;
creature.Merge((lhs, rhs, prop) => 
    { 
        diff += lhs.Difference(rhs); 
        if (diff > SomeCutThreshold) { return lhs; }
        else { return rhs; }
    });

…或者你确实想知道切割点在哪里?

切割点含义:for(int i=0; i<dna1.count;i++) { newList[i] = i>cutPoint? dna1[i] : dna2[i] }

double diff = 0;
DNAProperty cutpoint = (DNAProperty)int.MaxValue;
creature.Merge((lhs, rhs, prop) => 
    { 
        diff += lhs.Difference(rhs); 
        if (diff > SomeCutThreshold) { cutpoint = prop; diff = double.MinValue; }
        return lhs;
    });
creature.Merge((lhs, rhs, prop) => ((int)prop > (int)cutpoint)?lhs:rhs);
结论

虽然这看起来像是要做更多的工作,但是如果您使用了正确的抽象和继承树,那么您最终应该使用更少的代码,而不是更多。例如,'Height'和'Width'之间的大多数基本功能可能是共享的。我也会尝试着以1或2个"Func"内容的过载结束,而不是更多。

您可以使用反射来获取属性,然后在对象列表中遍历它们吗?

如果你不熟悉反射,这里有一个MSDN的例子应该有所帮助:https://msdn.microsoft.com/en-us/library/kyaxdd3x (v = vs.110) . aspx