Как найти период массива

Господа, задача слушающая.
Мне даже немного стыдно, но увы – я не могу придумать решение.
Периодом массива a=(a1, a2,…, an) называется такой его кратчайший подмассив b=(a1, a2,…, ak), что k делит n, а будучи записанным n/k раз подряд он в точности окажется равным a.

Например, у массива (1, 2, 1, 1, 2, 1, 1, 2, 1) длина периода равна 3, а сам период — (1, 2, 1),
задача по заданному массиву найти длину его периода.
Подскажите хотя бы алгоритм, как в заданном ниже вручную массиве, можно выявить последовательность и ее длину.

Код:

import java.util.Scanner;

public class Solution {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);      
        int n = scan.nextInt();
        int[] a = new int[n];
        for (int i = 0; i < n; i++) {
            a = scan.nextInt();        
        }
}
}

Есть идея реализовать так :
Возьмем вот массив 121121121, В массиве ищем след. единицу, после 1ой и проверяем, идет ли после нее второй элемент ( =2). Если нет, то ищем следующую единицу и проверяем еще раз. Так же для проверки берем и 3ий элемент. Но проблема в том, что элементов последовательности, может быть в данном случае 1,2,3,6. Получается каждый раз искать число целых делителей длинны массива?
В общем я путаюсь и реализовать не получается)

Using the undocumented "Periodic" padding as the third argument of PadRight:

