简体   繁体   English

如何在C#中处理大量变量

[英]How to deal with a great numbers of variables in C#

I'm making a game about genetics, and I stumble into a little "problem". 我正在制作一个关于遗传学的游戏,我偶然发现了一个“问题”。 (it's more of a question in fact) (事实上​​这更像是一个问题)

I have a DNA class with a lot of variables that stores the infos about each "creature" in the game. 我有一个DNA类,里面有很多变量,可以存储游戏中每个“生物”的信息。

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

Now let's imagine I want to average two DNAs. 现在让我们想象一下我想平均两个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;
    [...]
}

But it seems pretty long, and every time I'm going to do some calculation, I'll need to rewrite every variable one by one. 但它似乎很长,每次我要做一些计算,我都需要逐个重写每个变量。

So I made a function that stores all the variables (normalized between 0 and 1) into a list 所以我创建了一个函数,将所有变量(归一化在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;
        [...]
    }
}

Now, I can do: 现在,我可以这样做:

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);
}

It's easier to implement new functions and calculations, but it's pretty heavy (partly due to Lists creation), and not very pretty (I guess ?). 它更容易实现新的功能和计算,但它相当沉重(部分原因是列表创建),而且不是很漂亮(我猜?)。

I would not prefer using just a List<float> for the sake of readability. 为了便于阅读,我不想只使用List<float>

I wanted to know if there's a better way to handle that kind of situations? 我想知道是否有更好的方法来处理这种情况?

(Please pardon my poor english, as I'm not a native speaker) (请原谅我可怜的英语,因为我不是母语人士)

If you're always applying the same operation to all, you could use a parameter to specify the operation to be performed: 如果您始终对所有操作应用相同的操作,则可以使用参数指定要执行的操作:

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
}

This allows you to reuse this method for a wide range of different operations - as long as they always operate on the same amount of fields: 这允许您将此方法重用于各种不同的操作 - 只要它们始终在相同数量的字段上运行:

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

etc. 等等

If you do want to apply the operation more selectively, you could also expand the Cross method to take an enum specifying which fields exactly you want to update. 如果你想更选择性地应用操作,你也可以扩大Cross方法采取哪些字段正好要更新一个枚举指定。 And of course, you might want to add another Func (or Action , depending on how comfortable you are with mutating arguments) to update the rest in any way you want. 当然,您可能希望添加另一个Func (或Action ,取决于您对变量参数的适应程度),以您想要的任何方式更新其余部分。

I think a better approach is to define your own arithmetic operators on the DNA class. 我认为更好的方法是在DNA类上定义自己的算术运算符。 See this link on how to do it. 请参阅此链接以了解如何操作。

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;
   }
}

Then you can do: 然后你可以这样做:

var newDna = dna1 + dna2;

I guess internally you can use @Luaan suggestion to avoid retyping the same thing over and over, but the usage is neater with the operators IMHO. 我想在内部你可以使用@Luaan建议来避免反复重复输入相同的东西,但是使用与操作员恕我直言。

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

Use Dictionary instead of the fields. 使用Dictionary而不是字段。 So that when you need to get the average you can use: 因此,当您需要获得平均值时,您可以使用:

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

That way you don't care about new fields. 这样你就不关心新的领域。

You could define an interface/class model around a Trait which knows how to "combine" with a similar trait. 您可以在Trait周围定义一个接口/类模型,它知道如何“组合”一个类似的特征。 For example, a trair represented as a float, where the combination is an average could be expressed as: 例如,表示为浮点数的trair,其中组合是平均值,可表示为:

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);
    }
}

You can then have properties in your DNA object which represent traits, not absolute values 然后,您可以在DNA对象中具有表示特征的属性,而不是绝对值

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

The addition of a dictionary to reference these makes it much simpler to combine them (Worth noting: Ive used string literals as the key, it would be more appropriate to use an enumeration! h/t @Luaan) 添加一个字典来引用它们使得组合它们变得更加简单(值得注意的是:我使用字符串文字作为键,使用枚举更合适!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);
    }
}   

You could extend this to support "traits" other than those represented by a float . 您可以扩展它以支持float表示的“特征”。 You could also alter the mechanism for "combining" traits if averaging is not appropriate. 如果平均值不合适,您还可以更改“组合”特征的机制。

Live example of this working: http://rextester.com/MVH5197 这个工作的实例: http//rextester.com/MVH5197

I'm assuming here you're not always doing the same operation, and the operation depends on the type of object. 我在这里假设你并不总是做同样的操作,操作取决于对象的类型。

