Generating Random Doable Math Problems Swift

Generating random doable math problems swift

I started programming with Turbo Pascal and as Niklaus Wirth said: Algorithms + Data Structure = Programs. You need to define data structures appropriate for your program.

First, some basic data structures. (Swift enum is much more powerful than that in other languages)

enum MathElement : CustomStringConvertible {
case Integer(value: Int)
case Percentage(value: Int)
case Expression(expression: MathExpression)

var description: String {
switch self {
case .Integer(let value): return "\(value)"
case .Percentage(let percentage): return "\(percentage)%"
case .Expression(let expr): return expr.description
}
}

var nsExpressionFormatString : String {
switch self {
case .Integer(let value): return "\(value).0"
case .Percentage(let percentage): return "\(Double(percentage) / 100)"
case .Expression(let expr): return "(\(expr.description))"
}
}
}

enum MathOperator : String {
case plus = "+"
case minus = "-"
case multiply = "*"
case divide = "/"

static func random() -> MathOperator {
let allMathOperators: [MathOperator] = [.plus, .minus, .multiply, .divide]
let index = Int(arc4random_uniform(UInt32(allMathOperators.count)))

return allMathOperators[index]
}
}

Now the main class:

class MathExpression : CustomStringConvertible {
var lhs: MathElement
var rhs: MathElement
var `operator`: MathOperator

init(lhs: MathElement, rhs: MathElement, operator: MathOperator) {
self.lhs = lhs
self.rhs = rhs
self.operator = `operator`
}

var description: String {
var leftString = ""
var rightString = ""

if case .Expression(_) = lhs {
leftString = "(\(lhs))"
} else {
leftString = lhs.description
}
if case .Expression(_) = rhs {
rightString = "(\(rhs))"
} else {
rightString = rhs.description
}

return "\(leftString) \(self.operator.rawValue) \(rightString)"
}

var result : Any? {
let format = "\(lhs.nsExpressionFormatString) \(`operator`.rawValue) \(rhs.nsExpressionFormatString)"
let expr = NSExpression(format: format)
return expr.expressionValue(with: nil, context: nil)
}

static func random() -> MathExpression {
let lhs = MathElement.Integer(value: Int(arc4random_uniform(10)))
let rhs = MathElement.Integer(value: Int(arc4random_uniform(10)))

return MathExpression(lhs: lhs, rhs: rhs, operator: .random())
}
}

Usage:

let a = MathExpression(lhs: .Integer(value: 1), rhs: .Integer(value: 2), operator: .divide)
let b = MathExpression(lhs: .Integer(value: 10), rhs: .Percentage(value: 20), operator: .minus)
let c = MathExpression.random()

let d = MathExpression(lhs: .Integer(value: 1), rhs: .Integer(value: 2), operator: .plus)
let e = MathExpression(lhs: .Integer(value: 3), rhs: .Integer(value: 4), operator: .plus)
let f = MathExpression(lhs: .Expression(expression: d), rhs: .Expression(expression: e), operator: .multiply)

let x = MathExpression.random()
let y = MathExpression.random()
let z = MathExpression(lhs: .Expression(expression: x), rhs: .Expression(expression: y), operator: .plus)

print("a: \(a) = \(a.result!)")
print("b: \(b) = \(b.result!)")
print("c: \(c) = \(c.result!)")

print("f: \(f) = \(f.result!)") // the classic (1 + 2) * (3 + 4)
print("z: \(z) = \(z.result!)") // this is completely random

Generating a simple algebraic expression in swift

Since all you want is a string representing an equation and a value for x, you don't need to do any solving. Just start with x and transform it until you have a nice equation. Here's a sample: (copy and paste it into a Playground to try it out)

import UIKit

enum Operation: String {
case addition = "+"
case subtraction = "-"
case multiplication = "*"
case division = "/"

static func all() -> [Operation] {
return [.addition, .subtraction, .multiplication, .division]
}

static func random() -> Operation {
let all = Operation.all()
let selection = Int(arc4random_uniform(UInt32(all.count)))
return all[selection]
}

}

