SentenceGenerator.java
001 /*
002  * Java Genetic Algorithm Library (jenetics-7.1.1).
003  * Copyright (c) 2007-2022 Franz Wilhelmstötter
004  *
005  * Licensed under the Apache License, Version 2.0 (the "License");
006  * you may not use this file except in compliance with the License.
007  * You may obtain a copy of the License at
008  *
009  *      http://www.apache.org/licenses/LICENSE-2.0
010  *
011  * Unless required by applicable law or agreed to in writing, software
012  * distributed under the License is distributed on an "AS IS" BASIS,
013  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014  * See the License for the specific language governing permissions and
015  * limitations under the License.
016  *
017  * Author:
018  *    Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com)
019  */
020 package io.jenetics.ext.grammar;
021 
022 import static java.util.Objects.requireNonNull;
023 import static java.util.stream.Collectors.joining;
024 import static io.jenetics.ext.grammar.SentenceGenerator.Expansion.LEFT_MOST;
025 
026 import java.util.ArrayList;
027 import java.util.List;
028 import java.util.ListIterator;
029 
030 import io.jenetics.ext.grammar.Cfg.NonTerminal;
031 import io.jenetics.ext.grammar.Cfg.Symbol;
032 import io.jenetics.ext.grammar.Cfg.Terminal;
033 
034 /**
035  * Standard implementation of a sentence generator. The generator can generate
036  * sentences by expanding the grammar in a {@link Expansion#LEFT_MOST} or
037  {@link Expansion#LEFT_TO_RIGHT} order.
038  <p>
039  * The following code snippet shows how to create a random sentence from a
040  * given grammar:
041  <pre>{@code
042  * final Cfg<String> cfg = Bnf.parse("""
043  *     <expr> ::= ( <expr> <op> <expr> ) | <num> <var> |  <fun> <arg><arg> )
044  *     <fun>  ::= FUN1 | FUN2
045  *     <arg>  ::= <expr> <var> <num>
046  *     <op>   ::= + | - | * | /
047  *     <var>  ::= x | y
048  *     <num>  ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
049  *     """
050  * );
051  *
052  * final var random = RandomGenerator.of("L64X256MixRandom");
053  * final var generator = new SentenceGenerator<String>(
054  *     SymbolIndex.of(random),
055  *     1_000
056  * );
057  * final List<Terminal<String>> sentence = generator.generate(cfg);
058  * final String string = sentence.stream()
059  *     .map(Symbol::name)
060  *     .collect(Collectors.joining());
061  *
062  * System.out.println(string);
063  * }</pre>
064  <em>Some sample output:</em>
065  <pre>{@code
066  * > ((x-FUN1(5,5))+8)
067  * > (FUN2(y,5)-FUN2(0,x))
068  * > x
069  * > FUN2(x,x)
070  * > 5
071  * > FUN2(y,FUN2((FUN1(5,FUN1(y,2))*9),y))
072  * > ((FUN1(x,5)*9)*(x/(y*FUN2(x,y))))
073  * > (9-(y*(x+x)))
074  * > }</pre>
075  *
076  @see DerivationTreeGenerator
077  *
078  @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
079  @since 7.1
080  @version 7.1
081  */
082 public final class SentenceGenerator<T>
083     implements Generator<T, List<Terminal<T>>>
084 {
085 
086     /**
087      * Defines the expansion strategy used when generating the sentences.
088      */
089     public enum Expansion {
090 
091         /**
092          * The symbol replacement always starting from the leftmost nonterminal
093          * as described in
094          * <a href="https://www.brinckerhoff.org/tmp/grammatica_evolution_ieee_tec_2001.pdf">Grammatical Evolution</a>.
095          */
096         LEFT_MOST,
097 
098         /**
099          * The symbol replacement is performed from left to right and is repeated
100          * until all non-terminal symbol has been expanded.
101          */
102         LEFT_TO_RIGHT;
103     }
104 
105     private final SymbolIndex _index;
106     private final Expansion _expansion;
107     private final int _limit;
108 
109     /**
110      * Create a new sentence generator from the given parameters.
111      *
112      @param index the symbol index function used for generating the sentences
113      @param expansion the sentence generation strategy to use for generating
114      *        the sentences
115      @param limit the maximal allowed sentence length. If the generated
116      *        sentence exceeds this length, the generation is interrupted and
117      *        an empty sentence (empty list) is returned.
118      */
119     public SentenceGenerator(
120         final SymbolIndex index,
121         final Expansion expansion,
122         final int limit
123     ) {
124         _index = requireNonNull(index);
125         _expansion = requireNonNull(expansion);
126         _limit = limit;
127     }
128 
129     /**
130      * Create a new sentence generator from the given parameters.
131      *
132      @param index the symbol index function used for generating the sentences
133      @param limit the maximal allowed sentence length. If the generated
134      *        sentence exceeds this length, the generation is interrupted and
135      *        an empty sentence (empty list) is returned.
136      */
137     public SentenceGenerator(
138         final SymbolIndex index,
139         final int limit
140     ) {
141         this(index, LEFT_MOST, limit);
142     }
143 
144     /**
145      * Generates a new sentence from the given grammar, <em>cfg</em>.
146      *
147      @param cfg the generating grammar
148      @return a newly created terminal list (sentence), or an empty list if
149      *         the length of the sentence exceed the defined sentence limit
150      */
151     @Override
152     public List<Terminal<T>> generate(final Cfg<? extends T> cfg) {
153         final var sentence = new ArrayList<Symbol<T>>();
154         generate(Cfg.upcast(cfg), sentence);
155 
156         // The 'generate' step guarantees that the list only
157         // contains terminal symbols. So this cast is safe.
158         @SuppressWarnings("unchecked")
159         final var result = (List<Terminal<T>>)(Object)sentence;
160         return List.copyOf(result);
161     }
162 
163     void generate(final Cfg<T> cfg, final List<Symbol<T>> symbols) {
164         symbols.add(cfg.start());
165 
166         boolean proceed;
167         do {
168             proceed = false;
169 
170 
171             final ListIterator<Symbol<T>> sit = symbols.listIterator();
172             while (sit.hasNext() &&
173                 (_expansion == Expansion.LEFT_TO_RIGHT || !proceed))
174             {
175                 if (sit.next() instanceof NonTerminal<T> nt) {
176                     sit.remove();
177                     Generator.select(nt, cfg, _index).forEach(sit::add);
178                     proceed = true;
179                 }
180             }
181 
182             if (symbols.size() > _limit) {
183                 symbols.clear();
184                 proceed = false;
185             }
186         while (proceed);
187     }
188 
189     /**
190      * Converts a list of symbols to a string, by concatenating the names of
191      * the given symbols.
192      *
193      @param sentence the symbols list to covert
194      @return the converted sentences
195      */
196     public static String toString(final List<? extends Symbol<?>> sentence) {
197         return sentence.stream().map(Symbol::name).collect(joining());
198     }
199 
200 }