Python 3/ eli5 - Random numbers game vs computer - random

I have the task to make a game against the computer. I should "think of a number" and the computer must guess it. In case it has correctly guessed it, I should say C and break out of the loop. In case of a Lower number, I should say L and the computer should try to generate a lower number. H is for higher and is the opposite situation. So far I have managed to successfully implement everything with one exception. With the code below, the if I tell the computer L for example, it will not exceed the limit of the last number, however, if I then say H, it will randomly generate the numbers again.
Please bear in mind this is a task for a beginners course (functions are NOT yet covered). We have to use loops. For the functions getInteger and getLetter, do not pay attention, they are functions our professor has created and are similar to input() but just restrict the user to enter something different than a letter or an integer.
Here is the code:
from pcinput import getLetter, getInteger
from random import random, randint, seed
mynum = getInteger("My number is:")
comnum = randint(0, 1000)
print("Is your number:", comnum, "?")
while True:
answer = getLetter("That is: ")
if answer == "C":
print("Congratulations!")
break
if answer == "L":
comnum = randint(0,comnum)
print("Is your number:", comnum, "?")
possibility = range(comnum, )
continue
elif answer == "H":
comnum = randint(comnum, 1000)
print("Is your number:", comnum, "?")
continue
*comnum is the letter the computer should be entering
My question is basically how to fix this code so that the computer will create some sort of a range between the first and the last guess and do not exceed or go below it thus shortening the interval between the guesses each time. (I hope you get my point).
Thank you a lot!

My method would be (starting from your code):
from random import random, randint, seed
mynum = int(input("My number is: "))
maximum = 1000
minimum = 0
while True:
comnum = randint(minimum, maximum)
print("Is your number:", comnum, "?")
answer = input("That is ")
if answer == "C":
print("Congratulations!")
break
if answer == "L":
maximum = comnum - 1
continue
elif answer == "H":
minimum = comnum + 1

Related

Euler Project #520, making current code more efficient for finding simber integers?

Recently, for my Computer Algebra Systems class to work on. I have been given a problem randomly assigned from Project Euler to solve (Problem 520). The question goes as follows:
"We define a simber to be a positive integer in which any odd digit,
if present, occurs an odd number of times, and any even digit, if
present, occurs an even number of times.
For example, 141221242 is a 9-digit simber because it has three 1's,
four 2's and two 4's.
Let Q(n) be the count of all simbers with at most n digits.
You are given Q(7) = 287975 and Q(100) mod 1 000 000 123 = 123864868.
Find (∑1≤u≤39 Q(2^u)) mod 1 000 000 123."
The class requires me to use Wolfram-Mathematica to solve the problem using efficient coding. The code below is what my function of Q(n). I've included comments to help navigate through my coding.
Q[x_] := (
s = 0; (*Initalizes simber count*)
For[i = 1, StringLength[ToString[i]] <= x,
i++,(*Starts at 1 and continues until number is no longer 'x' digits long*)
num = ToString[i]; (*Sets 'num' to 'i' in string form*)
While[num != "", (*Loops until 'num' string is blank*)
If[EvenQ[
ToExpression[
Characters[num][[
1]]]], (*If the first digit of 'num' is Even*)
If[EvenQ[
StringCount[num,
Characters[num][[
1]]]], (*Checks if there are an Even quantity of the first digit in the string*)
num =
StringDelete[num,
Characters[num][[
1]]]; (*Removes all digits in the string that is the first digit e.g. 43442421 => 3221*)
If[num == "", (*If string is blank, increase simber count*)
s++;
]
,(*else*)
num =
"" (*If first digit is Even but not and Even quanity sets num to "" to break loop *)
]
,(*If the first digit of 'num' is Odd*)
If[OddQ[
StringCount[num,
Characters[num][[
1]]]], (*Checks if there are an Odd quantity of the first digit in the string*)
num = StringDelete[num, Characters[num][[1]]];
(*Removes all digits in the string that is the first digit e.g.
3292133 => 2921*)
If[num == "",(*If string is blank, increase simber count*)
s++;
]
,(*else*)
num =
"" (*If first digit is Odd but not and Odd quanity sets num to "" to break loop *)
]
]
]
];
s (*Displays final simber count*)
)
I've tested this code with Q[7] to verify the result as 287975. However, the time to process Q[7] took 6 minutes. As the value of n increases, the time to process the code gets exponentially higher.
Any help or recommendations to cut this processing time, or have I approached the Euler Problem from the wrong angle?
First, your code is very, well, bloated. This can be written down easier
isSimber[int_Integer] := isSimber[IntegerDigits[int]];
isSimber[digits_List] := And ## Equal ### EvenQ[Tally[digits]]
q[n_] := Count[isSimber /# Range[0, 10^n - 1], True]
q[7] // Timing
(* {58.328, 287975} *)
Second, yes I believe you need to think about this further. Just look that you, with your approach, would need to iterate to a number with 2^39 digits. Look that a simple Do loop with u being only 3 takes already 21s. And absolutely nothing is happening there, let alone a computation:
Do[
x = i,
{i, 10^(2^3)}
] // Timing
And we haven't even started to think about the memory. So yes, your current approach will not work.

Playing random songs algorithm

Wondering what is the best way to solve this problem: Random play a song from a list of given songs in such a way that no songs is repeated until all the songs are played.
My algorithm basically calls get_random with the reduced set of music sets to find out the next song to play. get_random uses power of 2 to divide the list and then further sub-dividing.
Is this the best I can do or any other better algorithms I can come up with? I need just the idea.
import random
import math
def get_random(number):
if number == 0:
return number
if number == 1:
return number
#make the number power of 2
orig_no = number
number = 1 << (math.floor(math.log(number))+1)
left = 0
right = number
# check if toss falls in this current half and then change the half for next recursion.
# we change half from 1, 2, 4, 8, 16, 36, 64, 128
while left < right:
#f1 can be replaced by this rd.randint(0, 1)
toss_value = f1()
if toss_value:
right = math.floor((left + right)/2)
else:
left = math.floor((left + right)/2) + 1
if left >= orig_no:
return get_random(orig_no)
else:
return left
songs = ["i am here", "your are beautiful", "soorry", "i am happy", "where am i", "what did i do", "nothing wrong with you"]
for i in range(0, len(songs)-1):
value = get_random(len(songs))
print(songs[value])
songs.pop(value)
Shuffle the array of songs, play all the songs, and shuffle it again....
You can use Fisher–Yates shuffle to shuffle the array. Since you are using Python, there's already random.shuffle for you.

Get sum of numbers within range (from 0 to user input)

This is the code i am using, its purpose is for the user to enter an integer, the program will then take the sum of all the numbers up to and including the one entered. There is probable an easier way to do this
sum = 0
puts "please enter a number"
counter = gets.chomp.to_i
begin
sum += counter
counter -= 1
end while counter == 0
The issue with your code is in counter == 0 condition in your loop, since it runs only once and then if count is not equal to 0 (in other words, if user's input wasn't 1), it stops. You not only can make this without using loops, you can shorten the whole process:
counter = gets.to_i
sum = (0..counter).inject(:+)
Demo
P.S. As you could have noticed, you can omit .chomp when you are using .to_i.
Yes, use something like this (sum from ActiveSupport):
sum = (counter + 1).times.sum
or without ActiveSupport:
sum = (counter + 1).times.inject(&:+)
num = gets.to_i
sum = num*(num+1)/2
If I understood you correctly, you could try something like this...
puts "Please enter a positive number..."
number = gets.chomp.to_i
puts "Sum of all numbers is: #{ (1..number).inject { |sum, n| sum + n} }"
I'm using enumerable method 'inject' to sum up the total. Learn more about 'inject' method at http://ruby-doc.org/core-2.2.2/Enumerable.html#method-i-inject.
Hope this helps!
As I understand you are trying to sum elements of range. Giving number 3, you want to get sum which is 6.
One way (time consuming) is to use inject or sum. You can try following:
1. [*1..value].sum
2. [*1..value].inject(:+)
The other (recommended), very efficient way is to use this equation:
(value + 1) * (value) / 2

Randomize numbers in Lua with no repeats

I am doing a project in Lua which involves randomizing numbers without repeats. Here's my code
for i = 1, 86000 do
while rndom[num] ~= nil do
num = math.random(1,95000)
end
rndom[num] = num
for k=1, 11 do
file2:write(input[num][k], " ")
end
file2:write("\n")
end
Basically it puts a value to the rndom[num] so that when randomized number repeats and rndom[num] ~= nil, it will randomize number again. My problem is, it's taking too long to load as my 'i' gets higher and there will come a time that it will stop. I'm guessing it's because randomizer can't get a rndom[num] that's 'nil'. I mean, what are the odds right? I'd like to improve the running time of it. Anyone who can suggest a better solution to my problem?
Thanks!
It is better to generate a permutation with O(N) time complexity.
local n = 95000
local t = {}
for i = 1, n do
t[i] = i
end
for i = 1, 86000 do
local j = math.random(i, n)
t[i], t[j] = t[j], t[i]
for k = 1, 11 do
file2:write(input[t[i]][k], " ")
end
file2:write"\n"
end
One simple solution is instead of using random again when you get a variable you already have, trying to return the next one available. That way you are guaranteed to have O(N^2) running time (maximum).

Better random "feeling" integer generator for short sequences

I'm trying to figure out a way to create random numbers that "feel" random over short sequences. This is for a quiz game, where there are four possible choices, and the software needs to pick one of the four spots in which to put the correct answer before filling in the other three with distractors.
Obviously, arc4random % 4 will create more than sufficiently random results over a long sequence, but in a short sequence its entirely possible (and a frequent occurrence!) to have five or six of the same number come back in a row. This is what I'm aiming to avoid.
I also don't want to simply say "never pick the same square twice," because that results in only three possible answers for every question but the first. Currently I'm doing something like this:
bool acceptable = NO;
do {
currentAnswer = arc4random() % 4;
if (currentAnswer == lastAnswer) {
if (arc4random() % 4 == 0) {
acceptable = YES;
}
} else {
acceptable = YES;
}
} while (!acceptable);
Is there a better solution to this that I'm overlooking?
If your question was how to compute currentAnswer using your example's probabilities non-iteratively, Guffa has your answer.
If the question is how to avoid random-clustering without violating equiprobability and you know the upper bound of the length of the list, then consider the following algorithm which is kind of like un-sorting:
from random import randrange
# randrange(a, b) yields a <= N < b
def decluster():
for i in range(seq_len):
j = (i + 1) % seq_len
if seq[i] == seq[j]:
i_swap = randrange(i, seq_len) # is best lower bound 0, i, j?
if seq[j] != seq[i_swap]:
print 'swap', j, i_swap, (seq[j], seq[i_swap])
seq[j], seq[i_swap] = seq[i_swap], seq[j]
seq_len = 20
seq = [randrange(1, 5) for _ in range(seq_len)]; print seq
decluster(); print seq
decluster(); print seq
where any relation to actual working Python code is purely coincidental. I'm pretty sure the prior-probabilities are maintained, and it does seem break clusters (and occasionally adds some). But I'm pretty sleepy so this is for amusement purposes only.
You populate an array of outcomes, then shuffle it, then assign them in that order.
So for just 8 questions:
answer_slots = [0,0,1,1,2,2,3,3]
shuffle(answer_slots)
print answer_slots
[1,3,2,1,0,2,3,0]
To reduce the probability for a repeated number by 25%, you can pick a random number between 0 and 3.75, and then rotate it so that the 0.75 ends up at the previous answer.
To avoid using floating point values, you can multiply the factors by four:
Pseudo code (where / is an integer division):
currentAnswer = ((random(0..14) + lastAnswer * 4) % 16) / 4
Set up a weighted array. Lets say the last value was a 2. Make an array like this:
array = [0,0,0,0,1,1,1,1,2,3,3,3,3];
Then pick a number in the array.
newValue = array[arc4random() % 13];
Now switch to using math instead of an array.
newValue = ( ( ( arc4random() % 13 ) / 4 ) + 1 + oldValue ) % 4;
For P possibilities and a weight 0<W<=1 use:
newValue = ( ( ( arc4random() % (P/W-P(1-W)) ) * W ) + 1 + oldValue ) % P;
For P=4 and W=1/4, (P/W-P(1-W)) = 13. This says the last value will be 1/4 as likely as other values.
If you completely eliminate the most recent answer it will be just as noticeable as the most recent answer showing up too often. I do not know what weight will feel right to you, but 1/4 is a good starting point.

Resources