Reputation: 99
I have several methods which falsify a word, and I need to call them randomly, until one achieve to create a "human mistake".
I want to call a random method then another, until it's ok, but never twice the same.
Putting them in an array (or a list) imply that I rewrite an array each time I try a method, it's an ugly computational complexity and I'd like to write "Scala style" code, with minimum of var and mutability.
EDIT:
The Oleg Pyzhcov solution works well, but now I have some functions with String params, and other without. How to store functions and their call params in a collection?
val rand: Random = new Random()
def m1(a: String): Boolean = rand.nextBoolean()
def m2(): Boolean = rand.nextBoolean()
def m3(a: String, b: String): Boolean = rand.nextBoolean()
def m4(): Boolean = rand.nextBoolean()
def tryUntilOk(): Unit = {
def out = rand.shuffle(Stream(m1 _, m2 _, m3 _, m4 _))
.map(method => method()) // calling without params so error
.find(result => result) // stop when a method return true
}
EDIT 2:
DETAILS
I have several methods which tries to falsify a word, without guarantee that they achieve it. Some methods take the mood and tense of a verb, to change the tense or the mood, other just take the word correct writing to remove some letters, other take the gender and number of a noun to change it's gender.
I want to call a random method among all possible, and if it fail to falsify the word (for example the given noun only exist in the feminine form) then call another randomly. Repeating this operation until no more methods are available, so we give up. The solution of Oleg is nice for the random part, but I can't find how to give to methods call parameters.
Concrete exemple:
package Counterfeiters
import Words.Noun
object Noun extends Counterfeiter[Noun] {
override def counterfeit(word: Noun): Unit = {
// For now I call methods without random order
// This one take only one String param
// And split letters that are duplicated like boot -> bot
word.currentWriting = splitDoubleLetters(word.correctWriting)
// Then we compare word.currentWriting and word.correctWriting
// If the word wasn't containing double letters, it isn't counterfeited
if (!word.isCounterfeited)
// This one take 5 String params
// And counterfeit the gender or the number of the word, randomly
word.currentWriting = counterfeitGenderNumberWord("N", word.correctWriting, word.lemma, word.gender, word.number)
}
}
To apply the solution of Oleg, I just need to find how to store methods in a collection, with corresponding params. In this case (splitDoubleLetters, (word.correctWriting))
and (counterfeitGenderNumberWord, ("N", word.correctWriting, word.lemma, word.gender, word.number))
.
SOLUTION
I did what Oleg advised in a comment:
object Noun extends Counterfeiter[Words.Noun] {
override def counterfeit(word: Words.Noun): Unit = {
if (word.isCounterfeited) return
def split: () => String = () => splitDoubleLetter(word.correctWriting)
def ctftGenderNumber: () => String = () => counterfeitGenderNumberWord("N", word.correctWriting, word.lemma, word.gender, word.number)
val methods: immutable.Seq[() => String] = Stream(split, ctftGenderNumber)
val res: Option[String] = randomizer.shuffle(methods) // Shuffle methods
.map(method => method()) // Call them one by one
.find(result => result != word.correctWriting) // Until one counterfeit the word
word.currentWriting = res match {
case None => word.correctWriting // If every methods failed
case _ => res.get
}
}
}
SergGr explained well a possible architecture, I'll fit to this as it's clear.
You can find my complete project code on GitHub if you want to understand better what I do.
Upvotes: 2
Views: 180
Reputation: 23788
I agree with Oleg that what you need is to convert all you methods into a collection of the same shape. I assume that in the word
package you have base class Word
with subclasses for different parts of the speech with different characteristics. Something like this:
abstract class Word(val correctWriting: String, var currentWriting: String, val lemma: String) {
def isCounterfeited: Boolean = !correctWriting.equals(currentWriting)
}
sealed trait Gender
case object Masculine extends Gender
case object Feminine extends Gender
case object Neutral extends Gender
sealed trait Number
case object Singular extends Number
case object Plural extends Number
class Noun(correctWriting: String, currentWriting: String, lemma: String, val gender: Gender, val number: Number) extends Word(correctWriting, currentWriting, lemma) {
}
and you have trait Counterfeiter
defined as
trait Counterfeiter[-W <: Word] {
def counterfeit(word: W): Unit
}
Then you may define helper class RandomCompoundCounterfeiter
type SimpleCounterfeiter[W <: Word] = (String, W) => String
class RandomCompoundCounterfeiter[W <: Word](val children: Seq[SimpleCounterfeiter[W]]) extends Counterfeiter[W] {
override def counterfeit(word: W): Unit = {
Random.shuffle(children).takeWhile(c => {
word.currentWriting = c(word.correctWriting, word)
!word.isCounterfeited
})
}
}
RandomCompoundCounterfeiter
is the class the does the main job you were asking for: it applies other SimpleCounterfeiter
in a random order. It does this by first shuffling the list of children
(i.e. real counterfeiters) and applying them until after some word.isCounterfeited
is finally true
or the list is exhausted.
Note that RandomCompoundCounterfeiter
re-shuffles counterfeiters on each call. If you want to have your order to be different between different runs of the application but the same for different words inside single run, just move the shuffling to the constructor.
Now define list of basic SimpleCounterfeiter
s functions such as
object Noun {
val singularCounterfeiter = (correctWriting: String, word: Noun) => {
if (word.number == Singular)
correctWriting
else
???
}
val pluralCounterfeiter = (correctWriting: String, word: Noun) => {
if (word.number == Plural)
correctWriting
else
???
}
def genderCounterfeiter(newGender: Gender): SimpleCounterfeiter[Noun] = (correctWriting: String, word: Noun) => {
if (word.gender == newGender)
correctWriting
else
???
}
val all = List(
GenericCounterfeiters.splitDoubleLetters,
singularCounterfeiter,
pluralCounterfeiter,
genderCounterfeiter(Neutral),
genderCounterfeiter(Masculine),
genderCounterfeiter(Feminine))
val nounCounterfeiter = new RandomCompoundCounterfeiter[Noun](all)
}
Now you can use Noun.nounCounterfeiter
as your random Counterfeiter[Noun]
. The main idea here is to have the same shape for each atomic counterfeiter and this is achieved by always passing the whole Word
(or its subclass) to the method. So now each method has access to all the relevant information if it needs some.
If you prefer to move the typical if
condition in counterfeiters to a single place, you may refactor your code to a bit more OOP-way:
class RandomCompoundCounterfeiter[W <: Word](val children: Seq[Counterfeiter[W]]) extends Counterfeiter[W] {
override def counterfeit(word: W): Unit = {
Random.shuffle(children).takeWhile(c => {
c.counterfeit(word)
!word.isCounterfeited
})
}
}
trait SimpleCounterfeiter[-W <: Word] extends Counterfeiter[W] {
override def counterfeit(word: W): Unit = {
if (isApplicable(word))
word.currentWriting = counterfeitImpl(word.correctWriting, word)
}
def isApplicable(word: W): Boolean
def counterfeitImpl(correctWriting: String, word: W): String
}
object GenericCounterfeiters {
val splitDoubleLetters = new SimpleCounterfeiter[Word] {
override def isApplicable(word: Word) = true
override def counterfeitImpl(correctWriting: String, word: Word) = ???
}
}
object Noun {
val singularCounterfeiter = new SimpleCounterfeiter[Noun] {
override def isApplicable(word: Noun) = word.number != Singular
override def counterfeitImpl(correctWriting: String, word: Noun) = ???
}
val pluralCounterfeiter = new SimpleCounterfeiter[Noun] {
override def isApplicable(word: Noun) = word.number != Plural
override def counterfeitImpl(correctWriting: String, word: Noun) = ???
}
def genderCounterfeiter(newGender: Gender) = new SimpleCounterfeiter[Noun] {
override def isApplicable(word: Noun) = word.gender != newGender
override def counterfeitImpl(correctWriting: String, word: Noun) = ???
}
val all = List(
GenericCounterfeiters.splitDoubleLetters,
singularCounterfeiter,
pluralCounterfeiter,
genderCounterfeiter(Neutral),
genderCounterfeiter(Masculine),
genderCounterfeiter(Feminine))
val nounCounterfeiter = new RandomCompoundCounterfeiter[Noun](all)
}
Upvotes: 1
Reputation: 7353
Using Stream
for laziness and Random#shuffle
method you can get:
import scala.util.Random
def m1(): Boolean = Random.nextBoolean()
def m2(): Boolean = Random.nextBoolean()
def m3(): Boolean = Random.nextBoolean()
def m4(): Boolean = Random.nextBoolean()
def out = Random.shuffle(Stream(m1 _, m2 _, m3 _, m4 _))
.map(method => method()) // pass in any necessary params
.find(result => !result) // do your check
Here, out
has type Option[Boolean]
since methods m1-m4 all return Boolean
Upvotes: 2