Converting a log analysis script from Python to Nim - nim-lang

Nim looks (very) close to Python, but I am still having a hard time translating the following script:
import sys
months = { "Jan": 1, "Feb": 2, "Mar": 3, "Apr": 4, "May": 5, "Jun": 6,
"Jul": 7, "Aug": 8, "Sep": 9, "Oct": 10, "Nov": 11, "Dec": 12 }
months_r = { v:k for k,v in months.items() }
totals = {}
for line in sys.stdin:
if "redis" in line and "Partial" in line:
f1, f2 = line.split()[:2]
w = (months[f1], int(f2))
totals[w] = totals.get(w, 0) + 1
for k in sorted(totals.keys()):
print(months_r[k[0]], k[1], totals[k])
Even after reading the Manual for a few hours, I am still unsure about tuples and the way to convert the month names back and forth (my attempts with a table have failed, I did not manage to access the table like it is done in Python).
Any help will be greatly appreciated.
Thanks

Just being silly now, but I couldn't leave this alone without refactoring it to use an enum instead of the month tables
import tables, strutils, algorithm,sequtils
type Month = enum Jan=1,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
var totals: CountTable[(Month, int)]
for line in stdin.lines:
if "redis" in line and "Partial" in line:
let flds = line.split()
inc totals,(flds[0].parseEnum[:Month], flds[1].parseInt)
for k in toSeq(totals.keys).sorted:
echo k[0], " ", k[1], " ", totals[k]
Edit: had to nick #xbello's solution using the CountTable, too good.

I don't have good news. Nim code is lot longer then python code, but anyway here is my implementation, eventhough i do not understand what your program should do.
import tables, strutils, os, parseutils, algorithm
var months = {"Jan": 1, "Feb": 2, "Mar": 3, "Apr": 4, "May": 5, "Jun": 6,
"Jul": 7, "Aug": 8, "Sep": 9, "Oct": 10, "Nov": 11, "Dec": 12}.toTable
var invertedMonths: Table[int, string]
for k, v in months:
invertedMonths[v] = k
var totals: Table[(int, int), int]
while true:
let line = readLine(stdin)
if line.strip == "": break
if "redis" in line and "Partial" in line:
let args = line.split()
let w = (months[args[0]], parseInt(args[1]))
totals[w] = totals.getOrDefault(w, 0) + 1
var keys = newSeq[(int, int)](totals.len)
keys.setLen(0)
for k in totals.keys():
keys.add(k)
# i have no idea how python would sort this
keys.sort(proc(a, b: (int, int)): int =
a[0] + a[1] - b[0] - b[1]
)
for k in keys:
echo invertedMonths[k[0]], " ", k[1], " ", totals[k]
edit
After some advice is restructures code as follows and length looks lot better now.
import tables, strutils, algorithm, sequtils
var months = {"Jan": 1, "Feb": 2, "Mar": 3, "Apr": 4, "May": 5, "Jun": 6,
"Jul": 7, "Aug": 8, "Sep": 9, "Oct": 10, "Nov": 11, "Dec": 12}.toTable
var invertedMonths: Table[int, string]
for k, v in months: invertedMonths[v] = k
var totals: Table[(int, int), int]
for line in stdin.lines:
if line.strip == "": break
if "redis" in line and "Partial" in line:
let args = line.split()
let w = (months[args[0]], parseInt(args[1]))
totals[w] = totals.getOrDefault(w, 0) + 1
for k in toSeq(totals.keys).sorted:
echo invertedMonths[k[0]], " ", k[1], " ", totals[k]

