Бинарное дерево — двоичное дерево поиска. Основные операции с бинарными деревьями (C#, Java)

Что такое бинарное дерево

Бинарное дерево представляет собой иерархическую структуру данных, в которой каждый узел имеет не более двух дочерних узлов. Как правило, первый называется родительским узлом или корнем дерева (root), а дочерние узлы называются левым и правым наследниками.

Бинарное дерево либо является пустым, либо состоит из данных и двух поддеревьев, каждое из которых может быть пустым. Каждое поддерево в свою очередь тоже является деревом. Узлы без наследников принято называть листьями.

Для такого дерева должны выполняться следующие условия:

  1. Левое и правое поддерево так же являются бинарными деревьями;
  2. У всех узлов левого поддерева произвольного узла x значения ключей данных меньше значения ключа данных самого узла x;
  3. У всех узлов правого поддерева произвольного узла x значения ключей данных больше либо равны значению ключа данных самого узла x.

Основные операции с бинарным деревом

Основными операциями с бинарными деревьями являются добавление элемента в дерево, удаление элемента и поиск элемента в дереве. Сложность каждой из этих операций O(log\,n) в лучшем случае, и O(n) в худшем. Зависит от сбалансированности дерева.

Пример сбалансированного бинарного дерева (лучший случай):

Пример несбалансированного бинарного дерева (худший случай):Добавление элемента в дерево

При добавлении элемента x в дерево проверяем значение текущего узла.

  • Если значение добавляемого элемента x меньше значения текущего узла, спускаемся к левому поддереву. Если его не существует, то создаем его и присваиваем значение x. Если существует, то обозначим левое поддерево как текущий узел и повторим сначала.
  • Если значение добавляемого элемента x больше или равно значению текущего узла, спускаемся к правому поддереву. Если его не существует, то создаем его и присваиваем значение x. Если существует, то обозначим правое поддерево как текущий узел и повторим сначала.

Пример добавления элемента в двоичное дерево

Создадим бинарное дерево с корневым элементом 33 и добавим в него элементы в следующей последовательности: 5, 35, 1, 20, 99, 17, 18, 19, 31, 4. Получим бинарное дерево такого вида:

BinaryTree

Поиск элемента в бинарном дереве

Поиск начинаем с родительского элемента. Допустим, мы ищем значение 18 (обозначим его за x). Алгоритм поиска будет иметь следующий вид:

  1. x<33 — спускаемся в левое поддерево;
  2. x>5 — спускаемся в правое поддерево;
  3. x<20 — спускаемся в левое поддерево;
  4. x>17 — спускаемся в правое поддерево;
  5. x=18 — мы нашли элемент.

Добавление элемента в бинарное дерево

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

Удаление элемента из бинарного дерева

Удаление листьев

Если удаляемый элемент является листом, то просто удаляем у его родителя ссылку на этот элемент (например на значение 31). Удалим его.

Удаление узла, имеющего левое поддерево, но не имеющее правого поддерева

После удаления 31 элементом, имеющим левое поддерево, но не имеющим правого поддерева является элемент 20. Удалим его из дерева:

  1. Указываем, что родителем элемента 17 теперь будет элемент 5.
  2. Указываем, что правым потомком элемента 5 теперь является элемент 17.

После удаления значений 31 и 20 дерево приобретает такой вид:

Бинарное дерево на C#

Удаление узла, имеющего правое поддерево, но не имеющее левого поддерева

  1. Удалим элемент 17. Присвоим его правому поддереву в качестве родителя элемент 5.
  2. Элементу 5 укажем, что его правым поддеревом теперь является элемент 18.

Получим следующую картину:Удаление элемента из бинарного дерева

Удаляем узел, имеющий поддеревья с обеих сторон

Первый случай

Правое поддерево не имеет потомка.

Чтобы иметь возможность рассмотреть этот случай, добавим элемент 34 в дерево:BinaryTreeУдалим элемент 35. Для этого:

  1. Правому поддереву (99) присвоим в качестве родителя элемент 33;
  2. Ему же в качестве левого поддерева присваиваем элемент 34;
  3. Элементу 34 указываем нового родителя — 99;
  4. Родителю удаляемого элемента (33) указываем, что его правым поддерево теперь является элемент 99.

Получим такое дерево:Бинарное дерево

Второй случай

Правое поддерево имеет своих потомков.

Удаляем элемент 5. Первым потомком (одновременно самым левым — минимальным в его поддереве) элемента 5 является элемент 18:

  1. Элементу 18 в качестве левого узла присвоим элемент 1;
  2. Элементу 1 присвоим 18 как родителя;
  3. Элементу 33 (родителю удаляемого элемента) укажем в качестве левого дочернего узла элемент 18;
  4. Элементу 18 указываем в качестве родителя элемент 33 (родитель удаляемого элемента).

Дерево приобретает такой вид:

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

В своем коде я использовал нерекурсивный механизм удаления.

Существуют и другие механизмы удаления. Визуализировать свое дерево вы можете на ресурсе usfca.edu. Вы заметите, что алгоритм удаления там отличается от описанного выше.

Код класса дерева на Java в моем исполнении имеет следующий вид:

package main;

import java.util.ArrayList;
import java.util.List;

public class Tree<T extends Comparable<T>> {
    private T val;
    private Tree left;
    private Tree right;
    private Tree parent;
    private List<T> listForPrint = new ArrayList<>();

    public T val() {
        return val;
    }

    public Tree left() {
        return left;
    }

    public Tree right() {
        return right;
    }

    public Tree parent() {
        return parent;
    }

    public Tree(T val, Tree parent) {
        this.val = val;
        this.parent = parent;
    }


    public void add(T...vals){
        for(T v : vals){
            add(v);
        }
    }

    public void add(T val){
        if(val.compareTo(this.val) < 0){
            if(this.left==null){
                this.left = new Tree(val, this);
            }
            else if(this.left != null)
                this.left.add(val);
        }
        else{
            if(this.right==null){
                this.right = new Tree(val, this);
            }
            else if(this.right != null)
                this.right.add(val);
        }
    }

    private Tree<T> _search(Tree<T> tree, T val){
        if(tree == null) return null;
        switch (val.compareTo(tree.val)){
            case 1: return _search(tree.right, val);
            case -1: return _search(tree.left, val);
            case 0: return tree;
            default: return null;
        }
    }

    public Tree<T> search(T val){
        return _search(this, val);
    }

    public boolean remove(T val){
        //Проверяем, существует ли данный узел
        Tree<T> tree = search(val);
        if(tree == null){
            //Если узла не существует, вернем false
            return false;
        }
        Tree<T> curTree;

        //Если удаляем корень
        if(tree == this){
            if(tree.right!=null) {
                curTree = tree.right;
            }
            else curTree = tree.left;

            while (curTree.left != null) {
                curTree = curTree.left;
            }
            T temp = curTree.val;
            this.remove(temp);
            tree.val = temp;

            return true;
        }

        //Удаление листьев
        if(tree.left==null && tree.right==null && tree.parent != null){
            if(tree == tree.parent.left)
                tree.parent.left = null;
            else {
                tree.parent.right = null;
            }
            return true;
        }

        //Удаление узла, имеющего левое поддерево, но не имеющее правого поддерева
        if(tree.left != null && tree.right == null){
            //Меняем родителя
            tree.left.parent = tree.parent;
            if(tree == tree.parent.left){
                tree.parent.left = tree.left;
            }
            else if(tree == tree.parent.right){
                tree.parent.right = tree.left;
            }
            return true;
        }

        //Удаление узла, имеющего правое поддерево, но не имеющее левого поддерева
        if(tree.left == null && tree.right != null){
            //Меняем родителя
            tree.right.parent = tree.parent;
            if(tree == tree.parent.left){
                tree.parent.left = tree.right;
            }
            else if(tree == tree.parent.right){
                tree.parent.right = tree.right;
            }
            return true;
        }

        //Удаляем узел, имеющий поддеревья с обеих сторон
        if(tree.right!=null && tree.left!=null) {
            curTree = tree.right;

            while (curTree.left != null) {
                curTree = curTree.left;
            }

            //Если самый левый элемент является первым потомком
            if(curTree.parent == tree) {
                curTree.left = tree.left;
                tree.left.parent = curTree;
                curTree.parent = tree.parent;
                if (tree == tree.parent.left) {
                    tree.parent.left = curTree;
                } else if (tree == tree.parent.right) {
                    tree.parent.right = curTree;
                }
                return true;
            }
            //Если самый левый элемент НЕ является первым потомком
            else {
                if (curTree.right != null) {
                    curTree.right.parent = curTree.parent;
                }
                curTree.parent.left = curTree.right;
                curTree.right = tree.right;
                curTree.left = tree.left;
                tree.left.parent = curTree;
                tree.right.parent = curTree;
                curTree.parent = tree.parent;
                if (tree == tree.parent.left) {
                    tree.parent.left = curTree;
                } else if (tree == tree.parent.right) {
                    tree.parent.right = curTree;
                }

                return true;
            }
        }
        return false;
    }


    private void _print(Tree<T> node){
        if(node == null) return;
        _print(node.left);
        listForPrint.add(node.val);
        System.out.print(node + " ");
        if(node.right!=null)
            _print(node.right);
    }

    public void print(){
        listForPrint.clear();
        _print(this);
        System.out.println();
    }

    @Override
    public String toString() {
        return val.toString();
    }
}

Поработать с классом можно следующим образом:

public static void main(String[] args) {
  //Создадим дерево с корневым элементом 33
  Tree<Integer> tree = new Tree<>(33, null);
  tree.add(5, 35, 1, 20, 4, 17, 31, 99, 18, 19);
  //Распечатаем элементы дерева
  tree.print();
  //Удалим корень
  tree.remove(33);
  tree.remove(17);

  tree.print();

  //Проверяем элементы дерева
  System.out.println(tree);
  System.out.println(tree.left());
  System.out.println(tree.left().left());
  System.out.println(tree.right().left());
}

Получим такой вывод:

Java Binary Tree Class Output

К слову, на Java такой код особого смысла писать нет, т.к. там существуют классы TreeSet и TreeMap, представляющие собой деревья.

На C# код класса бинарного дерева может иметь такой вид:

/*
 * User: Николай Разиов
 * Date: 29.10.2018
 */
using System;
using System.Collections.Generic;

namespace Trees
{
  public class BinaryTree<T> where T : IComparable<T>
  {
    private BinaryTree<T> parent, left, right;
    private T val;
    private List<T> listForPrint = new List<T>();
    
    public BinaryTree(T val, BinaryTree<T> parent)
    {
      this.val = val;
      this.parent = parent;
    }
    
    public void add(T val)
    {
          if(val.CompareTo(this.val) < 0){
              if(this.left==null){
                  this.left = new BinaryTree<T>(val, this);
              }
              else if(this.left != null)
                  this.left.add(val);
          }
          else{
              if(this.right==null){
                  this.right = new BinaryTree<T>(val, this);
              }
              else if(this.right != null)
                  this.right.add(val);
          }
    }
    
    private BinaryTree<T> _search(BinaryTree<T> tree, T val)
    {
          if(tree == null) return null;
          switch (val.CompareTo(tree.val)){
              case 1: return _search(tree.right, val);
              case -1: return _search(tree.left, val);
              case 0: return tree;
              default: return null;
          }
    	}
    
    public BinaryTree<T> search(T val)
    {
        	return _search(this, val);
    	}
    
    public bool remove(T val)
    {
          //Проверяем, существует ли данный узел
          BinaryTree<T> tree = search(val);
          if(tree == null){
              //Если узла не существует, вернем false
              return false;
          }
          BinaryTree<T> curTree;
  
          //Если удаляем корень
          if(tree == this){
              if(tree.right!=null) {
                  curTree = tree.right;
              }
              else curTree = tree.left;
  
              while (curTree.left != null) {
                  curTree = curTree.left;
              }
              T temp = curTree.val;
              this.remove(temp);
              tree.val = temp;
  
              return true;
          }
  
          //Удаление листьев
          if(tree.left==null && tree.right==null && tree.parent != null){
              if(tree == tree.parent.left)
                  tree.parent.left = null;
              else {
                  tree.parent.right = null;
              }
              return true;
          }
  
          //Удаление узла, имеющего левое поддерево, но не имеющее правого поддерева
          if(tree.left != null && tree.right == null){
              //Меняем родителя
              tree.left.parent = tree.parent;
              if(tree == tree.parent.left){
                  tree.parent.left = tree.left;
              }
              else if(tree == tree.parent.right){
                  tree.parent.right = tree.left;
              }
              return true;
          }
  
          //Удаление узла, имеющего правое поддерево, но не имеющее левого поддерева
          if(tree.left == null && tree.right != null){
              //Меняем родителя
              tree.right.parent = tree.parent;
              if(tree == tree.parent.left){
                  tree.parent.left = tree.right;
              }
              else if(tree == tree.parent.right){
                  tree.parent.right = tree.right;
              }
              return true;
          }
  
          //Удаляем узел, имеющий поддеревья с обеих сторон
          if(tree.right!=null && tree.left!=null) {
              curTree = tree.right;
  
              while (curTree.left != null) {
                  curTree = curTree.left;
              }
  
              //Если самый левый элемент является первым потомком
              if(curTree.parent == tree) {
                  curTree.left = tree.left;
                  tree.left.parent = curTree;
                  curTree.parent = tree.parent;
                  if (tree == tree.parent.left) {
                      tree.parent.left = curTree;
                  } else if (tree == tree.parent.right) {
                      tree.parent.right = curTree;
                  }
                  return true;
              }
              //Если самый левый элемент НЕ является первым потомком
              else {
                  if (curTree.right != null) {
                      curTree.right.parent = curTree.parent;
                  }
                  curTree.parent.left = curTree.right;
                  curTree.right = tree.right;
                  curTree.left = tree.left;
                  tree.left.parent = curTree;
                  tree.right.parent = curTree;
                  curTree.parent = tree.parent;
                  if (tree == tree.parent.left) {
                      tree.parent.left = curTree;
                  } else if (tree == tree.parent.right) {
                      tree.parent.right = curTree;
                  }
  
                  return true;
              }
          }
          return false;
      }
    
      private void _print(BinaryTree<T> node)
      {
          if(node == null) return;
          _print(node.left);
          listForPrint.Add(node.val);
          Console.Write(node + " ");
          if(node.right!=null)
              _print(node.right);
    	}

      public void print()
      {
          listForPrint.Clear();
          _print(this);
          Console.WriteLine();
      }
      
      public override string ToString()
    {
      	return val.ToString();
    }

  }
}

Код примерно такой же, только для C# он будет гораздо полезнее.

Исходники проектов можно взять здесь:

Github Java Binary Tree Github C# csharp Binary Tree

Шифрование данных с помощью алгоритма AES-256 (C#)

В текущей статье напишу два метода для шифрования потока байт с помощью алгоритма AES-256, и расшифровки обратно в массив байт.

Что такое AES-256

AES-256 представляет собой алгоритм шифрования с симметричным ключом. Это означает, что для шифрования и дешифрирования используется один и тот же криптографический ключ.

Symmetric key, симметричный ключ

Алгоритм может пригодиться где угодно — от шифрования файлов до защиты данных, передаваемых по открытым каналам.

Реализация алгоритма на C#

Для реализации алгоритма нужно подключить библиотеку System.Security.Cryptography и указать пространство имен:

using System.Security.Cryptography;

Далее напишем метод для шифрования данных:

/// <summary>
/// Шифрует исходное сообщение AES ключом (добавляет соль)
/// </summary>
/// <param name="src"></param>
/// <returns></returns>
public static byte[] ToAes256(string src)
{
  //Объявляем объект класса AES
  Aes aes = Aes.Create();
  //Генерируем соль
  aes.GenerateIV();
  //Присваиваем ключ. aeskey - переменная (массив байт), сгенерированная методом GenerateKey() класса AES
  aes.Key = aeskey;
  byte[] encrypted;
  ICryptoTransform crypt = aes.CreateEncryptor(aes.Key, aes.IV);
  using (MemoryStream ms = new MemoryStream())
  {
    using (CryptoStream cs = new CryptoStream(ms, crypt, CryptoStreamMode.Write))
    {
      using (StreamWriter sw = new StreamWriter(cs))
      {
        sw.Write(src);
      }
    }
    //Записываем в переменную encrypted зашиврованный поток байтов
    encrypted = ms.ToArray();
  }
  //Возвращаем поток байт + крепим соль
  return encrypted.Concat(aes.IV).ToArray();
}

Метод, для дешифрирования потока байтов:

/// <summary>
/// Расшифровывает криптованного сообщения
/// </summary>
/// <param name="shifr">Шифротекст в байтах</param>
/// <returns>Возвращает исходную строку</returns>
public static string FromAes256(byte[] shifr)
{
  byte[] bytesIv = new byte[16];
  byte[] mess = new byte[shifr.Length - 16];

  //Списываем соль
  for (int i = shifr.Length - 16, j = 0; i < shifr.Length; i++, j++)
    bytesIv[j] = shifr[i];

  //Списываем оставшуюся часть сообщения
  for (int i = 0; i < shifr.Length - 16; i++)
    mess[i] = shifr[i];

  //Объект класса Aes
  Aes aes = Aes.Create();
  //Задаем тот же ключ, что и для шифрования
  aes.Key = aeskey;
  //Задаем соль
  aes.IV = bytesIv;
  //Строковая переменная для результата
  string text = "";
  byte[] data = mess;
  ICryptoTransform crypt = aes.CreateDecryptor(aes.Key, aes.IV);

  using (MemoryStream ms = new MemoryStream(data))
  {
    using (CryptoStream cs = new CryptoStream(ms, crypt, CryptoStreamMode.Read))
    {
      using (StreamReader sr = new StreamReader(cs))
      {
        //Результат записываем в переменную text в вие исходной строки
        text = sr.ReadToEnd();
      }
    }
  }
  return text;
}

Зашифрованное сообщение можно передавать в открытый канал. При перехвате злоумышленники обнаружат лишь шифр, с которым мало что смогут сделать.

Стоит отметить, что сам ключ стоит передавать по закрытому каналу либо используя алгоритмы шифрования с ассиметричным ключом. Например RSA.

Ключ AES шифруется открытым ключом RSA, передается в открытый канал, далее вторая сторона расшифровывает ключ AES закрытым ключом RSA.

Во время передачи шифра в открытый канал может произойти MITM атака — попытка подмены сообщения и т.п. злоумышленником.

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

Данным методом можно воспользоваться, чтобы зашифровать данные другими алгоритмами с симметричным ключом, например TripleDes.

Как прочитать Excel с помощью OleDB (C#)

В этой статье опишу как прочитать Excel с помощью OleDB.

Иногда бывает нужно вытянуть таблицу из Excel документа, записать в DataTable для последующей обработки.

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

oledb C#

Определение строки подключения

Для разных версий Excel будут свои строки подключения.

Строка подключения для Excel 2007 и более новых версий

//Можно использовать, если количество строк менее 65536
Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties='Excel 8.0;HDR=Yes'

//Если строк больше 65536
Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0}; Extended Properties="Excel 12.0 Xml;HDR=YES";

Для работы с данными версиями необходимо установить Microsoft Access Database Engine 2010 Redistributable.

Так же C# может выбрасывать исключения по поводу недостающих драйверов. В этом случае необходимо скачать соответствующие драйверы с сайта Microsoft.

Строка подключения для более ранних версий

Строка подключения для Excel версии 2003 может иметь такой вид:

Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Extended Properties='Excel 8.0;HDR=Yes'

Если C# выбросит исключение, скачайте недостающий драйвер, Visual Studio подскажет, какой.

Как сделать SQL запрос из таблицы Excel

Для выполнения SQL запроса нужно найти таблицу в документе и выполнить к ней запрос:

//Объявляем OleDB соединение
using(OleDbConnection conn = new OleDbConnection(conStr))
{
  //Открываем подключение
  conn.Open();
  //Запрашиваем таблицы
  DataTable schemaTable = conn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, new object[] { null, null, null, "TABLE" });
  DataRow schemaRow = schemaTable.Rows[0];
  //Получаеи имя таблицы
  string sheet = schemaRow["TABLE_NAME"].ToString();
  //Объявляем команду
  OleDbCommand com = conn.CreateCommand();
  //Создаем SQL запрос
  com.CommandText = "SELECT * FROM [" + sheet + "]";
  //Выполняем SQL запрос
  OleDbDataReader reader = com.ExecuteReader();
  //Записываем результат в DataTable
  DataTable table = new DataTable();
  table.Load(reader);
  //Выводим DataTable в таблицу на форму (если нужно)
  gridControl1.DataSource = table;
}

На этом шаге мы имеем считанные данные из Excel документа в DataTable.

После обработки данных можно сохранить данные в другой документ или оформить сводную таблицу.

Как работать с Excel с помощью C# обсуждалось ранее.

Так же можете посмотреть, как работать со сводными таблицами в Excel с помощью C# и редактора VBA, встроенного в Excel.

Создаем сводные таблицы на C# в Excel

Сегодня программируем сводные таблицы на C#. Для этого надо будет иметь заготовленную таблицу с данными (обычную).

Заготовленная таблица будет иметь данные о телефонных звонках.

Пример сводной таблицы:

Сводные таблицы на C#

Открываем документ и создаем диапазоны для сводной таблицы

Откроем Excel документ с таблицей:

ex.Workbooks.Open("Excel.xlsx",
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing);

Далее создаем диапазон из одной ячейки, в которой указываем, куда будет помещаться сводная таблица:

Excel.Range pivotDestination = sheetPivot.get_Range("A1", "A1");

Объявляем диапазон сводной таблицы:

Excel.Range all = sheetPivot.get_Range("A1", "D1000");

Пишем код создания сводной таблицы

Код для создания сводной таблицы из обычной таблицы с данными имеет такой вид:

workBook.PivotTableWizard(
  Excel.XlPivotTableSourceType.xlDatabase,
all,                        //Таблица, на основе которой строим сводную
pivotDestination,              //Где сохранять
"NumberA",                        //Название таблицы
true,
true,
true,
true,
Type.Missing,
Type.Missing,
false,
false,
Excel.XlOrder.xlDownThenOver,
0,
Type.Missing,
Type.Missing
);

Распределяем поля сводной таблицы

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

Для этого создаем экземпляры полей на основе заголовков столбцов таблицы:

//Создаем поля таблицы
Excel.PivotTable pivotTable = (Excel.PivotTable)sheetPivot.PivotTables("Number");
Excel.PivotField Y = ((Excel.PivotField)pivotTable.PivotFields("Y"));
Excel.PivotField M = ((Excel.PivotField)pivotTable.PivotFields("M"));
Excel.PivotField Src_country = ((Excel.PivotField)pivotTable.PivotFields("Country"));
Excel.PivotField Operator_Name = ((Excel.PivotField)pivotTable.PivotFields("Operator"));
Excel.PivotField Minutes = ((Excel.PivotField)pivotTable.PivotFields("Minutes"));

Распределяем поля:

//Расставляем строки, столбцы и данные
Y.Orientation = Excel.XlPivotFieldOrientation.xlColumnField;
M.Orientation = Excel.XlPivotFieldOrientation.xlColumnField;
Src_country.Orientation = Excel.XlPivotFieldOrientation.xlRowField;
Operator_Name.Orientation = Excel.XlPivotFieldOrientation.xlRowField;
Minutes.Orientation = Excel.XlPivotFieldOrientation.xlDataField;
//Функция, применяемая к данным - есть еще количество, MAX, MIN и т.д.
Minutes.Function = Excel.XlConsolidationFunction.xlSum;

Создаем сводную таблицу без исходной таблицы с данными

Описываю, как создать сводную таблицу на основе SQL-запроса к БД.

Для этого понадобится библиотека ADODB (adodb.dll можно скачать в интернете).

Подключим adodb.dll к проекту:

using ADODB;

Объявим строку подключения (драйвер SQLOLEDB.1):

Provider=SQLOLEDB.1;Password=123456;Persist Security Info=True;User ID=sqlserver;Initial Catalog=DBName;Data Source=serverNameOrIp

Далее пишем код для работы со сводной таблицей:

//Создаем подключение и открываем его
ADODB.Connection sql = new Connection();
sql.CursorLocation = ADODB.CursorLocationEnum.adUseClient;
sql.ConnectionString = conString;
sql.Open();

//Объявляем команду. Здесь функция getQuery() возвращает SQL-запрос
ADODB.Command cmd = new Command();
cmd.CommandType = CommandTypeEnum.adCmdText;
cmd.CommandText = getQuery();
cmd.CommandTimeout = 5000;
cmd.ActiveConnection = sql;

//Объявляем Recordset и выполняем запрос, сохраняя в него данные
ADODB.Recordset rs = new Recordset();
rs.Open(cmd, Type.Missing, ADODB.CursorTypeEnum.adOpenForwardOnly, ADODB.LockTypeEnum.adLockReadOnly, 1);
//тут хранится результат запроса

Excel.PivotCache pc = wb.PivotCaches().Add(
Excel.XlPivotTableSourceType.xlExternal
, Type.Missing);

pc.Recordset = rs;
//Ссылка на все сводные таблицы на листе
Excel.PivotTables pts = (Excel.PivotTables)sheet.GetType().InvokeMember("PivotTables", System.Reflection.BindingFlags.GetProperty, null, sheet, null);
//Создаем новую сводную таблицу
Excel.PivotTable pt = pts.Add(pc, sheet.get_Range("A3", Type.Missing), "NumA", Type.Missing, Excel.XlPivotTableVersionList.xlPivotTableVersionCurrent);

//Распределяю поля и настраиваю форматы полей
((Excel.PivotField)pt.PivotFields("Year")).Orientation = Excel.XlPivotFieldOrientation.xlColumnField;
((Excel.PivotField)pt.PivotFields("Month")).Orientation = Excel.XlPivotFieldOrientation.xlColumnField;
((Excel.PivotField)pt.PivotFields("Country")).Orientation = Excel.XlPivotFieldOrientation.xlRowField;
((Excel.PivotField)pt.PivotFields("Operator")).Orientation = Excel.XlPivotFieldOrientation.xlRowField;
((Excel.PivotField)pt.PivotFields("Minutes")).Orientation = Excel.XlPivotFieldOrientation.xlDataField;
((Excel.PivotField)pt.PivotFields("Сумма по полю Minutes")).Function = Excel.XlConsolidationFunction.xlSum;
((Excel.PivotField)pt.PivotFields("Сумма по полю Minutes")).NumberFormat = "# ##0";
((Excel.PivotField)pt.PivotFields("Operator")).ShowDetail=false;
((Excel.PivotField)pt.PivotFields("Country")).ShowDetail = false;

pt.TableStyle2 = "PivotStyleMedium13";     //Стиль сводной таблицы. Взял через VBA

При работе со сводными таблицами так же окажет помощь встроенный в Excel редактор VBA, о котором речь шла в теме Работа с Excel с помощью C# (Microsoft.Office.Interop.Excel).

Работа с Excel с помощью C# (Microsoft.Office.Interop.Excel)

Оставляю заметку по работе с Excel с помощью C#.

Привожу фрагменты кода, которые искал когда-то сам для работы с Excel документами.

Наработки очень пригодились в работе для формирования отчетности.

Прежде всего нужно подключить библиотеку Microsoft.Office.Interop.Excel.

Подключение Microsoft.Office.Interop.Excel
Visual Studio здесь довольно старой версии. Если у вас версия новая, отличаться будет только вид окна.

Далее создаем псевдоним для работы с Excel:

using Excel = Microsoft.Office.Interop.Excel;

//Объявляем приложение
Excel.Application ex = new Microsoft.Office.Interop.Excel.Application();

//Отобразить Excel
ex.Visible = true;

//Количество листов в рабочей книге
ex.SheetsInNewWorkbook = 2;

//Добавить рабочую книгу
Excel.Workbook workBook = ex.Workbooks.Add(Type.Missing);

//Отключить отображение окон с сообщениями
ex.DisplayAlerts = false;                                       

//Получаем первый лист документа (счет начинается с 1)
Excel.Worksheet sheet = (Excel.Worksheet)ex.Worksheets.get_Item(1);

//Название листа (вкладки снизу)
sheet.Name = "Отчет за 13.12.2017";

//Пример заполнения ячеек
for (int i = 1; i <= 9; i++)
{
  for (int j = 1; j < 9; j++)
  sheet.Cells[i, j] = String.Format("Boom {0} {1}", i, j);
}

//Захватываем диапазон ячеек
Excel.Range range1 = sheet.get_Range(sheet.Cells[1, 1], sheet.Cells[9, 9]);

//Шрифт для диапазона
range1.Cells.Font.Name = "Tahoma";
//Размер шрифта для диапазона
range1.Cells.Font.Size = 10;

//Захватываем другой диапазон ячеек
Excel.Range range2 = sheet.get_Range(sheet.Cells[1, 1], sheet.Cells[9, 2]);
range2.Cells.Font.Name = "Times New Roman";

//Задаем цвет этого диапазона. Необходимо подключить System.Drawing
range2.Cells.Font.Color = ColorTranslator.ToOle(Color.Green);
//Фоновый цвет
range2.Interior.Color = ColorTranslator.ToOle(Color.FromArgb(0xFF, 0xFF, 0xCC));

Расстановка рамок.

Расставляем рамки со всех сторон:

range2.Borders.get_Item(Excel.XlBordersIndex.xlEdgeBottom).LineStyle = Excel.XlLineStyle.xlContinuous;
range2.Borders.get_Item(Excel.XlBordersIndex.xlEdgeRight).LineStyle = Excel.XlLineStyle.xlContinuous;
range2.Borders.get_Item(Excel.XlBordersIndex.xlInsideHorizontal).LineStyle = Excel.XlLineStyle.xlContinuous;
range2.Borders.get_Item(Excel.XlBordersIndex.xlInsideVertical).LineStyle = Excel.XlLineStyle.xlContinuous;
range2.Borders.get_Item(Excel.XlBordersIndex.xlEdgeTop).LineStyle = Excel.XlLineStyle.xlContinuous;

Цвет рамки можно установить так:

range2.Borders.Color = ColorTranslator.ToOle(Color.Red);

Выравнивания в диапазоне задаются так:

rangeDate.VerticalAlignment = Excel.XlVAlign.xlVAlignCenter;
rangeDate.HorizontalAlignment = Excel.XlHAlign.xlHAlignLeft;

Формулы

Определим задачу: получить сумму диапазона ячеек A4:A10.

Для начала снова получим диапазон ячеек:

Excel.Range formulaRange = sheet.get_Range(sheet.Cells[4, 1], sheet.Cells[9, 1]);

Далее получим диапазон вида A4:A10 по адресу ячейки ( [4,1]; [9;1] ) описанному выше:

string adder = formulaRange.get_Address(1, 1, Excel.XlReferenceStyle.xlA1, Type.Missing, Type.Missing);

Теперь в переменной adder у нас хранится строковое значение диапазона ( [4,1]; [9;1] ), то есть A4:A10.

Вычисляем формулу:

//Одна ячейка как диапазон
Excel.Range r = sheet.Cells[10, 1] as Excel.Range;
//Оформления
r.Font.Name = "Times New Roman";
r.Font.Bold = true;
r.Font.Color = ColorTranslator.ToOle(Color.Blue);
//Задаем формулу суммы
r.Formula = String.Format("=СУММ({0}", adder);

Выделение ячейки или диапазона ячеек

Так же можно выделить ячейку или диапазон, как если бы мы выделили их мышкой:

sheet.get_Range("J3", "J8").Activate();
//или
sheet.get_Range("J3", "J8").Select();
//Можно вписать одну и ту же ячейку, тогда будет выделена одна ячейка.
sheet.get_Range("J3", "J3").Activate();
sheet.get_Range("J3", "J3").Select();

Авто ширина и авто высота

Чтобы настроить авто ширину и высоту для диапазона, используем такие команды:

range.EntireColumn.AutoFit(); 
range.EntireRow.AutoFit();

Получаем значения из ячеек

Чтобы получить значение из ячейки, используем такой код:

//Получение одной ячейки как ранга
Excel.Range forYach = sheet.Cells[ob + 1, 1] as Excel.Range;
//Получаем значение из ячейки и преобразуем в строку
string yach = forYach.Value2.ToString();

Добавляем лист в рабочую книгу

Чтобы добавить лист и дать ему заголовок, используем следующее:

var sh = workBook.Sheets;
Excel.Worksheet sheetPivot = (Excel.Worksheet)sh.Add(Type.Missing, sh[1], Type.Missing, Type.Missing);
sheetPivot.Name = "Сводная таблица";

Добавление разрыва страницы

//Ячейка, с которой будет разрыв
Excel.Range razr = sheet.Cells[n, m] as Excel.Range;
//Добавить горизонтальный разрыв (sheet - текущий лист)
sheet.HPageBreaks.Add(razr); 
//VPageBreaks - Добавить вертикальный разрыв

Сохраняем документ

ex.Application.ActiveWorkbook.SaveAs("doc.xlsx", Type.Missing,
  Type.Missing, Type.Missing, Type.Missing, Type.Missing, Excel.XlSaveAsAccessMode.xlNoChange,
  Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);

Как открыть существующий документ Excel

ex.Workbooks.Open(@"C:\Users\Myuser\Documents\Excel.xlsx",
  Type.Missing, Type.Missing, Type.Missing, Type.Missing,
  Type.Missing, Type.Missing, Type.Missing, Type.Missing,
  Type.Missing, Type.Missing, Type.Missing, Type.Missing,
  Type.Missing, Type.Missing);

Комментарии

При работе с Excel с помощью C# большую помощь может оказать редактор Visual Basic, встроенный в Excel.

Для этого в настройках ленты надо добавить пункт «Разработчик». Далее начинаем запись макроса, производим действия и останавливаем запись.

Далее заходим в редактор Visual Basic и смотрим код, который туда записался:

Vusial Basic (VBA)

Например:

Sub Макрос1()
'
' Макрос1 Макрос
'

'
    Range("E88").Select
    ActiveSheet.ListObjects.Add(xlSrcRange, Range("$A$1:$F$118"), , xlYes).Name = _
        "Таблица1"
    Range("A1:F118").Select
    ActiveSheet.ListObjects("Таблица1").TableStyle = "TableStyleLight9"
    Range("E18").Select
    ActiveWindow.SmallScroll Down:=84
End Sub

В данном макросе записаны все действия, которые мы выполнили во время его записи. Эти методы и свойства можно использовать в C# коде.

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

//Складываем значения предыдущих 12 ячеек слева
rang.Formula = "=СУММ(RC[-12]:RC[-1])";

Так же во время работы может возникнуть ошибка: метод завершен неверно. Это может означать, что не выбран лист, с которым идет работа.

Чтобы выбрать лист, выполните sheetData.Select(Type.Missing); где sheetData это нужный лист.

Пишем класс, описывающий рациональные дроби (C#)

Рациональные дроби

Рациональные дроби — это числа, представляемые обыкновенной дробью с числителем и знаменателем.

Рациональные дроби

В данной статье выкладываю код, который я написал для реализации класса рациональных дробей. Это было одно из заданий для получения сертификата курса проектирования на C#.

В представленном листинге ниже реализован данный класс:

Показать текст ->

В классе описаны методы выполнения арифметический операций с рациональными дробями:

  • Сложение и вычитание дробей через НОЗ (наименьший общий знаменатель);
  • Умножение и деление дробей;
  • Умножение дроби на число;
  • Преобразование числа к рациональной дроби (любое число это рациональная дробь, где взятое число делится на 1);
  • Преобразование рациональной дроби к десятичной;
  • Описаны функции нахождения НОЗ и сокращения рациональной дроби.

Перегрузка операторов и преобразования типов (C#)

Примеры перегрузки операторов

В данной рассматриваем примеры перегрузки операторов + и — в языке C#. Таким же образом можно перегрузить другие операторы, которые принимают два параметра.

Так же показываю как задать инструкцию явного преобразования типов из одного созданного класса в другой.

В коде ниже создано 2 класса: Vector и Coord. В классе Vector перегружены два бинарных оператора + и -.

В классе Coord объявлены только конструктор и два свойства X, Y, обозначающие координату.

С помощью инструкции public static explicit operator Coord(Vector v)  мы можем задать правило преобразования объекта класса Vector в объект класса Coord:

using System.IO;
using System;
using System.Text;

class Coord
{
  public int X {get;set;}
  public int Y {get;set;}
  
  public Coord(int x, int y)
  {
      this.X = x;
      this.Y = y;
  }
  
  public override string ToString()
  {
      return String.Format("Coord: X = {0}, Y = {1}", this.X, this.Y);
  }
}

class Vector
{
    //Объвляем автосвойства, чтобы не использовать public поля
  public int X {get;set;}
  public int Y {get;set;}

  public Vector(int x, int y)
  {
    this.X = x;
    this.Y = y;
  }

    //Оператор + принимает на вход 2 параметра
  public static Vector operator+(Vector v1, Vector v2)
  {
    return new Vector(v1.X + v2.X, v1.Y + v2.Y);
  }

    //Оператор - принимает на вход 2 параметра
  public static Vector operator-(Vector v1, Vector v2)
  {
    return new Vector(v1.X - v2.X, v1.Y - v2.Y);
  }

    //Переопределение метода ToString()
    public override string ToString()
    {
        return String.Format("Vector: X = {0}, Y = {1}", this.X, this.Y);
    }
    
    //Инструкция по явному преобразованию класса Vector в класс Coord
    //Вместо explicit можно написать implicit, тогда преобразование можно
    //будет делать неявным
    public static explicit operator Coord(Vector v)
    {
        return new Coord(v.X, v.Y);
    }
  
}

class Program
{
  static void Main()
  {
    Console.WriteLine("Start program.");
    Vector v1 = new Vector(10,20);
    Vector v2 = new Vector(15,50);
    Console.WriteLine(v1+v2);
    Console.WriteLine(v1-v2);
    //----------------------
    Coord c1 = new Coord(100,200);
    Console.WriteLine(c1); //Явное преобразование Vector в Coord
    Coord c2 = (Coord)(v1+v2);
    Console.WriteLine(c2);
  }
}

Результат работы программы:

Подключаемся к серверу MySQL через SSH для выполнения запросов (C#)

В данной записи показываю, как подключаться в MySQL через SSH с помощью C# и библиотеки Renci.SshNet
В среде Visual Studio (старше 2008) через систему управления пакетами Nuget добавляем библиотеку SshNet.
Добавляем библиотеку mysql.dll (Можно скачать connector на www.mysql.com).
Переходим к написанию кода. Объявляем SshClient:

private static SshClient client = new SshClient("120.20.20.20", "login_ssh", "password_ssh");

Настраиваем строку подключения к MySQL

Объявим строку подключения connBuilderIMSoverSSH и настроим ее:

private static MySqlConnectionStringBuilder connBuilderIMSoverSSH = new MySqlConnectionStringBuilder();

connBuilderIMSoverSSH.Server = "127.0.0.1";     //Текуший ПК
connBuilderIMSoverSSH.Port = 12140;               //Порт, который пробрасываем (Не обязательно такой. Можно пробросить любой свободный)
connBuilderIMSoverSSH.UserID = "login_mysql";
connBuilderIMSoverSSH.Password = "pass_mysql ";
connBuilderIMSoverSSH.Database = "Database_Name";

Пробрасываем порт для SSH

Проверяем, открыто ли подключение к SSH. Если не открыто, открываем:

if (!client.IsConnected)
{
  client.Connect();
  //Объявляем и инициализируем пробрасываемый порт
  ForwardedPortLocal port = new ForwardedPortLocal("127.0.0.1", 12140, "10.247.250.100", 3306);	//3306 - порт MySQL
  client.AddForwardedPort(port);
  //Открываем порт
  port.Start();
}

Выполняем запрос к MySQL через SSH

/// <summary>
/// Выбирает данные из новой системы через SSH
/// </summary>
/// <param name="query">Запрос</param>
/// <returns></returns>
public DataTable ExecuteSqlOverSSH(string query)
{
  DataTable table = new DataTable();
  //Открываем соединение с MySQL
  using (var sql = new MySqlConnection(connBuilderIMSoverSSH.ConnectionString))
  {
    sql.Open();
    var cmd = sql.CreateCommand();
    cmd.CommandTimeout = 600;
    cmd.CommandText = query;
    var rdr = cmd.ExecuteReader();
    table.Load(rdr);
  }

  return table;
}

В итоге мы получили функцию, которая возвращает результат запроса в БД MySQL через SSH в виде DataTable.