Example #1
0
        public List <List <BinaryOption> > getSample(Dictionary <string, string> parameter)
        {
            int numberConfigs;

            int optionWeight = 1;

            string numConfigsValue = null;

            if (parameter.ContainsKey(NUMBER_SAMPLES))
            {
                numConfigsValue = parameter[NUMBER_SAMPLES];
            }
            else
            {
                numConfigsValue = "asOW";
            }

            if (numConfigsValue.Contains("asOW"))
            {
                FeatureWise fw = new FeatureWise();
                numberConfigs = fw.generateFeatureWiseConfigsCSP(vm).Count;
            }
            else if (numConfigsValue.Contains("asTW"))
            {
                numConfigsValue.Replace("asTW", "");
                numberConfigs = Int32.Parse(numConfigsValue);
                TWise tw = new TWise();
                numberConfigs = tw.generateT_WiseVariants_new(vm, numberConfigs).Count;
            }
            else
            {
                numberConfigs = Int32.Parse(numConfigsValue);
            }

            if (parameter.ContainsKey(OPTION_WEIGHT))
            {
                optionWeight = Int32.Parse(parameter[OPTION_WEIGHT]);
            }

            List <BinaryOption> minimalConfiguration = ConfigurationBuilder.vg.MinimizeConfig(new List <BinaryOption>(), vm, true, null);

            return(ConfigurationBuilder.vg.DistanceMaximization(vm, minimalConfiguration, numberConfigs, optionWeight));
        }
Example #2
0
        /// <summary>
        /// Creates the t-wise sampling according to the given t-value.
        /// </summary>
        /// <param name="vm">The variability model containing the binary options for which we want to generate the pair-wise configurations.</param>
        /// <param name="t"> The t of the t-wise</param>
        /// <returns>A list of configurations in which each configuration is represented by a list of SELECTED binary options</returns>
        public List <List <BinaryOption> > generateT_WiseVariants_new(VariabilityModel vm, int t)
        {
            // dirty fix for twise issue
            if (t == 1)
            {
                FeatureWise fw = new FeatureWise();
                return(fw.generateFeatureWiseConfigurations(vm));
            }

            List <BinaryOption>         candidate = new List <BinaryOption>();
            List <List <BinaryOption> > result    = new List <List <BinaryOption> >();

            generatePowerSet(vm, candidate, t, result, 0);

            //remove double entries...
            List <List <BinaryOption> > resultCleaned = new List <List <BinaryOption> >();
            List <String> configs = new List <string>();

            foreach (List <BinaryOption> options in result)
            {
                options.Sort(delegate(BinaryOption o1, BinaryOption o2) { return(o1.Name.CompareTo(o2.Name)); });

                String currConfig = "";

                foreach (BinaryOption binOpt in options)
                {
                    currConfig = currConfig + " " + binOpt.Name;
                }

                if (!configs.Contains(currConfig))
                {
                    resultCleaned.Add(options);
                    configs.Add(currConfig);
                }
            }


            return(resultCleaned);
        }