The problem is to convert the month name between the number and the tree letter? Nim has this:
import times
echo Month(2) # February
echo ($Month(2))[.. 2] # Feb
echo parse("Feb", "MMM").month # February
echo ord(parse("Feb", "MMM").month) # 2
Then for the counting you can do this:
import tables, times
var totals = initCountTable[(Month, int)]()
# The following is some sample data, you just get each
# tuple[Mont, int] from your parsed file
var sample = #[
(m: 1.Month, i: 3),
(m: 2.Month, i: 3),
(m: 1.Month, i: 1),
(m: 1.Month, i: 3),
(m: 12.Month, i: 2)]
for item in sample:
totals.inc(s)
echo totals
# {(January, 3): 2, (February, 3): 1, (January, 1): 1, (December, 2): 1}
Finally, the sorting. As I understand, this is sorted alphabetically per month name, and the ties per int. I have the impression that you want to sort per month number instead of name, thus all the table gimnastics. Nim will sort per month number if you used Month as the table key:
import algorithm
echo sorted(#[12.Month, 1.Month, 6.Month])
##[January, June, December]
Putting it all together, and assuming you have a log file that follows the pattern "Month Day Message" like:
Jan 1 Log Message Partial redis
Mar 31 A Partial redis in the same date
Jan 02 More Log Messages but not captured
Mar 31 Even More Log Messages Partial redis
Jan 15 An out of order message Partial redis
This can be done in 5 clean lines, 9 if you count variable declarations and imports:
import algorithm, sequtils, strscans, strutils, tables, times
var totals = initCountTable[(Month, int)]()
var month, msg: string
var day: int
for l in stdin.lines:
if scanf(l, "$w $i $*", month, day, msg) and "redis" in l and "Partial" in l:
totals.inc (parse(month, "MMM").month, day)
for k in sorted(toSeq(totals.keys())):
echo k, ": ", totals[k]
Results:
(January, 1): 1
(January, 15): 1
(March, 31): 2

Related

How to find highest number from the vector provided?

Say, a dictionary is provided with certain values.
How to find the highest number ?
Input
d1 = {1: 1, 2: 6, 3: 7, 4: 1, 5: 3}
vector = 5
d1 = {1: 1, 2: 6, 3: 7, 4: 1, 5: 3}
vector = 5
l1 = list(td.values())
Based on vector value, it should print output.
vector is 5, so sum of the dict-values to form vector is 3,1,1
Corresponding keys are 5,4,1
so, the output should be 541 but slight change here.
Since value '1' is associated with multiple keys, it should pick up highest key,
so, output should be 544 instead of 541 (For above input, to brief about combinations without considering '1+1+1+1+1' to '44444')
Another example
d1 = {1: 1, 2: 6, 3: 7, 4: 1, 5: 3}
vector = 7
Possible combinations:
3 # --> Key of 7
21 # --> Key of 6 & 1 (6+1 = 7)
24 # --> Key of 6 & 1 (6+1 = 7)
12 # --> Key of 1 & 6 (1+6 = 7)
42 # --> Key of 1 & 6 (1+6 = 7)
Output : 42 (Highest number)
Another
d1 = {1:9,2:4,3:2,4:2,5:6,6:3,7:2,8:2,9:1}
vector = 5
here, it would be 1+2+2 (988).
But, '1' can also be added 5 times to form vector 5,
which would be '99999'
Since #Patrick Artner requested for minimal reproducible example, posting this though doesn't work as expected.
from itertools import combinations
def find_sum_with_index(l1, vector):
index_vals = [iv for iv in enumerate(l1) if iv[1] < target]
for r in range(1, len(index_vals) + 1):
for perm in combinations(index_vals, r):
if sum([p[1] for p in perm]) == target:
yield perm
d1 = {1: 1, 2: 6, 3: 7, 4: 1, 5: 3}
vector=5
l1=list(d1.values())
for match in find_sum_with_index(l1, vector):
print(dict(match))
Is there any specific algorithm to be chosen for these kind of stuffs ?
Similar to the other answer but allowing repeatedly using the same keys to get the max number of keys which values sum up to vector:
d1 = {1: 1, 2: 6, 3: 7, 4: 1, 5: 3}
vector = 7
#create a dict that contains value -> max-key for that value
d2 = {}
for k,v in d1.items():
d2[v] = max(d2.get(v,-1), k)
def mod_powerset(iterable,l):
# uses combinations_with_replacement to allow multiple usages of one value
from itertools import chain, combinations_with_replacement
s = list(set(iterable))
return chain.from_iterable(combinations_with_replacement(s, r) for r in range(l))
# create all combinations that sum to vector
p = [ s for s in mod_powerset(d1.values(),vector//min(d1.values())+1) if sum(s) == vector]
print(p)
# sort combinations by length then value descending and take the max one
mp = max( (sorted(y, reverse=True) for y in p), key=lambda x: (len(x),x))
# get the correct keys to be used from d2 dict
rv = [d2[num] for num in mp]
# sort by values, biggest first
rv.sort(reverse=True)
# solution
print(''.join(map(str,rv)))
Original powerset - see itertools-recipes.
There are some steps involved, see documentation in comments in code:
d1 = {1: 1, 2: 6, 3: 7, 4: 1, 5: 3}
vector = 7
# create a dict that contains value -> sorted key-list, used to get final keys
from collections import defaultdict
d2 = defaultdict(list)
for k,v in d1.items():
d2[v].append(k)
for k,v in d2.items():
d2[k] = sorted(v, reverse=True)
from itertools import chain, combinations
def powerset(iterable):
"see itertools: powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
s = list(iterable)
return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))
# create all combinations that sum to vector
p = [ s for s in powerset(d1.values()) if sum(s) == vector]
# sort combinations by length then value descending and take the max one
mp = max( (sorted(y, reverse=True) for y in p), key=lambda x: (len(x),x))
# get the correct keys to be used from d2 dict
rv = []
for num in mp:
rv.append(d2[num][0])
# remove used key from list
d2[num][:] = d2[num][1:]
# sort by values, biggest first
rv.sort(reverse=True)
# solution
print(''.join(map(str,rv)))
For powerset - see itertools-recipes.

How to make combination, if any one of the element exists that can be added to make sum?

To find all possible combinations that can be added to make given sum.
Combinations can be formed with multiple elements and also if any single element exists.
Input:
l1 = [9,1, 2, 7, 6, 1, 5]
target = 8
**Constraints**
1<=(len(l1))<=500000
1<=each_list_element<=1000
Output:
Format : {index:element}
{1:1, 5:1, 4:6} #Indices : 1,5,4 Elements : 1,1,6
{1:1, 2:2, 6:5}
{5:1, 2:2, 6:5}
{1:1, 3:7}
{5:1, 3:7}
{2:2, 4:6}
More Scenarios:
Input = [4,6,8,5,3]
target = 3
Output {4:3}
Input = [4,6,8,3,5,3]
target = 3
Output {5:3,3:3}
Input = [1,2,3,15]
target = 15
Output {3:15}
Below code covers for all above scenarios.
Scenarios to be handled, along with above.
Input =[1,6,7,1,3]
target=5
Output={0:1,3:1,4:3} , {0:1,0:1,4:3}, {3:1,3:1,4:3}
Input=[9,6,8,1,7]
target=5
Output={3:1,3:1,3:1,3:1,3:1}
As suggested by #Chris Doyle in previous question, will be using that code.
(How to find indices and combinations that adds upto given sum?)
Code:
from itertools import combinations
def find_sum_with_index(l1, target):
index_vals = [iv for iv in enumerate(l1) if iv[1] < target]
for r in range(1, len(index_vals) + 1):
for perm in combinations(index_vals, r):
if sum([p[1] for p in perm]) == target:
yield perm
l1 = [9, 1, 2, 7, 6, 1, 5]
target = 8
for match in find_sum_with_index(l1, target):
print(dict(match))
You can use dictionary comprehension
from itertools import combinations
l1 = [9,1, 2, 7, 6, 1, 5]
target = 8
for i in range(len(l1)):
for c in combinations(l1,i):
if sum(c) == target:
res = { i:x for i,x in enumerate(c)}
print(res)

Improving While-Loop with Numpy

I have given the following three variables:
start = 30 #starting value
end = 60 #ending value
slice_size = 6 #value difference per tuble
start and end are row numbers of an array. My goal is to create an array/list of tuples, where each tuples includes as much items as slice_size defines. A little example: If start and end have the above values the first four tuples would be:
[[30,35],[36,41],[42,47],[48,53],...].
But now comes the clue: the first value of the next tuple does not start with the first value before + slice_size, but rather with first value + slice_size/2. So I want something like this:
[[30,35],[33,38],[36,41],[39,44],...].
This list of tuples goes on until end is reached or right before it is reached - so until <=end . The last value of the list is not allowed to pass the value of end. The value of slice_size must of course always be an even number to work properly.
My nooby attempt is done by a while loop:
condition = 0
i = 0
list = []
half_slice = int(slice_size /2)
while condition <= end:
list.append([start+int(slice_size/2)*i,start+((slice_size-1)+i*half_slice)])
condition = start+((slice_size-1)+i*int(slice_size/2))
i += 1
The thing is, it works. However I know this is complete rubbish and I want to improve my skill. Do you have a suggestion how to do it in a couple of code lines?
you must not use list as it is a reserved word
import numpy as np
start = 30 #starting value
end = 60 #ending value
slice_size = 6 #value difference per tuble
l = [[i,j] for i,j in zip(np.arange(start, end, slice_size/2),
np.arange(start + slice_size - 1,
end + slice_size - 1,
slice_size/2)
)
]
print(l)
Output:
[[30.0, 35.0],
[33.0, 38.0],
[36.0, 41.0],
[39.0, 44.0],
[42.0, 47.0],
[45.0, 50.0],
[48.0, 53.0],
[51.0, 56.0],
[54.0, 59.0],
[57.0, 62.0]]
1) Do NOT use list as a variable name. It is a reserved key-word.
2) Not a NumPy solution but you can use list comprehension:
start = 30 #starting value
end = 60 #ending value
slice_size = 6 #value difference per tuble
result = [[current, current + slice_size - 1] for current in range(start, end - slice_size + 2, slice_size // 2)]
print(result)
Output:
[[30, 35], [33, 38], [36, 41], [39, 44], [42, 47], [45, 50], [48, 53], [51, 56], [54, 59]]
This will work for an odd number slice_size as well.

python random lottery number generator game

I have to make a game where like the lottery my program generates 5 random numbers from a list of numbers 1-50 and one additional number from a list of numbers 1-20 and combines them into a final list that reads eg: (20, 26, 49, 01, 11, + 06) where two numbers are never repeated like (22, 11, 34, 44, 01, + 22) <--- this is what I don't want
attached below is the code I have written yet how do I make it so two numbers or more are never repeated and to add the + into my list without the "" signs
input:
import random
a = list(range(1,51))
b = random.randint(1, 20)
temp = []
for i in range(5):
random.shuffle(a)
temp.append(random.choice(a[:5]))
temp.append('+')
temp.append(b)
print(temp)
output:
[14, 12, 3, 16, 23, '+', 9]
You can not add + without the ' around them - they mark the + as string.
Also: you shuffle your list - simply take the first 5 values - they are random and your list does not contain any dupes so you are golden:
nums = list(range(1,51))
random.shuffle(nums)
five_nums = nums[:5]
print(five_nums) # [44, 23, 34, 38, 3]
To simplyfy it, use:
import random
# creates 5 unique elements from 1..50 and adds a + and a [0-19]+1 number
randlist = random.sample(range(1,51),k=5) + ["+", random.choice(range(20))+1]
print(randlist)
Now you got mixed numbers and strings - you can create a combined string by:
print("You drew {} {} {} {} {} {} {}".format(*randlist))
To create a string like
[48, 2, 9, 6, 41, '+', 8]
You drew 48 2 9 6 41 + 8
Doku:
random.sample (draw without putting back)
You can try the following:
import random
randList, run = [], 0
while run < 6:
number = random.randint(1,51)
if number not in randList:
if run == 5:
randList.append('+'+str(number))
break
randList.append(number)
run += 1
print(randList)
You can't have a string in a list without quotes, however, if you were to print every item in the list (using a for loop or join), the quotes wouldn't be there.
This code will generate a list of 7 random numbers
import random
def main():
numbers = []
for num in range(7):
num = random.randrange(50)
numbers.append(num)
print(numbers)
main()
#No repeating numbers and sorted output
import random
picks = int (input("How Many Picks ?: "))
for i in range (picks):
num_list = random.sample(range(1, 45), 5,)
num_list.sort()
joker_num = random.sample(range(1, 20), 1)
print("Lucky Numbers :", num_list, "-", "Joker :", joker_num)
It didn't work because you need to have
import random

How to insert a String as Integer to a List in Python

I need to insert a number (user input) as an integer to a Python list.
My code:
count = 0
list1 = []
for number in input():
if number != ' ':
list1.append(int(number))
Input: 10 11 12
Output: [1, 0, 1, 1, 1, 2]
Expected Output: [10, 11, 12]
Looping over a string (such as the one returned by input()) will loop over the individual characters in the string:
>>> s = 'hi guys'
>>> for char in s:
... print(char)
...
h
i
g
u
y
s
>>>
To loop over the "words" (i.e. substrings separated by spaces), you want to split() the user's input instead:
>>> s = 'hi guys'
>>> words = s.split()
>>> words
['hi', 'guys']
>>> for word in words:
... print(word)
...
hi
guys
>>>
So in your case, that would be:
for number in input().split():
list1.append(int(number))
We can leave the if number != ' ': out because split() already gets rid of all the spaces and simply returns a list of the numbers.
Here You Go
input_array = []
c_input = input('please enter the input\n')
for item in c_input.split():
input_array.append(int(item))
print (input_array)
input: - 1 11 23 23 456
Output:- [1, 11, 23, 23, 456]
i hope you find it useful
You should use split method to split the values as a list with string:
str_nums = input.split() #will give ['10','11','12']
then
lst_nums = []
for i in str_nums.split():
lst_nums.append(int(i))
print(lst_nums)
#output [10,11,12]
You can also use map and split together.
inp = "10 11 12"
print(list(map(int,inp.split(" "))))
#Output
[10,11,12]
Or
print([int(i) for i in input().split()])

Resources