VNourdin
VNourdin

Reputation: 99

Call a random method, not twice

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

Answers (2)

SergGr
SergGr

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 SimpleCounterfeiters 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

Oleg Pyzhcov
Oleg Pyzhcov

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

Related Questions