Example #3
0
        /// <summary>
        /// Returns a set of random binary partial configurations.
        /// </summary>
        /// <param name="parameters">Parameters for this random sampling. The following paramters are supported:
        /// seed = the seed for the random generator (int required)
        /// numConfigs = the number of configurations that have to be selected.
        ///              To be able ot select a number of configurations equal to the number selected by the OW heuristic or
        ///              the TWise heuristics, two special values can be given for this paramter. To select a number equal to
        ///              the OW heuristics use "asOW" as value and to select a number equal to a TWise heuristics with a t of X
        ///              use "asTWX".
        /// </param>
        /// <returns>A list of random binary partial configuartions.</returns>
        public List <List <BinaryOption> > getRandomConfigs(Dictionary <String, String> parameters)
        {
            configurations.Clear();

            int seed       = 0;
            int numConfigs = varModel.BinaryOptions.Count;

            // parse parameters
            if (parameters.ContainsKey("numConfigs"))
            {
                String numConfigsValue = parameters["numConfigs"];
                if (!int.TryParse(numConfigsValue, out numConfigs))
                {
                    // special constants as parameter (numConfigs = asOW or asTWX
                    if (numConfigsValue.Contains("asOW"))
                    {
                        FeatureWise fw = new FeatureWise();
                        numConfigs = fw.generateFeatureWiseConfigsCSP(varModel).Count;
                    }
                    else if (numConfigsValue.Contains("asTW"))
                    {
                        numConfigsValue = numConfigsValue.Replace("asTW", "").Trim();
                        int.TryParse(numConfigsValue, out numConfigs);
                        TWise tw = new TWise();
                        numConfigs = tw.generateT_WiseVariants_new(varModel, numConfigs).Count;
                    }
                }
            }
            if (parameters.ContainsKey("seed"))
            {
                int.TryParse(parameters["seed"], out seed);
            }

            // build set of all valid binary partial configurations
            VariantGenerator            vg         = new VariantGenerator();
            List <List <BinaryOption> > allConfigs = vg.generateAllVariantsFast(varModel);

            //repair wrong parameters
            if (numConfigs >= allConfigs.Count)
            {
                if (numConfigs > allConfigs.Count)
                {
                    GlobalState.logError.logLine("Random Sampling: numConfigs to large for variability model. num set to " + allConfigs.Count);
                }
                configurations = allConfigs;
                return(allConfigs);
            }

            // select random configurations
            Random r = new Random(seed);

            for (int i = 0; i < numConfigs; i++)
            {
                List <BinaryOption> selectedConfig = allConfigs[r.Next(allConfigs.Count + 1)];

                if (configurations.Contains(selectedConfig))
                {
                    i -= 1;
                }
                else
                {
                    configurations.Add(selectedConfig);
                }
            }
            return(configurations);
        }
        /// <summary>
        /// Performs the functionality of one command. If no functionality is found for the command, the command is retuned by this method. 
        /// </summary>
        /// <param name="line">One command with its parameters.</param>
        /// <returns>Returns an empty string if the command could be performed by the method. If the command could not be performed by the method, the original command is returned.</returns>
        public string performOneCommand(string line)
        {
            GlobalState.logInfo.log(COMMAND + line);

            // remove comment part of the line (the comment starts with an #)
            line = line.Split(new Char[] { '#' }, 2)[0];
            if (line.Length == 0)
                return "";

            // split line in command and parameters of the command
            string[] components = line.Split(new Char[] { ' ' }, 2);
            string command = components[0];
            string task = "";
            if (components.Length > 1)
                task = components[1];

            string[] taskAsParameter = task.Split(new Char[] { ' ' });

            switch (command.ToLower())
            {
                case COMMAND_TRUEMODEL:
                    StreamReader readModel = new StreamReader(task);
                    String model = readModel.ReadLine().Trim();
                    readModel.Close();
                    exp.TrueModel = new InfluenceFunction(model.Replace(',','.'), GlobalState.varModel);
                    NFProperty artificalProp = new NFProperty("artificial");
                    GlobalState.currentNFP = artificalProp;
                    computeEvaluationDataSetBasedOnTrueModel();
                    break;

                case COMMAND_SUBSCRIPT:
                    {

                        FileInfo fi = new FileInfo(task);
                        StreamReader reader = null;
                        if (!fi.Exists)
                            throw new FileNotFoundException(@"Automation script not found. ", fi.ToString());

                        reader = fi.OpenText();
                        Commands co = new Commands();
                        co.exp = this.exp;

                        while (!reader.EndOfStream)
                        {
                            String oneLine = reader.ReadLine().Trim();
                            co.performOneCommand(oneLine);

                        }
                    }
                    break;
                case COMMAND_EVALUATION_SET:
                    {
                        GlobalState.evalutionSet.Configurations = ConfigurationReader.readConfigurations(task, GlobalState.varModel);
                        GlobalState.logInfo.log("Evaluation set loaded.");
                    }
                    break;
                case COMMAND_CLEAR_GLOBAL:
                    SPLConqueror_Core.GlobalState.clear();
                    break;
                case COMMAND_CLEAR_SAMPLING:
                    exp.clearSampling();
                    break;
                case COMMAND_CLEAR_LEARNING:
                    exp.clear();
                    break;
                case COMMAND_LOAD_CONFIGURATIONS:
                    GlobalState.allMeasurements.Configurations = (GlobalState.allMeasurements.Configurations.Union(ConfigurationReader.readConfigurations(task, GlobalState.varModel))).ToList();
                    GlobalState.logInfo.log(GlobalState.allMeasurements.Configurations.Count + " configurations loaded.");

                    break;
                case COMMAND_SAMPLE_ALLBINARY:
                    {
                        VariantGenerator vg = new VariantGenerator(null);
                        if (taskAsParameter.Contains(COMMAND_VALIDATION))
                        {
                            exp.addBinarySelection_Validation(vg.generateAllVariantsFast(GlobalState.varModel));
                            exp.addBinarySampling_Validation(COMMAND_SAMPLE_ALLBINARY);
                        }
                        else
                        {
                            exp.addBinarySelection_Learning(vg.generateAllVariantsFast(GlobalState.varModel));
                            exp.addBinarySampling_Learning(COMMAND_SAMPLE_ALLBINARY);
                        }

                        break;
                    }
                case COMMAND_ANALYZE_LEARNING:
                    {
                        GlobalState.logInfo.log("Models:");
                        FeatureSubsetSelection learning = exp.learning;
                        if (learning == null)
                        {
                            GlobalState.logError.log("Error... learning was not performed!");
                            break;
                        }
                        foreach (LearningRound lr in learning.LearningHistory)
                        {
                            double relativeError = 0;
                            if (GlobalState.evalutionSet.Configurations.Count > 0)
                            {
                                double relativeErro2r = exp.learning.computeError(lr.FeatureSet, GlobalState.evalutionSet.Configurations, out relativeError);
                            }
                            else
                            {
                                double relativeErro2r = exp.learning.computeError(lr.FeatureSet, GlobalState.allMeasurements.Configurations, out relativeError);
                            }

                            GlobalState.logInfo.log(lr.ToString() + relativeError);
                        }

                        break;
                    }
                case COMMAND_EXERIMENTALDESIGN:
                    performOneCommand_ExpDesign(task);
                    break;

                case COMMAND_SAMPLING_OPTIONORDER:
                    parseOptionOrder(task);
                    break;

                case COMMAND_VARIABILITYMODEL:
                    GlobalState.varModel = VariabilityModel.loadFromXML(task);
                    if (GlobalState.varModel == null)
                        GlobalState.logError.log("No variability model found at " + task);
                    break;
                case COMMAND_SET_NFP:
                    GlobalState.currentNFP = GlobalState.getOrCreateProperty(task.Trim());
                    break;
                case COMMAND_SAMPLE_OPTIONWISE:
                    FeatureWise fw = new FeatureWise();
                    if (taskAsParameter.Contains(COMMAND_VALIDATION))
                    {
                        exp.addBinarySelection_Validation(fw.generateFeatureWiseConfigsCSP(GlobalState.varModel));
                        exp.addBinarySampling_Validation("FW");
                    }
                    else
                    {
                        //exp.addBinarySelection_Learning(fw.generateFeatureWiseConfigsCSP(GlobalState.varModel));
                        exp.addBinarySelection_Learning(fw.generateFeatureWiseConfigurations(GlobalState.varModel));
                        exp.addBinarySampling_Learning("FW");
                    }
                    break;

                case COMMAND_LOG:

                    string location = task.Trim();
                    GlobalState.logInfo.close();
                    GlobalState.logInfo = new InfoLogger(location);

                    GlobalState.logError.close();
                    GlobalState.logError = new ErrorLogger(location + "_error");
                    break;
                case COMMAND_SET_MLSETTING:
                    exp.mlSettings = ML_Settings.readSettings(task);
                    break;
                case COMMAND_LOAD_MLSETTINGS:
                    exp.mlSettings = ML_Settings.readSettingsFromFile(task);
                    break;

                case COMMAND_SAMPLE_PAIRWISE:
                    PairWise pw = new PairWise();
                    if (taskAsParameter.Contains(COMMAND_VALIDATION))
                    {
                        exp.addBinarySelection_Validation(pw.generatePairWiseVariants(GlobalState.varModel));
                        exp.addBinarySampling_Validation("PW");
                    }
                    else
                    {
                        exp.addBinarySelection_Learning(pw.generatePairWiseVariants(GlobalState.varModel));
                        exp.addBinarySampling_Learning("PW");
                    }
                    break;

                case COMMAND_PRINT_MLSETTINGS:
                    GlobalState.logInfo.log(exp.mlSettings.ToString());
                    break;

                case COMMAND_PRINT_CONFIGURATIONS:
                    {
                        List<Dictionary<NumericOption, double>> numericSampling = exp.NumericSelection_Learning;
                        List<List<BinaryOption>> binarySampling = exp.BinarySelections_Learning;

                        List<Configuration> configurations = new List<Configuration>();

                        foreach (Dictionary<NumericOption, double> numeric in numericSampling)
                        {
                            foreach (List<BinaryOption> binary in binarySampling)
                            {
                                Configuration config = Configuration.getConfiguration(binary, numeric);
                                if (!configurations.Contains(config) && GlobalState.varModel.configurationIsValid(config))
                                {
                                    configurations.Add(config);
                                }
                            }
                        }
                        string[] para = task.Split(new char[] { ' ' });
                        // TODO very error prune..
                        ConfigurationPrinter printer = new ConfigurationPrinter(para[0], para[1], para[2], GlobalState.optionOrder);
                        printer.print(configurations);

                        break;
                    }
                case COMMAND_SAMPLE_BINARY_RANDOM:
                    {
                        string[] para = task.Split(new char[] { ' ' });
                        int treshold = Convert.ToInt32(para[0]);
                        int modulu = Convert.ToInt32(para[1]);

                        VariantGenerator vg = new VariantGenerator(null);
                        if (taskAsParameter.Contains(COMMAND_VALIDATION))
                        {
                            exp.addBinarySelection_Validation(vg.generateRandomVariants(GlobalState.varModel, treshold, modulu));
                            exp.addBinarySampling_Validation("random " + task);
                        }
                        else
                        {
                            exp.addBinarySelection_Learning(vg.generateRandomVariants(GlobalState.varModel, treshold, modulu));
                            exp.addBinarySampling_Learning("random " + task);
                        }
                        break;
                    }
                case COMMAND_START_LEARNING:
                    {
                        InfluenceModel infMod = new InfluenceModel(GlobalState.varModel, GlobalState.currentNFP);

                        List<Configuration> configurations_Learning = new List<Configuration>();

                        List<Configuration> configurations_Validation = new List<Configuration>();

                        if (exp.TrueModel == null)
                        {
                            //List<List<BinaryOption>> availableBinary
                            //configurations_Learning = GlobalState.getMeasuredConfigs(exp.BinarySelections_Learning, exp.NumericSelection_Learning);
                            configurations_Learning = GlobalState.getMeasuredConfigs(Configuration.getConfigurations(exp.BinarySelections_Learning, exp.NumericSelection_Learning));
                            configurations_Learning = configurations_Learning.Distinct().ToList();

                            configurations_Validation = GlobalState.getMeasuredConfigs(Configuration.getConfigurations(exp.BinarySelections_Validation, exp.NumericSelection_Validation));
                            configurations_Validation = configurations_Validation.Distinct().ToList();
                            //break;//todo only to get the configurations that we haven't measured
                        } else
                        {
                            foreach (List<BinaryOption> binConfig in exp.BinarySelections_Learning)
                            {
                                if (exp.NumericSelection_Learning.Count == 0)
                                {
                                    Configuration c = new Configuration(binConfig);
                                    c.setMeasuredValue(GlobalState.currentNFP, exp.TrueModel.eval(c));
                                    if (!configurations_Learning.Contains(c))
                                        configurations_Learning.Add(c);
                                    continue;
                                }
                                foreach (Dictionary<NumericOption, double> numConf in exp.NumericSelection_Learning)
                                {

                                    Configuration c = new Configuration(binConfig, numConf);
                                    c.setMeasuredValue(GlobalState.currentNFP, exp.TrueModel.eval(c));
                                    if(GlobalState.varModel.configurationIsValid(c))
                //                    if (!configurations_Learning.Contains(c))
                                        configurations_Learning.Add(c);
                                }
                            }

                        }
                            if (configurations_Learning.Count == 0)
                            {
                                configurations_Learning = configurations_Validation;
                            }

                            if (configurations_Learning.Count == 0)
                            {
                                GlobalState.logInfo.log("The learning set is empty! Cannot start learning!");
                                break;
                            }

                            if (configurations_Validation.Count == 0)
                            {
                                configurations_Validation = configurations_Learning;
                            }
                            //break;
                            GlobalState.logInfo.log("Learning: " + "NumberOfConfigurationsLearning:" + configurations_Learning.Count + " NumberOfConfigurationsValidation:" + configurations_Validation.Count
                            + " UnionNumberOfConfigurations:" + (configurations_Learning.Union(configurations_Validation)).Count());

                        // prepare the machine learning
                        exp.learning.init(infMod, exp.mlSettings);
                        exp.learning.setLearningSet(configurations_Learning);
                        exp.learning.setValidationSet(configurations_Validation);
                        exp.learning.learn();

                    }
                    break;

                case COMMAND_SAMPLE_NEGATIVE_OPTIONWISE:
                    // TODO there are two different variants in generating NegFW configurations.
                    NegFeatureWise neg = new NegFeatureWise();

                    if (taskAsParameter.Contains(COMMAND_VALIDATION))
                    {
                        exp.addBinarySelection_Validation(neg.generateNegativeFW(GlobalState.varModel));
                        exp.addBinarySampling_Validation("newFW");
                    }
                    else
                    {
                        exp.addBinarySelection_Learning(neg.generateNegativeFW(GlobalState.varModel));//neg.generateNegativeFWAllCombinations(GlobalState.varModel));
                        exp.addBinarySampling_Learning("newFW");
                    }
                    break;
                default:
                    return command;
            }
            return "";
        }
        public static List<Configuration> buildConfigs(VariabilityModel vm, List<SamplingStrategies> strategies)
        {
            List<Configuration> result = new List<Configuration>();
            VariantGenerator vg = new VariantGenerator(null);
            ExperimentalDesign design = null;

            List<List<BinaryOption>> binaryConfigs = new List<List<BinaryOption>>();
            List<Dictionary<NumericOption, Double>> numericConfigs = new List<Dictionary<NumericOption, double>>();
            foreach (SamplingStrategies strat in strategies)
            {
                switch (strat)
                {
                    //Binary sampling heuristics
                    case SamplingStrategies.ALLBINARY:
                        binaryConfigs.AddRange(vg.generateAllVariantsFast(vm));
                        break;
                    case SamplingStrategies.BINARY_RANDOM:
                        binaryConfigs.AddRange(vg.generateRandomVariants(GlobalState.varModel, binaryThreshold, binaryModulu));
                        break;
                    case SamplingStrategies.OPTIONWISE:
                        FeatureWise fw = new FeatureWise();
                        binaryConfigs.AddRange(fw.generateFeatureWiseConfigurations(GlobalState.varModel));
                        break;
                    case SamplingStrategies.PAIRWISE:
                        PairWise pw = new PairWise();
                        binaryConfigs.AddRange(pw.generatePairWiseVariants(GlobalState.varModel));
                        break;
                    case SamplingStrategies.NEGATIVE_OPTIONWISE:
                        NegFeatureWise neg = new NegFeatureWise();//2nd option: neg.generateNegativeFWAllCombinations(GlobalState.varModel));
                        binaryConfigs.AddRange(neg.generateNegativeFW(GlobalState.varModel));
                        break;

                    //Experimental designs for numeric options
                    case SamplingStrategies.BOXBEHNKEN:
                        if (optionsToConsider.ContainsKey(SamplingStrategies.BOXBEHNKEN))
                            design = new BoxBehnkenDesign(optionsToConsider[SamplingStrategies.BOXBEHNKEN]);
                        else
                            design = new BoxBehnkenDesign(vm.NumericOptions);

                        foreach (Dictionary<string, string> expDesignParamSet in parametersOfExpDesigns[SamplingStrategies.BOXBEHNKEN])
                        {
                            design.computeDesign(expDesignParamSet);
                            numericConfigs.AddRange(design.SelectedConfigurations);
                        }
                        break;

                    case SamplingStrategies.CENTRALCOMPOSITE:
                        if (optionsToConsider.ContainsKey(SamplingStrategies.CENTRALCOMPOSITE))
                            design = new CentralCompositeInscribedDesign(optionsToConsider[SamplingStrategies.CENTRALCOMPOSITE]);
                        else
                            design = new CentralCompositeInscribedDesign(vm.NumericOptions);

                        foreach (Dictionary<string, string> expDesignParamSet in parametersOfExpDesigns[SamplingStrategies.CENTRALCOMPOSITE])
                        {
                            design.computeDesign(expDesignParamSet);
                            numericConfigs.AddRange(design.SelectedConfigurations);
                        }
                        break;

                    case SamplingStrategies.FULLFACTORIAL:
                        if (optionsToConsider.ContainsKey(SamplingStrategies.FULLFACTORIAL))
                            design = new FullFactorialDesign(optionsToConsider[SamplingStrategies.FULLFACTORIAL]);
                        else
                            design = new FullFactorialDesign(vm.NumericOptions);

                        foreach (Dictionary<string, string> expDesignParamSet in parametersOfExpDesigns[SamplingStrategies.FULLFACTORIAL])
                        {
                            design.computeDesign(expDesignParamSet);
                            numericConfigs.AddRange(design.SelectedConfigurations);
                        }

                        break;

                    case SamplingStrategies.HYPERSAMPLING:
                        if (optionsToConsider.ContainsKey(SamplingStrategies.HYPERSAMPLING))
                            design = new HyperSampling(optionsToConsider[SamplingStrategies.HYPERSAMPLING]);
                        else
                            design = new HyperSampling(vm.NumericOptions);
                        foreach (Dictionary<string, string> expDesignParamSet in parametersOfExpDesigns[SamplingStrategies.HYPERSAMPLING])
                        {
                            design.computeDesign(expDesignParamSet);
                            numericConfigs.AddRange(design.SelectedConfigurations);
                        }
                        break;

                    case SamplingStrategies.ONEFACTORATATIME:
                        if (optionsToConsider.ContainsKey(SamplingStrategies.ONEFACTORATATIME))
                            design = new OneFactorAtATime(optionsToConsider[SamplingStrategies.ONEFACTORATATIME]);
                        else
                            design = new OneFactorAtATime(vm.NumericOptions);

                        foreach (Dictionary<string, string> expDesignParamSet in parametersOfExpDesigns[SamplingStrategies.ONEFACTORATATIME])
                        {
                            design.computeDesign(expDesignParamSet);
                            numericConfigs.AddRange(design.SelectedConfigurations);
                        }
                        break;

                    case SamplingStrategies.KEXCHANGE:
                        if (optionsToConsider.ContainsKey(SamplingStrategies.KEXCHANGE))
                            design = new KExchangeAlgorithm(optionsToConsider[SamplingStrategies.KEXCHANGE]);
                        else
                            design = new KExchangeAlgorithm(vm.NumericOptions);

                        foreach (Dictionary<string, string> expDesignParamSet in parametersOfExpDesigns[SamplingStrategies.KEXCHANGE])
                        {
                            design.computeDesign(expDesignParamSet);
                            numericConfigs.AddRange(design.SelectedConfigurations);
                        }
                        break;

                    case SamplingStrategies.PLACKETTBURMAN:
                        if (optionsToConsider.ContainsKey(SamplingStrategies.PLACKETTBURMAN))
                            design = new PlackettBurmanDesign(optionsToConsider[SamplingStrategies.PLACKETTBURMAN]);
                        else
                            design = new PlackettBurmanDesign(vm.NumericOptions);

                        foreach (Dictionary<string, string> expDesignParamSet in parametersOfExpDesigns[SamplingStrategies.PLACKETTBURMAN])
                        {
                            design.computeDesign(expDesignParamSet);
                            numericConfigs.AddRange(design.SelectedConfigurations);
                        }
                        break;

                    case SamplingStrategies.RANDOM:
                        if (optionsToConsider.ContainsKey(SamplingStrategies.RANDOM))
                            design = new RandomSampling(optionsToConsider[SamplingStrategies.RANDOM]);
                        else
                            design = new RandomSampling(vm.NumericOptions);

                        foreach (Dictionary<string, string> expDesignParamSet in parametersOfExpDesigns[SamplingStrategies.RANDOM])
                        {
                            design.computeDesign(expDesignParamSet);
                            numericConfigs.AddRange(design.SelectedConfigurations);
                        }
                        break;
                }
            }

            foreach (List<BinaryOption> binConfig in binaryConfigs)
            {
                if (numericConfigs.Count == 0)
                {
                    Configuration c = new Configuration(binConfig);
                    result.Add(c);
                }
                foreach (Dictionary<NumericOption, double> numConf in numericConfigs)
                {
                    Configuration c = new Configuration(binConfig, numConf);
                    result.Add(c);
                }
            }

            return result.Distinct().ToList();
        }