Есть задача: по кругу стоят от 1 до N человек. Идя по кругу надо вычеркивать каждого второго, пока не останется один и, соответственно, вывести его в консоль. Программа должна работать с классами List и LinkedList (обращаться напрямую по индексу к элементу нельзя).
Метод для удаления должен быть общий для List и LinkedList.
В метод я передаю IEnumerable<T> ie
, но у него нет метода Remove.
Пытался создать объект ICollection<T>
- кидает ошибку.
Как грамотно реализовать метод, чтобы работал и для List<T>
, и для LinkedList<T>
?
public static void RemoveEachSecondItem<T>(IEnumerable<T> ie)
{
ICollection<T> ic = new ICollection<T>(ie);
while (ie.Count() > 1)
{
bool isOdd = false;//четный элемент
foreach (var item in ic)
{
if (isOdd)
{
ic.Remove(item);
}
}
}
}
"Честный" вариант решения
Алгоритм, по идее, прост:
IEnumerable
) элементовРеализация может получиться сложнее или проще... Пример:
Узел списка:
private class MyNode<T>
{
public readonly T item;
public MyNode<T> next;
public MyNode(T item)
{
this.item = item;
}
}
Построение списка по перечислению элементов:
previousNode
как раз указывает на него) в качестве следующего первый_
private static MyNode<T> CreateCircularList<T>(IEnumerable<T> ie)
{
MyNode<T> firstNode = null;
MyNode<T> previousNode = null;
foreach (var item in ie)
{
var newNode = new MyNode<T>(item);
if (firstNode == null)
{
firstNode = newNode;
}
else
{
previousNode.next = newNode;
}
previousNode = newNode;
}
previousNode.next = firstNode;
return firstNode;
}
Непосредственно само удаление элементов:
isOdd
), отвечающий за удаление каждого второго узла (== true
- удалять, == false
- нет)_
public static T RemoveEachSecondItem<T>(IEnumerable<T> ie)
{
var elementsCount = ie.Count();
if (elementsCount == 0)
throw new Exception("Empty ie");
var firstNode = CreateCircularList(ie);
var isOdd = false;
var currentNode = firstNode;
MyNode<T> previousNode = null;
while (elementsCount > 1)
{
if (isOdd)
{
previousNode.next = currentNode.next;
elementsCount--;
}
else
{
previousNode = currentNode;
}
currentNode = currentNode.next;
isOdd = !isOdd;
}
return currentNode.item;
}
"Нечестный" вариант решения
Пример:
public static T RemoveEachSecondItemShort<T>(IEnumerable<T> ie)
{
var elementsCount = ie.Count();
var powOfTwo = 1;
while (powOfTwo <= elementsCount)
{
powOfTwo <<= 1;
}
powOfTwo >>= 1;
var requiredElementIndex = (elementsCount - powOfTwo) * 2;
var index = 0;
foreach (var element in ie)
{
if (index == requiredElementIndex)
return element;
index++;
}
throw new Exception("Empty ie");
}
Сейчас он: isOdd == false постоянно. Когда мы в foreach делаем if(isOdd == true), это никогда не произойдет
Как можно сделать: Сначала он true(при инициализация. В начале цикла он меняется на противоположный(получается первый элемент нечетный, а второй четный. И таким образом происходит переключение).
while (ie.Count() > 1)
что вы хотите получить от этого куска кода? ie.Count() будет возвращать одно и то же, ибо когда вы делаете это ICollection<T> ic = new ICollection<T>(ie);
вы получаете копию, а не работаете с той же коллекцией
В итоге когда закончится цикл foreach, вы выйдете в цикл while иии... он будет крутится вечно).
Если я правильно понял идею, вам нужно заменить while (ie.Count() > 1)
на while (ic.Count() > 1)
ICollection<T> ic = new ICollection<T>(ie);
нельзя создавать объект интерфейса. Ибо интерфейс - лишь декларация, описание того как можно работать с чем то. Он не имеет ни строчки реализации и не может. Вы можете создать(инстанцировать) только объект определенного класса. Например ICollection<T> ic = new List<T>(ie);
тут мы говорим, что я создаю объект класс List<T>
, но работать с ним буду через интерфейс ICollection<T>
.
По сути ваша задача — "Считалка Иосифа Флавия"
И если у нас на входе N элементов, то нужно найти такое максимальное целое A, чтобы N = 2A+L, где L ⩾ 0. Тогда результатом будет элемент под индексом 2L (индексы с 0).
Поскольку, по условию на входе могут быть List<T>
или LinkedList<T>
, которые реализуют ICollection<T>
, можно написать такое простое решение:
T FlaviusRoulette<T>(ICollection<T> source)
{
int c = source.Count, n = 0;
while (1 << n <= c) ++n;
return source.ElementAt(2 * c - (1 << n));
}
Если же вы хотите, чтобы метод работал для любых IEnumerable<T>
— выхода нет, придется закешировать все элементы, т. к. любой может оказаться ответом:
T FlaviusRoulette<T>(IEnumerable<T> source)
{
var col = source as ICollection<T> ?? source.ToList();
int c = col.Count, n = 0;
while (1 << n <= c) ++n;
return col.ElementAt(2 * c - (1 << n));
}
Проверка:
Console.WriteLine(FlaviusRoulette(Enumerable.Range(1, 41))); // 19
Проверку source == null
и source.Count == 0
оставляю на ТС.
Можно пожертвовать памятью и получить быстрое и довольно простое решение:
T RemoveEachSecondItem<T>(IEnumerable<T> source)
{
List<T> src = source.ToList();
List<T> tgt = new List<T>(src.Count / 2);
foreach(var item in source)
src.Add(item);
bool isRemoved = false;
while(src.Count > 1)
{
foreach(T item in src)
{
if(!isRemoved)
tgt.Add(item);
isRemoved = !isRemoved;
}
List<T> temp = src;
src = tgt;
tgt = temp;
tgt.Clear();
}
return src.FirstOrDefault();
}
//Пример использования
Console.WriteLine(RemoveEachSecondItem<int>(Enumerable.Range(1, 7), false));
Достоинства:
List<T>
.Недостатки:
src
)src
со всеми вытекающими накладными расходами.Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Делаю приложение сканирования штрихкодовИспользую Xamarin Forms Проблема с включением фонарика при сканировании
Имеется массив кнопок btnИмеется изображение, которое хранится в ресурсах Properties
На выходе выводит строку, а должно массив, где 1 строка - 1 элемент Текст в файл писал вручнуюФайл, можно сказать использую вместо базы данных,...