Langford numbers pairing using using Python - python

I'm trying to generate Langford numbers with Python. I have already written the following code which works well for getting the fourth Langford number. Here is my code :
import itertools
n=0
for l in set(itertools.permutations(["1", "1", "2", "2", "3", "3", "4", "4"])):
t1, t2, t3, t4 = [i for i, j in enumerate(l) if j == "1"], [i for i, j in enumerate(l) if j == "2"], [i for i, j in enumerate(l) if j == "3"], [i for i, j in enumerate(l) if j == "4"]
if abs(t1[1]-t1[0]) == 2 and abs(t2[1]-t2[0]) == 3 and abs(t3[1]-t3[0]) == 4 and abs(t4[1]-t4[0]) == 5:
print("".join(l))
n+=1
else:
pass
print(n)
I have two questions :
first, are there techniques to make this code quicker (for the moment it finds the result in 0.1s)
second, could you give me hints on how I could adapt the code to get any n-th Langford number
I you wonder, here is the wikipedia page for Langford numbers.
Thank you very much if you take the time to answer me !

Yes, there are a few faster ways to make Langford sequences.
Firstly, here's a fairly simple way. Rather than testing all of the permutations containing the pairs of numbers from 1 to n, we generate the permutations of numbers from 1 to n and then try to build Langford sequences from those permutations by placing each pair of numbers into the next available pair of slots. If no pair of slots is available we abandon that permutation and go onto the next one.
Building a sequence is a little slower than simply testing if a full permutation of 2n items is valid, but it means we need to test a lot fewer permutations when n is large. Eg, if n=7 there are 7! = 5040 permutations, but if we test the permutations of 7 pairs, that's 14! = 87178291200 permutations!
We can reduce that number though, because it contains a lot of duplicates. For 7 pairs, the number of unique permutations is 14! / (2**7) = 681080400 since swapping the 2 items in any of the 7 pairs produces a duplicate permutation. Unfortunately, itertools.permutations doesn't care about duplicates, but my answer here has code for a permutation generator that doesn't produce duplicate permutations. But still, 681 million permutations is a large number, and it takes a long time to test them. So it's better if we can avoid doing that.
import sys
from itertools import permutations
def place(t):
slen = 2 * len(t)
seq = [0] * slen
for u in t:
# Find next vacant slot
for i, v in enumerate(seq):
if v == 0:
break
else:
# No vacant slots
return
j = i + u + 1
if j >= slen or seq[j]:
return
seq[i] = seq[j] = u
return tuple(seq)
def langford(n):
count = 0
for t in permutations(range(1, n+1)):
seq = place(t)
#if seq and seq < seq[::-1]:
if seq:
count += 1
print(seq, count)
return count // 2
def main():
n = int(sys.argv[1]) if len(sys.argv) > 1 else 4
count = langford(n)
print(count)
if __name__ == '__main__':
main()
output for n=7
(1, 4, 1, 5, 6, 7, 4, 2, 3, 5, 2, 6, 3, 7) 1
(1, 4, 1, 6, 7, 3, 4, 5, 2, 3, 6, 2, 7, 5) 2
(1, 5, 1, 4, 6, 7, 3, 5, 4, 2, 3, 6, 2, 7) 3
(1, 5, 1, 6, 3, 7, 4, 5, 3, 2, 6, 4, 2, 7) 4
(1, 5, 1, 6, 7, 2, 4, 5, 2, 3, 6, 4, 7, 3) 5
(1, 5, 1, 7, 3, 4, 6, 5, 3, 2, 4, 7, 2, 6) 6
(1, 6, 1, 3, 5, 7, 4, 3, 6, 2, 5, 4, 2, 7) 7
(1, 6, 1, 7, 2, 4, 5, 2, 6, 3, 4, 7, 5, 3) 8
(1, 7, 1, 2, 5, 6, 2, 3, 4, 7, 5, 3, 6, 4) 9
(1, 7, 1, 2, 6, 4, 2, 5, 3, 7, 4, 6, 3, 5) 10
(2, 3, 6, 2, 7, 3, 4, 5, 1, 6, 1, 4, 7, 5) 11
(2, 3, 7, 2, 6, 3, 5, 1, 4, 1, 7, 6, 5, 4) 12
(2, 4, 7, 2, 3, 6, 4, 5, 3, 1, 7, 1, 6, 5) 13
(2, 5, 6, 2, 3, 7, 4, 5, 3, 6, 1, 4, 1, 7) 14
(2, 6, 3, 2, 5, 7, 3, 4, 6, 1, 5, 1, 4, 7) 15
(2, 6, 3, 2, 7, 4, 3, 5, 6, 1, 4, 1, 7, 5) 16
(2, 6, 7, 2, 1, 5, 1, 4, 6, 3, 7, 5, 4, 3) 17
(2, 7, 4, 2, 3, 5, 6, 4, 3, 7, 1, 5, 1, 6) 18
(3, 4, 5, 7, 3, 6, 4, 1, 5, 1, 2, 7, 6, 2) 19
(3, 4, 6, 7, 3, 2, 4, 5, 2, 6, 1, 7, 1, 5) 20
(3, 5, 7, 2, 3, 6, 2, 5, 4, 1, 7, 1, 6, 4) 21
(3, 5, 7, 4, 3, 6, 2, 5, 4, 2, 7, 1, 6, 1) 22
(3, 6, 7, 1, 3, 1, 4, 5, 6, 2, 7, 4, 2, 5) 23
(3, 7, 4, 6, 3, 2, 5, 4, 2, 7, 6, 1, 5, 1) 24
(4, 1, 6, 1, 7, 4, 3, 5, 2, 6, 3, 2, 7, 5) 25
(4, 1, 7, 1, 6, 4, 2, 5, 3, 2, 7, 6, 3, 5) 26
(4, 5, 6, 7, 1, 4, 1, 5, 3, 6, 2, 7, 3, 2) 27
(4, 6, 1, 7, 1, 4, 3, 5, 6, 2, 3, 7, 2, 5) 28
(4, 6, 1, 7, 1, 4, 5, 2, 6, 3, 2, 7, 5, 3) 29
(4, 6, 3, 5, 7, 4, 3, 2, 6, 5, 2, 1, 7, 1) 30
(5, 1, 7, 1, 6, 2, 5, 4, 2, 3, 7, 6, 4, 3) 31
(5, 2, 4, 6, 2, 7, 5, 4, 3, 1, 6, 1, 3, 7) 32
(5, 2, 4, 7, 2, 6, 5, 4, 1, 3, 1, 7, 6, 3) 33
(5, 2, 6, 4, 2, 7, 5, 3, 4, 6, 1, 3, 1, 7) 34
(5, 2, 7, 3, 2, 6, 5, 3, 4, 1, 7, 1, 6, 4) 35
(5, 3, 6, 4, 7, 3, 5, 2, 4, 6, 2, 1, 7, 1) 36
(5, 3, 6, 7, 2, 3, 5, 2, 4, 6, 1, 7, 1, 4) 37
(5, 6, 1, 7, 1, 3, 5, 4, 6, 3, 2, 7, 4, 2) 38
(5, 7, 1, 4, 1, 6, 5, 3, 4, 7, 2, 3, 6, 2) 39
(5, 7, 2, 3, 6, 2, 5, 3, 4, 7, 1, 6, 1, 4) 40
(5, 7, 2, 6, 3, 2, 5, 4, 3, 7, 6, 1, 4, 1) 41
(5, 7, 4, 1, 6, 1, 5, 4, 3, 7, 2, 6, 3, 2) 42
(6, 1, 5, 1, 7, 3, 4, 6, 5, 3, 2, 4, 7, 2) 43
(6, 2, 7, 4, 2, 3, 5, 6, 4, 3, 7, 1, 5, 1) 44
(7, 1, 3, 1, 6, 4, 3, 5, 7, 2, 4, 6, 2, 5) 45
(7, 1, 4, 1, 6, 3, 5, 4, 7, 3, 2, 6, 5, 2) 46
(7, 2, 4, 5, 2, 6, 3, 4, 7, 5, 3, 1, 6, 1) 47
(7, 2, 4, 6, 2, 3, 5, 4, 7, 3, 6, 1, 5, 1) 48
(7, 2, 6, 3, 2, 4, 5, 3, 7, 6, 4, 1, 5, 1) 49
(7, 3, 1, 6, 1, 3, 4, 5, 7, 2, 6, 4, 2, 5) 50
(7, 3, 6, 2, 5, 3, 2, 4, 7, 6, 5, 1, 4, 1) 51
(7, 4, 1, 5, 1, 6, 4, 3, 7, 5, 2, 3, 6, 2) 52
26
That takes about 0.2 seconds on my old 2GHz machine.
Conventionally, 2 Langford sequences are considered to be the same if one is a reversal of the other. One way to deal with that is to compare a sequence with its reversed version, and only print it if its less than the reversed version. You can change the above code to do that by commenting out
if seq:
in the langford function and un-commenting the following line:
#if seq and seq < seq[::-1]:
The above code is an improvement, but we can do better. My next solution uses a technique known as recursive backtracking. This technique can be elegantly implemented in Python through the use of a recursive generator function.
We start with a sequence of zeros. Starting with the highest number, we try to place a pair of numbers into each legal pair of slots, and if we're successful we recurse to place the next pair of numbers, if there are no numbers left to place we've found a solution and we can yield it.
import sys
def langford(n, seq):
''' Generate Langford sequences by recursive backtracking '''
# The next n
n1 = n - 1
# Test each valid pair of positions for this n
for i in range(0, len(seq) - n - 1):
j = i + n + 1
if not (seq[i] or seq[j]):
# Insert this n into the sequence
seq[i] = seq[j] = n
if n1:
# Recurse to add the next n
yield from langford(n1, seq)
else:
# Nothing left to insert
yield seq
# Remove this n from the sequence in preparation
# for trying the next position
seq[i] = seq[j] = 0
def main():
n = int(sys.argv[1]) if len(sys.argv) > 1 else 4
for i, t in enumerate(langford(n, [0] * 2 * n), 1):
print(t, i)
if i % 1000 == 0:
print(' ', i, end='\r', flush=True)
print('\n', i // 2)
if __name__ == '__main__':
main()
The if i % 1000 == 0: stuff lets you see the progress when n is large. This is handy if you comment-out the print(t, i) line.
This code can generate the 35584 = 2*17792 sequences for n=11 in under 25 seconds on my machine.
If you want to collect the sequences yielded by langford into a list, rather than just printing them, you can do it like this:
n = 7
results = list(langford(n, [0] * 2 * n))
However, if you want to do that you must make a slight change to the langford function. Where it says
yield seq
change it to
yield seq[:]
so that it yields a copy of seq rather than the original seq list.
If you just want to get the count of sequences (not counting reversals), you can do this:
n = 7
count = sum(1 for _ in langford(n, [0] * 2 * n)) // 2
print(count)
That will work ok with yield seq.
The above code will be slow for larger values of n. There are faster techniques for calculating the number of Langford sequences, using fairly advanced mathematics, but there's no known simple formula. The OEIS has a list of Langford sequences numbers at A014552.

You are complicating things: All that is needed is to generate all permutations, and eliminate those that are not Langford sequences.
1- do not use set(itertools...), itertools already returns unique elements.
2- for each permutation, you must check is it is a Langfort sequence.
3- if not, break and check the next one
4- if it is, check that its inverse has not yet been collated, and save it in a set of unique elements
5- return the resulting unique langfort sequences
This code is fast for n=4, and can find sequences for an arbitrary n; however, the time complexity is massively exponential; past n=6, it will require quite a bit of time to finish.
import itertools
def langfort(n):
seq = [_ for _ in range(1, n+1)] * 2
lang = set()
for s in itertools.permutations(seq):
for elt in seq:
first = s.index(elt)
if s[first+1:].index(elt) == elt:
continue
else:
break
else:
if s[::-1] not in lang:
lang.add(s)
return lang
langfort(4)
output:
{(4, 1, 3, 1, 2, 4, 3, 2)}
Performance:
on a 2011 mac book air:
%timeit langfort(4)
10 loops, best of 3: 53.4 ms per loop
more output:
langfort(5)
set() # there are no langfort(5) sequences
langfort(6)
set() # there are no langfort(6) sequences

Related

How to reshape an array using np tile

I have a array X structured with shape 2, 5 as follows:
0, 6, 7, 9, 1
2, 4, 6, 2, 7
I'd like to reshape it to repeat each row n times as follows (example uses n = 3):
0, 6, 7, 9, 1
0, 6, 7, 9, 1
0, 6, 7, 9, 1
2, 4, 6, 2, 7
2, 4, 6, 2, 7
2, 4, 6, 2, 7
I have tried to use np.tile as follows, but it repeats as shown below:
np.tile(X, (3, 5))
0, 6, 7, 9, 1
2, 4, 6, 2, 7
0, 6, 7, 9, 1
2, 4, 6, 2, 7
0, 6, 7, 9, 1
2, 4, 6, 2, 7
How might i efficiently create the desired output?
If a be the main array:
a = np.array([0, 6, 7, 9, 1, 2, 4, 6, 2, 7])
we can do this by first reshaping to the desired array shape and then use np.repeat as:
b = a.reshape(2, 5)
final = np.repeat(b, 3, axis=0)
It can be done with np.tile too, but it needs unnecessary extra operations, something as below. So, np.repeat will be the better choice.
test = np.tile(b, (3, 1))
final = np.concatenate((test[::2], test[1::2]))
For complex repeats, I'd use np.kron instead:
np.kron(x, np.ones((2, 1), dtype=int))
For something relatively simple,
np.repeat(x, 2, axis=0)

Numpy: Sample group of indices with different values

Given some numpy array a
array([2,2,3,3,2,0,0,0,2,2,3,2,0,1,1,0])
what is the best way to get all groups of n indices with each of them having a different value in a?
Obviously there is no group larger than the number of unique elements in a, here 4.
So for example, one group of size 4 is
array([0,2,5,13])
Consider that a might be quite long, let's say up to 250k.
If the result gets too large, it might also be desirable not to compute all such groups, but only the first k requested.
For inputs as integers, we can have a solution based on this post -
In [41]: sidx = a.argsort() # use kind='mergesort' for first occurences
In [42]: c = np.bincount(a)
In [43]: np.sort(sidx[np.r_[0,(c[c!=0])[:-1].cumsum()]])
Out[43]: array([ 0, 2, 5, 13])
Another closely related to previous method for generic inputs -
In [44]: b = a[sidx]
In [45]: np.sort(sidx[np.r_[True,b[:-1]!=b[1:]]])
Out[45]: array([ 0, 2, 5, 13])
Another with numba for memory-efficiency and hence performance too, to select first indices along those unique groups and also with the additional k arg -
from numba import njit
#njit
def _numba1(a, notfound, out, k):
iterID = 0
for i,e in enumerate(a):
if notfound[e]:
notfound[e] = False
out[iterID] = i
iterID += 1
if iterID>=k:
break
return out
def unique_elems(a, k, maxnum=None):
# feed in max of the input array as maxnum value if known
if maxnum is None:
L = a.max()+1
else:
L = maxnum+1
notfound = np.ones(L, dtype=bool)
out = np.ones(k, dtype=a.dtype)
return _numba1(a, notfound, out, k)
Sample run -
In [16]: np.random.seed(0)
...: a = np.random.randint(0,10,200)
In [17]: a
Out[17]:
array([5, 0, 3, 3, 7, 9, 3, 5, 2, 4, 7, 6, 8, 8, 1, 6, 7, 7, 8, 1, 5, 9,
8, 9, 4, 3, 0, 3, 5, 0, 2, 3, 8, 1, 3, 3, 3, 7, 0, 1, 9, 9, 0, 4,
7, 3, 2, 7, 2, 0, 0, 4, 5, 5, 6, 8, 4, 1, 4, 9, 8, 1, 1, 7, 9, 9,
3, 6, 7, 2, 0, 3, 5, 9, 4, 4, 6, 4, 4, 3, 4, 4, 8, 4, 3, 7, 5, 5,
0, 1, 5, 9, 3, 0, 5, 0, 1, 2, 4, 2, 0, 3, 2, 0, 7, 5, 9, 0, 2, 7,
2, 9, 2, 3, 3, 2, 3, 4, 1, 2, 9, 1, 4, 6, 8, 2, 3, 0, 0, 6, 0, 6,
3, 3, 8, 8, 8, 2, 3, 2, 0, 8, 8, 3, 8, 2, 8, 4, 3, 0, 4, 3, 6, 9,
8, 0, 8, 5, 9, 0, 9, 6, 5, 3, 1, 8, 0, 4, 9, 6, 5, 7, 8, 8, 9, 2,
8, 6, 6, 9, 1, 6, 8, 8, 3, 2, 3, 6, 3, 6, 5, 7, 0, 8, 4, 6, 5, 8,
2, 3])
In [19]: unique_elems(a, k=6)
Out[19]: array([0, 1, 2, 4, 5, 8])
Use Numpy.unique for this job. There are several other options, one can for instance return the number of times each unique item appears in a.
import numpy as np
# Sample data
a = np.array([2,2,3,3,2,0,0,0,2,2,3,2,0,1,1,0])
# The unique values are in 'u'
# The indices of the first occurence of the unique values are in 'indices'
u, indices = np.unique(a, return_index=True)

In python, how should i Weighted-random coding?

I want to know the method that weighted-random in Python.
1:10%, 2:10%, 3:10%, 4:50%, 5:20%
Then I choose the random number without duplication. How should I code? Generally, we will code below that:
Python
from random import *
sample(range(1,6),1)
You should have a look at random.choices (https://docs.python.org/3/library/random.html#random.choices), which allows you to define a weighting, if you are using python 3.6 ore newer
Example:
import random
choices = [1,2,3,4,5]
random.choices(choices, weights=[10,10,10,50,20], k=20)
Output:
[3, 5, 2, 4, 4, 4, 5, 3, 5, 4, 5, 4, 5, 4, 2, 4, 5, 2, 4, 4]
Try this:
from numpy.random import choice
list_of_candidates = [1,2,5,4,12]
number_of_items_to_pick = 120
p = [0.1, 0, 0.3, 0.6, 0]
choice(list_of_candidates, number_of_items_to_pick, p=probability_distribution)
If you really wanted a sample-version you can prepare the range accordingly:
nums = [1,2,3,4,5]
w = [10,10,10,50,20] # total of 100%
d = [x for y in ( [n]*i for n,i in zip(nums,w)) for x in y]
a_sample = random.sample(d,k=5)
print(a_sample)
print(d)
Output:
# 5 samples
[4, 2, 3, 1, 4]
# the whole sample input:
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5]
If you just need 1 number you can use random.choices - it is limited to 1 number because its drawing with replacement.
import random
from collections import Counter
# draw and count 10k to show distribution works
print(Counter( random.choices([1,2,3,4,5], weights=[10,10,10,50,20], k=10000)).most_common())
Output:
[(4, 5019), (5, 2073), (3, 1031), (1, 978), (2, 899)]
Using a "sample" w/o replacement and "weighted" is (for me) weired - because you would change the weighting for each successive number because you removed available numbers from the range (thats by feel - my guess would be the math behind tells me its not so).

Append multiple items to a list based on variable?

I searched a lot for an answer, but I can only find answers on just adding one item multiple times. Or just multiplying two lists.
#ratio of bar
ratio_variant1 = 0.1
ratio_variant2 = 0.3
ratio_variant3 = 0.4
ratio_variant4 = 0.2
ratio = []
ratio.extend([ratio_variant1, ratio_variant2, ratio_variant3, ratio_variant4])
#ratio to integer
ratiointeger = [x*100 for x in ratio]
#size of bar
size_variant1 = 2
size_variant2 = 3
size_variant3 = 4
size_variant4 = 6
size = []
size.extend([size_variant1, size_variant2, size_variant3, size_variant4])
bucket = size * ratiointeger
I'm afraid that my way of creating the ratio and size list are not really pythonic, but the main problem is that I'm not able to make a list/bucket with:
10 items of 2
30 items of 3
40 items of 4
20 items of 6
It seems to me you need:
bucket = [x for l in ([s]*r for s, r in zip(size, ratiointeger)) for x in l]
This builds a list of lists, containing the repetitions you want:
>>> [[s]*r for s, r in zip(size, ratiointeger)]
[[2, 2, 2, 2, 2, 2, 2, 2, 2, 2], [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4], [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6]]
Then flattens them using the method from this post.
Try this:
bucket = [[s] * int(r) for (s, r) in zip(size, ratiointeger)]

how to get all combinations between 1 2 3 4 5 6 7 8 using each number once python

first of all I would like to inform you I don't have any knowledge about python. I tryed to figure the basics by searching trough some beginner tutorials but I can't even wrap my head around those.
As I have a very specific thing I'd like to generate I hope theres someone over here who could help me out.
I need all the possible combinations between the numbers 1,2,3,4,5,6,7,8
the first and the last number always have to be 1
and the numbers can't be used twice.
for example:
1 2 3 4 5 6 7 8 1 -
1 2 3 4 5 6 8 7 1 -
1 2 3 4 5 7 8 6 1 -
1 2 3 4 5 7 6 8 1 -
1 2 3 4 5 8 6 7 1 -
1 2 3 4 5 8 7 6 1 -
and so on :)
from itertools import permutations
for a in permutations(range(2, 9)):
a = (1,) + a + (1,)
print(a)
For learning more try this: how-to-generate-all-permutations-of-a-list-in-python
You want the permutations of the numbers 2 through to 8, and then just add the 1's:
from itertools import permutations
for combo in permutations(range(2, 9)):
combo = (1,) + combo + (1,)
print(combo)
Demo:
>>> from itertools import permutations
>>> for combo in permutations(range(2, 9)):
... combo = (1,) + combo + (1,)
... print(combo)
...
(1, 2, 3, 4, 5, 6, 7, 8, 1)
(1, 2, 3, 4, 5, 6, 8, 7, 1)
(1, 2, 3, 4, 5, 7, 6, 8, 1)
(1, 2, 3, 4, 5, 7, 8, 6, 1)
(1, 2, 3, 4, 5, 8, 6, 7, 1)
(1, 2, 3, 4, 5, 8, 7, 6, 1)
(1, 2, 3, 4, 6, 5, 7, 8, 1)
(1, 2, 3, 4, 6, 5, 8, 7, 1)
(1, 2, 3, 4, 6, 7, 5, 8, 1)
#
# ... many lines omitted
#
(1, 8, 7, 6, 4, 3, 2, 5, 1)
(1, 8, 7, 6, 4, 3, 5, 2, 1)
(1, 8, 7, 6, 4, 5, 2, 3, 1)
(1, 8, 7, 6, 4, 5, 3, 2, 1)
(1, 8, 7, 6, 5, 2, 3, 4, 1)
(1, 8, 7, 6, 5, 2, 4, 3, 1)
(1, 8, 7, 6, 5, 3, 2, 4, 1)
(1, 8, 7, 6, 5, 3, 4, 2, 1)
(1, 8, 7, 6, 5, 4, 2, 3, 1)
(1, 8, 7, 6, 5, 4, 3, 2, 1)

Categories