ClearAll[fpF, fpF2]
fpF = Block[{i = 1}, While[i < Length@# && 
      PadRight[#[[;; i]], Length@#, "Periodic"] != #, i++]; i] &;
fpF2 = Block[{i = 1}, While[i < Length@# && 
      PadRight[#[[;; i]], Length@#, "Periodic"] != #, i++]; {i, #[[;; i]]}] &;

Examples: Using tc from @ubpdqn’s answer:

tc = {{19, 6, 19, 6, 19, 6, 19, 6, 19, 6, 19, 6}, 
      {73, 7, 4, 73, 7, 4, 73, 7, 4, 73, 7, 4, 73, 7},
      {73, 7, 4, 7, 2, 6, 7, 2, 7, 73, 9, 17, 7, 7}, 
      {6, 3, 6, 3, 3, 6, 3, 6, 3, 3, 6, 3, 6, 3, 3, 6, 3, 6, 3, 3},
      {6, 1, 6, 2, 6, 3, 6, 1, 6, 2, 6, 3, 6, 1, 6, 2},
      {1, 1, 1}, 
      {1, 2, 1, 2, 1}};

fpF /@ tc
(* {2, 3, 14, 5, 6, 1, 2} *)

{#, ## & @@ fpF2@#} & /@ tc // Grid[#, Dividers -> All] & 
(* or {#,fpF @ #, #[[;;fpF @ #]]}&/@tc //Grid *)

enter image description here

If a complete periodic pattern were sought, we could search for periods less than or equal to half the Length of the input list:

ClearAll[fpFa, fpFb]
fpFa = Block[{i = 1, n = Length@#},  While[i < 1 + n/2 && 
        PadRight[#[[;; i]], n, "Periodic"] != #, i++]; i = If[i < 1 + n/2, i, n]] &;
fpFb = Block[{i = 1, n = Length@#}, While[i < 1 + n/2 && 
        PadRight[#[[;; i]], n, "Periodic"] != #, i++]; i = If[i < 1 + n/2, i, n]; 
        {i, #[[;; i]]}] &;

0 / 0 / 0

Регистрация: 02.01.2014

Сообщений: 11

1

Найти период сгенерированных определенным образом чисел

06.03.2014, 21:07. Показов 10393. Ответов 19


Студворк — интернет-сервис помощи студентам

Допустим я генерирую числа определенным способом(Митчелла и Мура, Линейный конгруэнтный метод и т.д).
Эти числа со временем начинают повторяться. Как найти период повтора?
Например:
0654847856(Новый период)4847856(Новый период)4847856(Новый период)48….



0



294 / 265 / 48

Регистрация: 09.04.2013

Сообщений: 1,037

07.03.2014, 22:14

2

Примерный алгоритм:
-записываем последовательность чисел в буфер (можно массив)
-на каждом шаге ищем минимальную последовательность чисел повторяющихся несколько раз (тут нужно либо определиться после повторения скольки раз будем считать что нашли период, либо циклически перезаписывать буфер – тогда уже чтобы все числа в буфере входили в повторы)

Саму проверку на повторы периода можно целиком позаимствовать из алгоритмов проверки равенства строк.



1



22 / 15 / 7

Регистрация: 27.10.2013

Сообщений: 95

08.03.2014, 23:10

3

Нужно вывести все числа, кроме тех, что относятся к повторяющимся? Каков критерий определения повторяющихся чисел? Могут по 2е цифры часто повторятся, по 3 и т.д. Повторяющиеся числа это самая длинная, из повторяющихся последовательностей чисел? Длина периода всегда одна и та же? Если немного уточните условия задачи, то выложу код на c#.



1



0 / 0 / 0

Регистрация: 02.01.2014

Сообщений: 11

10.03.2014, 22:48

 [ТС]

4

Нужно найти период последовательности псевдослучайных чисел.
Допустим набор чисел сгенерирован линейным конгруэнтным методом.
Xn=(a*Xn-1+c) mod(m);
Я так понимаю сначала идет предпериод, а за ним собственно и период.
Вот нужно найти период.
Несколько моментов:
-Как определить длину предпериода?
-Как без запоминания всей последовательности найти период? – она ведь может быть гигантской



0



294 / 265 / 48

Регистрация: 09.04.2013

Сообщений: 1,037

11.03.2014, 00:28

5

Цитата
Сообщение от Vadya_PB
Посмотреть сообщение

Xn=(a*Xn-1+c) mod(m);

Цитата
Сообщение от Vadya_PB
Посмотреть сообщение

-Как определить длину предпериода?

a,c,m константы ? тогда из-за рекурсивности формулы получаем что для определения предпериода достаточно найти сам период, потом запустить генератор с начала и ждать любое число из периода, когда нашли – сделать шаг назад. Полученный порядковый номер числа и будет длинной пред периода.
Рекурсивность с зависимостью от одного предыдущего числа нам гарантирует, что если мы встретим число хотя бы еще один раз – значить мы нашли сам период.

Цитата
Сообщение от Vadya_PB
Посмотреть сообщение

-Как без запоминания всей последовательности найти период? – она ведь может быть гигантской

А ведь и не надо её всю запоминать, нужно только найти период.
Вот когда ты привел формулу генератора то я могу сказать что для поиска периода достаточно найти в последовательности всего лишь второе появление числа в последовательности, что не должно требовать так уж много памяти (ну а по другому вряд ли можно сделать).

Кстати, а не может ли быть математического описания когда появятся повторения ?

Добавлено через 7 минут
Как бы само собой напрашивается что в сумме количество чисел в периоде и предпериоде должно быть не больше чем m



2



831 / 639 / 101

Регистрация: 20.08.2013

Сообщений: 2,524

12.03.2014, 18:25

6

Цитата
Сообщение от Vadya_PB
Посмотреть сообщение

Допустим набор чисел сгенерирован линейным конгруэнтным методом.
Xn=(a*Xn-1+c) mod(m);

Тогда надо генерировать последовательность двумя счётчиками – один с единичным шагом, второй – с двойным. Когда получится одинаковое число, запомнить его и продолить генерировать до тех пор пока оно не повторится. Это позволяет найти период. Чтобы найти предпериод, начинаем генерировать сначала, но одним из счётчиков пролистываем начало, по длинне равное периоду, после чего считаем обоими счётчиками синхронно. Когда числа совпадут, предпериод найден.



1



Potanin

22 / 15 / 7

Регистрация: 27.10.2013

Сообщений: 95

12.03.2014, 19:49

7

Не уверен что правильно понял задания, но, если понял правильно, то этот алгоритм создает одну и ту же постоянно повторяющуюся последовательность чисел. То есть например 123123123123, тогда здесь периодом будет 123.
Внизу алгоритм, который находит такие периоды. +10 в нем нужно для того, чтобы убедиться, что действительно был найдет весь период, а не лишь его часть. Чем больше это число, тем выше вероятность того, что найден весь период.
Алгоритм не перебирает все числа а как бы прыгает по строке из чисел, каждый раз начиная с предыдущего места где было найдено соответствие между начальным числом и затем найденным числом в строке.
Чтобы найти последовательность у указанного вами метода нужно присвоить переменной “а” не метод Posled, а метод LKM с необходимыми параметрами.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Найти_период_повтора
{
    class Program
    {
        static void Main(string[] args)
        {
            //string posled = LKM(1, 2, 3, 4, 100000);
            string a = Posled("52878932764237082345/52878932764237082345/52878932764237082345/52878932764237082345/52878932764237082345/");
            Console.WriteLine(" Период = " + a);
            Console.WriteLine("Длина периода составила " + a.Length.ToString()); 
            Console.WriteLine("Программа успешно завершилась");
            Console.Read();
            
        }
        public static string LKM(int x, int a, int c, int m, int dlina)
        {
            string posled = "";
            for (int i = 0; i <= dlina; i++)
            {
                posled += x.ToString();
                x = (a * x + c) * m;
            }
            return posled;
        }
        public static string Posled(string posled)
        {
           int a = 0; int b = 1;
           back: a++; b++;
                while (posled.Substring(0,a) != posled.Substring(b, a))
                {
                    Console.WriteLine(a.ToString() + " " + b.ToString());
                    b++;
                }
                if (posled.Substring(0, a + 10) != posled.Substring(b, a + 10)) goto back;
                else return posled.Substring(0, b);
        }
    }
}



1



831 / 639 / 101

Регистрация: 20.08.2013

Сообщений: 2,524

14.03.2014, 11:46

8

Potanin, просто ужас…



0



294 / 265 / 48

Регистрация: 09.04.2013

Сообщений: 1,037

14.03.2014, 12:50

9

Цитата
Сообщение от Potanin
Посмотреть сообщение

x = (a * x + c) * m; }

Не умножить, а найти модуль от деления на m

Не по теме:

Про поиск в строке промолчу, ибо думаю что я недостаточно знаю этот ЯП, вдруг что-то не то подумаю… Но спинным мозгом чувствую что что-то не то



2



22 / 15 / 7

Регистрация: 27.10.2013

Сообщений: 95

14.03.2014, 16:02

10

Так тоже работает, хоть и поиздевался над алгоритмом)))) Если просто удвоенный счетчик использовать то могут быть проблемы с, например 11311341131134. Первые три цифры и последующие три может ошибочно посчитать за период. Если посмотреть удлиненную версию (для этого берется +10 но тут можно +1) то такой ошибки можно избежать.



1



831 / 639 / 101

Регистрация: 20.08.2013

Сообщений: 2,524

14.03.2014, 18:11

11

Цитата
Сообщение от Potanin
Посмотреть сообщение

Так тоже работает, хоть и поиздевался над алгоритмом))))

Я не про сдвиг 10, а про саму реализацию. Использование строк подобным образом – это извращение.
К тому же, в изначальном вопросе было сказано, что нельзя сохранять последовательность.

Цитата
Сообщение от Potanin
Посмотреть сообщение

Если просто удвоенный счетчик использовать то могут быть проблемы с, например 11311341131134.

Я ответил на конкретный вопрос. Для всех последовательностей, где каждый последующий элемент определяется ровно одним предыдущим, этот способ верный, причём есть доказательство этого факта.
Про другие методы генерации я не смотрел зависимости, поэтому в моём ответе явно процитирована формула генерации, для которой всё верно.



1



294 / 265 / 48

Регистрация: 09.04.2013

Сообщений: 1,037

16.03.2014, 02:21

12

Цитата
Сообщение от Potanin
Посмотреть сообщение

) Если просто удвоенный счетчик использовать то могут быть проблемы с, например 11311341131134

У тебя изначально задание сохранять последовательность в одну и ту же строку без символов-разделителей или ты так сам себе выбрал? В обоих случаях это плохая задумка, тут нужно либо массив чисел, либо добавлять разделители между строковым представлением чисел.



0



22 / 15 / 7

Регистрация: 27.10.2013

Сообщений: 95

16.03.2014, 08:56

13

Последнее замечание не совсем понятно. В итоге выводятся сам период и его длина, а не вся строка. Или суть в том, что необходимо сохранить не последовательность цифр в str, а последовательность чисел, формирующих период? Тогда тут возникает проблема. Допустим у меня началась генерация и мы получаем: 1 2 3 4 5 (затем) 12 34 51 и какую тут последовательность чисел приписать к периоду во втором случае?



0



831 / 639 / 101

Регистрация: 20.08.2013

Сообщений: 2,524

16.03.2014, 12:40

14

Не надо вообще использовать строки.



0



294 / 265 / 48

Регистрация: 09.04.2013

Сообщений: 1,037

16.03.2014, 18:55

15

Цитата
Сообщение от Potanin
Посмотреть сообщение

Последнее замечание не совсем понятно.

Нужно работать с числами чтобы правильно находить период, а не цифрами в числах, поэтому и получается у вас, что “могут быть проблемы с, например 11311341131134”. Желательно отказаться от строк вообще и перейти на целочисленные массивы.



0



22 / 15 / 7

Регистрация: 27.10.2013

Сообщений: 95

16.03.2014, 19:18

16

Все еще не ясно как, если работать не с цифрами, а с числами, получать период целиком. В любом же случае, если, как я указывал выше, есть 1 2 3 4 5 и 12 34 51, чтобы во втором случае вычислить период необходимо от последнего числа отделить последнюю цифру. Или же соответствующие алгоритмы генерации чисел устроены таким образом, что данной проблемы не возникает?



0



831 / 639 / 101

Регистрация: 20.08.2013

Сообщений: 2,524

16.03.2014, 23:04

17

Цитата
Сообщение от Potanin
Посмотреть сообщение

Или же соответствующие алгоритмы генерации чисел устроены таким образом, что данной проблемы не возникает?

Нужно найти период в последовательности чисел, а не цифр.
7 148 1 2 3 4 12 34 1 2 3 4 12 34 …
Здесь период 1 2 3 4 12 34, его длина 6
предпериод 7 148, его длина 2



1



22 / 15 / 7

Регистрация: 27.10.2013

Сообщений: 95

17.03.2014, 09:58

18

Но ведь по линейному конгруэнтному методу каждое последующее число больше предыдущего, в следствие чего повторение последовательности именно из чисел невозможно.



0



Qwertiy

831 / 639 / 101

Регистрация: 20.08.2013

Сообщений: 2,524

17.03.2014, 13:26

19

Цитата
Сообщение от Potanin
Посмотреть сообщение

Но ведь по линейному конгруэнтному методу каждое последующее число больше предыдущего

Неправда. Там же по модулю.

Javascript
1
2
3
4
5
6
7
8
9
10
11
function gen(a, x0, c, m, n) {
  var res = new Array(n), q;
 
  res[0] = x0;
  for(q=1; q<n; ++q)
    res[q] = (a*res[q-1]+c) % m;
 
  return res;
}
 
gen(5, 11, 2, 15, 16) // [11, 12, 2, 12, 2, 12, 2, 12, 2, 12, 2, 12, 2, 12, 2, 12]



1



Potanin

22 / 15 / 7

Регистрация: 27.10.2013

Сообщений: 95

17.03.2014, 13:54

20

Цитата
Сообщение от Qwertiy
Посмотреть сообщение

Неправда. Там же по модулю.

Javascript
1
2
3
4
5
6
7
8
9
10
11
function gen(a, x0, c, m, n) {
  var res = new Array(n), q;
 
  res[0] = x0;
  for(q=1; q<n; ++q)
    res[q] = (a*res[q-1]+c) % m;
 
  return res;
}
 
gen(5, 11, 2, 15, 16) // [11, 12, 2, 12, 2, 12, 2, 12, 2, 12, 2, 12, 2, 12, 2, 12]

Не о том модуле подумал, разобрался, спасибо



0



IT_Exp

Эксперт

87844 / 49110 / 22898

Регистрация: 17.06.2006

Сообщений: 92,604

17.03.2014, 13:54

20

Связь периода и бордера

Теорема:

Если у строки длины есть бордер длины , то у нее также имеется период длины .

Доказательство:

Пусть дана строка .

Напишем формально определение бордера длины строки :

Сделаем замену :

Получили определение периода длины . Но , значит у строки есть период длины .

Свойства периода

Теорема (о кратном периоде):

Если у строки есть период длины , то у нее имеется также период длины , где .

Доказательство:

Пусть длина строки равна , сама строка — .

Доказательство будем вести индукцией по числу .

  • База
    Для утверждение очевидно.
  • Переход
    Пусть верно для . Докажем то же для .
    Из определения периода имеем

    а из предположения индукции

    С учётом этого получаем, что

    следовательно

    Значит у строки есть период длины .

Утверждение доказано.

Перед доказательством следующей теоремы проверим пару интуитивно понятных утверждений.

Лемма (1):

Пусть строка имеет периоды и , причём . Тогда суффикс и префикс длины имеют период .

Доказательство:

Покажем истинность утверждения про префикс; с суффиксом доказательство аналогичное.

Требуется показать:

Исходя из того, что имеет период , выполнено

Также имеет период и из ограничений на верно , поэтому

Лемма (2):

Пусть строка имеет период , и существует подстрока такая, что и имеет период , где . Тогда имеет период .

Доказательство:

Пусть , где .

Требуется показать: .

Зафиксируем и . Заметим, что поскольку , отрезок содержит по меньшей мере целых чисел, так что найдутся .

С учётом можем написать [1].

Помимо того , а в таком случае верно и .

Теперь воспользуемся следующим фактом: если строка имеет период , то (действительно, без ограничения общности можем сказать, что , и исходя из этого выстроить цепочку равенств ).

В виду того, что имеет период , имеют место равенства и . Кроме того имеет период , потому верно . Тогда и .

Теорема (Фин и Вильф):

Если у строки есть периоды и , где , то также является периодом этой строки.

Доказательство:

Обозначим . Доказательство будем вести индукцией по .

В случае видим что , что соответствует базе, в то время как при выполнено , так что .

  • База
    Истинность утверждения следует из .
  • Переход
    В силу того, что , без ограничения общности будем считать (вообще говоря, исходя из свойств НОД можно дать более строгую оценку: , чем мы позже воспользуемся).
    Пусть , где .
    По лемме 1 имеет период , также имеет период как подстрока . Теперь рассмотрим длину :
    .
    Ещё заметим, что для периодов будет меньшее , нежели чем для , поскольку . А тогда по предположению индукции заключаем: имеет период . Учитывая , можем сказать что имеет период .
    Как уже упоминалось, , поэтому , в следствие чего по лемме 2 имеет период .

См. также

  • Основные определения, связанные со строками

Примечания

  1. Свойство сравнений (№8)

Источники информации

  • Wikipedia — Substring
  • Lothaire M. Algebraic Combinatorics on Words — Cambridge University Press, 2002. — с. 272. — ISBN 0-521-81220-8

Вот пример ответа на ваш вопрос с помощю php

$user_id = 57;
$array = [
    ['user_id' => 57, 'phone_code' => 104, 'date' => '2017-06-07 07:49:52'],
    ['user_id' => 43, 'phone_code' => 104, 'date' => '2017-06-07 08:57:41'],
    ['user_id' => 72, 'phone_code' => 104, 'date' => '2017-06-07 09:49:38'],
    ['user_id' => 57, 'phone_code' => 104, 'date' => '2017-06-07 11:08:34'],
    ['user_id' => 25, 'phone_code' => 104, 'date' => '2017-06-07 12:49:38'],
];

for($i=0;$i<count($array);$i++){

    if($array[$i]['user_id'] == $user_id){
        $phone_code = $array[$i]['phone_code'];
        break;
    }

}
$user_array = [];
for($i=0;$i<count($array);$i++){

    if($array[$i]['phone_code'] == $phone_code && $array[$i]['user_id'] == $user_id && $array[$i+1]['user_id'] != $user_id){
        $user_array[$i]['date'] = $array[$i]['date'].' - '.$array[$i+1]['date'];
    }

}
print_r($user_array);

Но это не вариант, если у вас строки в таблице достигнут тысяч.

Для этого надоSQL запросом вывести только нужные строки.

Вот если тебе не составит труда создать вот такую таблицу то у тебя не будет проблем с выводом нужных строк.

CREATE TABLE `user_log` (
  `id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL,
  `prev_user_id` int(11) NOT NULL,
  `phone_code` mediumint(9) DEFAULT NULL,
  `date` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


INSERT INTO `user_log` (`id`, `user_id`, `prev_user_id`, `phone_code`, `date`) VALUES
(1853, 57, 52, 104, '2017-06-07 07:49:52'),
(1863, 43, 57, 104, '2017-06-07 08:57:41'),
(1866, 72, 43, 104, '2017-06-07 09:49:38'),
(1867, 57, 72, 104, '2017-06-07 11:08:34'),
(1868, 25, 57, 104, '2017-06-07 12:49:38');

И тут уже совсем простой запрос даст тебе то что нужно.

SELECT * FROM `user_log` WHERE `user_id` = 57 OR `prev_user_id` = 57;

И на последок для того что бы на больших количествах строк у тебя запрос не медлил можешь поставить на колонках user_id и prev_user_idиндексы.

Добавить комментарий