First thing to do is to add an extra abstraction layer to float 's that defines the base functionality. 首先要做的是为float添加一个额外的抽象层来定义基本功能。 I assume you want to add semantics, eg: 我假设你想添加语义,例如:

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

If you end up with most methods being 'empty', make it an abstract class and define some base implementation that does nothing. 如果您最终将大多数方法都设置为“空”,请将其设为抽象类并定义一些不执行任何操作的基本实现。

Next, add a class that wraps a float, int, string or whatever you like. 接下来,添加一个包装float,int,string或任何你喜欢的类。 You might want to make semantic classes like weight, height, etc like I did. 你可能想像我一样制作像体重,身高等语义类。 In this case I use a struct, but if you cram a lot of stuff in there you might want to use a class. 在这种情况下,我使用一个结构,但如果你在那里填充了很多东西,你可能想要使用一个类。 The main thing here is that they all share the same interface that we can use to do stuff: 这里最重要的是它们都共享我们可以用来做东西的相同界面:

public struct Height : IDNAComputable
{
    // implement members
}

Updated It seems that you want to cut things and so on as well. 更新似乎你想要切割东西等等。 Might want to add an extra enum that describes the properties. 可能想添加一个描述属性的额外枚举。 Since they map to integers, you can do a lot of fun stuff with them, including random indexes, etc. 因为它们映射到整数,所以你可以用它们做很多有趣的事情,包括随机索引等。

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

Last thing to do is build the DNA thing itself: 最后要做的是构建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.
    }
}

Note that the overhead is not that bad, it basically uses a few extra indications and virtual function calls; 请注意,开销并不是那么糟糕,它基本上使用了一些额外的指示和虚函数调用; it should be fast enough for your use case. 它应该足够快你的用例。

Last thing to do is to define the actual functionality. 最后要做的是定义实际功能。

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

update Some practical examples based on your comments: 根据您的意见更新一些实际示例:

Right now I'm using the ToList() method to compare two DNAs (to know how much they differ), to apply a mutation to a random variable, and to cross two DNAs (I'm not averaging, it was just an example) 现在我正在使用ToList()方法来比较两个DNA(知道它们有多大差异),将变异应用于随机变量,并跨越两个DNA(我不是平均值,它只是一个例子)

Calculate the difference is easy with this approach, even if your 'height' doesn't attribute the same as the 'width'. 使用这种方法计算差异很容易,即使您的“高度”与“宽度”的属性不同。 You can even introduce a 'body mass' property if you like etc: 如果你喜欢,你甚至可以引入'体重'属性:

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

... or if you only want to use a certain set of properties for that, you can do that as well: ...或者,如果您只想使用某组属性,那么您也可以这样做:

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

List is pretty useful, though, as I just have to pick crossover points, iterate the lists, and "cut" List非常有用,因为我只需选择交叉点,迭代列表,然后“剪切”

Don't need a list for that: 不需要列表:

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

        if (diff > SomeCutThreshold) { return lhs; }
        else { return rhs; }
    });

... or perhaps you do want to know where the cut point will be? ...或者你可能想知道切点在哪里?

cut points meant: for(int i=0; i<dna1.count;i++) { newList[i] = i>cutPoint? dna1[i] : dna2[i] } 切点意味着: for(int i=0; i<dna1.count;i++) { newList[i] = i>cutPoint? dna1[i] : dna2[i] } 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);

To conclude 总结一下

While this might seem like more work, if you make the right abstractions and inheritance trees you should end up with less code, not more. 虽然这可能看起来更有效,但如果你做出正确的抽象和继承树,你最终会得到更少的代码,而不是更多。 For example, most basic functionality between 'Height' and 'Width' is probably shared. 例如,“高度”和“宽度”之间的大多数基本功能可能是共享的。 I'd also try to end up with 1 or 2 overloads of the 'Func' thing, no more. 我也试着结束'Func'事情的1或2次重载,不再这样。

Can you use reflection to get the properties, then loop through them on your list of objects? 你可以使用反射来获取属性,然后在对象列表中循环它们吗?

In case you're not familiar with reflection, here's an MSDN example that should help: https://msdn.microsoft.com/en-us/library/kyaxdd3x(v=vs.110).aspx 如果您不熟悉反射,这里有一个MSDN示例应该有所帮助: https//msdn.microsoft.com/en-us/library/kyaxdd3x(v = vs.110).aspx

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM