Split string every nth character, excluding hidden characters (e.g. "\033[92m") - python

I'm writing a program to represent the "qlocktwo" clock in python.
This is the code:
The most important part to the question is the last four lines (I've left the rest to negate any confusion).
#!/usr/bin/python3
from datetime import datetime
import num2words
import string
grid = "itlisasampmacquarterdctwentyfivexhalfstenftopasterunineonesixthreefourfivetwoeightelevenseventwelvetenseoclock"
now = datetime.now()
minute = int(now.strftime("%M"))
hour = int(now.strftime("%H"))
def checkTime(hour, minute):
if hour >= 12:
hour -= 12
t = "past"
if hour == 0:
a = "oclock"
if minute > 30:
a = 60 - minute
t = "to"
hour += 1
return a, t, hour
elif minute <= 30:
a = minute
t = "past"
return a, t, hour
def round(a):
for i in range(1, 7):
i = i * 5
for j in range(i-2, i + 3):
if a == j:
return i
time = checkTime(hour, 0)
if time[0] != 0:
outMin = num2words.num2words(round(time[0])).replace("-", " ")
else:
outMin = "oclock"
outHour = num2words.num2words(time[2])
output = (f"it is {outMin} {time[1]} {outHour}").split(" ")
newGrid = grid
for i in output:
newGrid = newGrid.replace(i, f"\033[92m{i}\033[0m")
for i in [newGrid[i:i+10] for i in range(0, len(newGrid), 10)]:
print(i)
At the end, I've used this answer to split based on the number of characters (in my case, 10), but this also ends up counting hidden characters, such as terminal colors, so it splits on them as well.
So this is the output I get:
https://imgur.com/fiVFPNT
Whereas, this is the output I want (but with the colors remaining):
https://imgur.com/m1sKUqb
Is there a way to split solely on visible characters?

Related

Creating shift patterns

I'm a little lost and would love to help build shift buildings.
The work is carried out in an 8-hour shift and employees are entitled to three 10-minute breaks during their shift, which for convenience can start every ten minutes (i.e .: breaks can start at 08: 00,09: 20, ...)
It is determined that the maximum interval between breaks and breaks or between breaks and the beginning / end of the shift does not exceed w = 160 minutes) it can be assumed that W receives a value that is double 10 minutes.
I need to build all the possible shift structures.
I say this list indicates a shift, every interval 10 minutes
print(list(range(1,49)))
Example of one shift
A1=[2,3,....,15,17,........,32,34,...48]
I.e. break 1 in the first 10 minutes of the shift, break 2 after 150 minutes and last break after 330 minutes.
Thanks
All shift structures can be obtained by performing a Exhaustive Search in three loops(a number of breaks) so that the condition is match.
work = set(range(1, 49))
w = 16
breaktime = []
check = 0
for i in range(1, w + 2):
if i > check:
break1 = i
for j in range(i, i + w + 2):
if j > break1:
break2 = j
for k in range(j, j + w + 2):
if k > break2 and k < 49:
break3 = k
if w + k > 49:
breaktime.append([break1, break2, break3])
check += 1
shift_list = []
for l in breaktime:
shift = list(work - set(l))
print(shift)
shift_list.append(shift)
I'd just be lazy and use itertools functions here since the data is quite small. At larger numbers one might want to be more clever.
The breaks (and shift) results are zero-indexed for programming convenience (as opposed to 1-indexed as in the question), but that's trivial to adjust, and makes it easier to use e.g. datetime for actual times.
import itertools
def compute_breaks(
*,
timeblock_count: int,
n_breaks: int,
min_between: int,
min_start: int = 0,
min_end: int = 0,
):
timeblocks = range(timeblock_count)
for breaks in itertools.combinations(timeblocks, n_breaks):
if any(abs(a - b) < min_between for a, b in itertools.combinations(breaks, 2)):
continue
if any(a < min_start or a > timeblock_count - min_end for a in breaks):
continue
yield set(breaks)
def render_shift(shift, timeblock_count: int):
return "".join("O" if x in shift else "." for x in range(timeblock_count))
def breaks_to_shift(breaks, timeblock_count: int):
return [i for i in range(timeblock_count) if i not in breaks]
for breaks in sorted(compute_breaks(timeblock_count=48, n_breaks=3, min_between=16)):
shift = breaks_to_shift(breaks, timeblock_count=48)
print(render_shift(shift, timeblock_count=48))
The renderings show breaks as .s and work blocks as Os, e.g. (truncated):
.OOOOOOOOOOOOOOO.OOOOOOOOOOOOOOO.OOOOOOOOOOOOOOO
.OOOOOOOOOOOOOOO.OOOOOOOOOOOOOOOO.OOOOOOOOOOOOOO
.OOOOOOOOOOOOOOO.OOOOOOOOOOOOOOOOO.OOOOOOOOOOOOO
...
.OOOOOOOOOOOOOOO.OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.O
.OOOOOOOOOOOOOOO.OOOOOOOOOOOOOOOOOOOOOOOOOOOOOO.
...
O.OOOOOOOOOOOOOOOOOOOOO.OOOOOOOOOOOOOOOO.OOOOOOO
O.OOOOOOOOOOOOOOOOOOOOO.OOOOOOOOOOOOOOOOO.OOOOOO
O.OOOOOOOOOOOOOOOOOOOOO.OOOOOOOOOOOOOOOOOO.OOOOO
...
OOOOO.OOOOOOOOOOOOOOOO.OOOOOOOOOOOOOOOO.OOOOOOOO
OOOOO.OOOOOOOOOOOOOOOO.OOOOOOOOOOOOOOOOO.OOOOOOO
OOOOO.OOOOOOOOOOOOOOOO.OOOOOOOOOOOOOOOOOO.OOOOOO
...
OOOOOOOOOOOOO.OOOOOOOOOOOOOOOO.OOOOOOOOOOOOOOOO.
OOOOOOOOOOOOO.OOOOOOOOOOOOOOOOO.OOOOOOOOOOOOOOO.
OOOOOOOOOOOOOO.OOOOOOOOOOOOOOO.OOOOOOOOOOOOOOO.O
OOOOOOOOOOOOOO.OOOOOOOOOOOOOOO.OOOOOOOOOOOOOOOO.

python-datetime outputting -1 day instead of desired output

So, my goal is to change this output from datetime:
time left: -1 day, 23:57:28
To this:
time left: 0:00:30
Now, this needs to be dynamic, as the code is supposed to be changed in the dictionary. I'm trying to figure out why it is outputting with
-1 day, 23:57:28
I've tried moving where it executes and even changing some other code. I just don't understand why it's showing with -1 day. It seems likes it is executing one too many times
Also, a side note, the purpose of this program is to figure out how many songs can fit into a playlist given a time restraint. I can't seem to figure out the right if statement for it to work. Could someone also help with this?
This is the current output of the program:
0:02:34
0:06:30
Reached limit of 0:07:00
time left: -1 day, 23:57:28
See code below:
import datetime
#durations and names of songs are inputted here
timeDict = {
'Song1' : '2:34',
'Song2' : '3:56',
'Song3' : '3:02'
}
def timeAdder():
#assigns sum to the datetime library's timedelta class
sum = datetime.timedelta()
#sets the limit, can be whatever
limit = '0:07:00'
#calculates the sum
for i in timeDict.values():
(m, s) = i.split(':')
d = datetime.timedelta(minutes=int(m), seconds=int(s))
sum += d
#checks whether the limit has been reached
while str(sum)<limit:
print(sum)
break
#commits the big STOP when limit is reached
if str(sum)>limit:
print("Reached limit of " + limit)
break
#timeLeft variable created as well as datetime object conversion to a string
x = '%H:%M:%S'
timeLeft = datetime.datetime.strptime(limit, x) - datetime.datetime.strptime(str(sum), x)
for i in timeDict:
if timeDict[i] <= str(timeLeft):
print("You can fit " + i + " into your playlist.")
print("time left: " + str(timeLeft))
def main():
timeAdder()
main()
Any help with this would be appreciated.
It seems likes it is executing one too many times
Bingo. The problem is here:
sum += d
...
#commits the big STOP when limit is reached
if str(sum)>limit:
print("Reached limit of " + limit)
break
You are adding to your sum right away, and then checking whether it has passed the limit. Instead, you need to check whether adding to the sum will pass the limit before you actually add it.
Two other things: first, sum is a Python keyword, so you don't want to use it as a variable name. And second, you never want to compare data as strings, you will get weird behavior. Like:
>>> "0:07:30" > "2:34"
False
So all of your times should be timedelta objects.
Here is new code:
def timeAdder():
#assigns sum to the datetime library's timedelta class
sum_ = datetime.timedelta()
#sets the limit, can be whatever
limit = '0:07:00'
(h, m, s) = (int(i) for i in limit.split(":"))
limitDelta = datetime.timedelta(hours=h, minutes=m, seconds=s)
#calculates the sum
for i in timeDict.values():
(m, s) = i.split(':')
d = datetime.timedelta(minutes=int(m), seconds=int(s))
if (sum_ + d) > limitDelta:
print("Reached limit of " + limit)
break
# else, loop continues
sum_ += d
print(sum_)
timeLeft = limitDelta - sum_
for songName, songLength in timeDict.items():
(m, s) = (int(i) for i in songLength.split(':'))
d = datetime.timedelta(minutes=m, seconds=s)
if d < timeLeft:
print("You can fit " + songName + " into your playlist.")
print("time left: " + str(timeLeft))
Demo

Change UTC offset for a time string

I would like to change the time to a manually chosen offset for a timestring
In this example, I will subract 11 hours from the times, but the utc offset would still be 00:00.. How can I do this correctly?
time = "2020-03-03T18:21:19+00:00"
utc_diff = 2
obj1 = datetime.datetime.strptime(
re.sub(r"([\+-]\d\d):(\d\d)(?::(\d\d(?:.\d+)?))?", r"\1\2\3", time),
"%Y-%m-%dT%H:%M:%S%z")
obj1 = obj1 - datetime.timedelta(hours=-utc_diff)
Using this example the result would be
"2020-03-03T20:21:19+00:00"
But I want the offset to change also,:
"2020-03-03T20:21:19+02:00"
This should work for your needs:
def utc_offset(time, offset):
def pad(number):
n = str(abs(number))
while len(n) < 2:
n = "0" + n
if number >= 0:
n = "+" + n
else:
n = "-" + n
return n
utc_diff_format = f"{pad(offset)}:00"
time = list(time)
i = time.index("+")
time[i:] = list(utc_diff_format)
time = ''.join(time)
return time
time = "2020-03-03T18:21:19+00:00"
utc_diff = 2
print(utc_offset(time, utc_diff))
Output:
2020-03-03T18:21:19+02:00
you can pass integer values directly to datetime.timedelta(hours=11)

Rolling a roulette with chances and counting the maximum same-side sequences of a number of rolls

I am trying to generate a random sequence of numbers, with each "result" having a chance of {a}48.6%/{b}48.6%/{c}2.8%.
Counting how many times in a sequence of 6 or more {a} occurred, same for {b}.
{c} counts as neutral, meaning that if an {a} sequence is happening, {c} will count as {a}, additionally if a {b} sequence is happening, then {c} will count as {b}.
The thing is that the results seem right, but every "i" iteration seems to give results that are "weighted" either on the {a} side or the {b} side. And I can't seem to figure out why.
I would expect for example to have a result of :
{a:6, b:7, a:8, a:7, b:9} but what I am getting is {a:7, a:9, a:6, a:8} OR {b:7, b:8, b:6} etc.
Any ideas?
import sys
import random
from random import seed
from random import randint
from datetime import datetime
import time
loopRange = 8
flips = 500
median = 0
for j in range(loopRange):
random.seed(datetime.now())
sequenceArray = []
maxN = 0
flag1 = -1
flag2 = -1
for i in range(flips):
number = randint(1, 1000)
if(number <= 486):
flag1 = 0
sequenceArray.append(number)
elif(number > 486 and number <= 972):
flag1 = 1
sequenceArray.append(number)
elif(number > 972):
sequenceArray.append(number)
if(flag1 != -1 and flag2 == -1):
flag2 = flag1
if(flag1 != flag2):
sequenceArray.pop()
if(len(sequenceArray) > maxN):
maxN = len(sequenceArray)
if(len(sequenceArray) >= 6):
print(len(sequenceArray))
# print(sequenceArray)
# print(sequenceArray)
sequenceArray = []
median += maxN
print("Maximum sequence is %d " % maxN)
print("\n")
time.sleep(random.uniform(0.1, 1))
median = float(median/loopRange)
print("\n")
print(median)
I would implement something with two cursors prev (previous) and curr (current) since you need to detect a change between the current and the previous state.
I just write the code of the inner loop on i since the external loop adds complexity without focusing on the source of the problem. You can then include this in your piece of code. It seems to work for me, but I am not sure to understand perfectly how you want to manage all the behaviours (especially at start).
prev = -1
curr = -1
seq = []
maxN = 0
for i in range(flips):
number = randint(1, 1000)
if number<=486:
curr = 0 # case 'a'
elif number<=972:
curr = 1 # case 'b'
else:
curr = 2 # case 'c', the joker
if (prev==-1) and (curr==2):
# If we start with the joker, don't do anything
# You can add code here to change this behavior
pass
else:
if (prev==-1):
# At start, it is like the previous element is the current one
prev=curr
if (prev==curr) or (curr==2):
# We continue the sequence
seq.append(number)
else:
# Break the sequence
maxN = max(maxN,len(seq)) # save maximum length
if len(seq)>=6:
print("%u: %r"%(prev,seq))
seq = [] # reset sequence
seq.append(number) # don't forget to append the new number! It breaks the sequence, but starts a new one
prev=curr # We switch case 'a' for 'b', or 'b' for 'a'

Python interval interesction

My problem is as follows:
having file with list of intervals:
1 5
2 8
9 12
20 30
And a range of
0 200
I would like to do such an intersection that will report the positions [start end] between my intervals inside the given range.
For example:
8 9
12 20
30 200
Beside any ideas how to bite this, would be also nice to read some thoughts on optimization, since as always the input files are going to be huge.
this solution works as long the intervals are ordered by the start point and does not require to create a list as big as the total range.
code
with open("0.txt") as f:
t=[x.rstrip("\n").split("\t") for x in f.readlines()]
intervals=[(int(x[0]),int(x[1])) for x in t]
def find_ints(intervals, mn, mx):
next_start = mn
for x in intervals:
if next_start < x[0]:
yield next_start,x[0]
next_start = x[1]
elif next_start < x[1]:
next_start = x[1]
if next_start < mx:
yield next_start, mx
print list(find_ints(intervals, 0, 200))
output:
(in the case of the example you gave)
[(0, 1), (8, 9), (12, 20), (30, 200)]
Rough algorithm:
create an array of booleans, all set to false seen = [False]*200
Iterate over the input file, for each line start end set seen[start] .. seen[end] to be True
Once done, then you can trivially walk the array to find the unused intervals.
In terms of optimisations, if the list of input ranges is sorted on start number, then you can track the highest seen number and use that to filter ranges as they are processed -
e.g. something like
for (start,end) in input:
if end<=lowest_unseen:
next
if start<lowest_unseen:
start=lowest_unseen
...
which (ignoring the cost of the original sort) should make the whole thing O(n) - you go through the array once to tag seen/unseen and once to output unseens.
Seems I'm feeling nice. Here is the (unoptimised) code, assuming your input file is called input
seen = [False]*200
file = open('input','r')
rows = file.readlines()
for row in rows:
(start,end) = row.split(' ')
print "%s %s" % (start,end)
for x in range( int(start)-1, int(end)-1 ):
seen[x] = True
print seen[0:10]
in_unseen_block=False
start=1
for x in range(1,200):
val=seen[x-1]
if val and not in_unseen_block:
continue
if not val and in_unseen_block:
continue
# Must be at a change point.
if val:
# we have reached the end of the block
print "%s %s" % (start,x)
in_unseen_block = False
else:
# start of new block
start = x
in_unseen_block = True
# Handle end block
if in_unseen_block:
print "%s %s" % (start, 200)
I'm leaving the optimizations as an exercise for the reader.
If you make a note every time that one of your input intervals either opens or closes, you can do what you want by putting together the keys of opens and closes, sort into an ordered set, and you'll be able to essentially think, "okay, let's say that each adjacent pair of numbers forms an interval. Then I can focus all of my logic on these intervals as discrete chunks."
myRange = range(201)
intervals = [(1,5), (2,8), (9,12), (20,30)]
opens = {}
closes = {}
def open(index):
if index not in opens:
opens[index] = 0
opens[index] += 1
def close(index):
if index not in closes:
closes[index] = 0
closes[index] += 1
for start, end in intervals:
if end > start: # Making sure to exclude empty intervals, which can be problematic later
open(start)
close(end)
# Sort all the interval-endpoints that we really need to look at
oset = {0:None, 200:None}
for k in opens.keys():
oset[k] = None
for k in closes.keys():
oset[k] = None
relevant_indices = sorted(oset.keys())
# Find the clear ranges
state = 0
results = []
for i in range(len(relevant_indices) - 1):
start = relevant_indices[i]
end = relevant_indices[i+1]
start_state = state
if start in opens:
start_state += opens[start]
if start in closes:
start_state -= closes[start]
end_state = start_state
if end in opens:
end_state += opens[end]
if end in closes:
end_state -= closes[end]
state = end_state
if start_state == 0:
result_start = start
result_end = end
results.append((result_start, result_end))
for start, end in results:
print(str(start) + " " + str(end))
This outputs:
0 1
8 9
12 20
30 200
The intervals don't need to be sorted.
This question seems to be a duplicate of Merging intervals in Python.
If I understood well the problem, you have a list of intervals (1 5; 2 8; 9 12; 20 30) and a range (0 200), and you want to get the positions outside your intervals, but inside given range. Right?
There's a Python library that can help you on that: python-intervals (also available from PyPI using pip). Disclaimer: I'm the maintainer of that library.
Assuming you import this library as follows:
import intervals as I
It's quite easy to get your answer. Basically, you first want to create a disjunction of intervals based on the ones you provide:
inters = I.closed(1, 5) | I.closed(2, 8) | I.closed(9, 12) | I.closed(20, 30)
Then you compute the complement of these intervals, to get everything that is "outside":
compl = ~inters
Then you create the union with [0, 200], as you want to restrict the points to that interval:
print(compl & I.closed(0, 200))
This results in:
[0,1) | (8,9) | (12,20) | (30,200]

Categories