func addNewTerm(formula: String, result: Int) -> (formula: String, result: Int) {
// choose a random number and operation
let operation = Operation.random()
let number = chooseRandomNumberFor(operation: operation, on: result)
// apply to the left side
let newFormula = applyTermTo(formula: formula, number: number, operation: operation)
// apply to the right side
let newResult = applyTermTo(result: result, number: number, operation: operation)
return (newFormula, newResult)
}

func applyTermTo(formula: String, number:Int, operation:Operation) -> String {
return "\(formula) \(operation.rawValue) \(number)"
}

func applyTermTo(result: Int, number:Int, operation:Operation) -> Int {
switch(operation) {
case .addition: return result + number
case .subtraction: return result - number
case .multiplication: return result * number
case .division: return result / number
}
}

func chooseRandomNumberFor(operation: Operation, on number: Int) -> Int {
switch(operation) {
case .addition, .subtraction, .multiplication:
return Int(arc4random_uniform(10) + 1)
case .division:
// add code here to find integer factors
return 1
}
}

func generateFormula(_ numTerms:Int = 1) -> (String, Int) {
let x = Int(arc4random_uniform(10))
var leftSide = "x"
var result = x

for i in 1...numTerms {
(leftSide, result) = addNewTerm(formula: leftSide, result: result)
if i < numTerms {
leftSide = "(" + leftSide + ")"
}
}

let formula = "\(leftSide) = \(result)"

return (formula, x)
}

func printFormula(_ numTerms:Int = 1) {
let (formula, x) = generateFormula(numTerms)
print(formula, " x = ", x)
}

for i in 1...30 {
printFormula(Int(arc4random_uniform(3)) + 1)
}

There are some things missing. The sqrt() function will have to be implemented separately. And for division to be useful, you'll have to add in a system to find factors (since you presumably want the results to be integers). Depending on what sort of output you want, there's a lot more work to do, but this should get you started.

Here's sample output:

(x + 10) - 5 = 11                       x =  6
((x + 6) + 6) - 1 = 20 x = 9
x - 2 = 5 x = 7
((x + 3) * 5) - 6 = 39 x = 6
(x / 1) + 6 = 11 x = 5
(x * 6) * 3 = 54 x = 3
x * 9 = 54 x = 6
((x / 1) - 6) + 8 = 11 x = 9

How to generate random numbers to satisfy a specific mean and median in python?

One way to get a result really close to what you want is to generate two separate random ranges with length 100 that satisfies your median constraints and includes all the desire range of numbers. Then by concatenating the arrays the mean will be around 12 but not quite equal to 12. But since it's just mean that you're dealing with you can simply generate your expected result by tweaking one of these arrays.

In [162]: arr1 = np.random.randint(2, 7, 100)    
In [163]: arr2 = np.random.randint(7, 40, 100)

In [164]: np.mean(np.concatenate((arr1, arr2)))
Out[164]: 12.22

In [166]: np.median(np.concatenate((arr1, arr2)))
Out[166]: 6.5

Following is a vectorized and very much optimized solution against any other solution that uses for loops or python-level code by constraining the random sequence creation:

import numpy as np
import math

def gen_random():
arr1 = np.random.randint(2, 7, 99)
arr2 = np.random.randint(7, 40, 99)
mid = [6, 7]
i = ((np.sum(arr1 + arr2) + 13) - (12 * 200)) / 40
decm, intg = math.modf(i)
args = np.argsort(arr2)
arr2[args[-41:-1]] -= int(intg)
arr2[args[-1]] -= int(np.round(decm * 40))
return np.concatenate((arr1, mid, arr2))

Demo:

arr = gen_random()
print(np.median(arr))
print(arr.mean())

6.5
12.0

The logic behind the function:

In order for us to have a random array with that criteria we can concatenate 3 arrays together arr1, mid and arr2. arr1 and arr2 each hold 99 items and the mid holds 2 items 6 and 7 so that make the final result to give as 6.5 as the median. Now we an create two random arrays each with length 99. All we need to do to make the result to have a 12 mean is to find the difference between the current sum and 12 * 200 and subtract the result from our N largest numbers which in this case we can choose them from arr2 and use N=50.

Edit:

If it's not a problem to have float numbers in your result you can actually shorten the function as following:

import numpy as np
import math

def gen_random():
arr1 = np.random.randint(2, 7, 99).astype(np.float)
arr2 = np.random.randint(7, 40, 99).astype(np.float)
mid = [6, 7]
i = ((np.sum(arr1 + arr2) + 13) - (12 * 200)) / 40
args = np.argsort(arr2)
arr2[args[-40:]] -= i
return np.concatenate((arr1, mid, arr2))

Find position far enough away

I dont' use unity, but I think this problem isn't unity/c# specific.

You can implement it considering that every planet you want to place "depends" from a parent, someway, sun excluded and placed at origin.

So said, sun it's located at (0,0,0), Planet1 belongs from sun and it's located at certain radius distance, so it has it's own radius and a rotation angle (always related to its parent).
Planet 2 may have sun as parent, so it's a "planet", or from Planet1, becoming its moon.
And so on...

With this simple scheme, you always have to manage 4 things which can be easly implemented as a class, i.e (not tested, I wrote it here without a compiler, check it from possible syntax errors):

public class Planet
{
public Planet Parent; //if null, I'm the first in chain
public float OrbitRadius; //From my parent
public float OrbitAngle; //0-360 around my parent
public float Radius; //Planet size

//EDIT:
public Vector3 Position;

public void CalculatePosition()
{
if(Parent == null) this.Position = new Vector(0, 0, 0);
this.Position = Parent.Position + OrbitRadius + Math.Sin(OrbitAngle * Math.PI / 180.0f); //Check if it has to be done for each vector component
}

}

More, you can easly implement sun, planets, moons, moons of moons :-)

Every time you create a planet, you will assign its parent, randomize class values taking care only of OrbitRadius >> Radius (>> I mean really "really greater", not just "greater")

This should also give you a strongest "planet chain" to deal with, maybe with recursive functions.

As final step, you can "render" their positions walking all the chain, going to add PlanetX position to parent's position(s) in an incremental manner, dealing only to OrbitAngle; you need some Sin/Cos math here but you will not loose with scale factors.

Hope this helps

edit: I added a sort of CalculatePosition, can't verify it now, It's only there to clarify the concept and can be surely improved.

If I called arc4random_uniform(6) at 5 o'clock OR 5:01, would I get the same number?

Your tester was probably thinking of the idea that software random number generators are in fact pseudo-random. Their output is not truly random as a physical process like a die roll would be: it's determined by some state that the generators hold or are given.

One simple implementation of a PRNG is a "linear congruential generator": the function rand() in the standard library uses this technique. At its core, it is a straightforward mathematical function, and each output is generated by feeding in the previous one as input. It thus takes a "seed" value, and -- this is what your tester was thinking of -- the sequence of output values that you get is completely determined by the seed value.

If you create a simple C program using rand(), you can (must, in fact) use the companion function srand() (that's "seed rand") to give the LCG a starting value. If you use a constant as the seed value: srand(4), you will get the same values from rand(), in the same order, every time.

One common way to get an arbitrary -- note, not random -- seed for rand() is to use the current time: srand(time(NULL)). If you did that, and re-seeded and generated a number fast enough that the return of time() did not change, you would indeed see the same output from rand().

This doesn't apply to arc4random(): it does not use an LCG, and it does not share this trait with rand(). It was considered* "cryptographically secure"; that is, its output is indistinguishable from true, physical randomness.

This is partly due to the fact that arc4random() re-seeds itself as you use it, and the seeding is itself based on unpredictable data gathered by the OS. The state that determines the output is entirely internal to the algorithm; as a normal user (i.e., not an attacker) you don't view, set, or otherwise interact with that state.

So no, the output of arc4random() is not reliably repeatable by you. Pseudo-random algorithms which are repeatable do exist, however, and you can certainly use them for testing.


*Wikipedia notes that weaknesses have been found in the last few years, and that it may no longer be usable for cryptography. Should be fine for your game, though, as long as there's no money at stake!



Related Topics



Leave a reply



Submit